콘텐츠로 건너뛰기

[스파르타 웹 개발 종합] Flask 사용하여 서버 만들기. 클라이언트-서버-DB 통신 구축하기

4주차~5주차 초반 에서는 로컬 개발환경에서 개발. 5주차 후반에는 별도 컴퓨터에 서버 개발

HTML과 MongoDB까지 연동해서 서버를 개발해 봅시다

pycharm 프로젝트에서 venv 폴더 란?

main.cpp 처럼 파이썬에서 프로그램이 돌아가게 만드는 파일을 보통 app.py로 이름을 지음

1. 서버 만들기

새로운 프로젝트 생성. pyCharm에 Flask 프레임워크 설치

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

파일 이름은 아무렇게나 해도 상관없지만, 통상적으로 flask 서버를 돌리는 파일은 app.py라고 이름을 짓습니다

라이브러리 / 프레임워크?

프레임워크는 남이 짜둔 규칙이나 틀 안에서 내가 코딩을 자유롭게 하는 것.
라이브러리는 내가 내 마음대로 코딩을 하는데, 중간에 남이 만들어놓은 코드를 조금씩 가져다 쓰는 것

Flask 서버 실행 코드

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)

위 코드를 실행 후 http://localhost:5000/ 로 접속. localhost:5000이 현재 돌아가고 있는 서버

실제로 몇가지 세팅을 더 하면, 남이 볼 수 있는 서버를 만들 수 있음

2. HTML 파일 서버에 로딩하기

HTML 코드를 입력하면 웹에 구현 됌

from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
   return '<button> 나는 버튼이다</button>'


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

이렇게 쓰면 너무 복잡하니까, framework를 사용

Flask framework는 정해진 구조가 있음

app.py: 프로그램을 실행시키는 파일

static 폴더: css나 이미지 파일을 담아두는 폴더

templates 폴더: html 파일을 담아두는 폴더

templates 폴더에 html을 생성해주고, app.py에 명시해주면 서버가 해당 html 파일을 불러와줌

app.py

from flask import Flask, render_template
# render_template: template 파일을 읽어오기 위한 것
app = Flask(__name__)

@app.route('/')
def home():
   return render_template('index.html')
   # render_template: template 파일 중 index.html을 불러 옴 


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">
    <title>Title</title>
</head>
<body>
    하나만 입력해 봅시다.
</body>
</html>

3. 본격 API 만들기

API에도 규칙이 있다. – 대표적인 2가지 방식: GET, POST

클라이언트가 서버에 요청을 할 때에는 ‘방식’ 이 존재한다.
HTTP라는 통신 규약을 따른다는 것 잊지 않으셨죠? 클라이언트는 요청할 때 HTTP request method(요청 메소드)를 통해, 어떤 요청 종류인지 서버 쪽에 정보를 알려주는 거에요.

GET / POST 방식

여러 방식이 존재하지만 우리는 가장 많이 쓰이는 GET, POST 방식을 다룬다
GET: 통상적으로 데이터 조회(read)를 요청 할 때 쓰임
POST: 통상적으로 데이터를 변경(update) 할 때 쓰임

서버에 데이터 요청할 때 어떻게 해 ? Ajax 사용!

클라이언트는 Ajax를 이용해 서버에 요청
서버는 API를 이용해 클라이언트에 답변

GET 요청 API 코드 (서버 단)

@app.route('/test', methods=['GET'])
def test_get():
   title_receive = request.args.get('title_give')
   print(title_receive)
   return jsonify({'result':'success', 'msg': '이 요청은 GET!'})

GET 요청 Ajax 코드 (클라이언트 단)

$.ajax({
    type: "GET",
    url: "/test?title_give=봄날은간다",
    data: {},
    success: function(response){
       console.log(response)
    }
  })

app.py 코드

from flask import Flask, render_template, request, jsonify
# render_template: template 파일을 읽어오기 위한 것
# request, jsonify: GET 요청 API 코드를 위한 것
app = Flask(__name__)

@app.route('/')
def home():
   return render_template('index.html')
   # render_template: template 파일 중 index.html을 불러 옴

