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은 서버에 저장되며 위에서 언급한 시간만큼 유지되다가 초기화된다.
아직 추가해야할 부분이 많으니 수정사항이 생기면 그때그때 포스팅해보자.
'python > 웹' 카테고리의 다른 글
Flask - Html 파일/이미지 전송 (0) | 2021.11.18 |
---|---|
Flask CORS (0) | 2021.09.27 |
[파이썬 서버] Flask를 잘 써보자 - Blueprint (0) | 2021.08.19 |
[파이썬 서버] 간단하게 파이썬 웹페이지 만들기 ( Flask ) [2] (0) | 2021.08.17 |
[파이썬 서버] 간단하게 파이썬 웹페이지 만들기 ( Flask ) [1] (0) | 2021.08.17 |