함수 기반 뷰에서는 뷰 함수 자체가 내장 함수이고, 클래스 기반 뷰에서는 뷰 클래스가 내장 함수를 반환하는 as_view()
클래스 메서드를 제공합니다.
🦝 django.views.generic.View 에서 해당 메커니즘이 구현되며,
모든 클래스 기반 뷰는 모체 View
클래스를 직간접적으로 상속 및 오버라이딩하여 이용합니다.
ex) CBV 사용 예시
AboutView
클래스는 CBV의 모체인 View
클래스를 상속받습니다.
View
클래스에는 as_view
메서드와 dispatch()
메서드가 정의되어 있습니다.
# urls.py
# CBV를 사용할 때의 URLconf
from django.urls import path
from django.views.generic import AboutView
urlpatterns = [
path('about/', AboutView.as_view()),
]
# views.py
# AboutView 정의
from django.http import HttpResponse
from django.views.generic import View
class AboutView(View):
# 뷰 로직 작성
return HttpResponse('result')
ex) DetailView 사용 예시
# urls.py
from . import views
urlpatterns = [
path('post/<int:pk>/, post_detail),
path('article/<int:pk>/, article_detail),
]
# views.py
from django.views.generic import DetailView
post_detail = DetailView.as_view(model=Post)
article_detail = DetailView.as_view(model=Article)
제네릭 클래스 기반 뷰의 장점은 단순화를 희생해서 얻은 결과입니다. 제네릭 클래스 기반 뷰는 최대 여덟 개의 슈퍼 클래스가 상속되기도 하는 복합적인 상속체이므로, 커스텀마이징할 때 django.views.generic.View 의 관례에 대해 이해하며 써야합니다.
모든 cbv의 모체인 View
를 직접 쓸 일은 거의 없습니다. 대신 주요 기능을 http method별로 지정 이름의 멤버 함수를 호출토록 구현합니다.
새로운 View를 생성할 때 CBV.as_view(**initkwargs)
를 통해 생성하는데, 이 때 initkwargs
인자는 CBV 생성자로 그대로 전달됩니다.
# Base views - View : 모든 cbv의 모체
class View:
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
"""
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def __init__(self, **kwargs):
"""
Constructor. Called in the URLconf; can contain helpful extra
keyword arguments, and other things.
"""
# Go through keyword arguments, and either save their values to our
# instance, or raise an error.
for key, value in kwargs.items():
setattr(self, key, value)
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process."""
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
def http_method_not_allowed(self, request, *args, **kwargs):
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
return HttpResponseNotAllowed(self._allowed_methods())
def options(self, request, *args, **kwargs):
"""Handle responding to requests for the OPTIONS HTTP verb."""
response = HttpResponse()
response['Allow'] = ', '.join(self._allowed_methods())
response['Content-Length'] = '0'
return response
def _allowed_methods(self):
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
진입 메서드 as_view()의 요청처리 순서
as_view()
진입 메서드는 클래스의 인스턴스를 생성합니다.- 생성된 인스턴스의
dispatch()
메서드를 호출합니다.dispatch()
메서드는 요청을 검사해여 GET, POST 등의 어떤 HTTP 메서드로 요청되었는지를 알아냅니다.- 인스턴스 내에서 해당 이름을 갖는 메서드로 요청을 중계해줍니다.
- 만일 해당 메서드가 정의되어 있지 않으면
HttpResponseNotAllowed
Exception을 방생시킵니다.
믹스인이란 실체화(instantiation) 된 클래스가 아니라 상속해 줄 기능들을 제공하는 클래스를 의미합니다. 다중 상속을 해야 할 때 믹스인을 쓰면 클래스에 더 나은 기능을 제공할 수 있습니다. 믹스인을 이용하여 뷰 클래스를 제작할 때는 다음과 같은 상속 규칙을 따릅시다.
from django.views.generic import TemplateView
class FreshFruitMixin(object): # object 객체 타입을 상속한다.
def get_context_data(self, **kwargs):
...
return context
# FreshFruitMixin 과 TemplateView 를 둘다 상속하지만
# 규칙에 의해 TemplateView는 가장 오른쪽에 위치하고, Mixin은 왼쪽에 둔다.
class FruitFlavorView(FreshFruitMixin, TemplateView):
template_name = "fruit_flavor.html"