본문 바로가기

스파르타코딩 공부내용 정리/웹개발 플러스

2주차_나만의 단어장 만들기

1주차와 다른 점

 - Detail 페이지가 있다는 것

 - 서버사이드랜더링

 - 멀티사이드페이지

 - 공공 API

 

 

디자인이 된 페이지를 받았을 때 생각할 것

1. 우리가 만들어야 하는 페이지와 기능들은?

    1) 메인 페이지

    2) 상세 페이지

2. 가져야 할 마음들은?

    -> 돌아가기만 하면 된다.

 

 

상세페이지 만들기

1. detail.html 먼저 생성해야 한다.

2. detail page로 가기 위해 app.py 연결

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

 

메인페이지에서 상세페이지로 넘어가기

1. a tag

<a href="/detail">상세 페이지로 가기</a>

2. windows.location.href를 이용해주는 방법

// script 태그 안에 정의하기
function to_main() {
	window.location.href = "/"
}
<!-- 버튼에 함수 연결하기 -->
<button onclick="to_main()">메인으로 돌아가기</button>

3. 배너 눌렀을 때 돌아가는 함수 

<div class="main_logo" onclick="to_main()">
	<img src="../static/logo_red.png" alt="">
</div>

4. 메인 list에 상세페이지로 가는 이벤트 삽입

<td onclick="window.location.href='/detail'">elephant</td>

 

Open API에서 토근을 발급 받는 이유 [여기서는 사전 API 사용]

Open API에 나쁜 의도를 가지고 많은 requests를 의도적으로 보내면해당 서버에 많은 부하가 걸리면서
문제 발생할 수도 있기 때문
=> 개개인별 토큰을 부여하여 사용자 추적 후 하루 요청 횟수 혹은 시간당 요청횟수 등에 제한을 둠

 

Server Side Rendering

1. 렌더링의 종류

    1) Client-side rendering (CSR)

        클라이언트 쪽에서 Ajax 요청 보내서 서버에서 데이터를 받아와 HTML 완성하는 방법

    2) Server-side rendering (SSR)

        서버 쪽에서 템플릿 HTML에 데이터를 끼워넣어 완성된 형태의 HTML을 보내주는 방법

2. Jinja2

    - Flask 프레임워크에서 사용하는 템플릿 언어

    - '템플릿'이 되는 HTML 문서에 데이터가 들어갈 곳을 표시해놓는 역할

 

 

완성 코드

1. app.py

from flask import Flask, render_template, request, jsonify
from pymongo import MongoClient
import requests

client = MongoClient('mongodb://test:test@3.39.24.201', 27017)
db = client.dbsparta_plus_week2

app = Flask(__name__)


@app.route('/')
def main():
    words = list(db.words.find({}, {"_id":False}))
    print(words)
    return render_template("index.html", words=words)

@app.route('/detail/<keyword>')
def detail(keyword):
    print(keyword)
    db_word = db.words.find_one({'word':keyword}, {"_id": False})
    print(db_word)

    word_name = keyword # 단어이름
    result = None # open api 응답값
    is_saved = False # 저장 유무
    examples = [] # 예문

    if db_word:
        if "result" in db_word:
            print('result in db')
            result = db_word["result"]
        else:
            r = requests.get(f"https://owlbot.info/api/v4/dictionary/{keyword}", headers={"Authorization": "Token a637b82188240fdd705a304c813b91d92d01f6ba"})
            result = r.json()

        is_saved=True
        examples = list(db.examples.find({"word":keyword}, {"_id":False}))

    else:
        r = requests.get(f"https://owlbot.info/api/v4/dictionary/{keyword}", headers={"Authorization": "Token a637b82188240fdd705a304c813b91d92d01f6ba"})
        result = r.json()

    return render_template("detail.html", word_name=word_name, result=result, is_saved=is_saved, examples=examples)


