2023.07.16 - [Flask] - Flask 이용한 웹사이트 제작기 (3) - DB 연결
Blueprints and Views
이 장부터 뭔가 코드와 글이 꽉꽉 들어차있는 느낌이다.
일단... 진행하자.
- Create a Blueprint
Blueprint는 애플리케이션 개체를 미리 요구하지 않고 애플리케이션 기능을 정의할 수 있는 개체입니다. 플라스크와 동일한 장식기를 사용하지만 나중에 등록할 수 있도록 기록하여 응용프로그램의 필요성을 줄입니다.
blueprint가 뭔고 하니.. 실제로 app이 만들어지기 전, view나 다른 code들을 app에다 직접 붙이는 게 아니라, view / code 를 blueprint에 먼저 붙이고, 그 blueprint를 app에 붙이는 방식으로 사용 가능하게끔 도와주는 거라고 한다.
그러니까 결국 app 생성 전에, 앱이 가지고 있는 property들을 만들어둔 후, 앱이 생성될 때 한번에 붙이는 거라고 생각하면 되지 않을까?
이 듀토리얼에서는 blog post blueprint 와 authentication blueprint 두 가지의 모듈을 만들 것이라고 한다.
먼저 auth.py
// 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')
이런 식으로 /auth 라는 url prefix가 나왔을 때 붙게 할 'auth' blueprint를 만들 수 있다.
만든 blueprint는
// flaskr/__init__.py
def create_app():
app = ...
~~~
from . import auth
app.register_blueprint(auth.bp)
~~~
return app
이렇게 app에 register_blueprint를 통하여 붙일 수 있다!
- The First View: Register
그 다음은 register view이다. 목표는 /auth/register 로 url이 들어왔을 때, 회원가입하는 창을 띄워주고, 사용자가 해당 칸을 다 채워서 제출하면 valid 검사 후 새로운 user를 만들어 주는 것 까지가 목표이다.
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.'
if error is None:
try:
db.execute(
"INSERT INTO user (username, password) VALUES (?, ?)",
(username, generate_password_hash(password)),
)
db.commit()
except db.IntegrityError:
error = f"User {username} is already registered."
else:
return redirect(url_for("auth.login"))
flash(error)
return render_template('auth/register.html')
이러하다. auth.py 밑에다 아래와 같은 함수를 추가해 주었다.
적당히 읽어 보면.. request form에서 username, password를 가져와서 저장하고, valid 검사를 수행한 뒤, db에다 저장한다.
끝까지 잘 되었으면 auth.login 창으로 넘어가고, 아니라면 error 메시지를 띄운 후 다시 auth/register 창으로 넘어간다.
근데 여기서 redirect(url_for("auth.login")) 과
return_template('auth/register.html'). 이렇게 두 가지의 방법으로 다른 창으로 넘어가게 했는데...
먼저 url_for은 현재 이름을 기준으로 url을 생성한다고 한다. 여기서 login이라는 url path를 만드는 듯 하다.
redirect와 render_template의 차이가 궁금해서 찾아 보니.
redirect :
- 클라이언트를 다른 URL로 보내는 데 사용.
- 다른 페이지로 이동할 떄 사용
render_template :
- HTML 템플릿 파일을 렌더링하여 동적인 콘텐츠를 생성하는 데 사용.
- 페이지를 동적으로 생성할 때 사용합니다.
이라고 chat gpt님께서 알려주셨다.
즉 현재 창을 그대로 두고 다른 화면을 보여 줄 것이냐,
아예 다른 곳으로 갈 것이냐, 의 차이인 듯 하다.
- Login
그다음은 위에서 보내주기만 한 login 창을 이어서 만든다.
// 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')
위와 패턴이 비슷비슷 하다.
처음 보는 게 몇 개 있는데
- fetchone : 쿼리문에 맞는 row 하나만 가져온다. fetchall 이면 전부.
- check_password_hash : 비밀번호라 그런지, 바로 값을 비교하지 않고 안전하게 비교한다.
- session : 사용자의 상태 / 정보를 유지하기 위한 정보. login에 성공하면 해당 id를 새로운 session으로 저장한다.
이를 통해 우리가 여타 다른 홈페이지에서 보던 [로그인 하면 내 정보가 홈페이지에 뜨는] 그 상황을 만들 수 있 나보다!
그리고. user가 auth 쪽으로 들어오기 전, session에서 현재 로그인된 id가 무엇인지 확인해오는 함수를 auth.py에 추가한다.
// 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는 어떤 url이 오든, 해당되는 함수가 실행되기 전 불리는 함수라고 한다.
이것을 통해 session에서 user_id를 가져와, g.user에 그 정보를 넣어준다. 없다면 None으로 하고. 아 근데 저 g 또나왔네...
- Logout
이제 login이 있으니 logout도 만들어 준다. session만 클리어하면 되다 보니 상당히 간단하다.
// flaskr/auth.py
@bp.route('/logout')
def logout():
session.clear()
return redirect(url_for('index'))
- Require Authentication in Other Views
blog 포스트를 작성/ 수정/ 삭제 전에는 login 되어있는지 확인하는 작업이 필요하다.
그리고 이 방법은 decorator를 이용하여 쉽게 체크 가능하다고 한다.
decorator pattern처럼 해당 기능을 flask에서 쉽게 구현한 것 같다.
decorator pattern이란 현재 내가 가지고 있는 객체에다가 기능을 계속 추가할 수 있게 도와주는 패턴이라고 보면 된다.
decorator pattern이 궁금하다면?
// 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
코드는 위와 같이 작성한다.
view를 한번 감싸서, 바로 원하는 view로 가지 않고, wrapped view를 한 번 거쳐가게 만들었다. 이 때 login이 되어있는지 확인하면 간단할 것이다.
- Endpoints and URLs
위에서 잠깐 말했듯, url_for() function은 현재 name과 argu에 따라 url을 생성해 준다고 한다. 이 때 이 뷰와 관련된 이름을 endpoint 라 부르고, 기본적으론 view function의 name과 같다고 한다.
그러니.. 자세힌 모르겠지만 읽어보면, 우리가 auth.py에서 'auth' 라는 blueprint를 만들었으니, auth.py 내부에 정의된 login과 같은 함수들의 endpoint는 'auth.login' 이 된다고 한다.
으음.... 이 부분은 나중에 좀 더 해봐야것다.
일단 이 장에서는 html 은 건드리지 않고 코드 부분만 만들었다. 다음 Templates 에서 마저 진행할 듯 하다!
2023.07.21 - [Flask] - Flask 이용한 웹사이트 제작기 (5) - 기본 template 구현
'Flask' 카테고리의 다른 글
Flask 이용한 웹사이트 제작기 (6) - static file 추가 (0) | 2023.07.22 |
---|---|
Flask 이용한 웹사이트 제작기 (5) - 기본 template 구현 (0) | 2023.07.21 |
Flask 이용한 웹사이트 제작기 (3) - DB 연결 (0) | 2023.07.16 |
Flask 이용한 웹사이트 제작기 (2) - 폴더 구조/ hello world 띄우기 (0) | 2023.07.15 |
Flask 이용한 웹사이트 제작기 (1) - 설치 (1) | 2023.07.10 |