> For the complete documentation index, see [llms.txt](https://dahye-jeong.gitbook.io/til/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dahye-jeong.gitbook.io/til/django/rest_framework/2019-04-22-viewset.md).

# ViewSet

Django REST framework는 `ViewSet` 이라는 단일 클래스에서 관련된 논리를 결합할 수 있다. `ViewSet`은 단순히 `get()`이나 `post()`와 같은 메소드 핸들러를 제공하지 않으며, 대신 `list()` 와 `create()` 같은 액션을 제공한다(CBV 유형). `ViewSet` 의 method 핸들러는 **`as_view()` 함수가 호출되어 view가 끝나는 시점의 해당 액션에만 바인딩**된다.

> 바인딩(binding)
>
> **각종 값들이 확정되어 더 이상 변경할 수 없는 상태가 되는 것**이다. 식별자(identifier)가 그 대상인 메모리 주소, 데이터형 또는 실제값으로 배정되는 것을 뜻한다.

일반적으로 url설정시에 view를 명시적으로 등록하는 대신 **`router` 클래스로 등록하여 자동으로 url을 설정**한다.

## START VIEWSETS

```python
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from .serializers import UserSerializer
from rest_framework import viewsets
from rest_framework.response import Response

class UserViewSet(viewsets.ViewSet):
    def list(self, request):
        queryset = User.objects.all()
        serializer = UserSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        queryset = User.objects.all()
        user = get_object_or_404(queryset, pk=pk)
        serializer = UserSerializer(user)
        return Response(serializer.data)
```

필요시에는 이 ViewSet을 아래와 같이 개별 뷰 바인딩을 할 수 있다.

```python
user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})
```

일반적으로 개별 뷰 바인딩을 하지 않으며, ViewSet을 라우터에 등록하여 자동으로 URLconf(url 설정파일)이 생성되도록 한다.

```python
from .views import UserViewSet
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns - router.urls
```

이때 viewset을 본인이 작성하는 것보다 **기본 동작을 제공해주는 기본 클래스를 사용**하는 것을 권장한다.

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

### 장점

view 클래스를 사용하는 것보다 viewsets 클래스를 사용하면 좋은 점이 2가지 있다.

1. 반복되는 로직을 하나의 클래스로 결합할 수 있다. 즉, viewsets을 이용하면 **queryset을 단 한번만 정의**하면 된다.
2. **router를 사용**함으로써, URLconf(url 설정파일)을 다룰 필요가 없어진다.

하지만 아래와 같은 trade-off도 있다.

* view와 url설정을 사용하면 보다 명확하고 상세하게 제어할 수 있다.

`ViewSet` 은 빠르게 실행하거나 대규모 API가 있는 경우, 또는 전체적으로 일관된 URLconf를 적용하려는 경우에 유리하다.

## MARKING EXTRA ACTIONS FOR ROUTING

REST framework에 포함된 기본 router는 **create** / **retrieve** / **update** / **destroy** 스타일 작업의 기본 method를 제공한다.

```python
class UserViewSet(viewsets.ViewSet):
    """
    Example empty viewset demonstrating the standard
    actions that will be handled by a router class.

    If you're using format suffixes, make sure to also include
    the 'format=None' keyword argument for each action.
    """

    def list(self, request):
        pass

    def create(self, request):
        pass

    def retrieve(self, request, pk=None):
        pass

    def update(self, request, pk=None):
        pass

    def partial_update(self, request, pk=None):
        pass

    def destroy(self, request, pk=None):
        pass
```

라우팅이 필요한 임시 메소드가 있는 경우에는 `@detail_router` 또는 `@list_router` 데코레이터를 사용하여 라우팅을 필요로 한다는 것을 표시할 수 있다.

* `@detail_router` 는 URL 패턴에 **pk**를 포함하며 단일 인스턴스를 요구하는 메소드에 대한 것이다.
* `@list_router` 는 객체 목록에서 작동하는 메소드에 대한 것이다.

```python
rom django.contrib.auth.models import User
from rest_framework import status
from rest_framework import viewsets
from rest_framework.decorators import detail_route, list_route
from rest_framework.response import Response
from .serializers import UserSerializer, PasswordSerializer

class UserViewSet(viewsets.ModelViewSet):

    queryset = User.objects.all()
    serializer_class = UserSerializer

    @detail_route(methods=['post'])
    def set_password(self, request, pk=None):
        user = self.get_object()
        serializer = PasswordSerializer(data=request.data)
        if serializer.is_valid():
            user.set_password(serializer.data['password'])
            user.save()
            return Response({'status': 'password set'})
        else:
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

    @list_route()
    def recent_users(self, request):
        recent_users = User.objects.all().order('-last_login')

        page = self.paginate_queryset(recent_users)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(recent_users, many=True)
        return Response(serializer.data)
```

데코레이터는 오직 라우팅된 View에서만 추가적인 arguments를 받을 수 있다.

```python
@detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf])
def set_password(self, request, pk=None):
  ...
```

해당 데코레이터는 기본적으로 `GET` request를 routing하지만 `methods` 인수를 사용하여 다른 HTTP Method로 routing할 수 있다.

```python
@detail_route(methods=['post', 'delete'])
def unset_password(self, request, pk=None):
  ...
```

위의 두가지 새로운 action들은 `^users/{pk}/set_password/$`, `^users/{pk}/unset_password/$` 로 사용할 수 있다.

## API REFERENCE

### ViewSet

`ViewSet` 클래스는 `APIView` 에서 상속받는다. `permission_classes`, `authentication_classes` 와 같은 표준 속성을 이용하여 API 정책을 통제할 수 있다.

`ViewSet` 클래스는 action의 구현을 제공하지 않는다. `ViewSet` 클래스를 사용하기 위해서는 해당 클래스를 overrice하고, 명시적으로 action을 정의해야한다.

### GenericViewSet

`GenericViewSet` 클래스는 `GenericAPIView` 에서 상속 받는다. `get_object`, `get_queryset` 메소드와 다른 generic view의 기본 action을 제공하지만, 기본적으로는 어떠한 액션도 포함하고 있지 않다.

`GenericViewSet` 클래스를 사용하기 위해서는 해당 클래스를 override하고 필요한 `mixin` 클래스를 혼합하여 명시적으로 action을 정의해아한다.

### ModelViewSet

`ModelViewSet` 클래스는 `GenericAPIView` 를 상속하고 다수의 `mixin` 클래스의 동작을 혼합하여 다양한 action에 대한 구현을 포함한다.

`.list()`, `.retrieve()`, `.create()`, `.update()`, `.partial_update()`, `.destroy()` 를 제공한다.

```python
class AccountViewSet(viewsets.ModelViewSet):
    queryset = Account.objects.all()
    serializer_class = AccountSerializer
    permission_classes = [IsAccountAdminOrReadOnly]
```

`GenericAPIView` 를 상속받고 있기 대문에 기본적으로 `queryset`과 `serializer_class` 속성을 필수로 제공해야한다.

```python
class AccountViewSet(viewsets.ModelViewSet):
    serializer_class = AccountSerializer
    permission_classes = [IsAccountAdminOrReadOnly]

    def get_queryset(self):
        return self.request.user.accounts.all()
```

queryset을 동적으로 결정하는 viewsets을 사용하려면 위와 같이 메소드 오버라이드를 할 수 있다.

여기서 주의해야할 점이 있다. `ViewSet`의 `queryset` 속성을 제거하면 연결된 라우터가 모델의 **base\_name**을 자동으로 파생시킬 수 없으므로 **base\_name**의 **kwarg**를 지정해야한다. 이 클래스는 기본적으로 **create** / **list** / **retrieve** / **update** /**destroy** 액션을 제공하지만 표준 권한 클래스(permission)를 사용해 제한할 수 있다.

### ReadOnlyModelViewSet

`ReadOnlyModelViewSet` 클래스는 `GenericAPIView` 를 상속한다. `ModelViewSet`과 동일하게 다양한 action애 대한 구현도 포함되지만 오직 읽기 전용 action인 `.list()` 와 `.retrieve()`만 제공한다.

```python
class AccountViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Account.objects.all()
    serializer_class = AccountSerializer
```

## 참고링크

* <https://kimdoky.github.io/django/2018/07/07/drf-ViewSets.html>
* <https://brownbears.tistory.com/82>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dahye-jeong.gitbook.io/til/django/rest_framework/2019-04-22-viewset.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