@app.route('/api/save_word', methods=["POST"])
def save_word():
    # 단어 저장하기
    word_receive = request.form["word_give"]
    definition_receive = request.form["definition_give"]

    r = requests.get(f"https://owlbot.info/api/v4/dictionary/{word_receive}", headers={"Authorization": "Token a637b82188240fdd705a304c813b91d92d01f6ba"})
    result = r.json()

    print(word_receive, definition_receive)

    doc = {
        "word":  word_receive,
        "definition" : definition_receive,
        "result": result
    }

    db.words.insert_one(doc)
    return jsonify({"result": "success", "msg": f"word '{word_receive}' saved"})

@app.route('/api/delete_word', methods=["POST"])
def delete_word():
    # 단어 삭제하기
    word_receive = request.form["word_give"]
    db.words.delete_one({"word": word_receive})
    db.examples.delete_many({"word": word_receive})

    return jsonify({"result": "success", "msg": f"word '{word_receive}' deleted"})

@app.route('/api/save_ex', methods=["POST"])
def save_ex():
    # 예문 저장하기
    word_receive = request.form["word_give"]
    example_receive = request.form["example_give"]
    doc = {
        "word":  word_receive,
        "example" : example_receive
    }
    db.examples.insert_one(doc)
    return jsonify({"result": "success", "msg": f"example '{example_receive}' saved"})

@app.route('/api/delete_ex', methods=["POST"])
def delete_ex():
    # 예문 저장하기
    word_receive = request.form["word_give"]
    example_receive = request.form["example_give"]
    db.examples.delete_one({
        "word": word_receive,
        "example": example_receive
    })
    return jsonify({"result": "success", "msg": f"example '{example_receive}' deleted"})

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