# GET 요청 API 코드
@app.route('/test', methods=['GET'])
def test_get():
   title_receive = request.args.get('title_give')
   print(title_receive)
   return jsonify({'result':'success', 'msg': '이 요청은 GET!'})

# POST 요청 API 코드
@app.route('/test', methods=['POST'])
def test_post():
   title_receive = request.form['title_give']
   print(title_receive)
   return jsonify({'result':'success', 'msg': '이 요청은 POST!'})

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

Ajax 코드를 localhost:5000 콘솔에 입력하면 다음과 같은 결과값이 나온다. API에서 클라이언트에 보낸 값이다
{msg: “이 요청은 GET!”, result “success”}

동시에 서버에서는 아래와 같은 결과 값이 나온다. 클라이언트에서 보낸 값 ‘봄날은간다’를 출력한다.

post 요청 API 코드

@app.route('/test', methods=['POST'])
 def test_post():
    title_receive = request.form['title_give']
    print(title_receive)
    return jsonify({'result':'success', 'msg': '이 요청은 POST!'})

post 요청 ajax 코드

$.ajax({
    type: "POST",
    url: "/test",
    data: { title_give:'봄날은간다' },
    success: function(response){
       console.log(response)
    }
  })

이 코드에 대한 결과로, 클라이언트 콘솔에는 {msg: “이 요청은 POST!”, result: “success”}가 나타나고, 서버 출력창에는 ‘봄날은간다’ 가 출력됨

연습하기1. 모두의 책 리뷰 사이트

1) 프로젝트 및 필요 파일, 폴더 생성(app.py, static folder, templates folder, index.html)

2) 필요한 패키지 설치 – Flask, pymongo

3) 뼈대 준비하기

모두의 책 리뷰 – app.py 뼈대 코드

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

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

## HTML을 주는 부분
@app.route('/')
def home():
    return render_template('index.html')

## API 역할을 하는 부분
@app.route('/review', methods=['POST'])
def write_review():
    sample_receive = request.form['sample_give']
    print(sample_receive)
    return jsonify({'msg': '이 요청은 POST!'})


@app.route('/review', methods=['GET'])
def read_reviews():
    sample_receive = request.args.get('sample_give')
    print(sample_receive)
    return jsonify({'msg': '이 요청은 GET!'})


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

모두의 책 리뷰 – index.html 뼈대 코드

<!DOCTYPE html>
<html lang="ko">

    <head>
        <!-- Webpage Title -->
        <title>모두의 책리뷰 | 스파르타코딩클럽</title>

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

        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
              integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
              crossorigin="anonymous">

        <!-- JS -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
                integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
                crossorigin="anonymous"></script>

        <!-- 구글폰트 -->
        <link href="https://fonts.googleapis.com/css?family=Do+Hyeon&display=swap" rel="stylesheet">

        <script type="text/javascript">

            $(document).ready(function () {
                $("#reviews-box").html("");
                showReview();
            });

            function makeReview() {
                $.ajax({
                    type: "POST",
                    url: "/review",
                    data: {sample_give:'샘플데이터'},
                    success: function (response) {
                        alert(response["msg"]);
                        window.location.reload();
                    }
                })
            }

            function showReview() {
                $.ajax({
                    type: "GET",
                    url: "/review?sample_give=샘플데이터",
                    data: {},
                    success: function (response) {
                        alert(response["msg"]);
                    }
                })
            }
        </script>

        <style type="text/css">
            * {
                font-family: "Do Hyeon", sans-serif;
            }

            h1,
            h5 {
                display: inline;
            }

            .info {
                margin-top: 20px;
                margin-bottom: 20px;
            }

            .review {
                text-align: center;
            }

            .reviews {
                margin-top: 100px;
            }
        </style>
    </head>

    <body>
        <div class="container">
            <img src="https://previews.123rf.com/images/maxxyustas/maxxyustas1511/maxxyustas151100002/47858355-education-concept-books-and-textbooks-on-the-bookshelf-3d.jpg"
                 class="img-fluid" alt="Responsive image">
            <div class="info">
                <h1>읽은 책에 대해 말씀해주세요.</h1>
                <p>다른 사람을 위해 리뷰를 남겨주세요! 다 같이 좋은 책을 읽는다면 다 함께 행복해질 수 있지 않을까요?</p>
                <div class="input-group mb-3">
                    <div class="input-group-prepend">
                        <span class="input-group-text">제목</span>
                    </div>
                    <input type="text" class="form-control" id="title">
                </div>
                <div class="input-group mb-3">
                    <div class="input-group-prepend">
                        <span class="input-group-text">저자</span>
                    </div>
                    <input type="text" class="form-control" id="author">
                </div>
                <div class="input-group mb-3">
                    <div class="input-group-prepend">
                        <span class="input-group-text">리뷰</span>
                    </div>
                    <textarea class="form-control" id="bookReview"
                              cols="30"
                              rows="5" placeholder="140자까지 입력할 수 있습니다."></textarea>
                </div>
                <div class="review">
                    <button onclick="makeReview()" type="button" class="btn btn-primary">리뷰 작성하기</button>
                </div>
            </div>
            <div class="reviews">
                <table class="table">
                    <thead>
                    <tr>
                        <th scope="col">제목</th>
                        <th scope="col">저자</th>
                        <th scope="col">리뷰</th>
                    </tr>
                    </thead>
                    <tbody id="reviews-box">
                    <tr>
                        <td>왕초보 8주 코딩</td>
                        <td>김르탄</td>
                        <td>역시 왕초보 코딩교육의 명가답군요. 따라하다보니 눈 깜짝할 사이에 8주가 지났습니다.</td>
                    </tr>
                    </tbody>
                </table>
            </div>
        </div>
    </body>

