택시짱의 개발 노트

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

Django(장고)

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

택시짱 2021. 10. 11. 00:12

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

장고의 뷰는 요청 객체를 받고 응답 객체를 반환하는 내장 함수다.
함수 기반 뷰에서는 뷰 함수 자체가 내장 함수이고, 클래스 기반 뷰에서는 뷰 클래스가 내장 함수를 반환하는 as_view() 클래스 메서드를 제공한다.
django.views.generic.View에서 해당 매커니즘이 구현되며 모든 클래스 기반 뷰는 이 클래스를 직간접적으로 상속받아 이용한다.

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

  • 뷰 코드의 양은 적으면 적을수록 좋다.
  • 뷰 안에서 같은 코드를 반복적으로 이용하지 말자.
  • 뷰는 프레젠테이션 로직에서 관리하도록 하자. 비즈니스 로직은 모델에서 처리하자. 매우 특별한 경우에는 폼에서 처리하자.
  • 뷰는 간단 명료해야 한다.
  • 403, 404, 500 에러 핸들링에 클래스 기반 뷰는 이용하지 않는다. 대신 함수 기반 뷰를 이용하자
  • 믹스인은 간단 명료해야 한다.

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

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

  • 프로그래밍에서 mixin은 실체화된 클래스가 아니라 상속해줄 기능들을 제공하는 클래스를 의미한다.
  • 프로그래밍 언어에서 다중 상속을 해야 할 때 믹스인을 쓰면 클래스에 향상된 기능과 동작을 추가할 수 있다.

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

  1. Django가 제공하는 기본 뷰 클래스는 항상 오른쪽으로 이동한다.
  2. mixin은 기본 뷰에서부터 왼쪽으로 진행한다.
  3. 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의 각 클래스 기반 뷰의 목적과 이름을 나열

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

  1. '제네릭 뷰의 모든 종류를 최대한 이용'
  2. 장고를 사용하는 이유는 개발 작업의 양을 최소화 하는데 목적이 있다는 철학에 기반을 둔 그룹의 주장. 작업량을 최소화 하기 위해 제네릭 뷰가 제공하는 모든 종류의 뷰를 최대한 이용하기를 장려. 이러한 방법으로 다수의 프로젝트를 빠르고 쉽게 개발, 유지 보수 하는데 큰 성공을 거둠
  1. '심플하게 django.views.generic.View 하나로 모든 뷰를 다 처리'
  2. 장고의 기본 클래스 기반 뷰로도 충분히 원하는 기능을 다 소화할 수 있다. 난해한 태스크들에 대해서 기본 클래스를 사용하는 방법이 매우 효율적임을 발견
  3. '뷰를 정말 상속할 것이 아닌 이상 그냥 무시'
  4. 프로젝트를 시작할 때 읽기 쉽고 이해하기도 쉬운 함수 기반 뷰로 시작한 후 클래스 기반 뷰가 반드시 필요한 상황이 되었을 때만 클래스 기반 뷰를 이용하자. 그렇다면 어떠한 상황에서 클래스 기반 뷰를 이용해야 할까? 여러 뷰에서 재사용할 수 있는 코드의 양이 꽤 많은 경우이다.

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 %} <ul>
    <li>Likes: {{ view.likes_and_favorites.likes }}</li>
    <li>Favorites: {{ view.likes_and_favorites.favorites_count}}</li>
</ul>
{% 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 뷰 + 모델폼 예제

  • 가장 단순하고 일반적인 장고 폼 시나리오.
    1. FlavorCreateView: 새로운 종류의 아이스크림을 추가하는 폼
    2. FlavorUpdateView: 기존 아이스크림을 수정하는 폼
    3. 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

요약

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