FlaskForm
계정에 로그인하고 상품을 주문하거나 설문조사를 하는 것과 같이 사용자로부터 정보를 입력받는 방식
Content
- Flask에서 Form 활용을 위한 프로젝트 폴더 및 파일 관리
- form.py
- run.py
- register.py
form.py
pip install Flask-WTF
Flask Framework의 폼 검증 모듈로서, 쉽게 폼을 생성 할 수 있으며
json 데이터 상호 작용을 위한 검증도구로도 사용 가능
#form.py
#회원 가입을 위한 form을 구성하는 파일을 만듦
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, Email, EqualTo
class RegistrationForm(FlaskForm):
username = StringField("아이디",
validators=[DataRequired(), Length(min=4, max=20)])
email = StringField("이메일",
validators=[DataRequired(), Email()])
password = PasswordField("비밀번호",
validators=[DataRequired(), Length(min=4, max=20)])
confirm_password = PasswordField("비밀번호 확인",
validators=[DataRequired(), EqualTo("password")] )
submit = SubmitField("가입")
FlaskWTF의 FlaskForm를 불러오는데, 내가 원하는 form을 만들기 위해서는 FlaskForm이라는 부모 클래스를 상속 받아 자식 클래스를 만들어 사용하면 된다.
==> 자식클래스를 선언할때 소괄호로 부모클래스를 포함시키게 되면 자식클래스에서는 부모클래스의 속성과 메소드는 기재하지 않아도 알아서 포함이 된다.
wforms
wtforms라는 라이브러리를 사용한다.
문자열인지(StringField),
화면에 표시되지 않아야 할 패스워드인지(PasswordField),
제출 버튼인지(SubmitField)에 따라 원하는 폼을 지정할 수 있다.
# core.py
__all__ = (
'BooleanField', 'DecimalField', 'DateField', 'DateTimeField', 'FieldList',
'FloatField', 'FormField', 'IntegerField', 'RadioField', 'SelectField',
'SelectMultipleField', 'StringField',
)
BooleanField : True or False
StringField : 문자열
DecimalField : 소수점 텍스트 필드 ex)‘1.23’
DateField : 날짜 필드,형식:'%Y-%m-%d'
DateTimeField: 날짜 필드,형식:'%Y-%m-%d %H:%M:%S'
FloatField : 부동소수점 유형
IntegerField : 정수
SelectMultipleField:체크박스
RadioField : 라디오박스
# simple.py
TextAreaField : 텍스트 필드(여러줄 입력)
PasswordField : 패스워드(보여지지 않음)
FileField :파일을 업로드(파일 확인은 하지 않으며, 확인은 수동으로 처리해야함)
HiddenField:숨겨진 필드
SubmitField:제출 필드
wtforms.validators
wtforms.validators를 통해
필수입력값인지(DataRequired),
길이는 어떻게 제한하는지(Length),
이메일인지(Emil),
이미 입력한 값과 같은 값을 입력했는지(EqualTo)
등의 유효성 검사를 할 수 있다.
__all__ = (
'DataRequired', 'data_required', 'Email', 'email', 'EqualTo', 'equal_to',
'IPAddress', 'ip_address', 'InputRequired', 'input_required', 'Length',
'length', 'NumberRange', 'number_range', 'Optional', 'optional',
'Required', 'required', 'Regexp', 'regexp', 'URL', 'url', 'AnyOf',
'any_of', 'NoneOf', 'none_of', 'MacAddress', 'mac_address', 'UUID'
)
모듈에는 data_required에 해당하는 DataRequired와 같이 대문자와 소문자에 해당하는 모드가 있습니다.
DataRequired/data_required :데이터가 실제로 존재하는지, 즉 비어있을 수 없는지, 공백이 아닌 문자열이어야하는지 확인. 그렇지 않으면 StopValidation 오류가 트리거가된다.
InputRequired/input_required :DataRequired와의 차이점은 빈 문자열 일 수 있다는 것.
Required/required :data_required의 별칭
Email/email :가장 기본적인 이메일 형식
EqualTo/equal_to :비밀번호와 비밀번호 확인과 같은 두 필드의 값을 비교. 두 필드가 같지 않으면 오류 발생. equal_to (field, message), 다른 필드의 이름을 입력해야함.
IPAddress/ip_address :IP 주소인지 확인(기본적으로 IPV4 주소가 확인됨)
MacAddress/mac_address :Mac 형식을 준수하는지 확인
UUID:uuid 형식인지 여부.
URL/url :URL 형식을 준수하는지 확인
Regexp/regexp :제공된 정규식을 사용하여 필드 유효성 검사, Regexp (r "")
Length/length :필드 값의 길이를 설정, Length (min, max);
NumberRange/number_range :숫자 필드의 값 범위를 설정(부동 소수점 숫자 및 소수일 수도 있음) NumberRange(min, max)
run.py
from flask import Flask, render_template, url_for, flash, redirect
from forms import RegistrationForm
app = Flask(__name__)
#CSRF(Cross-Site Request Forgery)
app.config["SECRET_KEY"] = 'd2707fea9778e085491e2dbbc73ff30e'
@app.route('/')
def home():
return render_template('layout.html')
@app.route('/register', methods=["GET", "POST"])
def register():
form = RegistrationForm()
if form.validate_on_submit():
# 알람 카테고리에 따라 부트스트랩에서 다른 스타일을 적용 (success, danger)
flash(f'{form.username.data} 님 가입 완료!', 'success')
return redirect(url_for('home'))
return render_template('register.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
1. CSRF(Cross-Site Request Forgery)
app.config["SECRET_KEY"] = 'd2707fea9778e085491e2dbbc73ff30e'
SECRET_KEY는 CSRF 공격을 방지하는데 사용한다.
폼으로 전송된 데이터가 실제 웹 페이지에서 작성된 데이터인지를 판단해 주는 가늠자 역할을 한다.
SECRET_KEY 값을 생성할 때 파이썬 secrets 모듈을 활용 가능
import secrets 한 후 secrets.token_hex(16)라고 적어주면 이렇게 복잡한 문자열이 하나 생성된다.
2. HTTP 메소드
웹에서 클라이언트가 서버와 통신 할 때 마다 요청(request)을 하게된다.
Flask에서 웹 페이지를 route()하게 되면 기본적으로 GET 요청만 지원한다.
method를 지정하지 않으면 기본값은 GET
@app.route('/register', methods=["GET", "POST"])
하지만 form을 입력해서 그 양식을 제출하면 POST 요청으로 전송
** HTTP 프로토콜 **
GET : 정보를 요청하기 위해서 사용한다. (SELECT)
POST : 정보를 밀어넣기 위해서 사용한다. (INSERT)
PUT : 정보를 업데이트하기 위해서 사용한다. (UPDATE)
DELETE : 정보를 삭제하기 위해서 사용한다. (DELETE)
3. 함수 작성
3.1 GET 요청
단순히 /register에 접속했다면(POST아닌 GET이라면)render_template('register.html', form=form)을 돌려줘야 한다.
3.2 POST 요청
만약 클라이언트가 POST요청을 통해 Form을 정상적으로 제출했다면 form.validate_on_submit()로 확인하여 요청을 처리 한다. form.validate_on_submit() 함수는 전송된 폼 데이터의 정합성을 점검한다.
즉, 폼을 생성할 때 각 필드에 지정한 DataRequired()같은 점검 항목에 이상이 없는지 확인한다.
* DataRequired : 데이터가 실제로 존재하는지
redirect(url_for('home'))을 반환하여 home이라는 뷰 함수와 연결된 곳,
즉 루트 URL로 리다이렉트 시켰다. (url_for() 안에는 URL을 써주는 게 아니라 뷰 함수이름을 써줘야 한다.)
flash() : 부트스트랩을 사용해서 알림 메시지를 띄우기 위해 사용
url_for() : url 주소값을 가져 온다. url_for() 괄호안에 route 함수 명을 넣으면 해당 route가 가지는 rul 주소를 가져온다.
redirect() : 말 그대로 요청을 다시 지시하는 것임.
** redirect가 필요한 이유
카페에 가입되어 있거나 혹은 카페에 가입되어 있다고 하더라고 특정 등급이 되어야 열람할 수 있는 글들이 있다. 만약 해당 글의 URL을 알아내어 브라우저의 주소창에 입력한 후 접근한다고 했을때 권한이 없다면 글에 대한 내용을 웹서버가 응답하면 안된다. 이때 웹서버는 글에 접근하는 요청마다 권한을 검사하고 권한이 되지 않는 경우 권한을 확인하세요 라는 경고 문구가 뜨는 페이지나 혹은 네이버 계정이 로그인 되어 있지 않은 상태라면 로그인 페이지로 리다이렉트 시킬 필요가 있습니다.
register.html
<form action="/" method="post">
{{ form.hidden_tag() }}
{{ form.username.label }}
{{ form.username() }}
{{ form.email.label }}
{{ form.email() }}
{{ form.password.label }}
{{ form.password() }}
{{ form.confirm_password.label }}
{{ form.confirm_password() }}
{{ form.submit() }}
</form>
{{ template_form.hidden_tag() }} : 화면에 표시되지는 않지만 CSRF로부터 보호하는 데 필요한 작업을 처리한다
{{ form.username.label() }}과 같은 식으로 레이블을 가져오고,
이어서 양식을 작성할 부분에는 {{ form.username }}를 넣어준다.
----------------------------------------------------------------------------------------------------------------
부트스트랩 적용
layout.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
</head>
<body>
<main role="main" class="container">
<div class="row">
<div class="col-md-8">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}
{% endblock %}
</div>
</div>
</main>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>
</body>
</html>
register.html
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<form method="post" action="">
{{ form.hidden_tag() }}
<fieldset class="form-group">
<legend class="border-bottom mb-4">가입하기</legend>
<div class="form-group">
{{ form.username.label(class="form-control-label") }}
{% if form.username.errors %}
{{ form.username(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.username.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.username(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.email.label(class="form-control-label") }}
{% if form.email.errors %}
{{ form.email(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.email.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.email(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.password.label(class="form-control-label") }}
{% if form.password.errors %}
{{ form.password(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.password(class="form-control form-control-lg") }}
{% endif %}
</div>
<div class="form-group">
{{ form.confirm_password.label(class="form-control-label") }}
{% if form.confirm_password.errors %}
{{ form.confirm_password(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.confirm_password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.confirm_password(class="form-control form-control-lg") }}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
{% endblock content %}
Result
'Backend > Python' 카테고리의 다른 글
Python Multithread vs Multiprocessing (0) | 2021.12.29 |
---|---|
Python Thread (0) | 2021.12.28 |
Python Flask 웹 페이지 제작(2) - Jinja2 템플릿 (2) | 2021.12.21 |
Python Flask 웹 페이지 제작(1) - 구조, Route (0) | 2021.12.21 |
Python Flask + mysql 웹 기반 어플리케이션 제작 (0) | 2021.11.27 |