문제 해결 서머리
프론트 단에서 Ajax로 데이터를 POST 형식으로 보내려면 plainObject, string, array 3가지 방식이 있는데, 복수의 데이터를 보내기 위해 JSON.stringfy 메소드를 사용해서 string화 시킨 데이터를 서버를 보냈다. 그렇게 되면 flask 서버단에서는 ‘dict 형태 처럼 보이는’ 문자열을 받는데, 이를 eval() 함수를 사용해서 딕셔너리로 전환시키면 즉시 활용 가능하다.
업데이트(2022-12-07)
이전에 학습했던 웹종합반 3주차 강의에서 ajax post 데이터를 보낼 때 이렇게 3개의 key, value 페어로 보내고, 받아올 때 request.form을 활용해서 데이터들을 개별적으로 변수에 저장하는 방식으로 진행한 것을 확인했다.
역시 배울 때는 모든 것이 당연해보이지만 막상 문제를 풀려하면 마치 신기루처럼 사라진다.
function posting() {
let url = $('#url').val()
let star = $('#star').val()
let comment = $('#comment').val()
$.ajax({
type: 'POST',
url: '/movie',
data: {url_give: url, star_give: star, comment_give: comment},
success: function (response) {
alert(response['msg'])
window.location.reload()
}
});
}
def movie_post():
url_receive = request.form['url_give']
star_receive = request.form['star_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')
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"]')
image = og_image['content']
title = og_title['content']
description = og_description['content']
doc = {
'image':image,
'title':title,
'desc':description,
'star':star_receive,
'comment':comment_receive
}
db.movies.insert_one(doc)
return jsonify({'msg':'POST 연결 완료!'})
문제
$.ajax({
type: "POST",
url: "/music/postComment",
data: {commentNum_give: 여러개의 데이터},
success: function(response) {
alert(response["msg"])
window.location.reload();
}
})
jquery에서 ajax post 시 data를 하나의 변수가 아니라 여러개의 변수로 보내고 싶다. 어떻게 해야할까?
문제 원인, 시도 방안
시도1. 실패 - Array를 파라미터 값으로 넣기
$.ajax({
type: "POST",
url: "/music/postComment",
data: {comment_give: [num, inputText]},
success: function(response) {
alert(response["msg"])
window.location.reload();
}
})
data 객체의 commentNum_give의 키값에 어레이 형태로 2개의 변수를 보내고 싶었으나 실패.
jquery.min.js:2 Uncaught TypeError: Cannot convert undefined or null to object
at entries (<anonymous>)
...
시도2. 실패 - Array를 파라미터 값으로 넣기
$.ajax({
type: "POST",
url: "/music/postComment",
data: {comment_give: num,
commentText_give: inputText},
success: function(response) {
alert(response["msg"])
window.location.reload();
}
})
데이터 객체에 comment_give, commentText_give 2개 키벨류 페어를 넣기. 응 당연히 실패.
시도3. 도큐먼트, 블로그 관측
https://api.jquery.com/jquery.ajax/
공식 문서에 따르면
- data에는 plainObject, string, array가 올 수 있다.
- HTTP GET 방식일 경우, data를 url 스트링에 어펜드 한다. -> GET 방식일 경우 data를 비워두는 이유는 따로 붙일 것이 없어서라는 점을 알 수 있다.
- data가 object인 경우, jQuery가 데이터의 키/값 페어로부터 데이터 스트링을 생성하는데, 이때 processData option이 true여야 한다. { a: “bc”, d: “e, f”}의 경우, “a=bc&d=e%2Cf”로 바뀐다.
- data가 array일 경우, jQuery가 기본 세팅의 값에 기반한 같은 키와 함께 순서대로 여러 값들을 연속적으로 보낸다
- data가 string일 경우, 인코딩을 해야 한다.
찾아보니 jQuery에서 아래와 같은 양식으로 보낼 수 있다고 한다.
data: {status: status, name: name}, |
그러면 혹시 데이터를 보내는 jQuery의 문제가 아니라 데이터를 받는 flask 문법의 이슈가 아닐까?
스택오버플로우의 질문 ‘Get the data received in a Flask request’의 답변에 따르면 아래와 같다.
The docs describe the attributes available on the request object (from flask import request) during a request. In most common cases request.data will be empty because it's used as a fallback:
request.data Contains the incoming request data as string in case it came with a mimetype Flask does not handle.
- request.args: the key/value pairs in the URL query string
- request.form: the key/value pairs in the body, from a HTML post form, or JavaScript request that isn't JSON encoded
- request.files: the files in the body, which Flask keeps separate from form. HTML forms must use enctype=multipart/form-data or files will not be uploaded.
- request.values: combined args and form, preferring args if keys overlap
- request.json: parsed JSON data. The request must have the application/json content type, or use request.get_json(force=True) to ignore the content type.
- All of these are MultiDict instances (except for json). You can access values using:
- request.form['name']: use indexing if you know the key exists
- request.form.get('name'): use get if the key might not exist
- request.form.getlist('name'): use getlist if the key is sent multiple times and you want a list of values. get only returns the first value.
잘 이해가 안된다. 어떤 메소드가 복수의 데이터를 보내는데 적합한지 파악이 되지 않았다.
혹시..
이런저런 자료를 찾아보다가 string화 시키는 사례들이 많아서 그 사례를 한번 따라해보기로 했다.
> index.html
function postComment(num) {
// getElementsByClassName는 어레이를 반환하기 때문에 0번째 인덱스를 추가해줘야 한다
// const inputText = document.getElementsByClassName(`comment-post-input-1`)[0].value;
// getElementById는 값을 반환하기 때문에 바로 value 값을 얻을 수 있다
let inputText = document.getElementById(`comment-post-input-${num}`);
// 문제: inputText 변수와 .value 프로퍼티를 따로 써야 활용 가능함
console.log(inputText.value)
const commentData = JSON.stringify({comment_num: num, comment_text: inputText.value});
//const commentData = {comment_num: num, comment_text: inputText.value};
console.log(commentData);
// send post request to server
$.ajax({
type: "POST",
url: "/music/postComment",
data: {comment_give: commentData},
success: function(response) {
alert(response["msg"])
//window.location.reload();
}
})
> app.py
@app.route('/music/postComment', methods=['POST'])
def postComment():
comment_post = request.form['comment_give']
print(comment_post)
print(type(comment_post))
return jsonify({'msg': '댓글 등록 완료'})
파이썬 콘손창
{"comment_num":1,"comment_text":"hello"}
<class 'str'>
postComment()에서 보낸 데이터를 JSON.stringify으로 하니 서버로 잘 보내졌다. 처음에는 이게 파이썬 딕셔너리 형태로 받는건가 싶었는데, 확인해보니 str이라고 한다. MDN에 따르면 JSON.stringify는 JS 값을 JSON 문자열로 바꾼다고 한다.
JSON.stringify()
The JSON.stringify() method converts a JavaScript value to a JSON string, optionally replacing values if a replacer function is specified or optionally including only the specified properties if a replacer array is specified.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
그럼 결국 이 딕셔너리같이 생긴 string값을 가공해주면 된다
‘{"comment_num":1,"comment_text":"hello"}’
eval() 함수 쓰기
그런데 혹시 이 str을 eval() 함수를 사용하면 곧 바로 딕셔너리 형태로 바꿀 수 있지 않을까라는 생각이 떠올랐다. 보통 계산식 문자열을 실제 계산식으로 바꿔주는 함수이니까, 문자열로 된 딕셔너리도 실제 딕셔너리로 바꿀 수 있지 않을까?
예상이 맞았다.
testStr = '{"comment_num":1,"comment_text":"ㅁㅁ"}'
print(type(testStr))
print(type(eval(testStr)))
<class 'str'>
<class 'dict'>
해결 코드 스니펫
index.html
function postComment(num) {
// getElementsByClassName는 어레이를 반환하기 때문에 0번째 인덱스를 추가해줘야 한다
// const inputText = document.getElementsByClassName(`comment-post-input-1`)[0].value;
// getElementById는 값을 반환하기 때문에 바로 value 값을 얻을 수 있다
let inputText = document.getElementById(`comment-post-input-${num}`);
// 문제: inputText 변수와 .value 프로퍼티를 따로 써야 활용 가능함
console.log(inputText.value)
const commentData = JSON.stringify({comment_num: num, comment_text: inputText.value});
//const commentData = {comment_num: num, comment_text: inputText.value};
console.log(commentData);
// send post request to server
$.ajax({
type: "POST",
url: "/music/postComment",
data: {comment_give: commentData},
success: function(response) {
alert(response["msg"])
window.location.reload();
}
})
inputText.value = '';
}
app.py
@app.route('/music/postComment', methods=['POST'])
def postComment():
comment_post = eval(request.form['comment_give'])
receive_num = comment_post['comment_num']
receive_text = comment_post['comment_text']
print(type(receive_num))
print(receive_text)
# mongoDB에 새 코멘트 저장
video_comments = list(db.team8video.find({'num': str(receive_num)}, {'_id': False}))[0]['comment']
count = len(video_comments)+1
video_comments[str(count)] = receive_text
print(video_comments)
db.team8video.update_one({'num': str(receive_num)}, {'$set': {'comment': video_comments}})
return jsonify({'msg': '댓글 등록 완료'})
mongoDB 1개 데이터 예시
알게된 점
프론트 단에서 복수의 데이터를 POST 형식으로 보내려면 plainObject, string, array 3가지 방식이 있는데, 나는 JSON.stringfy 메소드를 사용해서 string화 시킨 데이터를 보냈다.
그렇게 되면 flask 서버단에서는 ‘dict 형태 처럼 보이는’ 문자열을 받는데, 이를 eval() 함수를 사용해서 고대로 딕셔너리로 만들어주었다. 그렇게 되면 키값 활용해서 값에 곧바로 접근할 수 있다. 문자열을 쪼개서 어떻게 해볼까 생각해봤지만 이 방식이 훨씬 깔끔하다고 생각한다.
그렇게 받은 댓글 정보와 댓글 창의 카드의 고유 넘버를 가져오면, 몽고 DB에서 해당 고유 넘버의 카드 정보를 가져올 수 있게 된다. 타겟팅한 카드의 댓글 객체를 가지고 온 뒤, 새 댓글을 추가하여 update_one 해주면 댓글이 업데이트 된다.
레퍼런스
https://stackoverflow.com/questions/10434599/get-the-data-received-in-a-flask-request
https://10study.tistory.com/17
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
전체 코드
app.py
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)
import requests
from bs4 import BeautifulSoup
from pymongo import MongoClient
client = MongoClient(
'mongodb+srv://ID:PW@cluster0.5hnlvb6.mongodb.net/?retryWrites=true&w=majority')
db = client.dbsparta
doc1 = {
'url_image': 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.o_R4qA7Jtlu0Lz-mPz9rtgHaLG%26pid%3DApi&f=1&ipt=23bf029f19adb29c7a78f63f94f28a20fa7fa0516bb42a925d7bd29407c75b44&ipo=images',
'url': 'https://www.youtube.com/watch?v=vx2u5uUu3DE&ab_channel=BonJoviVEVO',
'title': "Bon Jovi - It's my life",
'category': 'music',
'num': '1',
'comment': {
'alpha': 'hello',
'bravo': 'hello',
},
}
doc2 = {
'url_image': 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse4.mm.bing.net%2Fth%3Fid%3DOIP.FFSpDBisBp3BP4d32G9aAAHaLH%26pid%3DApi&f=1&ipt=2a944854ea39f96eaa71cfcff99d7298061fb3bf816087b1849fc00f31c00060&ipo=images',
'url': 'https://www.youtube.com/watch?v=lDK9QqIzhwk&ab_channel=BonJoviVEVO',
'title': "Bon Jovi - Livin' On A Prayer",
'category': 'music',
'num': '2',
'comment': {
'alpha': 'hello',
'bravo': 'hello',
},
}
doc3 = {
'url_image': 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.o_R4qA7Jtlu0Lz-mPz9rtgHaLG%26pid%3DApi&f=1&ipt=23bf029f19adb29c7a78f63f94f28a20fa7fa0516bb42a925d7bd29407c75b44&ipo=images',
'url': 'https://www.youtube.com/watch?v=vx2u5uUu3DE&ab_channel=BonJoviVEVO',
'title': "Bon Jovi - It's my life",
'category': 'music',
'num': '3',
'comment': {
'alpha': 'hello',
'bravo': 'hello',
},
}
# db.team8video.insert_one(doc1)
# db.team8video.insert_one(doc2)
# db.team8video.insert_one(doc3)
@app.route('/')
def home():
return render_template('index.html')
@app.route('/music', methods=['GET'])
def music():
music_list = list(db.team8video.find({'category': 'music'}, {'_id': False}))
return jsonify({'music': music_list})
@app.route('/music/postComment', methods=['POST'])
def postComment():
comment_post = eval(request.form['comment_give'])
receive_num = comment_post['comment_num']
receive_text = comment_post['comment_text']
print(type(receive_num))
print(receive_text)
# mongoDB에 새 코멘트 저장
video_comments = list(db.team8video.find({'num': str(receive_num)}, {'_id': False}))[0]['comment']
count = len(video_comments)+1
video_comments[str(count)] = receive_text
print(video_comments)
db.team8video.update_one({'num': str(receive_num)}, {'$set': {'comment': video_comments}})
return jsonify({'msg': '댓글 등록 완료'})
if __name__ == '__main__':
app.run('0.0.0.0', port=5000, debug=True)
# @app.route('/game', methods=['GET'])
# def game_get():
# game_list = list(db.team8video.find({'category': 'game'}, {'_id': False}))
# return jsonify({'games': game_list})
#
# @app.route('/news', methods=['GET'])
# def news_get():
# news_list = list(db.toys.find({'category': 'news'}, {'_id': False}))
# return jsonify({'news': news_list})
#
# @app.route('/test', methods=['POST'])
# def test_post():
# title_receive = request.form['title_give']
# print(title_receive)
# return jsonify({'result':'success', 'msg': '이 요청은 POST!'})
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<!-- jquery -->
SCRIPT
https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous"
/>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
crossorigin="anonymous"
></script>
<title>Video Scraper</title>
<script src="../static/comment.js"></script>
<style>
#cards-box {
display: flex;
}
.card-img-top {
height: 250px;
object-fit: cover;
}
.margin-top-small {
margin-top: 10px;
}
</style>
<script>
$(document).ready(function () {
showMusic();
});
function showMusic() {
$("#cards-box").empty();
$.ajax({
type: "GET",
url: "/music",
data: {},
success: function (response) {
const rows = response["music"];
rows.forEach((row) => {
const num = row["num"];
const title = row["title"];
const url = row["url"];
const url_image = row["url_image"];
const comments = row["comment"];
const comment_keys = Object.keys(comments);
let temp_comment = '';
for(let i = 0; i < comment_keys.length; i++) {
temp_comment+= `<li>${comments[comment_keys[i]]}</li>`
}
const temp_html = `
<div class="card" style="width: 18rem;">
<div>${num}번째 영상</div>
<img src=${url_image} class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">${title}</h5>
<a href="${url}" class="btn btn-primary">Go somewhere</a>
<div class="comment-area margin-top-small">
<button value="0" onclick="btnToggle(${num})" id="comment-toggle-${num}">댓글 보기</button>
<div class="comment-user" id="comment-user-${num}">
<div class="comment-post">
<input id="comment-post-input-${num}" type="text" placeholder="할말 없나?"><button onclick="postComment(${num})">등록</button>
</div>
<div class="comment-get">
<ul class="comment-list">
${temp_comment}
</ul>
</div>
</div>
</div>
</div>
</div>`;
$("#cards-box").append(temp_html);
//$(".comment-user").hide();
});
},
});
}
function btnToggle(num) {
const btnValue = document.getElementById(`comment-toggle-${num}`);
console.log(btnValue);
// open -> close
if (btnValue.value == "1") {
btnValue.value = "0";
$(`#comment-user-${num}`).hide();
// close -> open
} else if (btnValue.value == "0") {
btnValue.value = "1";
$(`#comment-user-${num}`).show();
}
}
function postComment(num) {
// getElementsByClassName는 어레이를 반환하기 때문에 0번째 인덱스를 추가해줘야 한다
// const inputText = document.getElementsByClassName(`comment-post-input-1`)[0].value;
// getElementById는 값을 반환하기 때문에 바로 value 값을 얻을 수 있다
let inputText = document.getElementById(`comment-post-input-${num}`);
// 문제: inputText 변수와 .value 프로퍼티를 따로 써야 활용 가능함
console.log(inputText.value)
const commentData = JSON.stringify({comment_num: num, comment_text: inputText.value});
//const commentData = {comment_num: num, comment_text: inputText.value};
console.log(commentData);
// send post request to server
$.ajax({
type: "POST",
url: "/music/postComment",
data: {comment_give: commentData},
success: function(response) {
alert(response["msg"])
window.location.reload();
}
})
inputText.value = '';
}
/*
function game() {
$("#cards-box").empty();
$.ajax({
type: "GET",
url: "/game",
data: {},
success: function (response) {
let rows = response["games"];
let temp_html = ``;
for (let i = 0; i < rows.length; i++) {
let title = rows[i]["title"];
let url = rows[i]["url"];
let comment = rows[i]["comment"];
temp_html = `<div class="card" style="width: 18rem;">
<img src=${url} class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">${title}</h5>
<p class="card-text">${comment}</p>
<a href="#" class="btn btn-primary">Go somewhere</a>
</div>
</div>`;
$("#cards-box").append(temp_html);
}
},
});
}
function news() {
$.ajax({
type: "GET",
url: "/news",
data: {},
success: function (response) {
console.log(response);
},
});
}
$.ajax({
type: "POST",
url: "/test",
data: {title_give: "봄날은간다"},
success: function (response) {
console.log(response);
},
}); */
</script>
</head>
<body>
<div>
<button onclick="showMusic()">music</button>
<button onclick="game()">게임</button>
<button onclick="news()">뉴스</button>
<button>sample</button>
</div>
<hr/>
<div id="cards-box">Here is video one</div>
</body>
</html>
'Log > Trouble shoot' 카테고리의 다른 글
mongoDB 에러_ Converting circular structure to JSON (0) | 2023.01.18 |
---|---|
Expres.js_SequelizeDatabaseError Unknown column 'userId' in 'field list' (0) | 2023.01.15 |
flask_템플릿 엔진 jinja가 무엇인가 (0) | 2022.12.11 |
js_inputText의 입력값 가져오기 문제(getElementById, getElementsByClassName) (0) | 2022.12.06 |
잘못된 블로그 기록 형식 개선 필요성 (0) | 2022.12.05 |
댓글