Archive의 CRUD 기능은 전반적으로 user의 follow/following
기능과 유사하지만, follow/following
이 user 테이블 하나만 셀프 참조하는 반면, Archive 기능은 user와 content를 ManyToMany를 연결하는 점이 다르다. 사실 이런 점에서는 Archive 기능이 구현하는데 좀 더 직관적으로 수월한 면이 있다고 할 수 있다.
Archive에서 CRUD 기능에 대해 각각 소개하면
"ALREADY_EXIST"
메시지와 400 status code를 반환한다.@id_auth
def post(self, request, content_pk):
try:
data = json.loads(request.body)
user = request.user
content = Content.objects.get(id=content_pk)
archivetype = ArchiveType.objects.get(id=data['archive_type'])
if user.archived_contents.filter(content_id = content_pk).exists():
return JsonResponse({"message": "ALREADY_EXIST"}, status = 400)
Archive.objects.create(user = user, content = content, archive_type = archivetype)
return JsonResponse({"message": "SUCCESS"}, status = 200)
except json.JSONDecodeError as e:
return JsonResponse({"message": f"{e}"}, status = 400)
except Content.DoesNotExist:
return JsonResponse({"message": "INVALID_CONTENT"}, status = 400)
except ArchiveType.DoesNotExist:
return JsonResponse({"message": "INVALID_ARCHIVETYPE"}, status = 400)
"archive_type"
에 빈 스트링 값을 반환하고, 데이터가 있을 경우 해당 유저 - 영화의 Archive 타입 (wish, watching, not_interseted) 중 하나를 반환한다.@id_auth
def get(self, request, content_pk):
try:
user = request.user
if user.archived_contents.filter(content = content_pk).exists():
archive = Archive.objects.get(user = user, content = content_pk)
return JsonResponse({"archive_type" : archive.archive_type.name}, status = 200)
return JsonResponse({"archive_type" : ''}, status = 200)
except Archive.DoesNotExist:
return JsonResponse({"message": "INVALID_ARCHIVE"}, status = 400)
"INVALD_ARCHIVE"
메시지와 400 status code를 반환한다."SAME_ARCHIVETYPE"
메시지와 400 status code를 반환한다. 위의 조건들을 통과한다면 기존 로그인한 유저가 URL을 통해 확인한 컨텐츠를 Archive한 Type을 전달받은 값으로 변경한다.@id_auth
def patch(self, request, content_pk):
try:
data = json.loads(request.body)
user = request.user
content = Content.objects.get(id = content_pk)
archivetype = ArchiveType.objects.get(id = data['archivetype'])
if Archive.objects.filter(user = user, content = content).exists():
patch_object = Archive.objects.get(user = user, content = content)
if patch_object.archive_type == archivetype:
return JsonResponse({"message": "SAME_ARCHIVETYPE"}, status = 400)
patch_object.archive_type = archivetype
patch_object.save()
return JsonResponse({"message": "ARCHIVE_UPDATED"}, status = 200)
return JsonResponse({"message": "INVALID_ARCHIVE"}, status = 400)
except json.JSONDecodeError as e:
return JsonResponse({"message": f"{e}"}, status = 400)
except Content.DoesNotExist:
return JsonResponse({"message": "INVALID_CONTENT"}, status = 400)
except ArchiveType.DoesNotExist:
return JsonResponse({"message": "INVALID_ARCHIVETYPE"}, status = 400)
@id_auth
def delete(self, request, content_pk):
try:
user = request.user
content = Content.objects.get(id = content_pk)
Archive.objects.get(user = user, content = content).delete()
return JsonResponse({"message": "NO_CONTENT"}, status = 204)
except Content.DoesNotExist:
return JsonResponse({"message": "INVALID_CONTENT"}, stauts = 400)
except Archive.DoesNotExist:
return JsonResponse({"message": "INVALID_ARCHIVE"}, status = 400)
컨텐츠별 Rating 조회 기능은 특정 컨텐츠의 상세 페이지에 접속했을 때, 해당 컨텐츠의 평균 별점과 전체 별점 평가 데이터를 반환하는 API이다.
아래 API를 구현하면서 처음으로 select_related
와 prefetch_related
에 대해 공부하고 적용시키고자 했는데, ratings를 따로 역참조하는 변수는 없었기 때문에 본 API에서는 select_related
기능만을 사용했다.
아래 API의 작동 과정은 content의 PK를 URL을 통해 전달 받아 해당 컨텐츠에 대한 ratings를 기록을 필터링을 걸어 역참조로 조회하여, rating 평가 유저, rating 별점 등의 전체 기록을 리스트에 담아 반환하다.
전체 기록을 순회하면서 데이터를 리스트에 담는 과정에서 해당 rating이 참조하는 user와 content 데이터를 조회하기 위해 반복적으로 여러번 쿼리를 날리며 DB에 접근하게 되는데 이를 방지하기 위해 전체 ratings 기록을 조회하면서 select_Related
를 활용하여 애초에 역참조로 접근하는 user
와 content
데이터를 JOIN으로 한꺼번에 불러와 활용함으로써 여러번 쿼리를 날리는 것을 방지한다.
class ContentRatingView(View):
def get(self, request, content_pk):
try:
content = Content.objects.get(id = content_pk)
ratings = content.rating_users.all().select_related('user', 'content')
results = [{
"id" : rating.id,
"user_id" : rating.user_id,
"user" : rating.user.username,
"content" : rating.content.title_korean,
"rating" : rating.rating
} for rating in ratings]
average_rating = ratings.aggregate(Avg('rating'))
return JsonResponse({"result": results, "avg_rating": average_rating['rating__avg']}, status = 200)
except Content.DoesNotExist:
return JsonResponse({"message": "INVALID_CONTENT"}, status = 400)
또한 aggregate
함수를 활용하여 해당 영화의 전체 rating
점수를 평균을 내어서 별도로 함께 반환한다.
유저별 Rating, Archive 기능은 해당 유저의 개인 페이지에 접근했을 때 유저가 그동안 평가한 영화 기록들과 Archive 기록을 조회하기 위해 구현했다.
상단에서 영화별 Rating 기록을 조회한 기능과 유사하게 select_related
를 통해, 데이터 반환을 위한 리스트를 만드는 과정에서 여러번 DB에 접근하는 것을 방지했다.
class UserArchiveView(View):
def get(self, request, user_pk):
try:
data = json.loads(request.body)
user = User.objects.get(id = user_pk)
archivetype = ArchiveType.objects.get(id = data['archive_type'])
if Archive.objects.filter(user = user, archive_type = archivetype).exists():
archives = Archive.objects.filter(user = user, archive_type = archivetype).select_related('content')
results = [{
"id" : archive.id,
"content_id" : archive.content.id,
"content" : archive.content.title_korean,
"updated_at" : archive.updated_at,
} for archive in archives]
return JsonResponse({"result": results}, status = 200)
return JsonResponse({"result": []}, status = 200)
except json.JSONDecodeError as e:
return JsonResponse({"message": f"{e}"}, status = 400)
except User.DoesNotExist:
return JsonResponse({"message": "INVALID_USER"}, status = 400)
except ArchiveType.DoesNotExist:
return JsonResponse({"message": "INVALID_ARCHIVETYPE"}, status = 400)
class UserRatingView(View):
def get(self, request, user_pk):
try:
user = User.objects.get(id = user_pk)
if Rating.objects.filter(user = user).exists():
ratings = Rating.objects.filter(user = user).select_related('content')
results = [{
"id" : rating.id,
"content_id" : rating.content.id,
"content" : rating.content.title_korean,
"rating" : rating.rating,
"updated_at" : rating.updated_at,
} for rating in ratings]
return JsonResponse({"result": results}, status = 200)
return JsonResponse({"result": []}, status = 200)
except User.DoesNotExist:
return JsonResponse({"message": "INVALID_USER"}, status = 400)