</html>

4) post 연습 (리뷰 저장)
– 클라이언트와 서버 확인하기
– 서버부터 만들기: 클라이언트에서 보내는 데이터(title, author, review)를 가져오기. 데이터를 DB에 저장하기
– 클라이언트 만들기: input form에 입력되는 데이터를 읽어오기(jQuery). 읽어온 데이터를 DB에 보내기(ajax)
– 완성 확인하기

app.py

## API 역할을 하는 부분
## POST API
@app.route('/review', methods=['POST'])
def write_review():
    # 클라이언트로부터 데이터 가져오기
    title_receive = request.form['title_give']
    author_receive = request.form['author_give']
    review_receive = request.form['review_give']
    print(title_receive, author_receive, review_receive)

    # 가져온 데이터를 DB에 저장하기
    doc = {
        'title': title_receive,
        'author': author_receive,
        'review': review_receive
    }
    db.bookreview.insert_one(doc)

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

index.html

            // ajax - post
            function makeReview() {

                // input form에 입력된 데이터 읽어오기(jquery)
                let title = $('#title').val()
                let author = $('#author').val()
                let review = $('#bookReview').val()

                // 읽어온 데이터를 서버로 POST 요청 하기(ajax)
                $.ajax({
                    type: "POST",
                    url: "/review",
                    data: {title_give:title, author_give:author, review_give: review},
                    success: function (response) {
                        alert(response["msg"]);
                        window.location.reload();
                    }
                })
            }

DB에는 이렇게 저장된다

5) get 연습 (리뷰 보여주기)
– 클라이언트와 서버 확인하기
– 서버부터 만들기: DB에 저장된 리뷰 데이터를 읽어 오기. 읽어온 데이터를 클라이언트에 보내는 API 작성하기
– 클라이언트 만들기: 페이지 로딩이 완료된 시점에 서버에 리뷰 데이터 요청하기. 서버에서 받아온 리뷰 데이터를 html에 붙여서 출력하기
– 완성 확인하기

app.py

## GET API
@app.route('/review', methods=['GET'])
def read_reviews():
    #DB에서 데이터(title, author, review)를 읽어와 클라이언트에 보내는 함수

    #DB에서 데이터 읽어오기
    reviews = list(db.bookreview.find({}, {'_id': False}))

    #읽어온 데이터를 클라이언트로 return
    return jsonify({'all_reviews': reviews})

