폼 검증 (WTForms) — 폼을 클래스로 정의하고 검증하기

폼 검증 (WTForms) — 폼을 클래스로 정의하고 검증하기

브라우저 뷰가 제출한 폼 데이터를 다루다 보면 코드가 금방 읽기 어려워져요. 이 과정을 더 쉽게 관리하도록 설계된 라이브러리가 있는데, 그중 하나가 WTForms예요. 폼이 많아지는 상황이라면 한번 써 보는 걸 추천해요.

WTForms를 쓸 때는 먼저 폼을 클래스로 정의해야 해요. 앱을 여러 모듈로 나누고 폼용 별도 모듈을 추가하는 걸 권장해요.

출처: 공식문서 - Form Validation with WTForms

본문

확장으로 WTForms를 최대한 활용하기

Flask-WTF 확장은 이 패턴을 확장하고 폼과 Flask를 다루는 걸 더 즐겁게 해주는 몇 가지 작은 헬퍼를 추가해요. PyPI 에서 받을 수 있어요.

폼 정의

전형적인 등록 페이지용 예시 폼이에요.

from wtforms import Form, BooleanField, StringField, PasswordField, validators

class RegistrationForm(Form):
    username = StringField('Username', [validators.Length(min=4, max=25)])
    email = StringField('Email Address', [validators.Length(min=6, max=35)])
    password = PasswordField('New Password', [
        validators.DataRequired(),
        validators.EqualTo('confirm', message='Passwords must match')
    ])
    confirm = PasswordField('Repeat Password')
    accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()])

뷰에서 사용

뷰 함수에서 이 폼의 사용법은 이렇게 생겼어요.

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm(request.form)
    if request.method == 'POST' and form.validate():
        user = User(form.username.data, form.email.data,
                    form.password.data)
        db_session.add(user)
        flash('Thanks for registering')
        return redirect(url_for('login'))
    return render_template('register.html', form=form)

여기서 뷰가 SQLAlchemy를 쓴다고 암시하고 있지만, 그건 필수 조건이 아니에요. 필요에 따라 코드를 적응시키세요.

기억할 점들:

  1. 데이터가 HTTP POST 메서드로 제출되면 request의 :attr:~flask.request.form 값에서, GET으로 제출되면 :attr:~flask.request.args에서 폼을 만들어요.
  2. 데이터를 검증하려면 :func:~wtforms.form.Form.validate 메서드를 호출해요. 데이터가 검증되면 True, 그렇지 않으면 False를 반환해요.
  3. 폼에서 개별 값을 가져오려면 form.<NAME>.data에 접근해요.

템플릿에서 폼 렌더링

이제 템플릿 쪽이에요. 폼을 템플릿에 전달하면 거기서 쉽게 렌더링할 수 있어요. 다음 예시 템플릿을 보면 얼마나 쉬운지 알 수 있어요. WTForms가 폼 생성의 절반은 이미 해 주거든요. 더 깔끔하게 만들려면, 라벨과 오류 목록(있으면)을 가진 필드를 렌더링하는 매크로를 작성할 수 있어요.

:file:_formhelpers.html 템플릿에서 그런 매크로의 예시를 볼게요.

{% macro render_field(field) %}
  <dt>{{ field.label }}
  <dd>{{ field(**kwargs)|safe }}
  {% if field.errors %}
    <ul class=errors>
    {% for error in field.errors %}
      <li>{{ error }}</li>
    {% endfor %}
    </ul>
  {% endif %}
  </dd>
{% endmacro %}

이 매크로는 WTForms의 field 함수에 전달되는 몇 가지 키워드 인자를 받아요. field 함수는 우리를 위해 필드를 렌더링해 주죠. 키워드 인자들은 HTML 속성으로 삽입돼요. 예를 들어 render_field(form.username, class='username')을 호출하면 input 요소에 class를 추가할 수 있어요. WTForms는 표준 파이썬 문자열을 반환하므로, 이 데이터가 이미 HTML 이스케이프됐다는 걸 |safe 필터로 Jinja에 알려줘야 한다는 점을 기억하세요.

:file:register.html 템플릿은 위에서 쓴 함수용으로, :file:_formhelpers.html 템플릿을 활용해요.

{% from "_formhelpers.html" import render_field %}
<form method=post>
  <dl>
    {{ render_field(form.username) }}
    {{ render_field(form.email) }}
    {{ render_field(form.password) }}
    {{ render_field(form.confirm) }}
    {{ render_field(form.accept_tos) }}
  </dl>
  <p><input type=submit value=Register>
</form>

WTForms에 대한 더 많은 정보는 WTForms 웹사이트를 봐요.

더 알아보기