django 3.1 버전에서 CBV를 공부하던 도중, 다중상속과 여러가지 함수의 호출 순서가 너무 복잡해 보여서 눈으로 따라가기 어려웠다. 이에 코드를 보는것으로 해결되지 않아서, CreateView를 잡아 2500*2500 [px] 그림을 그려서 확인해 보았다. 초보자들에게 도움이 될지는 잘 모르겠지만, django CBV가 복잡하다는 것은 알 수 있었다.
아무래도 CBV가 상속을 많이 받다보니, 어디에 구현된 함수가 어디에서 실행되는지 쉽게 알기가 어렵다는 생각이 들었다. 그래서 헷갈리기 쉬운 View지만 많이 쓰는 View Class를 하나 잡아서, request.GET을 받았을때, 과연 무슨일이 일어나는지 보기위해 다이어 그램을 그리게 되었다.
2500px x 2500px 그림을 그려보았는데, 생각보다 의문점이 많이 들게 하는 구조였다. 먼저 다중상속 개념이 들어갔기 때문에, 간단히 python의 MRO (Mehotd Resuloution Order) 에 대해 업급하자면, class의 __mro__ 라는 속성에 의해 메소드의 호출 순서가 결정된다.
class A:
def get(self):
print('A')
class B(A):
def get(self):
print('B')
super().get()
class C(A):
def get(self):
print('C')
super().get()
class D(B, C):
def get(self):
print('D')
super().get()
d = D()
d.get()
>>>
D
B
C
A
죽음의 다이아몬드 (오류가 많이 발생해서 이렇게 불린다고 한다) 에 의하면, 다중상속시 메서드 호출 순서는 위와 같다. 중요한 점을 보면 class D에서 super().get을 호출하면, B의 super()가 C라는 점이다.
이에 유의하면서 보자
# https://github.com/django/django/blob/50a5f8840fa564dcefdb1fa5c58f06fcd472ee70/django/views/generic/base.py
# django/django/views/generic/base.py
@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(
'The method name %s is not accepted as a keyword argument '
'to %s().' % (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)
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
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
먼저 생성은 쉽다. 단순히 class에 정의된 모든 변수에 대해서만 인자를 받아서 as_view가 view를 만들 수 있다. 쉬운말로 하자면, as_view는 kwargs를 받는데, 이때 class에서 미리 쓰겠다고 선언된 변수명이 아니라면 kwargs_dict에 새로운 key-value를 넣어도 동작하지 않는다는 뜻이다. (재선언만 한다) 또한 Http method는 key값으로 쓰일 수 없다.
as_view는 view를 반환하면서, view를 감싸고 있는 class에 대해 update를 실행하는데, 파이썬 고급 문법인듯 싶으니 넘어가기로 하자. 업데이트된 cls에 의해 view는 다음과 같은 일을 할 수 있다.
인자로 들어온 request에 대해서 setup() 함수를 이용해 self.request = request 등과 같이 기본 cls인자를 세팅한다. 그리고 dispatch 함수를 이용하여 get, post, option 과 같은 method가 있다면 이를 기본 함수로 등록한다.
여기까지 하면 생성은 마무리다. 문제는 호출이다.
CreateView의 MRO에 따르면 get 함수가 첫번째로 불리는 곳은 바로 위의 함수인 BaseCreateView 의 get이다. 여기서는 별다른 일을 하지 않고 바로 super()를 호출하는데 여기서 [^1 의문점 1.] 이 발생한다. 여기에서의 get은 단순히 get_context_data 와 render_to_response 함수를 이용하여 반환하는데, 문제는 부모가 이 함수를 가지고 있지 않음에도 그냥 자식 클래스가 다중상속으로 TemplateView 나 ContextView를 가질 것이라고 예상하고 바로 함수를 사용한다. 아직까지는 이렇게 설계된 이유를 모르기 때문에, 오류인지 아닌지 잘 모르겠다.
이후에는 SingleOjectMixin, FormMixin과 ContextMixin 을 순서대로 부르며 각각 context에 self.object와 self.form, extra_context를 채워넣는다.
이 context를 이용하여 TemplateView 단에서 template_name 혹은 template_name + template_name_suffix를 가진 템플릿을 찾아서 TemplateResponse를 호출하고, TemplateResponse는 HttpResponse를 호출 하여 반환한다.
크게 3가지 분류로 나눌 수 있겠다.
나머지 파생 Mixin은 Model이나 Form등과 관련하여 get_context_data에 Model과 Form을 불러오는 함수를 덧씌우는 느낌이다.
대충의 flow가 이해가 됬지만, 크게 다음과 같은 의문점들이 남았다.
우선 다이어 그램 목업을 이용하지 않은 점은 잘 선택한것 같다. 먼저 다이어 그램이 가진 특성에 익숙하지 않았고, 그래서 도화지에 그림을 그리듯이 계속해서 확장하면서 작성하였다. 레이어를 잘 활용 할 수 있어서 좋았던 것 같다. 아마 다이어그램 목업툴을 사용하면 한계가 오지 않았을까 싶지만, 그럼에도 3D max나 다이어그램 툴이 가지고 있는 화살표와 어떤 물체와의 고정 (물체를 옮기면 화살표도 같이 이동한다)이 필요하다. 이를 3D 스케치에서는 constraints 라고 하는데, 혹시 관련 프로그램이 없나 찾아봐야겠다.
어떤 철학이나 규칙을 가지 이러한 구조를 띄게 되었는지는 좀 더 공부해서 살펴봐야 되겠다.
ccbv : 그래프는 아니지만 MRO를 쉽게 보여주기 위해 제작된 사이트가 존재한다.
epydoc : 그래프로 보여줘서 도움이 많이 되는 사이트이다. 진작 발견했으면 하는 아쉬움이 있다.
아래는 epydoc 사이트에서 퍼온 그래프