카테고리 보관물: Python Flask

Python Flask 관련 자료

Python – Flask

Flask

  • 파이썬 기반 마이크로 웹 개발 프레임워크
  • 웹 개발의 핵심기능만 간경하게 유지
  • 필요한 기능은 다른 라이브러리나 프레임워크로 손쉽게 확장
  • 신속하게 최소한의 노력으로 웹 애플리케이션 개발 가능

Flask 특징

  • 개발용 서버와 디버거 내장
  • RESTful 요청 처리
  • Jinja2 템플릿 엔진 내장(JADE 등 다른 템플릿 엔진 사용 가능)
  • 유니코드 기반

홈페이지

개발 환경 설정

파이썬 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) / 에디터 설치

시작하기

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 으로 접근

플라스크 애플리케이션 실행 순서

  1. 특정 URL 호출(request) : http://127.0.0.1:5000/ 또는 http://localhost:5000
  2. 특정 URL 매핑 검색 : @app.route('/')
  3. 특정 URL에 매칭된 함수(def 함수) 실행 : def hello_world()
  4. 비즈니스 로직 실행 : result
  5. 결과 응답으로 전송(response): return result
  6. HTML 로 화면에 출력
  7. 쿠키(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)


<!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 항목을 통해 로깅 가능
  • 콘솔에 개발에 필요한 정보등 출력 표시할 때 사용