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>
'스파르타코딩 공부내용 정리 > 웹개발 플러스' 카테고리의 다른 글
완주 회고 (0) | 2022.07.20 |
---|---|
4주차_Sweeter(온라인 메신저) 만들기 (0) | 2022.07.20 |
3주차_맛집 리스트 만들기 (0) | 2022.07.14 |
1주차_나만의 일기장 예제로 웹개발 기본 익히기 (0) | 2022.07.01 |