Flask Example
Hello, World
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello world!'
$ FLASK_APP=app.py flask run
$ pip install flask-sqlalchemy
$ pip inttall Flask-WTF # from 관리
$ python app.py
$ sqlite3 db.splite
$ .tables
$ .schema
app.py
import os
from flask import Flask
from flask import request
from flask import redirect
from flask import render_template
from models import db
from models import Fcuser
app = Flask(__name__)
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
userid = request.form.get('userid')
username = request.form.get('username')
password = request.form.get('password')
re_password = request.form.get('re-password')
if (userid and username and password and re_password) and password == re_password:
fcuser = Fcuser()
fcuser.userid = userid
fcuser.username = username
fcuser.password = password
db.session.add(fcuser)
db.session.commit()
return redirect('/')
return render_template('register.html')
@app.route('/')
def hello():
return render_template('hello.html')
if __name__ == "__main__":
basedir = os.path.abspath(os.path.dirname(__file__)) # 현재파일주소를 절대경로로
dbfile = os.path.join(basedir, 'db.sqlite')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + dbfile # 데이터베이스 지정
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True # 반영의미
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 수정사항 추적
db.init_app(app) # 위에 없는 앱 설정값을 모두 초기화
db.app = app # 앱을 디비 안으로 명시화
db.create_all()
app.run(host='127.0.0.1', port=5000, debug=True)
models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Fcuser(db.Model):
__tablename__ = 'fcuser'
id = db.Column(db.Integer, primary_key=True)
password = db.Column(db.String(64))
userid = db.Column(db.String(32))
username = db.Column(db.String(8))
templates/register.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://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous">
</script>
</head>
<body>
<div class="container">
<div class="row mt-5">
<h1>회원가입</h1>
</div>
<div class="row mt-5">
<div class="col-12">
<form method="POST">
<div class="form-group">
<label for="userid">아이디</label>
<input type="text" class="form-control" id="userid" placeholder="아이디" name="userid" />
</div>
<div class="form-group">
<label for="username">사용자 이름</label>
<input type="text" class="form-control" id="username" placeholder="사용자 이름" name="username" />
</div>
<div class="form-group">
<label for="password">비밀번호</label>
<input type="password" class="form-control" id="password" placeholder="비밀번호" name="password" />
</div>
<div class="form-group">
<label for="re-password">비밀번호 확인</label>
<input type="password" class="form-control" id="re-password" placeholder="비밀번호 확인" name="re-password" />
</div>
<button type="submit" class="btn btn-primary">등록</button>
</form>
</div>
</div>
</div>
</body>
</html>
flask-todoapp
app.py
import os
from flask import Flask
from flask import request, redirect, render_template, session
from models import db, Fcuser, Todo
from forms import RegisterForm, LoginForm
from api_v1 import api as api_v1
app = Flask(__name__)
app.register_blueprint(api_v1, url_prefix='/api/v1') # api 웹주소는 /api/v1 다음으로 지정 /register면 /api/v1/register
@app.route('/', methods=['GET'])
def home():
userid = session.get('userid', None)
todos = []
if userid:
fcuser = Fcuser.query.filter_by(userid=userid).first()
todos = Todo.query.filter_by(fcuser_id=fcuser.id)
return render_template('home.html', userid=userid, todos=todos)
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
session['userid'] = form.data.get('userid')
return redirect('/')
return render_template('login.html', form=form)
@app.route('/logout', methods=['GET'])
def logout():
session.pop('userid', None)
return redirect('/')
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
fcuser = Fcuser()
fcuser.userid = form.data.get('userid')
fcuser.password = form.data.get('password')
db.session.add(fcuser)
db.session.commit()
return redirect('/login')
return render_template('register.html', form=form)
basedir = os.path.abspath(os.path.dirname(__file__))
dbfile = os.path.join(basedir, 'db.sqlite')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + dbfile
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'jqiowejrojzxcovnklqnweiorjqwoijroi'
csrf = CSRFProtect()
csrf.init_app(app)
db.init_app(app)
db.app = app
db.create_all()
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Todo(db.Model):
__tablename__ = 'todo'
id = db.Column(db.Integer, primary_key=True)
fcuser_id = db.Column(db.Integer, db.ForeignKey('fcuser.id'), nullable=False)
title = db.Column(db.String(256))
status = db.Column(db.Integer)
due = db.Column(db.String(64))
tstamp = db.Column(db.DateTime, server_default=db.func.now())
@property # 이것을 쓰면 serialize를 함수가 아니라 변수처럼 사용 가능, 모델을 직렬화해야 사용 가능
def serialize(self):
return {
'id': self.id,
'title': self.title,
'fcuser': self.fcuser.userid,
'tstamp': self.tstamp
}
class Fcuser(db.Model):
__tablename__ = 'fcuser'
id = db.Column(db.Integer, primary_key=True)
userid = db.Column(db.String(32))
password = db.Column(db.String(128))
todos = db.relationship('Todo', backref='fcuser', lazy=True)
forms.py
from models import Fcuser
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, EqualTo # 커맨드 누르고 클릭하면 소스 까볼 수 있다 -> UserPassword의 구조는 DataRequired에서 참고
class RegisterForm(FlaskForm):
userid = StringField('userid', validators=[DataRequired()])
password = PasswordField('password', validators=[DataRequired(), EqualTo('repassword')])
repassword = PasswordField('repassword', validators=[DataRequired()])
class LoginForm(FlaskForm):
class UserPassword(object):
def __init__(self, message=None):
self.message = message
def __call__(self, form, field):
userid = form['userid'].data
password = field.data
fcuser = Fcuser.query.filter_by(userid=userid).first() # 첫번째값
if fcuser.password != password:
raise ValueError('Wrong password')
userid = StringField('userid', validators=[DataRequired()])
password = PasswordField('password', validators=[DataRequired(), UserPassword()])
templates/register.html
- static 파일 그냥 static 폴더 만들어서 저장
- url_for() 함수 사용하면 static 폴더에 없어도 파일 추적 가능
<html>
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous">
</script>
</head>
<body>
<div class="container">
<div class="row mt-5">
<h1>회원가입</h1>
</div>
<div class="row mt-5">
<div class="col-12">
<form method="POST" action="/register">
{{ form.csrf_token }}
<div class="form-group">
{{ form.userid.label("아이디") }}
{{ form.userid(class="form-control", placeholder="아이디") }}
</div>
<div class="form-group">
{{ form.password.label("비밀번호") }}
{{ form.password(class="form-control", placeholder="비밀번호") }}
</div>
<div class="form-group">
{{ form.repassword.label("비밀번호 확인") }}
{{ form.repassword(class="form-control", placeholder="비밀번호 확인") }}
</div>
<button type="submit" class="btn btn-primary">등록</button>
</form>
</div>
</div>
</div>
</body>
</html>
templates/login.html
<html>
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous">
</script>
# <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> static폴더에 없어도 파일 경로 자동으로 찾아주는 함수 url_for
</head>
<body>
<div class="container">
<div class="row mt-5">
<h1>로그인</h1>
</div>
<div class="row mt-5">
<div class="col-12">
<form method="POST" action="/login">
{{ form.csrf_token }}
<div class="form-group">
{{ form.userid.label("아이디") }}
{{ form.userid(class="form-control", placeholder="아이디") }}
</div>
<div class="form-group">
{{ form.password.label("비밀번호") }}
{% if form.password.errors %} # password 내 errors를 담는 그릇이 있다
{{ form.password.errors.0 }} # errors의 0번째
{% endif %}
{{ form.password(class="form-control", placeholder="비밀번호") }}
</div>
<button type="submit" class="btn btn-primary">로그인</button>
</form>
</div>
</div>
</div>
</body>
</html>
templates/home.html
<html>
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous">
</script>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js">
</script>
<script>
$(document).ready(function () {
$("#date").datepicker({});
});
function createTodo() {
$.ajax({
url: '/api/v1/todos',
contentType: 'application/json',
method: 'POST',
data: JSON.stringify({
title: $("#title").val(),
due: $("#date").val()
})
}).done(function (res) {
$("#title").val('');
$("#date").val('');
window.location = '/';
});
}
function doneTodo(todo_id) {
$.ajax({
url: '/api/v1/todos/done',
contentType: 'application/json',
method: 'PUT',
data: JSON.stringify({
todo_id: todo_id
})
}).done(function (res) {
window.location = '/';
});
}
</script>
</head>
<body>
<div class="container">
<div class="row mt-5">
<h1>Home</h1>
</div>
<div class="row mt-5">
<div class="col-12">
<ul class="nav">
{% if userid %}
<li class="nav-item">
<a class="nav-link" href="/logout">로그아웃</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">{{ userid }}</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="/login">로그인</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/register">회원가입</a>
</li>
{% endif %}
</ul>
</div>
</div>
<div class="row mt-5">
<h3>할일 생성</h3>
<div class="col-12">
<!-- 할일 제목 -->
<input type="text" class="form-control" id="title" placeholder="할일 제목" />
</div>
<div class="col-12 col-sm-7 mt-2">
<!-- 날짜 -->
<input type="text" class="form-control" id="date" placeholder="기한" />
</div>
<div class="col-12 col-sm-5 mt-2">
<!-- 확인 -->
<button type="button" class="btn btn-primary" onclick="createTodo();">생성</button>
</div>
</div>
<div class="row mt-5">
<h3>할일 목록</h3>
<div class="col-12">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">할일제목</th>
<th scope="col">기한</th>
<th scope="col">완료처리</th>
</tr>
</thead>
<tbody>
{% for todo in todos %}
{% if todo.status %}
<tr>
<th scope="row"><del>{{ todo.id }}</del></th>
<td><del>{{ todo.title }}</del></td>
<td><del>{{ todo.due }}</del></td>
<td>완료</td>
</tr>
{% else %}
<tr>
<th scope="row">{{ todo.id }}</th>
<td>{{ todo.title }}</td>
<td>{{ todo.due }}</td>
<td><button type="button" class="btn btn-secondary" onclick="doneTodo({{ todo.id }});">완료</button></td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
api_v1/init.py
from flask import Blueprint
api = Blueprint('api', __name__)
from . import todo
api_v1/todo.py
from flask import jsonify
from flask import request
from flask import Blueprint
from flask import session
from models import Todo, db, Fcuser
import datetime
import requests
from . import api
def send_slack(msg):
res = requests.post('https://hooks.slack.com/services/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', json={
'text': msg
}, headers={'Content-Type': 'application/json'})
@api.route('/todos/done', methods=['PUT'])
def todos_done():
userid = session.get('userid', 1)
if not userid:
return jsonify(), 401
data = request.get_json()
todo_id = data.get('todo_id')
todo = Todo.query.filter_by(id=todo_id).first()
fcuser = Fcuser.query.filter_by(userid=userid).first()
if todo.fcuser_id != fcuser.id:
return jsonify(), 400
todo.status = 1
db.session.commit()
send_slack('TODO가 완료되었습니다\n사용자: %s\n할일 제목:%s'%(fcuser.userid, todo.title))
return jsonify()
@api.route('/todos', methods=['GET', 'POST', 'DELETE'])
def todos():
userid = session.get('userid', None)
if not userid:
return jsonify(), 401
if request.method == 'POST':
data = request.get_json()
todo = Todo()
todo.title = data.get('title')
fcuser = Fcuser.query.filter_by(userid=userid).first()
todo.fcuser_id = fcuser.id
todo.due = data.get('due')
todo.status = 0
db.session.add(todo)
db.session.commit()
send_slack('TODO가 생성되었습니다\n사용자: %s\n할일 제목:%s\n기한:%s'%(fcuser.userid, todo.title, todo.due))
return jsonify(), 201
elif request.method == 'GET':
todos = Todo.query.filter_by(fcuser_id=userid, status=0)
return jsonify([t.serialize for t in todos])
elif request.method == 'DELETE':
data = request.get_json()
todo_id = data.get('todo_id')
todo = Todo.query.filter_by(id=todo_id).first()
db.session.delete(todo)
db.session.commit()
return jsonify(), 203
return jsonify(data)
@api.route('/slack/todos', methods=['POST'])
def slack_todos():
res = request.form['text'].split(' ')
cmd, *args = res
ret_msg = ''
if cmd == 'create':
todo_user_id = args[0]
todo_name = args[1]
todo_due = args[2]
fcuser = Fcuser.query.filter_by(userid=todo_user_id).first()
todo = Todo()
todo.fcuser_id = fcuser.id
todo.title = todo_name
todo.due = todo_due
todo.status = 0
db.session.add(todo)
db.session.commit()
ret_msg = 'Todo가 생성되었습니다'
send_slack('[%s] "%s" 할일을 만들었습니다.'%(str(datetime.datetime.now()), todo_name)) # 사용자 정보, 할일 제목, 기한
elif cmd == 'list':
todo_user_id = args[0]
fcuser = Fcuser.query.filter_by(userid=todo_user_id).first()
todos = Todo.query.filter_by(fcuser_id=fcuser.id)
for todo in todos:
ret_msg += '%d. %s (~ %s, %s)\n'%(todo.id, todo.title, todo.due, ('미완료', '완료')[todo.status])
elif cmd == 'done':
todo_id = args[0]
todo = Todo.query.filter_by(id=todo_id).first()
todo.status = 1
db.session.commit()
ret_msg = 'Todo가 완료 처리되었습니다'
elif cmd == 'undo':
todo_id = args[0]
todo = Todo.query.filter_by(id=todo_id).first()
todo.status = 0
db.session.commit()
ret_msg = 'Todo가 미완료 처리되었습니다'
return ret_msg
PythonAnywhere 배포
- 파일 압축해서 업로드(가상환경파일 제외)
- Open Bash console here
$ unzip.project.zip -d project
$ virtualenv --python=python3.8 falsk_fastcampus
$ source flask_fastcampus/bin/activate
$ pip install flask flask-wtf flask-sqlalchemy
$ exit
- Web - add a new web app
- Code -> 디렉토리 설정
- Virtualenv -> 디렉토리 설정
- WSGI
- HELLO_WORLD 주석 설정
- FLASK 주석 해제
- 폴더 위치 수정
from app import app as application # noqa