반응형

사용자에게 입력 양식을 편리하게 제공하기 위해 사용합니다.

플라스크에서 폼을 사용하려면 Flask-WTF라는 라이브러리를 설치해야 합니다.

 

Flask-WTF를 사용하려면 플라스크 환경 변수 SECRET_KEY가 필요합니다.
SECRET_KEY는 CSRF라는 웹 사이트 취약 점 공격을 방지하는데 사용합니다.

(사용자의 요청을 위조하는 웹 사이트 공격 기법입니다. )
CSRF 토큰은 폼으로 전송된 데이터가 실제 웹 페이지에서 작성된 데이터인지를 판단해 주는 역할을 합니다.

 

먼저 cmd를 이용해 설치하겠습니다.

 

import os

BASE_DIR = os.path.dirname(__file__)

SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(os.path.join(BASE_DIR, 'pybo.db'))
SQLALCHEMY_TRACK_MODIFICATIONS = False

SECRET_KEY = 'dev'

# SQLALCHEMY_DATABASE_URI에는 DB접속 주소가 들어간다.
# BASE_DIR 은 루트디렉터리 경로를 의미 (C:/projects/myproject/)'
# pybo.db를 루트디렉터리에 저장하는 것
# SQLALCHEMY_TRACK_MODIFICATIONS는 이벤트 처리 옵션 지금은 필요 없어서 False로 비활성화


config.py을 열어서 SECRET_KEY 환경 변수를 추가하겠습니다.

SECRET_KEY = "dev"는 위험한 설정입니다. 실제 서비스에서는 유추하기 쉬운 문자열을 입력하면 안 됩니다.

 

질문 등록 기능을 넣겠습니다. 먼저 질문 등록 버튼을 만들겠습니다.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% extends 'base.html' %}
    {% block content %}

    <div class="container my-3 text-center">
        <table calss="table">
            <thead>
                <tr class="bg-dark text-white">
                        <th> 번호 </th>
                        <th> 제목 </th>
                        <th> 작성일시 </th>
                </tr>
            </thead>

            <tbody>
                {% if question_list %}
                {% for question in question_list %}
                <tr>
                        <td>{{ loop.index }}</td>
                        <td><a href="{{ url_for('question.detail', question_id=question.id) }}">{{question.subject}}</a></td>
                        <td>{{ question.create_date  }}</td>
                </tr>
                {% endfor %}
                {% else %}
                <tr>
                    <td colspan="3"> 질문이 없습니다. </td>
                </tr>
                {% endif %}
            </tbody>
        </table>
        <a href="{{ url_for('question.create') }}" class="btn btn-primary"> 질문 등록하기 </a>
    </div>

    {% endblock %}

</body>
</html>

 

question_list.html 입니다.

 

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

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>')
def detail(question_id):
    question = Question.query.get_or_404(question_id)
    return render_template('question/question_detail.html', question=question)

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

question_views.py 입니다.

 

질문 등록 라우트 함수를 추가했습니다. 여기서 methods 방식을 2개로 적어주세요 (GET, POST)

 

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()])

pybo/forms.py 입니다.

 

FlaskForm을 상속받아 검증을 해주는 역할을 해줍니다.

 

subject 에는 폼 라벨 '제목'이고 validators=[DataRequired()] 반드시 작성하라는 검증입니다.

content도 위와 동일합니다.

 

(그 외에도 여러한 검증이 있습니다.)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% extends 'base.html' %}
    {% block content %}
    <div class="container">
      <h5 class="my-3 border-bottom pb-2">질문등록</h5>
      <form method="post" class="post-form my-3">

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

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

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

      </form>
    </div>
    {% endblock %}
</body>
</html>

question_form.html 입니다.

 

form.subject.label'제목'을 가져오고 form.subject()를 이용해 그러한 검증을 하는 기능이 있는 박스가 생성됩니다.

form.content도 또한 동일합니다.

 

 

 

 

저장해도 저장이 안 될 것입니다. 왜냐하면 db에 저장하는 과정을 추가를 안 했습니다.

추가해보도록 하겠습니다.

 

from flask import Blueprint, render_template, request, url_for
from ..models import Question
from werkzeug.utils import redirect
from .. import db

from ..forms import QuestionForm

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>')
def detail(question_id):
    question = Question.query.get_or_404(question_id)
    return render_template('question/question_detail.html', question=question)

@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)

question_views.py 입니다.

 

request.methodPOST방식인지 GET방식인지 알 수 있습니다.

form.validate_on_submit()의 경우는 폼 데이터의 정합성을 검사합니다. 

즉 DataRequired() 같은 점검 항목에 이상 없는지 확인해줍니다.

 

질문등록은 GET 방식이기 때문에 render_template을 보여주는 것이고

저장하기의 경우에는 POST방식으로 저장해야해서 methods 방식을 저렇게 한 것입니다.

 

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

또한 post-form 이기 때문에 post방식이라서 위에 post를 추가해준 것입니다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% extends 'base.html' %}
    {% block content %}

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

        {{ form.subject.label }}
        {{ form.subject(class="form-control") }}

        {{ form.content.label }}
        {{ form.content(class="form-control") }}

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

      </form>
    </div>

    {% endblock %}

