개발자로 후회없는 삶 살기

Flask PART.상세 기능 만들기 본문

[백엔드]/[Etc]

Flask PART.상세 기능 만들기

몽이장쥰 2023. 1. 20. 17:12

서론

※ 이 포스트는 다음 교재의 학습이 목적임을 밝힙니다.

https://wikidocs.net/81044

 

2-01 플라스크 기초 다지기

현재 파이보 프로젝트는 `projects/myproject` 디렉터리 아래에 pybo.py 파일만 생성한 상태다. 그런데 이보다 규모를 갖춘 플라스크 프로젝트를 만들고자 한다면 …

wikidocs.net

 

본론

2.5 질문 목록과 질문 상세 기능 만들기

- 질문 목록 만들기

이제는 127.0.0.1로 들어가면 위처럼 문자열 출력이 아닌 게시판 질문 목록이 출력되도록 main_views.py의 블루 프린트 라우팅 함수를 수정할 것입니다. index 함수가 질문 목록 페이지를 반환하도록 변경하면 됩니다.

 

@bp.route('/')
def index():
    question_list = Question.query.order_by(Question.create_date.desc())
    return render_template('question/question_list.html', question_list = question_list)

질문 모델에서 작성일시(create_date)를 기준으로 역순으로 정렬하여 질문 목록을 가지고 오고 render_template 함수로 템플릿 파일을 화면에 렌더링 할 것입니다. 

 

+ 이 코드로 알 수 있는 사실은 모델에서 대입연산자를 사용한다는 것은 db의 행들을 리스트로 가져온다는 것이고 대입 연산자를 안 쓰면 그냥 쿼리문만 실행한다는 것입니다. 또한 render template에서 두 번째 인자는 A=A, B=B처럼 내 맘대로 하면 되고 대입 연산자 양쪽을 동일하게 맞추는 것이 좋습니다.

 

> 조회한 질문 목록 데이터를 render 함수의 파라미터로 전달하면 템플릿에서 해당 데이터로 화면을 구성할 수 있습니다. 또한 템플릿 파일은 html과 비슷하지만 플라스크의 특별한 태그를 사용할 수 있는 엄연히 다른 파일입니다.

 

 

- 질문 목록 템플릿 작성하기

template 폴더에 question 폴더에 question_list.html 템플릿 파일을 작성합니다. template dir은 플라스크가 앱으로 지정한 모듈 아래에 작성하면 된다고 하는데 여기서는 pybo입니다. 자세히 보자면 venv \ myproject \ pybo이니 myproject를 현재 프로젝트에 필요한 모든 앱(피보), migrations 등이 있는 폴더로 보면 되고 애플리케이션은 pybo라고 보면 됩니다!

 

+ 애플리케이션과 애플리케이션을 위한 리소스를 전부 모아놓은 게 myproject입니다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <!-- render 함수에서 전달받은 질문 목록 데이터가 있는지 검사 -->
    {% if question_list %}
        <ul>

<!--        목록에 저장된 데이터를 하나씩 꺼내 question 객체에 대입한다.-->
        {% for question in question_list %}
            <li><a href="/detail/{{ question.id }}/">{{ question.subject }}</a></li>
        {% endfor %}
        </ul>
    {% else %}
        <p>질문이 없습니다.</p>
    {% endif %}
</body>
</html>

html에 질문 목록 코드를 작성합니다. 템플릿 파일에 사용한 if 같은 특이한 표현이 눈에 띌 것인데 {% %}로 둘러싸인 문장을 템플릿 태그라고 합니다. 이 태그가 파이썬 코드와 연결됩니다. 이처럼 파이썬 명령어를 사용하여 쉽게 html에 적용할 수 있는 것이 파이썬 런타임 프레임워크인 Flask의 장점입니다!

 

 

 

이제 로컬 서버를 실행하면 html 템플릿 파일이 보입니다.

 

 

- 플라스크에서 사용하는 템플릿 태그

자주 사용하는 3가지 유형이 있습니다. 이 3가지 유형만 알아도 여러 기능을 충분히 만들 수 있습니다.


1. 분기문 태그
파이썬의 조건문과 유사한데 무조건 endif로 닫아줘야 합니다.

2. 반복문 태그
for문과 같고 역시 닫는 태그 endfor가 필수입니다.

