[DRF #7] The Complete Guide to Django REST framework

LILO Ghim·2022년 2월 8일
0

Competency Assessment Level Two


#permissions.py

from rest_framework import permissions

class IsAdminUserOrReadonly(permissions.IsAdminUser):

    def has_permission(self, request, view):
        is_admin = super().has_permission(request, view)
        return request.method in permissions.SAFE_METHODS or is_admin


#pagination.py

from rest_framework.pagination import PageNumberPagination

class SmallSetPagination(PageNumberPagination):
    	page_size = 30


#serializers.py

class QuoteSerializer(serializers.ModelSerializer):

    class Meta:
        	model = Quote
        	fields = "__all__"
        
#views.py

class QuoteListCreateAPIView(generics.ListCreateAPIView):
    	queryset = Quote.objects.all().order_by("id")
    	serializer_class = QuoteSerializer
    	permission_classes = [IsAdminUserOrReadonly]
    	pagination_class = SmallSetPagination

class QuoteDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
    	queryset = Quote.objects.all()
    	serializer_class = QuoteSerializer
    	permission_classes = [IsAdminUserOrReadonly]

level two 에서는 permission, pagination, Concrete View Class등을 사용해야 해서
더 복잡한 듯 하지만
nested relationships 같은 것이 없어서 오히려 level one보다 더 간단했는데,
이번에도 역시 눈에 밟혔던

perform_create()와 self.kwargs.get() Debugging


Concrete View class의 종류는 아래와 같은데,

  • CreateAPIView
  • ListAPIView
  • RetrieveAPIView
  • DestroyAPIView
  • UpdateAPIView
  • ListAPIView
  • ListCreateAPIView
  • RetrieveUpdateAPIView
  • RetrieveDestroyAPIView
  • RetrieveUpdateDestroyAPIView

go to definition을 타고타고 들어가지 않으면 이해할 수 없는 엄청난 추상화를 거친 클래스들이다.
타고타고 들어가도 이해가 잘 안됨

genericAPIViewMixins를 합친 것 이상으로, method들을 이미 내장하고 있기 때문에, views.py 코드만 봐서는 도대체 어떻게 돌아가는지 알 수가 없다.

Concrete View class의 상속


먼저, 사용한 APIView들의 상속 관계를 보면,

generics.ListCreateAPIVIew

class ListCreateAPIView(mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        GenericAPIView):
    """
    Concrete view for listing a queryset or creating a model instance.
    """
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

generics.RetrieveUpdateDestroyAPIView

class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
                                   mixins.UpdateModelMixin,
                                   mixins.DestroyModelMixin,
                                   GenericAPIView):
    """
    Concrete view for retrieving, updating or deleting a model instance.
    """
    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

그리고 대망의

generics.CreateAPIView -> mixins.CreateModelMixin -> mixins.
-> create


class CreateAPIView(mixins.CreateModelMixin,
                    GenericAPIView):
    """
    Concrete view for creating a model instance.
    """
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)
        
        
class CreateModelMixin:
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}

perform_create()

CreateModelMixincreate 함수를 가지고 있는데,

1. request.data를 받아서 
2. serializer를 태우고 
3. is_valid로 유효성 검사를 한 뒤 
4. perform_create 함수를 호출해서 데이터를 객체정보를 DB에 저장하고
5. 성공시, headers에 담기
6. Response로 data, status_code, header를 리턴해준다

알겠고,
그런데, 왜 ReviewCreateAPIView에서는 이런 코드가 나오냐는 것인데,


class ReviewCreateAPIView(generics.CreateAPIView):
   	   queryset = Review.objects.all()
   	   serializer_class = ReviewSerializer

       def perform_create(self, serializer):
           ebook_pk = self.kwargs.get("ebook_pk")
           ebook = get_object_or_404(Ebook, pk=ebook_pk)
           permission_classes = [permissions.IsAuthenticatedOrReadOnly]

           review_author = self.request.user

           review_queryset = Review.objects.filter(ebook=ebook, review_author=review_author)

           if review_queryset.exists:
               raise ValidationError("You Have Already Reviewd This Ebook!")

           serializer.save(ebook = ebook, review_author=review_author)

위 api는 리뷰를 등록(request.data)하면서 ebook pk, review_author와 같이 추가로 저장해야 하는 데이터(fk로 연결된 데이터와 같이)가 있을 경우에는 override해서 사용한다.

알겠고, 그럼

self와 kwargs???


ebook_pk = self.kwargs.get("ebook_pk")

이 코드가 그렇게 어려울 일이냐 흫


아주 옛날 옛적 어느 주말,
(self, request, **arg, **kwargs)에 집착하며,
그림선생님을 붙잡고 저게 없어도 데이터가 나오는데 왜 있는 거냐고,
도저히 이해가 안갔었는데, 대답도 이해가 안됐었 흫
그리고 여러 명의 동기들이 또 나에게 물어봤었는데
아마 그들도 내 대답으로는 이해가 안됐었겠지?


어쨌든 저것을 이해하기 위해선 GenericAPIView의 속성인 lookup_field의 이해가 먼저이고,
기본값이 pk라는 것을,
내맘대로 커스텀 할 수 있으며, (그렇다고 또 인스턴스에 추가해주면 또 안됨)
crazy debugging을 통해 알게 되었다.


kwargs는 어디로 부터 오는가

class가 아닌 serializer에 lookup_field를 추가하고(==객체 조회에 사용되는 field),
parameter를 변경하면
pk가 아니라 author로 ebook을 가져오는 것!


여기서 더 나아가,


kwargs와 lookup_field를 더 뒤져 보기!!!


로! 했으나,

drf는 10시까지만! 하기로 했으므로,
한도끝도 없,,,
일단 오늘은 여기까지!

profile
킴릴로

0개의 댓글