프로그래밍/Python

[파이썬3 웹프로그래밍] Flask & MongoDB 응용

Sanghou 2017. 10. 11. 12:01
back_2

Back-end 교육 2주차 - 실습

: Flask & MongoDB 응용

2017년 9월 26일 안상호


1. 지난주 복습

1.1. Flask

  • 정의

플라스크는 파이썬으로 웹어플리케이션을 만들 수 있게 해주는 Micro Web Framework이다.

출처

여기서 Micro라는 것은 핵심기능(서버 구동 및 기타) 만을 제공해주고 DB 선택이나 로그인 같은

선택적인 부분은 사용자가 선택해서 확장가능하도록 만든다는 것을 의미합니다.

(게임에서 DLC, 확장팩처럼)

1.1.1. Flask Quick Start
In [1]:
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'


if __name__ == '__main__':
    app.run(port=5000,debug = True)
    ## Jupyter Notebook에서는 debug시 에러가 뜸
 * Restarting with stat
An exception has occurred, use %tb to see the full traceback.

SystemExit: 1
D:\PycharmProjects\backend1\lib\site-packages\IPython\core\interactiveshell.py:2918: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.
  warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)
  • 이 코드는 무슨 일을 한것인가?

    • Flask(name)

      • Flask class를 물려받았다.


    • @app.route('/')

      • 데코레이터 (@)를 이용해서 Flask에게 어떤 URL이 우리가 작성한 함수를 실행하는지 알려준다.
      • 데코레이터 (@)란 대상 함수를 wrapping 하고, 이 wrapping 된 함수의 앞뒤에 추가적으로 꾸며질 구문 들을 정의해서 손쉽게 재사용 가능하게 해주는 것이다. 참고
      • 파라미터인 '/'는 URL을 나타낸다.


    • run(port=5000,debug = True)
      • 최종적으로 run()함수를 통해 어플리케이션을 로컬서버로 실행한다.
      • parameter port=는 어떤 로컬 서버로 실행할지를 나타내는 주소다.
      • parameter debug=는 코드 변경이 있을 때 Flask 서버 자체적으로 이를 감지하고 리로드할지에 대한 것을 설정할 수 있다.
1.1.2. 예제로 보는 Flask 구조 짜보기

일단 이 사이트를 들어 가볼까요?

뒤에 있는 .jsp는 back-end로 자바를 사용한 것이니 그 이전까지를 사용해서 server.py를 구성해봅시다.

기본 URL은 http://ebiz.ajou.ac.kr 라고 가정합니다!

In [2]:
from flask import Flask, render_template
app = Flask(__name__)

# http://ebiz.ajou.ac.kr/ebiz/
@app.route('/ebiz/')
def main():
    return render_template('main.html')

if __name__ == '__main__':
    app.run(port=5000,debug = True)
 * Restarting with stat
An exception has occurred, use %tb to see the full traceback.

SystemExit: 1
D:\PycharmProjects\backend1\lib\site-packages\IPython\core\interactiveshell.py:2918: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.
  warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)

Quiz1

자 그러면 학과소개, 교육, 교수진, 학생활동, 커뮤니티에 대해서 URL을 정의해 봅시다! (구조만 짜보는 것으로, route부분과 함수이름, 그리고 return 부분을 건드려 주세요~)

조건1 : .html은 함수이름과 같게 만들어주세요

조건2 : .jsp부분은 URL에서 없는 걸로하고 진행해주세요

  • 그리고 main.html도 바꿔줘야 한답니다.
    • 여기서 사용되는 것이 바로 Jinja2 문법

1.2. MongoDB

우선 cmd, powershell에서 mongod.exe 실행

1.2.1. Start
In [3]:
from pymongo import MongoClient

client = MongoClient()

db = client["test"] # 새로운 엑셀 파일로 생각하시면 됩니다~
collection = db.restaurants # 새 시트 만들기로 생각하시면 됩니다~
  • MongoDB에서의 대응 관계
    • db는 새로운 데이터 베이스 전체를 만든 것입니다.
    • collectiondb에서 정의한 데이터 베이스 내에서 새로운 데이터 테이블을 만드는 것입니다.
    • collection = client.test.restaurants로도 표현할 수가 있습니다.
