본문 바로가기
Python/FLASK

4장 - 블로그 만들기 회원가입/로그인/로그아웃

by sophia02 2022. 7. 24.

지난 정리의 회원가입과 로그인의 template 코드를 보면 둘 다 method 방식이 POST인 것을 확인할 수 있습니다.

오늘은 로그인과 로그아웃 회원가입에 대해 정리해 보려고 하기에 로그인과 관련된 내용을 적어둔 auth.py 파일에서 로그인과 회원가입 부분을 수정해 줄 겁니다.

 

from flask import Blueprint, render_template

views = Blueprint("views", __name__)

@views.route("/")
def blog_home():
    return render_template("index.html")

@views.route("/about")
def about_me():
    return render_template("about.html")

@views.route("/contact")
def contact():
    return render_template("contact.html")

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

method = ['GET', 'POST']를 써줌으로써 POST요청을 처리할 수 있도록 코드를 수정해주었습니다


👌request로 폼에서 데이터 받아오기

from flask import Blueprint, render_template, redirect, request

auth = Blueprint("auth", __name__)

@auth.route("/login",methods=['GET','Post'])

def login():
    return render_template("login.html")

@auth.route("/logout")
def logout():
    return redirect("views.blog_home")

@auth.route("/sign-up",methods=['GET','Post'])
def signup():
    email = request.form.get('email')
    print(email)
    
    username = request.form.get('username')
    print(username)
    
    password1 = request.form.get('password1')
    print(password1)

    password2 = request.form.get('password2')
    print(password2)
    
    return render_template("signup.html")

request를 import 해줍니다 또한 get() 메서드의 인자로 name 속성에 사용되었던 email, username 등으로 데이터를 받아와 줍니다.

정보를 입력해 준 뒤 sign up을 누르면 터미널에 입력한 정보가 출력됩니다


 

Flask - SQLAlchemy 를 이용한 데이터베이스 처리❓

우리는 기존 사용했던 파이썬 sqlite3 모듈을 사용해서 DB에 값을 넣는 등 쿼리를 '직접' 날려서 데이터 베이스와 소통했습니다.

파이썬에서 그런 방식으로 데이터베이스를 다룰 수 있다는 것은 좋은일이지만, 복잡하다는 단점이 있습니다.

따라서 이번에는 SQLAlchemy를 사용해 볼 겁니다. 하지만 SQLAlchemy를 알기 전에는 ORM이란 개념을 알아야 합니다. 

ORM은 간단하게 객체와 데이터베이스의 데이터를 매핑해주는 것을 의미하며, SQLAlchemy는 이러한 ORM의 한 종류 입니다.

 

앞서 기존에는 쿼리를 직접 날려서 데이터베이스를 다뤘지만, 지금은 쿼리를 직접 날리는게 아닌 파이썬 클래스로 데이터베이스를 다루게 될 겁니다.

 

먼저, 우리는 폼으로부터 이메일, 비밀번호 등의 정보를 받아왔습니다. 그리고 그 데이터들을 데이터베이스에 넣어야 회원가입이라는 절차가 마무리 됩니다. 따라서 유저 테이블을을 만들어야 합니다.

 

먼저 데이터베이스 설정을 하기 위해 __init__.py를 수정해 주겠습니다.

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from os import path
from flask_login import LoginManager
from pprint import pprint

#db설정하기
db = SQLAlchemy()
DB_NAME = "blog_db"

# app을 만들어주는 함수를 지정
def create_app():
    app = Flask(__name__) # Flask app 만들기
    app.config['SECRET_KEY'] = "IFP"

    #db 설정하기
    app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DB_NAME}'
    db.init_app(app)

    from .views import views # blueprint 등록, '/blog를 기본으로 함'
    from .auth import auth
    app.register_blueprint(views, url_prefix ="/blog")
    app.register_blueprint(auth, url_prefix="/blog")

    return app

db 설정하기 아래의 코드를 추가로 넣어주었습니다.

 

두번째로 데이터베이스가 존재하지 않는다면 생성하는 코드를 create__app 함수 바로 아래에 추가해 줍니다

이제는 파이썬 클래스로 테이블을 만드는 작업을 해줄겁니다. 먼저 models.py 파일을 만들어 아래 코드를 작성해봅시다.

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)      # id : 유일 키, Integer
    email = db.Column(db.String(150), unique=True)    # email : 같은 이메일을 가지고 있는 유저가 없도록 함, String
    username = db.Column(db.String(150), unique=True) # username : 같은 이름을 가지고 있는 유저가 없도록 함, String
    password = db.Column(db.String(150))              # password : 비밀번호, String
    created_at = db.Column(db.DateTime(timezone=True), default=func.now()) # 생성일자, 기본적으로 현재가 저장되도록 함

 

코드를 보자면 id를 기본키로 설정해주고, email,username은 중복이 없도록 unique를 사용해주었습니다.

마지막으로 데이터베이스가 만들어지기 전 models.py에서 작성한 유저 모델을 먼저 등록해야 하므로 __init__.py에 코드를 추가해줍니다

from .models import User
create_database(app)

Flask-LoginManager() 사용해보기

 

flask_login 라이브러리는 플라스크에서 로그인 기능을 쉽게 구현할 수 있는 라이브러리 입니다.

