10장. 클래스 기반 뷰의 모범적인 이용

10장. 클래스 기반 뷰의 모범적인 이용

반응형

10장. 클래스 기반 뷰의 모범적인 이용

장고의 뷰는 요청 객체를 받고 응답 객체를 반환하는 내장 함수다.

함수 기반 뷰에서는 뷰 함수 자체가 내장 함수이고, 클래스 기반 뷰에서는 뷰 클래스가 내장 함수를 반환하는 as_view() 클래스 메서드를 제공한다.

django.views.generic.View에서 해당 매커니즘이 구현되며 모든 클래스 기반 뷰는 이 클래스를 직간접적으로 상속받아 이용한다.

10.1 클래스 기반 뷰의 가이드 라인

뷰 코드의 양은 적으면 적을수록 좋다.

뷰 안에서 같은 코드를 반복적으로 이용하지 말자.

뷰는 프레젠테이션 로직에서 관리하도록 하자. 비즈니스 로직은 모델에서 처리하자. 매우 특별한 경우에는 폼에서 처리하자.

뷰는 간단 명료해야 한다.

403, 404, 500 에러 핸들링에 클래스 기반 뷰는 이용하지 않는다. 대신 함수 기반 뷰를 이용하자

믹스인은 간단 명료해야 한다.

10.2 클래스 기반 뷰와 믹스인 이용하기

클래스 기반 뷰 (Classy Class-Based Views) 참고 링크 ( 링크 )

프로그래밍에서 mixin은 실체화된 클래스가 아니라 상속해줄 기능들을 제공하는 클래스를 의미한다.

프로그래밍 언어에서 다중 상속을 해야 할 때 믹스인을 쓰면 클래스에 향상된 기능과 동작을 추가할 수 있다.

mixin을 사용하여 자체 뷰 클래스를 구성할 때 다음 규칙을 권장

Django가 제공하는 기본 뷰 클래스는 항상 오른쪽으로 이동한다. mixin은 기본 뷰에서부터 왼쪽으로 진행한다. mixin은 파이썬의 기본 객체 타입을 상속 해야한다. 즉 다른 클래스에 상속 되어서는 안된다.

from django.views.generic import TemplateView class FreshFruitMixin: def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["has_fresh_fruit"] = True return context class FruitFlavorView(FreshFruitMixin, TemplateView): template_name = "fruity_flavor.html"

FruitFlavorView 클래스는 FreshFruitMixin과 TemplateView를 상속 받고 있다.

TemplateView가 장고에서 제공하는 기본 클래스이기 때문에 가장 오른쪽에 위치

FreshFruitMixin은 Mixin이기 때문에 기본 클래스의 왼쪽에 위치

10.3 어떤 장고 제네릭 클래스 기반 뷰를 어떤 태스크에 이용할 것인가?

Django의 각 클래스 기반 뷰의 목적과 이름을 나열

장고 클래스 기반 뷰/ 제네릭 클래스 기반 뷰의 이용에 대한 세 가지 의견

'제네릭 뷰의 모든 종류를 최대한 이용' 장고를 사용하는 이유는 개발 작업의 양을 최소화 하는데 목적이 있다는 철학에 기반을 둔 그룹의 주장. 작업량을 최소화 하기 위해 제네릭 뷰가 제공하는 모든 종류의 뷰를 최대한 이용하기를 장려. 이러한 방법으로 다수의 프로젝트를 빠르고 쉽게 개발, 유지 보수 하는데 큰 성공을 거둠

'심플하게 django.views.generic.View 하나로 모든 뷰를 다 처리' 장고의 기본 클래스 기반 뷰로도 충분히 원하는 기능을 다 소화할 수 있다. 난해한 태스크들에 대해서 기본 클래스를 사용하는 방법이 매우 효율적임을 발견 '뷰를 정말 상속할 것이 아닌 이상 그냥 무시' 프로젝트를 시작할 때 읽기 쉽고 이해하기도 쉬운 함수 기반 뷰로 시작한 후 클래스 기반 뷰가 반드시 필요한 상황이 되었을 때만 클래스 기반 뷰를 이용하자. 그렇다면 어떠한 상황에서 클래스 기반 뷰를 이용해야 할까? 여러 뷰에서 재사용할 수 있는 코드의 양이 꽤 많은 경우이다.

10.4 장고 클래스 기반 뷰에 대한 일반적인 팁

10.4.1 인증된 사용자에게만 장고 클래스 기반 뷰/제네릭 클래스 기반 뷰 접근 가능하게 하기

from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import DetailView from .models import Flavor class FlavorDetailView(LoginRequiredMixin, DetailView): model = Flavor

10.4.2 뷰에서 유효한 폼을 이용하여 커스텀 액션 구현하기

from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import CreateView from .models import Flavor class FlavorCreateView(LoginRequiredMixin, CreateView): model = Flavor fields = ['title', 'slug', 'scoops_remaining'] def form_valid(self, form): # 커스텀 로직이 이곳에 위치 # from_valid()의 반환형은 django.http.HttpResponseRedirect가 된다. return super().form_valid(form)

10.4.3 뷰에서 부적합한 폼을 이용하여 커스텀 액션 구현하기

from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import CreateView from .models import Flavor class FlavorCreateView(LoginRequiredMixin, CreateView): model = Flavor def form_invalid(self, form): # 커스텀 로직이 이곳에 위치 # form_invalid()는 django.http.HttpResponse를 반환한다. return super().form_invalid(form)

10.4.4 뷰 객체 이용하기

