Flask

SCPKYG·2023년 3월 30일
0

Flask 시작하기 - 서버만들기

prac 폴더 구조


prac(폴더이름)
|— venv
|— app.py (서버)
|— templates(폴더 생성)
|— index.html (클라이언트 파일)

Flask는 만들 프로젝트의 폴더 구조가 정해져 있어요! 규칙을 지켜주세요!

주의 사항

templates 폴더는 반드시 고정해야 합니다! Flask의 규칙이에요!
index.html은 변경해도 좋습니다만, 첫 페이지는 일반적으로 index.html을 사용해요!

venv 생성하기

  1. 화면 상단 Terminal > New Terminal 을 클릭
  2. 터미널에 python(맥은 python3) -m venv venv 입력
  3. venv활성화
    VS Code 오른쪽 아래의 파이썬 버전(예: 3.9.6)을 클릭한 뒤, 화면 위쪽에서 venv가 쓰여있는 파이썬 버전을 클릭해주세요!
    새터미널 활성화 후 터미널에서 (venv) 라고 뜨게 된다면 가상환경이 활성화 완료

Flask 패키지 설치 및 실행 하기

  1. Terminal > New Terminal
  2. 터미널에 pip install flask 라고 입력
  3. 터미널 아래 부분의 Successfully installed … 라고 적혀 있다면 설치 완료
  4. flask 시작 코드(app.py에 입력)
from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
   return 'This is Home!'

if __name__ == '__main__':  
   app.run('0.0.0.0',port=5000,debug=True)
  1. Terminal > New Terminal 을 클릭
  2. (venv)가 보인다면 가상환경 활성화 상태
  3. 하단의 이미지 처럼 실행 되면 완성

종료 버튼(?) 단축키(?)

터미널 창을 클릭하시고, ctrl + c 을 누르시면 서버를 종료할 수 있다.

Flask란?

  • Flask 프레임워크: 서버를 구동시켜주는 편한 코드 모음.
  • 웹 서버를 구동하는데 필요한 복잡한 코드들을 쉽게 가져다 쓸 수 있습니다.

Flask - HTML 파일 주기

Flask 서버를 만들 때, 항상,
프로젝트 폴더 안에,
ㄴtemplates 폴더 (html파일을 넣어둡니다)
ㄴapp.py 파일
형태로 생성

Flask 기초: HTML 파일 불러오기

  1. 간단한 index.html 파일을 templates 안에 만들기
  2. html 파일 불러오기
from flask import Flask, render_template
app = Flask(__name__)

## URL 별로 함수명이 같거나,
## route('/') 등의 주소가 같으면 안됩니다.

@app.route('/')
def home():
   return 'This is Home!'

@app.route('/mypage')
def mypage():

   return render_template('index.html')

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

templates 폴더의 역할

HTML 파일을 담아두고, 불러오는 역할

Flask - 본격 API 만들기

GET, POST 요청 타입 리마인드

  • 클라이언트가 요청 할 때에도, "방식"이 존재합니다.
  • HTTP 라는 통신 규약을 따른다는 거 잊지 않으셨죠? 클라이언트는 요청할 때 HTTP request method(요청 메소드)를 통해, 어떤 요청 종류인지 응답하는 서버 쪽에 정보를 알려주는 거에요.
  • 여러 방식이 존재하지만, 가장 많이 쓰이는 GET, POST 방식에 대해서 다루겠습니다!
  • 1) GET 요청
    • 통상적으로 데이터 조회(Read)를 요청할 때, 사용합니다!

      예) 영화 목록 조회
      → 데이터 전달 : URL 뒤에 물음표를 붙여 key=value로 전달

  • 2) POST 요청
    • 통상적으로 데이터 생성(Create), 변경(Update), 삭제(Delete) 요청 할 때 사용합니다!

      예) 회원가입, 회원탈퇴, 비밀번호 수정

      → 데이터 전달 : 바로 보이지 않는 HTML

프로젝트 세팅(예시)