index.html

            // ajax - get
            function showReview() {

                // 서버에 리뷰 데이터 요청하기
                $.ajax({
                    type: "GET",
                    url: "/review",
                    data: {},
                    success: function (response) {
                        let reviews = response['all_reviews'];

                        // 읽어온 리뷰 데이터를 테이블(#reviews-box)에 추가하기
                        for (let i=0; i<reviews.length; i++){
                            let title = reviews[i]['title']
                            let author = reviews[i]['author']
                            let review = reviews[i]['review']

                            let temp_html =
                                `
                                <tr>
                                  <td>${title}</td>
                                  <td>${author}</td>
                                  <td>${review}</td>
                                </tr>
                                `
                            $('#reviews-box').append(temp_html)
                        }
                    }
                })
            }

데이터가 이렇게 화면에 나타난다

연습하기 2 – 영화 URL과 리뷰를 입력하면 영화 정보와 리뷰를 등록해주는 사이트 구현 (Alonememo)

1) 폴더 구성

2) 필요한 Package 설치 – Flask, Pymongo, Requests, BS4

3) 뼈대

  • API 설계하기
    기능1: 클라이언트로부터 데이터 입력 받기(URL, 리뷰) – post 방식 API / 버튼 클릭 시
    기능2: 데이터 스크래핑 – URL의 meta 태그 정보를 바탕으로 제목, 설명, 이미지 URL을 스크래핑
    기능3: 스크래핑 한 데이터를 DB에 저장
    기능4: 저장된 데이터를 클라이언트에 보여주기 – get 방식 API / 페이지 로딩 끝날 시

잠깐! – 입력받는 URL만 가지고 어떻게 여러가지 데이터(제목, 설명, 이미지 URL)를 스크래핑 하지?

meta태그를 활용한다. meta태그란? header에 들어있음.

meta 태그는 <head></head> 부문에 들어가는, 눈으로 보이는 것(body) 외에 사이트의 속성을 설명해주는 태그들입니다. ex) og:image, og:title, og:description 태그

4) 조각 코드 구현/확인하기

  • 스크래핑 연습하기 – app.py에 바로 구현하는 게 아니고, 스크래핑을 어떻게 구현할지 테스트 하는 파일을 별도로 만들어(ex. meta_prac.py)
import requests
from bs4 import BeautifulSoup

url = 'https://movie.naver.com/movie/bi/mi/basic.nhn?code=171539'

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,headers=headers)

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

# 여기에 코딩을 해서 meta tag를 먼저 가져와보겠습니다.
title = soup.select_one('head > meta:nth-child(9)')
print(title)

이렇게 하면 title print 안됨. 이유? 사람이 접속했을 때 meta tag 순서와 python 코드가 접속했을 때 meta tag의 순서가 다름

# 명시한 property에 맞는 meta tag를 가져 온다
title = soup.select_one('meta[property="og:title"]')
print(title)
# 명시한 property에 맞는 meta tag를 가져 온다
title = soup.select_one('meta[property="og:title"]')['content']
print(title)
image = soup.select_one('meta[property="og:image"]')['content']
print(image)
desc = soup.select_one('meta[property="og:description"]')['content']
print(desc)

5) 뼈대 소스

app.py

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

import requests
from bs4 import BeautifulSoup

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

## HTML을 주는 부분
@app.route('/')
def home():
   return render_template('index.html')

@app.route('/memo', methods=['GET'])
def listing():
    sample_receive = request.args.get('sample_give')
    print(sample_receive)
    return jsonify({'msg':'GET 연결되었습니다!'})

## API 역할을 하는 부분
@app.route('/memo', methods=['POST'])
def saving():
    sample_receive = request.form['sample_give']
    print(sample_receive)
    return jsonify({'msg':'POST 연결되었습니다!'})

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

index.html

