일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- java
- js
- javascript
- django ORM
- API
- AWS
- django widget
- react
- es6
- 장고
- c++
- 백준
- 알고리즘 풀이
- 알고리즘 문제
- CSS
- 알고리즘
- 알고리즘 연습
- 파이썬
- MAC
- 파이썬 알고리즘
- web
- Algorithm
- Git
- django rest framework
- Django
- PYTHON
- form
- HTML
- Baekjoon
- DRF
- Today
- Total
수학과의 좌충우돌 프로그래밍
[Django] CBV (1) CBV 와 Base Views 본문
CBV 란?
기존에는 views 를 함수로 사용해왔습니다. 이는 Function Based View , 줄여서 FBV
라고 합니다. 그렇다면 함수 외에 다른 것으로 views 를 작성할 수는 없을까요? 가능합니다. views는 사실 함수가 아닌 Callable Objects,즉 호출가능한 객체면 문제 없습니다. 함수도 호출가능한 객체 중 하나이기 때문에 사용할 수 있는 것이죠.
따라서 다른 호출가능한 객체인 클래스로도 views 를 구성할 수 있습니다. 이는 Class Based View, 줄여서 CBV
라고 합니다.
CBV 의 장점
우선 클래스의 장점을 모두 사용할 수 있습니다. 상속, 오버라이딩 등등 여러 방식으로 코드의 효율을 극대화 할 수 있습니다. FBV 로 views 를 작성하다보면 생각보다 중복되는 코드들을 많이 볼 수 있습니다. SNS 를 예로 들어도 글쓰기, 댓글쓰기. 글지우기 , 댓글지우기 등등 벌써 부터 겹치는 기능들이 많이 있습니다. 이를 중심이 되는 클래스를 하나 만들고 상황에 맞게 상속함으로서 코드의 길이를 줄이고 효율을 높일 수 있는 것이죠. 이 밖의 여러 장점들은 실습을 하면서 몸으로 느끼도록 하겠습니다.
CBV 의 단점
CBV 는 이미 많은 부분이 정해져있습니다. 이는 장점으로 작용하여 바로 사용할 수 도 있지만 이미 정해져있는 부분들을 제대로 이해하고 있지 않는다면 이를 커스텀하기에 애로사항을 겪을 수 있습니다. 또한 클래스의 상속 등과 같은 여러 파이썬 문법에 대한 선행 지식이 선행되어야 하니 초심자에게는 어려울 수 있습니다. FBV 를 어느 정도 숙지하신 후에 CBV 를 사용하실 것을 권장합니다.
base views
기본 토대가 되는 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)]
-
init
뒤에 나올
as_view
에 인자를 넘겨주게 되면 그 인자가*kwargs
로 넘어와 이 dict 를 순회하며 해당 설정을 해주게 됩니다. 예를 들어template_name
값을 지정했다고 하면 인스턴스 변수를 생성하여 이에 값을 할당하고 해당 변수가 필요할 경우 클래스 변수보다 인스턴스 변수를 먼저 참조합니다. -
as_view
as_view
메소드는 view를 하나 만들어서 이를 return 합니다. 즉 함수가 하나 만들어지는거죠. CBV도 결국 기본적인 동작은 함수를 베이스로 합니다. 클래스를 정의한 후에 이에 해당하는 함수를 만들어 그 함수를 사용하는 방식으로 말이죠. 뒤 쪽에서 as_view 를 자주 보실 수 있을 겁니다. -
dispatch
dispatch
메소드는as_view
메소드 내부에서 호출됩니다. request method 를 확인하고http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
안에 있는 정상적인 method 라면request
에서 이를 가져오게 됩니다. 만약 정상적인 method 가 아니라면http_method_not_allowed
를 호출합니다 .
TemplateView
- 실습
전체적인 코드를 살펴보기 전에 어떻게 사용되고 있는지 예시를 먼저 보도록 하겠습니다. 다른 부분의 코드는 모두 동일하다고 생각하고 FBV
로 먼저 코드를 짠 후 그에 대한 CBV
를 알아보도록 하겠습니다.
# FBV
from django.shortcuts import render
def index(request):
context = {
'one': 1,
'myUser' : request.user
}
return render(request, 'core/index.html', context)
여기서 3가지를 주의깊게 보도록 하겠습니다. 첫번째로 template 의 이름, 두 번째로 정적인 context, 마지막으로 동적은 context 입니다.
# CBV
from django.views.generic import TemplateView
index = TemplateView.as_view(
template_name = "core/index.html",
extra_context= {'one': 1},
)
template_name
을 통해서 template의 이름을, extra_context
로 다음과 같이 정적인 context 를 지정해 줄 수 있습니다. 하지만 request.user
처럼 동적인 경우에는 extra_context
가 아니라 get_context_data
함수를 사용해야 합니다.
# CBV
class MyTemplateView(TemplateView):
template_name= "core/index.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'one': 1,
'myUser' : self.request.user,
})
return context
index = MyTemplateView.as_view()
이를 위해서 MyTemplateView
라는 class 를 정의하고 TemplateView
를 상속받았습니다. get_context_data
함수는 상속해준 TemplateView
의 get_context_data
함수를 호출해 context 를 받아오고 이에 원하는 내용을 추가해 return 해주었습니다. 이 때 request
가 아닌 self.request
인 점 유의해주시면 되겠습니다.
-
TemplateView 살펴보기
위의 내용이 이해가 안되는게 정상입니다. 우리가 위에서 사용했던 변수, 함수들이 어떻게 정의되어 있는지 알 수 없으니깐요.
TemplateView
가 어떻게 구성되어있는지 확인해보도록 하겠습니다.class TemplateView(TemplateResponseMixin, ContextMixin, View): """ Render a template. Pass keyword arguments from the URLconf to the context. """ def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) return self.render_to_response(context)
TemplateResponseMixin
,ContextMixin
,View
다음 3개의 class 에 대해서 상속을 받았습니다.-
TemplateResponseMixin
class TemplateResponseMixin: """A mixin that can be used to render a template.""" template_name = None template_engine = None response_class = TemplateResponse content_type = None def render_to_response(self, context, **response_kwargs): """ Return a response, using the `response_class` for this view, with a template rendered with the given context. Pass response_kwargs to the constructor of the response class. """ response_kwargs.setdefault('content_type', self.content_type) return self.response_class( request=self.request, template=self.get_template_names(), context=context, using=self.template_engine, **response_kwargs ) def get_template_names(self): """ Return a list of template names to be used for the request. Must return a list. May not be called if render_to_response() is overridden. """ if self.template_name is None: raise ImproperlyConfigured( "TemplateResponseMixin requires either a definition of " "'template_name' or an implementation of 'get_template_names()'") else: return [self.template_name]
template 의 기본적인 설정들, template의 이름, rendering 하는 과정 등을 담당하게 됩니다. 하지만 여기서 context 를 rendering 해주는 코드는 있지만 context 를 받아주는 과정은 보이지 않습니다. 이는 아래
ContextMixin
에서 해줍니다. -
ContextMixin
class ContextMixin: """ A default context mixin that passes the keyword arguments received by get_context_data() as the template context. """ extra_context = None def get_context_data(self, **kwargs): kwargs.setdefault('view', self) if self.extra_context is not None: kwargs.update(self.extra_context) return kwargs
위에 실습에서 봤듯이 정적인 context 에 대해서는
extra_context
변수를 사용하면 되고 동적으로 사용되야 하는 경우에는get_context_data
함수를 재정의 함으로서 사용하면 됩니다.
-
RedirectView
- 실습
위와 마찬가지로 다른 부분의 코드는 모두 동일하다고 생각하고 FBV
로 먼저 코드를 짠 후 그에 대한 CBV
를 알아보도록 하겠습니다.
# FBV
from django.shortcuts import redirect
def index2(reqeust):
return redirect('index')
크게 의미 없는 코드이지만 실습을 위해 작성하였습니다.
# CBV
index2 = RedirectView.as_view(pattern_name='index')
pattern_name
을 통해서 해당 이름을 가진 url 로 redirect 할 수 있습니다.
-
RedirectView 살펴보기
이번에도 마찬가지로
RedirectView
를 보면서 전체적인 코드의 흐름을 이해해보도록 하겠습니다.class RedirectView(View): """Provide a redirect on any GET request.""" permanent = False url = None pattern_name = None query_string = False def get_redirect_url(self, *args, **kwargs): """ Return the URL redirect to. Keyword arguments from the URL pattern match generating the redirect request are provided as kwargs to this method. """ if self.url: url = self.url % kwargs elif self.pattern_name: url = reverse(self.pattern_name, args=args, kwargs=kwargs) else: return None args = self.request.META.get('QUERY_STRING', '') if args and self.query_string: url = "%s?%s" % (url, args) return url def get(self, request, *args, **kwargs): url = self.get_redirect_url(*args, **kwargs) if url: if self.permanent: return HttpResponsePermanentRedirect(url) else: return HttpResponseRedirect(url) else: logger.warning( 'Gone: %s', request.path, extra={'status_code': 410, 'request': request} ) return HttpResponseGone() def head(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) def options(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) def patch(self, request, *args, **kwargs): return self.get(request, *args, **kwargs)
-
get
get_redirect_url
을 호출하여 url 을 얻습니다. url을 얻지 못한다면 에러를 출력합니다. 제대로 url 을 얻었다면permanent
에 의해서 분기 되기는 하지만 결과적으로redirect
합니다.
-
get_redirect_url
get_redirect_url
는 클래스 변수 url 을 통해 직접 url 을 받아오거나pattern_name
을 통해 url 의 name 값을 받아왔으면 이를 return 해줍니다. 부가적으로parmnent
값을 지정해주거나query_string
을 지정해줄 수 있습니다.
-
head, post … patch
아래 구현된 메소드 모두 get 함수를 실행시킵니다.
-
'웹프로그래밍 > Django' 카테고리의 다른 글
[Django] FBV 와 CBV 의 decorators 사용법 (0) | 2019.08.08 |
---|---|
[Django] CBV (2) Generic display views (2) | 2019.08.06 |
[Django] media 파일 업로드하기 (7) | 2019.07.30 |
[Django] static 파일 다루기 (0) | 2019.07.30 |
[Django] json_script 를 이용한 효과적인 json parsing (1) | 2019.07.28 |