본문 바로가기
  • 실행력이 모든걸 결정한다
Django

[Django] 클래스형 뷰와 제네릭 뷰 소개

by 김코더 김주역 2023. 1. 24.
반응형

장고에서는 뷰를 클래스로도 작성할 수 있으며, 함수형 뷰보다는 클래스형 뷰가 더 이점이 많다.

이번 포스팅에서는 클래스형 뷰와 제네릭 뷰에 대한 감을 잡아보자.

 

1. 클래스형 뷰의 장점

- 다중 상속과 같은 객체 지향 기술을 사용할 수 있기 때문에 코드를 재사용하기 편리하다. 그래서 공통 기능들을 추상화한 제네릭 뷰와 다른 클래스에 부가 기능을 제공하는 믹스인 클래스 등도 활용할 수 있게 된다.

- HTTP 메소드에 따른 처리 로직을 IF 문이 아닌 메소드로 구분할 수 있기 때문에 코드의 구조가 깔끔해진다.

- 규모가 큰 프로젝트에 강하다.

 

 

 

2. 클래스형 뷰 정의

- django.views.generic의 View 클래스를 상속받는 뷰 클래스를 정의했다. 뷰 클래스는 views.py 파일에 작성한다.

- 클래스형 뷰에서는 HTTP 메소드 이름으로 메소드를 정의하면 된다. 즉, get(), post(), head() 등과 같이 이름을 지어주면 된다.

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

class HomeView(View):
    def get(self, request, *args, **kwargs):
        # 로직 처리
        return HttpResponse('result')

- 클래스형 뷰의 kwargs (메소드 내에서 self.kwargs로 사용됨)에는 URL 패턴에서 넘어오는 값이 딕셔너리 형태로 담겨있다. 딕셔너리의 키 값은 URL 패턴 내부에 선언한 변수 이름이 된다. self.kwargs와 메소드의 매개 변수인 **kwargs는 다르니 혼동하지 않기를 바란다.

 

 

 

3. urls.py에서 클래스형 뷰 선언

- URLconf에서 함수형 뷰가 아닌 클래스형 뷰를 사용하도록 지정해야 한다. 다음과 같이 뷰 클래스의 as_view() 라는 진입 메소드를 사용하면 된다.

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', HomeView.as_view(), name='home'),
]

- as_view() 진입 메소드는 클래스의 인스턴스를 생성해서 그 인스턴스의 dispatch() 메소드를 호출하는 역할을 한다. dispatch() 메소드는 어떤 HTTP 메소드로 요청되었는지를 알아내고 해당 이름을 갖는 메소드로 보내주는 역할을 한다. 해당 메소드가 정의되어 있지 않으면 HttpResponseNotAllowed 예외가 발생한다.

※ as_view(), dispatch() 메소드는 View 클래스에 정의되어 있다.

 

 

 

4. 제네릭 뷰란?

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

- 클래스형 뷰를 작성할 때 제네릭 뷰를 상속받아서 작성하는 경우가 일반적이다. 물론 오버라이딩도 가능하다.

- 제네릭 뷰에서 요청 request 객체를 분석하고 컨텍스트 변수를 구성해주기 때문에 개발자가 작성할 로직이 상당히 줄어든다.

- 제네릭 뷰들은 django.views.generic 패키지에 담겨있다.

 

 

 

5. 제네릭 뷰의 종류

각 제네릭 뷰들의 주 역할을 소개할 것이다. 먼저, 어떤 제네릭 뷰를 사용할 지 결정하고, 결정한 제네릭 뷰에서 어떤 속성과 메소드를 오버라이딩함으로써 기능을 추가하거나 변경할 것인지 결정하면 된다. 사용 예시는 포스팅 하단에 첨부한 공식 문서에 잘 나와있기 때문에 참고하길 바란다.

 

1) Base View

- 제네릭 뷰의 부모 클래스를 제공하는 기본 제네릭 뷰

 

● View : 최상위 제네릭 뷰로, 원하는 로직에 맞는 제네릭 뷰가 없을 때 상속받아서 사용한다.

● TemplateView : 주어진 템플릿을 렌더링하는 뷰다.

● RedirectView : 주어진 URL로 리다이렉트하는 뷰다.

 

 

2) Generic Display View

- 객체의 리스트를 보여주거나, 특정 객체의 상세 정보를 보여주는 뷰

 

