Flask 기본설정
1. Flask 설치 및 설정 $ pip install flask 1-1) 제대로 설치 되었는지 확인하기 # on python import flask 오류가 발생하지 않으면 제대로 설치된 상태입니다. 1-2) app을 저장할 새로운 디렉터리 생성 # 폴더..
ksh950510.tistory.com
이후의 내용을 다루고 있습니다.
1. Flask - WTform
이번에는 웹페이지에 사용자들이 계정을 생성하고, 로그인을 할 수 있는 기능을 구현하려고 합니다.
혼자서 처음부터 구현하려고 하면, 암호화 문제도 발생하고, 유효한 이메일인지 구분하는것도 꽤 오래걸릴겁니다.
그런 문제점을 겪은 프로그래머들이 고생해서 만든 WTform이란걸 사용해봅시다.
$ pip install flask-wtf
# on Flask_app folder
$ touch forms.py
이번 파트는 프론트엔드 개발에 관심이 없다면 깊게 이해하지 않아도 됩니다.
하지만 웹디자이너가 아니여도 템플릿을 가져와서 임의로 고친다던가, 대규모 프로젝트에서 다른 웹 개발자가 만든 HTML 코드를 이해하려면 직접돌려보면서 작동방식을 이해하는게 도움이 될것이라 생각합니다.
전체적인 코드와 그에대한 주석을 첨부하는식으로 게시물을 작성했습니다.
1) wtform(flask-wtf)을 설치 한 후 flask 폴더 안에 forms.py 파일을 생성합니다.
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
# 문자열, 비밀번호, 제출, 제약조건 을 위한 필드
from wtforms.validators import DataRequired, Length, Email, EqualTo
# 유효성 검사기 : 데이터의 유무, 글자수, 이메일, 동일성 확인
# FlaskForm에 상속되는 클래스 생성
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(),Length(min=2, max=20)])
# 유저는 문자열 필드로 선언하고, 이름은 Username
# 제한사항 : 공백금지, 글자수 제한(2~20)
email = StringField('Email', validators=[DataRequired(), Email()])
# 제한사항 : 공백금지, 유효한 이메일
password = PasswordField('Password', validators=[DataRequired()])
# 제한사항 : 공백금지
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
# 제한사항 : 공백금지, 비밀번호와 같아야됨
submit = SubmitField('Sing Up')
# 회원가입
# 로그인 양식, 등록 양식에서 조금만 수정해주면 된다.
class LoginForm(FlaskForm):
email = StringField('Email',
validators=[DataRequired(), Email()])
# 유저이름은 잘 잊어먹으므로 이메일을 로그인 양식으로 사용할것입니다.
password = PasswordField('Password', validators=[DataRequired()])
# 비밀번호를 확인하는 파트는 필요없어집니다.
remember = BooleanField('Remeber Me')
# 제약조건을 통한 로그인 유지
submit = SubmitField('Login')
# 로그인
2) 만든 form을 인식하기 위해서 Flask_app.py(메인 스크립트)에도 코드를 추가해줍시다.
from flask import Flask, render_template, flash, redirect, url_for
from forms import RegistrationForm, LoginForm
# forms.py 에서 Form 클래스들을 호출
app = Flask(__name__)
app.config['SECRET_KEY'] = '최하단에 시크릿키를 만드는코드를 첨부했습니다'
# app에서 2가지 폼을 사용하기 위해 시크릿 키를 사용합니다.
# 시크릿 키는 쿠키 수정(cookie modify)과, 교차 사이트 요청 위조과 같은 간단한 해킹을 막아줍니다.
post1 = [
{
'author': 'Sooho Kim',
'title': 'Post 1',
'content': 'First post content',
'date_posted': 'March 23, 2021'
},
{
'author': 'Jane Doe',
'title': 'Post 2',
'content': 'Second post content',
'date_posted': 'March 25, 2021'
}
]
@app.route('/')
@app.route('/home')
def home():
return render_template('home.html', posts=post1)
@app.route('/about')
def about():
return render_template('about.html', title='About')
@app.route('/register', methods=['GET', 'POST'])
# register 페이지에서 발생하는 함수, 이 페이지에서는 get과 post 명령을 사용할 수 있음
def register():
# register 페이지에 RegistrationForm() 클래스가 작동하게 만들어줌
form = RegistrationForm()
# 여기의 폼은 forms.py에서 선언한 Regist~~form이고,
if form.validate_on_submit():
# 만약 제출한 폼이 유효성 검사에 통과했다면 아래의 코드를 실행한다.
flash(f'Account created for {form.username.data}!', 'success')
return redirect(url_for('home'))
# 유저 등록이 성공적으로 될시에 위와같은 일회성 문구를 전송하고, 홈페이지로 돌아갑니다.
return render_template('register.html', title='Register', form=form)
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# 만약 제출한 폼이 유효성 검사에 통과했다면 아래의 코드를 실행한다.
if form.email.data == 'admin@blog.com' and form.password.data == 'password':
# 데이터 베이스를 얻기 전까지 임시적인 방법입니다.
# 만약 admin@blog.com으로 로그인 양식을 제출하고, password에 'password'를 입력한다면
flash('You have been logged in!', 'success')
return redirect(url_for('home'))
else:
flash('Login Unsuccessful. Please check username and password', 'danger')
return render_template('login.html', title='Login', form=form)
# login 페이지에 LoginForm() 클래스가 작동하게 만들어줌
if __name__ == '__main__':
app.run(debug=True)
3) register.html과 login.html을 만들어줍시다.
register.html
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<!-- content-section은 main.css파일에 있는 스타일입니다. -->
<form method="POST" action="">
<!-- 동일한 경로에 해당정보를 우리가 있는 동일한 경로에 게시(POST)한다. -->
{{ form.hidden_tag() }}
<!-- hidden_tag는 간단한 해킹으로 부터 보호하는 역할을 합니다 -->
<fieldset class="form-group">
<legend class="border-bottom mb-4">
<!-- 등록양식의 폼이 됩니다. 아래의 문구는 register 페이지로 갈시 뜨는 문구입니다.-->
Join Today
</legend>
<!-- username form -->
<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>
<!-- email form -->
<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>
<!-- password form -->
<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>
<!-- confirm_password form -->
<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>
<!-- submit 버튼 -->
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
<div class="border-top pt-3">
<small class="text-muted">
<!-- 제출 폼 마지막에 이미 계정이 있는지 묻는 문구와 함께 로그인 링크 제공 -->
Already Have An Account? <a class="ml-2" href="{{ url_for('login') }}">Sign In</a>
</small>
</div>
{% endblock content %}
login.html
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<!-- content-section은 main.css파일에 있는 스타일입니다. -->
<form method="POST" action="">
<!-- 해당정보를 우리가 있는 경로에 게시(POST)한다. -->
{{ form.hidden_tag() }}
<!-- hidden_tag는 간단한 해킹으로 부터 보호하는 역할을 합니다 -->
<fieldset class="form-group">
<legend class="border-bottom mb-4">
<!-- 등록양식의 폼이 됩니다. 아래의 문구는 register 페이지로 갈시 뜨는 문구입니다.-->
Log In
</legend>
<!-- email form -->
<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>
<!-- password form -->
<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>
<!-- 로그인 정보를 기억할지 정하는 form -->
<div class="form-check">
{{ form.remember(class="form-check-input") }}
{{ form.remember.label(class="form-check-label") }}
</div>
<small class="text_muted ml-2">
<a href="#">Forgot Password?</a>
<!-- 더미링크 -->
</small>
</fieldset>
<!-- submit 버튼 -->
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
<div class="border-top pt-3">
<small class="text-muted">
<!-- 제출 폼 마지막에 이미 계정이 있는지 묻는 문구와 함께 로그인 링크 제공 -->
Need An Account? <a class="ml-2" href="{{ url_for('register') }}">Sign UP Now</a>
</small>
</div>
{% endblock content %}
layout.html
<!DOCTYPE html>
<html>
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">
{% if title %}
<title>Flask app - {{ title }}</title>
{% else %}
<title>Flask app</title>
{% endif %}
</head>
<body>
<header class="site-header">
<nav class="navbar navbar-expand-md navbar-dark bg-steel fixed-top">
<div class="container">
<a class="navbar-brand mr-4" href="/">Flask Blog</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggle" aria-controls="navbarToggle" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarToggle">
<div class="navbar-nav mr-auto">
<a class="nav-item nav-link" href="{{ url_for('home') }}">Home</a>
<a class="nav-item nav-link" href="{{ url_for('about') }}">About</a>
</div>
<!-- Navbar Right Side -->
<div class="navbar-nav">
<a class="nav-item nav-link" href="{{ url_for('login') }}">Login</a>
<a class="nav-item nav-link" href="{{ url_for('register') }}">Register</a>
</div>
</div>
</div>
</nav>
</header>
<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 class="col-md-4">
<div class="content-section">
<h3>Our Sidebar</h3>
<p class='text-muted'>You can put any information here you'd like.
<ul class="list-group">
<li class="list-group-item list-group-item-light">Latest Posts</li>
<li class="list-group-item list-group-item-light">Announcements</li>
<li class="list-group-item list-group-item-light">Calendars</li>
<li class="list-group-item list-group-item-light">etc</li>
</ul>
</p>
</div>
</div>
</div>
</main>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body>
</html>
SECRET_KEY 만드는 코드
# on terminal
import secrets
secrets.token_hex(16)
'888820cfdd1ba89feb0d91f49f3d2819'
# 위와 같은 16글자의 랜덤한 토큰이 반환됩니다.
'데이터 분석 > Flask' 카테고리의 다른 글
JSON (JavaScript Object Notation) (0) | 2021.03.30 |
---|---|
Flask - 패키지 구조화 (0) | 2021.03.27 |
Flask - SQLAlchemy(ORM) (0) | 2021.03.27 |
HTTP (0) | 2021.03.26 |
Flask - 1. 기본설정, 템플릿, 부트스트랩 (0) | 2021.03.23 |