본문 바로가기

웹 개발강의/파이썬 웹개발

[파이썬 웹개발+] 2_Flask 멀티페이지_나만의 단어장(jinja2)

Flask 멀티페이지 사이트

 

1. Jinja2 템플릿 언어

  • Jinja는 Python 프로그래밍 언어용 웹 템플릿 엔진
  • Jinja 템플릿 엔진을 사용하여 템플릿 디자이너가 객체애 대한 인수를 사용하여 함수를 호출할 수 있다. template에 변수 입력 및 제어문 등을 이용하여 rendering을 쉽고 빠르게 할 수 있다.

 

2. Jinja2 템플릿 언어 사용

1) 렌더링할 html에 미세먼지 정보 보내기(app.py)

return render_template("index.html", name=name, rows=rows)

 

2) 첫번째 구의 정보를 태그로 만들기

<li>{{ rows[0].MSRSTE_NM }}: {{ rows[0].IDEX_MVL }}</li>

 

3) 변수에 저장

{% set gu_name = rows[0].MSRSTE_NM %}
{% set gu_mise = rows[0].IDEX_MVL %}
<li>{{ gu_name }}: {{ gu_mise }}</li>

 

4) 모든 구에 대해서 태그 만들기

{% for row in rows %}
    {% set gu_name = row.MSRSTE_NM %}
    {% set gu_mise = row.IDEX_MVL %}
    <li>{{ gu_name }}: {{ gu_mise }}</li>
{% endfor %}

 

5) 미세먼지 수치가 50 이상일 때만 태그 만들기

{% if gu_mise >= 50 %}
    <li>{{ gu_name }}: {{ gu_mise }}</li>
{% endif %}

 

3. URL의 일부를 변수로 받기

👉 브라우저에 HTML을 띄우는 것은 GET 요청이기 때문에, 주소 뒤에? 를 붙여 파라미터를 넘겨줄 수 있습니다.

1) 브라우저에 해당 API로 요청 보내기

http://localhost:5000/detail?word_give=hello

 

2) 서버에서 파라미터 값을 받아 HTML으로 넘겨주기

@app.route('/detail')
def detail():
    word_receive = request.args.get("word_give")
    return render_template("detail.html", word=word_receive)

 

3) HTML에서 word라는 변수에 저장된 값 나타내기

받은 단어는 {{ word }}

 

4) 플라스크 프레임워크에서 URL의 일부를 변수로 받는 방법

@app.route('/detail/<keyword>')
def detail(keyword):
    return render_template("detail.html", word=keyword)

 

4. 나만의 단어장

1) 사전API 가져오기(Owlbot open API에서 이메일 정보를 입력 후 토큰 신청)

 

 

 

참조 : https://owlbot.info/

 

Owlbot English Dictionary API

 

owlbot.info

 

2) 폴더구조

👉 폴더 구조

 

3) 서버(app.py)

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

app = Flask(__name__)

# client = MongoClient('내AWS아이피', 27017, username="아이디", password="비밀번호")
client = MongoClient('localhost', 27017)
db = client.dbsparta_plus_week2

# 메인페이지 조회(Read)
@app.route('/')
def main():
    # DB에서 저장된 단어 찾아서 HTML에 나타내기
    msg = request.args.get("msg")

    words = list(db.words.find({}, {"_id": False}))
    return render_template("index.html", words=words, msg=msg)

# 상세페이지 조회(Read)
@app.route('/detail/<keyword>')
def detail(keyword):
    status_receive = request.args.get("status_give")
    # API에서 단어 뜻 찾아서 결과 보내기
    r = requests.get(f"https://owlbot.info/api/v4/dictionary/{keyword}",
                     headers={"Authorization": "Token [내 토큰번호"})

    # 주소에 단어가 아닌 것을 넣었을 때, 사전 API에서 단어를 찾을 수 없기 때문에 에러가 납니다
    # 값을 잘 받아왔을 때 상태 코드가 200이므로 200이 아닐 때 main으로 리다이렉팅
    if r.status_code != 200:
    	# 단어 찾기 실패 얼럿을 띄우려면 redirect()에 메시지를 같이 전달
        return redirect(url_for("main", msg="단어가 이상해요 ㅠㅠ"))

    result = r.json()
    print(result)
    return render_template("detail.html", word=keyword, result=result, status=status_receive)

# 단어저장(insert)
@app.route('/api/save_word', methods=['POST'])
def save_word():
    # 단어 저장하기
    word_receive = request.form["word_give"]
    definition_receive = request.form["definition_give"]
    doc = {"word": word_receive, "definition": definition_receive}
    db.words.insert_one(doc)

    return jsonify({'result': 'success', 'msg': f'단어{word_receive} 저장!'})

# 단어삭제(delete)
@app.route('/api/delete_word', methods=['POST'])
def delete_word():
    # 단어 삭제하기
    word_receive = request.form["word_give"]
    db.words.delete_one({"word": word_receive})
    return jsonify({'result': 'success', 'msg': f'단어{word_receive} 삭제!'})

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

 