● ListView

- 여러 객체의 리스트를 보여주는 뷰다. 기본적으로는 모든 레코드를 읽는다.

- 주어진 조건에 맞는 리스트를 구성하고 싶다면 get_queryset() 메소드를 오버라이딩하면 된다.

def get_queryset(self):
    return Snack.objects.filter(supply_year__exact=self.kwargs['year']).filter(supply_month__exact=self.kwargs['month'])

- 개발자가 따로 지정하지 않아도 디폴트 컨텍스트 변수로 object_list(또는 [모델명 소문자]_list)를 사용할 수 있다. 컨텍스트 변수명을 직접 지정하고 싶다면 context_object_name 속성에 지정해주면 된다.

- 기본 template_name은 "[앱명]/[모델명 소문자]_list.html"이다.

 

 DetailView

- 특정 객체만의 상세 정보를 보여주는 뷰다.

- 특정 객체를 PK로 조회하는 경우에는 model 속성에 모델 클래스명만 지정해주면 된다. 객체 조회 시 사용할 PK 값은 다음과 같이 URLconf에서 추출하여 뷰로 넘어온 파라미터를 자동으로 사용한다.

urlpatterns = [
    ...,
    path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),
]

※ pk가 아니어도 unique하다면 slug를 사용하기도 한다. 공식 문서에서는 slug를 사용하는 예시가 나와있다.

- 특정 객체 하나를 컨텍스트 변수에 담아서 템플릿 시스템에 넘겨준다. 개발자가 따로 지정하지 않아도 디폴트 컨텍스트 변수로 object(또는 [모델명 소문자])를 사용할 수 있다. DetailView 역시 컨텍스트 변수명을 명시적으로 지정해줄 수 있다.

- 기본 template_name은 "[앱명]/[모델명 소문자]_detail.html"이다.

 

 

3) Generic Edit View

- 폼을 통해 객체를 생성, 수정, 삭제하는 기능을 제공하는 뷰

- HTTP 메소드에 따른 구분된 처리, 유효성 검증, 리다이렉트 등의 복잡한 과정을 알아서 처리해준다. 개발자는 필요한 속성이나 메소드만 오버라이딩해주면 된다.

 

● FormView

- 폼을 보여준다. 폼 클래스인 form_class, 폼과 함께 렌더링할 템플릿 이름인 template_name, 폼 처리가 성공한 후에 리다이렉트 시켜줄 success_url 속성이 필요하다.

- 제출된 폼이 유효성 검사를 통과하면 form_valid() 메소드가 호출된다.

 

● CreateView

- FormView의 기능을 포함하며, 새로운 레코드를 생성해서 테이블에 저장한다.

- 작업 대상 모델 클래스를 model 속성으로 지정하고, 폼을 만들 때 사용할 필드를 fields 속성으로 정의한다.

- 기본 template_name은 "[앱명]/[모델명 소문자]_form.html"이다.

 

● UpdateView

- 기존 레코드를 수정하는 뷰라는 점 외에는 CreateView와 유사하다.

- 수정할 레코드는 DetailView와 동일한 방식으로 URLconf에서 지정하면 알아서 처리해준다.

- 기본 template_name은 "[앱명]/[모델명 소문자]_form.html"이다.

 

● DeleteView

- 기존 레코드를 삭제하는 뷰다.

- 렌더링되는 화면은 삭제 확인 화면이기 때문에 폼 데이터를 입력받지 않는다. 즉, model, success_url 속성만 지정하면 되고 fields 속성은 지정하지 않는다. 삭제할 레코드는 DetailView와 동일한 방식으로 URLconf에서 지정하면 알아서 처리해준다.

- 기본 template_name은 "[앱명]/[모델명 소문자]_confirm_delete.html"이다.

 

 

4) Generic Date View

- 날짜 기반 객체들을 날짜 정보에 따라 구분하여 보여주는 뷰

 

● ArchiveIndexView

- 날짜 기반 제네릭 뷰의 최상위 뷰다.

- 대상이 되는 모든 객체를 date_field 속성에 지정한 날짜 필드를 기준으로 내림차순으로 보여준다.

- context 변수로는 object_list와 대상 객체들의 연도를 담고 있는 date_list가 있다.

- 기본 template_name은 "[앱명]/[모델명 소문자]_archive.html"이다.

 

