[스파르타코딩클럽] 웹개발 종합반 - 4주차 개발일지

권혁준·2022년 3월 8일
0

📚 [수업 목표]
1. Flask 프레임워크를 활용해서 API를 만들 수 있다.
2. '모두의책리뷰' API를 만들고 클라이언트에 연결한다.
3. '나홀로메모장' API를 만들고 클라이언트와 연결한다.

Flask

1. 서버만들기

1) Flask 패키지 설치

✅ 패키지 설치 화면 진입하는 방법
맥 : 좌상단 Pycharm → Preference → Python Interpreter

  • python interpreter 화면에서 + 버튼을 누르면 아래 창이 뜬다
    (맥은 아래에 위치)

  • flask로 검색한 후 install package 클릭

2) Flask 기초 : 기본 실행

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

  • app.py 파일을 만들어 코드를 붙여넣기

    ✅ 통상적으로 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)
  • 오른쪽 클릭 → 'Run app'을 클릭 터미널에서 아래와 같은 메시지가 뜨면 실행 성공

  • 크롬에서 http://localhost:5000/으로 접속

    ✅ 서버 종료하는 방법
    터미널 창을 클릭한 후, command + c를 누르면 종료

3) Flask 기초: URL 나눠보기

  • @app.route('/) 부부늘 수정하면 URL을 나눌 수 있다.

    ✅ URL 별로 함수명이 같거나 route('/')내의 주소가 같으면 안된다

    from flask import Flask
     app = Flask(__name__)
    
     @app.route('/')
     def home():
        return 'This is Home!'
    
     @app.route('/mypage')
     def mypage():  
        return 'This is My Page!'
    
     if __name__ == '__main__':  
        app.run('0.0.0.0',port=5000,debug=True)

2. HTML 파일 주기

1) Flask 기초: 기본 폴더구조 - 항상 이렇게 세팅하고 시작!

Flask 서버를 만들 때 항상

프로젝트 폴더 안에
ㄴstatic 폴더(이미지, css파일 보관)
ㄴtemplates 폴더(html파일 보관)
ㄴapp.py파일

2) Flask 기초: HTML 파일 불러오기

✅ templates 폴더의 역할 : HRML 파일 담아두고 불러오는 역할

(1) index.html 파일을 templates 폴더 안에서 만든다
(2) html 파일 불러오기 - flask 내장함수 render_template를 이용
from flask import Flask, render_template
app = Flask(__name__)

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

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

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

3. 본격 API 만들기

1) 요청타입 - GET, POST 리마인드

클라이언트가 요청할 때 "방식"이 존재
HTTP라는 통신 규약을 따른다. 클라이언트는 요청할 때 HTTP request method(요청 메소드)를 통해 어떤 요청 종류인지 응답하는 서버 쪽에 정보를 알려준다.

  • GET 방식
    • 통상적으로 데이터 조회(Read)를 요청할 때
      ex) 영화 목록 조회
    • 데이터 전달 : URL 뒤에 물음표를 붙여 key=value로 전달
      ex) google.com?q=북극곰
  • POST 방식
    • 통상적으로 데이터 생성(Create), 변경(Update), 삭제(Delete) 요청할 때
      ex) 회원가입, 회원탈퇴, 비밀번호 수정
    • 데이터 전달 : 바로 보이지 않는 HTML body에 key:value 형태로 전달

      🚨 GET, POST 방식 이외에 여러 방식이 존재
      https://developer.mozilla.org/ko/docs/Web/HTTP/Methods

2) GET, POST 요청에서 클라이언트의 데이터를 받는 방법

  • ex) 클라이언트에서 서버에 title_give란 키 값으로 데이터를 들고 왔다고 생각
    (주민등록번호라는 키값으로 850120-..을 가져온 것과 같은 의미)
    • 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)
           }
         })
    • 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)
           }
         })

모두의책리뷰

1. 프로젝트 세팅

1) 프로젝트 설정 - flask 폴더 구조 만들기

  • static, templates 폴더 + app.py 만들기

2. 뼈대 준비하기

1)프로젝트 준비 - 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)

2)프로젝트 준비 - 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 () {
                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>

3. POST 연습(리뷰 저장)

1) API 만들고 사용하기 - 제목, 저자, 리뷰 정보 저장하기(Create → POST)

(1) 클라이언트와 서버 확인