앞서 작성한 models.py에 UserMixin은 플라스크 로그인에서 수행하는 메소드에 대한 기본구현을 제공해 줍니다.

아래 두 줄의 코드로 로그인이 필요한 곳에 로그인하지 않은 유저가 접근할 경우 로그인 페이지로 리다이렉트 될 수 있도록 해 줍니다.

아래의 코드를 넣어주면 로그인의 준비는 끝이 납니다.


 데이터 검증을 위한 라이브러리를 설치해 줍시다.(이메일, 유저네임 중복 여부 확인 등 유효한 데이터인지 검증할 수 있도록)

pip install flask-wtf
pip install email-validator

두개의 명령을 터미널에 입력해주면 설치는 완료 됩니다.


회원가입 처리하기

 

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 : 필수 입력 항목이며, 이메일의 형식을 유지해야 함.
    email = EmailField('email', validators=[DataRequired(), Email()])
    # username : 필수 입력 항목이며, 최소 5글자부터 최대 30글자까지 허용됨.
    username = StringField('username', validators=[DataRequired(), Length(4, 30)])
    # password1 : 필수 입력 항목이며, 최소 8글자부터 최대 30글자까지 허용됨, password2와 값이 같아야 함.
    password1 = PasswordField('password', validators=[DataRequired(), Length(8, 30), EqualTo("password2", message="Password must match...")])
    password2 = PasswordField('password again', validators=[DataRequired()])

 

auth.py의 코드를 수정해줍니다

from flask import Blueprint, render_template, redirect, request, flash, url_for

from . import db
from blog.forms import SignupForm
from blog.models import User

auth = Blueprint("auth", __name__)

@auth.route("/login",methods=['GET','Post'])

def login():
    return render_template("login.html")

@auth.route("/logout")
def logout():
    return redirect("views.blog_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=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)

마지막으로 폼 코드 아래에  {{ form.csrf_token }}를 추가해주어야 폼이 작동합니다

views.home에는 각자 views에서 설정해둔 home함수의 이름을 적어주면 됩니다. (필자는 blog_home)


비밀번호 해상하기

지금까지 작업으로 폼에서 받아온 데이터를 DB에 저장이 가능해졌습니다. 하지만 우리는 비밀번호를 hashing해 주어야합니다. 여기서 hashing이란 비밀번호를 알 수 없는 값, 즉 암호화 해주는 것을 말합니다

 

비밀번호를 저장하기 전 해싱하기 위해 from werkzeug.security import generate_password_hash, check_password_hash를 입력해줍니다

그리고 폼에서 받아온 데이터를 generate_password_hash()메서드를 사용해 해싱해줍니다

이제 실제로 입력해보겠습니다


각 항목별로 입력한 후 sign up을 누른다면 

이렇게 초기화가 진행되고

이런 식으로 db에 저장이 된 후 home 화면으로 이동하게 됩니다


로그인 처리하기

forms.py에 로그인 폼 코드를 작성해 줍니다.

다음은 auth.py의 로그인 부분입니다

@auth.route("/login", methods=['GET', 'POST'])  # 로그인에서 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)

앞서 진행했던 대로 login template에도 {{ form.csrf_token }}을 적어줍시다

로그아웃

 


오류 메시지 나타내기

 

base.html의 <nav>태그 아래 {%block header%}{%endblock%} 아래 이런 코드를 적어줍니다

{% with messages = 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 %}

위의 코드를 입력하고 메시지를 확인해 봅시다

성공 시 로그인 문구가 나옵니다.

실패한다면 이런 오류 메시지가 출력됩니다.

 

추가적으로 블루프린트를 수정해 줍니다. __init__.py를 열고

이 부분을 추가해 준 뒤, views.py를 열고

수정해주면 완성된다


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

로그인을 했을 경우 "Welcome! 닉네임" 처럼 이름이 뜨도록 하거나 로그인 링크가 보이지 않도록 설정을 해 주어야 합니다.

navbar 안에 리스트를 만들어줍니다

현재 로그인한 유저의 정보를 넘겨주기 위해 user=current_user를 추가해줍니다

또한 네비게이션 바는 base.html에 포함되므로 base.html이 있는 모든 템플릿에 user가 변수로서 전달되어야 합니다.

 

따라서 views와 auth를 수정해 줍니다

views

from flask import Blueprint, render_template
from flask_login import current_user

views = Blueprint("views", __name__)

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

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

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

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

auth

from flask import Blueprint, render_template, redirect, request, flash, url_for
from flask_login import login_user, login_required, logout_user, current_user
from werkzeug.security import generate_password_hash, check_password_hash
from . import db
from blog.forms import SignupForm, LoginForm
from blog.models import User

auth = Blueprint("auth", __name__)


@auth.route("/login", methods=['GET', 'POST'])  # 로그인에서 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.blog_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")
@login_required
def logout():
    logout_user()
    return redirect(url_for("views.home"))  # 로그아웃하면 views의 blog_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("이메일이 이미 존재합니다...", category='error')
        # 유저네임 중복 검사
        elif username_exists:
            flash("유저네임이 중복됩니다...", 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 부분에 아까 추가했던 곳을 수정해 줍니다

 

완성되면

네비게이션 바가 변화하는 것을 볼 수 있습니다.

로그아웃 하게 된다면 다시 이렇게 바뀝니다