Django 커스텀 데코레이터의 동작 원리와 작성법

데코레이터는 Django에서 뷰 함수나 클래스에 공통적인 로직을 쉽게 추가할 수 있도록 해주는 강력한 도구입니다. 이 글에서는 데코레이터의 작동 원리작성 방법을 단계별로 설명하여, 여러분이 직접 커스텀 데코레이터를 작성할 수 있도록 돕겠습니다.

1. 데코레이터란 무엇인가?

데코레이터는 Python의 고차 함수(Higher-order function)입니다. 즉, 하나의 함수를 인수로 받아 내부에서 새로운 함수를 반환하는 함수입니다.

  • 데코레이터의 역할:
    • 함수를 감싸 특정 기능을 추가하거나 수정할 수 있습니다.
    • 코드를 재사용 가능하고 간결하게 만들어줍니다.

2. 데코레이터의 기본 동작 원리

데코레이터의 작동 원리를 이해하기 위해 간단한 예제를 살펴봅시다.

def simple_decorator(func):
    def wrapper():
        print("Before the function runs")
        func()
        print("After the function runs")
    return wrapper

@simple_decorator
def say_hello():
    print("Hello!")

say_hello()

출력 결과:

Before the function runs
Hello!
After the function runs

설명:

  1. @simple_decoratorsay_hello 함수를 simple_decorator 함수에 전달합니다.
  2. simple_decorator는 내부에서 새로운 wrapper 함수를 생성해 반환합니다.
  3. say_hello가 호출될 때, 원래 함수 대신 wrapper가 실행됩니다.
  4. wrapper 함수 내부에서 추가 동작(프린트 메시지)을 실행하고, 원래 함수를 호출합니다.

3. Django에서 커스텀 데코레이터를 작성하는 원리

Django에서 데코레이터를 작성하려면 뷰 함수의 인자인 request 객체를 다룰 줄 알아야 합니다. 이를 기반으로 특정 조건에 따라 HTTP 응답을 조작하거나 로직을 실행할 수 있습니다.

4. 커스텀 데코레이터 작성의 단계별 구조

다음은 Django에서 커스텀 데코레이터를 작성하는 기본 구조입니다:

  1. functools.wraps 사용: 원래 함수의 이름, 문서화 문자열 등 메타데이터를 유지하기 위해 @wraps를 사용합니다.
  2. request를 기반으로 로직 추가: 요청 객체를 분석하거나 검증하는 로직을 작성합니다.
  3. 원래 함수를 호출하거나, 대체 응답 반환: 특정 조건에 따라 원래 함수 호출을 차단하고 다른 응답을 반환할 수 있습니다.

다음은 예제 코드입니다:

from functools import wraps
from django.http import HttpResponseForbidden

def custom_decorator(view_func):
    @wraps(view_func)
    def _wrapped_view(request, *args, **kwargs):
        # 요청 객체에서 사용자 정보를 확인
        if not request.user.is_authenticated:
            return HttpResponseForbidden("You must be logged in to access this page.")
        
        # 원래 뷰 함수를 호출
        return view_func(request, *args, **kwargs)
    
    return _wrapped_view

A visually engaging illustration explaining how a Django custom decorator works

5. 데코레이터에 매개변수를 추가하려면?

데코레이터가 매개변수를 받아야 하는 경우에는 함수를 두 번 감싸는 구조를 사용해야 합니다. 그 이유는 첫 번째 함수는 데코레이터의 매개변수를 처리하기 위해 사용되며, 실제 뷰 함수를 감싸는 역할은 두 번째 함수에서 이루어지기 때문입니다.

기본 구조:

def decorator_with_params(param1, param2):
    def actual_decorator(view_func):
        @wraps(view_func)
        def _wrapped_view(request, *args, **kwargs):
            # 매개변수 param1, param2를 활용한 로직
            return view_func(request, *args, **kwargs)
        return _wrapped_view
    return actual_decorator

설명:

  1. 첫 번째 함수 decorator_with_params데코레이터의 매개변수(param1, param2)를 처리합니다.
  2. 내부에서 실제 뷰 함수를 감싸는 데코레이터 함수(actual_decorator)를 반환합니다.
  3. actual_decorator 내부에서 원래 뷰 함수를 호출하거나 추가 로직을 실행합니다.

6. 실전 예제: 시간대 제한 데코레이터

다음은 특정 시간대에만 접근 가능한 데코레이터 예제입니다. 이 데코레이터는 매개변수로 허용 시간대를 받아 처리합니다.

from datetime import datetime
from django.http import HttpResponseForbidden

def time_restricted_access(start_hour, end_hour):
    def decorator(view_func):
        @wraps(view_func)
        def _wrapped_view(request, *args, **kwargs):
            current_hour = datetime.now().hour
            if not (start_hour <= current_hour < end_hour):
                return HttpResponseForbidden("This page is accessible only during certain hours.")
            return view_func(request, *args, **kwargs)
        return _wrapped_view
    return decorator

@time_restricted_access(9, 17)  # 오전 9시부터 오후 5시까지 허용
def working_hours_view(request):
    return render(request, 'working_hours.html')

동작 원리 설명:

  1. time_restricted_access매개변수(start_hour, end_hour)를 처리하는 첫 번째 함수입니다.
  2. 첫 번째 함수는 내부에서 뷰를 감싸는 데코레이터 함수를 반환합니다.
  3. 데코레이터 함수request를 받아서 현재 시간(datetime.now().hour)을 확인하고, 시간 조건에 따라:
    • 조건이 만족하면 뷰를 호출하고 결과를 반환합니다.
    • 조건이 불만족하면 HttpResponseForbidden으로 차단합니다.

왜 함수를 두 번 감싸야 할까?

함수를 두 번 감싸는 이유는 매개변수 처리와 뷰 함수 처리의 역할을 분리하기 위해서입니다.

  • 첫 번째 함수: 매개변수(start_hour, end_hour)를 받아 내부에서 데코레이터를 생성합니다.
  • 두 번째 함수: 생성된 데코레이터가 뷰 함수를 감싸고, 요청에 대해 동작합니다.

이 구조는 데코레이터를 유연하고 재사용 가능하게 만들어줍니다.

7. 클래스 기반 뷰(CBV)에서 사용하기

클래스 기반 뷰에는 method_decorator를 활용해야 합니다.

from django.utils.decorators import method_decorator
from django.views import View

@method_decorator(time_restricted_access(9, 17), name='dispatch')
class MyView(View):
    def get(self, request):
        return render(request, 'my_template.html')

8. 결론: 커스텀 데코레이터 작성 시 유의점

  1. 단순함 유지: 데코레이터 로직은 간단하고 명확해야 합니다.
  2. 재사용성 고려: 특정 로직이 여러 뷰에서 사용된다면 데코레이터로 추출하세요.
  3. 매개변수 처리: 매개변수가 필요한 경우 함수를 두 번 감싸는 구조를 사용하세요.
  4. 메타데이터 보존: @wraps를 사용해 원래 함수의 메타데이터를 유지하세요.

마무리

이제 여러분은 Django에서 커스텀 데코레이터의 내부 동작 원리를 이해하고, 매개변수를 받아 동작하는 데코레이터를 작성할 수 있게 되었습니다. 이 글을 기반으로 프로젝트에 맞는 데코레이터를 작성해보세요!