3. 객체 태그
객체를 출력하는 템플릿 태그도 있습니다. question 같은 class의 객체를 출력할 수 있고 조건, 반복문은 '%'가 있어야 하는데 객체는 {{}}만 있으면 됩니다. 객체.속성도 사용 가능합니다. > question은 ORM 클래스이므로 객체를 생성하고 .으로 속성을 참조할 수 있습니다. 

 

 

- 질문 상세 기능 만들기

질문 링크를 눌러보면 페이지가 없다고 나옵니다. 이 문제를 질문 링크를 누르면 질문 제목과 내용이 표시되도록 해보겠습니다. 오류 링크를 보면 question 모델의 id값이 1인 데이터를 조회하라는 의미로 

 

href="/detail/{{ question.id }}/

a 태그에 작성한 값을 보면 q.id랑 연결됩니다. 이 요청이 대응될 수 있도록 블루프린팅에 라우팅 함수를 추가해 보겠습니다. 

 

※ 모든 포트 번호 5000번으로의 URL 입력은 라우팅 함수를 호출하는 것이라고 보면 됩니다!!

 

 

@bp.route('/detail/<int:question_id>/') # 매개변수 URL 매핑 규칙
def detail(question_id):
    question = Question.query.get(question_id) # 매개변수로 전달 받은 id의 질문
    return render_template('question/question_detail.html', question = question)

매개변수로 전달받은 id의 질문을 추출해서 html에 전달합니다.

 

 

결과적으로 html이 화면에 렌더링 됩니다.

 

 

 

 

- 404 오류 페이지 표시하기

localhost/detail/30을 URL에 입력해 보면 id가 30인 질문을 질문 목록에서 클릭한 것과 같은 효과로 detail 함수에 id 30이 매개변수로 호출되어 화면에 전시되어야 합니다. 결과는 빈 화면이 나옵니다. 하지만 이것은 아예 없는 페이지가 아니고 db에 id 30값이 없으므로 빈페이지가 나오는 것이므로 없는 페이지라고 메세지를 표시할 필요가 있습니다. 

question = Question.query.get_or_404(question_id) # 매개변수로 전달 받은 id의 질문

이를 하려면 detail 함수에서 쿼리.get_or_404를 하면 db에서 데이터를 찾을 수 없는 경우 404 페이지를 출력해 줍니다.

 

 

- 블루프린트로 기능 분리하기

질문 목록과 질문 상세 기능을 전부 main_view에 구현했었습니다. 모든 기능을 다 main_view에 구현할 수도 있지만 ★ 각 기능을 블루프린트 파일을 분리해 관리하면 유지, 보수하는데 유리합니다. ★

 

from flask import Blueprint, render_template
from pybo.models import Question

'''
인수 1 블루프린트의 '별칭'으로 url_for 함수에서 사용된다.
인수 2 모듈 명 main_views가 전달
인수 3 라우팅 함수의 애너테이션 URL 암페 기본으로 붙일 URL

-> 원래 hello_pybo는 '/'이니 localhost:5000/으로 입력하는데 
url_p가 '/main'이었다면 localhost:5000[/main]/이다. = 애너테이션 URL의 앞에 /main을 붙여야한다.
'''

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

@bp.route('/list/')
def _list():
    question_list = Question.query.order_by(Question.create_date.desc())
    return render_template('question/question_list.html', question_list = question_list)

@bp.route('/detail/<int:question_id>/') # 매개변수 URL 매핑 규칙
def detail(question_id):
    question = Question.query.get_or_404(question_id) # 매개변수로 전달 받은 id의 질문
    return render_template('question/question_detail.html', question = question)

 

-> 질문 목록, 질문 상세 기능 분리하기

블루프린트의 별칭과 url_prefix를 구별하도록 합니다. 이제 질문에 관련된 질문 목록, 질문 상세는 question를 통해서 라우팅 할 것입니다.

 

