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보다 더 간단했는데,
이번에도 역시 눈에 밟혔던
Concrete View class의 종류는 아래와 같은데,
go to definition
을 타고타고 들어가지 않으면 이해할 수 없는 엄청난 추상화를 거친 클래스들이다.
타고타고 들어가도 이해가 잘 안됨
genericAPIView
와 Mixins
를 합친 것 이상으로, 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 {}
CreateModelMixin
은 create
함수를 가지고 있는데,
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해서 사용한다.
알겠고, 그럼
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을 가져오는 것!
여기서 더 나아가,
로! 했으나,
drf
는 10시까지만! 하기로 했으므로,
한도끝도 없,,,
일단 오늘은 여기까지!