from datetime import datetime
from flask import Blueprint, url_for, request, render_template, g
from werkzeug.utils import redirect
from .. import db
from ..models import Question, Answer
from ..forms import AnswerForm
bp = Blueprint('answer', __name__, url_prefix='/answer')
@bp.route('/create/<int:question_id>', methods=('POST', ))
def create(question_id):
form = AnswerForm()
question = Question.query.get_or_404(question_id) # 그 질문에 해당하는 번호를 가져온다.
if form.validate_on_submit():
content = request.form['content'] # name 속성이 content인 값
answer = Answer(content=content, create_date=datetime.now(), user=g.user) # textArea 내용, 작성시각, 작성자
question.answer_set.append(answer) # question.answer_set 은 Answer과 동일 즉, 거기에다가 값을 추가하는 것임
db.session.commit() # db에 저장
return redirect(url_for('question.detail', question_id=question_id)) # 상세 질문과 답변이 있는 페이지로 전달
return render_template('question/question_detail.html', question=question, form=form)
answer_views.py입니다.
댓글 작성자가 필요하기 때문에 로그인 되어 있으면 g.user에 id가 저장되어 있으므로 그걸 user에다 저장합니다.
from datetime import datetime
from flask import Blueprint, render_template, request, url_for, g
from ..models import Question
from werkzeug.utils import redirect
from .. import db
from ..forms import QuestionForm, AnswerForm
bp = Blueprint('question', __name__, url_prefix='/question')
@bp.route('/list')
def _list():
page = request.args.get('page', type=int, default=1) # 페이지
question_list = Question.query.order_by(Question.create_date.desc())
question_list = question_list.paginate(page, per_page=10)
return render_template('question/question_list.html', question_list=question_list)
@bp.route('/detail/<int:question_id>')
def detail(question_id):
form = AnswerForm()
question = Question.query.get_or_404(question_id)
return render_template('question/question_detail.html', question=question, form=form)
@bp.route('/create/', methods=('GET', 'POST'))
def create():
form = QuestionForm()
if request.method == 'POST' and form.validate_on_submit():
question = Question(subject=form.subject.data, content=form.content.data, create_date=datetime.now(), user=g.user)
db.session.add(question)
db.session.commit()
return redirect(url_for('main.index'))
return render_template('question/question_form.html', form=form)
question_views.py 입니다.
글 작성자가 필요하기 때문에 로그인 되어 있으면 g.user에 id가 저장되어 있으므로 user에다 저장합니다.
로그아웃 상태에서 질문 또는 답변을 등록이 불가능하게 오류를 발생시켜보도록 하겠습니다.
@login_required 어노테이션을 지정하면 login_required 데코레이션 함수가 먼저 실행 됩니다.
(즉 def login_required(view)라는 것이 @login_required 과 똑같은 의미입니다.)
login_required 함수는 g.user가 있는지 조사하고 없으면 로그인 URL로 리다이렉트합니다.
g.user가 있으면 원래 함수를 그대로 실행합니다.
이런걸 데코레이터 함수라고 하는데 나중에 설명해서 올리도록 하겠습니다.
from datetime import datetime
from flask import Blueprint, url_for, request, render_template, g
from werkzeug.utils import redirect
from .auth_views import login_required
from .. import db
from ..models import Question, Answer
from ..forms import AnswerForm
bp = Blueprint('answer', __name__, url_prefix='/answer')
@bp.route('/create/<int:question_id>', methods=('POST', ))
@login_required
def create(question_id):
form = AnswerForm()
question = Question.query.get_or_404(question_id) # 그 질문에 해당하는 번호를 가져온다.
if form.validate_on_submit():
content = request.form['content'] # name 속성이 content인 값
answer = Answer(content=content, create_date=datetime.now(), user=g.user) # textArea 내용, 작성시각, 작성자
question.answer_set.append(answer) # question.answer_set 은 Answer과 동일 즉, 거기에다가 값을 추가하는 것임
db.session.commit() # db에 저장
return redirect(url_for('question.detail', question_id=question_id)) # 상세 질문과 답변이 있는 페이지로 전달
return render_template('question/question_detail.html', question=question, form=form)
answer_views.py 입니다
@login_required를 넣어서 로그인 라우트가 실행하기 전에 로그인 되어있는지 확인하는 것입니다.
from datetime import datetime
from flask import Blueprint, render_template, request, url_for, g
from .auth_views import login_required
from ..models import Question
from werkzeug.utils import redirect
from .. import db
from ..forms import QuestionForm, AnswerForm
bp = Blueprint('question', __name__, url_prefix='/question')
@bp.route('/list')
def _list():
page = request.args.get('page', type=int, default=1) # 페이지
question_list = Question.query.order_by(Question.create_date.desc())
question_list = question_list.paginate(page, per_page=10)
return render_template('question/question_list.html', question_list=question_list)
@bp.route('/detail/<int:question_id>')
def detail(question_id):
form = AnswerForm()
question = Question.query.get_or_404(question_id)
return render_template('question/question_detail.html', question=question, form=form)
@bp.route('/create/', methods=('GET', 'POST'))
@login_required
def create():
form = QuestionForm()
if request.method == 'POST' and form.validate_on_submit():
question = Question(subject=form.subject.data, content=form.content.data, create_date=datetime.now(), user=g.user)
db.session.add(question)
db.session.commit()
return redirect(url_for('main.index'))
return render_template('question/question_form.html', form=form)
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, PasswordField
from wtforms.fields.html5 import EmailField
from wtforms.validators import DataRequired, Email, EqualTo, Length
class QuestionForm(FlaskForm):
subject = StringField('제목', validators=[DataRequired('제목은 필수 입력 항목입니다.')])
content = TextAreaField('내용', validators=[DataRequired('내용은 필수 입력 항목입니다.')])
class AnswerForm(FlaskForm):
content = TextAreaField('내용', validators=[DataRequired('내용은 필수 입력 항목입니다.')])
class UserCreateForm(FlaskForm):
username = StringField('사용자이름', validators=[
DataRequired(), Length(min=3, max=25)])
password1 = PasswordField('비밀번호', validators=[
DataRequired(), EqualTo('password2', '비밀번호가 일치하지 않습니다.')])
password2 = PasswordField('비밀번호확인', validators=[DataRequired()])
email = EmailField('이메일', [DataRequired(), Email()])
class UserLoginForm(FlaskForm):
username = StringField('사용자이름', validators=[DataRequired(), Length(min=3, max=25)])
password = PasswordField('비밀번호', validators=[DataRequired()])
forms.py 입니다.
로그인할 때 검증에 필요한 userLoginForm을 만들었습니다. (이미 했던 거라 설명은 생략하겠습니다.)
from flask import Blueprint, request, render_template, flash, url_for, session
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.utils import redirect
from .. import db
from pybo.forms import UserCreateForm, UserLoginForm
from pybo.models import User
bp = Blueprint('auth', __name__, url_prefix='/auth')
@bp.route('/signup/', methods=('GET','POST'))
def signup():
form = UserCreateForm()
if request.method == 'POST' and form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if not user:
user = User(username=form.username.data,
password=generate_password_hash(form.password1.data),
email=form.email.data)
db.session.add(user)
db.session.commit()
return redirect(url_for('main.index'))
else:
flash('이미 존재하는 사용자입니다.')
return render_template('auth/signup.html', form=form)
@bp.route('/login/', methods=('GET','POST'))
def login():
form = UserLoginForm()
if request.method == 'POST' and form.validate_on_submit():
error = None
user = User.query.filter_by(username=form.username.data).first()
if not user:
error = "존재하지 않는 사용자입니다."
elif not check_password_hash(user.password, form.password.data):
error = "비밀번호가 올바르지 않습니다."
if error is None:
session.clear()
session['user_id'] = user.id
return redirect(url_for('main.index'))
flash(error)
return render_template('auth/login.html', form=form)
auth_views.py입니다.
POST방식이라면 로그인을 수행하고 그렇지 않으면 GET방식으로 auth/login.html 템플릿을 렌더링합니다.
POST 방식으로 들어오고 올바른 폼이라면 error에 None을 넣습니다.
username.data값과 DB에 있는 username이 일치하는 경우비밀번호를 체크합니다.
물론 없는 경우(if not user)'존재하지 않는 사용자입니다'를 넣습니다.
비밀번호는 암호화되어서 저장했으므로 바로 비교할 수 없습니다.
입력된 비밀번호는 반드시 check_pass_word_hash 함수로 똑같이 암호화하여 비교해야합니다.
틀린 경우는 '비밀번호가 올바르지 않습니다.'를 넣습니다.
만약 error값이 그대로 None이면 (다 일치했으니까 그대로이다.)session에 키와 키값을 저장합니다
from flask import Blueprint, request, render_template, flash, url_for, session, g
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.utils import redirect
from .. import db
from pybo.forms import UserCreateForm, UserLoginForm
from pybo.models import User
bp = Blueprint('auth', __name__, url_prefix='/auth')
@bp.route('/signup/', methods=('GET','POST'))
def signup():
form = UserCreateForm()
if request.method == 'POST' and form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if not user:
user = User(username=form.username.data,
password=generate_password_hash(form.password1.data),
email=form.email.data)
db.session.add(user)
db.session.commit()
return redirect(url_for('main.index'))
else:
flash('이미 존재하는 사용자입니다.')
return render_template('auth/signup.html', form=form)
@bp.route('/login/', methods=('GET','POST'))
def login():
form = UserLoginForm()
if request.method == 'POST' and form.validate_on_submit():
error = None
user = User.query.filter_by(username=form.username.data).first()
if not user:
error = "존재하지 않는 사용자입니다."
elif not check_password_hash(user.password, form.password.data):
error = "비밀번호가 올바르지 않습니다."
if error is None:
session.clear()
session['user_id'] = user.id
return redirect(url_for('main.index'))
flash(error)
return render_template('auth/login.html', form=form)
@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 = User.query.get(user_id)
auth_view.py 입니다.
load_logged_in_user 함수를 구현하겠습니다.
@bp.before_app_request 어노테이션을 사용하겠습니다.
이 어노테이션은 라우트 함수보다 먼저 실행 됩니다. 즉 모든 라우트 함수보다 먼저 실행이 되는 것이죠
g는 플라스크가 제공하는 변수입니다.(누군 글로벌변수, 누구는 로컬변수라고하네요...)
JSP에서 Application 영역입니다. 즉 서버가 꺼질 때까지 남아있다는 말입니다.
일단 로그인을 했을때 session에 user_id를 저장했기 때문에 session에 user_id를 가져옵니다.
그게 있으면 이제 g.user에 user_id값을 쿼리문으로 조회해서 넣습니다.
(그냥 user_id를 바로 넣어도 되는 거 아닌가? 잘 모르겠네요..)
from flask import Blueprint, request, render_template, flash, url_for, session, g
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.utils import redirect
from .. import db
from pybo.forms import UserCreateForm, UserLoginForm
from pybo.models import User
bp = Blueprint('auth', __name__, url_prefix='/auth')
@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 = User.query.get(user_id)
@bp.route('/signup/', methods=('GET','POST'))
def signup():
form = UserCreateForm()
if request.method == 'POST' and form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if not user:
user = User(username=form.username.data,
password=generate_password_hash(form.password1.data),
email=form.email.data)
db.session.add(user)
db.session.commit()
return redirect(url_for('main.index'))
else:
flash('이미 존재하는 사용자입니다.')
return render_template('auth/signup.html', form=form)
@bp.route('/login/', methods=('GET','POST'))
def login():
form = UserLoginForm()
if request.method == 'POST' and form.validate_on_submit():
error = None
user = User.query.filter_by(username=form.username.data).first()
if not user:
error = "존재하지 않는 사용자입니다."
elif not check_password_hash(user.password, form.password.data):
error = "비밀번호가 올바르지 않습니다."
if error is None:
session.clear()
session['user_id'] = user.id
return redirect(url_for('main.index'))
flash(error)
return render_template('auth/login.html', form=form)
@bp.route('/logout/')
def logout():
session.clear()
return redirect(url_for('main.index'))
auth_views.py입니다.
logout 라우트를 만들어줬습니다.
user_id = session.get('user_id') 이거를 통해서 session에 값을 가져와서 g.user에 저장하는 형식이였기 때문에
email에는반드시 데이터를 입력하게 했고(DataRequired())이메일이 맞는지 확인하게했습니다.(Email())
email 검증을 사용하려면 email-validator가 필요하므로 설치해줍시다.
회원가입용 화면을 만들어 보겠습니다.
메인 뷰 (main_views.py)와 답변 뷰 (answer_views.py)에 어디에도 해당하지 않으므로 새로운 파일을 만들겠습니다.
from flask import Blueprint, request, render_template, flash, url_for
from werkzeug.security import generate_password_hash
from werkzeug.utils import redirect
from .. import db
from pybo.forms import UserCreateForm
from myproject.pybo.models import User
bp = Blueprint('auth', __name__, url_prefix='/auth')
@bp.route('/signup/', methods=('GET','POST'))
def signup():
form = UserCreateForm()
if request.method == 'POST' and form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if not user:
user = User(username=form.username.data,
password=generate_password_hash(form.password1.data),
email=form.email.data)
db.session.add(user)
db.session.commit()
return redirect(url_for('main.index'))
else:
flash('이미 존재하는 사용자입니다.')
return render_template('auth/signup.html', form=form)
pybo/views/auth_views.py입니다.
/auth/가 URL 접두어이고 그와 연결된/auth/singup 과 연결된 signup 함수를만들었습니다.
value에 값이 들어오면fmt 규격에 따라 datetime객체를 strftime을 이용해 규격에 맞게 변환시켜줍니다.
from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
import config
db = SQLAlchemy()
migrate = Migrate()
def create_app():
app = Flask(__name__)
app.config.from_object(config) # config.py에 작성 내용을 환경 변수로 부르기 위한 작업
# ORM
db.init_app(app) # 우리가 적은 환경으로 DB 초기화
migrate.init_app(app,db) # DB와 우리가 적은 환경을 다른 환경으로 옮김
from . import models
# bluePrint
from .views import main_views, question_views, answer_views # .view에서 .은 현재위치의 views 폴더로부터 import한다는 말
app.register_blueprint(main_views.bp) # 블루프린트 객체 bp 등록
app.register_blueprint(question_views.bp) # 블루프린트 객체 bp 등록
app.register_blueprint(answer_views.bp)
# 필터
from .filter import format_datetime
app.jinja_env.filters['datetime'] = format_datetime
return app
__init__.py 입니다.
여기에다가 filter를 추가시켜주면 됩니다. jinja_env.filters[필터이름] = 내가 만든 필터함수