Two Scoops of Django 3.x를 보고 정리한 글입니다.
함수 기반 뷰는 함수적인 특징이 장점이다. 이로 인해 몇가지 흥미로운 전략이 가능하다.
유틸리티 함수다. 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
위의 유틸리티 함수를 수정하였다. 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
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})
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 특성상 함수안에서 함수를 반복적으로 사용할 수 밖에 없고, 프로젝트가 커지면 언제 사용이 되었는지 찾기가 어려워질 수 있다.
이는 데코레이터를 통해서 개선할 수 있다.
데코레이터는 가독성을 위한 문법이다. 기능을 추가한 것이 아니라 코드를 간결하게 해주기 위해 추가되었다.
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
# 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
# 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같은 중요한 데이터를 포함한 메타데이터를 새로이 데코레이션 되는 함수에 복사해 주는 도구다. 꼭 필요하지는 않으며 프로젝트 관리를 위해 사용한다.
데코레이터를 남용하면 안된다. 너무 많은 데코레이터는 뷰를 복잡하게 한다. 얼마나 많은 데코레이터가 뷰에 사용될 것인지 미리 한정지어두자.