<!Doctype html>
<html lang="ko">

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

        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
              integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
              crossorigin="anonymous">

        <!-- JS -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
                integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
                crossorigin="anonymous"></script>

        <!-- 구글폰트 -->
        <link href="https://fonts.googleapis.com/css?family=Stylish&display=swap" rel="stylesheet">


        <title>스파르타코딩클럽 | 나홀로 메모장</title>

        <!-- style -->
        <style type="text/css">
            * {
                font-family: "Stylish", sans-serif;
            }

            .wrap {
                width: 900px;
                margin: auto;
            }

            .comment {
                color: blue;
                font-weight: bold;
            }

            #post-box {
                width: 500px;
                margin: 20px auto;
                padding: 50px;
                border: black solid;
                border-radius: 5px;
            }
        </style>
        <script>
            $(document).ready(function () {
                showArticles();
            });
            
            function openClose() {
                if ($("#post-box").css("display") == "block") {
                    $("#post-box").hide();
                    $("#btn-post-box").text("포스팅 박스 열기");
                } else {
                    $("#post-box").show();
                    $("#btn-post-box").text("포스팅 박스 닫기");
                }
            }

            function postArticle() {
                $.ajax({
                    type: "POST",
                    url: "/memo",
                    data: {sample_give:'샘플데이터'},
                    success: function (response) { // 성공하면
                        alert(response["msg"]);
                    }
                })
            }

            function showArticles() {
                $.ajax({
                    type: "GET",
                    url: "/memo?sample_give=샘플데이터",
                    data: {},
                    success: function (response) {
                        alert(response["msg"]);
                    }
                })
            }
        </script>

    </head>

    <body>
        <div class="wrap">
            <div class="jumbotron">
                <h1 class="display-4">나홀로 링크 메모장!</h1>
                <p class="lead">중요한 링크를 저장해두고, 나중에 볼 수 있는 공간입니다</p>
                <hr class="my-4">
                <p class="lead">
                    <button onclick="openClose()" id="btn-post-box" type="button" class="btn btn-primary">포스팅 박스 열기
                    </button>
                </p>
            </div>
            <div id="post-box" class="form-post" style="display:none">
                <div>
                    <div class="form-group">
                        <label for="post-url">아티클 URL</label>
                        <input id="post-url" class="form-control" placeholder="">
                    </div>
                    <div class="form-group">
                        <label for="post-comment">간단 코멘트</label>
                        <textarea id="post-comment" class="form-control" rows="2"></textarea>
                    </div>
                    <button type="button" class="btn btn-primary" onclick="postArticle()">기사저장</button>
                </div>
            </div>
            <div id="cards-box" class="card-columns">
                <div class="card">
                    <img class="card-img-top"
                         src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg"
                         alt="Card image cap">
                    <div class="card-body">
                        <a target="_blank" href="#" class="card-title">여기 기사 제목이 들어가죠</a>
                        <p class="card-text">기사의 요약 내용이 들어갑니다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세 무궁화 삼천리 화려강산...</p>
                        <p class="card-text comment">여기에 코멘트가 들어갑니다.</p>
                    </div>
                </div>
                <div class="card">
                    <img class="card-img-top"
                         src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg"
                         alt="Card image cap">
                    <div class="card-body">
                        <a target="_blank" href="#" class="card-title">여기 기사 제목이 들어가죠</a>
                        <p class="card-text">기사의 요약 내용이 들어갑니다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세 무궁화 삼천리 화려강산...</p>
                        <p class="card-text comment">여기에 코멘트가 들어갑니다.</p>
                    </div>
                </div>
                <div class="card">
                    <img class="card-img-top"
                         src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg"
                         alt="Card image cap">
                    <div class="card-body">
                        <a target="_blank" href="#" class="card-title">여기 기사 제목이 들어가죠</a>
                        <p class="card-text">기사의 요약 내용이 들어갑니다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세 무궁화 삼천리 화려강산...</p>
                        <p class="card-text comment">여기에 코멘트가 들어갑니다.</p>
                    </div>
                </div>
                <div class="card">
                    <img class="card-img-top"
                         src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg"
                         alt="Card image cap">
                    <div class="card-body">
                        <a target="_blank" href="#" class="card-title">여기 기사 제목이 들어가죠</a>
                        <p class="card-text">기사의 요약 내용이 들어갑니다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세 무궁화 삼천리 화려강산...</p>
                        <p class="card-text comment">여기에 코멘트가 들어갑니다.</p>
                    </div>
                </div>
                <div class="card">
                    <img class="card-img-top"
                         src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg"
                         alt="Card image cap">
                    <div class="card-body">
                        <a target="_blank" href="#" class="card-title">여기 기사 제목이 들어가죠</a>
                        <p class="card-text">기사의 요약 내용이 들어갑니다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세 무궁화 삼천리 화려강산...</p>
                        <p class="card-text comment">여기에 코멘트가 들어갑니다.</p>
                    </div>
                </div>
                <div class="card">
                    <img class="card-img-top"
                         src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg"
                         alt="Card image cap">
                    <div class="card-body">
                        <a target="_blank" href="#" class="card-title">여기 기사 제목이 들어가죠</a>
                        <p class="card-text">기사의 요약 내용이 들어갑니다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세 무궁화 삼천리 화려강산...</p>
                        <p class="card-text comment">여기에 코멘트가 들어갑니다.</p>
                    </div>
                </div>
            </div>
        </div>
    </body>

