본문 바로가기
파이썬 웹 프로그래밍 기본

5. 입력 처리 - 5.2. Flask WTF를 이용한 입력처리

by 영바이트 2020. 8. 20.

클라이언트(사용자)로 부터 데이터를 받기 위해 폼(form)이라는 요소를 사용한다. 폼이라는 말이 생소하게 들릴수도 있겠지만 사실 일상 생활에서도 적지 않게 쓰인다. 예를 들어 관공서를 방문해서 민원 신청을 위해 제출하는 서류들을 '○○폼', '□□폼' 이라고 지칭하기도 한다. 결국 어떤 형식(format)에 맞게 빈칸을 채우라는 의미다.

 

웹 페이지의 폼 요소도 다르지 않다. 입력받을 데이터에 맞는 형식을 정의하고 해당 형식에 맞게 사용자에게 데이터 입력을 요청하게된다. 폼을 다루는 대표적인 파이썬 패키지인 Flask-WTF(What The Form)를 사용해서 사용자 입력을 받아보자.

 

폼을 다루기 위해서는 아래의 세 가지 요소를 고려해야 한다.

 

① 입력 형식 정의: 모델(model)/폼

② 사용자에게 보여지는 모양: 뷰(view)

③ 사용자가 데이터를 보냈을 때의 처리: 로직(logic) 또는 컨트롤(control)

 

가장 먼저 어떤 데이터를 어떤 형식으로 받겠다고 모델/폼 정의가 먼저 이루어지는 것이 순서상 자연스럽다. 두 번째로 입력할 데이터를 사용자에게 어떻게 보여주는가 하는 부분이다. 이 부분을 뷰(view)라고 부른다. 보통 HTML 엘리먼트로 데이터 입력 페이지를 구성한다. 마지막으로 사용자가 데이터를 입력하고 전송하면 웹 서버에서 이를 받아 처리하는 부분이다. 이 부분을 로직(logic) 또는 컨트롤(control)이라고 한다. 정리하면 아래 순서로 사용자 데이터입력을 다루게 된다.

 


모델/폼 정의 → 뷰 구현 → 로직(컨트롤) 구현

 

실제 예를 통해 살펴보자. 03_form_user_input을 이름으로 프로젝트 디렉토리를 생성하고, 그 아래 static, templates 디렉토리를 생성하자.

 

<프로젝트 디렉토리 생성>

 

파이참의 File → Settings → Project Interpreter → pip을 선택하고 Flask-WTF 패키지를 검색해서 설치한다.

 

<Flask-WTF 패키지 검색 및 설치>

 

같은 방법으로 email-validator 패키지도 설치한다.

 

<email-validator 패키지 검색 및 설치>

 

03_form_user_input 디렉토리 아래에 forms.py 파일을 생성하자. 이 파일 안에 폼을 정의할 것이다. 입력받을 데이터는 사용자 계정 생성을 위해 필요한 내용으로 이름(username), 이메일 주소(email), 그리고 로그인을 위한 암호(password) 세 가지이고, 계정 생성 후 로그인을 위해 이메일 주소와 암호를 입력 받을 것이다.

 

<사용자 입력 폼을 정의할 파일 생성>

 

▶ 03_form_user_input/forms.py

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, EqualTo, Email

class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired(), Length(min = 2, max = 20)])
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    confirmPassword = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField("Sign Up")

class LoginForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField("Sign In")

 

아래와 같이 flask_wtf 패키지에서 필요한 클래스들을 가져온다.

FlaskForm 입력 데이터의 전송 등과 같이 기본적인 기능을 제공하는 클래스이다.

 

StringField 문자열 입력 창에 사용되는 데이터 형식을 정의한다.
PasswordField 암호 입력 창에 사용되는 데이터 형식을 정의한다.
SubmitField 제출(submit) 버튼을 정의한다.

 

DataRequired 입력된 데이터가 있는지 검증한다.
Length 입력된 데이터 길이가 정해진 조건을 만족하는지 검증한다.
EqualTo 입력한 데이터가 어떤 다른 데이터와 같은지 검증한다.
Email 입력한 데이터가 이메일 형식에 맞는지 검증한다.

 

이들 데이터 형식과 검증클래스들은 입력받을 데이터형식 지정에 사용된다. 사용자이름(username)을 예로 살펴보자.

 

username = StringField('Username', validators=[DataRequired(), Length(min = 2, max = 20)])

→ 사용자이름(username) 변수는 문자열(StringField) 객체이며 객체의 이름(label)은 첫 번째 인자인 'Username'이 된다. 사용자가 폼의 사용자이름 필드에 데이터를 입력했을 때 validators 리스트에 지정된 조건들로 입력 데이터가 검증된다. 첫 번째 조건인 DataRequired()는 비워놓을 수 없다는 조건이고, 두 번째 Length() 조건은 입력 데이터의 길이를 한정하게 된다.

 

다음 순서로 데이터 입력 페이지(=뷰, view)를 구현한다. templates 디렉토리 아래에 register.html 파일을 만들고 아래 내용을 입력해보자.

 

▶ 03_form_user_input/templates/register.html