API 정보
1. 요청 정보 : 요청 URL = /review, 요청방식 = POST
2. 서버가 제공할 기능 : 클라이언트에게 정해진 메시지를 보냄
3. 응답 데이터 : (JSON 형식) 'result'='success', 'msg'=리뷰가 성공적으로 작성되었습니다.'

  • [서버 코드 - app.py]
     ## API 역할을 하는 부분
     @app.route('/review', methods=['POST'])
     def write_review():
         # 1. 클라이언트가 준 title, author, review 가져오기.
         # 2. DB에 정보 삽입하기
         # 3. 성공 여부 & 성공 메시지 반환하기
         return jsonify({'result': 'success', 'msg': '리뷰가 성공적으로 작성되었습니다.'})
  • [클라이언트 코드 - index.html]
     function makeReview() {
             // 1. 제목, 저자, 리뷰 내용을 가져옵니다.
             // 2. 제목, 저자, 리뷰 중 하나라도 입력하지 않았을 경우 alert를 띄웁니다.
             // 3. POST /review 에 저장을 요청합니다.
         $.ajax({
             type: "POST",
             url:  "/review",
             data: { },
             success: function (response) {
                 if (response["result"] == "success") {
                     alert(response["msg"] );
                     window.location.reload();
                 }
             }
         })
     }
(2) 서버부터 만들기

API는 약속

리뷰를 작성하기 위해 필요한 정보는 세 가지
- 제목(title)
- 저자(author)
- 리뷰(review)

따라서 API 기능은 세 단계로 구성
1. 클라이언트가 준 title, author, review 가져오기
2. DB에 정보 삽입
3. 성공 여부 & 성공 메시지 반환

⭐️ 사용할 API 정보

A. 요청 정보
- 요청 URL= /review , 요청 방식 = POST
- 요청 데이터 : 제목(title), 저자(author), 리뷰(review)

B. 서버가 제공할 기능 : 클라이언트에게 보낸 요청 데이터를 데이터베이스에 생성(Create)하고, 저장이 성공했다고 응답 데이터를 보냄

C. 응답 데이터 : (JSON 형식) 'msg'= '리뷰가 성공적으로 작성되었습니다.'

@app.route('/review', methods=['POST'])
def write_review():
    # title_receive로 클라이언트가 준 title 가져오기
    title_receive = request.form['title_give']
    # author_receive로 클라이언트가 준 author 가져오기
    author_receive = request.form['author_give']
    # review_receive로 클라이언트가 준 review 가져오기
    review_receive = request.form['review_give']

    # DB에 삽입할 review 만들기
    doc = {
        'title': title_receive,
        'author': author_receive,
        'review': review_receive
    }
    # reviews에 review 저장하기
    db.bookreview.insert_one(doc)
    # 성공 여부 & 성공 메시지 반환
    return jsonify({'msg': '리뷰가 성공적으로 작성되었습니다.'})
(3) 클라이언트 만들기

API는 약속

리뷰를 작성하기 위해 필요한 정보는 세 가지
- 제목(title)
- 저자(author)
- 리뷰(review)

따라서 클라이언트 코드는 세 단계로 구성
1. input에서 title, author, review 가져오기
2. 입력값이 하나라도 없을 때 alert 띄우기
3. Ajax로 서버에 저장 요청하고 화면 다시 로딩하기

⭐️ 사용할 API 정보

A. 요청 정보
- 요청 URL= /review , 요청 방식 = POST
- 요청 데이터 : 제목(title), 저자(author), 리뷰(review)

B. 서버가 제공할 기능 : 클라이언트에게 보낸 요청 데이터를 데이터베이스에 생성(Create)하고, 저장이 성공했다고 응답 데이터를 보냄

C. 응답 데이터 : (JSON 형식) 'result'= 'success', 'msg'= '리뷰가 성공적으로 작성되었습니다.'

function makeReview() {
    // 화면에 입력어 있는 제목, 저자, 리뷰 내용을 가져옵니다.
    let title = $("#title").val();
    let author = $("#author").val();
    let review = $("#bookReview").val();

    // POST /review 에 저장(Create)을 요청합니다.
    $.ajax({
        type: "POST",
        url: "/review",
        data: { title_give: title, author_give: author, review_give: review },
        success: function (response) {
            alert(response["msg"]);
            window.location.reload();
        }
    })
}
(4) 완성 확인

4. GET 연습(리뷰 보여주기)

1) API 만들고 사용하기 - 저장된 리뷰를 화면에 보여주기(Read → GET)

(1) 클라이언트와 서버 확인

