1. mixins
믹스인이란 특정한 클래스에 상속을 통해 새로운 속성이나 새로운 기능을 추가(Mix in)하는 것을 의미합니다. 이를 잘 활용한 오픈소스가 바로 Django Rest Framework이다. Django Rest Framework에서는 GenericAPIView에 생성, 수정 등의 다양한 믹스인 클래스를 추가해 쉽게 API View를 구현할 수 있게 해준다.
Django Rest Framework 공식문서에서 제공하는 예제를 봐보면 ListModelMixin과 CreateModelMixin을 상속 받음으로써 get 메소드와 post 메소드에서 list와 create라는 메소드를 사용할 수 있다. 이를 통해 개발자들은 본인들이 원하는 기능을 보다 쉽고 간편하게 이용할 수 있다.
📃 rest_framework/mixins.py
아래의 Class들을 사용하기 위해서는 다음과 같이 import를 필요로 합니다.
from rest_framework.mixins import (
ListModelMixin, CreateModelMixin, UpdateModelMixin)
📃 rest_framework/viewsets.py
아니면 viewsets 파일 내 ViewSet 종류들 중 원하는 형태를 import해 사용하는 것도 가능합니다.
from rest_framework.viewsets import ModelViewSet
1. .ListModelMixin
쿼리 집합 나열
method : GET
주로 쓰는 형태는 다음과 같습니다.
def list(request, *args, **kwargs)
위의 코드만으로 View가 만들어질 수 있는 것은 Django에서 제공하는 ListAPIView를 상속받았기 때문이다.
그럼 이 간단한 형태를 어떻게 쓸 수 있는지 한 번 살펴봐보자.
📃 rest_framework/generics.py
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
이 코드를 간단히 요약하면, 사용자가 GET 요청을 보낼 경우, list()라는 함수를 실행시킨다는 내용입니다.
그렇다면 list() 함수는 어딨는지, 어떻게 작동하는지 확인해보려면 restframework/mixins.py에 구현되어 있는 ListModelMixin class입니다.
📃 rest_framework/mixins.py
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset()) # 1번
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True) # 2번
return Response(serializer.data) # 3번
먼저 한 줄씩 살펴봐보자. 먼저 1번을 살펴보면
📃 1번
queryset = self.filter_queryset(self.get_queryset())
filter_queryset과 get_queryset은 함수명에서 볼 수 있듯이 queryset을 가져온다는 것을 알 수 있습니다. 이 query는 어디서 가져오는걸까?
📃 view.py
class DongListView(ListAPI):
serializer_class = DongSerializer
queryset = Dong.objects.all()
해당 클래스를 사용하고 queryset = model.objects.all()와 같이 호출하게 되면 방금 본 1번의 코드가 작동하는 것입니다.
📃 2번
serializer = self.get_serializer(queryset, many=True)
여기 역시 알지 못하는 함수의 반환값이 변수에 넘겨지고 있는 것을 볼 수 있습니다.
get_serializer의 이름에서 알 수 있듯이 serializer를 가져오는 함수를 의미합니다. 이 또한 DongListView에서 선언한 serializer_class의 값을 가져오는 것을 알 수 있습니다.
하지만 여기서 다른 점이 있는데, 매개 변수를 넣어준다는 점입니다. 매개 변수로 queryset과 many=True 값이 들어가있는데, queryset은 Model의 데이터들인 반면에 many=True는 어떤 값을 의미할까요?
바로 매개 변수명에서 알 수 있듯이 여거 개의 데이터들을 직렬화한다는 것을 의미합니다. iterable한 객체(list, dict, set...)들을 직렬화한다는 것입니다.
📃 3번
return Response(serializer.data)
마지막 줄입니다! Response라는 함수에 serializre의 data를 넘겨주고 있습니다.
요청(request)과 응답(response) 중, 서버는 Request를 받았으므로 Response를 사용자에게 넘겨주어야합니다.
사용자는 Post들의 목록을 Response로 받기를 원하기 때문에, serializer를 통해 JSON 형태로 바뀐 데이터를 넘겨주는 것입니다.
그럼 지금까지 알아본 것을 정리해보자!
- 사용자가 URL에 GET으로 요청을 보냈습니다.
- URL에 라우팅되어 실행하기로 약속한 View가 실행됩니다.
- View에 선언한 queryset이 list()에 넘겨지게 됩니다.
- 넘겨진 query은 마찬가지로 View에서 선언한 serializer를 통해 JSON 형태로 바뀌게됩니다.
- JSON으로 바뀐 데이터를 사용자에게 응답(Response)합니다.
2. CreateModelMixin
새 모델 인스턴스 생성 및 저장
method : POST
주로 쓰는 형태는 다음과 같습니다.
def create(request, *args, **kwargs)
동일하게 위의 코드만으로 View가 만들어질 수 있는 것은 CreateAPIView를 상속받았기 때문이다.
한 번 살펴보자.
📃 rest_framework/generics.py
class CreateAPIView(mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for creating a model instance.
"""
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
사용자가 POST 요청을 보낼 경우, create()라는 함수를 실행시킨다는 내용입니다.
그렇다면 create() 함수는 어딨는지, 어떻게 작동하는지 확인해보려면 restframework/mixins.py에 구현되어 있는 CreateModelMixin Class를 살펴보면 됩니다.
다음은 restframework에 구현되어 있는 CreateModelMixin class입니다.
class CreateModelMixin:
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data) # 1번
serializer.is_valid(raise_exception=True) # 2번
self.perform_create(serializer) # 3번
headers = self.get_success_headers(serializer.data) # 4번
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) # 5번
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 {}
먼저 한 줄씩 살펴봐보자. 먼저 1번을 살펴보면
📃 1번
serializer = self.get_serializer(data=request.data)
get_serializer의 이름에서 알 수 있듯이 serializer를 가져오는 함수를 의미합니다. 이 또한 DongListView에서 선언한 serializer_class의 값을 가져오는 것을 알 수 있습니다.
📃 2번
serializer.is_valid(raise_exception=True)
유효성 검사를 진행하는 코드입니다. 만약 검사를 통과하지 못하면 예외를 발생시킵니다.
📃 3번
self.perform_create(serializer)
serializer를 저장하는 부분입니다.
📃 4번
headers = self.get_success_headers(serializer.data)
성공했을 때 응답 해줄 헤더 목록을 헤더에 넣어주는 부분입니다.
📃 5번
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
위의 과정이 원활하게 잘 진행되었다면 응답해주는 부분입니다.
3. .RetrieveModelMixin
기존 모델 인스턴스 반환을 구현
method : GET
주로 쓰는 형태는 다음과 같습니다.
def retrieve(request, *args, **kwargs)
위의 코드만으로 View가 만들어질 수 있는 것은 Django에서 제공하는 RetrieveAPIView를 상속받았기 때문이다.
그럼 이 간단한 형태를 어떻게 쓸 수 있을까?
📃 rest_framework/generics.py
class RetrieveAPIView(mixins.RetrieveModelMixin,
GenericAPIView):
"""
Concrete view for retrieving a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
사용자가 GET 요청을 보낼 경우, retrieve()라는 함수를 실행시킨다는 내용입니다.
그렇다면 retrieve() 함수는 어딨는지, 어떻게 작동하는지 확인해보려면 아래를 참고하면 됩니다.
다음은 restframework에 구현되어 있는 RetrieveModelMixin class입니다.
class RetrieveModelMixin:
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
instance = self.get_object() # 1번
serializer = self.get_serializer(instance) # 2번
return Response(serializer.data) # 3번
# get_object 소스 코드
def get_object(self, queryset=None):
"""
Returns the object the view is displaying.
By default this requires `self.queryset` and a `pk` or `slug` argument
in the URLconf, but subclasses can override this to return any object.
"""
# Use a custom queryset if provided; this is required for subclasses
# like DateDetailView
if queryset is None:
queryset = self.get_queryset()
# Next, try looking up by primary key.
pk = self.kwargs.get(self.pk_url_kwarg, None)
slug = self.kwargs.get(self.slug_url_kwarg, None)
if pk is not None:
queryset = queryset.filter(pk=pk)
# Next, try looking up by slug.
if slug is not None and (pk is None or self.query_pk_and_slug):
slug_field = self.get_slug_field()
queryset = queryset.filter(**{slug_field: slug})
# If none of those are defined, it's an error.
if pk is None and slug is None:
raise AttributeError("Generic detail view %s must be called with "
"either an object pk or a slug."
% self.__class__.__name__)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj # Model.object.get(pk=pk) 의미
📃 1번
instance = self.get_object()
위에 get_object 소스 코드를 첨부했는데 이를 정리해보면 다음과 같습니다.
get_object는
- queryset과 pk값을 인자로 받아서,
- queryset.filter(pk=pk)로 queryset을 뽑고,
- obj = queryset.get()으로 객체만 뽑아서 리턴해 주는 메소드
=> 결국, 위 코드는 Model.objects.get(pk=pk) 리턴합니다.
따라서 1번은 Model.objects.get(pk=pk)을 instance라는 변수에 담아주는 것을 의미합니다.
📃 2번
get_serializer는 instance를 인자로 받아서, 해당 객체을 serialize해 주는 메소드입니다.
📃 3번
serializer.data를 반환합니다.
4. .UpdateModelMixin
기존 모델 인스턴스 업데이트 및 저장 구현
method : PUT
주로 쓰는 형태는 다음과 같습니다.
def update(request, *args, **kwargs)
그럼 이 간단한 형태를 어떻게 쓸 수 있을까?
class UpdateAPIView(mixins.UpdateModelMixin,
GenericAPIView):
"""
Concrete view for updating a model instance.
"""
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)
사용자가 PUT 요청을 보낼 시, update() 함수를 실행시키고, patch 요청을 보낼 시 partial_update() 함수를 실행시킵니다.
다음은 restframework에 구현되어 있는 UpdateModelMixin class입니다.
class UpdateModelMixin:
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False) # 1번
instance = self.get_object() # 2번
serializer = self.get_serializer(instance, data=request.data, partial=partial) # 3번
serializer.is_valid(raise_exception=True) # 4번
self.perform_update(serializer) # 5번
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
📃 1번
pop 메소드로 딕셔너리 내에 partial 라는 키워드 인수를 꺼내줍니다. 만약 없다면 메소드의 두 번째 인수로 지정해주면 됩니다.
📃 2번
Model.objects.get(pk=pk)을 instance라는 변수에 담아주는 것을 의미합니다.
📃 3번
get_serializer는 instance를 인자로 받아서, 해당 객체을 serialize해 주는 메소드입니다.
📃 4번
유효성 검사를 진행하는 코드입니다. 만약 검사를 통과하지 못하면 예외를 발생시킵니다.
📃 5번
아래 perform_update 함수가 별도로 존재합니다. 해당 함수는 serializer의 값을 저장하는 메소드입니다.
5. DestoryModelMixin
기존 모델 인스턴스의 삭제 구현
method : DELETE
주로 쓰는 형태는 다음과 같습니다.
def destory(request, *args, **kwargs)
그럼 이 간단한 형태를 어떻게 쓸 수 있을까?
class DestroyAPIView(mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for deleting a model instance.
"""
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
사용자가 delete 요청을 보낼 시, destory() 함수를 실행시킵니다.
다음은 restframework에 구현되어 있는 DestoryModelMixin class입니다.
class DestroyModelMixin:
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
instance = self.get_object() # 1번
self.perform_destroy(instance) # 2번
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
📃 1번
Model.objects.get(pk=pk)을 instance라는 변수에 담아주는 것을 의미합니다.
📃 2번
Class 내에 perform_destroy라는 함수가 존재합니다. instance에 해당하는 값을 지워주는 함수입니다.
2. generics
📃 rest_framework/generics.py
아래의 Class를 사용하기 위해서는 다음과 같이 import를 필요로 합니다.
from rest_framework.mixins import (
ListModelMixin, CreateModelMixin, UpdateModelMixin)
1. get_queryset
get_queryset은 다음 특징들을 가지고 있습니다.
- 들어오는 요청에 따라 다르게 정의할 수 있습니다.
- 다른 곳에서 사용하려면 self.get_queryset을 이용해 사용합니다.
- queryset은 한 번만 호출되고 후속 요청들에 대해서는 결과가 캐싱됩니다.
!!여기서 캐싱된다는 의미를 잘 이해 못했고 좀 더 들여다 볼 필요가 있어보임
어떤식으로 캐싱되고 어떤 부분에서 효과적인지 check 필요!!
그럼 queryset과 get_queryset()의 차이는 무엇일까? 요약해보면 다음과 같다.
queryset은 request 발생 시 한 번만 쿼리셋이 동작하고, get_queryset()은 매 request마다 쿼리를 발생시킨다. 조건이 걸린 쿼리셋을 쓸 때는 get_queryset()을 오버라이딩하자.
generics APIView 정리 (GenericsAPIView 상속)
- generics.CreateAPIView : post → create
- generics.ListAPIView : get → list
- generics.RetrieveAPIView : get → retrieve
- generics.DestroyAPIView : delete → destroy
- generics.UpdateAPIView : put → update, patch → partial_update
- generics.ListCreateAPIView : get → list, post → create
- generics.RetrieveUpdateAPIView : get → retrieve, put → update, patch → partial_update
- generics.RetrieveDestroyAPIView : get → retrieve, delete → destroy
- generics.RetrieveUpdateDestroyAPIView : get → retrieve, put → update, patch → partial_update, delete → destroy
View 구현 시 상속받는 클래스의 추상화 정도: APIView < mixins < Generics < ViewSet
- mixins : APIView에서 일반적인 로직들을 섞어서 재사용성을 높인 것
- Generics : mixins 사용을 패턴화해서 정리함
- ViewSet : Generics를 한번 더 합쳐서 정리
이를 어떻게 view에서 연동해 사용하는지도 추가
'Django' 카테고리의 다른 글
[DRF] - Transaction (0) | 2024.11.09 |
---|---|
[DRF] - Serializer (0) | 2024.11.08 |
[Django] - Model Inheritance (0) | 2024.09.07 |
[DRF] - Authentication (1) | 2024.09.07 |
[Django] - virtual env (0) | 2024.08.31 |