가오리의 코딩일기

Ep04. 블로그 웹 애플리케이션 개발(1) 본문

Python/flask

Ep04. 블로그 웹 애플리케이션 개발(1)

류경혜 2022. 7. 22. 15:00

🔗 파일위치 확인

 

💡 로그인을 위한 준비

🧩 POST 방식

→ 회원가입에서는 새로운 유저를 추가하고 로그인에서는 아이디와 비밀번호 정보가 맞는지 확인해서 맞으면 로그인 시켜준다.

→ 회원가입과 로그인 모두 폼에서 사용자의 데이터를 받아오는 과정이 있는데 폼으로부터 데이터를 POST 방식으로 받아와야 한다.

→ 그래서 login.html과 signup.html의 Main Content에 있는 <form id=”contactForm” method=”POST”>로 수정해준다.

→ 만약 두 곳 다 POST라면 생략하면 된다.

LOGIN을 누르면 POST IN 요청이 이루어진다
폼에 임의의 값을 작성한 후 LOGIN을 눌러 제출하면 응답 상태 코드로 200을 받는다

🔗 응답 상태 코드

더보기

 → 응답  코드는 5개의 클래스(분류)로 구분된다.

 → 1XX : 정보, 요청을 받았으며 프로세스를 계속한다

 → 2XX : 성공, 요청을 성공적으로 받았으며 인식했고 수용했다

 → 3XX : 리다이렉션, 요청 완료를 위해 추가 작업 조치가 필요하다

 → 4XX : 클라이언트 오류, 요청의 문법이 잘못되었거나 요청을 처리할 수 없다

 → 5XX : 서버 오류, 서버가 명백히 유효한 요청에 대해 충족을 실패했다

 

 

 

 

🧩 회원가입

 

 

 

 

🧩 ORM: Object Relational Mapping

→ 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑(연결)해주는 것

→ 데이터베이스의 데이터 ←매핑→ objcet 필드

→ 장점 : DBMS에 대한 종속성이 줄어들고 재사용 및 유지보수의 편리성이 증가한다

→ 단점 : 완벽한 ORM으로만 서비스를 구현하기 어렵다

 

 

 

 

🧩 flask-SQLAlchemy

→ 파이썬의 클래스와 데이터베이스를 매핑해줄 수 있도록 하는 파이썬 모듈

→ 클래스를 데이터베이스의 테이블로 만드는 작업을 대신 할 예정

더보기
# blog/__init__.py
def create_app():
    # from .models import User
    app = Flask(__name__) # Flask app 만들기
    app.config['SECRET_KEY'] = "IFP"

    app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DB_NAME}'
    db.init_app(app)

    from .views import views
    app.register_blueprint(views, url_prefix="/")
    from .auth import auth
    app.register_blueprint(auth, url_prefix='/auth')
		return app
# blog/__init__.py
def create_database(app):
    if not path.exists("blog/"+DB_NAME):
        db.create_all(app=app)

 

 

 

 

🧩 DB 생성하는 함수 호출하기

더보기
# blog/models.py
from  . import db
from flask_login import UserMixin 
from sqlalchemy.sql import func

class User(db.Model, UserMixin):
    id = db.Column(db.Integer,primary_key=True)
    email = db.Column(db.String(150), unique=True)
    username = db.Column(db.String(150), unique=True)
    password = db.Column(db.String(150))
    created_at = db.Column(db.DateTime(timezone=True), default=func.now())
# blog/__init__.py/create_app 밑에 추가
from .models import User
    create_database(app)

 

 

 

 

🧩 __init__.py

→ flask_login : 플라스크에서 로그인 기능을 쉽게 구현할 수 있도록 도와주는 라이브러리

더보기
# blog/__init__.py/create_app()에 추가
login_manager= LoginManager() # LoginManager() 객체를 만들어준다
login_manager.login_view = " auth.login"
login_manager.init_app(app)

@login_manager.user_loader
def load_user_by_id(id):
    return User.query.get(int(id))
