python/웹

[파이썬 웹] flask를 이용한 웹페이지

끼발자 2021. 10. 22. 17:20
반응형

https://github.com/giho5374/flask_web 

 

간단하게 웹페이지를 만들어 보았다.

registerform은 

https://velykitty-itnote.tistory.com/43

여기에 있는 폼을 조금 수정했다.

 

db설계 -> 회원가입 -> 로그인 -> 글쓰기등의 기능만 구현한 상태이며, 

html 예쁘게 꾸며보겠다고 시도하다 포기하고 초라한 외형만 구현했다.

node나 vue같은 툴을 다룰 줄 알았으면 api형태로 만들었겠지만,

아직 가야할 길이 멀다.

 

구조는 대강 아래와 같다.

 

실행파일에는 config와 세션 만료의 기능만 담겨있고,

fun에 uri, db_model에 모델 구성정보가 정의되어있다.

앞선 포스트에서 언급했듯, fun은 blueprint를 사용했다.

 

# app.py
from flask import Flask,session
from flask_sqlalchemy import SQLAlchemy
from datetime import timedelta

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db?check_same_thread=False'
app.config['SQLALCHEMY_ECHO'] = True # sql log등이 찍힌다
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True # 이벤트 처리 옵션이라고 하는데 자세히는 모르겠다.
app.secret_key = '12345'
db = SQLAlchemy(app)
db.create_all()

@app.before_request # 요청 전에 실행되는 데코레이터
def before_request():
    session.permanent = True
    app.permanent_session_lifetime = timedelta(minutes=1) # 세션은 1분마다 초기화 된다.

if __name__ == '__main__':
    from fun import app_fun
    app.register_blueprint(app_fun)
    app.run(debug=True)

우리는 app.py를 실행하면 되지만, 이 안에서 flask 서버를 실행만 시킬 뿐, 실질적인 기능은 세션 초기화밖에없다.

blueprint를 사용해서 기능만 따로 분류했다.

 


#db_model.py
from datetime import datetime
from app import db
from werkzeug.security import generate_password_hash, check_password_hash

class User(db.Model):
    __table_name__ = 'user'

    user_id = db.Column(db.String(30),primary_key = True, unique = True)
    user_name = db.Column(db.String(30),nullable=False)
    user_email = db.Column(db.String(120),nullable=False)
    user_pw = db.Column(db.String(100),nullable=False)
    created = db.Column(db.DateTime)
    post = db.relationship('Post',backref = 'author')

    def __init__(self,user_id,user_name,user_email,user_pw,**kwargs):
        self.user_email =user_email
        self.user_name = user_name
        self.user_id = user_id
        self.set_password(user_pw)
        self.created = datetime.now()

    def set_password(self, user_pw):
        self.user_pw = generate_password_hash(user_pw)

    def check_password(self,hash_pw,input_pw):
        return check_password_hash(hash_pw,input_pw)

class Post(db.Model):
    __table_name__ = 'post'
    id = db.Column(db.Integer,primary_key = True)
    user_id = db.Column(db.Integer,db.ForeignKey('user.user_id'))
    title = db.Column(db.String(500), nullable = False)
    content = db.Column(db.Text ,nullable = False)
    create_time = db.Column(db.DateTime)

 

평범한 데이터베이스 설계다.

회원가입에 필요한 유저정보를 담았고, 포스트한 내용을 담을 테이블을 하나 만들었다.

from werkzeug.security import generate_password_hash, check_password_hash

를 통해서 비밀번호를 해시로 바꾼 뒤, 확인하는 암호화 작업을 거친다.

 

from werkzeug.security import generate_password_hash, check_password_hash


pwd = 'password'
hashed_pwd = generate_password_hash(pwd)
#'pbkdf2:sha256:260000$VL6k9vM3nx7xIOIT$b48faeebe2b10a0744146b8c1bb76835b3a14baa5071ac13bfa6b204e662bc1c'

이렇게 해시값을 비교해서 bool 을 반환해준다.

 

# fun.py
from flask import Blueprint, render_template, redirect, flash, session, request, url_for
from werkzeug.security import check_password_hash
from db_model import User, Post
from app import db
from datetime import datetime

app_fun = Blueprint('app_fun', __name__)


def login_check():
    return 'userid' not in session


def session_id_check():
    return User.query.filter(User.user_id == session['userid']).first()


@app_fun.route('/', methods=['GET', 'POST'])
def index():
    if not login_check():
        return redirect('main')
    return render_template('index.html')


@app_fun.route('/main')
def main():
    if login_check():
        flash('로그인이 필요한 서비스입니다.')
        return redirect('/')

    return render_template('main.html', userid=session_id_check().user_name)


@app_fun.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        try:
            pw = User.query.filter(User.user_id == request.form['id']).first().user_pw
        except:
            flash('ID가 등록되어있지 않습니다. 회원가입을 진행해주세요.')
            return render_template('login.html')
        if check_password_hash(pw, request.form['pwd']):
            session['userid'] = request.form['id']
            return redirect(url_for('main'))
        else:
            flash('로그인 정보가 다릅니다.')
            return render_template('login.html', id=request.form['id'])

    return render_template('login.html')


@app_fun.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        if User.query.filter(User.user_id == request.form['id']).first():
            error = '사용중인 ID 입니다.'
            flash(error)
            return render_template('register.html', error=error, id=request.form['id'],
                                   name=request.form['name'], mailid=request.form['mailid'],
                                   email=request.form['email'])
        user = User(request.form['id'], request.form['name'], request.form['mailid'] + '@' + request.form['email'],
                    request.form['pwd'])
        db.session.add(user)
        db.session.commit()
        flash('회원가입이 완료되었습니다.')
        return redirect(url_for('index'))
    return render_template('register.html')


