Django - 클래스형 뷰

아래 내용은 파이썬 웹프로그래밍(한빛출판사) 책의 내용을 재정리한 것입니다.

요약

  • 클래스로 작성되어 있는 뷰 객체를 말한다.
  • 상속과 믹스인 기능 사용으로 코드의 재사용이 가능
  • 뷰의 체계적 관리
  • 제네릭 뷰 작성

클래스형 뷰의 시작점

클래스형 뷰는 URLconf에서의 사용으로 시작한다.
MyView 라는 클래스형 뷰를 사용한다면 URLconf의 모습은 다음과 같을 것이다.

# urls.py
from django.conf.urls import patterns  
from myapp.views import MyView

urlpatterns = patterns('',  
    (r'^about/', MyView.as_view()),
)

클래스형 뷰는 클래스로 진입하기 위한 진입 메소드를 제공하는데, 이것이 위 예제에서의 as_view()메소드이며, 아래의 순서로 요청을 처리한다.

  • as_view() 메소드에서 클래스의 인스턴스를 생성한다.
  • 생성된 인스턴스의 dispatch() 메소드를 호출한다.
  • dispatch() 메소드는 요청을 검사해서 HTTP의 메소드(GET, POST)를 알아낸다.
  • 인스턴스 내에 해당 이름을 갖는 메소드로 요청을 중계한다.
  • 해당 메소드가 정의되어 있지 않으면, HttpResponseNotAllowd 예외를 발생시킨다.
MyView 클래스의 정의
# views.py
from django.http import HttpResponse  
from django.views.generic import View

class MyView(View):  
    def get(self, request):
        # 뷰 로직 작성
        return HttpResponse('result')
  • MyView 클래스는 View 클래스를 상속받는다.
  • View 클래스에는 as_view() 메소드와 dispatch() 메소드가 정의되어 있다.

클래스형 뷰의 장점

함수형 뷰와 비교했을 때 클래스형 뷰가 가지는 장점

  • GET, POST 등의 HTTP 메소드에 따른 처리를 메소드명으로 구분 할 수 있어, 좀 더 깔끔한 구조의(IF 문이 없는) 코드를 생산할 수 있다.
  • 다중 상속과 같은 객체 지향 기술이 가능하여 코드의 재사용성이나 개발 생산성을 높여준다.
효율적인 메소드 구분

요청을 수신했을 때 요청의 HTTP 메소드를 처리하는 방식을 함수형 뷰와 클래스형 뷰의 차이점을 비교해보자. 우선, 함수형 뷰는 아래와 같은 로직을 작성한다.

from django.http import HttpResponse

def my_view(request):  
    if request.method == 'GET':
        # 뷰 로직 작성
        return HttpResponse('result')

함수형 뷰에서는 예제에서 볼 수 있듯 HTTP 메소드별 다른 처리가 필요할 때 if 문을 이용해야 한다. 하지만, 클래스형 뷰는 다음과 같이 코드의 구조가 훨씬 깔끔해짐을 볼 수 있다.

from django.http import HttpResponse  
from django.views.generic import View

class MyView(View):  
    def get(self, request):
        # 뷰 로직 작성
        return HttpResponse('result')

클래스형 뷰에서는 HTTP 메소드 이름으로 클래스 내의 메소드를 정의하면 된다.
단, 메소드명은 소문자로~ 이러한 처리가 가능한 것은 내부적으로 dispatch() 메소드가 어떤 HTTP 메소드로 요청되었는지 알아내고, 이를 처리해주기 때문이다.

아래의 예는 HTTP의 HEAD 메소드로 코딩하는 예를 보여준다.

from django.http import HttpResponse  
from django.views.generic import ListView  
from books.models import Book

class BookListView(ListView):  
    model = Book

    def head(self, *args, **kwargs):
        last_book = self.get_queryset().latest('publication_date')
        response = HttpResponse('')
        response['Last-Modified'] = last_book.publication_date.strftime('%a, %d %b %y %H:%M:%S GMT')
        return response

위 예제는 서점에 방문한 직후에 새롭게 출간된 책이 있는지를 문의하는 로직이다. 최근 발간된 책이 없는데도 책 리스트를 서버로부터 받아 오게되면 네트워크 대역폭이 낭비되므로, 이를 방지하기 위해 HEAD 메소드를 사용한다. HEAD 요청에 대한 응답은 바디 없이 헤더만 보내주면 된다.

상속 기능 가능

개발자가 작성하는 대부분의 클래스형 뷰는 장고가 제공해주는 제네릭 뷰를 상속받아 작성한다.

제네릭 뷰
뷰 개발 과정에서 공통적으로 사용할 수 있는 기능들을 추상화하고, 장고에서 기본적으로 제공해주는 클래스형 뷰