1.2.2. Insert
In [4]:
new_documents = [
  {
    "name": "Sun Bakery Trattoria",
    "stars": 4,
    "categories": ["Pizza","Pasta","Italian","Coffee","Sandwiches"]
  }, {
    "name": "Blue Bagels Grill",
    "stars": 3,
    "categories": ["Bagels","Cookies","Sandwiches"]
  }, {
    "name": "Hot Bakery Cafe",
    "stars": 4,
    "categories": ["Bakery","Cafe","Coffee","Dessert"]
  }, {
    "name": "XYZ Coffee Bar",
    "stars": 5,
    "categories": ["Coffee","Cafe","Bakery","Chocolates"]
  }, {
    "name": "456 Cookies Shop",
    "stars": 4,
    "categories": ["Bakery","Cookies","Cake","Coffee"]
  }
]

# collection.insert_many(new_documents)
1.2.3. Query
In [11]:
for restaurant in collection.find({'stars' : 3}):
  print(restaurant)
{'name': 'Blue Bagels Grill', '_id': ObjectId('59c0b35b18271e16e0ae6634'), 'categories': ['Bagels', 'Cookies', 'Sandwiches'], 'stars': 3}
{'name': 'Blue Bagels Grill', '_id': ObjectId('59c0b36a18271e16e0ae663a'), 'categories': ['Bagels', 'Cookies', 'Sandwiches'], 'stars': 3}
{'name': 'Blue Bagels Grill', '_id': ObjectId('59c0b4c818271e16e0ae6640'), 'categories': ['Bagels', 'Cookies', 'Sandwiches'], 'stars': 3}
{'name': 'Blue Bagels Grill', '_id': ObjectId('59c0b50918271e16e0ae6646'), 'categories': ['Bagels', 'Cookies', 'Sandwiches'], 'stars': 3}
{'name': 'Blue Bagels Grill', '_id': ObjectId('59c0ec0418271e02c07c56af'), 'categories': ['Bagels', 'Cookies', 'Sandwiches'], 'stars': 3}
{'name': 'Blue Bagels Grill', '_id': ObjectId('59c9e60a18271e0b349c590b'), 'stars': 3, 'categories': ['Bagels', 'Cookies', 'Sandwiches']}
1.2.4. skip().limit()
In [12]:
result = collection.find().skip(0).limit(5)

for post in result:
    print(post)
{'name': 'Sun Bakery Trattoria', '_id': ObjectId('59c0b35b18271e16e0ae6633'), 'categories': ['Pizza', 'Pasta', 'Italian', 'Coffee', 'Sandwiches'], 'stars': 4}
{'name': 'Blue Bagels Grill', '_id': ObjectId('59c0b35b18271e16e0ae6634'), 'categories': ['Bagels', 'Cookies', 'Sandwiches'], 'stars': 3}
{'name': 'Hot Bakery Cafe', '_id': ObjectId('59c0b35b18271e16e0ae6635'), 'categories': ['Bakery', 'Cafe', 'Coffee', 'Dessert'], 'stars': 4}
{'name': 'XYZ Coffee Bar', '_id': ObjectId('59c0b35b18271e16e0ae6636'), 'categories': ['Coffee', 'Cafe', 'Bakery', 'Chocolates'], 'stars': 5}
{'name': '456 Cookies Shop', '_id': ObjectId('59c0b35b18271e16e0ae6637'), 'categories': ['Bakery', 'Cookies', 'Cake', 'Coffee'], 'stars': 4}

2. blog 예제

  • To do list
    • 블로그 구조를 server.py로 코드로 작성해본다.
    • MongoDB와 연동하여 데이터의 입출력을 코드로 구현한다.
    • Jinja2 문법을 통해 html을 효율적으로 이용해본다.
    • 글쓰기, 글수정, 글삭제 기능을 구현해본다.


  • 블로그 구조 {함수 (url)}
    • main ('/') : 글쓰기, 수정, 삭제 버튼이 있고, 글목록이 보임
    • add ('/add') : 글쓰기 페이지
    • update ('/update') : 글수정 페이지
    • delete ('/delete') : 글삭제 페이지


  • 준비물
    • html
    • css

2.1. Main & add 페이지

In [15]:
from pymongo import MongoClient
from flask import Flask, render_template  
app = Flask(__name__)

client = MongoClient()


