[Threepark] ๋ฐฑ์๋ ์๋ฒ์ ํต์ฌ ๊ธฐ๋ฅ ๊ตฌํ
์ด๋ฒ ํฌ์คํ ์ ๋ฐฑ์๋ ์๋ฒ ๊ตฌ์ถ๊ณผ์ ๊ณผ ํต์ฌ ๊ธฐ๋ฅ ๊ตฌํ์ ๋ํด ์์๋ณธ๋ค.
๊ฐ๋ฐ ํ๊ฒฝ๊ณผ ์๋ฒ ๊ตฌ์ฑํ๊ธฐ
๋ฐฑ์๋ ๊ฐ๋ฐ์ ์์ํ๊ธฐ ์ํด ํ์ํ ๋ฐ๋ฒ ์๋ฒ๋ฅผ ๋จผ์ ์์ฑํ๋๋ก ํ๋ค.
RDS๋ฅผ ์ด์ฉํด ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๊ด๋ฆฌํ๊ธฐ๋ก ํ์๊ณ , MySQL์ ์ฌ์ฉํ๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค
RDS์์ฑ
AWS์ ํ์๊ฐ์ ์ ์๋ฃํ ์ํ๋ก ์์ํ๋ค.
AWS ์ฝ์์ ์๋น์ค์์ RDS๋ก ๋ค์ด๊ฐ๋ค. ์ด๋ ์๋จ์ region์ ์์ธ๋ก ๋ฐ๊ฟ์ผ ํ๋ค.
์ด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ฑ์ ๋๋ฌ RDS์ธ์คํด์ค ์์ฑ์ ์์ํ๋ค.

๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ฑ ๊ณผ์


ํ์ค ์์ฑ์ ์ ํํ๋ค.
์ฐ๋ฆฌ๋ MySQL์ ์ฌ์ฉํ ์์ ์ด๋ฏ๋ก MySQL์ ํ.
๋ฐ๋ก ์๋ ์์ง ๋ฒ์ ์ ํ์ธํ์

์ฐ๋ฆฌ๋ ํ๋ฆฌํฐ์ด๋ฅผ ์ฌ์ฉํ ๊ฒ์ด๋ฏ๋ก ํ๋ฆฌํฐ์ด ์ ํ.

์ฌ์ฉํ RDS ์ธ์คํด์ค์ ์ด๋ฆ์ ์ ๋ ฅํ๊ณ , ๋ง์คํฐ ์ฌ์ฉ์๋ฅผ ์ค์ ํ๋ค. ๋ง์คํฐ ์ฌ์ฉ์ ์ด๋ฆ๊ณผ ์ํธ๋ ๊ผญ ์์ง๋ง์!!
ํ๋ฆฌํฐ์ด์์ ์คํ ๋ฆฌ์ง๋ 20GiB๊น์ง ์ธ ์ ์๋ค.

์๋์์ ํผ๋ธ๋ฆญ ์ก์ธ์ค๋ฅผ '์'๋ก ๋ฐ๊ฟ์ฃผ์.

VPC๋ณด์๊ทธ๋ฃน์ ์์ฑํด์ผํ๋ค. ๋ง์ฝ ๊ธฐ์กด ๊ทธ๋ฃน์ด ์๋ค๋ฉด ์ ์๋ฅผ ์ ํํ ์ ์์. ๋ง์ฝ ์ฒ์ ์ฌ์ฉํ๋๊ฑฐ๋ผ๋ฉด ์๋ก ์์ฑ์ ์ ํํ๋ค.
์๋ก ์์ฑ์ ๋๋ ๋ค๋ฉด ์ VPC ๋ณด์ ๊ทธ๋ฃน ์ด๋ฆ์ ์ด๋ฆ์ ์ ํด์ค๋ค.

์ด๊ธฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ด๋ฆ์ ์ ํด์ฃผ์
์ด์ ๊ธฐํ ์กฐ๊ฑด๋ค์ ์ ์ฝ์ด๋ณด๊ณ , ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ฑ์ ์ ํํด ์์ฑ ์๋ฃํ๋ค.
์ธ๋ฐ์ด๋ ๊ท์น ํธ์ง
์์ ๊ณผ์ ์ ํตํด์ RDS์์ฑ์ ์๋ฃํ๋ค. ๊ทธ๋ฌ๋ ์ง๊ธ ์ด ์ํ๋ก๋ ์ฌ์ฉํ ์ ์๋ค.
RDS์ ์ ๊ทผํ๊ธฐ ์ํด์๋ ์ธ๋ฐ์ด๋ ๊ท์น์ ์ค์ ํด์ค์ผ ํ๋ค. ์ธ๋ฐ์ด๋ ๊ท์น์ ํตํด ํ์ฉ๋ IP์ฃผ์, ํฌํธ์์ ์ด RDS์ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋๋ก ํ๋ค.
๋จผ์ ๋์๋ณด๋ ์๋์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์์ฑํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ณผ ์ ์๋ค.
์ด๋ ๊ฒ ์ฐ๊ฒฐ ๋ฐ ๋ณด์์ ๋ณด๋ฉด VPC ๋ณด์ ๊ทธ๋ฃน์ด ํ์ ๋๋ค. ํด๋น ๋ณด์๊ทธ๋ฃน์ผ๋ก ๋ค์ด๊ฐ ์ธ๋ฐ์ด๋ ๊ท์น์ ํธ์งํ๋ค.

์ธ๋ฐ์ด๋ ๊ท์น ํธ์ง์ ๋๋ฌ MySQL/Aurora, Port(3306)์ Anywhere-IPv4,Anywhere-IPv6์ ๋ชจ๋ ์ด์ด์ค๋ค.