API 정보
1. 요청 정보 : 요청 URL = /review, 요청방식 = GET
2. 서버가 제공할 기능 : 클라이언트에게 정해진 메시지를 보냄
3. 응답 데이터 : (JSON 형식) {'msg': '이 요청은 GET!'}

  • [서버 코드 - app.py]

    @app.route('/review', methods=['GET'])
    	def read_reviews():
       	sample_receive = request.args.get('sample_give')
       	print(sample_receive)
       	return jsonify({'msg': '이 요청은 GET!'})
  • [클라이언트 코드 - index.html]

    function showReview() {
    		// 서버의 데이터를 받아오기
    		$.ajax({
           type: "GET",
           url: "/review?sample_give=샘플데이터",
           data: {},
           success: function (response) {
               alert(response["msg"]);
           }
       })
    }
    
(2) 서버부터 만들기

API는 약속

리뷰를 작성하기 위해 필요한 정보는 세 가지
- 제목(title)
- 저자(author)
- 리뷰(review)

따라서 API 기능은 두 단계로 구성
1. DB에서 리뷰 정보 모두 가져오기
2. 성공 여부 & 리뷰 목록 변환

⭐️ 사용할 API 정보

A. 요청 정보
- 요청 URL= /review , 요청 방식 = GET
- 요청 데이터 : 제목(title), 저자(author), 리뷰(review)

B. 서버가 제공할 기능 : 데이터베이스에 리뷰 정보를 조회(Read)하고, 성공 메시지와 리뷰 정보를 응답 데이터를 보냄

C. 응답 데이터 : (JSON 형식) 'all_reviews'= 리뷰리스트

@app.route('/review', methods=['GET'])
 def read_reviews():
     # 1. DB에서 리뷰 정보 모두 가져오기
     reviews = list(db.bookreview.find({}, {'_id': False}))
     # 2. 성공 여부 & 리뷰 목록 반환하기
     return jsonify({'all_reviews': reviews})
(3) 클라이언트 만들기

API는 약속

리뷰를 작성하기 위해 필요한 정보는 세 가지
- 제목(title)
- 저자(author)
- 리뷰(review)

따라서 API 기능은 세 단계로 구성
1. 리뷰 목록을 서버에 요청
2. 요청 성공 여부 확인
3. 요청 성공했을 때 리뷰를 올바르게 화면에 나타내기

⭐️ 사용할 API 정보

A. 요청 정보
- 요청 URL= /review , 요청 방식 = GET
- 요청 데이터 : 제목(title), 저자(author), 리뷰(review)

B. 서버가 제공할 기능 : 데이터베이스에 리뷰 정보를 조회(Read)하고, 성공 메시지와 리뷰 정보를 응답 데이터를 보냄

C. 응답 데이터 : (JSON 형식) 'all_reviews'= 리뷰리스트

 function showReview() {
              $.ajax({
                  type: "GET",
                  url: "/review",
                  data: {},
                  success: function (response) {
                      let reviews = response['all_reviews']
                      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)
                      }
                  }
              })
          }
(4) 완성 확인

나홀로메모장

1. 프로젝트 세팅

1) 프로젝트 설정 - flask 폴더 구조 만들기

  • static, templates 폴더 + app.py 만들기

2) 프로젝트 설계 - 만들 API 설계

✅ 포스팅 API - 카드 생성 (Create)

A. 요청 정보

  • 요청 URL= /memo , 요청 방식 = POST
  • 요청 데이터 : URL(url_give), 코멘트(comment_give)

B. 서버가 제공할 기능

  • URL의 meta태그 정보를 바탕으로 제목, 설명, 이미지URL 스크래핑
  • (제목, 설명, URL, 이미지URL, 코멘트) 정보를 모두 DB에 저장

C. 응답 데이터

  • API가 정상적으로 작동하는지 클라이언트에게 알려주기 위해서 성공 메시지 보내기
  • (JSON 형식) 'result'= 'success'

✅ 리스팅 API - 저장된 카드 보여주기 (Read)

A. 요청 정보

  • 요청 URL= /memo , 요청 방식 = GET
  • 요청 데이터 : 없음

B. 서버가 제공할 기능

  • DB에 저장 되어있는 모든 (제목, 설명, URL, 이미지URL, 코멘트) 정보를 가져오기

C. 응답 데이터

  • 아티클(기사)들의 정보(제목, 설명, URL, 이미지URL, 코멘트) → 카드 만들어서 붙이기
  • (JSON 형식) 'articles': 아티클 정보