2. index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Sparta Vocabulary Notebook</title>

    <!-- 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 href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">

    <title> 나만의 단어장 </title>
    <style>
      .wrap {
          background-color: RGBA(232, 52, 78, 0.2);
          height: 100%;
          padding-bottom: 50px;
      }
      .main_logo {
          text-align: center;
          background-color: white;
      }
      .input_div {
          display: flex;
          width: 70%;
          margin: 50px auto;
          max-width: 700px;
      }
      .input_box {
          width: 100%;
          margin-right: 10px;
      }

      .table_wrap {
          width: 80%;
          max-width: 800px;
          margin: auto;
      }

      .table {
          border-collapse: collapse;
          border-radius: 10px;

      }

      .highlight {
          background-color: #e8344e;
          color: white;
      }

    </style>
    <script>
      let words = {{ words|tojson }};
      let word_list = [];
      for (let i = 0; i < words.length; i++) {
        word_list.push(words[i]["word"])
      }

      function enterkey() {
        if (window.event.keyCode == 13) {
          // 엔터키가 눌렸을 때
          find_word()
        }
      }

      function find_word(){
        let word = $("#input_word").val()
        if (word == ""){
          alert('단어를 입력해주세요!')
          return
        }

        if (word_list.includes(word)){
          {# 포함하고 있으면 해당 위치로 이동하면서 CSS 적용  #}
          $('tr').removeClass("highlight")
          $(`#word-${word}`).addClass("highlight")
          var offset = $(`#word-${word}`).offset(); //해당 위치 반환
          $("html, body").animate({scrollTop: offset.top}, 400); // 선택한 위치로 이동. 두번째 인자는 0.4초를 의미한다.
        } else {
          {# 바로 상세페이지로 이동  #}
          window.location.href = `/detail/${word}`
        }


      }


    </script>
  </head>
  <body>
    <div class="wrap">
      <div class="main_logo">
        <img src="../static/logo_red.png" alt="">
      </div>
      <div class="input_div">
        <input id="input_word" class="input_box" type="text" onkeyup="enterkey()">
        <button onclick="find_word()" type="button" class="btn btn-light"><i class="fa fa-search"></i></button>
      </div>
      <div class="table_wrap">
        <table class="table table-light">
          <thead class="table-secondary">
          <tr>
            <th scope="col">WORD</th>
            <th scope="col">MEANING</th>
          </tr>
          </thead>
          <tbody>
          {% for word in words %}
            <tr id="word-{{ word.word }}">
              <td onclick="window.location.href='/detail/{{ word.word }}'">{{ word.word }}</td>
              <td>{{ word.definition }}</td>
            </tr>
          {% endfor %}
          </tbody>
        </table>
      </div>
    </div>
  </body>
</html>

3. detail.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>

    <meta property="og:title" content="{{ word_name }}">
    <meta property="og:description" content="{{ result.definitions[0].definition }}">
    <meta property="og:image" content="{{ url_for('static', filename='logo_red.png') }}">

    <title>Sparta Vocabulary Notebook</title>

    <!-- 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 href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">

    <title> 나만의 단어장 </title>
    <style>
        .wrap {
            background-color: RGBA(232, 52, 78, 0.2);
            height: 100vh;
        }

        .main_logo {
            text-align: center;
            background-color: white;
        }

        .container {
            width: 80%;
            max-width: 800px;
            margin: 30px auto;
            padding: 20px;
            background-color: white;
            border: solid 1px gray;
            border-radius: 10px;
        }

        .name_div {
            display: flex;
            justify-content: space-between;
        }

        .definition {
            padding: 10px;
        }

        .input_div {
            display: flex;
            width: 90%;
            margin: 20px auto;
            max-width: 700px;
        }

        .input_box {
            width: 100%;
            margin-right: 10px;
        }

    </style>
    <script>

      function to_main() {
        window.location.href = "/"
      }

      function save_word() {
        $.ajax({
          type: "POST",
          url: "/api/save_word",
          data: {
            word_give: "{{ word_name }}",
            definition_give: "{{ result.definitions[0].definition }}"
          },
          success: function (response) {
            alert(response['msg'])
            window.location.reload()
          }
        })
      }

      function delete_word() {
        $.ajax({
          type: "POST",
          url: "/api/delete_word",
          data: {
            word_give: "{{ word_name }}",
          },
          success: function (response) {
            alert(response['msg'])
            window.location.href = "/"
          }
        })
      }

      function save_ex() {
        let new_ex = $("#new-example").val()

        $.ajax({
          type: "POST",
          url: "/api/save_ex",
          data: {
            word_give: "{{ word_name }}",
            example_give: new_ex
          },
          success: function (response) {
            alert(response['msg'])
            window.location.reload()
          }
        })
      }

      function delete_ex(word, example) {
        console.log(word, example)
        $.ajax({
          type: "POST",
          url: "/api/delete_ex",
          data: {
            word_give: word,
            example_give: example
          },
          success: function (response) {
            alert(response['msg'])
            window.location.reload()
          }
        })
      }

    </script>
  </head>
  <body>
    <div class="wrap">
      <div class="main_logo" onclick="to_main()">
        <img src="../static/logo_red.png" alt="">
      </div>
      <div class="container">
        <div class="name_div">
          <div style="display: flex">
            <h1>{{ word_name }}</h1>
            <h3 style="padding-left: 5px; padding-top:13px">{{ result.pronunciation }}</h3>
          </div>
          <div>
            {% if is_saved == True %}
              <button onclick="delete_word()" type="button" class="btn btn-outline-danger btn-lg"><i
                class="fa fa-trash-o" aria-hidden="true"></i></button>
            {% else %}
              <button onclick="save_word()" type="button" class="btn btn-outline-danger btn-lg"><i
                class="fa fa-floppy-o" aria-hidden="true"></i></button>
            {% endif %}
          </div>
        </div>
        <hr>
        <div>
          {% for definition in result.definitions %}
            <div class="definition">
              <div style="font-style: italic;">{{ definition.type }}</div>
              <div>{{ definition.definition }}</div>
              <div style="color: #808080; font-size: 14px;">{{ definition.example }}</div>
            </div>
          {% endfor %}
        </div>
      </div>
      {% if is_saved == True %}
        <div class="container">
          <div style="text-align: center">
            <h2>Write your own sentences!</h2>
          </div>
          <div>
            <ul>
              {% for example in examples %}
                <li>{{ example.example }} <span style="color: lightgray; padding-left:5px" onclick="delete_ex('{{ word_name }}', '{{ example.example }}')" >delete</span></li>
              {% endfor %}
            </ul>
          </div>
          <div class="input_div">
            <input id="new-example" class="input_box" type="text">
            <button onclick="save_ex()" type="button" class="btn btn-outline-secondary btn-sm">add</button>
          </div>
        </div>
        </div>
      {% endif %}
  </body>
</html>