</body>
</html>

question_form.html 입니다.

 

기능은 잘 작동 안 하지만 먼저 폼에 부트스트랩을 적용해보도록 하겠습니다.

저런식으로 class를 넣어서 부트스트랩을 적용시킬 수 있습니다.

 

위와 같은 방식으로하면 HTML 코드를 자동적으로 생성해 빠르게 만드는데 도움이 되지만

내가 원하는 디자인 적용하기나 속성 추가하기도 어렵습니다. 직접 다시 수정해서 만들어보도록 하겠습니다.

 

어떤 방식으로 하든 정답이 없지만 여기에서는 직접 HTML을 작성하는 방식으로 구성할 것입니다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% extends 'base.html' %}
    {% block content %}

    <div class="container">
      <h5 class="my-3 border-bottom pb-2">질문등록</h5>
      <form method="post" class="post-form my-3">
          <!-- 오류표시 Start -->
          {% for field, errors in form.errors.items() %}
              <div class="alert alert-danger" role="alert">
                  <strong>{{ form[field].label }}</strong>: {{ ','.join(errors) }}
              </div>
          {% endfor %}
          <!-- 오류표시 End -->
          <div class="form-group">
              <label for="subject">제목</label>
              <input type="text" class="form-control" name="subject" id="subject">
          </div>
          <div class="form-group">
              <label for="content">내용</label>
              <textarea class="form-control" name="content" id="content" rows="5"></textarea>
          </div>
          <button type="submit" class="btn btn-primary">저장하기</button>
      </form>
    </div>

    {% endblock %}

</body>
</html>

question_form.html 입니다.

 

이제 기능이 왜 작동 안 하는지 알아보도록 하겠습니다.

form.errors.items() 을 이용하면 에러를 여러개를 잡아줍니다. (제목 : field , 에러내용 : errors)

form[field].label오류 제목 errors라는 곳에는 오류 내용이 들어갑니다.

 

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% extends 'base.html' %}
    {% block content %}

    <div class="container">
      <h5 class="my-3 border-bottom pb-2">질문등록</h5>
      <form method="post" class="post-form my-3">
          {{ form.csrf_token }}
          <!-- 오류표시 Start -->
          {% for field, errors in form.errors.items() %}
              <div class="alert alert-danger" role="alert">
                  <strong>{{ form[field].label }}</strong>: {{ ','.join(errors) }}
              </div>
          {% endfor %}
          <!-- 오류표시 End -->
          <div class="form-group">
              <label for="subject">제목</label>
              <input type="text" class="form-control" name="subject" id="subject">
          </div>
          <div class="form-group">
              <label for="content">내용</label>
              <textarea class="form-control" name="content" id="content" rows="5"></textarea>
          </div>
          <button type="submit" class="btn btn-primary">저장하기</button>
      </form>
    </div>

    {% endblock %}

</body>
</html>

검증에 필요한 CSRF 토큰이 빠졌다라는 오류가 나오게 됩니다. 이걸 해결해 보겠습니다.

그냥 간단하게 {{ form.csrf_token }}를 추가시키면 됩니다.

 

 

 

잘 작동하게 됩니다. 하지만 오류메세지가 발생했을 때 입력한 값이 다 초기화되어서

다시 쳐야하는 불상사가 찾아옵니다. (티스토리에서 날려먹은 자료가 몇갠지.. 하..)

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% extends 'base.html' %}
    {% block content %}

    <div class="container">
      <h5 class="my-3 border-bottom pb-2">질문등록</h5>
      <form method="post" class="post-form my-3">
          {{ form.csrf_token }}
          <!-- 오류표시 Start -->
          {% for field, errors in form.errors.items() %}
              <div class="alert alert-danger" role="alert">
                  <strong>{{ form[field].label }}</strong>: {{ ','.join(errors) }}
              </div>
          {% endfor %}
          <!-- 오류표시 End -->
          <div class="form-group">
              <label for="subject">제목</label>
              <input type="text" class="form-control" name="subject" id="subject" value="{{ form.subject.data or '' }}">
          </div>
          <div class="form-group">
              <label for="content">내용</label>
              <textarea class="form-control" name="content" id="content" rows="5" value="{{ form.content.data or '' }}"></textarea>
          </div>
          <button type="submit" class="btn btn-primary">저장하기</button>
      </form>
    </div>

    {% endblock %}

</body>
</html>

request_form.html 입니다.

 

value="{{ form.subject.data or '' }}" 이거만 추가시켜주면 됩니다.

 

form.forms.py에서 작성한 변수.data 그 변수의 데이터를 보관해준다는 의미입니다. 

or ''의 경우는 데이터가 아무 것도 안 적혀있을 때 None 값이 나오게되는데 기본값을 ''으로 해준다는 의미입니다.

 

 

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('내용은 필수 입력 항목입니다.')])

forms.py 입니다.

 

나오는 오류 메세지를 한글로 바꿔보도록 하겠습니다. DataRequired 안에 추가만 시키면 됩니다.

 

 

반응형