클라이언트(사용자)로 부터 데이터를 받기 위해 폼(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 패키지를 검색해서 설치한다.
같은 방법으로 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 | 입력한 데이터가 어떤 다른 데이터와 같은지 검증한다. |
입력한 데이터가 이메일 형식에 맞는지 검증한다. |
이들 데이터 형식과 검증클래스들은 입력받을 데이터형식 지정에 사용된다. 사용자이름(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) 사이의 관계를 기억하고 갔으면 좋겠다.
이어지는 글에서 사용자 입력처리 프로젝트 예를 완성하고 그 동작을 살펴보면서 사용자 입력 처리와 관련한 내용을 다시 한번 살펴보도록 하겠다.
'파이썬 웹 프로그래밍 기본' 카테고리의 다른 글
6. 패키지 - 6.1. 개념 소개 (0) | 2020.08.25 |
---|---|
5. 입력 처리 - 5.3. Flask WTF를 이용한 입력처리 프로젝트 (0) | 2020.08.21 |
5. 입력 처리 - 5.1. 비밀 토큰(secret token) (0) | 2020.08.18 |
4. 웹 페이지 - 4.2. 템플릿 예제 코드분석 (0) | 2020.08.17 |
4. 웹 페이지 - 4.1. 템플릿(template) 엔진 (0) | 2020.08.16 |
댓글