정확한 프로젝트 기획을 위해 사용자 기반의 요구사항 명세서를 먼저 작성해보았다.

pip install django
python -m startproject Fairy-Taiary
python manage.py startapp diary
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'diary'
]
Django Rest Framework의 약자로 Django를 기반으로 REST API 서버를 만들기 위한 라이브러리
DRF로는 CRUD(Create, Read, Update, Delete)를 구현하기 쉽기 때문에 DRF를 사용해보았다.
Django REST Freamework를 사용하면 ViewSet을 사용해 클래스 안에서 관련있는 View들에 대한 로직을 합칠 수 있다.
Django에서 View란 Client에서 보낸 Request에 대해서 Response을 보내주는 역할을 한다. 세부적으로는 View는 Django 필요한 데이터를 모델에서 가져와서 적절히 가공하여 웹 페이지 결과를 만들도록 한다.
from rest_framework import mixins, status
from rest_framework.decorators import action
from rest_framework.viewsets import GenericViewSet
from rest_framework.generics import get_object_or_404
class DiaryViewSet(GenericViewSet,
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin):
permission_classes = [IsOwner]
serializer_class = DiarySerializer
queryset = Diary.objects.all()
"""
Diary 작성에 관한 ViewSet
"""
def filter_queryset(self,queryset):
queryset = queryset.filter(user=self.request.user)
return super().filter_queryset(queryset)
def list(self, request, *args, **kwargs):
"""
diary_list 작성한 일기 목록 조회 API
---
"""
return super().list(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
"""
diary_create 오늘의 일기를 작성하는 API
---
## 예시 request:
{
"user": 1,
"title": "일기 제목",
"content": "일기 내용",
"is_open": true
}
## 예시 response:
201
{
"id": 1,
"user": 1,
"title": "일기 제목",
"content": "일기 내용",
"is_open": true,
"registered_at": "2024-05-03T10:00:00Z",
"last_update_at": "2024-05-03T10:00:00Z"
}
400
{'detail': '오늘은 이미 일기를 작성했습니다.'}
"""
now = datetime.now()
date = now.date() #날짜 설정
diary_exists = Diary.objects.filter(user=request.user, registered_at__date=date).exists()
if diary_exists:
return Response({'detail': '오늘은 이미 일기를 작성했습니다.'}, status=status.HTTP_400_BAD_REQUEST)
else:
return super().create(request, *args, **kwargs)
def retrieve(self, request, *args, **kwargs):
'''
diary_read 작성한 일기의 내용을 조회하는 API
---
'''
return super().retrieve(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
'''
diary_update 이미 작성한 일기의 내용을 수정하는 API
---
이떄 이미지, emotion은 결과 삭제됨. 재생성 해야함.
이미지 생성 : post image/
emotion&응원문구 분석: post emotion/
music의 경우 diary_music으로 재추천 해야 함 : put /diary_music/{일기id}
'''
instance = self.get_object()
emotions = Emotion.objects.filter(diary=instance)
images = Image.objects.filter(diary=instance)
emotions.delete()
images.delete()
return super().update(request, *args, **kwargs)
def partial_update(self, request, *args, **kwargs):
'''
diary_partial_update 이미 작성한 일기의 내용을 수정하는 API
---
이떄 이미지, emotion은 결과 삭제됨. 재생성 해야함.
이미지 생성 : post image/
emotion&응원문구 분석: post emotion/
music의 경우 diary_music으로 재추천 해야 함 : put /diary_music/{일기id}
'''
return super().partial_update(request, *args, **kwargs)
def destroy(self, request, *args, **kwargs):
'''
diary_delete 작성한 일기를 삭제하는 API
---
'''
return super().destroy(request, *args, **kwargs)
django framework 에서 데이터모델을 만들어주는 역할을 하고 있는 파일이다.
models.py 파일안에 클래스형으로 데이터 모델을 만들어주면 장고가 ORM(object-oriented-mapping)을 통해 데이터베이스에 데이터 모델을 생성해준다.
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'
이 python 파일은 app 폴더에 직접 추가해야 한다.
serializer는 직렬화라는 뜻을 가진 단어. django에 저장되어 있는 모델 인스턴스를 REST API에서 사용하는 JSON의 형태로 바꿔주는 것을 말한다.
우리는 그중에서도 가장 간단한 ModelSerializer를 사용하였다.
from rest_framework import serializers
from .models import Diary
from recommend_music.models import Music
from recommend_music.serializers import MusicSerializer
from books.serializers import BookSerializer
class DiarySerializer(serializers.ModelSerializer):
class Meta:
model = Diary
fields = ['id','user','title','content','registered_at','last_update_at', 'is_open']
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
class DiaryAdminSerializer(serializers.ModelSerializer):
music = MusicSerializer(required=False)
book = BookSerializer(required=False)
class Meta:
model = Diary
fields = '__all__'
django manage.py startapp books
from rest_framework import mixins
from rest_framework.generics import get_object_or_404
from rest_framework.viewsets import GenericViewSet
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework import status
from .models import *
from diaries.models import Diary
from diaries.serializers import *
from fairy_tairy.permissions import *
from .serializers import *
'''
다른 파일에서 함수들 불러오기
'''
class BookViewSet(GenericViewSet,
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin):
"""
일기를 하나로 묶어주는 Book(표지) API
"""
permission_classes = [IsOwner]
serializer_class = BookSerializer
queryset = Book.objects.all()
def list(self, request, *args, **kwargs):
"""
책(표지)의 list를 불러오는 API
---
"""
return super().list(request, *args, **kwargs)
def retrieve(self, request, *args, **kwargs):
"""
book의 id를 통해 책의 표지 정보 조회하는 API
---
일기를 하나로 묶어주는 Book(표지) API
"""
return super().retrieve(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
"""
book의 id를 통해 책의 표지를 생성하는 API
---
일기를 하나로 묶어주는 Book(표지) API
"""
return super().create(request, *args, **kwargs)
def partial_update(self, request, *args, **kwargs):
"""
책의 표지 정보를 업데이트하는 API
---
일기를 하나로 묶어주는 Book(표지) API
"""
return super().partial_update(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
"""
책의 표지 정보를 업데이트하는 API
---
일기를 하나로 묶어주는 Book(표지) API
"""
return super().update(request, *args, **kwargs)
class PageViewSet(GenericViewSet,
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin):
permission_classes = [IsOwner]
serializer_class = PageSerializer
queryset = Page.objects.all()
def filter_queryset(self,queryset):
queryset = queryset.filter(diary__user=self.request.user)
return super().filter_queryset(queryset)
def destroy(self, request, *args, **kwargs):
"""
책과 일기 사이의 연결 끊는 API
---
"""
instance = self.get_object()
instance.delete()
return Response(status=status.HTTP_200_OK)
def retrieve(self, request, *args, **kwargs):
"""
페이지의 ID로 책의 페이지를 조회하는 API
---
### id : Page의 ID
"""
return super().retrieve(request, *args, **kwargs)
def create(self, request, pk=None):
"""
일기를 책과 연결함
---
(일기,책)이 unigue해야 함
## 예시 request:
{
"user":1,
"book": 1,
"diary": 100
}
## 예시 response:
201
{
"id": 6,
"order": 2,
"user": 1,
"book": 1,
"diary": 100
}
404
"""
# 새 페이지를 만들고 요청된 데이터로 초기화.
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
# 연결할 일기와 책의 정보를 가져옴
diary_id = request.data.get('diary')
book_id = request.data.get('book')
user = request.user
diary = get_object_or_404(Diary, id=diary_id, user=request.user)
book = get_object_or_404(Book, id=book_id, user=request.user)
page = serializer.save(diary=diary, book=book, user=user)
# page = serializer.instance
# last_page = Page.objects.filter(book=book).order_by('-order').first().order
# page.diary = diary
# page.book = book
# # page.order = last_page+1
# page.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
from django.db import models
from django.conf import settings
from diaries.models import Diary
from django.db.models.signals import pre_save, post_delete
from django.dispatch import receiver
# Create your models here.
class Book(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
book_title = models.CharField(max_length = 30)
author = models.CharField(max_length = 30)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
is_open = models.BooleanField(default=False)
class Meta:
managed = True
db_table = 'book'
class Page(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
book=models.ForeignKey(Book, on_delete=models.CASCADE)
diary=models.ForeignKey(Diary, on_delete=models.CASCADE)
order = models.PositiveIntegerField(default=0, blank=False, null=False)
class Meta:
unique_together = ('book', 'diary')
ordering = ['order'] # 순서대로 정렬
managed = True
db_table='page'
def save(self, *args, **kwargs):
if not self.order:
# 새 페이지에 대한 순서를 설정합니다.
last_page = Page.objects.filter(book=self.book, diary=self.diary).order_by('-order').first()
if last_page:
self.order = last_page.order + 1
else:
self.order = 1
super(Page, self).save(*args, **kwargs)
책-일기 를 ManyToMany(다대다 관례)로 처리하려고 했다. 책 하나에 여러 일기가 들어갈 수 있고 하나의 일기가 여러 책에 포함될 수 있기 때문이다.
허나 ManyToMany는 수정이 불가능해 실무에서는 절대 사용하지 말아야한다는 글을 보고 '중간 테이블 생성' 법으로 이를 해결하기로 했다.
중간 테이블 생성
중간 테이블은 예를 들어 책과 일기가 있을 때 이들을 직접 다대다로 연결하는 것이 아닌 그 중간에 테이블을 만들어주는 것을 의미한다.
책 - 페이지 - 일기
이런 식으로 중간 테이블을 만드는 것이다.
책과 페이지를 다대일. 페이지와 일기를 일대다. 로 만들어서 사실상의 다대다로 만들어주는 것.
예를 들어 Page 모델에 book과 diary 속성에 집중해보자.
book도 diary도 현재 Foreign Key (외래키)로 설정되어 각각 diary와 book 모델에서 불러오고 있다.
만약 1번 책에 1번 일기를 넣고 싶으면 이런식으로 page 테이블에 등록할 수 있다.
현재 page 테이블에는 1번 책에 1번 일기와 100번 일기가 들어있다는 정보가 포함되어 있다.

from rest_framework import serializers
from .models import Book, Page
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
class PageSerializer(serializers.ModelSerializer):
class Meta:
model = Page
fields = '__all__'
books의 serializer는 field를 all로 간단히 설정했다.
사실상 백엔드만 담당하여 프론트엔드는 적지 않도록 하겠습니다.