[Two Scoops of Django] 9장. 함수 기반 뷰의 모범적인 이용

guava·2021년 10월 23일
0

Two Scoops of Django

목록 보기
9/12
post-thumbnail

Two Scoops of Django 3.x를 보고 정리한 글입니다.

9.1 함수 기반 뷰의 장점

함수 기반 뷰는 함수적인 특징이 장점이다. 이로 인해 몇가지 흥미로운 전략이 가능하다.

함수기반 뷰를 가지고 코드를 작성할 때 가이드 라인

  1. 뷰 코드는 작을수록 좋다.
  2. 뷰에서 절대 코드를 반복해서 사용하지 말자.
  3. 뷰는 프레젠테이션(presentation) 로직을 처리해야한다. 비즈니스 로직은 가능한 모델(Model) 로직에 적용시키고 만약 해야 한다면 폼 안에서 내재시켜야 한다.
  4. 뷰를 가능한 한 단순하게 유지하라.
  5. 403, 404, 500을 처리하는 커스텀 오류 처리기를 작성하라.
  6. 복잡하게 중첩된 if 블록 구문은 피하라.

9.2 HttpRequest 객체 전달하기

  • 코드 재사용 방지를 위해 미들웨어, 컨텍스트 프로세서같은 설정을 사용할 수 있으나 전역적인 전역 작업에 묶고싶지 않을 수도 있다.
  • 이 때에는 프로젝트에서 사용 가능한 유틸리티 함수를 만드는게 좋다.
  • 유틸리티 함수에서 HttpRequest객체 주된 인자로 삼으면 대부분의 인자 구성이 단순해 진다는걸 명심하자.
  • 다음 예시를 통해 HttpRequest를 인자로 받는 유틸리티 함수를 정의해보겠다.

Example 9.1: 유틸 함수 정의 (sprinkles/utils.py)

유틸리티 함수다. HttpRequest객체를 받아서 HttpRequest 객체를 반환하고있다.
권한이 없으면 PermissionDenied를 일으킨다.

from django.core.exceptions import PermissionDenied 
from django.http import HttpRequest

def check_sprinkle_rights(request: HttpRequest) -> HttpRequest: 
    if request.user.can_sprinkle or request.user.is_staff:
        return request
        
    # Return a HTTP 403 back to the user
    raise PermissionDenied

Example 9.2: 유틸 함수 개선 (sprinkles/utils.py)

위의 유틸리티 함수를 수정하였다. HttpRequest 객체를 받아서 권한이 있다면 추가적인 속성을 덧붙인 후에 HttpRequest 객체를 반환하고있다. 동적언어인 파이썬의 특성을 활용하였다. 마찬가지로 권한이 없으면 PermissionDenied 예외를 발생시킨다.

from django.core.exceptions import PermissionDenied 
from django.http import HttpRequest, HttpResponse

def check_sprinkles(request: HttpRequest) -> HttpRequest: 
    if request.user.can_sprinkle or request.user.is_staff:
        # 아래 내용을 추가하면 템플릿을 좀더 간결하게 작성할 수 있다.
        # {% if request.user.can_sprinkle or request.user.is_staff%}
        # 가 아니라
        # {% if request.can_sprinkle %} 
        # 와 같이 작성하면 된다.
        request.can_sprinkle = True 
        return request
    # Return a HTTP 403 back to the use
    raise PermissionDenied

Example 9.3: FBV에서 정의한 유틸함수 check_sprinkle에 HttpRequest객체를 전달

함수 기반 뷰에서 유틸 함수인 HttpRequest객체를 전달하고 있다.

# sprinkles/views.py
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.http import HttpRequest, HttpResponse

from .models import Sprinkle
from .utils import check_sprinkles


def sprinkle_list(request: HttpRequest) -> HttpResponse:
    """Standard list view"""
    request = check_sprinkles(request)  # 유틸 함수에 HttpRequest 객체를 전달
    return render(request, "sprinkles/sprinkle_list.html", {"sprinkles": Sprinkle.objects.all()})


def sprinkle_detail(request: HttpRequest, pk: int) -> HttpResponse:
    """Standard detail view"""
    request = check_sprinkles(request)  # 유틸 함수에 HttpRequest 객체를 전달
    sprinkle = get_object_or_404(Sprinkle, pk=pk)
    return render(request, "sprinkles/sprinkle_detail.html", {"sprinkle": sprinkle})