from .views import main_views, question_views
    app.register_blueprint(main_views.bp)
    app.register_blueprint(question_views.bp

분리를 하면 __init__에 블루프린트를 등록해줘야 합니다.

 

 

-> url_for로 리다이렉트 기능 추가하기

from flask import Blueprint, url_for
from werkzeug.utils import redirect

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

@bp.route('/hello')
def hello_pybo():
    return 'Hello, Pybo!'

@bp.route('/')
def index():
    return redirect(url_for('question._list'))

question_view에 질문 목록과 질문 상세 기능을 구현했으므로 main_view에는 해당 기능을 제거합니다. detail(이전 질문 목록) 함수를 제거하고 index 함수는 question._list에 해당하는 URL로 리다이렉트 하도록 코드를 수정했습니다.

 

 

redirect 함수는 입력받은 URL로 리다이렉트 하고, url_for 함수는 라우팅 함수명으로 URL을 역으로 찾는 함수입니다. question._list는 question은 등록된 블루프린트 별칭, _list는 블루프린트에 등록된 함수명입니다. 따라서 question._list는 question이라는 별칭으로 등록한 question_views.py 파일의 _list 함수를 의미합니다.

 

> 그리고 _list에 등록된 URL을 찾아 해당 별칭의 bp의 prefix URL인 /question/과 /list/가 더해져 반환됩니다.

 

※ url_for은 라우팅 함수에 매핑되어 있는 URL을 역으로 찾아 리턴한다고 했습니다. 덧붙이자면 question 별칭의 라우팅 함수인 _list에 매핑되어있는 URL '/list/'를 찾아서 prefix와 더해져서 리턴합니다.

 

 

이제 localhost:5000에 접속하면 question/list가 호출됩니다.

 

※ 리다이렉트는 다른 함수를 호출하는 것이지 html을 호출하는 것은 아닙니다. 따라서 html을 호출하고 싶다면 리다이렉트 하고 리다이렉트 된 곳에서 render를 해야 합니다.

 

 

-> 하드 코딩된 URL에 url_for 함수 이용하기

url_for을 이용하면 라우팅 함수명으로 URL을 찾아준다고 했습니다. 이 기능을 이용해 질문 목록에서 질문 상세를 호출하는 링크도 url_for을 사용해 볼 수 있습니다. 기존에는 상세 페이지로 연결하는 링크가 하드코딩되어 있었습니다. 보면 우리는 localhost/detail/id를 호출해야 합니다. 

 

★ 중요!! ★

그러면 여기서 생각해야 할 것은 바로 detail/id가 뭐냐는 것과 detail/id를 어떻게 호출하냐는 것이다. detail/id는 detail 함수를 호출하는 URL이었습니다. 

@bp.route('/detail/<int:question_id>/') # 매개변수 URL 매핑 규칙
def detail(question_id):

 

그러면 왜 detail 함수를 호출해야 할까? 플라스크에서 모든 페이지 전환은 함수를 호출하는 것으로 이루어집니다. 그러니 우리가 원하는 행동을 하려고 하면 단 두 가지 1) 어떤 함수를 호출할 건지, 2) 그 함수의 route URL이 무엇인지만 구하면 됩니다!!

 

 

수정 전
<li><a href="/detail/{{ question.id }}/">{{ question.subject }}</a></li>

수정 후
<li><a href="{{ url_for('question.detail', question_id=question.id) }}">{{ question.subject }}</a></li>

우리의 모든 URL 호출은 route 함수로 이루어집니다. detail/id는 detail 함수의 URL 매핑이었습니다. 그러니 이전에는 detail 함수를 호출하기 위해서 /detail/<int:question_id>/의 모양으로 href에 넣어준 것입니다. (/detail/<int:question_id>/ 이렇게 넣으면 알아서 localhost:5000이 붙나 봅니다!)

 

> href에는 URL을 넣으면 되고 수정 전처럼 하드코딩하지 말고 url_for을 사용하면 인자로 호출할 bp 별칭. 함수를 주면 됩니다. 현재 함수인 detail은 매개변수를 필요로 하니 render 할 때처럼 함께 넣어준다.

 

※ 느낀 점

1. render_template는 html을 호출하니 매개 변수를 html로 주는 것인데 url_for은 함수를 호출하니 매개변수를 호출하는 함수의 인자로 주는 것입니다.
2. url_for로 리다이렉트를 하거나 a 태그를 하면 url_for안의 함수의 route URL로 호출한 것이므로 그 후에 이뤄질 일은 route URL은 더 볼 필요 없고 밑에 함수 내용만 보면 됩니다.

 

 

2.6 답변 등록 기능 만들기