2. 조각 기능 구현

1) 프로젝트 준비 - URL에서 페이지 정보 가져오기(meta태그 스크래핑)

  • 어떤 부분에 스크래핑이 필요?

    • 기사 URL만 입력했는데 자동으로 불러와지는 부분들이 존재
    • '기사 제목', '썸네일 이미지', '내용'
      🚨 'meta'태그를 크롤링 하면서 공통적으로 얻을 수 있다.
    • meta 태그에 대해 알아보기
      • (링크)에 접속한 뒤 크롬 개발자 도구를 이용해 HTML의 생김새 살펴보기
      • 메타 태그는, <head></head>부분에 들어가는 눈에 보이는 것(body) 외에 사이트의 속성을 설명해주는 태그들
        ex) 구글 검색시 표시 될 설명문, 사이트 제목, 카톡 공유 시 표시 될 이미지 등
      • og:image / og:title / og:description을 크롤링할 예정
  • meta 태그 스프래핑 하기

    • 크롤링 기본 코드
    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를 먼저 가져와보겠습니다.
    • select_one을 이용해 meta tag를 먼저 가져온다
    og_image = soup.select_one('meta[property="og:image"]')
     og_title = soup.select_one('meta[property="og:title"]')
     og_description = soup.select_one('meta[property="og:description"]')
    
     print(og_image)
     print(og_title)
     print(og_description)
    • 가져온 meta tag의 content를 가져온다
    url_image = og_image['content']
     url_title = og_title['content']
     url_description = og_description['content']
    
     print(url_image)
     print(url_title)
     print(url_description)

3. 뼈대 준비하기

1)프로젝트 준비 - app.py, index.html 준비하기

  • 나홀로메모장 - 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>

4. POST 연습(메모하기)

1) API 만들고 사용하기 - 포스팅 API (Create → POST)

✅ 우리가 만들 API 두 가지
1. 포스팅API - 카드 생성(Create) : 클라이언트에서 받은 url, comment를 이용해서 페이지 정보를 찾고 저장
2. 리스팅API - 저장된 카드 보여주기 (Read)

(1) 클라이언트와 서버 확인
  • [서버 코드 - app.py]

    @app.route('/memo', methods=['POST'])
     def post_articles():
             sample_receive = request.form['sample_give']
             print(sample_receive)
         return jsonify({'msg': 'POST 연결되었습니다!'})
  • [클라이언트 코드 - index.html]

    function postArticle() {
      $.ajax({
        type: "POST",
        url: "/memo",
        data: {sample_give:'샘플데이터'},
        success: function (response) { // 성공하면
          alert(response['msg']);
        }
      })
    }
    
    <button type="button" class="btn btn-primary" onclick="postArticle()">기사저장</button>
(2) 서버부터 만들기

API는 약속

메모를 작성하기 위해 서버에 전달받아야하는 정보 두 가지
- URL(url_give)
- 코멘트(comment_give)

그리고 URL의 meta tag를 스크래핑해서 아래 데이터를 저장(Create)
- URL(url)
- 제목(title)
- 설명(desc)
- 이미지URL(image)
- 코멘트(comment)

따라서 서버 로직은 세 단계로 구성
1. 클라이언트로부터 데이터를 받기
2. meta tag를 스크래핑
3. mongoDB에 데이터를 넣기

@app.route('/memo', methods=['POST'])
def saving():
    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')

    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']

    doc = {
        'title':title,
        'image':image,
        'desc':desc,
        'url':url_receive,
        'comment':comment_receive
    }

    db.articles.insert_one(doc)

    return jsonify({'msg':'저장이 완료되었습니다!'})
(3) 클라이언트 만들기

API는 약속

메모를 작성하기 위해 서버에게 주어야하는 정보는 두 가지
- URL(url_give) : meta tag를 가져올 url
- comment(comment_give) : 유저가 입력한 코멘트

따라서 클라이언트 로직은 세 단계로 구성
1. 유저가 입력한 데이터를 #post-url과 #post-comment에서 가져오기
2. /memo에 POST 방식으로 메모 생성 요청
3. 성공 시 페이지 새로고침

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()
          }
      })
  }
(4) 완성 확인

5. GET 연습(보여주기)

1) API 만들고 사용하기 - 포스팅 API (Create → GET)

