Tiny Star

✨Framework+Library/🔵Django

[Django] @action과 @api_view : View 하나에 같은 HTTP 요청 메서드가 두개일 때 API 구현하기

청크 2024. 4. 25. 16:21

게시판과 댓글 CRUD의 로직이 완성됬다.

 

이후 내가 구현하고 싶은 것 게시판이 삭제 될 경우 댓글 전부 같이 삭제하는 로직

 

즉, 게시판 삭제 시 댓글의 is_delete가 False값에서 True값으로 일괄적으로 변경이 되어야 한다.

 

(공부하는 사람들의 게시물을 보면 종종 완전히 remove 시켜버리는 경우가 있는데, 현업에서 개발은 그렇지 않다.

API는 DELETE지만 실제 내부로직은 UPDATE가 맞음. DB에 한번 들어간 데이터를 지우는 일은 거의 없다.)

 

다시 본론으로 돌아와서 Comment View로직은 이미 댓글을 개별삭제 할 수 있는 delete 메서드가 있는 상태에

추가로 게시글 삭제 시 댓글이 삭제되는 delete메서드가 추가로 필요하다.

 

이럴 때 쓸 수 있는 데코레이터가 @action과 @api_view가 있는데

이 두 데코레이터는 Django REST Framework에서 API를 구현하는 기능으로 각각 사용목적과 구현방식에 차이가 있다.

 

@api_view는 구글에 사용예가 엄청 많지만 나는 @action을 사용 할 예정!

근데 정보가 별로 없길래 두가지를 비교해보고 사용방법도 적어보려한다.


@api_view

먼저 제일 많이 사용하는 것 같은 api_view는 Django REST Framework에서 함수 기반 뷰를 만들 때 사용된다.

즉, @api_view는 일반적인 장고 뷰함수를 DRF 기능을 지원하는 API뷰로 변환이 되는 것이다.

 

조금 더 쉽게 풀어보자면 이 데코레이터는 함수에 적용할 수 있고,

적용이 된 해당함수는 지정된 HTTP 메서드를 처리할 수 있는 API뷰로 변환되어

이 해당 함수 내에서 요청을 처리하고 Response 객체를 반환한다고 생각하면 조금 더 쉽다.

 

또한 클래스 기반 뷰와 달리, 단순한 함수에 적용되어 그 함수를 API 엔드포인트로 변환하기 때문에 별도의 클래스 지정이 불필요하다.

 

이 기능은 요청 객체를 파싱하고, 적절한 파서와 렌더러를 사용하여 요청과 응답을 처리한다.

 

주요 기능을 다시 정리해보자면

1. HTTP Method Control : 함수가 처리할 수 있는 HTTP 메서드를 지정한다.

2. 사용이 간단하여 작은 규모의 API또는 단일 기능에 적합하다.

 

예시코드1

from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(['GET', 'POST'])
def example_view(request):
	 # GET 요청 처리
    if request.method == 'GET':
        # 데이터 반환
        return Response({'message': 'This is GET request'})
      # POST 요청 처리
    elif request.method == 'POST':
        # 데이터 생성
        return Response({'message': 'This is POST request'})

예제를 보면 @api_view 내에 지정된 GET, POST에 대한 요청만 처리할 수 있는 뷰로 설정되어

request 파라미터를 받아서 필요한 요청을 수행한 뒤 결과를 Response객체로 반환한다.

 

또 하나의 특징이 api_view는 함수 기반 뷰이기 때문에

함수 기반 뷰를 사용할 때는 장고의 URLconf를 통해 URL을 함수와 연결시켜 주어야 외부에서 접근이 가능하다.

# urls.py 파일
from django.urls import path
from .views import simple_api

urlpatterns = [
    path('api/example/', simple_api, name='example_api')
]

이 설정을 통해, /api/example/ 경로로 들어오는 GET 또는 POST 요청이 simple_api 함수로 라우팅되어 적절히 처리되는 것이다.

 


@action

그 다음 내가 사용하려는 @action 데코레이터.

@action은 주로 Viewset 클래스 내에서 사용되며 표준 CRUD 외에 추가적인 사용자 정의 동작을 만들 때 사용하며,