{%extends "layout.html"%}
{%block content%}
<div>
    <form method="POST" action="">
        {{form.hidden_tag()}}   {# Transferring CSRF token = secret key #}
        <div>
            {{form.username.label}}
            {{form.username}}
            {%if form.username.errors%}
                <div>
                    {%for error in form.username.errors%}
                        <span>{{error}}</span>
                    {%endfor%}
                </div>
            {%endif%}
        </div>
        <div>
            {{form.email.label}}
            {{form.email}}
            {%if form.email.errors%}
            <div>
                {%for error in form.email.errors%}
                    <span>{{error}}</span>
                {%endfor%}
            </div>
            {%endif%}
        </div>
        <div>
            {{form.password.label}}
            {{form.password}}
            {%if form.password.errors%}
            <div>
                {%for error in form.password.errors%}
                    <span>{{error}}</span>
                {%endfor%}
            </div>
            {%endif%}
        </div>
        <div>
            {{form.confirmPassword.label}}
            {{form.confirmPassword}}
            {%if form.confirmPassword.errors%}
            <div>
                {%for error in form.confirmPassword.errors%}
                    <span>{{error}}</span>
                {%endfor%}
            </div>
            {%endif%}
        </div>
        <div>
            {{form.submit}}
        </div>
    </form>
</div>
<div>
    <small>
        Already Have An Account?<a href="{{url_for('login')}}"> Sign In</a>
    </small>
</div>
{% endblock content %}

 

코드의 길이가 짧지는 않지만 아래 요소가 반복되고 있음을 볼 수 있다.

{{form.username.label}}
{{form.username}}
{%if form.username.errors%}
    <div>
        {%for error in form.username.errors%}
            <span>{{error}}</span>
        {%endfor%}
    </div>
{%endif%}

 

{{form.username.label}}

→ form 변수를 통해 전달받은 폼 클래스의 username 변수(= 문자열 객체)의 이름(label)을 출력한다.

 

{{form.username}}

→ form 변수를 통해 전달받은  클래스의 username 변수를 화면에 출력한다. 문자열을 입력할 수 있는 빈 창이 표시된다.

 

{%if form.username.errors%}

    <div>

        {%for error in form.username.errors%}

            <span>{{error}}</span>

        {%endfor%}

    </div>

{%endif%}

→ 폼 정의의 validators에 지정된 검증 조건(여기서는 DataRequired, length 조건)을 만족하지 못하면 (앞으로 살펴볼) 로직 부분에서 errors 리스트가 반환된다. 입력한 데이터의 오류에 관한 errors 리스트 요소들을 뷰를 통해 사용자에게 알려준다.

 

<form method="POST" action="">

→ <form> tag의 method 속성의 값을 POST로 지정하였다. 사용자가 입력한 데이터를 POST 방식으로 전달한다.

클라이언트는 GET 방식과 POST 방식 두 가지로 서버에 데이터를 전송한다. 
GET 방식은 URL에 데이터를 포함시켜 보내는 방식으로, URL 주소 뒤에 '?var=value'와 같은 접미어 형태로 데이터가 전달된다.
POST 방식은 HTTP 메시지 본문(body)에 데이터를 포함시켜 서버로 전송하는 방식으로 전달되는 데이터가 사용자에게 바로 보이지 않는다. 일반적으로 데이터를 노출시키고 싶지 않거나 비교적 크기가 큰 데이터를 전달할 때 POST 방식을 이용한다.

 

마지막으로 로직을 구현해보자. 로직은 registration 폼과 register.html 뷰를 연결한다. 폼과 관련된 내용에 집중하기 위해 다른 내용들은 일단 생략하였다.

 

▶ 03_form_user_input/templates/form_user_input.py

...

@app.route("/register", methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        print("Account created successfully.")
        return redirect(url_for('home'))
    else:
        return render_template('register.html', title = 'Register', form = form)

...

 

@app.route 데코레이터를 보면 데이터 전달 방식을 지정하는 methods 변수를 ['GET', 'POST']로 설정하였다. GET방식과 POST 방식을 모두 사용하여 데이터를 전달하게끔 하였다.

 

form = LoginForm()

→ register() 함수를 보면 form 변수에 RegistrationForm 객체가 할당되어 render_template() 함수를 통해 뷰 파일인 register.html로 전달된다. 템플릿 엔진은 전달받은 form 변수를 이용해서 register.html 안에서 폼 요소들을 이용할 수 있게된다.

 

if form.validate_on_submit():

→ 폼 정의(forms.py)를 보면 SubmitField가 정의되어 있고 이 SubmitField는 뷰 파일(register.html) 안에서 '제출(submit)' 버튼으로 표시된다. 이 '제출' 버튼을 사용자가 클릭하면 validate_on_submit() 함수는 True를 반환하게 된다. 즉, 사용자가 입력한 데이터를 받아서 처리하는 코드를 이 if 조건문 아래에 코드로 기술하면 된다. 아직 데이터베이스 등 데이터 저장에 대해 살펴보지 않았으므로 예에서는 '계정이 성공적으로 생성되었음.'이라는 메시지를 출력하는 것으로 처리를 대신하였다.

 

return redirect(url_for('home'))

→ 플라스크의 redirect()함수는 인자로 전달받은 경로의 페이지를 클라이언트에 전송하여 해당 페이지로 사용자를 이동시킨다. url_for('home')은 home()함수를 찾아서 URL 접근 경로를 반환한다.

 

모델/폼, 뷰, 로직(컨트롤)의 관계를 다시 한번 그림으로 정리하고 이번 글을 마무리하려 한다.

 

<폼, 뷰, 로직의 관계>

 

처음에는 익숙하지 않고 어렵게만 생각될 수 있다. 처음부터 모든 내용을 속속들이 알 수 없는 것이 당연하다. 폼(forms.py), 뷰(register.html), 그리고 로직(form_user_input.py) 사이의 관계를 기억하고 갔으면 좋겠다.

 

이어지는 글에서 사용자 입력처리 프로젝트 예를 완성하고 그 동작을 살펴보면서 사용자 입력 처리와 관련한 내용을 다시 한번 살펴보도록 하겠다.

 

댓글