스파르타 웹개발종합반 열째 날 일기(5-1~8)

Flask를 써서 웹페이지 제작

무비스타 페이지 좋아요, 삭제 기능 구현

<수업목표>

  • Flask 프레임워크를 활용해서 API를 만들 수 있다.
  • '마이 페이보릿 무비스타'를 완성한다.
  • EC2에 내 프로젝트를 올리고, 자랑한다!

완성작

  1. 데이터 쌓기
    -> 미리 만들어둔 코드로 API를 가져와서 데이터베이스에 저장하는 코드가 제공됨
    init_db.py 파일을 만들어서 아래 코드를 넣고 파일을 실행하면 영화인 정보가 저장됨
import requests
from bs4 import BeautifulSoup

from pymongo import MongoClient

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


# DB에 저장할 영화인들의 출처 url을 가져옵니다.
def get_urls():
    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('https://movie.naver.com/movie/sdb/rank/rpeople.nhn', headers=headers)

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

    trs = soup.select('#old_content > table > tbody > tr')

    urls = []
    for tr in trs:
        a = tr.select_one('td.title > a')
        if a is not None:
            base_url = 'https://movie.naver.com/'
            url = base_url + a['href']
            urls.append(url)

    return urls


# 출처 url로부터 영화인들의 사진, 이름, 최근작 정보를 가져오고 mystar 콜렉션에 저장합니다.
def insert_star(url):
    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')

    name = soup.select_one('#content > div.article > div.mv_info_area > div.mv_info.character > h3 > a').text
    img_url = soup.select_one('#content > div.article > div.mv_info_area > div.poster > img')['src']
    recent_work = soup.select_one(
        '#content > div.article > div.mv_info_area > div.mv_info.character > dl > dd > a:nth-child(1)').text

    doc = {
        'name': name,
        'img_url': img_url,
        'recent': recent_work,
        'url': url,
        'like': 0
    }

    db.mystar.insert_one(doc)
    print('완료!', name)


# 기존 mystar 콜렉션을 삭제하고, 출처 url들을 가져온 후, 크롤링하여 DB에 저장합니다.
def insert_all():
    db.mystar.drop()  # mystar 콜렉션을 모두 지워줍니다.
    urls = get_urls()
    for url in urls:
        insert_star(url)


### 실행하기
insert_all()
  1. 뼈대 준비하기

1) index.html

<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
        <title>마이 페이보릿 무비스타 | 프론트-백엔드 연결 마지막 예제!</title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css"/>
        <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
        <style>
            .center {
                text-align: center;
            }

            .star-list {
                width: 500px;
                margin: 20px auto 0 auto;
            }

            .star-name {
                display: inline-block;
            }

            .star-name:hover {
                text-decoration: underline;
            }

            .card {
                margin-bottom: 15px;
            }
        </style>
        <script>
            $(document).ready(function () {
                showStar();
            });

            function showStar() {
                $.ajax({
                    type: 'GET',
                    url: '/api/list?sample_give=샘플데이터',
                    data: {},
                    success: function (response) {
                        alert(response['msg']);
                    }
                });
            }

            function likeStar(name) {
                $.ajax({
                    type: 'POST',
                    url: '/api/like',
                    data: {sample_give:'샘플데이터'},
                    success: function (response) {
                        alert(response['msg']);
                    }
                });
            }

            function deleteStar(name) {
                $.ajax({
                    type: 'POST',
                    url: '/api/delete',
                    data: {sample_give:'샘플데이터'},
                    success: function (response) {
                        alert(response['msg']);
                    }
                });
            }

        </script>
    </head>
    <body>
        <section class="hero is-warning">
            <div class="hero-body">
                <div class="container center">
                    <h1 class="title">
                        마이 페이보릿 무비스타😆
                    </h1>
                    <h2 class="subtitle">
                        순위를 매겨봅시다
                    </h2>
                </div>
            </div>
        </section>
        <div class="star-list" id="star-box">
            <div class="card">
                <div class="card-content">
                    <div class="media">
                        <div class="media-left">
                            <figure class="image is-48x48">
                                <img
                                        src="https://search.pstatic.net/common/?src=https%3A%2F%2Fssl.pstatic.net%2Fsstatic%2Fpeople%2Fportrait%2F201807%2F20180731143610623-6213324.jpg&type=u120_150&quality=95"
                                        alt="Placeholder image"
                                />
                            </figure>
                        </div>
                        <div class="media-content">
                            <a href="#" target="_blank" class="star-name title is-4">김다미 (좋아요: 3)</a>
                            <p class="subtitle is-6">안녕, 나의 소울메이트(가제)</p>
                        </div>
                    </div>
                </div>
                <footer class="card-footer">
                    <a href="#" onclick="likeStar('김다미')" class="card-footer-item has-text-info">
                        위로!
                        <span class="icon">
              <i class="fas fa-thumbs-up"></i>
            </span>
                    </a>
                    <a href="#" onclick="deleteStar('김다미')" class="card-footer-item has-text-danger">
                        삭제
                        <span class="icon">
              <i class="fas fa-ban"></i>
            </span>
                    </a>
                </footer>
            </div>
        </div>
    </body>