예시 : 스파르타피디아(http://spartacodingclub.shop/web/movie)

  1. sparta → projects → 03.pedia 폴더에서 시작
  2. flask 폴더 구조 만들기

    (주의 사항 : Flask는 만들 프로젝트의 폴더 구조가 정해져 있기에 규칙을 지켜야 합니다)
  • 폴더 안에 app.py 파일을 생성합니다!
  • 폴더 안에 templates 폴더를 생성합니다!
  • templates 폴더 안에 index.html 파일을 생성합니다!

app.py 코드

from flask import Flask, render_template, request, jsonify
app = Flask(__name__)

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

@app.route("/movie", methods=["POST"])
def movie_post():
	sample_receive = request.form['sample_give']
	print(sample_receive)
	return jsonify({'msg':'POST 연결 완료!'})

@app.route("/movie", methods=["GET"])
def movie_get():
	return jsonify({'msg':'GET 연결 완료!'})

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

index.html 코드

<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
            integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
            crossorigin="anonymous"></script>

    <title>스파르타 피디아</title>

    <link href="https://fonts.googleapis.com/css2?family=Gowun+Dodum&display=swap" rel="stylesheet">

    <style>
        * {
            font-family: 'Gowun Dodum', sans-serif;
        }

        .mytitle {
            width: 100%;
            height: 250px;

            background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('https://movie-phinf.pstatic.net/20210715_95/1626338192428gTnJl_JPEG/movie_image.jpg');
            background-position: center;
            background-size: cover;

            color: white;

            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
        }

        .mytitle > button {
            width: 200px;
            height: 50px;

            background-color: transparent;
            color: white;

            border-radius: 50px;
            border: 1px solid white;

            margin-top: 10px;
        }

        .mytitle > button:hover {
            border: 2px solid white;
        }

        .mycomment {
            color: gray;
        }

        .mycards {
            margin: 20px auto 0px auto;
            width: 95%;
            max-width: 1200px;
        }

        .mypost {
            width: 95%;
            max-width: 500px;
            margin: 20px auto 0px auto;
            padding: 20px;
            box-shadow: 0px 0px 3px 0px gray;

            display: none;
        }

        .mybtns {
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: center;

            margin-top: 20px;
        }
        .mybtns > button {
            margin-right: 10px;
        }
    </style>
    <script>
        $(document).ready(function(){
          listing();
        });

        function listing() {
            fetch('/movie').then((res) => res.json()).then((data) => {
            console.log(data)
            alert(data['msg'])
            })
        }

        function posting() {
						let formData = new FormData();
            formData.append("sample_give", "샘플데이터");

            fetch('/movie', {method : "POST",body : formData}).then((res) => res.json()).then((data) => {
            console.log(data)
            alert(data['msg'])
            })
        }

        function open_box(){
            $('#post-box').show()
        }
        function close_box(){
            $('#post-box').hide()
        }
    </script>
</head>

<body>
<div class="mytitle">
    <h1>내 생애 최고의 영화들</h1>
    <button onclick="open_box()">영화 기록하기</button>
</div>
<div class="mypost" id="post-box">
    <div class="form-floating mb-3">
        <input id="url" type="email" class="form-control" placeholder="name@example.com">
        <label>영화URL</label>
    </div>
    <div class="form-floating">
        <textarea id="comment" class="form-control" placeholder="Leave a comment here"></textarea>
        <label for="floatingTextarea2">코멘트</label>
    </div>
    <div class="mybtns">
        <button onclick="posting()" type="button" class="btn btn-dark">기록하기</button>
        <button onclick="close_box()" type="button" class="btn btn-outline-dark">닫기</button>
    </div>
</div>
<div class="mycards">
    <div class="row row-cols-1 row-cols-md-4 g-4" id="cards-box">
        <div class="col">
            <div class="card h-100">
                <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                     class="card-img-top">
                <div class="card-body">
                    <h5 class="card-title">영화 제목이 들어갑니다</h5>
                    <p class="card-text">여기에 영화에 대한 설명이 들어갑니다.</p>
                    <p>⭐⭐⭐</p>
                    <p class="mycomment">나의 한줄 평을 씁니다</p>
                </div>
            </div>
        </div>
        <div class="col">
            <div class="card h-100">
                <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                     class="card-img-top">
                <div class="card-body">
                    <h5 class="card-title">영화 제목이 들어갑니다</h5>
                    <p class="card-text">여기에 영화에 대한 설명이 들어갑니다.</p>
                    <p>⭐⭐⭐</p>
                    <p class="mycomment">나의 한줄 평을 씁니다</p>
                </div>
            </div>
        </div>
        <div class="col">
            <div class="card h-100">
                <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                     class="card-img-top">
                <div class="card-body">
                    <h5 class="card-title">영화 제목이 들어갑니다</h5>
                    <p class="card-text">여기에 영화에 대한 설명이 들어갑니다.</p>
                    <p>⭐⭐⭐</p>
                    <p class="mycomment">나의 한줄 평을 씁니다</p>
                </div>
            </div>
        </div>
        <div class="col">
            <div class="card h-100">
                <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                     class="card-img-top">
                <div class="card-body">
                    <h5 class="card-title">영화 제목이 들어갑니다</h5>
                    <p class="card-text">여기에 영화에 대한 설명이 들어갑니다.</p>
                    <p>⭐⭐⭐</p>
                    <p class="mycomment">나의 한줄 평을 씁니다</p>
                </div>
            </div>
        </div>
    </div>
</div>
</body>

</html>

POST

  1. API 만들고 사용하기 - 포스팅 API(Create→POST)
  2. 데이터 명세
    1. 요청 정보 : URL= /movie, 요청 방식 = POST
    1. 클라(fetch) → 서버(flask) : url, comment
    1. 서버(flask) → 클라(fetch) : 메시지를 보냄 (포스팅 완료!)
  1. 클라이언트와 서버 연결 확인하기
    <서버> app.py
@app.route("/movie", methods=["POST"])
def movie_post():
    sample_receive = request.form['sample_give']
    print(sample_receive)
    return jsonify({'msg':'POST 연결 완료!'})

<클라이언트> index.html

function posting() {
    fetch('/movie', {method: "POST",body: formData,}).then((res) => res.json()).then((data) => {
            console.log(data);
            alert(data["msg"]);
          });
}

<button onclick="posting()" type="button" class="btn btn-dark">기록하기</button>

4.서버 만들기

  • url, comment정보를 받아서, 저장하면 되겠죠?
  • 그리고, 우리가 미리 만든 meta_prac.py도 참고해서 붙여봅시다!
  • 우리가 일전에 만들어둔 dbprac.py 파일을 불러와봅시다!
@app.route("/movie", methods=["POST"])
def movie_post():
    url_receive = request.form['url_give']
    comment_receive = request.form['comment_give']

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
    data = requests.get(url_receive, headers=headers)

    soup = BeautifulSoup(data.text, 'html.parser')

    ogimage = soup.select_one('meta[property="og:image"]')['content']
    ogtitle = soup.select_one('meta[property="og:title"]')['content']
    ogdesc = soup.select_one('meta[property="og:description"]')['content']


    doc = {
        'title':ogtitle,
        'desc':ogdesc,
				'image':ogimage,
        'comment':comment_receive
    }

    db.movies.insert_one(doc)

    return jsonify({'msg':'저장완료!'})

5.클라이언트 만들기

  • 이번엔 url, comment정보를 보내주면 되겠죠?
  • formData에 데이터를 넣고 보내줍시다!
  • 요청을 보내고 포스팅 완료! 라고 받은 메시지를 alert()로 띄워줍니다
  • 그 다음은 자연스럽게 보이기 위해 window.location.reload() 새로고침을 해줍시다!
function posting() {
            let url = $('#url').val()
            let comment = $('#comment').val()

            let formData = new FormData()
            formData.append("url_give", url)
            formData.append("comment_give", comment)

            fetch('/movie', {method: "POST",body: formData}).then(res => res.json()).then(data => {
                    alert(data['msg'])
                    window.location.reload()
                })
        }

6.완성

  • DB에 잘 들어갔는지 확인

GET

  1. 데이터 명세
      1. 요청 정보 : URL= /movie, 요청 방식 = GET
      1. 클라(fetch) → 서버(flask) : 없음
      1. 서버(flask) → 클라(fetch) : 전체 주문을 보내주기
  2. 클라이언트와 서버 확인하기
    <서버> app.py
@app.route("/movie", methods=["GET"])
def movie_get():
    return jsonify({'msg':'GET 연결 완료!'})

<클라이언트> index.html

$(document).ready(function(){
	  listing();
});

function listing() {
    fetch('/movie').then((res) => res.json()).then((data) => {
            console.log(data)
            alert(data['msg'])
          })
}
  1. 서버 만들기
  • 받을 것 없이 movies 에 영화정보를 담아서 내려주기만 하면 됩니다!
@app.route("/movie", methods=["GET"])
def movie_get():
    all_movies = list(db.movies.find({},{'_id':False}))
    return jsonify({'result':all_movies})
  1. 클라이언트 만들기
  • 주문정보는 리스트 형식이겠죠? forEach문으로 반복하면서 데이터를 뽑아냅시다!
  • 뽑아낸 데이터는 temp_html에 담아줘야겠죠?
  • 담아준 temp_html을 넣을 자리를 찾아 제이쿼리로 append!
function listing() {
            fetch('/movie').then(res => res.json()).then(data => {
                    let rows = data['result']
				            $('#cards-box').empty()
                    rows.forEach((a) => {
												let comment = a['comment']
                        let title = a['title']
                        let image = a['image']
                        let desc = a['desc']

                        let temp_html = `<div class="col">
                                            <div class="card h-100">
                                                <img src="${image}"
                                                     class="card-img-top">
                                                <div class="card-body">
                                                    <h5 class="card-title">${title}</h5>
                                                    <p class="card-text">${desc}</p>
                                                    <p class="mycomment">${comment}</p>
                                                </div>
                                            </div>
                                        </div>`

                        $('#cards-box').append(temp_html)
                    })
                })
        }
  1. 완성하기
  • 동작 테스트: 화면을 새로고침 했을 때, DB에 저장된 리뷰가 화면에 올바르게 나타나는지 확인합니다.

완성 코드

app.py코드<서버>

from flask import Flask, render_template, request, jsonify
app = Flask(__name__)

from pymongo import MongoClient
client = MongoClient('내 mongoDB URL')
db = client.dbsparta

import requests
from bs4 import BeautifulSoup

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

@app.route("/movie", methods=["POST"])
def movie_post():
    url_receive = request.form['url_give']
    comment_receive = request.form['comment_give']

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
    data = requests.get(url_receive, headers=headers)

    soup = BeautifulSoup(data.text, 'html.parser')

    ogimage = soup.select_one('meta[property="og:image"]')['content']
    ogtitle = soup.select_one('meta[property="og:title"]')['content']
    ogdesc = soup.select_one('meta[property="og:description"]')['content']


    doc = {
        'title':ogtitle,
        'desc':ogdesc,
		'image':ogimage,
        'comment':comment_receive
    }

    db.movies.insert_one(doc)

    return jsonify({'msg':'저장완료!'})

@app.route("/movie", methods=["GET"])
def movie_get():
    all_movies = list(db.movies.find({},{'_id':False}))
    return jsonify({'result':all_movies})

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

index.html<클라이언트>

<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
        crossorigin="anonymous"></script>

    <title>스파르타 피디아</title>

    <link href="https://fonts.googleapis.com/css2?family=Gowun+Dodum&display=swap" rel="stylesheet">

    <style>
        * {
            font-family: 'Gowun Dodum', sans-serif;
        }

        .mytitle {
            width: 100%;
            height: 250px;

            background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('https://movie-phinf.pstatic.net/20210715_95/1626338192428gTnJl_JPEG/movie_image.jpg');
            background-position: center;
            background-size: cover;

            color: white;

            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
        }

        .mytitle>button {
            width: 200px;
            height: 50px;

            background-color: transparent;
            color: white;

            border-radius: 50px;
            border: 1px solid white;

            margin-top: 10px;
        }

        .mytitle>button:hover {
            border: 2px solid white;
        }

        .mycomment {
            color: gray;
        }

        .mycards {
            margin: 20px auto 0px auto;
            width: 95%;
            max-width: 1200px;
        }

        .mypost {
            width: 95%;
            max-width: 500px;
            margin: 20px auto 0px auto;
            padding: 20px;
            box-shadow: 0px 0px 3px 0px gray;

            display: none;
        }

        .mybtns {
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: center;

            margin-top: 20px;
        }

        .mybtns>button {
            margin-right: 10px;
        }
    </style>
    <script>
        $(document).ready(function () {
            listing();
        });

        function listing() {
            fetch('/movie').then(res => res.json()).then(data => {
                    let rows = data['result']
				            $('#cards-box').empty()
                    rows.forEach((a) => {
												let comment = a['comment']
                        let title = a['title']
                        let image = a['image']
                        let desc = a['desc']

                        let temp_html = `<div class="col">
                                            <div class="card h-100">
                                                <img src="${image}"
                                                     class="card-img-top">
                                                <div class="card-body">
                                                    <h5 class="card-title">${title}</h5>
                                                    <p class="card-text">${desc}</p>
                                                    <p class="mycomment">${comment}</p>
                                                </div>
                                            </div>
                                        </div>`

                        $('#cards-box').append(temp_html)
                    })
                })
        }

        function posting() {
            let url = $('#url').val()
            let comment = $('#comment').val()

            let formData = new FormData()
            formData.append("url_give", url)
            formData.append("comment_give", comment)

            fetch('/movie', {method: "POST",body: formData}).then(res => res.json()).then(data => {
                    alert(data['msg'])
                    window.location.reload()
                })
        }

        function open_box() {
            $('#post-box').show()
        }
        function close_box() {
            $('#post-box').hide()
        }
    </script>
</head>

<body>
    <div class="mytitle">
        <h1>내 생애 최고의 영화들</h1>
        <button onclick="open_box()">영화 기록하기</button>
    </div>
    <div class="mypost" id="post-box">
        <div class="form-floating mb-3">
            <input id="url" type="email" class="form-control" placeholder="name@example.com">
            <label>영화URL</label>
        </div>
        <div class="form-floating">
            <textarea id="comment" class="form-control" placeholder="Leave a comment here"></textarea>
            <label for="floatingTextarea2">코멘트</label>
        </div>
        <div class="mybtns">
            <button onclick="posting()" type="button" class="btn btn-dark">기록하기</button>
            <button onclick="close_box()" type="button" class="btn btn-outline-dark">닫기</button>
        </div>
    </div>
    <div class="mycards">
        <div class="row row-cols-1 row-cols-md-4 g-4" id="cards-box">
            <div class="col">
                <div class="card h-100">
                    <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                        class="card-img-top">
                    <div class="card-body">
                        <h5 class="card-title">영화 제목이 들어갑니다</h5>
                        <p class="card-text">여기에 영화에 대한 설명이 들어갑니다.</p>
                        <p>⭐⭐⭐</p>
                        <p class="mycomment">나의 한줄 평을 씁니다</p>
                    </div>
                </div>
            </div>
            <div class="col">
                <div class="card h-100">
                    <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                        class="card-img-top">
                    <div class="card-body">
                        <h5 class="card-title">영화 제목이 들어갑니다</h5>
                        <p class="card-text">여기에 영화에 대한 설명이 들어갑니다.</p>
                        <p>⭐⭐⭐</p>
                        <p class="mycomment">나의 한줄 평을 씁니다</p>
                    </div>
                </div>
            </div>
            <div class="col">
                <div class="card h-100">
                    <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                        class="card-img-top">
                    <div class="card-body">
                        <h5 class="card-title">영화 제목이 들어갑니다</h5>
                        <p class="card-text">여기에 영화에 대한 설명이 들어갑니다.</p>
                        <p>⭐⭐⭐</p>
                        <p class="mycomment">나의 한줄 평을 씁니다</p>
                    </div>
                </div>
            </div>
            <div class="col">
                <div class="card h-100">
                    <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                        class="card-img-top">
                    <div class="card-body">
                        <h5 class="card-title">영화 제목이 들어갑니다</h5>
                        <p class="card-text">여기에 영화에 대한 설명이 들어갑니다.</p>
                        <p>⭐⭐⭐</p>
                        <p class="mycomment">나의 한줄 평을 씁니다</p>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>

</html>
profile
뉴비입니다.

0개의 댓글