Django-Magic: View

Jaeho Lee·2021년 2월 15일
0

Django

목록 보기
1/1
post-thumbnail

DRF를 제대로 사용하기 위해서는 이 프레임웤이 제공하는 제공하는 CBV (Class Based View, MVC의 Controller에 해당)가 어떻게 이루어져 있는지 소스코드를 단순화해서 들여다볼 필요가 있다고 생각한다. 아래의 정리 내용은 소스코드 이해를 위해 불필요한 부분을 제거하고 단순화 해놓은 내용이다.

미들웨어를 통해 View에 도착한 request가 어떤 과정을 거쳐 response가 되는지 꿰차고 있다면 어떤 클래스를 상속하고, 어떤 메소드를 오버라이드하는 것이 좋은지 올바르게 결정할 수 있을 것이다.

Function Cascade in CLRUD 이건 일종의 cheetsheet으로 쓰기위해 만들었다. 개인 프로젝트에서 잘 써먹은 기억이 있다.

DRF CBV

# Intended to make myself better understand magics behind django CBV.
# Summarized the cascading inheritance among
# View -> APIView -> GenericAPIView + Mixins -> Viewset
# Pseudocodes included.

1. View

# 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()

2. APIView

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

3. GenericAPIView

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.

3.1 new attributes

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

3.2 new methods


    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():

4. Mixins

#### 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().

4.1 Mixins

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)

4.2 LC/RUD/APIView

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)

5. ViewSet and Router

일단 CBV까지만 이해해도 충분히 프로젝트에 사용할만한것 같다. 프로젝트가 끝나고 뷰셋에 대한 깊은 이해가 필요하다고 느끼면 그때 들여다 보고 정리해야겠다.

Function Cascade in CLRUD

각 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
        }
profile
코딩하는 꿀벌

0개의 댓글