</html>

2) app.py

from pymongo import MongoClient

from flask import Flask, render_template, jsonify, request

app = Flask(__name__)

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


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


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


@app.route('/api/like', methods=['POST'])
def like_star():
    sample_receive = request.form['sample_give']
    print(sample_receive)
    return jsonify({'msg': 'like 연결되었습니다!'})


@app.route('/api/delete', methods=['POST'])
def delete_star():
    sample_receive = request.form['sample_give']
    print(sample_receive)
    return jsonify({'msg': 'delete 연결되었습니다!'})


if __name__ == '__main__':
    app.run('0.0.0.0', port=5000, debug=True)
  1. GET연습(보여주기)
  • 만들 API
  1. 조회(Read) 기능: 영화인 정보 전체를 조회
  2. 좋아요(Update) 기능: 클라이언트에서 받은 이름(name_give)으로 찾아서 좋아요(like)를 증가
  3. 삭제(Delete) 기능: 클라이언트에서 받은 이름(name_give)으로 영화인을 찾고, 해당 영화인을 삭제
  • 만들 API 정보

A. 요청 정보

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

B. 서버가 제공할 기능 : 데이터베이스에 영화인 정보를 조회(Read)하고, 영화인 정보를 응답 데이터로 보냄

C. 응답 데이터 : (JSON 형식) 'stars_list'= 영화인 정보 리스트

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

<서버 코드>

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

<클라이언트 코드>

function showStar() {
      $.ajax({
          type: 'GET',
          url: '/api/list?sample_give=샘플데이터',
          data: {},
          success: function (response) {
              alert(response['msg']);
          }
      });
  }

*새로고침했을 때, 'list 연결되었습니다.' 라는 메시지가 뜨면 동작하는 것입니다.

2) 서버부터 만들기

API 는 약속이라고 했습니다. 위에 미리 설계해 둔 API 정보를 보고 만들어보죠!

영화인 정보 전체를 조회하기 위해 서버가 받을 정보는 없습니다. 조건없이 모든 정보를 보여줄 것이니까요!

따라서 서버 로직은 다음 단계로 구성되어야 합니다.
a. mystar 목록 전체를 검색합니다. ID는 제외하고 like 가 많은 순으로 정렬
b. 성공하면 success 메시지와 함께 stars_list 목록을 클라이언트에 전달

@app.route('/api/list', methods=['GET'])
def show_stars():
    movie_star = list(db.mystar.find({}, {'_id': False}).sort('like', -1))
    return jsonify({'movie_stars': movie_star})

3) 클라이언트 만들기

영화인 정보 전체를 조회하기 위해 서버가 받을 정보는 없습니다. 조건없이 모든 정보를 보여줄 것이니까요!

따라서 클라이언트 로직은 다음 단계로 구성되어야 합니다.
a. #star_box의 내부 html 태그를 모두 삭제
b. 서버에 1) GET 방식으로, 2) /api/list 라는 주소로 stars_list를 요청
c. 서버가 돌려준 stars_list를 stars라는 변수에 저장
d. for 문을 활용하여 stars 배열의 요소를 차례대로 조회
e. stars [i] 요소의 name, url, img_url, recent, like 키 값을 활용하여 값 조회
f. 영화인 카드 코드 만들어 #star-box에 붙이기