● YearArchiveView

- URLconfig에서 추출된 연도 인자를 기반으로 여러 개의 객체를 대상으로 가능한 월(month)을 알려준다. context 변수인 date_list에 주어진 연도에 해당하는 객체들의 월을 담고 있고, object_list는 기본적으로 None이다.

urlpatterns = [
    path('<int:year>/',
         ArticleYearArchiveView.as_view(),
         name="article_year_archive"),
]

만약 주어진 연도에 해당하는 객체들을 context 변수인 object_list에 저장하고 싶다면, 뷰의 make_object_list 속성을 True로 지정하면 된다. 디폴트는 False이기 때문에 object_list는 기본적으로 None인 것이다.

- 기본 template_name은 "[앱명]/[모델명 소문자]_archive_year.html"이다.

 

● MonthArchiveView

- URLconfig에서 추출된 연, 월에 해당하는 객체들을 보여준다. 월(month) 인자는 숫자형, 문자형 모두 가능하다.

urlpatterns = [
    # Example: /2012/08/
    path('<int:year>/<int:month>/',
         ArticleMonthArchiveView.as_view(month_format='%m'),
         name="archive_month_numeric"),
    # Example: /2012/aug/
    path('<int:year>/<str:month>/',
         ArticleMonthArchiveView.as_view(),
         name="archive_month"),
]

- context 변수로는 object_list와 대상 객체들의 일(day)을 담고 있는 date_list가 있다.

- 기본 template_name은 "[앱명]/[모델명 소문자]_archive_month.html"이다.

 

● WeekArchiveView

- URLconfig에서 추출된 연, 주(week)에 해당하는 객체들을 보여준다. 주 인자는 1부터 53까지의 값을 가진다.

urlpatterns = [
    # Example: /2012/week/23/
    path('<int:year>/week/<int:week>/',
         ArticleWeekArchiveView.as_view(),
         name="archive_week"),
]

- context 변수로는 object_list와 대상 객체들의 연도를 담고 있는 date_list가 있다.

- 기본 template_name은 "[앱명]/[모델명 소문자]_archive_week.html"이다.

 

● DayArchiveView

- URLconfig에서 추출된 연, 월, 일(day)에 해당하는 객체들을 보여준다.

urlpatterns = [
    # Example: /2012/nov/10/
    path('<int:year>/<str:month>/<int:day>/',
         ArticleDayArchiveView.as_view(),
         name="archive_day"),
]

- context 변수로는 object_list와 대상 객체들의 연도를 담고 있는 date_list가 있다.

- 기본 template_name은 "[앱명]/[모델명 소문자]_archive_day.html"이다.

 

TodayArchiveView

- 오늘 날짜에 해당하는 객체들을 보여준다. 즉, URLconfig에서 날짜 인자를 가져올 필요가 없다.

- 뷰 내부에서 datetime.date.today() 함수로 오늘 날짜를 알아내서 처리한다.

- context 변수로는 object_list와 대상 객체들의 연도를 담고 있는 date_list가 있다.

- 기본 template_name은 "[앱명]/[모델명 소문자]_archive_today.html"이다.

 

DateDetailView

- URLconfig에서 추출된 연, 월, 일 및 pk 또는 slug에 해당하는 하나의 객체만을 보여준다.

urlpatterns = [
    path('<int:year>/<str:month>/<int:day>/<int:pk>/',
         DateDetailView.as_view(model=Article, date_field="pub_date"),
         name="archive_date_detail"),
]

그래서 context 변수로는 해당 객체 하나만 할당되어 있는 object를 사용하고, date_list는 사용하지 않는다.

- 기본 template_name은 "[앱명]/[모델명 소문자]_detail.html"이다.

 

 

간단한 사용 예시

먼저, TemplateView를 상속받은 간단한 뷰 클래스를 작성해보았다.

from django.views.generic import TemplateView

class HomeView(TemplateView):
    template_name='home.html'

참고로, 위와 같이 단순히 사용할 템플릿 파일을 지정하는 것이 전부라면 views.py 파일에 클래스형 뷰를 작성할 필요 없이, 다음과 같이 urls.py 파일에 템플릿 파일만 지정해주는 방법을 사용해도 된다.

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', TemplateView.as_view(template_name="home.html"), name='home'),
]

 