</html>

6) 데이터 저장 부분 만들기 – POST API

app.py

## API 역할을 하는 부분
@app.route('/memo', methods=['POST'])
def saving():
    ## 클라이언트에서 url_give, comment_give 받아오기
    url_receive = request.form['url_give']
    comment_receive = request.form['comment_give']
    print(url_receive, comment_receive)

    ## 스크래핑하기 - 받은 URL을 사용해서 meta tag 스크래핑
    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')

    # 명시한 property에 맞는 meta tag를 가져 온다
    title = soup.select_one('meta[property="og:title"]')['content']
    image = soup.select_one('meta[property="og:image"]')['content']
    desc = soup.select_one('meta[property="og:description"]')['content']

    ## 데이터를 DB에 저장
    doc = {
        'title': title,
        'image': image,
        'desc': desc,
        'url': url_receive,
        'comment': comment_receive
    }
    db.article.insert_one(doc)

    return jsonify({'msg':'저장되었습니다.'})

index.html

            function postArticle() {
                let url = $('#post-url').val()
                let comment = $('#post-comment').val()

                $.ajax({
                    type: "POST",
                    url: "/memo",
                    data: {url_give: url, comment_give: comment},
                    success: function (response) { // 성공하면
                        alert(response["msg"]);
                        window.location.reload()
                    }
                })
            }

7) 데이터 보여주기 – GET API

app.py

@app.route('/memo', methods=['GET'])
def listing():
    # DB에서 모든 데이터 읽어오기
    articles = list(db.article.find({}, {'_id': False}))
    # 데이터를 클라이언트에 넘기기
    return jsonify({'all_articles':articles})

index.html

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

            function showArticles() {
                $.ajax({
                    type: "GET",
                    url: "/memo",
                    data: {},
                    success: function (response) {
                        let articles = response["all_articles"]

                        for(let i=0; i<articles.length; i++){
                            title = articles[i]['title']
                            comment = articles[i]['comment']
                            url = articles[i]['url']
                            image = articles[i]['image']
                            desc = articles[i]['desc']
                            console.log(title, comment, url, image, desc)

                            temp_html = `
                                        <div class="card">
                                            <img class="card-img-top"
                                                 src=${image}
                                                 alt="Card image cap">
                                            <div class="card-body">
                                                <a target="_blank" href=${url} class="card-title">${title}</a>
                                                <p class="card-text">${desc}</p>
                                                <p class="card-text comment">${comment}</p>
                                            </div>
                                        </div>
                                      `
                            $('#cards-box').append(temp_html)

                        }
                    }
                })
            }

연습하기3 (숙제) – 나홀로 쇼핑몰에 아래 기능 추가하기

쇼핑몰은 두 가지 기능을 수행해야 합니다.

1) 주문하기(POST): 정보 입력 후 ‘주문하기’ 버튼클릭 시 주문목록에 추가
2) 주문내역보기(GET): 페이지 로딩 후 하단 주문 목록이 자동으로 보이기

아래 완성본을 참고해주세요!