function showStar() {
                $.ajax({
                    type: 'GET',
                    url: '/api/list?sample_give=샘플데이터',
                    data: {},
                    success: function (response) {
                        let mystars = response['movie_stars']
                        for (let i = 0; i < mystars.length; i++) {
                            let name = mystars[i]['name']
                            let img_url = mystars[i]['img_url']
                            let recent = mystars[i]['recent']
                            let url = mystars[i]['url']
                            let like = mystars[i]['like']

                            let temp_html = `<div class="card">
                                                <div class="card-content">
                                                    <div class="media">
                                                        <div class="media-left">
                                                            <figure class="image is-48x48">
                                                                <img
                                                                        src="${img_url}"
                                                                        alt="Placeholder image"
                                                                />
                                                            </figure>
                                                        </div>
                                                        <div class="media-content">
                                                            <a href="${url}" target="_blank" class="star-name title is-4">${name} (좋아요: ${like})</a>
                                                            <p class="subtitle is-6">${recent}</p>
                                                        </div>
                                                    </div>
                                                </div>
                                                <footer class="card-footer">
                                                    <a href="#" onclick="likeStar('${name}')" class="card-footer-item has-text-info">
                                                        위로!
                                                        <span class="icon">
                                              <i class="fas fa-thumbs-up"></i>
                                            </span>
                                                    </a>
                                                    <a href="#" onclick="deleteStar('${name}')" class="card-footer-item has-text-danger">
                                                        삭제
                                                        <span class="icon">
                                              <i class="fas fa-ban"></i>
                                            </span>
                                                    </a>
                                                </footer>
                                            </div>`
                            $('#star-box').append(temp_html)
                        }
                    }
                });
            }

4) 완성 확인

화면을 새로고침 했을 때 영화인 정보가 조회되는지 확인합니다.

  1. POST연습(좋아요 +1)

클라이언트 좋아요 클릭 > 서버에서 좋아요 클릭된 이름 찾기 > 찾은 이름에서 라이크(숫자) 찾기 > 라이크 +1 > DB 업데이트

  • 만들 API

1) 조회: 영화인 정보 전체를 조회
2) 좋아요: 클라이언트에서 받은 이름(name_give)으로 찾아서 좋아요(like)를 증가
3) 삭제: 클라이언트에서 받은 이름(name_give)으로 영화인을 찾고, 해당 영화인을 삭제

  • 만들 API 정보

A. 요청 정보

  • 요청 URL= /api/like , 요청 방식 = POST
  • 요청 데이터 : 영화인 이름(name_give)

B. 서버가 제공할 기능 : 영화인 이름(요청 데이터)과 일치하는 영화인 정보의 좋아요 수를 한 개 증가시켜 데이터베이스에 업데이트하고(Update), 성공했다고 응답 메세지를 보냄

C. 응답 데이터 : (JSON 형식) 'msg'='좋아요 완료!'

1) 클라이언트 - 서버 연결 확인

<서버 코드>

@app.route('/api/like', methods=['POST'])
def like_star():
    sample_receive = request.form['sample_give']
    print(sample_receive)
    return jsonify({'msg': 'like 연결되었습니다!'})

<클라이언트 코드>

function likeStar(name) {
    $.ajax({
        type: 'POST',
        url: '/api/like',
        data: {sample_give:'샘플데이터'},
        success: function (response) {
            alert(response['msg']);
        }
    });
}

*'위로' 버튼을 눌렀을 때, 'like 연결되었습니다!' 내용의 alert창이 뜨면 제대로 동작하는 것입니다.

2) 서버부터 만들기

영화인 카드의 좋아요 수를 증가시키기 위해 서버가 클라이언트에게 전달받아야하는 정보는 다음과 같습니다.

  • 영화인의 이름 (name_give)

따라서 서버 로직은 다음 단계로 구성되어야 합니다.
a. 클라이언트가 전달한 name_give를 name_receive 변수에 넣습니다.
b. mystar 목록에서 find_one으로 name이 name_receive와 일치하는 star를 찾습니다.
c. star의 like 에 1을 더해준 new_like 변수를 만듭니다.
d. mystar 목록에서 name이 name_receive인 문서의 like 를 new_like로 변경합니다.

<서버 코드>

@app.route('/api/like', methods=['POST'])
def like_star():
    name_receive = request.form['name_give']

    target_star = db.mystar.find_one({'name': name_receive})
    current_like = target_star['like']

    new_like = current_like + 1

    db.mystar.update_one({'name': name_receive}, {'$set': {'like': new_like}})

    return jsonify({'msg': '좋아요 완료!'})