db = client.Flask  # MongoClient 아무거나 점찍고 뒤에 쓸 수 있음
collection = db.blog # collection

@app.route('/')
def main():
    return render_template('main.html')

@app.route('/add')
def add():
    return render_template('add.html')

if __name__ == '__main__':
    app.run(debug=True)
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

우선 add()에서는 실제로 저장 버튼을 눌렀을 때 db에 데이터가 저장되어야합니다.

그렇기에 안에 내용을 추가해야하는데, 아래 코드를 보도록 합시다.

  • add() 코드 변경
In [ ]:
from flask import Flask, render_template, request, redirect 

@app.route('/add', methods=['POST', 'GET'])
def add():
  if request.method == 'POST':
    collection.save({
      "title": request.form["title"],
      "content": request.form["content"]
    })
    return redirect('/')  
  else: # GET 방식을 의미합니다.
    return render_template('add.html')

갑자기 왜이렇게 늘어났죠....?

GET하고 POST는 또 뭐죠

GET과 POST

GET과 POST는 HTTP프로토콜을 이용해서 서버에 무언가를 전달할 때 사용하는 방식입니다. 출처 및 참고 alt text

  • GET : 대체로 POST 이전에 수행되는 method로 단순히 서버에서 어떤 데이터를 가져와서 보여주는 용도입니다.

  • POST : 대체로 GET 이후에 수행되는 method로 서버의 값이나 상태를 바꾸기 위해 사용합니다.


  • 이 코드는 무슨일을 하는가?
    • 간단하게 말하자면 처음에는 GET 방식으로 템플릿을 보여주고, 저장 버튼을 누르면 POST 방식으로 데이터를 MongoDB의 collection으로 전달합니다.
    • request.method : 현재 요청이 GET 인지 POST인지 구별
    • collection.save : 만든 데이터 베이스에 데이터를 저장해줍니다.
      • request.form : add.html 에서 정보를 가져옵니다.
    • redirect : POST에서의 작업이 끝났다면 다시 Main 페이지로 보내기 위함

자 그러면 수정하고 실행해봅시다!

In [ ]:
from pymongo import MongoClient

client = MongoClient()


db = client.Flask  # MongoClient 아무거나 점찍고 뒤에 쓸 수 있음
collection = db.blog # collection

for post in colletion.find():
    print(post)
  • Main()도 바꿔야겠죠?
In [14]:
@app.route('/') 
def main():
  posts = collection.find().skip(0).limit(20)
  return render_template('main.html', posts=posts)
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
  • 그리고 main.html도 바꿔줘야 한답니다.
    • 여기서 사용되는 것이 바로 Jinja2 문법
In [ ]:
    {% for post in posts %}
    <main class="contents">
     <article class="card">
       <h1>{{post.title}}</h1>
       <p>{{post.content}}</p>
     </article>
    </main>
    {% endfor %}
  • 전체 server.py 코드
In [ ]:
from pymongo import MongoClient
from flask import Flask, render_template, request, redirect
app = Flask(__name__)

client = MongoClient()

db = client.Flask  # MongoClient 아무거나 점찍고 뒤에 쓸 수 있음
collection = db.blog # collection

@app.route('/')
def main():
  posts = collection.find().skip(0).limit(20)
  return render_template('main.html', posts=posts)

@app.route('/add', methods=['POST', 'GET'])
def add():
  if request.method == 'POST':
    collection.save({
      "title": request.form["title"],
      "content": request.form["content"]
    })
    return redirect('/')
  else: # GET 방식을 의미합니다.
    return render_template('add.html')

@app.route('/update', methods=['POST','GET'])
def update():
    if request.method == 'POST':
        collection.save({ 
          "_id": ObjectId(request.form["_id"]),
          "title": request.form["title"],
          "content": request.form["content"]
        })
        return redirect('/')
    else: #GET
        _id = request.args.get('_id', '')
        post = collection.find_one({"_id": ObjectId(_id)}) 
        return render_template('detail.html', post = post)

@app.route('/delete/<_id>',methods=['POST','GET'])
def delete(_id):
    if request.method == 'POST':
        collection.remove({"_id":ObjectId(_id)})
        return redirect('/')
    else:
        post = collection.find_one({"_id": ObjectId(_id)})
        return render_template('delete-check.html', post = post)

if __name__ == '__main__':
    app.run(port=5001, debug=True)