본문 바로가기

Python

Flask 5 : 블루프린트와 뷰

원문 : https://flask.palletsprojects.com/en/1.1.x/tutorial/views/

파란색 글씨는 이해를 돕기위해 역자가 추가한 내용입니다.


블루프린트(Blueprint)와 뷰(View)

뷰(view)는 사용자의 요청(request)에 대응(respond)할 화면 의미합니다. Flask는 사용자의 URL 요청(request)이 들어오면 패턴매칭 방법을 이용해 특정 뷰에 연결시키고, 뷰는 이에 상응하는 화면을 만들어냅니다(respond). 


블루프린트 만들기

블루프린트란 연관있는 여러 개의 뷰를 그룹으로 묶어서 처리하는 방법을 말합니다. 각각의 뷰를 앱에 직접 등록하는 대신 블루프린트에 등록하고, 이 블루프린트를 앱에 등록시킵니다.

Flaskr 프로젝트에는 두 개의 블루프린트가 있습니다. 하나는 사용자 인증 기능이고 다른 하나는 블로그 게시물 관련 기능 입니다. 각 블루프린트에 해당하는 코드는 각각 별도의 모듈로 관리됩니다. 

우선 사용자 인증 기능부터 살펴보겠습니다.

flaskr/auth.py
import functools

from flask import (
    Blueprint, flash, g, redirect, render_template, request, session, url_for
)
from werkzeug.security import check_password_hash, generate_password_hash

from flaskr.db import get_db

bp = Blueprint('auth', __name__, url_prefix='/auth')

외부 라이브러리를 import 하는 부분은 건너뛰고 제일 마지막 줄 코드를 보겠습니다.

이 코드는 'auth'라는 이름의 블루프린트를 생성합니다. 앱의 오브젝트처럼 블루프린트 역시 위치를 지정해줘야 합니다. 이를 위해 두 번째 인자로 __name__을 전달합니다. url_prefix 는 이 블루프린트에 속한 페이지 전체의 URL 앞단에 붙을 주소 입니다. __name__ 으로 받아오는 url 앞에 무조건 /auth 를 붙여라 라는 뜻 입니다.

이번에는 __init__.py 에서 블루프린트를 등록해줄 차례 입니다이를 위한 신규 코드는 기존 코드 아래 추가합니다.

flaskr/__init__.py

def create_app(): app = ... # 기존 코드 생략 from . import auth app.register_blueprint(auth.bp) return app

app.register_blueprint() 을 이용하여 팩토리로부터 블루프린트를 임포트해서 등록합니다. 

사용자 인증 블루프린트는 신규사용자 등록과 로그인, 로그아웃 기능을 하는 뷰를 포함합니다.

The First View: 신규사용자 등록

웹사이트 방문자가 /auth/register 라는 URL에 접속하면 register 뷰가 html 코드를 보여줍니다. 방문자가 화면에서 요구하는 양식을 채워서 회원가입을 하면, html 페이지는 채워진 정보에 대한 정합성을 체크한 후 신규 사용자를 등록하고 로그인 페이지로 보내줍니다.

여기에서는 우선 뷰 코드만 작성하구요, 다음 페이지에서 html을 담을 템플릿을 작성할 것입니다.

flaskr/auth.py
@bp.route('/register', methods=('GET', 'POST'))
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        error = None

        if not username:
            error = 'Username is required.'
        elif not password:
            error = 'Password is required.'
        elif db.execute(
            'SELECT id FROM user WHERE username = ?', (username,)
        ).fetchone() is not None:
            error = 'User {} is already registered.'.format(username)

        if error is None:
            db.execute(
                'INSERT INTO user (username, password) VALUES (?, ?)',
                (username, generate_password_hash(password))
            )
            db.commit()
            return redirect(url_for('auth.login'))

        flash(error)

    return render_template('auth/register.html')

register 뷰의 기능을 살펴봅니다:

  1. @bp.route 은 /register 라는 URL 요청(request)이 들어오면 register 뷰로 연결해줍니다. Flask는 /auth/register URL 요청이 들어오면 하단의 register 함수를 실행하고 결과값을 반환합니다.

  2. 사용자가 내용을 입력하고 submit 버튼을 누르면, request.method 가 지정한대로 데이터는 'POST' 방식으로 전달됩니다.

  3. request.form 은 딕셔너리 형태로 작성되어 'key'와 'value' 형태로 저장됩니다. 사용자는 username과 password를 입력합니다.

  4. username과 password가 공백이 아닌지 체크합니다.

  5. username 중복체크를 위해 DB에 쿼리를 날립니다. 이 때 db.execute()를 통해 전달되는 쿼리에 있는 물음표(?)는 변수값을 전달하기 위한 placeholder 입니다. 파이썬에서 placeholder를 이용해 쿼리를 전달하는 방식은 예전에 유행했던 sql injection 공격을 막는 좋은 방법입니다. fetchone() 은 쿼리의 결과값 하나를 출력합니다.

  6.  중복체크까지 성공하면 신규 사용자 정보를 DB에 저장합니다. 보안을 위해 암호는 DB에 바로 저장하지 않고, generate_password_hash() 를 이용해 암호화 한 후 저장합니다. 데이터를 저장 한 후에는 db.commit() 을 이용해 커밋(DB의 데이터를 변경하였음을 확정하는 행위) 합니다.

  7.  사용자 등록이 완료되면 로그인 페이지로 화면을 전달(redirect) 합니다. url_for() 는 로그인 화면의 URL을 나타내고, redirect() 는 URL로 전달해주는 역할을 합니다.

  8. 신규사용자 등록에 실패하면 에러 메시지가 표시됩니다. flash() 는 에러메시지를 뿌려주는 기능을 합니다.

  9. 사용자가 직접 auth/register 에 접근하거나, 신규사용자 등록 에러가 발생하면 사용자 등록 화면이 표시됩니다. render_template() 은 사용자 등록을 위한 html 코드를 화면에 뿌려줍니다.

