[WatchaPedia Clone] Day 6. Archive(아카이빙 기능) views.py

jaylight·2020년 12월 22일
2

WatchaPediaClone

목록 보기
5/7
post-custom-banner

archive 기능, views.py 구성

Archive의 CRUD 기능은 전반적으로 user의 follow/following 기능과 유사하지만, follow/following이 user 테이블 하나만 셀프 참조하는 반면, Archive 기능은 user와 content를 ManyToMany를 연결하는 점이 다르다. 사실 이런 점에서는 Archive 기능이 구현하는데 좀 더 직관적으로 수월한 면이 있다고 할 수 있다.

Archive에서 CRUD 기능에 대해 각각 소개하면

  • Create (post)
    Access_Token을 통해 로그인한 유저(user)의 신원을 확인하여, URL로 Archive의 대상이 되는 영화의 Primary Key를 int로 받는다. 만일 기존에 로그인 유저가 해당 영화를 Archive 해 둔 기록이 DB에 존재한다면 "ALREADY_EXIST" 메시지와 400 status code를 반환한다.
    그리고 위의 중복을 거르는 조건을 통과한다면, body를 통해 Archive 유형의 id값(int)로 받아서 Archive에 user, content, archive_type 요소를 넣어 새로운 데이터를 생성한다.
@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)
  • Read (get)
    Create와 동일하게 Access_Token을 통해 로그인한 유저의 신원 확인 한다. URL로 컨텐츠의 PK 값을 조회하여 해당 유저가 영화를 아카이브한 데이터가 없을 경우, "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)
  • Update (patch)
    Access_Token을 통해 확인한 현재 로그인한 유저 확인 후, URL을 통해 전달된 content_pk를 통해 확인한 컨텐츠를 기존 Archive해 둔 기록이 없다면 "INVALD_ARCHIVE" 메시지와 400 status code를 반환한다.
    Archive 기록이 있는 것을 확인했다면 기존 Archive한 유형이 새로 body에 담겨져서 들어온 Archive_Type과 다른지 확인하고 같다면 "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)
  • Delete (delete)
    Access_Token과 URL을 통해 전달받은 content_pk를 통해 유저와 컨텐츠를 각각 식별한 뒤, 기존 Archive DB에 해당 유저가 콘텐츠를 Archive한 기록이 있다면 삭제한다.
@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 조회(Read) 기능

컨텐츠별 Rating 조회 기능은 특정 컨텐츠의 상세 페이지에 접속했을 때, 해당 컨텐츠의 평균 별점과 전체 별점 평가 데이터를 반환하는 API이다.

아래 API를 구현하면서 처음으로 select_relatedprefetch_related에 대해 공부하고 적용시키고자 했는데, ratings를 따로 역참조하는 변수는 없었기 때문에 본 API에서는 select_related 기능만을 사용했다.

아래 API의 작동 과정은 content의 PK를 URL을 통해 전달 받아 해당 컨텐츠에 대한 ratings를 기록을 필터링을 걸어 역참조로 조회하여, rating 평가 유저, rating 별점 등의 전체 기록을 리스트에 담아 반환하다.

전체 기록을 순회하면서 데이터를 리스트에 담는 과정에서 해당 rating이 참조하는 user와 content 데이터를 조회하기 위해 반복적으로 여러번 쿼리를 날리며 DB에 접근하게 되는데 이를 방지하기 위해 전체 ratings 기록을 조회하면서 select_Related를 활용하여 애초에 역참조로 접근하는 usercontent 데이터를 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 조회(Read) 기능

유저별 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)
post-custom-banner

1개의 댓글