์ฌ๊ธฐ๊น์ง ์๋ฃํ๋ฉด RDS์ธํ ์ ๋๋๋ค.
์ด์ MySQL์ RDS์ ์ฐ๊ฒฐํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํด๋ผ์ฐ๋๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณธ๋ค.
MySQL ๋ค์ด๋ก๋
https://dev.mysql.com/downloads/installer/
[MySQL :: Download MySQL Installer
Note: MySQL 8.0 is the final series with MySQL Installer. As of MySQL 8.1, use a MySQL product's MSI or Zip archive for installation. MySQL Server 8.1 and higher also bundle MySQL Configurator, a tool that helps configure MySQL Server.
dev.mysql.com](https://dev.mysql.com/downloads/installer/)
MySQL์ ๋ค์ด๋ฐ์
๋ฒ์ ์ 8.0์ผ๋ก ํ๋ค.

๋๊ฐ์ ๋ค์ด๋ก๋ ๋ฒํผ ์ค ๋๋ฒ์งธ๋ฅผ ๋๋ฅธ๋ค.
์ดํ ์๋ด๋ฅผ ๋ฐ๋ผ MySQL์ ์ค์นํ๋๋ก ํ๋ค.
MySQL๊ณผ RDS ์ฐ๊ฒฐ
์ค์น ์๋ฃ ํ MySQL Workbench๋ฅผ ์ฐ๋ค.

์ด์ MySQL Connections ์์ +๋ฒํผ์ ๋๋ฅธ๋ค


๊ทธ๋ผ ์ด๋ฐ ์ฐฝ์ด ๋จ๊ฒ๋๋ค.
๋จผ์ Connection Name์ ์ด๋ค MySQL์ ๋ํ ์ฐ๊ฒฐ์ธ์ง ์ธ์งํ๊ธฐ ์ํ ์ด๋ฆ์ด๋ฏ๋ก ๋ณธ์ธ์ด ์ํ๋ ์ด๋ฆ์ผ๋ก ์์ฑํ๋ค.
Hostname์๋ RDS์ ์๋ํฌ์ธํธ ์ ๋ณด๋ฅผ ์ ๋ ฅํ๋ค.
์ด์ ์ ์์ฑํ RDS์ ๋ค์ด๊ฐ๋ฉด ์๋ํฌ์ธํธ์ ๋ํ ์ ๋ณด๊ฐ ์์ผ๋ ๋ณต์ฌํ์ฌ ๋ถ์ฌ๋ฃ๋๋ค.
Port์๋ ํฌํธ ๋ฒํธ๋ฅผ ์ ๋ ฅํ๋ค.

๊ธฐ๋กํด๋๋ username์ ์ ๊ณ , store in vault๋ฅผ ๋๋ฌ password๋ฅผ ์ ๋ ฅํ๋ค.
Test Connection๋ฅผ ๋๋ฅด๊ณ ์๋ ์๋์ด ๋ฌ๋ค๋ฉด Ok๋ฅผ ๋๋ฅธ๋ค.

์ฌ๊ธฐ๊น์ง๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ๊ฒฝ ๊ตฌ์ฑ์ด ์๋ฃ๋๋ค.
์คํ ๋ฆฌ์ง ์๋น์ค
๊ตฌํํ๋ ค๋ ์๋น์ค๋ ํ๋๋ ์์ฑ ๋ชจ๋ธ์ ํ์ฉํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋ ๊ฒ์ด ์ฃผ์ ๊ธฐ๋ฅ์ด๊ธฐ ๋๋ฌธ์ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ ๊ณณ์ด ํ์ํ๋ค.
๋ฐ๋ผ์ AWS์ S3๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ๊ธฐ๋ก ํ๋ค. ํ๋ฆฌํฐ์ด ๋ง์ธ
์ด๋ฒ ํฌ์คํ ์์๋ AWS์ S3๋ฒํท์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ๋ํด ๊ธฐ๋กํ๋ค.
AWS S3 ์์ฑ
RDS์์ฑ๋์ ๊ฐ์ด AWS ํ์๊ฐ์ /๋ก๊ทธ์ธ ์ํ๋ก ์์ํ๋ค. ๋ฆฌ์ ์ด ์์ธ๋ก ๋์ด์๋๊ฒ์ ํ๋ฒ ๋ ํ์ธํ์.
AWS์ฝ์์ ์๋จ ๋ค๋น๊ฒ์ด์ ๋ฐ์์ ์๋น์ค ํด๋ฆญํ์ฌ S3๋ฅผ ์ฐพ์.

๋ฒํท ๋ง๋ค๊ธฐ ํด๋ฆญ
RDS์์ฑ ๋ ๋ณด๋ค ๊ฐ๋จํ๋ค.
๋ฒํท์ ๊ตฌ๋ณํ๊ธฐ ์ํ ์ด๋ฆ์ ์ ๋ ฅํ๋ค.

ํ๋ก ํธ๋ ๋ฐฑ์๋์์ ๋ฒํท์ ์ ๊ทผํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ป๊ธฐ ์ํด์๋ "๋ชจ๋ ํผ๋ธ๋ฆญ ์ก์ธ์ค ์ฐจ๋จ"์ ํ์ด์ผ ํ๋ค.
๋์ ์๋ ์ฌ์ง ์ฒ๋ผ ์ ํํ๊ณ , ์ถํ์ ์ ๊ทผ ๊ฐ๋ฅํ ์ก์ธ์ค ์ง์ ์ ์ ์ํ ๊ฒ์ด๋ค.

์ํธํ ์ค์ ์ ํ์ธํ๊ณ ์์ฑ ์๋ฃ ํ๋ค
์๋ฃ ํ ์ข์ธก ์ฌ์ด๋๋ฐ์ ๋ฒํท์ผ๋ก ์ด๋ํ๋ฉด ์์ฑ๋ ๋ฒํท์ ํ์ธํ ์ ์๋ค.

๊ทธ๋ฌ๋ ์ด ์ํ๋ก๋ ์ฌ์ฉํ ์ ์๋ค.
์ธ๋ถ์์ ์ด ๋ฒํท์ ์ ๊ทผํด ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ๊ณ , ์ฝ๊ธฐ ์ํด์๋ ์ ๊ทผ ๊ถํ์ ์ค์ ํด์ค์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ด๋ฒ์๋ CORS์ค์ ๊ณผ ๋ฒํท ์ ์ฑ ์ ์ค์ ํด์ผ ํ๋ค
๋ฒํท ์ ์ฑ ์ค์
์์ฑ๋ ๋ฒํท์ผ๋ก ๋ค์ด๊ฐ์ ๋ฒํท์ ๊ถํ์ ์ค์ ํ ์ ์๋ค

๊ถํ์ ํด๋ฆญํ๋ฉด ์ ์ฑ ์ ํธ์งํ ์ ์๋ค. ์ ์ฑ ํธ์ง์ ๋๋ฅธ๋ค.

๊ทธ๋ฌ๋ฉด ๋ฒํท ARN์ ๋ณผ ์ ์๋ค. ์ด๋ฅผ ๋ณต์ฌํ๊ณ , ์ ์ฑ ์์ฑ๊ธฐ๋ฅผ ํด๋ฆญํ๋ค.
1. Select Type of Policy ์์ S3 Bucket Policy๋ฅผ ์ ํํ๋ค.
2. Principal์ * ์ ๋ ฅ
3. Actions์ Get Object, Put Object ์ ์ฒดํฌํ๋ค.
4. Amazon Resource Name (ARN) ์ ์์์ ๋ณต์ฌํ ARN์ ์ ๋ ฅํ ํ /* ์ ๋ ฅ ex)arn:aws:s3:::ARN๋ณต์ฌํ๊ฒ/*
5. Add Statement ํด๋ฆญํ๋ค
์์ฑ์ ์๋ฃํ๋ฉด, policy json document ๊ฐ ๋์ค๋๋ฐ, ์ด๋ฅผ ๋ณต์ฌํ๋ค.
์ด์ ์ ์ ์ฑ ํธ์ง ํ์ด์ง๋ก ๋์์ฌ์ ์ ์ฑ ํ์ json๊ตฌ๋ฌธ์ ๋ถ์ฌ๋ฃ๊ธฐ ํ๊ณ ์ ์ฅํ๋ค.

CORS
๋ฒํท ์ ์ฑ ์๋์ CORS๋ฅผ ์ค์ ํ๋ค.

ํธ์ง์ ๋๋ฌ ์๋๋ฅผ ๋ถ์ฌ๋ฃ๋๋ค.
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"HEAD",
"GET",
"PUT",
"POST"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]
๊ณต๊ฐ ์ค์
์์ ๊ณผ์ ์ผ๋ก ๋ฒํท์ ์ ์ฑ ์ ๋ํ ์ค์ ์ ์๋ฃํ์ง๋ง, ๊ทธ๋ ๋ค๊ณ ํด์ ๋ฒํท์ ์ด๋ฏธ์ง๋ฅผ ์ธ๋ถ์์ ๋ก๋ํ ์ ์๋๊ฒ์ ์๋๋ค.
์ด๋ฒ์๋ ์ด๋ฏธ์ง๊ฐ ์ ์ฅ๋ images ํด๋๋ง ์ธ๋ถ์์ ์ด๋ฏธ์ง url๋ก ์ด๋ฏธ์ง๋ฅผ ๋ณผ ์ ์๋๋ก ํ์.
๋จผ์ ๋ฒํท์ผ๋ก ๋ค์ด์์ ํด๋ ๋ง๋ค๊ธฐ๋ฅผ ํตํด์ images ํด๋๋ฅผ ๋ง๋ ๋ค. ํฐ๋๊ฐ ์๋์ฒ๋ผ ์์ฑ๋์๋ค๋ฉด ๋ค์์ผ๋ก ๋์ด๊ฐ์

ํด๋ ์ผ์ชฝ์ ์ฒดํฌ๋ฐ์ค๋ฅผ ํด๋ฆญํ๊ณ , ์์ ์์ ์ ๋๋ฅด๋ฉด ACL์ ์ฌ์ฉํ์ฌ ํผ๋ธ๋ฆญ์ผ๋ก ์ค์ ํ ์ ์๋ค.

์ด์ ์ด ํด๋์ ์ด๋ฏธ์ง ํ์ผ์ ์ธ๋ถ์์ url์ ํตํด ์ฝ์ ์ ์๊ฒ ๋์๋ค.
ํ์ธ์ ์ํด ์ด๋ฏธ์ง๋ฅผ ๋ฃ๊ณ ,
url์ฐฝ์ ์ด๋ฏธ์ง url์ ๋ฃ์ผ๋ฉด ๋ค์์ฒ๋ผ ๋์จ๋ค.

์ฌ๊ธฐ๊น์ง ํ๋ฉด ์คํ ๋ฆฌ์ง ๊น์ง ์ค๋น๊ฐ ์๋ฃ๋๋ค.
๋ฐฑ์๋ - Django Rest Framework
์ด์ ๋ Django - DRF์ Flask๋ฅผ ์ด์ฉํด์ ๋ฐฑ์ค๋ API์ ์์ฑํ, ๋ถ๋ฅ ๋ชจ๋ธ์ ์ฐ๊ฒฐํ๋๋ก ํ๋ค.
๋ฐฑ์๋ API ๊ตฌํ์ ์์ํ๋๋ก ํ๋ค.
์ฐ๋ฆฌ ์๋น์ค์์๋ ์ฅ๊ณ ์ DRF๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ํ์๋ค. DRF ๊ฐ๋ฐ์ ์ํ ํ๊ฒฝ์ค์ ์ ํ๋ ๊ณผ์ ์ ๋ํด ์์๋ณด์.
Django Rest Framework ํ๊ฒฝ ์ค์
DRF ์ค์น
์ผ๋จ ํ์ด์ฌ ์ฌ์ฉ์ ์ ์ ๋ก ํ๋ค.
ํ์ด์ฌ ๋ฒ์ ์ 3.10์ ์ฌ์ฉํ๋ค.
pip install django
pip install djangorestframework
ํ๋ก์ ํธ ์์ฑ
ํ๋ก์ ํธ ์์ฑ์ ์ํ๋ ๋๋ ํ ๋ฆฌ์์ ๋ค์์ ์คํํ๋ค.
django-admin startproject ํ๋ก์ ํธ์ด๋ฆ .
์ด๋ฅผ ์๋ฃํ๋ฉด djnago ํ๋ก์ ํธ๊ฐ ์์ฑ๋๋ค.
app์์ฑ
๊ฐ๋ณ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ app๋ ๋ง๋ค๊ธฐ ์ํด์๋ ์๋์ ๋ช ๋ น์ด๋ฅผ ์คํํ๋ค.
python manage.py startapp APP์ด๋ฆ
์) python manage.py startapp diary
Settings.py ์ค์
settings.py ์ ๋ค์ด๊ฐ๋ฉด INSTALLED_APPS๊ฐ ์๋ค. ๊ฑฐ๊ธฐ์ ๋ค๋ฆ์ ์ถ๊ฐํ๋ค.

์ถ๊ฐ ํ ์ ์ฒด ์ ์ฅ์ ํ ๋ค ๋ค์์ ์คํํ๋ค.
python manage.py migrate
๊ฐ๋ฐ์ฉ ๋ก์ปฌ ์๋ฒ runserver
ํ์ฌ ๊ฐ๋ฐ์ ๋ํ ๊ตฌํ์ ๋ก์ปฌ์๋ฒ๋ฅผ ํตํด ํ์ธํ ์ ์๋ค.
python manage.py runserver
Django Environ์ผ๋ก ํ๊ฒฝ๋ณ์ ๊ด๋ฆฌํ๊ธฐ
ํ๋ก์ ํธ ์์ฑ์ ๋๋ฌ๊ณ , ์ด์ ํ๊ฒฝ ๋ณ์๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํ .envํ์ผ์ ๊ตฌ์ฑํด์ผ ํ๋ค.
DRF๋ฅผ ์ด์ฉํด ์๋น์ค๋ฅผ ๊ฐ๋ฐํ๊ธฐ ์ํด์ ๊ด๋ฆฌํด์ผ ํ ํ๊ฒฝ๋ณ์๋ค์ Django Environ์ ํตํด ๊ด๋ฆฌํ ์ ์๋ค.
ํนํ ํ๊ฒฝ๋ณ์๋ APIํค ๋ฑ ์ธ๋ถ๋ก ์ ์ถ๋๋ฉด ์๋๋ ์ ๋ณด๋ ์ด๋ฅผ ํตํด ๊ด๋ฆฌํด์ผ ํ๋ค.
์ค์น
pip install django-environ
.env ์์ฑ
ํ๋ก์ ํธ์ ๋ฃจํธ์ .env ํ์ผ์ ๋จผ์ ์์ฑํด์ค๋ค.

์์ฑ ํ์๋ ๊ผญ gitignoreํ์ผ์ .env๋ฅผ ์ถ๊ฐํ์ฌ ๊นํ๋ธ์ ์ฌ๋ผ๊ฐ์ง ์๋๋ก ํ๋ค.

install django-environ
๋ค์์ ํฐ๋ฏธ๋์ ์ ๋ ฅํ์ฌ django-environ์ ์ค์นํ๋ค.
pip install django-environ
.env ์์ฑ
ํ๊ฒฝ๋ณ์๋ก ์ง์ ํด์ผ ํ๋๊ฐ๋ค์ env์ ์ ์ํ๋ค.
๋ค์๊ณผ ๊ฐ์ด ์์ฑํด์ผ ํ๋ค.
SECRET_KEY='django-insecure-...'
DEBUG=True
# MySQL DB
DB_NAME='localdb'
...
์ด๋ ์ฃผ์ํ ์ ์ ํค์ ๊ฐ ์ฌ์ด์ ๋์ด์ฐ๊ธฐ๋ฅผ ํฌํจํด์๋ ์๋๋ค๋ ๊ฒ์ด๋ค.
# ํ๋ฆฐ ์์
DB_NAME = 'localdb'
# ์ฌ๋ฐ๋ฅธ ์์
DB_NAME='localdb'
settings.py ์์ฑ
settings.py์ ๋ค์์ ์ถ๊ฐํ๋ค.
import environ
...
env = environ.Env(DEBUG=(bool, False))
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
...
# ์ด์ ๋ ํ๊ฒฝ๋ณ์๊ฐ ๋ค์ด๊ฐ ์๋ฆฌ๋ฅผ ๋ค์์ฒ๋ผ ๋ฐ๊พธ์ด ์์ฑํด์ค๋ค.
SECRET_KEY = env('SECRET_KEY')
DEBUG = env('DEBUG')
...
migrate
settings.py์์ฑ์ ๋ง์ณค๋ค๋ฉด migrate๋ฅผ ์งํํ๋ค.
python manage.py migrate
Django์ RDS ์ฐ๊ฒฐํ๊ธฐ
์ด๋ฒ์๋ django ์ AWS RDS๋ฅผ ์ฐ๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์์๋ณธ๋ค.
์ค์น
pip install boto3
pip install mysqlclient
Settings.py
ํ๋ก์ ํธ์ settings.py์ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค.

๊ฐ๋ณ ํ๊ฒฝ๋ณ์๋ค์ ์ด์ ํฌ์คํ ์ ํตํด์ ์์ฑํ .envํ์ผ์ ์ ์ํ๋ค.
๋ชจ๋ ์ ์ฅํ๊ธฐ๋ฅผ ํ๋ฒ ๋๋ฅด๊ณ ,
migration์ ์งํํ๋ค.
python manage.py migrate
DRF ๋ฐฑ์๋ ๊ตฌํ
๋๋์ด ๋ฐฑ์๋์ ๊ตฌํ์ด ์์๋๋ค!! ๋ชจ๋ ๊ตฌํ๊ณผ์ ์ ์ค๋ช ํ ์๋ ์๊ธฐ ๋๋ฌธ์ ๊ตฌํ ์ ํน์ํ ๋ถ๋ถ๊ณผ ํต์ฌ ๊ธฐ๋ฅ๋ถ๋ถ์ ํฌ์คํ ํ๋ค.
๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ด ์งํ๋๋ค.
1. Model ๋ง๋ค๊ธฐ
2. Serializer ๋ง๋ค๊ธฐ
3. permissions ์ปค์คํ ํ์ฌ ์ ๊ทผ ๊ถํ ๋ง๋ค๊ธฐ
4. views ์์ฑํ๊ธฐ
Authentication์ด ๊ถ๊ธํ๋ค๋ฉด ์๋ ํฌ์คํ ์ ์ดํด๋ณผ ์ ์๋ค. (ํ์ง๋ง ์ด ํฌ์คํ ์์๋ ๋ค๋ฃจ์ง ์๋๋ค)
https://he-kate1130.tistory.com/61
[[Team 22] DRF Authentication - dj-rest-auth
์์ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ์ถํ์ ์ฝ๊ฒ ์ถ๊ฐํ๊ธฐ ์ํด์ dj-rest-auth๋ฅผ ์ฌ์ฉํด ํ์๊ฐ์ , ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ๊ตฌํํด๋ณด๋๋ก ํ์. ์ฌ์ค ์๋๋ simple jwt๋ฅผ ํ์ฉํด์ ํ ํฐ์ ๋ฐ๊ธํ๊ณ ์ฟ ํค์ ์ ์ฅํด๋๋ ๋ฐฉ์์ผ
he-kate1130.tistory.com](https://he-kate1130.tistory.com/61)
DB & MODEL
์ด์ DB Schema๋ฅผ ๊ตฌ์ฑํ๊ณ , django์ Model์ ์์ฑํ์
DB Schema ๊ตฌ์ฑ
DB ์คํค๋ง๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑํ๋ค.
๊ณํ์ ๊ณผ์ ์์ ๋ค์๊ณผ ๊ฐ์ ํํ๋ก ๊ตฌ์ํ๋ค. (์ด๋ฏธ์ง ์์ฒด๋ ๊ฒฐ๊ณผ ์ด๋ฏธ์ง๋ค)
- ์ ์ ์ ๋ณด์ ๊ฒฝ์ฐ django์์ ์ ๊ณตํ๋ ๋ชจ๋ธ์ ์ฌ์ฉํ๊ธฐ๋ก ํจ

๋ชจ๋ธ์ด ๋ง์ ํธ์ด๋ฏ๋ก user, follow, diary, music, image, emotion์ ๊ฒฝ์ฐ๋ง ์ด์ผ๊ธฐ ํด ๋ณธ๋ค.
User
์ ์ ๋ชจ๋ธ์ django์ auth๋ชจ๋ธ์ ์ฌ์ฉํ ๊ฒ์ด๋ค. ๋ฐ๋ผ์ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค.
from django.db import models
from django.contrib.auth.models import AbstractUser, BaseUserManager
class UserManager(BaseUserManager):
def create_user(self, email, username, password=None, **extra_fields):
if not email:
raise ValueError('The Email field must be set')
email = self.normalize_email(email)
user = self.model(email=email, username=username, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, username, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
return self.create_user(email, username, password, **extra_fields)
class User(AbstractUser):
REQUIRED_FIELDS = ['email']
objects = UserManager()
class Meta:
db_table = 'user'
def get_by_natural_key(self, username):
return self.get(username=username)
Follow
follow๋ชจ๋ธ์ ๊ฒฝ์ฐ ์ ์ ๋๋ช ๊ณผ ๋๋ช ์ ๊ด๊ณ๊ฐ ์ ์๋ status๊ฐ์ด ๋ค์ด๊ฐ๋ค.
์ด๋ ์ ์ ๋๋ช ์ ์ ๋ํฌํ ์์ด ๋์ด์ผ ํ๋ค. (์ ์ ๊ฐ์ ์ฌ๋ฌ๊ฐ์ ๊ด๊ณ๊ฐ ์์ ์ ์๋ค.)
from django.db import models
class Follow(models.Model):
REQUESTED = 'requested'
ACCEPTED = 'accepted'
REJECTED = 'rejected'
STATUS_CHOICES = (
(REQUESTED, 'Requested'),
(ACCEPTED, 'Accepted'),
(REJECTED, 'Rejected'),
)
follower = models.ForeignKey(User, related_name='following', on_delete=models.CASCADE)
following_user = models.ForeignKey(User, related_name='followers', on_delete=models.CASCADE)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=REQUESTED)
class Meta:
unique_together = ('follower', 'following_user')
db_table = 'follow'
managed = True
Music
์์ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ Music์ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค.
from django.db import models
class Music(models.Model):
music_title = models.CharField(max_length=100, null=True)
artist = models.CharField(max_length=100, null=True)
genre = models.CharField(max_length=20, null=True)
class Meta:
managed = True
db_table = 'music'
Diary
์์ ์ ๋ณด์ ์ ์ ๋ฅผ fk๋ก ๊ฐ์ง๋ diary์ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ๋ค.
ํ์ดํ๊ณผ ๋ด์ฉ(content), ์ต์ด ์์ฑ ์๊ฐ๊ณผ ๋ง์ง๋ง ์ ๋ฐ์ดํธ ์๊ฐ์ ์ ์ฅํ๊ณ , ํ๋ก์์๊ฒ ๊ณต๊ฐ์ฌ๋ถ๋ฅผ ์ ํ ์ ์๊ฒ ํ๋ค.
class Diary(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
music = models.ForeignKey(Music, on_delete=models.SET_NULL, blank=True, null=True)
title = models.CharField(max_length = 30)
content = models.TextField(blank=True)
registered_at = models.DateTimeField(auto_now_add=True)
last_update_at = models.DateTimeField(auto_now=True)
is_open = models.BooleanField(default=False)
class Meta:
managed = True
db_table = 'diary'
Image
์ผ๊ธฐ๋ฅผ fk๋ก ๊ฐ์ง๊ณ , ์ด๋ฏธ์ง์ url์ ๋ค๋ฃจ๋ ๋ชจ๋ธ๋ก, ๋ค์๊ณผ ๊ฐ์ด ๋ชจ๋ธ์ ๊ตฌ์ฑํ๋ค.
class Image(models.Model):
diary = models.ForeignKey(Diary, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
image_url = models.URLField(null=True)
image_prompt = models.TextField(null=True)
class Meta:
managed = True
db_table = 'image'
Emotion
์ผ๊ธฐ๋ฅผ fk๋ก ๊ฐ์ง๊ณ , emotion_label, chat์ ๋ค๋ฃจ๋ ๋ชจ๋ธ
class Emotion(models.Model):
diary = models.ForeignKey(Diary,on_delete=models.CASCADE)
emotion_label = models.CharField(max_length=10, blank=True)
emotion_prompt = models.TextField(blank=True)
chat = models.TextField(blank=True)
class Meta:
db_table ='emotion'
migration
์ ์ฒด ๋ชจ๋ธ์ ์ ์ฅํ๊ณ , ๋ค์์ ์์ฐจ ์คํํ๋ค.
python manage.py makemigrations
python manage.py migrate
์ฌ๊ธฐ๊น์งํด์ ๋ชจ๋ธ๊ตฌ์ฑ์ ํตํ DB ์คํค๋ง ๊ตฌ์ฑ์ด ๋๋๋ค.
SERIALIZER
๋ชจ๋ธ ์์ฑ์ ์๋ฃ ํ ์๋ฆฌ์ผ๋ผ์ด์ ๋ฅผ ์์ฑํ๋๋ก ํ๋ค.
์๋ฆฌ์ผ๋ผ์ด์ ์ ๊ฒฝ์ฐ ๋ฏผ๊ฐํ ๋ฐ์ดํฐ๊ฐ ์๋๊ฒฝ์ฐ ๋์ฒด๋ก all๋ก ์์ฑํ์์ผ๋ฉฐ, ํน์ํ ๊ฒฝ์ฐ์ธ community์ diary๋ง ์ดํด๋ณธ๋ค.
Diary
์ผ๊ธฐ์ ๊ฒฝ์ฐ ํฌ๊ฒ ์ผ๊ธฐ ๋ด์ฉ๊ณผ, ์ผ๊ธฐ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์์ฑ๋ ์์ , ์ด๋ฏธ์ง, ๊ฐ์ ๋ฐ์ดํฐ๋ก ํฌ๊ฒ ๋๋ ๋ณผ ์ ์๋ค.
์ด์ค ์์ ์ ๊ฒฝ์ฐ๋ diary๋ชจ๋ธ์์ fk๋ก ์ฐธ์กฐํ๊ธฐ ๋๋ฌธ์, ์์ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๊ณ , ์ผ๊ธฐ์ ์ฐ๊ฒฐํ๋ ๋ถ๋ถ์ ๋ฐ๋ก ๊ตฌ์ฑํ๋ค.
๋ฐ๋ผ์ ์๋ฆฌ์ผ๋ผ์ด์ ๋ ๋ค์๊ณผ ๊ฐ๋ค.
1. ์์ํ๊ฒ ์ผ๊ธฐ๋ฅผ ์์ฑํ ์ ์๊ฒ ํ๊ณ , ๋ค๋ฅธ ์ฐ๊ด ๋ฐ์ดํฐ๋ only read๋ก ์ฝ์์๋ง ์๋๋ก ํ๋ DiarySerializer
2. ์ผ๊ธฐ ๋ด์ฉ์ ํ ๋๋ก ์์ ์ ์ถ์ฒํ๊ณ , ์์ -์ผ๊ธฐ๋ฅผ ์ฐ๊ฒฐํ ์ ์๋๋ก ํ๋ DiaryMusicSerializer
DiarySerializer
class DiarySerializer(serializers.ModelSerializer):
music = MusicSerializer(required=False)
image_set = ImageSerializer(many=True, read_only=True)
emotion_set = EmotionSerializer(many=True, read_only=True)
class Meta:
model = Diary
fields = ['id','user','title','content','registered_at','last_update_at','music','is_open','image_set','emotion_set']
DiaryMusicSerializer
class DiaryMusicSerializer(serializers.ModelSerializer):
music = MusicSerializer(required=False)
class Meta:
model = Diary
fields =['id', 'user', 'content', 'music']
def update(self, instance, validated_data):
music_data = validated_data.pop('music', None)
instance = super().update(instance, validated_data)
if music_data:
music, _ = Music.objects.get_or_create(**music_data)
instance.music = music
return instance
Community
์ปค๋ฎค๋ํฐ์ ๊ฒฝ์ฐ ์๋ก ํ๋ก์ฐ๊ฐ ํ์ฉ๋ ๊ด๊ณ์ ์ ์ ์ผ๊ธฐ ์ค ๊ณต๊ฐ๋ ๊ฒ๋ค์ ์กฐํํ ์ ์๋๋ก ํ๋ค.
CommunitySerializer
class CommunitySerializer(serializers.ModelSerializer):
user = UserSerializer(required = False)
music = MusicSerializer(required=False)
image_set = ImageSerializer(many=True, read_only=True)
class Meta:
model = Diary
fields = ['id','user','title','content','music','image_set','registered_at','last_update_at', 'is_open']
์ฌ๊ธฐ์ ํ๋ก์ฐ ์ ๋ณด๊ฐ ์๋๋ฐ์? > ์ด๋ถ๋ถ์ ํ์ permissions๋ฅผ ํตํด ๊ฐ๋ฅํ๊ฒ ํ๋ค.
PERMISSION
๋ทฐ๋ฅผ ์์ฑํ๊ธฐ์ ์์์ ์ ๊ทผ์ ์ ์ด๋ฅผ ์ํ ์ปค์คํ permissions๋ฅผ ์์ฑํ์.
permissions.py
๋จผ์ permissions.py๋ฅผ settings.py๊ฐ ์๋ ํด๋์ ์์ฑํ๋ค.
์ด ํด๋์ ์ปค์คํ permissions๋ฅผ ์์ฑํ ๊ฒ์ด๋ค.
import
permissions.py์ ์๋จ์ ๋ค์์ import ํด์ผ ํ๋ค.
from rest_framework import permissions
Permission ์์ฑ
์ด ์ธ๊ฐ์ง์ permission์ ์์ฑํ ๊ฒ์ด๋ค.
1. ๋ณธ์ธ์ ๋ฐ์ดํฐ๋ง ์ ๊ทผ๊ฐ๋ฅํ๊ฒ ํ๋ IsOwner
2. ๋ณธ์ธ๋ง ์์ ,์ญ์ ํ ์ ์๊ฒ ํ๋ ํ์ฉ๋ ํ๋ก์์๊ฒ๋ ์กฐํํ ์ ์๊ฒ ํ๋ IsOwnerOrReadOnly
3. ํ๋ก์ ์ ์ฒญ ์ ๋ณธ์ธ๊ณผ ํ๋ก์ ์ ์ฒญ์ ๊ด๋ จ๋ ์ฌ๋๋ง ์กฐํ,ํธ์งํ ์ ์๊ฒ ํ๋ IsFollowerOrOwner
IsOwner
๋ฐ์ดํฐ์ ์์ ์๋ง ์ ๊ทผํ๊ณ ์์ ์ญ์ ํ ์ ์๊ฒ ํ๋ค
class IsOwner(permissions.BasePermission):
"""
๋ณธ์ธ์ data๋ง ์ ๊ทผ ๊ฐ๋ฅํ๋ค.
"""
def has_permission(self, request, view):
return request.user.is_authenticated
def has_object_permission(self, request, view, obj):
return obj.user == request.user
IsOwnerOrReadOnly
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
๊ฐ์ฒด๋ฅผ ๋ง๋ ์ฌ์ฉ์๋ง ์์ ํ ์ ์๋ค.
"""
def has_permission(self, request, view):
return request.user.is_authenticated
def has_object_permission(self, request, view, obj):
# ์์ฒญํ ์ฌ์ฉ์๊ฐ ํด๋น ๊ฐ์ฒด์ ์์ ์์ธ ๊ฒฝ์ฐ์๋ง ์ฐ๊ธฐ ๊ถํ์ ๋ถ์ฌํจ
return obj.follower == request.user or obj.following_user == request.user
IsFollowerOrOwner
class IsFollowerOrOwner(permissions.BasePermission):
"""
Custom permission to allow reading followed items only if they are open.
"""
def has_permission(self, request, view):
return request.user.is_authenticated
def has_object_permission(self, request, view, obj):
# Check if the request method is safe (GET, HEAD, OPTIONS)
if request.method in permissions.SAFE_METHODS:
if Follow.objects.filter(follower=request.user, following_user=obj.user, status='accepted').exists() | Follow.objects.filter(follower=obj.user, following_user=request.user, status='accepted').exists():
return obj.is_open
return obj.user == request.user
์์ permissions๋ค์ ํตํด ์ด์ ์ฌ์ฉ์์ ๋ฐ๋ฅธ ์ ๊ทผ ์ ์ด๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ๋ค.
VIEW
ํธ๋ฆฌํ API์ ๊ตฌ์ฑ์ ์ํด์ Mixins์ GenericViewSet์ ์ฌ์ฉํ๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ์๋์ ๊ฐ์ด ์ฌ์ฉํ๊ฒ ๋๋ค.
class DiaryViewSet(GenericViewSet, # ๋ฏน์ค์ธ ์ฌ์ฉ์ ์ํด ๊ผญ ์ถ๊ฐ
mixins.ListModelMixin,#๋ฆฌ์คํธ API
mixins.CreateModelMixin,#์์ฑ API
mixins.RetrieveModelMixin,#์กฐํ API
mixins.UpdateModelMixin,#์์ API. ๋ถ๋ถ ์์ ๊ณผ ์ ์ฒด ์์ ์์
mixins.DestroyModelMixin):#์ญ์ API
# ์๋ ํผ๋ฏธ์
~์ฟผ๋ฆฌ์
์ ํ์ ์์ฑ
permission_classes = [IsOwner]
serializer_class = DiarySerializer
queryset = Diary.objects.all()
"""
์ฌ๊ธฐ์ ์ ๋ ์ฃผ์์ ํ์ swagger API๋ฌธ์๋ฅผ ์ํ ๊ฒ. ์ด๋ค ๋ทฐ์ธ์ง ์์ฑ.
์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์์ฑ.
์ผ๊ธฐ์ ๋ด์ฉ์ ๋ํ API
"""
# ์ด ๋ถ๋ถ์ ๋ฏน์ค์ธ์ ์ฌ์ฉํ ๋ ์ฟผ๋ฆฌ๋ฅผ ํํฐ๋งํ์ฌ ๋ณธ์ธ์ ๋ฐ์ดํฐ๋ง ๋ณผ ์ ์๋๋ก ์ฒ๋ฆฌํ๊ฒ
def filter_queryset(self,queryset):
queryset = queryset.filter(user=self.request.user)
return super().filter_queryset(queryset)
# ์ด ์๋๋ก ๋ถํฐ ์ถ๊ฐ๋ก ์ปค์คํ
์ด ํ์ํ ๋ฏน์ค์ธ๋ค, ํจ์๋ค์ ์์ฑํ๋ค.
# ์๋๋ถ๋ถ์์ด๋ ๊ธฐ๋ณธ ๋ฏน์ค์ธ์์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ค
๊ธฐ๋ณธ ๋ฏน์ค์ธ์ ํจ์๋ค์ ์๋ ๊นํ์์ ํ์ธํ ์ ์๋ค.
https://github.com/encode/django-rest-framework/blob/master/rest_framework/mixins.py
[django-rest-framework/rest_framework/mixins.py at master ยท encode/django-rest-framework
Web APIs for Django. ๐ธ. Contribute to encode/django-rest-framework development by creating an account on GitHub.
github.com](https://github.com/encode/django-rest-framework/blob/master/rest_framework/mixins.py)
์ญ์ ๋ทฐ๊ฐ ์์ฒญ๋๊ฒ ๋ง๊ธฐ ๋๋ฌธ์ ํน์ํ ๊ฒฝ์ฐ๋ง ์ดํด๋ณด์.
Follow
ํ๋ก์ ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ mixins๋ค์ ๊ฐ ๊ธฐ๋ฅ๋ค์ ํ์ฉํ์ฌ ๊ตฌํํ์๋ค. (์ฝ๊ฐ ์ผ๋งค๋ก ๊ตฌํํ ๋๋.. ํ์ง๋ง ์ ๋์๊ฐ๋ค๋ฉด?)
1. permissions์ IsOwnerOrReadOnly๋ฅผ ํตํด ํ๋ก์์ ์ฐ๊ด๋ ์ฌ์ฉ์๋ง ์์ ํ ์ ์๋๋ก ํ๋ค.
2. filter_queryset์ ํตํด ์ฐ๊ด๋ ์ฌ์ฉ์๋ง ์ ๊ทผํ ์ ์๊ฒ ํ๋ค.
๊ฐ๋ณ ํจ์๋ณ ์ญํ
CREATE : ํ๋ก์ฐ ์์ฒญ
DESTROY: ํ๋ก์ฐ ์ทจ์/์ญ์
UPDATE: ํ๋ก์ฐ ํ์ฉ
PARTIAL_UPDATE: ํ๋ก์ฐ ๊ฑฐ์
class FollowViewSet(GenericViewSet,
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin):
permission_classes = [IsOwnerOrReadOnly]
serializer_class = FollowSerializer
queryset = Follow.objects.all()
def filter_queryset(self,queryset):
queryset = queryset.filter(Q(follower=self.request.user) | Q(following_user=self.request.user))
return super().filter_queryset(queryset)
'''
ํ๋ก์ฐ API
---
### id : ํ๋ก์ฐ ์์ฒญ์ id
'''
@swagger_auto_schema( request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'username': openapi.Schema(type=openapi.TYPE_STRING, description='ํ๋ก์ฐ ์์ฒญํ ์ ์ ์ username')
}
))
def create(self, request, *args, **kwargs):
'''
ํ๋ก์ฐ ์์ฒญํ๋ API
'''
# ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ์ฌ์ฉ์ ์ด๋ฆ์ ๋ฐ์
username = request.data.get('username')
# ๋ฐ์ ์ฌ์ฉ์ ์ด๋ฆ์ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์๋ฅผ ์ฐพ์
try:
following_user = User.objects.get(username=username)
except User.DoesNotExist:
return Response({"message": f"User '{username}' does not exist"}, status=status.HTTP_404_NOT_FOUND)
# ํ๋ก์ฐ ์์ฒญ ์์ฑ์ ์ฌ์ฉํ ๋ฐ์ดํฐ ๊ตฌ์ฑ
request_data = {
'follower': request.user.id,
'following_user': following_user.id,
'status': Follow.REQUESTED
}
serializer = self.get_serializer(data=request_data)
serializer.is_valid(raise_exception=True)
user = self.request.user
followee = serializer.validated_data.get('following_user')
if followee==user:
return Response({"message": f"Cannot Follow yourself, {followee.username}."}, status=status.HTTP_400_BAD_REQUEST)
if Follow.objects.filter(follower=user, following_user=followee).exists() | Follow.objects.filter(follower=user, following_user=followee).exists():
return Response({"message": f"Follow request already sent to {followee.username}."}, status=status.HTTP_400_BAD_REQUEST)
follow_request, created = Follow.objects.get_or_create(follower=request.user, following_user=followee, status=Follow.REQUESTED)
serializer = self.get_serializer(follow_request)
if created:
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response({"message": f"Follow request already sent to {followee.username}"}, status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, *args, **kwargs):
'''
ํ๋ก์ฐ ์์ฒญ ์ญ์ /์ทจ์ํ๋ API
'''
instance = self.get_object()
self.perform_destroy(instance)
return Response({"message": "Follow request deleted"}, status=status.HTTP_204_NO_CONTENT)
@swagger_auto_schema(
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={}
))
def update(self, request, *args, **kwargs):
'''
ํ๋ก์ฐ ์์ฒญ ํ์ฉํ๋ API
'''
instance = self.get_object()
# ์์ฒญ๋ฐ์ ์ฌ์ฉ์๊ฐ ํ์ฌ ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ ์ผ์นํ๋์ง ํ์ธ
if instance.following_user != request.user:
raise PermissionDenied("๊ถํ์ด ์์ต๋๋ค")
instance.status = Follow.ACCEPTED
instance.save()
serializer = self.get_serializer(instance)
return Response(serializer.data, status=status.HTTP_200_OK)
def partial_update(self, request, *args, **kwargs):
'''
ํ๋ก์ฐ ์์ฒญ ๊ฑฐ์ ํ๋ API
'''
instance = self.get_object()
instance.status = Follow.REJECTED
instance.save()
serializer = self.get_serializer(instance)
return Response(serializer.data, status=status.HTTP_200_OK)
AI ์์ ์ด ํ์ํ Viewset๋ค
์ด ์๋๋ API์์ฒญ์ด ๋ค์ด๊ฐ๋ ์ธ๋ถ ํจ์๋ฅผ ํตํด AI์ ์์ ์ด ์๋ ๋ถ๋ถ์ ๋ํ View์ด๋ค.
๋์ ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ๋ค. ๋นจ๊ฐ ๊ธ์จ๊ฐ ์๋ ๋ถ๋ถ์ด AI์๋ฒ๋ก ์์ฒญ์ ๋ฃ๋ ๋ถ๋ถ์ด๋ค.

Music
views ์๋จ์ AI ์๋ฒ๋ก ์์ ์ถ์ฒ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๋ ํจ์๋ฅผ ์์ฑํ๋ค.
def request_music_from_flask(content):
"""
diary content ๋ฅผ ai์๋ฒ์ ์ ๋ฌ, ์์
์ถ์ฒ ๋ฐ์์ด
"""
flask_url = f'http://{settings.FLASK_URL}:5000/get_music'
try:
response = requests.post(flask_url, json={'content': content},verify=False, timeout=50)
if response.status_code == 200:
response_data = response.json()
time.sleep(2)
return response_data
else:
print("Failed to get music from Flask:", response.status_code)
return None
except Exception as e:
print("Error:", e)
time.sleep(10)
return None
์ด๋ฅผ ๋ฐํ์ผ๋ก ์์ ๊ฐ์ฒด๋ฅผ ์ ์ฅํ๊ณ , ์ผ๊ธฐ์ ์์ ๋ฐ์ดํฐ๋ฅผ ์ฐ๊ฒฐํ๋ค.
def update(self, request,*args, **kwargs):
"""
diary_music_update ์ผ๊ธฐ์ ๋ํด ์์
์ ์ถ์ฒํ๋ API
---
### id = ์ผ๊ธฐ ID
์ต๋ 15์ด ์์ ๊ฐ๋ฅ
### ์์ request:
{
"user": 1,
}
### ์์ response:
200
{
"id": 1,
"user": 1,
"content": "๋๋ฌด ๋๊ทผ๊ฑฐ๋ฆฐ๋ค! ๊ณผ์ฐ rds์ ๋ด ๋ค์ด์ด๋ฆฌ๊ฐ ์ ์ฌ๋ผ๊ฐ๊น? ์ค๋ ์ด๊ฒ๋ง ์ฑ๊ณตํ๋ฉด ๋๋ฌด ์ฆ๊ฑฐ์ด ๋ง์์ผ๋ก ์ ์ ์์๊ฒ ๊ฐ๋ค!",
"music": {
"id": 1,
"music_title": "๊ทธ๋๋ง ์๋ค๋ฉด (์ฌ๋ฆ๋ ์ฐ๋ฆฌ X ๋๋์ปค๋ฅ์
(Nerd Connection))",
"artist": "๋๋์ปค๋ฅ์
(Nerd Connection)",
"genre": "๋ฐ๋ผ๋"
}
}
401
400
{'detail': 'Failed to get similar music from Flask'}
"""
partial = kwargs.pop('partial', True)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
print(serializer.data['content'])
response = request_music_from_flask(serializer.data['content'])
best_music = response.get('most_similar_song')
print(best_music)
similar_songs = response.get('similar_songs')
print(similar_songs)
if best_music:
music, created = Music.objects.get_or_create(music_title=best_music['title'], artist=best_music['artist'], genre=best_music['genre'])
instance.music = music
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response({'detail': 'Failed to get similar music from Flask'}, status=status.HTTP_400_BAD_REQUEST)
Emotion&Chat
views ์๋จ์ AI ์๋ฒ๋ก emotion label ๊ฒ์ถ๊ณผ ์์ ๋ฌธ๊ตฌ(comment) ๋ฅผ ์์ฑํ๋๋ก ์์ฒญํ๋ ํจ์๋ฅผ ์์ฑํ๋ค.
#views.py์ ์๋จ์ AI์๋ฒ๋ก ์์ฒญ์ํ๊ณ ์๋ต์ ๋ฐ๋ ํจ์ ์ถ๊ฐ
def request_emotion(content):
"""
์ผ๊ธฐ ๋ด์ฉ์ผ๋ก emootion label ๊ฒ์ถ
"""
flask_url = f'http://{settings.FLASK_URL}:5000/get_sentiment'
try:
response = requests.post(flask_url, json={'content': content},verify=False, timeout=50)
if response.status_code == 200:
response_data = response.json()
emotion_label = response_data['emotion_label']
print("Received emotion_label:", emotion_label)
time.sleep(2)
return emotion_label
else:
print("Failed to get emotion from Flask:", response.status_code)
return None
except Exception as e:
print("Error:", e)
time.sleep(10)
return None
def request_comment(content):
"""
์ผ๊ธฐ ๋ด์ฉ์ผ๋ก ์์ ๋ฌธ๊ตฌ ์์ฑ
"""
flask_url = f'http://{settings.FLASK_URL}:5000/get_comment'
try:
response = requests.post(flask_url, json={'content': content},verify=False, timeout=50)
if response.status_code == 200:
response_data = response.json()
comment = response_data['comment']
print("Received comment:", comment)
time.sleep(2)
return comment
else:
print("Failed to get comment from Flask:", response.status_code)
return None
except Exception as e:
print("Error:", e)
time.sleep(10)
return None
์ด๋ฅผ ๋ฐํ์ผ๋ก ViewSet๋ด๋ถ์ ํจ์๋ฅผ ์์ฑํ๋ค.
#ViewSet๋ด๋ถ์ create ์์ฑ
def create(self, request, *args, **kwargs):
"""
emotion_create ์ผ๊ธฐ ๋ด์ฉ์ผ๋ก ๊ฐ์ ๋ผ๋ฒจ, ์์๋ฌธ๊ตฌ ์์ฑ ํ๋ API
---
## ์์ request:
{
'diary' : 2
}
## ์์ response:
200
{
"id": 2,
"emotion_label": "๋ถ์",
"emotion_prompt": "",
"chat": " ์ด๋ณ์ ์ฌ์ค์ผ์ง๋ ๋ชจ๋ฅด๊ฒ ์ด์ ",
"diary": 2
}
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
diary = serializer.validated_data.get('diary')
if diary.user != request.user:
return Response({'error': "Diary does not belong to the current user."}, status=status.HTTP_400_BAD_REQUEST)
chat = request_comment(diary.content)
label = request_emotion(diary.content)
existing_emotion = Emotion.objects.filter(diary=diary).first()
if existing_emotion:
serializer = self.get_serializer(existing_emotion, data={'chat': chat, 'emotion_label': label}, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save(diary=diary, chat=chat, emotion_label = label)
else:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(diary=diary, chat=chat, emotion_label = label)
return Response(serializer.data, status=status.HTTP_201_CREATED)
Image
GPT - ์ด๋ฏธ์ง ์์ฑ์ฉ ํ๋กฌํํธ ์์ฑ
GPT API์ ๋ฏธ๋ฆฌ ์ ์ํ GPT ํ๋กฌํํธ๋ฅผ ํฉํ์ฌ GPT์ ์ด๋ฏธ์ง ์์ฑ์ฉ ํ๋กฌํํธ๋ฅผ ์์ฑํ๋ค.
from django.conf import settings
import openai
with open(f"{settings.BASE_DIR}/ai/genTextBase.txt", 'r', encoding='utf-8') as file:
base_text = ''.join(file.readlines())
api_key = settings.OPENAI_API_KEY
openai.api_key=api_key
def get_prompt(content):
"""
์ผ๊ธฐ ๋ด์ฉ์ ์
๋ ฅ๋ฐ์ ํ๋กฌํํธ ์์ฑ
"""
completion = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": f"{base_text} {content.strip()}"},]
)
generated_text = completion["choices"][0]["message"]["content"]
output_text=generated_text.split('\n')
pre_text = "(masterpiece,detailed), (Oil Painting:1.3), (Impressionism:1.3) ,(oil painting with brush strokes:1.2), (looking away:1.1), "
prompts = [pre_text+v for v in output_text if v]
return prompts
Flask&AI์๋ฒ - ์ด๋ฏธ์ง ์์ฑ ์์ฒญ
์ด๋ฏธ์ง ํ๋กฌํํธ๋ฅผ ์ ๋ฌํด AI์๋ฒ์์ ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๊ณ , ์ด๋ฏธ์ง url์ ์๋ต์ผ๋ก ๋ฐ๋๋ค.
def request_image_from_flask(prompt):
"""
์์ฑ๋ prompt๋ก ์ด๋ฏธ์ง ์์ฑ
"""
flask_url = f'http://{settings.FLASK_URL}:5000/get_image'
try:
# HTTP POST ์์ฒญ์ผ๋ก prompt๋ฅผ Flask์ ์ ์ก
response = requests.post(flask_url, json={'prompt': prompt},verify=False, timeout=150)
# ์๋ต ํ์ธ
if response.status_code == 200:
# ์ด๋ฏธ์ง ์์ฑ ์ฑ๊ณต
response_data = response.json()
image_url = response_data['image_url']
print("Received image url:", image_url)
time.sleep(2)
return image_url
else:
# ์ด๋ฏธ์ง ์์ฑ ์คํจ
print("Failed to get image from Flask:", response.status_code)
return None
except Exception as e:
print("Error:", e)
time.sleep(10)
return None
Viewset - createํจ์ ์์ฑ
์์์ ์์ฑํ ํจ์๋ค์ ํตํด ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๊ณ , ์ ๋ฌ๋ฐ์ url์ ํตํด ์ด๋ฏธ์ง ์๋ฆฌ์ผ๋ผ์ด์ ์์ฑ
def create(self, request, *args, **kwargs):
'''
์ด๋ฏธ์ง ์์ฑ API
---
### ์๋ต์ ์ต๋ 40์ด ์์ ๊ฐ๋ฅ
## ์์ request:
{
'diary' : 1
}
## ์์ response:
201
{
"id": 70,
"created_at": "2024-05-02T13:04:10.208658+09:00",
"image_url": "https://๋ฒํท์ฃผ์/images/826cb58e-46a3-41fc-9699-bc2eccdc1355.jpg",
"image_prompt": "(masterpiece,detailed), (Oil Painting:1.3), (Impressionism:1.3) ,(oil painting with brush strokes:1.2), (looking away:1.1), a girl in a traditional Korean hanbok, cherry blossom background, soft pastel colors, Korean artist reference, (ethereal:1.2), (delicate details:1.3), (dreamy atmosphere:1.25)",
"diary": 1
}
400
{
'error': "Failed to get image from Flask" ์ด ๊ฒฝ์ฐ AI ์๋ฒ๊ฐ ๊บผ์ ธ์์๋์
}
400
{
'error': "Error uploading image: {str(e)}"
}
401
403
'''
try:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
diary = serializer.validated_data.get('diary')
image_prompt = get_prompt(diary.content)[0]
image_url = request_image_from_flask(image_prompt)
if not image_url:
return Response({'error': "Failed to get image from Flask"}, status=status.HTTP_400_BAD_REQUEST)
new_image = Image.objects.get_or_create(diary=diary, image_url=image_url, image_prompt=image_prompt)
serializer.validated_data['diary'] = diary
serializer.validated_data['image_url'] = image_url
serializer.validated_data['image_prompt'] = image_prompt
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
except Exception as e:
return Response({'error': f"Error uploading image: {str(e)}"}, status=status.HTTP_400_BAD_REQUEST)
์ด๊ฒ์ผ๋ก ๋ฐฑ์๋ Django API๋ถ๋ถ์ ํต์ฌ ๊ธฐ๋ฅ์ ์ด๊ฒ์ผ๋ก ๊ตฌํ์ด ๋๋๋ค.
API๋ฌธ์ํ
API ๊ตฌํ์ด ๋๋ฌ๋ค๋ฉด ํ๋ก ํธ๋ก ๊ฐ API์ ๋ํ ์ ๋ณด๋ฅผ ๋๊ฒจ์ค์ผ ์์ ์ด ๊ฐ๋ฅํ๋ค.
์ด๊ฒ์ ์ฐ๋ฆฌ๋ Swagger๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํํ๋ค. ์ฅ๊ณ REST Framework์์ swagger๋ฅผ ํตํด API๋ฅผ ๋ฌธ์ํ ํด๋ณด์.
๋จผ์ drf-ysag์ ๋ํ ๋ฌธ์๋ ์๋์ ๋งํฌ๋ฅผ ํตํด ์ด๋ํ ์ ์๋ค. ์์ธํ ์ ๋ณด๋ฅผ ์ํ๋ค๋ฉด ๊ณต์ ๋ฌธ์๋ฅผ ํ์ฉํ์.
https://drf-yasg.readthedocs.io/en/stable/
install
โ
๊ฐ์ํ๊ฒฝ์ ํ์ฑํ ํ๊ณ install ํ์.
โ
pip install -U drf-yasg
โ
settings.py
โ
settings.py์ ๋ค์์ด ์ถ๊ฐ๋์ด์ผ ํ๋ค.
โ
INSTALLED_APPS = [
...
'django.contrib.staticfiles', # required for serving swagger ui's css/js files
'drf_yasg',
...
]
โ
urls.py
โ
์ด 4๊ฐ์ง์ ์๋ํฌ์ธํธ๋ฅผ ์ถ๊ฐํ ๊ฒ์ด๋ค.
A JSON view of your API specification at /swagger.json
A YAML view of your API specification at /swagger.yaml
A swagger-ui view of your API specification at /swagger/
A ReDoc view of your API specification at /redoc/
โ
ํ๋ก์ ํธ์ urls.py์ ๋ค์์ ์ถ๊ฐํ๋ค.
โ... from django.urls import re_path from rest_framework import permissions from drf_yasg.views import get_schema_view from drf_yasg import openapi โ ... โ schema_view = get_schema_view( openapi.Info( title="Snippets API", default_version='v1', description="Test description", terms_of_service="https://www.google.com/policies/terms/", contact=openapi.Contact(email="contact@snippets.local"), license=openapi.License(name="BSD License"), ), public=True, permission_classes=(permissions.AllowAny,), ) โ urlpatterns = [ path('swagger<format>/', schema_view.without_ui(cache_timeout=0), name='schema-json'), path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), ... ]
โํ์ธโ
runserver ํด์ฃผ๊ณ , /swagger/์ ์ ๊ทผํ๋ค.
โ
๊ทธ๋ผ ๋ค์์ฒ๋ผ api๋ฅผ ํ์ธํ ์ ์๋ค.
โ
Flask : AI๋ชจ๋ธ ์๋น
์ด๋ฒ์๋ GPU์๋ฒ์ ์ฌ๋ฆด Flask ๋ฅผ ์์ฑํ๋ค.
Flask์์๋ ๋ชจ๋ธ์ ๋ก๋ํ๊ณ , ๋ฐฑ์๋ API์ ํ์ํ AI์์ ์ ์ํํ๋ค.
GPU๋น์ฉ ๋ฌธ์ ๋ก, GPU๊ฐ ๊ผญ ํ์ํ ๊ฒฝ์ฐ๋ง ์ด ์๋ฒ์์ ๋์ํ๋ค.
์๊ฐ
Flask์์๋ ๋ค์์ ์ํํ๋ค.
โ
1. generate comment : ์ผ๊ธฐ ์์ฑ ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์์ ๋ฌธ๊ตฌ๋ฅผ ์์ฑํ๋ค.
2. generate image : ๋ฐฑ์๋๋ก๋ถํฐ ์ด๋ฏธ์ง ์์ฑ ํ๋กฌํํธ๋ฅผ ๋ฐ๊ณ , ํ๋๋ ๋ํจ์ ๋ชจ๋ธ์ ๋ก๋ํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋ค.
3. emotion classification : ์ผ๊ธฐ ์์ฑ ๋ด์ฉ์ ๋ฐํ์ผ๋ก ๊ฐ์ ๋ถ์ํ๋ค.
4. recommend music : ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ์ ํฌ๋กค๋ง์ ํตํด ์์งํ ์์
๋ฐ์ดํฐ์ ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฉํด ์ ์ฌ๋๋ฅผ ํตํ ์์
์ถ์ฒ, ๊ฒฐ๊ณผ ์ด 5๊ฐ์ง ๋ฐํ
โ
Image
โ
์ด๋ฏธ์ง๋ฅผ ์์ฑํ๊ธฐ ์ํ ํ๋กฌํํธ๋ฅผ ๋ฐ์์ ์ด๋ฏธ์ง ์์ฑ, S3๋ฒํท์ ์ ์ฅํ๊ณ , URL์ ๋ณด๋ฅผ ์๋ต์ผ๋ก ๋ฐํํ๋ค.
์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋ ํจ์๋ฅผ ๋จผ์ ๋ค๋ฅธ ํ์ผ์ ์์ฑํ์.
โ
#generate_image.py ์ ๋ชจ๋ธ์ ๋ก๋ํ๊ณ , ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋ ํจ์๋ฅผ ๋ง๋ค์๋ค.
from diffusers import StableDiffusionPipeline
import torch
โ
# ๋ชจ๋ธ ๋ก๋ ๋ฐ ๋๋ฐ์ด์ค ์ค์
model_path = '๋ชจ๋ธ ์์น' # FineTuning Model Path
pipe = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
device = torch.device('cuda')
pipe.unet.load_attn_procs(model_path)
pipe.to(device)
โ
# Negative prompt ์ค์
neg_prompt = '''FastNegativeV2,(bad-artist:1.0), (loli:1.2),
(worst quality, low quality:1.4), (bad_prompt_version2:0.8),
bad-hands-5,lowres, bad anatomy, bad hands, ((text)), (watermark),
error, missing fingers, extra digit, fewer digits, cropped,
worst quality, low quality, normal quality, ((username)), blurry,
(extra limbs), bad-artist-anime, badhandv4, EasyNegative,
ng_deepnegative_v1_75t, verybadimagenegative_v1.3, BadDream,
(three hands:1.1),(three legs:1.1),(more than two hands:1.4),
(more than two legs,:1.2),badhandv4,EasyNegative,ng_deepnegative_v1_75t,verybadimagenegative_v1.3,(worst quality, low quality:1.4),text,words,logo,watermark,
'''
โ
def get_image(prompt):
image = pipe(prompt, negative_prompt=neg_prompt,num_inference_steps=30, guidance_scale=7.5).images[0]
return image
โ
๋์ฒด๋ก 15์ด์ ๋ ์์๋๊ณ , ๋ฆ์ด์ง๋ฉด 30์ด ์ด๋ด๋ก ์์ฑ๋๋ค. ํ์ง๋ง ์ฌ์ค ๊ฝค ๊ธด ์๊ฐ์ด๋ฏ๋ก ์ด๋ฅผ ์ ๊ณ ๋ คํด์ ์๋ต์ ์ฃผ๊ณ ๋ฐ๋ ๋ฐฑ์๋ ์๋ฒ์์ ์๋ต ๋๊ธฐ ์๊ฐ์ 40์ด ์ ๋๋ก ๋๋ ค์ฃผ์.(์์ ์ฝ๋์ ์ฌ์ค ์ด๋ฏธ ์ ์ฉ๋์ด์๋ค)
โ
#app.py
โ
# S3์ ์ฐ๊ฒฐ
s3 = boto3.client('s3',
aws_access_key_id=S3_ACESS_KEY,
aws_secret_access_key=S3_SECRET_ACCESS_KEY)
โ
# ์ด๋ฏธ์ง ์์ฑ ์์ฒญ์ ๋ํ ์์
@app.route('/get_image', methods=['POST'])
async def process_image_request():
try:
# ๋ฐ์ ์์ฒญ์ ๋ฐ์ดํฐ๋ฅผ ํ์ธ
request_data = request.json
prompt = request_data.get('prompt')
#๋ฐ์ ํ๋กฌํํธ๋ก ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋ค.
image = get_image(prompt)
# ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ๋ค.
image_key = str(uuid.uuid4())
buffered = io.BytesIO()
image.save(buffered, format="JPEG")
buffered.seek(0)
s3.upload_fileobj(buffered, Bucket=S3_BUCKET_NAME, Key=f'images/{image_key}.jpg', ExtraArgs={'ContentType':'image/jpeg'})
image_url = f'https://{S3_BUCKET_NAME}.s3.{AWS_S3_REGION_NAME}.amazonaws.com/images/{image_key}.jpg'
buffered.close()
# ์ ์ฅ ํ ์ด๋ฏธ์ง์ URL์ ์๋ตํ๋ค.
return jsonify({'image_url': image_url}), 200
except Exception as e:
print("Exception occurred in process_request:", e)
return jsonify({"error": str(e)}), 500
โ
Comment
โ
ํ๋ก์ธ์ค๋ ์์ ๊ฐ๋ค. ๋ชจ๋ธ์ ๋ก๋ํ์ฌ ์์
์ ์ํํ๋ ํจ์๋ฅผ ํ๋ ๋ง๋ค๊ณ , API์์ฒญ์ ๋ํ ์์
์ app.py์ ์์ฑํ๋ค.
โ
#๋ชจ๋ธ ๋ก๋
#gpt model
print('gpt_load')
gpt_device = torch.device("cuda:0")
gpt_model = GPT2LMHeadModel.from_pretrained('๋ชจ๋ธ').to(gpt_device)
gpt_tokenizer = PreTrainedTokenizerFast.from_pretrained('๋ชจ๋ธ')
U_TKN = '<usr>'
S_TKN = '<sys>'
BOS = '</s>'
EOS = '</s>'
MASK = '<unused0>'
SENT = '<unused1>'
PAD = '<pad>'
โ
def get_comment(input_text): #koGPT2 ๋ชจ๋ธ์ ํ์ฉํ์ฌ ์
๋ ฅ๋ ์ง๋ฌธ์ ๋ํ ๋๋ต์ ์์ฑํ๋ ํจ์
q = input_text
a = ""
sent = ""
while True:
input_ids = torch.LongTensor(gpt_tokenizer.encode(U_TKN + q + SENT + sent + S_TKN + a)).unsqueeze(dim=0).to(gpt_device)
pred = gpt_model(input_ids)
pred = pred.logits
gen = gpt_tokenizer.convert_ids_to_tokens(torch.argmax(pred, dim=-1).squeeze().tolist())[-1]
if gen == EOS:
break
a += gen.replace("โ", " ")
return a
โ
# app.py
@app.route('/get_comment', methods=['POST'])
async def process_comment_request():
try:
request_data = request.json
content = request_data.get('content')
comment = get_comment(content)
return jsonify({'comment': comment}), 200
except Exception as e:
print("Exception occurred in process_request:", e)
return jsonify({"error": str(e)}), 500
โ
Emotion & Music
โ
def get_emotion_label(content):
emotion_pred = inference(content)
max_value = max(emotion_pred)
max_index = emotion_pred .index(max_value)
return emotion_pred, emotion_arr[max_index]
โ
โ
def get_music(content):
emotion_pred, max_index=get_emotion_label(content)
df_user_sentiment = pd.DataFrame([emotion_pred],columns=emotion_arr)
user_emotion_str = df_user_sentiment.apply(lambda x: ' '.join(map(str, x)), axis=1)
music_emotion_str = final_emotion[emotion_arr].apply(lambda x: ' '.join(map(str, x)), axis=1)
โ
tfidf = TfidfVectorizer()
user_tfidf_matrix = tfidf.fit_transform(user_emotion_str)
music_tfidf_matrix = tfidf.transform(music_emotion_str)
โ
cosine_sim = cosine_similarity(user_tfidf_matrix, music_tfidf_matrix)
most_similar_song_index = cosine_sim.argmax()
most_similar_song_info = final_emotion.iloc[most_similar_song_index]
โ
num_additional_recommendations = 4
similar_songs_indices = cosine_sim.argsort()[0][-num_additional_recommendations-1:-1][::-1]
similar_songs_info = final_emotion.iloc[similar_songs_indices]
โ
return most_similar_song_info, similar_songs_info
โ
@app.route('/get_sentiment', methods=['POST'])
async def process_sentiment_request():
try:
request_data = request.json
content = request_data.get('content')
_, emotion_label = get_emotion_label(content)
return jsonify({'emotion_label': emotion_label}), 200
except Exception as e:
print("Exception occurred in process_request:", e)
return jsonify({"error": str(e)}), 500
โ
@app.route('/get_music', methods=['POST'])
async def process_music_request():
try:
request_data = request.json
content = request_data.get('content')
most_similar_song_info, similar_songs_info = get_music(content)
response_data = {
'most_similar_song': {
'title': most_similar_song_info[0],
'artist': most_similar_song_info[1],
'genre': most_similar_song_info[2]
},
'similar_songs': [{
'title': song_info[0],
'artist': song_info[1],
'genre': song_info[2]
} for song_info in similar_songs_info.values]
}
return jsonify(response_data), 200
except Exception as e:
print("Exception occurred in process_request:", e)
return jsonify({"error": str(e)}), 500
GCP-VM์ ์ฌ๋ฆฌ๊ธฐ
์์ฑํ Flask๋ Github์ ์ฌ๋ฆฌ๊ณ , VM์ ์๋นํ๋๋ก ํ๋ค.