Viewset의 일부로 URL 구성이나 메서드, 세부사항 수준 등을 세세히 설정해줄 수 있다.

 

예를 들면 특정 사용자를 팔로우/언팔로우 하는 기능이나 항목에 대한 평가 기능 등을 구현할 때 사용한다.

 

장고의 decorators 가이드를 뜯어보면 아래처럼 주요 파라미터와 각각의 목적을 설명해주고 있다.

def action(methods=None, detail=None, url_path=None, url_name=None, **kwargs):
    """
    Mark a ViewSet method as a routable action.

    `@action`-decorated functions will be endowed with a `mapping` property,
    a `MethodMapper` that can be used to add additional method-based behaviors
    on the routed action.

    :param methods: A list of HTTP method names this action responds to.
                    Defaults to GET only.
    :param detail: Required. Determines whether this action applies to
                   instance/detail requests or collection/list requests.
    :param url_path: Define the URL segment for this action. Defaults to the
                     name of the method decorated.
    :param url_name: Define the internal (`reverse`) URL name for this action.
                     Defaults to the name of the method decorated with underscores
                     replaced with dashes.
    :param kwargs: Additional properties to set on the view.  This can be used
                   to override viewset-level *_classes settings, equivalent to
                   how the `@renderer_classes` etc. decorators work for function-
                   based API views.
    """
    methods = ['get'] if methods is None else methods
    methods = [method.lower() for method in methods]

    assert detail is not None, (
        "@action() missing required argument: 'detail'"
    )

    # name and suffix are mutually exclusive
    if 'name' in kwargs and 'suffix' in kwargs:
        raise TypeError("`name` and `suffix` are mutually exclusive arguments.")

 

1. methods

이 액션이 응답할 HTTP 메소드의 리스트로 @action(methods=['get']) 이런 형식으로 사용이 가능하다.

기본값은 get 메서드이며, 이외에 post, patch, put, delete 등 다양하게 사용할 수 있다.

 

2. detail

이 값이 True인 경우 /users/{id}/activate/ 이런식으로 특정 인스턴스에 액션이 적용된다.

즉, True는 URL에 ID값이 포함이 된다는 것이고 False이면 전체 컬렉션에 적용이 된다는 의미이다.

detail 매개변수는 필수값이기 때문에 True, False 중 선택을 해야한다.

 

3. url_path

url_path는 액션에 접근하기 위한 URL 세그먼트 정의가 가능하다.

쉽게 이야기하면 선택적 매개변수로 특정 액션에 URL접미사를 커스터마이징하는데 사용되며

url_path를 지정하지 않을 경우 해당 메서드 이름이 URL 접미사로 붙게된다.

 

만약 아래와 같은 코드가 있다고 가정해보자

from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import viewsets

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(detail=True, methods=['post'], url_path='activate1')
    def activate(self, request, pk=None):
        # 사용자 활성화 로직
        pass

이 경우, activate 메소드에 @action 데코레이터가 적용되며, url_path='activate1'가 지정되었고

생성되는 URL은 /users/{id}/activate1/ 이런식으로 생성이 되는 것이다.

 

url_path='activate1'을 지정하지 않은 경우는 /users/{id}/activate/ 이렇게 메서드 이름으로 URL 접미사가 결정된다.

 

4. url_name

내부 URL이름을 정의하는 파라미터로 특정 액션에 대한 URL 이름을 지정하는 데 사용된다.

이 이름은 Django의 URL 역참조(reverse URL lookups) 시스템에서 사용되어 프로그래밍적으로 

URL을 생성할 때 참조할 수 있는 이름을 제공한다.

 

조금 더 쉽게 설명해보자면  템플릿이나 뷰 내에서 reverse 함수 또는 {% url %} 템플릿 태그를 사용하여 

URL을 생성할 때 사용되는데 명시적으로 URL 이름을 지정함으로써 코드의 가독성과 유지보수성을 향상시킬 수 있으며, 

URL 구조가 변경되어도 이름이 동일하면 코드의 다른 부분을 수정할 필요가 없어진다. 

 

예를들어 사용자의 상태를 업데이트하는 메서드가 있다고 가정해보자