http://spartacodingclub.shop/homework

  1. 주문하기 (POST) API
    • app.py
      – 클라이언트에서 데이터 가져오기
      – 데이터를 DB에 저장하기
    • index.html
      – ‘주문하기’ 버튼을 누르면 post 요청 보내기(데이터 -이름, 수량, 주소, 전화번호)

완성 코드

app.py

#  local server 구현 코드 (client: index.html)
#  스파르타 웹 개발 종합반 4주차 숙제 - 2주차에 만든 쇼핑몰에 아래 기능 추가하기
#  1) 주문하기(POST): 정보 입력 후 '주문하기' 버튼클릭 시 주문목록에 추가
#  2) 주문내역보기(GET): 페이지 로딩 후 하단 주문 목록이 자동으로 보이기
#
#  4주차 내용: 클라이언트-서버-DB 통신 구현하기. Flask, pyMongo, requests 패키지 활용

from flask import Flask, render_template, jsonify, request

app = Flask(__name__)

from pymongo import MongoClient

client = MongoClient('localhost', 27017)
db = client.dbhomework


## HTML 화면 보여주기
@app.route('/')
def homework():
    return render_template('index.html')


# 주문하기(POST) API
@app.route('/order', methods=['POST'])
def save_order():
    # 클라이언트로부터 데이터 받아오기
    name_receive = request.form['name_give']
    qty_receive = request.form['qty_give']
    address_receive = request.form['address_give']
    phone_num_receive = request.form['phone_num_give']
    print(name_receive,qty_receive,address_receive,phone_num_receive)

    # DB에 데이터 저장하기
    doc = {
        'name': name_receive,
        'qty': qty_receive,
        'address': address_receive,
        'phone_num': phone_num_receive
    }

    db.order.insert_one(doc)
    return jsonify({'msg': '주문이 완료되었습니다'})


# 주문 목록보기(Read) API
@app.route('/order', methods=['GET'])
def view_orders():
    # DB에 저장된 데이터(주문 목록) 모두 가져오기
    orders = list(db.order.find({}, {'_id': False}))

    # 데이터를 client로 전송
    return jsonify({'all_orders': orders})


if __name__ == '__main__':
    app.run('0.0.0.0', port=5000, debug=True)
<!--  client 구현 코드 (server: app.py)  -->
<!--  스파르타 웹 개발 종합반 4주차 숙제 - 2주차에 만든 쇼핑몰에 아래 기능 추가하기  -->
<!--  1) 주문하기(POST): 정보 입력 후 '주문하기' 버튼클릭 시 주문목록에 추가  -->
<!--  2) 주문내역보기(GET): 페이지 로딩 후 하단 주문 목록이 자동으로 보이기  -->

<!--  4주차 내용: 클라이언트-서버-DB 통신 구현하기. Flask, pyMongo, requests 패키지 활용  -->


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

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

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
          integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
            integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
            crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
            integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
            crossorigin="anonymous"></script>

    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap" rel="stylesheet">
    <script>
        $(document).ready(function () {
            // 페이지 로드 완료 시 함수 실행
            show_exchange_rate() // 환율 서버에서 현재 환율 값 읽어오기
            show_orders() // 로컬 서버에서 저장된 주문 데이터 읽어오기
        });

        function show_exchange_rate(){
            // 환율 서버에서 환율 값 읽어오기(ajax-GET)
            $.ajax({
                type: "GET",
                url: "https://api.manana.kr/exchange/rate.json",
                data: {},
                success: function (response) {
                    console.log(response)
                    let rate = response[1]['rate'];
                    $('#exchange-rate').text(rate);
                }
            })
        }
        function show_orders(){
            // 로컬 서버에서 주문 목록 불러오기(ajax-GET). 테이블에 출력하기(jquery-append)
             $.ajax({
                type: "GET",
                url: "/order",
                data: {},
                success: function (response) {
                    let all_orders = response['all_orders']

                    for(let i=0; i<all_orders.length; i++){
                        let index = i+1
                        let name = all_orders[i]['name']
                        let address = all_orders[i]['address']
                        let phone_num = all_orders[i]['phone_num']
                        let qty = all_orders[i]['qty']

                        let temp_html
                            = `
                               <tr>
                                  <th scope="row">${index}</th>
                                  <td>${name}</td>
                                  <td>${qty}</td>
                                  <td>${address}</td>
                                  <td>${phone_num}</td>
                                </tr>
                            `
                        $('#order-list').append(temp_html)

                    }
                }
            })
        }

        function submit_order() {

            // 입력 폼에 입력된 값 가져오기(jquery-val 함수). 입력 값을 서버로 넘기기(ajax-POST)
            let name = $('#inputName').val()
            let address = $('#inputAddress').val()
            let qty = $('#inputQty').val()
            let phoneNum = $('#inputPhoneNum').val()

            $.ajax({
                type: "POST",
                url: "/order",
                data: {name_give: name, address_give: address, phone_num_give: phoneNum, qty_give: qty },
                success: function (response) {
                    alert(response['msg']);
                    window.location.reload()
                }
            })

        }


    </script>

    <title> 1주차 숙제 | 내 쇼핑몰 </title>