또 다른 예시로 폼 처리에 특화된 FormView라는 제네릭 뷰도 사용해보았다. 자세한 설명은 주석을 참고하자.

class MyFormView(FormView):
    form_class=MyForm # 사용자에게 보여줄 폼을 정의한 forms.py 파일 내의 클래스명
    template_name='my_form.html' # 폼과 함께 렌더링할 템플릿 파일 이름
    success_url='/home/' # 폼 처리가 정상적으로 완료되었을 때 리다이렉트 해줄 URL
    
    def form_valid(self, form): # 유효한 폼 데이터로 처리할 로직
        # form을 이용해 관련 로직 처리
        return super(MyFormView, self).form_valid(form) # success_url로 리다이렉션

 

 

추가 개념

- 제네릭 뷰의 작업 대상은 꼭 테이블이여야 하는 것은 아니다. 일반 객체들이 들어 있는 QuerySet 객체면 제네릭 뷰의 작업 대상이 될 수 있다. 그래서 대부분의 제네릭 뷰들은 작업 대상 객체들을 지정할 수 있게 하기 위해 model 속성이나 queryset 속성 또는 get_queryset() 메소드를 제공한다.

- View, TemplateView, RedirectView, FormView 등은 작업 대상을 지정할 필요가 없는 제네릭 뷰이기 때문에 model이나 queryset 속성을 필요로 하지 않는다.

- 더 많은 제네릭 뷰들과 각종 예제들은 아래에 첨부한 공식 문서에서 확인할 수 있다.

 

클래스형 뷰 공식 문서

https://docs.djangoproject.com/ko/4.1/ref/class-based-views/

 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

CCBV

https://ccbv.co.uk/

 

Django Class-Based-View Inspector -- Classy CBV

What are class-based views anyway? Django's class-based generic views provide abstract classes implementing common web development tasks. These are very powerful, and heavily-utilise Python's object orientation and multiple inheritance in order to be exten

ccbv.co.uk

 필자가 봤을 때 직관성 면에서는 ccbv가 더 좋았지만 누락되어 있는 내용이 조금씩 있는 것 같아서, 웬만하면 docs.djangoproject을 참조하는게 좋을 것 같다. 현업자분도 docs.djangoproject를 권하셨다.

 

 

 

6. 제네릭 뷰 오버라이딩

사용할 로직에 맞는 제네릭 뷰를 결정했다면, 결정한 제네릭 뷰에서 어떤 속성과 메소드를 오버라이딩함으로써 기능을 추가하거나 변경할 것인지 결정하면 된다. 오버라이딩에 자주 사용하는 속성과 메소드에는 어떤 것들이 있는지 살펴보자.

 

1) 속성 오버라이딩

(1) model

- 작업 대상 모델 클래스를 지정한다.

 

(2) queryset

- 작업 대상 QuerySet 객체를 지정한다.

- queryset 속성을 지정하면 model 속성은 무시된다.

queryset = Question.objects.all()

 

(3) template_name

- 템플릿 파일명을 문자열로 지정한다.

 

(4) context_object_name

- 템플릿 파일에서 사용할 오브젝트나 오브젝트 리스트에 대한 컨텍스트 변수명으로 object나 object_list가 아닌 다른 이름을 사용하고 싶을 때 변경할 수 있다. 리스트형 제네릭 뷰의 경우에는 보통 뒤에 _list나 s를 붙여서 사용한다.

 

(5) paginate_by

- 페이징 기능은 MultipleObjectMixin의 paginate_by 속성을 지정함으로써 활성화 가능하다. ListView와 날짜 기반 제네릭 뷰는 MultipleObjectMixin을 상속받고 있기 때문에 페이징이 가능한 것이다.

- 장고의 페이징 기능이 활성화되면 객체 리스트에는 페이지별로 구분되어 저장된다. 즉, 디폴트 context 변수인 object_list에는 특정 페이지에 해당하는 객체만 담긴다.

- paginate_by 속성에는 페이지당 몇 개의 항목을 출력할지 정수로 지정한다.

 

(6) date_field

- 날짜 기반 제네릭 뷰에서 기준이 되는 날짜 필드를 지정한다.

 

(7) make_object_list

- YearArchiveView에서 주어진 연도에 해당하는 객체들을 context 변수인 object_list에 저장하고 싶을 때, 이 속성값을 True로 변경한다.

 

(8) form_class