3) 클라이언트 만들기

좋아요 수를 증가시키기 위해 클라이언트가 전달할 정보는 다음과 같습니다.

  • 영화인의 이름 (name_give)

따라서 클라이언트 로직은 다음 단계로 구성되어야 합니다.
a. 서버에
1) POST 방식으로,
2) /api/like 라는 url에,
3) name_give라는 이름으로 name을 전달합니다.
(참고) POST 방식이므로 data: {'name_give': name} 사용
b. '좋아요 완료!' alert 창을 띄웁니다.
c. 변경된 정보를 반영하기 위해 새로고침합니다.

<클라이언트 코드>

function likeStar(name) {
    $.ajax({
        type: 'POST',
        url: '/api/like',
        data: {name_give:name},
        success: function (response) {
            alert(response['msg']);
            window.location.reload()
        }
    });
}

*'위로' 버튼을 눌렀을 때 좋아요 수가 증가하고 영화인 카드의 순위가 변경되는지 확인합니다.

  1. POST연습(삭제하기)
  • 만들 API
    a. 조회(Read) 기능: 영화인 정보 전체를 조회
    b. 좋아요(Update) 기능: 클라이언트에서 받은 이름(name_give)으로 찾아서 좋아요(like)를 증가
    c. 삭제(Delete) 기능: 클라이언트에서 받은 이름(name_give)으로 영화인을 찾고, 해당 영화인을 삭제

  • 만들 API 정보

A. 요청 정보

  • 요청 URL= /api/delete , 요청 방식 = POST
  • 요청 데이터 : 영화인 이름(name_give)

B. 서버가 제공할 기능 : 영화인 이름(요청 데이터)와 일치하는 영화인 정보를 데이터베이스에서 삭제(Delete)하고, 성공했다고 응답 메세지를 보냄

C. 응답 데이터 : (JSON 형식) 'msg'='삭제 완료!'

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

<서버 코드>

@app.route('/api/delete', methods=['POST'])
def delete_star():
    sample_receive = request.form['sample_give']
    print(sample_receive)
    return jsonify({'msg': 'delete 연결되었습니다!'})

<클라이언트 코드>

function deleteStar(name) {
    $.ajax({
        type: 'POST',
        url: '/api/delete',
        data: {sample_give:'샘플데이터'},
        success: function (response) {
            alert(response['msg']);
        }
    });
}

*'삭제' 버튼을 눌렀을 때, 'delete 연결되었습니다!' alert창이 뜨면
클라이언트 코드와 서버 코드가 연결 되어있는 것입니다.

2) 서버부터 만들기

영화인 카드를 삭제하기 위해 필요한 정보는 다음과 같습니다.

  • 영화인의 이름 (name_give)

따라서 서버 로직은 다음 단계로 구성되어야 합니다.
a. 클라이언트가 전달한 name_give를 name_receive 변수에 넣기
b. mystar 에서 delete_one으로 name이 name_receive와 일치하는 star를 제거
c. 성공하면 success 메시지를 반환

@app.route('/api/delete', methods=['POST'])
def delete_star():
    name_receive = request.form['name_give']
    db.mystar.delete_one({'name': name_receive})
    return jsonify({'msg': '삭제 완료!'})

3) 클라이언트 만들기

영화인 카드를 삭제하기 위해 필요한 정보는 다음과 같습니다.

  • 영화인의 이름 (name_give)

따라서 클라이언트 로직은 다음 단계로 구성되어야 합니다.
a. 서버에
1) POST 방식으로,
2) /api/delete 라는 url에,
3) name_give라는 이름으로 name을 전달
(참고) POST 방식이므로 data: {'name_give': name}
b. '삭제 완료! 안녕!' alert창 띄우기
c. 변경된 정보를 반영하기 위해 새로고침

function deleteStar(name) {
      $.ajax({
          type: 'POST',
          url: '/api/delete',
          data: {name_give:name},
          success: function (response) {
              alert(response['msg']);
              window.location.reload()
          }
      });
  }

*삭제 버튼을 눌렀을 때 영화인 카드가 삭제되는지 확인합니다.

  1. 마치며

이제 조금 눈에 익지만 아직 쉽지는 않다.

좋은 웹페이지 즐겨찾기