✅ 우리가 만들 API 두 가지
1. 포스팅API - 카드 생성(Create) : 클라이언트에서 받은 url, comment를 이용해서 페이지 정보를 찾고 저장
2. 리스팅API - 저장된 카드 보여주기 (Read)

(1) 클라이언트와 서버 확인
  • [서버 코드 - app.py]
    @app.route('/memo', methods=['GET'])
     def read_articles():
         # 1. 모든 document 찾기 & _id 값은 출력에서 제외하기
         # 2. articles라는 키 값으로 영화정보 내려주기
         return jsonify({'result':'success', 'msg':'GET 연결되었습니다!'})
  • [클라이언트 코드 - index.html]
    function showArticles() {
       $.ajax({
         type: "GET",
         url: "/memo",
         data: {},
         success: function (response) {
           if (response["result"] == "success") {
             alert(response["msg"]);
           }
         }
       })
     }
(2) 서버부터 만들기

API는 약속

메모를 보여주기 위해 서버가 추가로 전달받아야하는 정보는 없다.
why? 조건 없이 모든 메모를 보여줄 것이기 때문

따라서 서버 로직은 두 단계로 구성
1. mongoDB에서 _id 값을 제외한 모든 데이터 조회(Read)
2. articles라는 키 값으로 articles 정보 보내주기

@app.route('/memo', methods=['GET'])
def listing():
    articles = list(db.articles.find({}, {'_id': False}))
    return jsonify({'all_articles':articles})
(3) 클라이언트 만들기

API는 약속

메모를 보여주기 위해 서버가 추가로 전달받아야하는 정보는 없다.
why? 조건 없이 모든 메모를 보여줄 것이기 때문

따라서 클라이언트 로직은 두 단계로 구성
1. /memo에 GET 방식으로 메모 정보 요청하고 articles로 메모 정보 받기
2. , makeCard 함수를 이용해서 카드 HTML 붙이기
(→ 2주차 Ajax 연습과 동일!)

function showArticles() {
    $.ajax({
        type: "GET",
        url: "/memo",
        data: {},
        success: function (response) {
            let articles = response['all_articles']
            for (let i = 0; i < articles.length; i++) {
                let title = articles[i]['title']
                let image = articles[i]['image']
                let url = articles[i]['url']
                let desc = articles[i]['desc']
                let comment = articles[i]['comment']

                let 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)
            }
        }
    })
}
(4) 완성 확인

4주차 숙제 : 원페이지 쇼핑몰 완성하기

1) 문제

📃 1주차에 완성한 쇼핑몰을 완성해주세요!

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

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

아래 완성본을 참고해주세요!
http://spartacodingclub.shop/homework

🚨 힌트:
<모두의책리뷰>랑 아주아주 유사하답니다!
아래 주문정보를 붙일 때에는, 부트스트랩 Table(링크)을 이용하세요!

2) 나의 풀이

  • [서버 코드 - app.py]
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']
    count_receive = request.form['count_give']
    address_receive = request.form['address_give']
    phone_receive = request.form['phone_give']

    doc = {
        'name': name_receive,
        'count': count_receive,
        'address': address_receive,
        'phone': phone_receive
    }
    db.orders.insert_one(doc)

    return jsonify({'result': 'success', 'msg': '주문 완료!'})


# 주문 목록보기(Read) API
@app.route('/order', methods=['GET'])
def view_orders():
    orders = list(db.orders.find({}, {'_id': False}))
    return jsonify({'result': 'success', 'orders': orders})


if __name__ == '__main__':
    app.run('0.0.0.0', port=5000, debug=True)
  • [클라이언트 코드 - index.html]