def sprinkle_preview(request: HttpRequest) -> HttpResponse:
    """새 sprinkle의 프리뷰
    하지만 check_sprinkles함수가 사용되지는 않는다
    """
    sprinkle = Sprinkle.objects.all()
    return render(request, "sprinkles/sprinkle_preview.html", {"sprinkle": sprinkle})

Example 9.4: CBV에서 정의한 유틸함수 check_sprinkle에 HttpRequest객체를 전달

CBV로의 통합도 쉽다.

from django.views.generic import DetailView

from .models import Sprinkle
from .utils import check_sprinkles


class SprinkleDetail(DetailView):
    """Standard detail view"""
    model = Sprinkle

    def dispatch(self, request, *args, **kwargs): 
        request = check_sprinkles(request)  # 유틸 함수에 HttpRequest 객체를 전달
        return super().dispatch(request, *args, **kwargs)   

유틸 함수를 정의하고 FBV에서 HttpRequest 객체를 전달했다. 하지만 FBV 특성상 함수안에서 함수를 반복적으로 사용할 수 밖에 없고, 프로젝트가 커지면 언제 사용이 되었는지 찾기가 어려워질 수 있다.
이는 데코레이터를 통해서 개선할 수 있다.

9.3 편리한 데코레이터

데코레이터는 가독성을 위한 문법이다. 기능을 추가한 것이 아니라 코드를 간결하게 해주기 위해 추가되었다.

Example 9.5: Simple Decorator Template

import functools

def decorator(view_func):
    @functools.wraps(view_func)
    def new_view_func(request, *args, **kwargs):
        # 여기에서 request (HttpRequest) 객체를 수정하면 된다.
        response = view_func(request, *args, **kwargs)
        # 여기에서 response (HttpResponse) 객체를 수정하면 된다.
        return response
    return new_view_func

Example 9.6: Decorator Example

# sprinkles/decorators.py
import functools

from . import utils


# Example 9.5의 데코레이터 예시를 기반으로 작성
def check_sprinkles(view_func):
    """Check if a user can add sprinkles"""

    @functools.wraps(view_func)
    def new_view_func(request, *args, **kwargs):
        # request객체를 utils.can_aprinkle 함수에 전달한다.
        request = utils.can_sprinkle(request)
        
        # view 함수를 호출한다.
        response = view_func(request, *args, **kwargs)
        
        # HttpResponse 객체를 반환한다.
        return response

    return new_view_func

Example 9.7: Example of Using a Decorator

# sprinkles/views.py
from django.shortcuts import get_object_or_404, render 

from .decorators import check_sprinkles
from .models import Sprinkle

# view에 Example 9.6에서 정의한 데코레이터를 연결한다.
@check_sprinkles
def sprinkle_detail(request: HttpRequest, pk: int) -> HttpResponse: 
    """Standard detail view"""
    
    sprinkle = get_object_or_404(Sprinkle, pk=pk)
    
    return render(request, "sprinkles/sprinkle_detail.html",
        {"sprinkle": sprinkle})

functools.wraps()라는 데코레이터 함수가 사용되고 있다. 이 함수는 docstrings같은 중요한 데이터를 포함한 메타데이터를 새로이 데코레이션 되는 함수에 복사해 주는 도구다. 꼭 필요하지는 않으며 프로젝트 관리를 위해 사용한다.

데코레이터를 남용하면 안된다. 너무 많은 데코레이터는 뷰를 복잡하게 한다. 얼마나 많은 데코레이터가 뷰에 사용될 것인지 미리 한정지어두자.

9.4 HttpResponse 객체 넘겨주기

  • HttpResponse객체 또한 함수와 함수 사이에서 전달받을 수 있다.
  • Middleware.process_request() 메서드를 참고하자.
  • Example 9.5에서도 활용 방법을 주석에 명시하였다.

9.5 요약

  • 장고에서 함수기반 뷰는 많이 활용되고 있다.
  • 모든 함수는 HttpRequest 객체를 받고 HttpResponse 객체를 반환한다는 사실을 명심하자.
  • HttpRequest와 HttpResponse를 변경하는 함수를 정의할 수 있으며 데코레이터로 구성할 수 있다.
  • 함수기반 뷰의 모든 교훈은 클래스 기반뷰에서도 똑같이 적용될 수 있다.

0개의 댓글