질문 상세 화면에 답변을 입력하기 위한 텍스트창과 답변 등록 버튼을 만들어보겠습니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>{{ question.subject }}</h1>
    <div>
        {{ question.content }}
    </div>
    <form action="{{ url_for('answer.create', question_id=question.id) }}" method="post">
        <textarea name="content" id="content" rows="15"></textarea>
        <input type="submit" value="답변등록">
    </form>

</body>
</html>

 

- 답변 등록 버튼 만들기

버튼을 만든 후 질문 상세 화면을 호출하면 form의 action 속성의 answer.create 함수를 찾을 수 없다며 오류가 발생합니다. 이에 해당하는 블루 프린트를 만들겠습니다.

 

 

 

from datetime import datetime
from flask import Blueprint, url_for, request
from werkzeug.utils import redirect

from pybo import db
from pybo.models import Question, Answer

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

@bp.route('/create/<int:question_id>', methods=('POST',))
def create(question_id):
    question = Question.query.get_or_404(question_id) # 매개변수로 전달 받은 id의 질문
    content = request.form['content'] # POST 방식으로 전송된 데이터 항목 중 name 속성이 content인 값
    answer = Answer(question = question, content=content, create_date=datetime.now()) # 답변을 하나 만듬
    question.answer_set.append(answer) # 답변에 답변을 더함
    db.session.commit()
    return redirect(url_for('question.detail', question_id=question_id))

1) content는 # POST 방식으로 전송된 데이터 항목 중 name 속성이 content인 값을 의미합니다. textarea 태그의 name이 content라서 그렇습니다.

 

2) question.answer_set은  question = Question.query.get_or_404(question_id)로 조회한 질문에 달린 답변들을 의미하여 새로 단 답변을 원래 있던 답변들에 더한다는(append) 것입니다. 

 

3) 답변을 더하고 커밋하고 상세화면으로 이동하기 위해 redirect를 사용합니다.

 

※ request 객체란?

request 객체는 플라스크에서 생성 과정 없이 사용할 수 있는 기본 객체입니다. 플라스크는 브라우저의 요청부터 응답까지의 처리 구간에서 request 객체를 사용할 수 있고 이 객체를 이용해 브라우저에서 요청한 정보를 확인할 수 있습니다.

 

- 답변 표시하기

<h5>{{ question.answer_set|length }}개의 답변이 있습니다.</h5>
<div>
    <ul>
    {% for answer in question.answer_set %}
        <li>{{ answer.content }}</li>
    {% endfor %}
    </ul>
</div>

 

question_detail.html에 등록한 답변을 화면에 표시하도록 했습니다.

 

 

2.9 표준 html과 템플릿 상속 사용해 보기

처음 html 파일을 만들면 생기는 '표준 태그'를 사용해 보자는 것입니다. 앞에서 작성한 질문 목록, 질문 상세 템플릿을 표준 HTML 구조가 되도록 수정해 보겠습니다.. 그런데 템플릿 파일들을 모두 표준 HTML 구조로 변경하면 body 엘리먼트 바깥 부분(head 엘리먼트 등)은 모두 같은 내용으로 중복될 것입니다. 그러면 CSS 파일 이름이 변경되거나 새로운 CSS 파일이 추가될 때마다 모든 템플릿 파일을 일일이 수정해야 합니다.

 

<!doctype html>
<html lang="ko">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="{{ url_for('static', filename='bootstrap.min.css') }}">
    <!-- pybo CSS -->
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    <title>Hello, pybo!</title>
</head>
<body>
<!-- 기본 템플릿 안에 삽입될 내용 Start -->
{% block content %}
{% endblock %}
<!-- 기본 템플릿 안에 삽입될 내용 End -->
</body>
</html>

플라스크는 이런 불편함을 해소하기 위해 템플릿 상속 기능을 제공합니다. 기본 틀이 되는 base.html을 작성하고

 

자식 템플릿