<!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>

    <title>스파르타코딩클럽 | 부트스트랩 연습하기</title>

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

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

        .item-page {
            width: 502px;
            height: 798px;
            margin: auto;
            margin-top: 16px;
            border: 1px solid black;
        }

        .item-image {
            background-color: green;
            width: 450px;
            height: 300px;
            background-image: url("https://post-phinf.pstatic.net/MjAyMTEwMjJfODUg/MDAxNjM0ODcwNjg5Mzc2.n7nH7taibbSgR5BvAijbRzeuUyBS5DnTX2eZIkgp8mog.gUpqOjxlRzaIqB2Yyc5_m7jfFp2Ya45ux3xCkpUNDdEg.JPEG/Screenshot_2021-10-22_at_10.22.00.jpg?type=w1200");
            background-size: cover;
            background-position: center;
            margin: auto;
            margin-top: 16px;
        }

        .item-info {
            width: 450px;
            margin: 16px 25px;
        }

        .item-info i {
            color: red;
            font-weight: bold;
        }

        .item-title {
            font-size: 30px;
            font-weight: bold;
        }

        .item-price {
            font-size: 20px;
            color: tomato;
            margin-left: 20px;
        }

        .item-order {
            width: 450px;
            margin: 0px 25px 16px 25px;
        }

        .item-order-btn {
            margin: auto;
            display: block;
        }

        .rate {
            color: blue
        }

        .table {
            margin-top: 50px;
        }
    </style>

    <script>
        $(document).ready(function () {
            get_rate()
            listing()
        });

        function listing() {
            $.ajax({
                type: "GET",
                url: "/order",
                data: {},
                success: function (response) {
                    if (response["result"] == "success") {
                        let orders = response['orders']
                        for (let i = 0; i < orders.length; i++) {
                            let name = orders[i]['name'];
                            let count = orders[i]['count'];
                            let address = orders[i]['address'];
                            let phone = orders[i]['phone'];

                            let temp_html = `<tr>
                                                <th scope="row">${name}</th>
                                                <td>${count}</td>
                                                <td>${address}</td>
                                                <td>${phone}</td>
                                            </tr>`
                            $('#orders-box').append(temp_html)
                        }
                    }
                }
            })
        }

        function get_rate(){
           $.ajax({
                type: "GET",
                url: "https://api.manana.kr/exchange/rate.json",
                data: {},
                success: function (response) {
                let now_rate = response[1]['rate'];
                    $('#now-rate').text(now_rate);
                }
           })
        }

        function order() {
           let name = $('#order-name').val();
           let count = $('#order-count').val();
           let address = $('#order-address').val();
           let phone = $('#order-phone').val();

           $.ajax({
                type: "POST",
                url: "/order",
                data: {name_give: name, count_give: count, address_give: address, phone_give: phone},
                success: function (response) {
                    if (response["result"] == "success") {
                        alert(response["msg"]);
                        window.location.reload();
                    }
                }
            })
        }
    </script>
</head>

    <body>
        <div class="item-page">
            <div class="item-image"></div>
            <div class="item-info">
                <i>NEW</i>
                <h1 class="item-title">Sony A7IV ILCE-7M4<span class="item-price">3,360,530원</span></h1>
                <p>오늘날의 크리에이터들을 위한 풀프레임 미러리스, a7IV입니다. 풀프레임 베스트셀러 a7의 4세대 바디로서 카메라의 표준을 새로이 제시합니다.</p>
                <P class="rate">달러-원 환율: <span id="now-rate">1219.15</span></P>
            </div>
            <div class="item-order">
                <div class="input-group mb-3">
                    <div class="input-group-prepend">
                        <span class="input-group-text" id="inputGroup-sizing-default">주문자 이름</span>
                    </div>
                    <input id="order-name" type="text" class="form-control" aria-label="Default"
                           aria-describedby="inputGroup-sizing-default">
                </div>
                <div class="input-group mb-3">
                    <div class="input-group-prepend">
                        <label class="input-group-text" for="inputGroupSelect01">수량</label>
                    </div>
                    <select id="order-count" class="custom-select">
                        <option selected>-- 수량을 선택하세요 --</option>
                        <option value="1">1</option>
                        <option value="2">2</option>
                        <option value="3">3</option>
                    </select>
                </div>
                <div class="input-group mb-3">
                    <div class="input-group-prepend">
                        <span class="input-group-text">주소</span>
                    </div>
                    <input id="order-address" type="text" class="form-control" aria-label="Default"
                           aria-describedby="inputGroup-sizing-default">
                </div>
                <div class="input-group mb-3">
                    <div class="input-group-prepend">
                        <span class="input-group-text">전화번호</span>
                    </div>
                    <input id="order-phone" type="text" class="form-control" aria-label="Default"
                           aria-describedby="inputGroup-sizing-default">
                </div>
                <button onclick="order()" type="button" class="btn btn-primary item-order-btn">주문하기</button>
            </div>
            <table class="table">
                <thead>
                <tr>
                    <th scope="col">이름</th>
                    <th scope="col">수량</th>
                    <th scope="col">주소</th>
                    <th scope="col">전화번호</th>
                </tr>
                </thead>
                    <tbody id="orders-box">
                    </tbody>
            </table>
        </div>
    </body>

</html>
profile
비전공개발자의 개발일지

0개의 댓글

관련 채용 정보