from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import viewsets

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(detail=True, methods=['post'], url_path='update-status', url_name='user-update-status')
    def update_status(self, request, pk=None):
        # 상태 업데이트 로직
        return Response({'status': 'updated'})

이 경우, url_name='user-update-status'로 설정했기 때문에

이 액션의 URL을 역참조할 때 사용할 수 있는 이름이 user-update-status가 되는 것이다.

 

URL 역참조 사용

url_name을 설정하고 나면, Django의 reverse 함수를 사용하여 프로그래밍 방식으로 URL을 생성할 수 있는데

위의 예제에서 설정한 user-update-status 설정값은 아래처럼 쓸 수 있다.

from django.urls import reverse

# 특정 사용자의 상태 업데이트 URL을 얻기
user_id = 1
update_url = reverse('user-update-status', args=[user_id])

이 코드는 user_id에 해당하는 사용자의 상태를 업데이트하는 URL을 생성하는 코드이며

추가로 reverse 함수는 url_name과 매개변수를 받아 해당 URL을 동적으로 생성하는 역할을 한다.

 

url_name은 대규모 프로젝트나 API에서 굉장히 유용한 기능인데,

중간에 URL구조가 변경되거나 다른 라우트 설정이 필요할 때 URL자체를 참조하지않고 이름으로 대체하기 때문에

하드코딩된 URL을 찾아가면서 변경해 줄 필요가 없다.


@action의 URL 설정

@action 데코레이터는 @api_view와 다르게 URL 설정을 수동으로 해줄 필요가 없다.

그 이유는 장고 프레임워크의 라우터 시스템이 자동으로 URL 설정을 처리해주기 때문에 

개발자는 urls.py에서 별도의 경로를 지정할 필요가 없다.

 

다만, ViewSet이 사용될 수 있도록 urls.py 파일에서 DRF의 라우터 설정은 해주어야 한다.

 

url.py 안에서 아래 예시처럼 라우터를 지정해준다고 가정해보자

from rest_framework.routers import DefaultRouter
from django.urls import include, path
from .views import UserViewSet

router = DefaultRouter()
router.register(r'users', UserViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

이 설정을 통해 UserViewSet 내의 모든 액션(표준 CRUD 및 @action으로 정의된 추가 액션)에 대한 URL이 자동으로 생성되고 관리되는 것이다.


@api_view와 @action

그럼 이 두개의 데코레이터의 가장 큰 차이점들은 무엇일까.

개념 정리차원에서 비교를 해보려고 한다.

 

1. 적용 범위

@action은 ViewSet 내에서 사용되어 클래스 기반의 접근을 제공하고 

@api_view는 단일 함수에서 적용되어 더 단순하고 함수 기반의 접근을 제공한다.

 

2. URL처리

@action은 ViewSet과 함께 사용되므로 DRF의 라우팅 시스템을 통해 자동으로 URL이 생성되고

@api_view를 사용한 함수는 일반적으로 장고 내의 URLconf를 통하여 명시적인 URL 패턴을 설정해야한다.

 

3. 기능성

@action은 클래스 기반 뷰의 메소드로서 클래스 상태(예: 인스턴스 변수)를 활용할 수 있는 반면

@api_view는 상태를 유지하지 않는 단순한 함수이다.

 

결국엔

@action은 클래스 기반 ViewSet 내에서 사용되어 복잡하고 다양한 기능을 가진 API를 구성할 때 유용하고

URL과 HTTP 메소드 설정의 유연성을 제공한다.
@api_view는 간단한 함수 기반 뷰를 API 뷰로 변환하며,

단순함과 직접성이 장점인 데코레이션으로 API를 빠르게 구현할 때 적합하다.

 

때에 따라 알아서 맞춰쓰면 된다.

 

그럼...

이 지식을 바탕으로.. 해야 할 것을 하러 가야겠다.

    # @action(methods=['delete'], detail=False, url_path='comment')
    # 댓글 개별삭제 로직
    # @action(methods=['get'], detail=False, url_path='comments')
    # 게시판 삭제 시 댓글 전체삭제 (is_delete => True)