- 폼을 만드는 데 사용할 폼 클래스를 지정한다.

- FormView, CreateView, UpdateView에서 사용한다.

 

(9) initial

- 폼에 사용할 초기 데이터를 딕셔너리 타입으로 지정한다.

- FormView, CreateView, UpdateView에서 사용한다.

 

(10) fields

- 폼에 사용할 필드를 지정한다.

- ModelForm 클래스의 Meta.fields 속성와 동일한 의미다.

- CreateView, UpdateView에서 사용한다.

 

(11) success_url

- 폼에 대한 처리가 성공한 후에 리다이렉트 시켜줄 URL을 지정한다.

- FormView, CreateView, UpdateView, DeleteView에서 사용한다.

 

 

2) 메소드 오버라이딩

(1) get_queryset()

- 출력하려는 객체들이 담긴 QuerySet 객체를 반환한다. 디폴트로는 queryset 속성값을 반환하고, queryset 속성이 지정되지 않았다면 모든 객체가 담긴 QuerySet을 반환한다.

- queryset 속성은 서버를 시작할 때 한 번만 쿼리를 발생시키고, get_queryset() 메소드는 매번 쿼리를 발생시킨다는 차이점이 있다. 즉, get_queryset() 메소드는 URL 변수 등에 따라 검색 조건이 매번 달라지는 경우에 사용된다.

def get_queryset(self):
    return Snack.objects.filter(supply_year__exact=self.kwargs['year']).filter(supply_month__exact=self.kwargs['month'])

 

(2) get_context_data(**kwargs)

- 템플릿에서 사용할 컨텍스트 데이터를 반환한다.

- 디폴트로 사용하는 컨텍스트 변수 이외에 추가로 지정하고 싶은 컨텍스트 변수가 있을 때, 이 메소드를 오버라이딩하면 된다.

def get_context_data(self, **kwargs):
    context=super().get_context_data(**kwargs) # 기존 디폴트 컨텍스트 변수들을 챙겨둔다.
    context['time']=[self.kwargs['year'], self.kwargs['month']] # 컨텍스트 변수 추가
    return context # 추가본 반환

super().get_context_data() 메소드를 통해 기존 디폴트 컨텍스트 변수들을 가져와서, 여기에 새 컨텍스트 변수를 추가해준 것이다.

 

(3) form_valid(form)

- 제출된 폼이 유효성 검사를 통과했을 때 호출되는 메소드다. get_success_url() 메소드가 반환하는 URL로 리다이렉트를 수행한다.

- form.instance에는 form의 내용이 overwrite된 모델 객체가 할당되어 있다.

 

그 외에 더 많은 속성이나 메소드들을 살펴보고 싶다면 아래에 링크한 공식 문서나 [5. 제네릭 뷰의 종류]의 하단에 링크한 CCBV 사이트를 참고하면 도움이 많이 될 것이다.

https://docs.djangoproject.com/ko/4.1/ref/class-based-views/flattened-index/

 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

 

 

7. MRO

- 제네릭 뷰에서는 다중 상속을 사용하는데, 다중 상속에서는 여러 클래스 내에 동일한 이름을 가진 메소드가 존재할 경우 어느 메소드를 먼저 사용할지에 대한 정책이 있어야 한다. 이러한 문제를 해결하기 위해 파이썬에서는 MRO(Method Resolution Order)을 제공한다.

- MRO에서는 하위 클래스일수록 우선 순위가 높고, 다중 상속인 경우는 앞에 정의된 상속 클래스일 수록 우선 순위가 높다.

- 파이썬에서는 모든 클래스마다 MRO 순서를 반복 가능한 객체로 보여주는 __mro__ 속성을 제공한다.

ListView.__mro__

- [5. 제네릭 뷰의 종류]의 하단에 링크한 CCBV 사이트에서도 제네릭 뷰 클래스와 믹스인 클래스들에 대한 MRO를 확인할 수 있다.

 

반응형

'Django' 카테고리의 다른 글

[Django] 프로젝트 개발 순서  (0) 2023.01.25
[Django] 로깅  (0) 2023.01.24
[Django] 템플릿 시스템  (0) 2023.01.24
[Django] 장고 파이썬 쉘 / Model CRUD API  (0) 2023.01.24
[Django] Admin 사이트 커스터마이징  (0) 2023.01.22

댓글