return app

 

 

 

 

 

🧩 라이브러리 설치

→ pip install flask-wtf :  플라스크 프레임워크의 폼 검증 모듈, 쉽게 폼을 작성할 수 있으며 json 데이터 상호작용을 위한 검증 도구로도 사용된다

→ pip install email-validator : 이메일 유효성 검사 지원

더보기
# blog/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, PasswordField, EmailField
from wtforms.validators import DataRequired, Email, Length, EqualTo

class SignupForm(FlaskForm):
    email = EmailField('email', validators = [DataRequired(), Email()])
    username = StringField('username', validators=[DataRequired(), Length(4,30)])
    password1 = PasswordField('password', validators= [DataRequired(), Length(8,30), EqualTo("password2", message="Password must match...")])
    password2 = PasswordField('password again', validators=[DataRequired()])
# blog/auth.py
@auth.route("/sign-up", methods=['GET','POST'])
def signup():
    form = SignupForm()
    if request.method == "POST" and form.validate_on_submit():
        signup_user = User(
            email = form.email.data,
            username = form.username.data,
            password = generate_password_hash(form.password1.data),
        )
        email_exists = User.query.filter_by(email=form.email.data).first()
        username_exists = User.query.filter_by(username=form.username.data).first()
        if email_exists:
            flash('Email is already in use...', category='error')
        elif username_exists:
            flash('Username is already in use...', category='error')
        else:
            db.session.add(signup_user)
            db.session.commit()
            flash("User created!!!")
            return redirect(url_for("views.home"))
	return render_template("signup.html", form=form, user=current_user)