# some_app/urls.py
from django.conf.urls import patterns  
from some_app.views import AboutView

urlpatterns = patterns('',  
    (r'^about/', AboutView.as_view()),
)
# some_app/views.py
from django.views.generic import TemplateView

class AboutView(TemplateView):  
    template_name = "about.html"

views.py 파일의 소스는 단 2줄. 이것이 가능한 이유가 TemplateView라는 제네릭 뷰를 상속받아 사용하고 있기 때문이다.
아래 예제코드는 URLconf 의 작성만으로 위 예제와 같은 역할을 수행한다.

from django.conf.urls import patterns  
from django.views.generic import TemplateView

urlpatterns = patterns('',  
    (r'^about/', TemplateView.as_view(template_name="about.html")),
)

TemplateView 는 특별한 로직이 없고, URL 맞춰 해당 템플릿 파일의 내용만 보여줄 때 사용하는 제네릭 뷰이기 때문에 위의 예제들과 같이 사용할 수 있는 것이다.

Django 의 제네릭 뷰

Django 에서 제공하는 제네릭 뷰는 다음과 같이 4가지로 분류할 수 있다.

  • Base View: 뷰 클래스를 생성하고, 다른 제네릭 뷰의 부모 클래스를 제공하는 기본 제네릭 뷰
  • Generic Display View: 객체의 리스트를 보여주거나, 특정 객체의 상세 정보를 보여준다.
  • Generic Edit View: 폼을 통해 객체를 생성, 수정, 삭제하는 기능을 제공한다.
  • Generic Date View: 날짜 기반 객체의 년/월/일 페이지로 구분해서 보여준다.

아래는 위 4가지 분류에 따른 구체 뷰 클래스에 대한 설명을 보여준다.

  • Base View
    • View: 가장 기본이 되는 최상위 제네릭 뷰
    • TemplateView: 템플릿이 주어지면 해당 템플릿을 렌더링한다.
    • RedirectView: URL이 주어지면 해당 URL로 리다이렉트 시켜준다.
  • Generic Display View
    • DetailView: 객체 하나에 대한 상세한 정보를 보여준다.
    • ListView: 조건에 맞는 여러 개의 객체를 보여준다.
  • Generic Edit View
    • FormView: 폼이 주어지면 해당 폼을 보여준다.
    • CreateView: 객체를 생성하는 폼을 보여준다.
    • UpdateView: 기존 객체를 수정하는 폼을 보여준다.
    • DeleteView: 기존 객체를 삭제하는 폼을 보여준다.
  • Generic Date View
    • YearArchiveView: 년도가 주어지면 그 년도에 해당하는 객체를 보여준다.
    • MonthArchiveView: 월이 주어지면 그 월에 해당하는 객체를 보여준다.
    • DayArchiveView: 날짜가 주어지면 그 날짜에 해당하는 객체를 보여준다.

제네릭 뷰의 전체 리스트는 여기에서 확인 가능하다.

클래스 형 뷰에서의 폼 처리
from django.http import HttpResponseRedirect  
from django.shortcuts import render  
from django.views.generic import View

from .forms imports MyForm

class MyFormView(View):  
    form_class = MyForm
    initial = {'key': 'value'}
    template_name = 'form_template.html'

    def get(self, request, *args, **kwargs):
        form = self.form_class(initial=self.initial)
        return render(request, self.template_name, {'form': form}

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        if form.is_valid():
            # cleaned_data로 관련 로직 처리
            return HttpResponseRedirect('/success/')
        return render(request, self.template_name, {'form':form})

클래스형 뷰에서의 폼처리는 get, post 방식을 메소드로 구분하여 처리함으로써, 코드 구조가 깔끔해진다.
이 코드를 위에서 봤었던 제네릭 뷰인 FormView 를 상속받아 처리하면 아래와 같이 더 간결해지는 것을 볼 수 있다.

from .forms imports MyForm  
from django.views.generic.edit import FormView

class MyFormView(FormView):  
    form_class = MyForm
    template_name = 'form_template.html'
    success_url = '/thanks/'

    def form_valid(self, form)
        return super(MyFormView, self).form_valid(form)

get, post 메소드가 사라졌습니다. 그리고 여기에서는 아래 항목들만 유의해서 지정해주면 됩니다.

  • form_class: 사용자에 보여줄 폼을 정의한 forms.py 파일 내의 클래스명
  • template_name: 폼을 포함하여 렌더링할 템플릿 파일 이름
  • success_url: MyFormView 처리가 정상적으로 완료되었을 때 리다이렉트 될 URL
  • form_valid() 함수: 유효한 폼 데이터로 처리할 로직 코딩. 반드시 super() 함수를 호출해야 함.