콘텐츠를 렌더링하는 데 클래스 기반 뷰를 이용한다면 자체적인 메서드와 속성을 제공하는 뷰 객체를 이용하여 다른 메서드나 속성에서 호출이 가능하게 하는 방법을 고려해 볼 수 있다.

from django.contrib.auth.mixins import LoginRequiredMixin from django.utils.functional import cached_property from django.views.generic import UpdateView, TemplateView from .models import Flavor from .tasks import update_user_who_favorited class FavoriteMixin: @cached_property def likes_and_favorites(self): """likes와 favorites의 딕셔너리를 반환""" likes = self.object.likes() favorites = self.object.favorites() return { "likes": likes, "favorites": favorites, "favorites_count": favorites.count(), } class FlavorUpdateView(LoginRequiredMixin, FavoriteMixin, UpdateView): model = Flavor fields = ['title', 'slug', 'scoops_remaining'] def form_valid(self, form): update_user_who_favorited( instance=self.object, favorites=self.likes_and_favorites['favorites'] ) return super().form_valid(form) class FlavorDetailView(LoginRequiredMixin, FavoriteMixin, TemplateView): model = Flavor def form_valid(self, form): likes_and_favorites = self.likes_and_favorites --- {# flavors/base.html #} {% extends "base.html" %} {% block likes_and_favorites %} Likes: {{ view.likes_and_favorites.likes }} Favorites: {{ view.likes_and_favorites.favorites_count}} {% endblock likes_and_favorites %}

다양항 flavors 앱 템플릿에서 해당 속성을 호출할 수 있다는 장점이 있다.

10.5 제네릭 클래스 기반 뷰와 폼 사용하기

from django.db import models from django.urls import reverse class Flavor(models.Model): class Scoops(models.IntegerChoices) SCOOPS_0 = 0 SCOOPS_1 = 1 title = models.CharField(max_length=255) slug = models.SlugField(unique=True) scoops_remaining = models.IntegerField(choices=Scoops.choices, default=Scoops.SCOOPS_0) def get_absolute_url(self): return reverse("flavors:detail", kwargs={"slug": self.slug})

10.5.1 뷰 + 모델폼 예제

가장 단순하고 일반적인 장고 폼 시나리오. FlavorCreateView: 새로운 종류의 아이스크림을 추가하는 폼 FlavorUpdateView: 기존 아이스크림을 수정하는 폼 FlavorDetailView: 아이스크림 추가와 변경을 확정하는 폼

from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import CreateView, DetailView, UpdateView from .models import Flavor class FlavorCreateView(LoginRequiredMixin, CreateView): model = Flavor fields = ['title', 'slug', 'scoops_remaining'] class FlavorUpdateView(LoginRequiredMixin, UpdateView): model = Flavor fields = ['title', 'slug', 'scoops_remaining'] class FlavorDetailView(DetailView): model = Flavor

from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import CreateView, DetailView, UpdateView from .models import Flavor class FlavorActionMixin: fields = ['title', 'slug', 'scoops_remaining'] @property def success_msg(self): return NotImplemented def form_valid(self, form): messages.info(self.request, self.success_msg) return super().form_valid(form) class FlavorCreateView(LoginRequiredMixin, FlavorActionMixin, CreateView): model = Flavor success_msg = "Flavor created!" class FlavorUpdateView(LoginRequiredMixin, FlavorActionMixin, UpdateView): model = Flavor success_msg = "Flavor updated!" class FlavorDetailView(DetailView): model = Flavor

믹스인은 object를 상속해야 한다.

FlavorActionMixin은 이미 존재하는 믹스인이나 뷰를 상속하지 않고 파이썬의 object 타입을 상속한다는 점을 알아두자. 믹스인은 가능한 한 아주 단순한 상속의 연결이 되어야 한다는 것을 잊지 말자.

10.6 django.views.generic.View 이용하기

각 HTTP 메서드를 중첩된 if 문으로 처리하는 함수 기반 뷰를 작성하거나 get_context_data()와 form_valid() 메서드 뒤에 숨어 있는 HTTP 메서드들이 위치한 클래스 기반 뷰를 작성하는 대신 이 메서드들에 직접 접근할 수 있다면 어떨까?

from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import get_object_or_404 from django.shortcuts import render, redirect from django.views.generic import View from .forms import FlavorForm from .models import Flavor class FlavorView(LoginRequiredMixin, View): def get(self, request, *args, **kwargs): # Flavor 객체의 디스플레이를 처리 flavor = get_object_or_404(Flavor, slug=kwargs['slug']) return render(request, "flavors/flavor_detail.html", {"flavor": flavor}) def post(self, request, *args, **kwargs): # Flavor 객체의 업데이트를 처리 flavor = get_object_or_404(Flavor, slug=kwargs['slug']) form = FlavorForm(request.POST, instance=flavor) if form.is_valid(): form.save() return redirect("flavors:detail", flavor.slug) --- 커스텀 로직 class FlavorPDFView(LoginRequiredMixin, View): def get(self, request, *args, **kwargs): # 종류 할당 flavor = get_object_or_404(Flavor, slug=kwargs['slug']) # 응답 생성 response = HttpResponse(content_type='application/pdf') # PDF 스트림 생성 후 응답에 할당 response = make_flavor_pdf(response, flavor) return response

요약

클래스 기반 뷰와 폼 패턴을 잘 다루어 둔다면 또 다른 강력한 무기를 습득하게 된다.!

반응형

from http://taxijjang.tistory.com/145 by ccl(A) rewrite - 2021-10-11 00:27:13