<!--login.html-->
<form id="contactForm" method="POST">
            {{ form.csrf_token}}
			{# 없으면 폼 작동 안 함 #}
            <div class="form-floating">

 

 

 

 

🧩 비밀번호 해싱하기(auth.py)

# blog/auth.py
from werkzeug.sercurity import generate_password_hash, check_password_hash

→ generate_password_hash : 문자열을 암호화된 해시로 바꿔준다

→ check_password_hash : 암호화된 해시와 문자열을 비교해 이 문자열이 동일한 해시를 갖는 경우 참을 반환

입력했던 비밀번호 값이 이상한 값으로 암호화됨

 

 

 

 

🧩 로그인

더보기
# blog/forms.py
class LoginForm(FlaskForm):
    email =EmailField('email', validators=[DataRequired(), Email()])
    password = PasswordField('password', validators = [DataRequired()])
# blog/auth.py
@auth.route("/login", methods=['GET','POST'])
def login():
    form = LoginForm()
    if request.method == "POST" and form.validate_on_submit():
        password = form.password.data
        user= User.query.filter_by(email=form.email.data).first()
        if user:
            if check_password_hash(user.password, password):
                flash("Logged in!", category ='success')
                login_user(user,remember=True)
                return redirect(url_for('views.home'))
            else:
                flash("Password is incorrect!", category='error')
        else:
            flash("Email does not exist...", category='error')
    return render_template("login.html", form=form, user=current_user)

 

 

 

 

🧩 로그아웃

더보기
# blog/auth.py
@auth.route("/logout")
def logout():
    logout_user()
    return redirect(url_for("views.home"))

 

 

 

 

🧩 오류메시지 나타내기

더보기
<!--base.html/Navigation 주석 바로 위에-->
{% with message = get_flashed_messages(with_categories=True) %} 
{% if messages %} 
{% for category, message in messages %} 
{# 카테고리 == error이라면, 실패 메시지를 출력 #} 
{% if category =="error" %}
<div class="alert alert-danger alert-dismissable fade show" role="alert" style="text-align: center">
  {{message}}
  <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{# 그렇지 않다면, 성공 메시지를 출력 #} 
{% else %}
<div class="alert alert-success alert-dismissable fade show" role="alert" style="text-align: center">
  {{message}}
  <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endif %} 
{% endfor %} 
{% endif %} 
{% endwith %}

 

 

 

 

🧩 동적으로 변화하는 navbar 만들기

더보기
# # blog/views.py
from flask import Blueprint, render_template
from flask_login import current_user
views = Blueprint("views", __name__)

@views.route("/")
@views.route("/home")
def home():
    return render_template("index.html" , user=current_user)

@views.route("/about")
def about():
    return render_template("about.html", user=current_user)

@views.route("/categories-list")
def categories_list():
    return render_template("categories_list.html", user=current_user)

@views.route("/post-list")
def post_list():
    return render_template("post_list.html", user=current_user)

@views.route('posts/<int:id>')
def post_detail():
    return render_template("post_detail.html", user=current_user)

@views.route("/contact")
def contact():
    return render_template("contact.html", user=current_user)
# blog/auth.py
import logging
from flask_login import login_user, logout_user, current_user, login_required
from . import db
from .forms import SignupForm, LoginForm
from .models import User
from flask import Blueprint, render_template, request, url_for, flash
from werkzeug.utils import redirect
from werkzeug.security import generate_password_hash, check_password_hash

auth = Blueprint("auth", __name__)

@auth.route("/login", methods=['GET','POST'])
def login():
    form = LoginForm()
    if request.method == "POST" and form.validate_on_submit():
        password = form.password.data
        user= User.query.filter_by(email=form.email.data).first()
        if user:
            if check_password_hash(user.password, password):
                flash("Logged in!", category ='success')
                login_user(user,remember=True)
                return redirect(url_for('views.home'))
            else:
                flash("Password is incorrect!", category='error')
        else:
            flash("Email does not exist...", category='error')
    return render_template("login.html", form=form, user=current_user)

@auth.route("/logout")
def logout():
    logout_user()
    return redirect(url_for("views.home"))

@auth.route("/sign-up", methods=['GET','POST'])
# 회원가입에서 POST 요청을 처리해야 함
def signup():
    form = SignupForm()
    if request.method == "POST" and form.validate_on_submit():
        # 폼으로부터 검증된 데이터 받아오기
        signup_user = User(
            email = form.email.data,
            username = form.username.data,
            password = generate_password_hash(form.password1.data),
        )
        # 폼에서 받아온 데이터가 데이터베이스에 이미 존재하는지 확인
        email_exists = User.query.filter_by(email=form.email.data).first()
        username_exists = User.query.filter_by(username=form.username.data).first()
        # 이메일 중복 검사
        if email_exists:
            flash('Email is already in use...', category='error')
        # 유저네임 중복 검사
        elif username_exists:
            flash('Username is already in use...', category='error')
        # 위의 모든 과정을 통과한다면, 폼에서 받아온 데이터를 새로운 유저로서 저장
        else:
            db.session.add(signup_user)
            db.session.commit()
            flash("User created!!!")
            return redirect(url_for("views.home"))
            # 저장이 완료된 후 home으로 리다이렉트
            # GET 요청을 보낸다면 회원가입 템플릿을 보여줌
    return render_template("signup.html", form=form, user=current_user)
<!--base.html Navigation 안의 뒤에서 4번째 즈음부터-->
{% if user.is_authenticated %}
<li class="nav-item">
  <a class="nav-link px-lg-3 py-3 py-lg-4" style="color: red" href="#">
  welcome, {{ user.username }}!</a>
</li>
<li class="nav-item">
  <a class="nav-link px-lg-3 py-3 py-lg-4" style="color: red" href="{{ url_for('auth.logout') }}">
  Logout</a>
</li>
{% else %}
<li class="nav-item">
  <a class="nav-link px-lg-3 py-3 py-lg-4" href="{{ url_for('auth.signup') }}">
  Sign Up</a>
</li>
<li class="nav-item">
  <a class="nav-link px-lg-3 py-3 py-lg-4" href="{{ url_for('auth.login') }}">
  Login</a>
</li>
{% endif %}