본문 바로가기

데이터 분석/Flask

Flask - 2. Forms and User Input

728x90

ksh950510.tistory.com/15

 

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