{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
    <table class="table">
        (... 생략 ...)
    </table>
</div>
{% endblock %}

모든 템플릿에 공통으로 입력할 내용을 base.html에 포함한다고 생각하면 됩니다. block content와 endblock 태그 사이가 base.html을 상속한 템플릿에서 구현해야 할 영역이 됩니다. 이후에 각 자식 템플릿을 수정하면 됩니다.

 

+ 자식 템플릿은 정말로 extends 후에 block 사이만 써주면 됩니다. html 골격은 base를 상속 받은 것으로 대체됩니다.

 

 

2.10 폼 모듈로 데이터 검증 더 쉽게 하기

폼은 사용자에게 입력 양식을 편리하게 제공하기 위해 사용합니다. 이번에는 플라스크의 폼 모듈을 사용하겠습니다. 폼 모듈을 사용하면 폼에서 전송되는 데이터의 필수 여부, 길이, 형식 등을 더 쉽게 검증할 수 있습니다.

 

SECRET_KEY = "dev"

secret_key는 웹 사이트 취약점 공격을 방지하는 데 사용됩니다. config.py에 이 설정을 끝내야만 질문 등록이 정상적으로 동작합니다.

 

 

- 질문 등록

 

1. 질문 폼 만들기

질문 등록 시 사용할 QuestionForm을 먼저 만들어 보겠습니다. QuestionForm은 질문을 등록할 때 사용할 플라스크의 폼입니다. 플라스크 폼은 FlaskForm 클래스를 상속합니다. QuestionForm의 속성은 제목과 내용입니다. 

from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField
from wtforms.validators import DataRequired

class QuestionForm(FlaskForm):
    subject = StringField('제목', validators=[DataRequired()])
    content = TextAreaField('내용', validators=[DataRequired()])

-> 인자

제목 : 폼 라벨로 html에서 label과 input을 같이 쓰는데 그때 그 label입니다.
validators : 검증을 위해 사용되는 도구로 필수 항목인지를 체크하는 DataRequired, 이메일인지를 체크하는 Email, 길이를 체크하는 Length 등이 있습니다. 필수값이면서 이메일이어야 하면 validators=[DataRequired(), Email()] 과 같이 사용할 수 있습니다.

 

2. 질문 등록 라우팅 함수 추가하기

QuestionForm의 객체(form)는 템플릿에서 라벨이나 입력폼 등을 만들 때 필요합니다.

from pybo.forms import QuestionForm

(... 생략 ...)

@bp.route('/create/')
def create():
    form = QuestionForm()
    return render_template('question/question_form.html', form=form)

 

3. 질문 등록 템플릿 작성하기

이 파트가 지금 무엇을 하는 중인지를 제대로 알려주는 부분입니다.

<div class="container">
    <h5 class="my-3 border-bottom pb-2">질문등록</h5>
    <form method="post" class="my-3">

        {{ form.subject.label }}
        {{ form.subject() }}

        {{ form.content.label }}
        {{ form.content() }}

        <button type="submit" class="btn btn-primary">저장하기</button>
    </form>
</div>

원래는 질문의 제목과 내용을 받는 창이 따로 없었는데 현재 질문의 제목과 내용을 입력받는 창을 예쁘고 정확하고 검증되게 만드는 작업을 하는 중입니다. 따라서 질문 등록하기 버튼을 누르면 question_form html을 열어서 질문을 등록할 것입니다. 

 

질문 등록을 위해서는 질문의 제목과 내용이라는 입력 항목이 필요합니다. 정말 철저하게 Question 모델을 구상했던 것을 이용하는 것을 알 수 있습니다. 질문의 제목과 내용에 해당하는 입력 항목을 form 객체를 사용해서 만들 것입니다. question_views의 create함수를 호출하고 _form. html로 변수 form을 주고 그 form을 이용하여 label과 input 값을 받아 사용합니다. 

 

※ 순서 : 질문 목록에서 create 함수 호출 > Questionform 객체 생성 > form.html로 form 매개 변수 전달 > form 템플릿에서 QuestionForm에서 작성한 두 개의 필드 제목과 내용을 사용

 

 

<form method="post" class="my-3">

@bp.route('/create/', methods = ('GET', 'POST'))

QuestionForm 객체가 form.으로 만들어준 라벨과 입력항목이 보입니다. 이때 중요한 것이 템플릿에서 form을 post로 했으면 create 함수의 라우팅 정보를 수정해야 하는 것입니다.

 

+ action을 지정하지 않으면 현재 페이지의 URL이 디폴트 action으로 설정된다.

 

<a href="{{ url_for('question.create') }}" class="btn btn-primary">질문 등록하기</a>

현재 이렇게 question/create로 들어가서 question_from을 화면에 띄운 것이므로 

현재 페이지의 URL이 이렇게 되고 

<form method="post" action="{{url_for('question.create')}}" class="my-3">

action 속성을 명확하게 지정한 것처럼 된다. 이렇게 지정하지 않은 이유는 question_form.html을 질문 등록 외에 질문 수정에도 사용하기 위해 그런 것이다. 따라서 action 속성을 위처럼 명확하게 지정해도 된다.

 

 

- 입력한 폼 데이터를 데이터 베이스에 저장하기

create 함수에 POST 방식으로 요청된 폼 데이터를 데이터베이스에 저장하는 코드를 추가하겠습니다.

 

@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())
        db.session.add(question)
        db.session.commit()
        return redirect(url_for('main.index'))
    return render_template('question/question_form.html', form=form)

 

