모델 간의 관계가 있을 때 Swagger 문서를 위한 시리얼라이저를 구성하는 방법에 대해서 기록해두고자 한다. 예제는 이전 글에서 만든 샘플 프로젝트에서 이어진다.
요약하자면, 관계가 있는 모델 끼리 시리얼라이저도 잘 묶어주면 된다.
우선 기존의 Music 모델과 관계가 있는 새로운 모델을 선언한다.
# 프로젝트_루트/api/models.py
# -----------------------------------------------------
# ...
class PlayList(models.Model):
id = models.BigAutoField(primary_key=True, verbose_name='play_list_id')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='만들어진 날짜')
updated_at = models.DateTimeField(auto_now=True, verbose_name='업뎃 된 날짜')
play_list_name = models.CharField(null=False, max_length=128, verbose_name='플레이리스트 이름')
music = models.ForeignKey(Music, related_name='music', db_column='music_id',
on_delete=models.CASCADE, verbose_name='곡')
class Meta:
managed = True
db_table = 'play_lists'
app_label = 'api'
verbose_name_plural = '나의 플레이 리스트'
새로운 모델을 위한 시리얼라이저를 작성한다. 여기서는 response 시에 Music에서 특정 필드만 보여주고 싶어서 새로운 시리얼라이저를 만들었다. 하는 김에 request body와 query string을 위한 시리얼라이저도 만들었다.
Music 시리얼라이저를 PlayList의 music 변수에 선언해주는 것이 핵심이다. music이라는 변수명을 사용했는데, 모델에서 FK 필드를 선언해준 변수명과 동일하다. 변수명을 다르게 작성할 경우 인식하지 못한다.
# 프로젝트_루트/api/serializers.py
# -----------------------------------------------------
# ...
# PlayListSerializer에서 music 필드를 위해 사용할 새로운 시리얼라이저를 만듦.
class MusicForPlayListSerializer(serializers.ModelSerializer):
class Meta:
music = Music.objects.all()
model = Music
fields = ('singer', 'title', 'category', 'star_rating',)
class PlayListSerializer(serializers.ModelSerializer):
# 만약 get_queryset을 사용하고 관계가 있는 모델을 사용할 경우,
# 시리얼라이저의 필드명은 모델에서 선언한 필드명과 동일하게 써줘야 함.
# 안 그러면 response 값에 시리얼라이저가 안 먹혀서 해당 필드는 안 보이게 됨.
music = MusicForPlayListSerializer(read_only=True)
# music = MusicSerializer(read_only=True) # Music의 모든 필드를 보이고 싶다면 이 시리얼라이저를 사용하면 됨.
class Meta:
play_list = PlayList.objects.all()
model = PlayList
fields = ('play_list_name', 'created_at', 'updated_at','music',) # 원하는 필드만 선언할 때, 관계가 있는 모델의 필드명을 써준다.
class PlayListQuerySerializer(serializers.Serializer):
title = serializers.CharField(help_text="곡 제목으로 검색", required=False)
singer = serializers.CharField(help_text="가수명으로 검색", required=False)
class PlayListBodySerializer(serializers.Serializer):
name = serializers.CharField(help_text="플레이 리스트 이름")
play_list = MusicBodySerializer(read_only=True)
플레이 리스트를 위한 view를 만들자. 어노테이션 사용법은 이전 글에서 다룬대로 해주면 된다.
# 프로젝트_루트/api/views.py
# -----------------------------------------------------
# ...
class PlayListViewSet(viewsets.GenericViewSet,
mixins.ListModelMixin,
View):
serializer_class = PlayListSerializer
@swagger_auto_schema(query_serializer=PlayListQuerySerializer)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
def get_queryset(self):
conditions = {
'play_list_name': self.kwargs.get("play_list_name", None),
'music__title__contains': self.request.data.get('title', None),
'music__singer__contains': self.request.data.get('singer', None),
}
conditions = {key: val for key, val in conditions.items() if val is not None}
play_list = PlayList.objects.filter(**conditions)
if not play_list.exists():
raise Http404()
return play_list
@swagger_auto_schema(request_body=PlayListBodySerializer)
def add(self, request):
conditions = request.data['play_list']
music = Music.objects.get(**conditions)
play_list = PlayList.objects.create(
play_list_name=request.data['name'],
music=music # instance를 넣어도 되고, id를 넣어도 됨.
)
play_list.save()
return Response(PlayListSerializer(play_list).data, status=status.HTTP_201_CREATED)
새로운 View를 위한 새로운 url을 추가해주자.
# 프로젝트_루트/api/urls.py
# -----------------------------------------------------
# ...
from django.urls import path
from django.conf import settings
from .views import *
urlpatterns = [
# ...
path("v1/play_list", PlayListViewSet.as_view({"get": "list", "post": "add"}), name="play_lists"),
path("v1/play_list/<str:play_list_name>", PlayListViewSet.as_view({"get": "list"}), name="play_list"),
]
완성이다. 서버를 켜서 Swagger 문서를 확인해보자.
response를 확인해보면 작성한대로 잘 나온다.
시리얼라이저를 작성하는 것이 노다가성이 짙다는 게 약간 흠인 것 같다. 모델이 많아지면 지저분해보이기도 한다. 더 좋은 방법을 찾을 수 있길.