</head>
<style>

    * {
        font-family: 'Noto Sans KR', sans-serif;
    }

    .wrapper-all {
        width: 680px;
        margin: auto;
        padding: 20px;
    }

    .wrapper-prod-info {
        margin-top: 20px;
        margin-bottom: 20px;
    }

    .prod-name, .prod-price {
        display: inline;
    }

    .prod-description {
        line-height: 1.8;
        margin-top: 10px;

    }

    .prod-pic {
        background-image: url("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTYmGQIzvyc962hMqPLxMXHoumLCVuig3mmzg&usqp=CAU");
        height: 300px;
        background-size: cover;
        background-position: center;

    }

    .btn-submit {
        width: 120px;
        margin: auto;
        display: block;
    }

</style>

<body>
<div class="wrapper-all">
<div class="wrapper-prod">
    <div class="prod-pic">

    </div>
    <div class="wrapper-prod-info">
        <h1 class="prod-name"> 캠핑 의자</h1>
        <h5 class="prod-price"> 가격:34,800 원 / 개</h5>
        <p class="prod-description"> 우드 스타일 프레임과 우드 암레스트로 더욱 고급스러운 체어. 인체공학적 설계로 착석 시 편안함과 안락함이 뛰어난 체어</p>
    </div>

    <hr>
    <div class="wrapper-exchange-rate">
        <p> 달러/원 환율: <span id="exchange-rate"> </span></p>

    </div>
    <hr>

    <div class="wrapper-input">
        <div class="form-group row">
            <label for="inputName" class="col-sm-2 col-form-label">주문자 이름</label>
            <div class="col-sm-10">
                <input type="text" class="form-control" id="inputName">
            </div>
        </div>

        <div class="form-group row">
                <label for="inputQty" class="col-sm-2 col-form-label">수량</label>
                <div class="col-sm-10">

                    <select class="form-control" id="inputQty">
                        <option>수량을 선택하세요</option>
                        <option> 1</option>
                        <option> 2</option>
                        <option> 3</option>
                        <option> 4</option>
                        <option> 5</option>
                    </select>
                </div>
            </div>


            <div class="form-group row">
                <label for="inputAddress" class="col-sm-2 col-form-label">주소</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" id="inputAddress">
                </div>
            </div>
            <div class="form-group row">
                <label for="inputPhoneNum" class="col-sm-2 col-form-label">전화번호</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" id="inputPhoneNum">
                </div>
            </div>
            <div class="form-group row">
                <div class="col-sm-10">
                    <button type="submit" class="btn btn-primary btn-submit" onclick="submit_order()">주문하기</button>
                </div>
            </div>
        </div>

    </div>
<div class="wrapper-order-list">
    <table class="table table-striped" id="order-list">
  <thead>
    <tr>
      <th scope="col">#</th>
      <th scope="col">이름</th>
      <th scope="col">수량</th>
      <th scope="col">주소</th>
      <th scope="col">전화번호</th>
    </tr>
  </thead>
  <tbody>
  </tbody>
</table>

</div>
    </div>
</body>


</html>