1) request.method : create 함수로 요청된 전송 방식
2) form.validate_on_submit : 전송된 폼 데이터가 QuestionForm 클래스의 각 속성에 지정한 DataRequired() 같은 점검 항목에 이상이 없는지 확인

 

-> 함수가 return이 두 개 있는 이유 : 

질문을 등록하고 저장하는 순서 때문입니다. 질문을 등록하겠다는 의사를 질문 등록하기 버튼으로 하고 진짜로 질문을 db에 저장하겠다는 것을 저장하기 버튼으로 합니다. 이를 처리하는 방법이 form 요청 방식의 차이로 하는데 등록하기는 GET 방식으로 등록하는 form.html 화면을 render 해줍니다. 저장하기는 POST방식으로 db에 데이터를 저장하고 처음 화면으로 돌아갑니다.

 

 

 

form.validate_on_submit()을 만족하지 않으면 경고를 보냅니다.

 

-> 질문 등록 완성하기

<form method="post" class="my-3">
    {{ form.csrf_token }}

    {{ form.subject.label }}
    {{ form.subject() }}

    {{ form.content.label }}
    {{ form.content() }}

    <button type="submit" class="btn btn-primary">저장하기</button>
</form>

하지만 저장하기 버튼을 눌러도 제대로 동작하지 않습니다. > 앞에서 저장하기를 눌러도 화면에서 아무런 변화가 없었습니다. 이러한 점을 보완하고자 질문 등록 템플릿을 수정해 보겠습니다. 바로 CSRF 토큰이 빠졌기 때문입니다. 

 

토큰을 넣으면 질문이 잘 저장됩니다.

 

 

- 답변 등록  폼 사용하기

class AnswerForm(FlaskForm):
    content = TextAreaField('내용', validators=[DataRequired('내용은 필수입력 항목입니다.')])

순서는 동일합니다. 플라스크 폼을 만들고

 

@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():
        question = Question.query.get_or_404(question_id) # 매개변수로 전달 받은 id의 질문
        content = request.form['content'] # POST 방식으로 전송된 데이터 항목 중 name 속성이 content인 값
        answer = Answer(question = question, content=content, create_date=datetime.now()) # 답변을 하나 만듬
        question.answer_set.append(answer) # 답변에 답변을 더함
        db.session.commit()
        return redirect(url_for('question.detail', question_id=question_id))

    return render_template('question/question_detail.html', question=question, form=form)

적합한 답변이 등록되면 db에 값이 저장되고 그렇지 않으면 그냥 현재 페이지를 그대로 유지합니다.

 

<form action="{{ url_for('answer.create', question_id=question.id) }}" method="post">
    {{ form.csrf_token }}
    <textarea name="content" id="content" rows="15"></textarea>
    <input type="submit" value="답변등록">
</form>

답변 등록 템플릿에도 플라스크 폼을 사용하기 위한 토큰을 넣어줍니다.

 

@bp.route('/detail/<int:question_id>/') # 매개변수 URL 매핑 규칙
def detail(question_id):
    form = AnswerForm()
    question = Question.query.get_or_404(question_id) # 매개변수로 전달 받은 id의 질문
    return render_template('question/question_detail.html', question = question, form=form)

질문 상세 템플릿에 폼이 추가되었으므로 question_views.py 파일의 detail 함수도 폼을 사용하도록 수정해야 합니다. 이 과정이 없으면 템플릿에서 form 객체를 읽지 못해 오류가 납니다.

 

'[백엔드] > [Etc]' 카테고리의 다른 글

FLASK PART.서비스 개발  (0) 2023.01.21
Flask PART.플라스크 기초 다지기  (0) 2023.01.18
Flask PART.플라스크 개발 환경 준비  (0) 2023.01.18
Comments