Flask
- 파이썬 기반 마이크로 웹 개발 프레임워크
- 웹 개발의 핵심기능만 간경하게 유지
- 필요한 기능은 다른 라이브러리나 프레임워크로 손쉽게 확장
- 신속하게 최소한의 노력으로 웹 애플리케이션 개발 가능
Flask 특징
- 개발용 서버와 디버거 내장
- RESTful 요청 처리
- Jinja2 템플릿 엔진 내장(JADE 등 다른 템플릿 엔진 사용 가능)
- 유니코드 기반
홈페이지
- 웹사이트 http://flask.pocoo.org
- 깃허브 https://github.com/pallets/flask
- 구축사이트 http://flask.pocoo.org/community/poweredby/
- 참고사이트 http://flask-docs-kr.readthedocs.io/ko/latest/index.html
개발 환경 설정
파이썬 3 설치 (파이썬 2도 가능)
가상 파이썬 실행 환경 구성 (virtualenv)
- 다양한 환경이나 특정 실행 환경 구성하기
- virtualev 라는 가상의 파이썬 실행 환경 지원 함
# 설치
pip install virtualenv
# 확인
virtualenv -help
virtualenv --viersion
- 가상 환경으로 만들고 싶은 폴더에서 명령어 실행
virtualenv venv
- 가상 환경 활성화
cd venv/scripts
activate.bat
- pyenv, autoenv 함께 사용하면 더 편리
플라스크 설치
pip install flask
note: could not find a version that satisfies the requirement flask
가 나올 경우, 네트워크 문제로 외부 라이브러리 저장소에 접근하지 못할 경우 나오는 문제.
직접 https://github.com/mitsuhiko/flask 위치로 가서 소스 받아 설치 해야 함.
- 설치 방법 –
python setup.py install
통합개발환경(IDE) / 에디터 설치
- 파이참(PyCharm) : https://www.jetbrains.com/pycharm/download/#section=windows
- 아톰(ATOM) : https://atom.io/
- VSCODE : https://code.visualstudio.com/
- PyDev(Ecliplse기반) : http://www.pydev.org
시작하기
Hello World
- hello.py 생성 및 작성
from flask import Flask # 모듈 import
app = Flask(__name__) # 애플리케이션 객체 생성
@app.route('/') # route() 데코레이터
def hello_world(): # View 함수
result = 'hello world!'
return result
if __name__ == '__main__':
app.debug = True
app.run()
- 소스를 실행하고,
http://127.0.0.1:5000
으로 접근
플라스크 애플리케이션 실행 순서
- 특정 URL 호출(request) :
http://127.0.0.1:5000/
또는http://localhost:5000
- 특정 URL 매핑 검색 :
@app.route('/')
- 특정 URL에 매칭된 함수(def 함수) 실행 :
def hello_world()
- 비즈니스 로직 실행 :
result
- 결과 응답으로 전송(response):
return result
- HTML 로 화면에 출력
- 쿠키(Cookie), 세션(Session), 로깅(logging) 등 제공
라우팅
- URL을 통해 처리할 핸들러를 찾는 것
- 플라스크는 복잡한 URI를 함수로 연결하는 방법을 제공
- URI 를 연결하는 route() 데코레이터 함수 제공
/
접속 시 root_world() 가 호출 됨/hello
접속 시 hello_world() 가 호출 됨
from flask import Flask # 모듈 import
app = Flask(__name__) # 애플리케이션 객체 생성
@app.route('/') # route 데코레이터
def root_world(): # View 함수
result = 'root world!'
return result
@app.route('/hello') # route 데코레이터
def hello_world(): # View 함수
result = 'hello world!'
return result
if __name__ == '__main__':
app.debug = True
app.run()
-
동적 변수를 사용하여 URI 접속
-
<동적변수>
를 뷰 함수의 인자로 사용 -
<동적변수>
다음에/
를 넣으면 안됨 -
app.debug
는 개발의 편의를 위해 존재True
값을 경우 코드를 변경하면 자동으로 서버가 재 실행 됨- 또한, 웹상에서 파이썬 코드를 수행할 수 있게 되므로, 운영환경에서 사용을 유의해야 함.
-
현재 접근은 개발 소스가 존재하는 로컬에서만 접근 가능
- 외부에서도 접근을 가능하게 하려면
app.run(host='0.0.0.0')
로 서버 실행 부를 변경해야 함.
- 외부에서도 접근을 가능하게 하려면
@app.route('/users/<userid>') # route 데코레이터
def user_id(userid): # View 함수
result = 'user_id = ' + userid
return result
from flask import Flask
app = Flask(__name__)
@app.route('/')
def root_world():
result = 'root world!!'
return result
@app.route('/hello')
def hello_world():
result = 'hello world!'
return result
@app.route('/users/<userid>') # route 데코레이터
def user_id(userid): # View 함수
result = 'user_id = ' + userid
return result
if __name__ == '__main__':
app.debug = True
app.run()
- url_for() 함수 : 함수를 호출 하는 URI 를 반환
- redirect() : 다른 route 경로 이동 (다른 페이지로 이동)
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route('/admin')
def hello_admin():
return 'Hello Admin'
@app.route('/guest/<guest>')
def hello_guest(guest):
return 'Hello %s as Guest' % guest
@app.route('/user/<name>')
def hello_user(name):
if name =='admin':
return redirect(url_for('hello_admin'))
else:
return redirect(url_for('hello_guest',guest = name))
if __name__ == '__main__':
app.run(debug = True)
Flask GET 방식으로 값 전송 및 처리
- mkdir templates 폴더 생성
- login_form_get.html 파일 작성
- get 방식 지정 :
method="get"
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>login_form_get.html </title>
</head>
<body>
<form action="/login_get_proc" method="get">
user ID: <input type="text" name="user_id" > <br>
user PWD: <input type="text" name="user_pwd" > <br>
<input type="submit" value="Click" >
</form>
</body>
</html>
from flask import Flask, request, session, render_template
추가- 사용자가 요청한 값은 request 모듈의 args 객체의 get 메소드로 값을 가져옴
- 내부에 methods 항목을 통해 받을 REST Action Type을 지정
@app.route('/login_get_proc', methods=['GET'])
- 지정 이외의 Action Type을 사용하면 Flask가 405 에러를 출력
-
- request 모듈에서 GET 한 파라미터 값을 가져오기 위해서는
request.args.get('name') # url?name=value name 값
@app.route('/login_form_get')
def login_form_get():
return render_template('login/login_form_get.html')
@app.route('/login/login_get_proc', methods=['GET'])
def login_get_proc():
user_id = request.args.get('user_id')
user_pwd = request.args.get('user_pwd')
if len(user_id) == 0 or len(user_pwd) == 0:
return user_id+', '+ user_pwd + ' 로그인 정보를 제대로 입력하지 않았습니다.'
return user_id + ' 님 환영합니다.'
Flask POST 방식으로 으로 값 전송 및 처리
- login_form_post.html 파일 작성
- post 방식 지정 :
method="post"
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>login_form_post.html </title>
</head>
<body>
<form action="/login_post_proc" method="post">
user ID: <input type="text" name="user_id" > <br>
user PWD: <input type="text" name="user_pwd" > <br>
<input type="submit" value="Click" >
</form>
</body>
</html>
- 사용자가 요청한 값은 request 모듈의 form 객체로 값을 가져옴
- 내부에 methods 항목을 통해 받을 REST Action Type을 지정
@app.route('/login_post_proc', methods=['POST'])
- 지정 이외의 Action Type을 사용하면 Flask가 405 에러를 출력
- request 모듈에서 POST 한 파라미터 값을 가져오기 위해서는
request.form['name'] # html form input등의 Element들의 name 값
request.form['name']
로 사용 시name
파라미터가 없으면 Flask가 400 에러를 출력
@app.route('/login_form_post')
def login_post():
return render_template('login/login_form_post.html')
@app.route('/login_post_proc', methods=['POST'])
def login_post_proc():
user_id = request.form['user_id']
user_pwd = request.form['user_pwd']
if len(user_id) == 0 or len(user_pwd) == 0:
return user_id+', '+user_pwd + ' 로그인 정보를 제대로 입력하지 않았습니다.'
return user_id + ' 님 환영합니다.'
Flask 에서 HTML 처리 하기
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
html = "<html><body><h1>Hello World</h1></body></html>"
return html
if __name__ == '__main__':
app.run(debug = True)
- user_id 값 html 과 함께 처리 하기
- 처리할 값이 많아지면 너무 복잡해 짐
@app.route('/users/<userid>') # route 데코레이터
def user_id(userid): # View 함수
html = "<html><body><h1> User Id : "
html = html + userid
html = html + "</h1></body></html>"
return html
Flask – Jinja2 템플릿 : 기본
- 플라스크에서는 Jinja2 템플릿의 사용
- Jinja2에 대한 더 자세한 내용은 Jinja2 Template Documentation 공식 문서를 참고
- Jinja2 Template Documentation
- template 폴더 생성 (Jinja2 템플릿 기본 문서 위치)
- template 폴더 아래에 userid_view.html 생성
return render_template('userid_view.html', userid=userid)
userid 전달{{ userid }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1> User Id : {{ userid }}</h1>
</body>
</html>
@app.route('/users/<userid>') # route 데코레이터
def user_id(userid): # View 함수
return render_template('userid_view.html', userid=userid)
Flask – Jinja2 템플릿 : 리스트(list) , 튜블(tuple) 등 전달
@app.route('/plays')
def palys():
games = ('갤러그', '너구리', '리니지')
sports = ['야구', '축구', '농구']
return render_template('plays/play.html', title='PLAYS', games=games, sports=sports, )
- play.html
{# 주석(comment) #}
- 반복문 :
{% for i in sports %} ~ {% endfor %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{#
jinja template comment
http://jinja.pocoo.org/docs/
#}
<h3> - title : {{ title }}</h3>
<h3>Sports</h3>
{% for i in sports %}
<h4> {{ i }} </h4>
{% endfor %}
<h3>Games</h3>
{% for i in games %}
<h4> {{ i }} </h4>
{% endfor %}
</body>
</html>
Flask – Jinja2 템플릿 : <form> 값 전달
- input.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action = "/result" method = "POST">
<p> * Games Input * </p>
<p>갤러그 <input type = "text" name = "gname1" value="갤러그" /></p>
<p>너구리 <input type = "text" name = "gname2" value="너구리" /></p>
<p>리니지 <input type ="text" name = "gname3" value="리니지" /></p>
<p><input type = "submit" value = "submit" /></p>
</form>
</body>
</html>
@app.route('/result',methods = ['POST', 'GET'])
def result():
if request.method == 'POST':
result = request.form
return render_template("plays/result.html",result = result)
- result.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border = 1>
{% for key, value in result.items() %}
<tr>
<th> {{ key }} </th>
<td> {{ value }} </td>
</tr>
{% endfor %}
</table>
</body>
</html>
Flask – 쿠키 (Cookie)
- http 프로토콜은 기본적으로 상태 값을 유지 하지 않음(stateless protocol)
- 쿠키(cookie) 와 세션(session)은 서버와 클라이언트 간 상태 값을 저장 하는 기술
- 쿠키(cokkie) 심기 : response 객체의 set_cookie() 메서드 사용
- 쿠키(cokkie) 일기 : request 객체의 cookie.get() 사용
from flask import Flask, request, render_template, make_response
app = Flask(__name__)
@app.route('/')
def index():
return render_template('cookie/setcookie.html')
@app.route('/setcookie', methods=['POST', 'GET'])
def setcookie():
if request.method == 'POST':
user_id = request.form['user_id']
resp = make_response(render_template('readcookie.html'))
resp.set_cookie('user_id', user_id)
return resp
@app.route('/getcookie')
def getcookie():
name = request.cookies.get('user_id')
return '<h1>welcome '+name+'</h1>'
if __name__ == '__main__':
app.debug = True
app.run()
- setcookie.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action = "/setcookie" method = "POST">
<p><h3>Enter userID</h3></p>
<p><input type='text' name='user_id'/></p>
<p><input type='submit' value='Login'/></p>
</form>
</body>
</html>
Flask 로그인 및 세션 생성
- 쿠키(cookie)는 클라이언트 피씨에 생성
- 세선은 서버 메모리에 생성되어 생성 후 바로 사용 가능
@app.route('/login_form')
def login():
return render_template('login_form.html')
@app.route('/login_proc', methods=['POST'])
def login_proc():
if request.method == 'POST':
userId = request.form['id']
userPwd = request.form['pwd']
if len(userId) == 0 or len(userPwd) == 0:
return userId+', '+userPwd+' 로그인 정보를 제대로 입력하지 않았습니다.'
session['logFlag'] = True
session['userId'] = userId
return session['userId'] + ' 님 환영합니다.'
else:
return '잘못된 접근입니다.'
app.secret_key = 'sample_secreat_key'
-
app.secret_key
는 세션 키를 생성하며, 로그인과 같이 세션을 맺는 경우 필수적으로 넣어야 한다.
- 세션 생성 시,
app.secret_key
로 키를 생성하지 않으면 Flask가 500 에러를 출력
- 세션 생성 시,
@app.route('/login_proc', methods=['post'])
def login_proc():
user_id = request.form['user_id']
user_pwd = request.form['user_pwd']
if len(user_id) == 0 or len(user_pwd) == 0:
return user_id+', '+user_pwd + ' 로그인 정보를 제대로 입력하지 않았습니다.'
session['logFlag'] = True
session['userId'] = user_id
return render_template('session_view.html')
- session_view.html ( 세션이 유지 되는 시간동안 세션값은 바로 사용 가능)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ session['logFlag'] }} <br>
{{ session['userId'] }}
</body>
</html>
sqlite3 db 및 table생성
-
DB 서버 필요 없는 파일 기반 Embedded SQL DB 엔진
-
sqlite브라우저 : http://sqlitebrowser.org/
-
- db 없을 시 python.db 자동 생성 됨
# file_name : sqlite_con.py
import sqlite3
con = sqlite3.connect("python.db")
cursor = con.cursor()
def insertDb():
cursor.execute("drop table member")
sql = "create table member("
sql = sql + " idx INTEGER PRIMARY KEY AUTOINCREMENT "
sql = sql + ",userId TEXT NOT NULL"
sql = sql + ", userPwd TEXT NOT NULL"
sql = sql + ", userEmail TEXT, regDate DEFAULT (datetime('now', 'localtime')) )"
cursor.execute(sql)
insert_sql = "insert into member(userId, userPwd, userEmail) values( "
insert_sql = insert_sql + "'kim', 'kimpass','kim@email.com')"
cursor.execute(insert_sql)
insert_sql = "insert into member(userId, userPwd, userEmail) values( "
insert_sql = insert_sql + "'lee', 'leepass','lee@email.com')"
cursor.execute(insert_sql)
insert_sql = "insert into member(userId, userPwd, userEmail) values( "
insert_sql = insert_sql + "'park', 'parkpass','park@email.com')"
cursor.execute(insert_sql)
con.commit()
def readDb():
cursor.execute("select * from member")
rows = cursor.fetchall()
for rs in rows:
print(rs)
#print(rs[0])
#print(rs[1])
def main():
#insertDb() # 1. talbe 생성 및 더미 자료 입력
#readDb() # 2. 입력 확인
con.close
if __name__ == '__main__':
main()
로그인 체크 로직 변경
# hello.py 변경
from flask import Flask, request, session, render_template, redirect, url_for
import sqlite3
app = Flask(__name__)
@app.route('/main')
def main():
return render_template('main.html')
@app.route('/login_form')
def login():
return render_template('login/login_form.html')
@app.route('/login_proc', methods=['post'])
def login_proc():
userId = request.form['user_id']
userPwd = request.form['user_pwd']
if len(userId) == 0 or len(userPwd) == 0:
return userId + ', ' + userPwd + ' Login Data Not Found.'
else:
con = sqlite3.connect("python.db")
cursor = con.cursor()
sql = "select idx, userId, userPwd from member where userId = ?"
cursor.execute(sql,(userId,))
rows = cursor.fetchall()
for rs in rows:
if userId == rs[1] and userPwd == rs[2]:
session['logFlag'] = True
session['idx'] = rs[0]
session['userId'] = userId
return redirect(url_for('main'))
else:
return "member not found"
app.secret_key = 'sample_secreat_key'
if __name__ == '__main__':
app.debug = True
app.run()
# /templates/main.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% if session.get('logFlag') %}
userId : {{ session.get('userId')}} <br>
<a href="/logout">로그아웃</a>
{% else %}
<a href="/login_form">로그인</a>
{% endif %}
</body>
</html>
로그인 정보 수정
@app.route('/user_info_edit/<int:edit_idx>', methods=['GET'])
def getUser(edit_idx):
if session.get('logFlag') != True:
return redirect(url_for('login'))
con = sqlite3.connect("python.db")
cursor = con.cursor()
sql = "select userEmail from member where idx = ?"
cursor.execute(sql,(edit_idx,))
row = cursor.fetchone()
edit_email = row[0]
return render_template('users/user_info.html', edit_idx=edit_idx, edit_email=edit_email)
- user_info.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user_info_edit_proc" method="post">
<input type="hidden" name="h_edit_idx" value="{{ edit_idx }}"> <br>
Edit User ID : {{ session.get('userId')}} <br>
User PWD : <input type="text" name="userPwd" value=""> <br>
User Email : <input type="text" name="userEmail" value="{{ edit_email }}"> <br>
<input type="submit" value="Edit" >
<a href="/main">[취소]</a> <br>
</form>
</body>
</html>
@app.route('/user_info_edit_proc', methods=['post'])
def user_info_edit_proc():
h_edit_idx = request.form['h_edit_idx']
userPwd = request.form['userPwd']
userEmail = request.form['userEmail']
if len(h_edit_idx) == 0:
return ' Edit Data Not Found.'
else:
con = sqlite3.connect("python.db")
cursor = con.cursor()
update_sql = "update member set userPwd = ?, userEmail = ? where idx = ?"
cursor.execute(update_sql, (userPwd, userEmail, h_edit_idx))
con.commit()
return redirect(url_for('main'))
로그 아웃
@app.route('/logout', methods=['POST','GET'])
def logout():
session['logFlag'] = False
session.pop('userId', None)
return redirect(url_for('main'))
redirect()
를 활용하면, 사용자의 조회 위치를 변경할 수 있다.url_for()
는 route 주소로 이동하는 것이 아닌 정의된 함수를 호출한다. 위 예제에서 main을 호출하는 대상은main()
인 함수다.session.clear()
를 사용하면 따로 설정 필요없이 session을 비울 수 있다.
Flask 리스트 페이징 하기(list paging)
- SQLite 사용
- Table, 데이터 생성 : http://thecoding.kr/category/phython/python-sqlite/
- template 폴더에 miniboard 생성
- lists.html, list.html 생성
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>lists.html</title>
</head>
<body>
<h3>MiniBoard List 전체</h3>
<ul>
{% for i in lists %}
<li>{{ i }}</li>
{% endfor %}
</ul>
</body>
</html>
- 페이징 적용된 html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>miniboard 페이징 list</title>
</head>
<body>
<h3>MiniBoard List</h3>
<ul>
{% for i in lists %}
<li>{{ i }}</li>
{% endfor %}
</ul>
<table>
<tr>
{% for i in range(page_count) %}
<td><a href="/list/{{i+1}}">[{{ i+1 }}]</a></td>
{% endfor %}
</tr>
</table>
</body>
</html>
- Flask 소스 : miniboard_run.py
#miniboard_run.py
from flask import Flask, request, session, render_template, url_for
import sqlite3
import math
app = Flask(__name__)
def selecte():
con = sqlite3.connect("python.db")
cursor = con.cursor()
cursor.execute("select * from miniboard where idx = ? order by idx desc",(3,))
rows = cursor.fetchall()
con.close()
return rows
def selecte_page(list_limit, page):
con = sqlite3.connect("python.db")
cursor = con.cursor()
offset = (page-1) * list_limit
sql = "select * from miniboard order by idx desc limit ? offset ?"
cursor.execute(sql, (list_limit, offset))
rows = cursor.fetchall()
con.close()
return rows
def select_count():
con = sqlite3.connect("python.db")
cursor = con.cursor()
cursor.execute("select count(idx) from miniboard")
rows = cursor.fetchone()
con.close()
return rows[0]
def list_test():
list = selecte()
print(list)
@app.route('/lists')
def lists():
lists = selecte()
return render_template('miniboard/lists.html', lists=lists)
@app.route('/list/<int:page>')
def list(page):
list_num = 5
lists_count = select_count()
page_count = int(lists_count/list_num)
#page_count = math.ceil(lists_count/list_num)
lists = selecte_page(list_num, page)
return render_template('miniboard/list.html', lists=lists, page_count=page_count)
if __name__ == '__main__':
#app.run(debug=True)
list_test()
Flask 로깅
@app.route('/user/<username>')
def showUserProfile(username):
app.logger.debug('RETRIEVE DATA - USER ID : %s' % username)
app.logger.debug('RETRIEVE DATA - Check Compelete')
app.logger.warn('RETRIEVE DATA - Warning... User Not Found.')
app.logger.error('RETRIEVE DATA - ERR! User unauthenification.')
return 'USER : %s' % username
- Flask 자체
app.logger
항목을 통해 로깅 가능 - 콘솔에 개발에 필요한 정보등 출력 표시할 때 사용