Login : 로그인

login 뷰 까지 가는 방법은 위에서 설명한 register 뷰와 동일한 패턴매칭 방법을 이용합니다.

flaskr/auth.py
@bp.route('/login', methods=('GET', 'POST'))
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        error = None
        user = db.execute(
            'SELECT * FROM user WHERE username = ?', (username,)
        ).fetchone()

        if user is None:
            error = 'Incorrect username.'
        elif not check_password_hash(user['password'], password):
            error = 'Incorrect password.'

        if error is None:
            session.clear()
            session['user_id'] = user['id']
            return redirect(url_for('index'))

        flash(error)

    return render_template('auth/login.html')

register 뷰와 차이점 위주로 설명합니다:

  1. 사용자 정보는 쿼리를 통해 가져오고, 추후 사용을 위해 변수값에 저장합니다.

  2. check_password_hash() 를 이용해 비밀번호를 암호화하고, DB에 저장된 값과 비교합니다.

  3. 사용자 인증이 완료되면 사용자 정보는 세션에 저장합니다. 세션은 딕셔너리 형태로 되어있습니다. 

세션에 저장된 사용자 id는 후속 request에서 재사용이 가능합니다. 모든 request 가 시작될 때 세션에 저장된 사용자 id를 호출하고 이 정보에 따라 화면을 생성하게 됩니다.

flaskr/auth.py
@bp.before_app_request
def load_logged_in_user():
    user_id = session.get('user_id')

    if user_id is None:
        g.user = None
    else:
        g.user = get_db().execute(
            'SELECT * FROM user WHERE id = ?', (user_id,)
        ).fetchone()

bp.before_app_request() 을 이용해 각 view 함수가 실행되기 전에 실행할 함수를 생성합니다. 사용자가 호출하는 URL이 무엇이든 load_logged_in_user() 이 먼저 실행되도록 설정하는 것입니다. load_logged_in_user() 은 세션에 저장되어있는 사용자 user_id 를 가져오고, 이를 g.user에 저장합니다. 

Logout : 로그아웃

로그아웃은 user id를 세션에서 지우면 됩니다. 이후에는 load_logged_in_user 에서 user_id 가 더이상 조회되지 않습니다.

flaskr/auth.py
@bp.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('index'))

Require Authentication in Other Views : 사용자 인증 요구

블로그에서 글을 작성하거나, 수정, 삭제를 위해 사용자 정보가 필요합니다. 각각의 기능에서 이를 수행하기 위해 사용자 정보를 체크해야하는데 데코레이터(decorator)를 이용해 할 수 있습니다.

flaskr/auth.py
def login_required(view):
    @functools.wraps(view)
    def wrapped_view(**kwargs):
        if g.user is None:
            return redirect(url_for('auth.login'))

        return view(**kwargs)

    return wrapped_view

이 데코레이터는 데코레이터를 호출하는 모든 함수의 밖에서 실행됩니다. 주 기능은 user가 로그인 되었는지 확인하고, 그렇지 않다면 로그인 화면으로 보내주는 기능입니다. 

Endpoints and URLs : 엔드포인트와 URL

url_for() 함수는 이름과 인자값에 따라 URL을 생성해줍니다. 이렇게 생성된 URL을 엔드포인트라고 부르는데, 디폴트로 뷰 함수의 이름과 동일하게 생성됩니다.

예를 들면, hello() 뷰는 이름이 'hello' 구요, 이를 위한 링크는 url_for('hello') 와 같이 생성됩니다. 만일 누가 이 화면을 볼 것인지 지정하는 인자가 포함되어 있다면, url_for('hello', who='World') 처럼 생성되겠지요.  

블루프린트를 이용할 때는 블루프린트 이름 자체가 앞쪽에 붙습니다. 따라서 login 뷰라면 'auth.login' 와 같이 생성되는 것입니다. 

다음 : 템플릿


'Python' 카테고리의 다른 글

Flask 7 : 정적 파일  (0) 2019.09.08
Flask 6 : 템플릿  (0) 2019.09.06
Flask 4 : DB 구축  (1) 2019.08.28
Flask 3 : 어플리케이션 설치  (0) 2019.08.28
Flask 2 : 프로젝트 구조  (0) 2019.08.28