DRF를 제대로 사용하기 위해서는 이 프레임웤이 제공하는 제공하는 CBV (Class Based View, MVC의 Controller에 해당)가 어떻게 이루어져 있는지 소스코드를 단순화해서 들여다볼 필요가 있다고 생각한다. 아래의 정리 내용은 소스코드 이해를 위해 불필요한 부분을 제거하고 단순화 해놓은 내용이다.
미들웨어를 통해 View에 도착한 request가 어떤 과정을 거쳐 response가 되는지 꿰차고 있다면 어떤 클래스를 상속하고, 어떤 메소드를 오버라이드하는 것이 좋은지 올바르게 결정할 수 있을 것이다.
Function Cascade in CLRUD 이건 일종의 cheetsheet으로 쓰기위해 만들었다. 개인 프로젝트에서 잘 써먹은 기억이 있다.
# Intended to make myself better understand magics behind django CBV.
# Summarized the cascading inheritance among
# View -> APIView -> GenericAPIView + Mixins -> Viewset
# Pseudocodes included.
# View.as_view() -> returns 'view'
# when 'view' is called, it calls dispatch()
# and dispatch() returns proper View.method() based on the http method used for request.
# in urls.py : View.as_view() -> view
# by server : view() -> dispatch() -> View.proper_method()
Inherit View and add following features
1) Forced Caching
When the APIView has .queryset attribute.
Forbid objects.queryset() by overriding APIView.queryset._fetch_all()
it enforce using either .all() or .get_queryset() which caches data
2) Policy Application
classes for
-renderer
-parser
-authentification
-throttle
-permission
-content_negetiation
-metadata
-versioning
is assgined as attributes with default api setting
methods for policy instantiation and implementations are also included.
e.g.
-instantiation: get_permissions, get_throttles
-implementation: check_permissions, check_throttles
Inherit APIView.
# Help querying and serialization through pre-designed attributes and methods.
# Assign `queryset` and `serializer_class` with proper Model and Serializer
# Methods such as `get_object`, `get_serializer`, `filter_queryset` will help you handle Model and Serializer.
queryset = None
serializer_class = None
lookup_field = 'pk'
lookup_url_kwarg = None
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get_queryset():
return self.queryset.all()
def get_obejct():
queryset = self.filter_queryset(self.get_queryset)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
return obj
def get_serializer():
serializer_class = get_serializer_class()
kwargs.setdefault('context',get_serializer_context())
return serializer_class
def get_serialiser_class()
def get_serializer_context()
def filter_queryset():
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
def paginator():
def paginate_queryset():
def get_paginated_response():
#### Mixins + GenericAPIView => LC_APIView, RUD_APIView ####
# Basically there are 5 mixins: List, Create, Retreive, Update, Destroy.
# these mixins utilize methods defined in GenericAPIView.
# (such as get_object, get_serializer, filter_queryset and paginator.paginate_queryset)
# by inheriting Mixins and GenericAPIView, YourOwnAPIView will have much sexier outlook.
# ListCreateApiView, RetrieveUpdateDestroyApiView are good example of YourOwnAPIView
# Be aware http methods defined in the ListCreateApiView
# and that they call inherited methods from Mixins.
# You are doing great job if you have looked at what 'get()' is calling in each APIView.
# In ListCreateApiView, get() returns list().
# In RetrieveUpdateDeleteView, get() returns retrieve().
class ListModelMixin:
def list():
queryset = self.filter_queryset(self.get_queryset)
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)
return Response(serializer.data)
class CreateModelMixin:
def create():
serializer = self.get_serializer()
serializer.is_valid()
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
class RetrieveModelMixin:
def retrieve():
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
class UpdateModelMixin:
def update():
partial = kwargs.pop('partial',False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
serializer.save()
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to queryset,
# i.e. when we eagerloaded data (for whatever purpose),
# we need to empty the cache.
# (because we shouldn't update with cached data!)
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(self, request, *args, **kwargs)
class DestroyModelMixin:
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
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)
일단 CBV까지만 이해해도 충분히 프로젝트에 사용할만한것 같다. 프로젝트가 끝나고 뷰셋에 대한 깊은 이해가 필요하다고 느끼면 그때 들여다 보고 정리해야겠다.
각 action별 request~reponse까지 데이터 흐름을 따라가보자.
cf) 아래 정리내용에서 view.create
, view.list
, view.retrieve
, view.update
, view.destroy
다섯가지는 각 Mixin에서 유래한 메소드이다. Create, List, Retrieve, Update, Destroy
5가지 ModelMixin
에 주요 로직이 정의 되어 있다.
# `=` 은 return 받는것. ->는 콜스택쌓임(리턴으로 돌아옴)
# C: 데이터 생성, post요청
# view.dispatch()
# view.post()
# view.create()
# serializer = view.get_serializer()
# view.perform_create(serializer)
# instance = serializer.save() -> serializer.create() -> view.ModelClass.objects.create()
# Response(serializer.data)
# cf) ModelClass._default_manager.create(**validated_data) 를 줄여서 Model.objects.creat e()
# L: 데이터 읽기, get 요청
# view.dispatch()
# view.post()
# view.list()
# qs = view.filter_queryset(view.get_queryset())
# view.paginate_queryset(qs)
# serializer=view.get_serializer()
# serializer.is_valid(raise_exeption=True)
# return view.get_paginated_response(serializer)
# R: 데이터 읽기, get 요청 with pk
# view.dispatch()
# view.post()
# view.retrieve()
# instance=view.get_object()
# serializer=view.get_serializer(instance)
# return Response(serializer.data)
# U: 데이터 쓰기, put, patch요청 with pk
# view.dispatch()
# view.put, view.patch()
# view.update()
# partial = T/F
# instance=view.get_object()
# serializer=view.get_serializer(instance, data=request.data, partial=partial)
# serializer.is_valid(raise_exeption=True)
# view.perform_update(serializer)
# instance = serializer.save() -> view.instance? -> serializer.update() -> view.instance.save()
# return Response(serializer.data)
# D:
# view.dispatch()
# view.delete()
# view.destroy()
# instance=view.get_object()
# view.perform_destroy(instance)
# instance.delete()
# Response()
#FYI
# C,U에서 serializer.create() 및 serializer.update()를 할 때
# 공통적으로 serializer.validated_data를 인자로 넘기며, U는 view.instance도 넘긴다.
# 또한, model_meta.get_field_info로 필드(컬럼) 정보를 받아서 many_to_many가 있으면
# C는 validated_data에서 many_to_many정보를 pop해서 저장해둔뒤
# create()를 먼저 해서 pk를 받아오고 저장해뒀던 many_to_many 를 채운다.
# U는 validated_data중 many_to_many가 아닌것만 setattr(instance)로 먼저 instance내용을 업데이트-> instance.save() 를 하고
for attr, value in m2m_fields:
field = getattr(instance, attr)
field.set(value)
#이렇게 Many_to_ManyField.set(value) 로 별도로 저장한다.(사실상 다른 테이블에 대한 정보니까.)
#view.get_object와 view.get_serializer를 알아보자
#데이터 흐름과 argument들...
def get_queryset(self):
queryset = self.queryset
if isinstance(queryset, QuerySet):
queryset = queryset.all()
return queryset
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field # None or "pk"
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} # {"pk": 1}
# Q: How does the view has "pk" as kwargs?
# A: view.dispatch(**kwargs) has `self.kwargs=kwargs`
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
kwargs.setdefault('context', self.get_serializer_context())
return serializer_class(*args, **kwargs)
def get_serializer_context(self):
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}