@app_fun.route('/post', methods=['GET', 'POST'])
def post():
    if login_check():
        flash('로그인이 필요한 서비스입니다.')
        return redirect('login')
    if request.method == 'POST':
        post = Post(title=request.form['title'], content=request.form['content'], author=session_id_check().user_id,
                    create_time=datetime.now())
        db.session.add(post)
        db.session.commit()
        return redirect('main')
    return render_template('post.html')


@app_fun.route('/board')
def board():
    return render_template('board.html', posts=Post.query.all())


@app_fun.route('/logout')
def logout():
    session.pop('userid')
    return redirect('/')

 

db 조회는 익숙하지 않아서 저렇게 조회하는게 최선인가 싶다.

 

처음에는 html에서 javascript로 db를 조회하는걸 구현하고 싶었으나,

뭐가 뭔지 하나도 모르겠어서 코드상에서 확인하는 방법으로 눈길을 돌렸다.

 

대부분 보면 이해할 수 있는 간단한 코드이니 return에 사용된 함수만 설명해보자.

 

render_template, redirect, flash, session, request, url_for

Blueprint는 설명했으니 넘어가고,

- render_template

return에 직접 html코드를 적어도 실행된다! 물론

@app.route('/test')
def test():
    return '''<h1>Hello! This Text written by python return</h1>
    It also can use input type <input type="text">'''

하지만 코드도 깔끔하지않고, 기능이 많아지면 코드가 길어지니 templates라는 디렉터리 안에 html형태로 만들어놓는다.

만들어놓은 html형태로 return 하라는 함수가 render_template.

**kwargs 형태로 집어넣어주면 파라미터도 전달이 가능하다.

 

- redirect, url_for

재미있는게 redirect는 url을 따라가고, url_for는 함수를 따라간다.

@app.route('/tests')
def test():
    return 'this is test'

@app.route('/test_test')
def test_test():
    return redirect('tests')

@app.route('/test_test_test')
def test_test_test():
    return redirect(url_for('test'))

세가지 다른 uri를 만들었다.

모두 첫번째 반환값인 this is test가 화면에 나온다.

 

처음 uri만 return이 str이고 나머지는 페이지 이동을 반환한다.

다른점이 뭐냐.

 

0.0.0.0:5000/test_test

는 redirect('tests'), 즉 현재 서버에 tests를 향해 이동하고,

 

0.0.0.0:5000/test_test_test

는 redirect(url_for('test')), url_for('test')의 반환값으로 redirect한다.

그럼 url_for는 무엇을 반환하느냐?

uri를 반환한다! 

전달받은 변수와 같은 함수가 라우팅 된 주소를 반환한다. 

 

- flash

flask의 flash는 간단하게 html에서 alert와 같은 역할을 한다.

다만, 단독으로 사용하진 않고, 전달사항을 저장하고 레이아웃 (html 등)으로 전달해서 사용한다.

깃 대부분의 html코드에 명시되어있고, jinja2 템플릿을 사용하는 flask에서는 전달받은 변수를

{{ }}에 넣어서 사용 가능하며, 템플릿에서는 다음과 같이 사용한다.

{%with messages = get_flashed_messages()%}
    {%if messages %}
        <script type="text/javascript">
              alert("{{messages[-1]}}");
        </script>
    {%endif%}
{%endwith%}

 

- request

전달받은 값을 parsing하는 함수이다.

보통 웹에서 POST/GET 형태로 데이터를 송수신하는데, 이것을 받는 함수라고 생각하면 된다.

이 프로젝트에서는 대부분 html에서 POST로 데이터를 넘겨주기 때문에, form 형태에 담긴 name을 읽어온다.

즉, request.form['id'] 라고 하면, html의 form 안에 이름이 id라고 정의된 값을 받아온다.

 

POST와 GET의 차이가 무어냐 묻는다면. 

URI에 데이터가 보인다? => GET

URI에 데이터가 안 보인다? => POST 이다.

간단하게 header 와 body에 담겨서 온다. 자세한 설명은 나중에 하기로하자.

보안에 크게 취약하지 않다면 그냥 GET방식으로 받아와도 좋다.

데이터를 받아오는 방식은

 

@app.route('/test/<noname>')
def test(noname):
    return noname   
    
@app.route('/tests')
def tests():
    name = request.args.get('name')
    return name

위와 같다. 

test함수는 0.0.0.0:5000/test/hi 로 입력해주면 hi 라는 값이 반환되고,

tests함수는 0.0.0.0:5000/tests?name=hi 로 입력해주면 똑같이 hi가 반환된다.

라우팅 할 때, 전달받을 메소드를 GET , POST등 명시해주면 이외의 요청이 들어오면 튕겨낸다.

이것도 웹 통신에 대한 기초가 있어야 이해하기 수월하니, 잘 모르겠다면 GET방식만 사용하자. 

- session

나는 로그인한 결과를 session에 담아 서버에 저장시키고, 이를 확인해서 불필요한 재로그인을 막았다.

if check_password_hash(pw, request.form['pwd']):
    session['userid'] = request.form['id']
    return redirect(url_for('main'))

위에서 설명한대로 check_password_hash를 이용해서 암호확인을 하고,

session에 담아주기만 하면 된다.

session은 서버에 저장되며 위에서 언급한 시간만큼 유지되다가 초기화된다.

 

 

 

아직 추가해야할 부분이 많으니 수정사항이 생기면 그때그때 포스팅해보자.

 

 

반응형