4) 메인페이지(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>

    <meta property="og:title" content="Sparta Vocabulary Notebook"/>
    <meta property="og:description" content="mini project for Web Plus"/>
    <meta property="og:image" content="{{ url_for('static', filename='logo.svg') }}"/>
    <link rel="shortcut icon" href="{{ url_for('static', filename='logo.svg') }}" type="image/x-icon">
    <link rel="icon" href="{{ url_for('static', filename='logo.svg') }}" type="image/x-icon">

    <!-- 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">

    <link href='{{ url_for("static", filename="mystyle.css") }}' rel="stylesheet">

    <style>
        .search-box {
            width: 70%;
            margin: 50px auto;
            max-width: 700px;
        }

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

        tr.highlight > td > a {
            color: white;
        }
    </style>

    <script>
        // console.log({{ msg }})
        {% if msg %}
            alert("{{ msg }}")
        {% endif %}
        let words = {{ words|tojson }}
            word_list = [];
        for (let i = 0; i < words.length; i++) {
            word_list.push(words[i]["word"])
        }
        console.log(word_list)

        function find_word() {
            let word = $("#input-word").val().toLowerCase()
            if (word == "") {
                alert("값을 입력해주세요!")
                return
            }

            if (word_list.includes(word)) {
                // 리스트에 있으면 하이라이트
                $(`#word-${word}`).addClass("highlight")
                $(`#word-${word}`).siblings().removeClass("highlight")
                $(`#word-${word}`)[0].scrollIntoView()
            } else {
                // 리스트에 없으면 새 단어를 위한 상세페이지로
                window.location.href = `/detail/${word}?status_give=new`
            }
        }
    </script>
</head>
<body>
<div class="wrap">
    <div class="banner" onclick="location.href='/'">
    </div>
    <div class="search-box d-flex justify-content-center">
        <input id="input-word" class="form-control" style="margin-right: 0.5rem">
        <button class="btn btn-light" onclick="find_word()"><i class="fa fa-search"></i></button>
    </div>
    <table class="table">
        <thead class="thead-light">
        <tr>
            <th scope="col" style="width:30%">WORD</th>
            <th scope="col">MEANING</th>
        </tr>
        </thead>
        <tbody id="tbody-box">
        {% for word in words %}
            <tr id="word-{{ word.word }}">
                <td><a href="/detail/{{ word.word }}?status_give=old">{{ word.word }}</a></td>
                <td>{{ word.definition }}
                </td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
</div>
</body>
</html>

 

5) 상세페이지(detail.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">

    <link href='{{ url_for("static", filename="mystyle.css") }}' rel="stylesheet">

    <script>
        let word = '{{ word }}'
        $(document).ready(function () {
            //get_definitions()
        })

        function get_definitions() {
            $.ajax({
                type: "GET",
                url: `https://owlbot.info/api/v4/dictionary/${word}`,
                beforeSend: function (xhr) {
                    xhr.setRequestHeader("Authorization", "Token 6c6c9f83c0aa2c9fb4ddbf90afaf88212287db2b");
                },
                data: {},
                error: function (xhr, status, error) {
                    alert("에러 발생!");
                },
                success: function (response) {
                    console.log(response)
                    $("#word").text(response["word"])

                    if (response["pronunciation"] == null) {
                        $("#pronunciation").text("")
                    } else {
                        $("#pronunciation").text(`/${response["pronunciation"]}/`)
                    }

                    let definitions = response["definitions"]
                    $("#definitions").empty()
                    for (let i = 0; i < definitions.length; i++) {
                        let definition = definitions[i]
                        let html_temp = ""
                        if (definition["example"] == null) {
                            html_temp = `<div style="padding:10px">
                                <i>${definition["type"]}</i>
                                <br>${definition["definition"]}<br>
                            </div>`
                        } else {
                            html_temp = `<div style="padding:10px">
                                <i>${definition["type"]}</i>
                                <br>${definition["definition"]}<br>
                                <span class="example">${definition["example"]}</span>
                            </div>`
                        }

                        $("#definitions").append(html_temp)
                    }

                }
            })
        }

        function save_word() {
            $.ajax({
                type: "POST",
                url: `/api/save_word`,
                data: {
                    word_give: "{{ word }}",
                    definition_give: "{{ result.definitions[0].definition }}"
                },
                success: function (response) {
                    alert(response["msg"])
                    window.location.href = "/detail/{{ word }}?status_give=old"
                }
            });
        }

        function delete_word() {
            $.ajax({
                type: "POST",
                url: `/api/delete_word`,
                data: {word_give: "{{ word }}"},
                success: function (response) {
                    alert(response["msg"])
                    window.location.href = "/"
                }
            });
        }
    </script>
</head>
<body>
<div class="wrap">
    <div class="banner" onclick="window.location.href = '/'">
    </div>
    <div class="container">
        <div class="d-flex justify-content-between align-items-end">
            <div>
                {% if result.pronunciation %}
                    <h1 id="word" style="display: inline;">{{ result.word }}</h1>
                {% endif %}
                <h5 id="pronunciation" style="display: inline;">/{{ result.pronunciation }}/</h5>
            </div>
            {% if status == "new" %}
                <button id="btn-save" class="btn btn-outline-sparta btn-lg" onclick="save_word()">
                    <i class="fa fa-floppy-o" aria-hidden="true"></i>
                </button>
            {% else %}
                <button id="btn-delete" class="btn btn-sparta btn-lg" onclick="delete_word()">delete</button>
            {% endif %}
        </div>
        <hr>
        <div id="definitions">
            {% for definition in result.definitions %}
                <div style="padding:10px">
                    <i>{{ definition.type }}</i>
                    <br>{{ definition.definition }}<br>
                    {% if definition.example %}
                        <span class="example">{{ definition.example.encode('ascii','ignore').decode('utf-8')|safe }}</span>
                    {% endif %}
                </div>
            {% endfor %}
        </div>
    </div>
</div>
</body>
</html>