📌 이 포스팅에서는 평균 평점 기준으로 영화 리스트를 제공하는 API를 만드는 과정에 대해 정리하였습니다.
✔️ 왓챠피디아는 서비스 핵심이 영화에 대한 선호이기 때문에 모든 영화의 제공을 평균 평점순으로 제공하기로 했다.
✔️ 여기서 "모든" 이란 의미는 영화에 대한 리스트를 제공할 때, 이 영화리스트가 평점 순위의 순서를 가진다는 뜻이다.
✔️ 처음에는 페이지에 출력되는 모든 Data를 한번에 응답해야하는 줄 알았는데, 프론트에서 요청을 보낼 때 여러번의 fetch가 가능한 것을 배웠다.
✔️ 이에 영화 리스트를 제공하는 API를 만들고, 어떤 요청이 들어오든 이를 평점 순으로 제공하기로 했다.
✔️ 아래 코드는 프론트쪽에서 한페이지를 구성함에 있어 여러번의 fetch를 요청할 수 있는지 알지 못했을 때 작성한 코드이다.
✔️ 페이지에 보이는데로 필요로한 모든 정보를 담아 한번에 응답하는 방식인데 코드가 무척이나 길어질 수 밖에 없다.
✔️ 뿐만아니라, 다른 영화리스트를 요청하고 싶을 땐 또 다시 이에 대한 코드를 작성해야하기 때문에 확장성이 줄어든다.
class MovieListView(View): def get(self, request): BOXOFFICE = "박스오피스" WATCHA = "왓챠" NETFLIX = "넷플릭스" boxoffice_movies = ( Movie.objects.filter(sources__name=BOXOFFICE) .annotate(average_point=Avg("rating__rate")) .order_by("-average_point") )[OFFSET:LIMIT] box_category = {} box_res = [ { "id": movie.id, "title": movie.title, "poster_image_url": movie.poster_image_url, "released_at": movie.released_at, "country": movie.country, "ratings": movie.average_point, "sources": [source.name for source in movie.sources.all()], } for movie in boxoffice_movies ] box_category["category"] = BOXOFFICE box_category["feeds"] = box_res watcha_movies = ( Movie.objects.filter(sources__name=WATCHA) .annotate(average_point=Avg("rating__rate")) .order_by("-average_point") )[OFFSET:LIMIT] watcha_category = {} watcha_res = [ { "id": movie.id, "title": movie.title, "poster_image_url": movie.poster_image_url, "released_at": movie.released_at, "country": movie.country, "ratings": movie.average_point, "sources": [source.name for source in movie.sources.all()], } for movie in watcha_movies ] watcha_category["category"] = WATCHA watcha_category["feeds"] = watcha_res netflix_movies = ( Movie.objects.filter(sources__name=NETFLIX) .annotate(average_point=Avg("rating__rate")) .order_by("-average_point") )[OFFSET:LIMIT] netflix_category = {} netflix_res = [ { "id": movie.id, "title": movie.title, "poster_image_url": movie.poster_image_url, "released_at": movie.released_at, "country": movie.country, "ratings": movie.average_point, "sources": [source.name for source in movie.sources.all()], } for movie in netflix_movies ] netflix_category["category"] = NETFLIX netflix_category["feeds"] = netflix_res result.append(box_category) result.append(watcha_category) result.append(netflix_category) return JsonResponse({"message": result}, status=200)
✔️ 이에 여러 요청에 구애받지 않고, 필요한 영화리스트르 목록을 평점순으로 제공할 수 있는 API를 만들어보기로 하였다.
✔️ 코드가 훨씬 간결해지고, 검색에도 같은 View를 사용할 수 있기 때문에 이전보다 확장성을 갖출 수 있게 되었다.
✔️ 특히, 여러가지 조건을 가지고 filter를하기 위해 Q객체를 이용했는데, 이를 OR 또는 AND와 함께 사용하면서 조건을 제어할 수 있었다.
✔️ 이 과정에서 프론트에서 무비리스트의 수량을 조절할 수 있는 OFFSET과 LIMIT을 함께 사용하였는데, 별다른 요청이 없으면 기본값이 15개를 제공하는 방식이다.
from django.http import JsonResponse from django.views import View from django.db.models import Q, Avg from .models import * class MovieListView(View): def get(self, request): source = request.GET.get("source") rating = request.GET.get("rating") keyword = request.GET.get("keyword") OFFSET = int(request.GET.get("offset", 0)) LIMIT = int(request.GET.get("display", 15)) category = { "박스오피스": "박스오피스 영화 순위", "왓챠": "왓챠 영화 순위", "넷플릭스": "넷플릭스 영화 순위", "평균별점": "평균별점이 높은 작품", } result = {} q = Q() if source: q.add(Q(sources__name=source), q.AND) result["category"] = category[source] if keyword: q.add(Q(staffs__name__icontains=keyword) | Q(title__icontains=keyword), q.AND) if rating: q = Q() result["category"] = category[rating] movies = ( Movie.objects.filter(q) .annotate(average_point=Avg("rating__rate")) .order_by("-average_point")[OFFSET : OFFSET + LIMIT] ) result["movies"] = [ { "id": movie.id, "title": movie.title, "poster_image_url": movie.poster_image_url, "released_at": movie.released_at, "country": movie.country, "ratings": round(movie.average_point, 1) if movie.average_point != None else 0, "sources": [source.name for source in movie.sources.all()], } for movie in movies ] return JsonResponse({"message": result}, status=200)
✔️ 이를 바탕으로 API명세서를 만든 결과는 아래와 같다.