Django REST Framework/DRF 일반

[DRF] 공식 문서 - views의 정리 3 - CBV(Generic views)

bluebamus 2024. 1. 10.

1. API Reference

   1) GenericAPIView

      - Attributes

         - queryset: 이 뷰에서 개체를 반환하는 데 사용해야 하는 데이터베이스 쿼리 결과 집합이다. 일반적으로 이 속성을 직접 설정하거나 'get_queryset()' 메서드를 재정의해야 한다. 뷰 메서드를 재정의하기로 선택한 경우 이 속성에 직접 액세스하는 대신(self 사용) get_queryset()을 호출하는 것이 중요하다. 이는 쿼리 세트가 한 번 평가되고 모든 후속 요청에 대해 결과가 캐시되기 때문이다 .

            - queryset은 object가 생상되면서 읽은 데이터를 캐싱하고 이후 갱신을 하지 않는다고 한다. 때문에 갱신된 데이터가 계속 요구되는 경우 get_queryset을 사용하기를 권장한다.

            - 혹은 create, update 등의 http 메소드에서 save 이후 data = self.get_queryset()를 호출하는 방법이 있다.

from rest_framework.generics import GenericAPIView
from myapp.models import MyModel
from myapp.serializers import MyModelSerializer

class MyGenericView(GenericAPIView):
    queryset = MyModel.objects.all()  # Set the queryset to retrieve objects from MyModel
    serializer_class = MyModelSerializer

    def get(self, request, *args, **kwargs):
        data = self.get_queryset()
        serializer = self.get_serializer(data, many=True)
        return Response(serializer.data)

   

         - serializer_class: 입력의 유효성을 검사하고 역직렬화하는 것은 물론 출력을 직렬화하는 데 사용해야 하는 직렬 변환기의 클래스이다. 일반적으로 이 속성을 직접 설정하거나 'get_serializer_class()' 메서드를 재정의해야 한다.

from rest_framework.generics import GenericAPIView
from myapp.models import MyModel
from myapp.serializers import MyCustomSerializer

class MyCustomView(GenericAPIView):
    queryset = MyModel.objects.all()
    serializer_class = MyCustomSerializer  # Use MyCustomSerializer for input/output validation and serialization

    def get(self, request, *args, **kwargs):
        data = self.get_queryset()
        serializer = self.get_serializer(data, many=True)
        return Response(serializer.data)

 

         -  lookup_field: 개별 모델 인스턴스의 객체 조회를 수행하는 데 사용해야 하는 모델의 필드이다. 기본값은 'pk'(기본 키)입니다. 하이퍼링크된 API를 사용할 때 사용자 정의 값을 사용하려는 경우 API 보기와 직렬 변환기 클래스 모두 조회 필드를 설정하는지 확인해야 한다.

# models.py
from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)
    isbn = models.CharField(max_length=13, unique=True)  # Assuming ISBN is a 13-character identifier

# serializers.py
from rest_framework import serializers

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['title', 'isbn']

# myapp/urls.py
from django.urls import path
from .views import BookListView, BookDetailView

urlpatterns = [
    path('books/', BookListView.as_view(), name='book-list'),
    path('books/<str:isbn>/', BookDetailView.as_view(), name='book-detail'),  # Use <str:isbn> to capture the ISBN in the URL
]

# views.py

from rest_framework.generics import RetrieveAPIView
from myapp.models import Book
from myapp.serializers import BookSerializer

class MyRetrieveView(RetrieveAPIView):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    lookup_field = 'isbn'  # Use 'custom_field' in MyModel for object lookup

 

         -  lookup_url_kwarg: 객체 조회에 사용해야 하는 URL의 키워드 인수입니다. URL 구성에는 이 값에 해당하는 키워드 인수가 포함되어야 합니다. 설정하지 않으면 기본적으로 lookup_field와 동일한 값을 사용합니다.

            - 만약 field 이름과 동일한 key 이름을 사용한다면, lookup_field를 정의하지 않아도 자동으로 설정한다. 하지만 key와 field의 이름이 다르다면 둘다 정의를 해줘야 한다.

# models.py
from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)
    isbn = models.CharField(max_length=13, unique=True)  # Assuming ISBN is a 13-character identifier

# serializers.py
from rest_framework import serializers

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['title', 'isbn']

# views.py
from rest_framework.generics import ListAPIView, RetrieveAPIView
from .models import Book
from .serializers import BookSerializer

class BookListView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

class BookDetailView(RetrieveAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_field = 'isbn'  # Set the lookup field to 'isbn'
    lookup_url_kwarg = 'custom_isbn'  # Use 'custom_isbn' as the URL keyword argument for object lookup
    
# myapp/urls.py
from django.urls import path
from .views import BookListView, BookDetailView

urlpatterns = [
    path('books/', BookListView.as_view(), name='book-list'),
    path('books/<str:custom_isbn>/', BookDetailView.as_view(), name='book-detail'),  # Use <str:custom_isbn> to capture the ISBN in the URL
]

 

         -  Pagination : 이 속성은 목록 결과에 페이지를 매길 때 사용할 pagination 클래스를 지정한다. 기본값은 DEFAULT_PAGINATION_CLASS 설정인 'rest_framework.pagination.PageNumberPagination'과 동일하다.

            - pagination_class=None을 설정하면 현재 view 에서 pagination 이 비활성화된다.

# views.py
from rest_framework.generics import ListAPIView
from .models import Book
from .serializers import BookSerializer
from rest_framework.pagination import PageNumberPagination

class CustomPageNumberPagination(PageNumberPagination):
    page_size = 5  # Number of items per page
    page_size_query_param = 'page_size'
    max_page_size = 100

class BookListView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    pagination_class = CustomPageNumberPagination  # Use custom pagination class

 

         -  filter_backends: 이 속성은 쿼리 세트를 필터링하는 데 사용해야 하는 필터 백엔드 클래스 목록이다. 기본값은 DEFAULT_FILTER_BACKENDS 설정과 동일하다.

# views.py
from rest_framework.generics import ListAPIView
from .models import Book
from .serializers import BookSerializer
from rest_framework.filters import SearchFilter

class BookListView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [SearchFilter]  # Use SearchFilter for filtering
    search_fields = ['title', 'author', 'genre']  # Specify fields for searching

 

            - 커스텀으로 사용 하는 경우 

# views.py
from rest_framework.generics import ListAPIView
from .models import Book
from .serializers import BookSerializer
from rest_framework.filters import BaseFilterBackend
from rest_framework.response import Response
from rest_framework import status

class CustomFilterBackend(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        # Custom filtering logic goes here
        # For example, filtering books published after a specific year
        year = request.query_params.get('published_year')
        if year:
            try:
                year = int(year)
                queryset = queryset.filter(publication_date__year__gt=year)
            except ValueError:
                return Response({'error': 'Invalid year format'}, status=status.HTTP_400_BAD_REQUEST)
        return queryset

class BookListView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [CustomFilterBackend]  # Use CustomFilterBackend for custom filtering

 

      - Methods

         - Base methods:

            - get_queryset(self)

               - 이 메서드는 목록 보기용 쿼리 세트를 반환하고 세부정보 보기의 조회를 위한 기초 역할을 한다.

                  - 기본적으로 클래스 변수인 queryset 속성에 지정된 쿼리 세트를 반환한다.

               - self.queryset에 직접 액세스하는 대신 이 방법을 사용하는 것이 중요한데, self.queryset에 직접 액세스하면 평가 결과가 한 번만 발생하며 해당 결과는 모든 후속 요청에 대해 캐시되기 때문이다.

               - 요청에 특정한 쿼리 세트를 반환하는 등의 동적인 queryset의 결과가 필요하다면 재정의 해야한다.

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

 

            -  get_object(self)

               - 이 메서드는 상세 뷰에 사용해야 하는 객체 인스턴스를 반환한다.

                  - 기본적으로 lookup_field(쉽게 pk라고 이해 하면 된다) 매개변수를 사용하여 기본 쿼리 세트를 필터링한다.

               - 둘 이상의 URL 키워드 인수를 기반으로 하는 개체 조회와 같은, 보다 복잡한 동작을 해야 한다면 이 메서드를 재정의 해야 한다.

def get_object(self):
    queryset = self.get_queryset()
    filter = {}
    for field in self.multiple_lookup_fields:
        filter[field] = self.kwargs[field]

    obj = get_object_or_404(queryset, **filter)
    self.check_object_permissions(self.request, obj)
    return obj

 

               - if your API doesn't include any object level permissions, you may optionally exclude the self.check_object_permission

from rest_framework.generics import RetrieveAPIView
from django.shortcuts import get_object_or_404
from .models import YourModel
from .serializers import YourModelSerializer

class YourModelDetailView(RetrieveAPIView):
    queryset = YourModel.objects.all()
    serializer_class = YourModelSerializer
    lookup_field = 'your_lookup_field'  # Replace with your actual lookup field

    def get_object(self):
        # Note: If your API doesn't include any object-level permissions,
        # you may exclude self.check_object_permissions and directly return the object from get_object_or_404
        return get_object_or_404(self.get_queryset(), **{self.lookup_field: self.kwargs[self.lookup_url_kwarg]})

 

            -  filter_queryset(self, queryset)

               - 쿼리 세트가 주어지면 사용 중인 필터 백엔드를 이용해 필터링하여 새 쿼리 세트를 반환합니다.

                  - django 기본 필터 :

                     - 검색 필터 : 간단한 단일 쿼리 매개변수 검색을 지원하며 django admin 검색 기능을 기반으로 동작한다.

                        - 일반적으로 SearchFilter 클래스를 이용하고 search_fields를 정의한다.

                        - TextField 유형의 필드만 포함되어야 한다.

from rest_framework import filters

class Task_ViewSet(ModelViewSet):
    
    queryset = Task.objects.all().order_by('-id')
    serializer_class = TaskSerializer
    filter_backends = [filters.SearchFilter]
    search_fields  = ['title','content']

                         - 이중 밑줄 표기법을 사용하여 중첩된 json 구조에서 필터링 할 수 있다.

class Task_ViewSet(ModelViewSet):
    
    queryset = Task.objects.all().order_by('-id')
    serializer_class = TaskSerializer
    filter_backends = [filters.SearchFilter]
    search_fields  = ['title','content','subtasks__team']

 

                     - 순서 필터 : 결과를 순서대로 정렬하여 반환한다.

from rest_framework import filters

class Task_ViewSet(ModelViewSet):
    
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    # throttle_classes = [TestThrottle]
    filter_backends = [filters.OrderingFilter]
    ordering_fields = ['title','content']
    ordering  = ['title'] # ordering 기본 필드 설정

 

                  - DjangoFilterBackend : 모델 필드를 기반으로 하는 복잡하고 사용자 정의 가능한 필터링을 위해 설계되었다.

                     - 설치 :

pip install django-filter

                     - settings.py 설정 : 

INSTALLED_APPS = [
    ...
    'django_filters',
    ...
]

                      - 필터 백엔드 설정 : 

# 전역 설정 - settins.py
REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}

# view에서 개별 필터 백엔드 설정
class Task_ViewSet(ModelViewSet):
    
    queryset = Task.objects.all().order_by('-id')
    serializer_class = TaskSerializer
    filter_backends = [django_filters.rest_framework.DjangoFilterBackend]

 

                      -  기능:
                        - 복잡한 필터링: 모델 필드에 복잡한 필터를 정의할 수 있습니다.

                           - ex) 'Book' 모델이 있고 출판 연도 범위와 특정 장르를 기준으로 책을 필터링한다고 가정한다.

                           - filterset_fields 사전을 사용하면 'gte'(크거나 같음), 'lte'(작거나 같음) 및 'exact'와 같은 필터 유형과 함께 필드를 지정할 수 있다.

# url
http://example.com/api/books/?publication_year__gte=2000&publication_year__lte=2022
http://example.com/api/books/?genre=sci-fi
## genre=sci-fi: 'sci-fi'와 정확히 일치하는 장르로 책을 필터링합니다.

# views.py
from rest_framework.generics import ListAPIView
from .models import Book
from .serializers import BookSerializer
from django_filters.rest_framework import DjangoFilterBackend

class BookListView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = {
        'publication_year': ['gte', 'lte'],  # Filter books published between a range of years
        'genre': ['exact'],  # Filter books with a specific genre
    }


                        - 다중 필터: 서로 다른 필드에서 동시에 여러 필터를 지원합니다.

                           - ex) 저자 이름과 장르를 기준으로 책을 필터링하는 검색 기능을 제공한다고 가정해 본다.

                           - 'filterset_fields' 사전에는 여러 필드가 포함되어 있어 저자와 장르별로 책을 동시에 필터링할 수 있다.

# views.py
from rest_framework.generics import ListAPIView
from .models import Book
from .serializers import BookSerializer
from django_filters.rest_framework import DjangoFilterBackend

class BookListView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = {
        'author': ['exact'],  # Filter books by a specific author
        'genre': ['exact'],   # Filter books with a specific genre
    }


                        - 사용자 정의 조회: 사용자 정의 조회를 지원하여 필터링 논리를 정의할 수 있습니다.

                           - ex) 제목에 특정 키워드가 포함된 책과 같이 사용자 정의 조건에 따라 책을 필터링한다고 가정한다.

                           - '제목' 필드에 대해 사용자 정의 조회 'custom_contains'가 정의된다.

                              - filter_title_custom_contains 메소드는 사용자 정의 필터링 로직을 구현한다.

# url
http://example.com/api/books/?title__custom_contains=python

# views.py
# views.py
from rest_framework.generics import ListAPIView
from .models import Book
from .serializers import BookSerializer
from django_filters.rest_framework import DjangoFilterBackend

class BookListView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = {
        'title': ['custom_contains'],  # Custom lookup for filtering books with titles containing a certain keyword
    }

    def filter_queryset(self, queryset):
        # Override filter_queryset to handle custom lookup
        title_custom_contains = self.request.query_params.get('title__custom_contains')
        if title_custom_contains:
            queryset = self.filter_title_custom_contains(queryset, 'title', title_custom_contains)
        return queryset

    def filter_title_custom_contains(self, queryset, name, value):
        return queryset.filter(title__icontains=value)

 

                  - 기타 필터 패키지

                     - django-rest-framework-filters package
                     - djangorestframework-word-filter
                     - django-url-filter
                     - drf-url-filter

 

            -  get_serializer_class(self)

               - serializer에 사용될 class를 반환한다.

               - 클래스 변수인 serializer_class 속성을 반환하는 것을 기본값으로 한다.

               -  읽기와 쓰기 동작에 서로 다른 serializer를 사용하고 있거나 사용자의 타입에 따라 서로 다른 serializer들을 사용하고 있다면, 다양한 동작을 제공하기 위해 오버라이딩할 수 있다.

def get_serializer_class(self):
    if self.request.user.is_staff:
        return FullAccountSerializer
    return BasicAccountSerializer

 

         - Save and deletion hooks:

            - 아래 3개의 메서드들은 mixin class들에 의해 제공되며, 객체의 저장과 삭제 행위를 위한 쉬운 오버라이딩 방법을 제공한다.

            - 아래 hook들은 request 안에 내재된 attribute 들을 세팅할 때 부분적으로 유용하다.

               - 하지만 request data의 한 부분은 아니다.

 

            -  perform_create(self, serializer)

               - 새로운 object instance를 저장할 때 CreateModelMixin을 사용한다.

               - request user 또는 URL kwarg를 기반으로 개체에 속성을 설정할 수 있다.

def perform_create(self, serializer):
    serializer.save(user=self.request.user)

  

               - ValidationError()를 사용해 추가적인 validation을 사용하는 데 사용할 수 있다. 해당 로직은 데이터베이스에 데이터를 저장하는 시점에 유용할 수 있다.

def perform_create(self, serializer):
    queryset = SignupRequest.objects.filter(user=self.request.user)
    if queryset.exists():
        raise ValidationError('You have already signed up')
    serializer.save(user=self.request.user)

 

            -  perform_update(self, serializer)

               - 기존 객체 인스턴스를 저장할 때 호출한다.

               - 존재하는 object instance를 저장할 때 UpdateModelMixin을 사용한다.

               - 확인 이메일을 보내거나 업데이트를 기록하는 것과 같이 objcet를 저장하기 전이나 후에 발생하는 동작을 추가하는 데 특히 유용하다.

def perform_update(self, serializer):
    instance = serializer.save()
    send_email_confirmation(user=self.request.user, modified=instance)

 

            -  perform_destroy(self, instance)

               - 객체 인스턴스를 삭제할 때 호출된다.

 

         - Other methods: 

            - GenericAPIView를 사용하여 custom view를 작성하는 경우 호출해야 할 수도 있지만 일반적으로 다음 메소드를 오버라이딩할 필요가 없다.

 

            - get_serializer_context(self) 

               - serializer에 제공해야 하는 추가 컨텍스트가 포함된 사전을 반환한다.

               - 기본적으로 request, view, format 키를 포함한다.

 

            - get_serializer(self, instance=None, data=None, many=False, partial=False)

               - serialzer instance를 반환한다.

 

            - get_paginated_response(self, data)

               - 페이징된 Response object를 반환한다.

 

            - paginate_queryset(self, queryset)

               - 만약 필요하다면 queryset을 페이징한다.

               - page objcet를 반환하거나, 만약 pagination이 구성되지 않은 view라면 None 값을 반환한다.

 

            - filter_queryset(self, queryset)   

               - queryset을 주고 filter backends에서 사용중인 필터를 통해 새로운 queryset을 반환할 수 있다.

 

    2) Customizing the generic views

      - generic view들을 사용하면서 조금의 커스터마이징된 행위들을 추가할 수 있다.

      - Creating custom mixins

         - 예를 들어, URL conf에 포함되어 있는 여러 개의 필드들을 사용해 object들을 검색하는 기능이 필요하다면 아래와 같이 mixin class를 생성해 사용할 수 있다.

class MultipleFieldLookupMixin:
    """
    Apply this mixin to any view or viewset to get multiple field filtering
    based on a `lookup_fields` attribute, instead of the default single field filtering.
    """
    def get_object(self):
        queryset = self.get_queryset()             # Get the base queryset
        queryset = self.filter_queryset(queryset)  # Apply any filter backends
        filter = {}
        for field in self.lookup_fields:
            if self.kwargs.get(field): # Ignore empty fields.
                filter[field] = self.kwargs[field]
        obj = get_object_or_404(queryset, **filter)  # Lookup the object
        self.check_object_permissions(self.request, obj)
        return obj

 

         - 위에서 생성한 minxin class를 아래와 같이 view 또는 viewser에 추가해 커스텀을 할 수 있다.

class RetrieveUserView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    lookup_fields = ['account', 'username']

 

      - Creating custom base classes

         - 여러 view에서 mixin을 사용하는 경우, 한 단계 더 나아가 프로젝트 전체에서 사용할 수 있는 자신만의 기본 view들의 세트를 만들 수 있다.

         - 아래와 같이 새로운 커스텀 클래스를 생성하는 것은 커스텀된 행위가 전체 프로젝트 안에서 많이 사용되게 될 때 아주 좋은 옵션이 될 수 있다.

class BaseRetrieveView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
    pass

class BaseRetrieveUpdateDestroyView(MultipleFieldLookupMixin, generics.RetrieveUpdateDestroyAPIView):
    pass

 

 - 공식 사이트 문서 : https://www.django-rest-framework.org/api-guide/generic-views/#generic-views

 

Generic views - Django REST framework

 

www.django-rest-framework.org

 

 - reference : 

https://wikidocs.net/197563

 

03) DRF 뷰와 뷰셋

[TOC] ## DRF 뷰와 뷰셋 이 섹션에서는 Django REST Framework (DRF)의 뷰와 뷰셋에 대해 알아보겠습니다. DRF의 뷰는 API 엔드포인트의 동작…

wikidocs.net

https://velog.io/@mechauk418/DRF-Filtering-%ED%95%84%ED%84%B0%EB%A7%81

 

[DRF] Filtering (필터링)

필터링은 쿼리에서 반환되는 항목을 조건부로 제한하는 것을 말한다.가장 간단한 필터링은 GenericAPIView 에서 .get_queryset()를 오버라이딩하여 직접 필터링 하는 것이다.목록에서 팀명이 다래인 자

velog.io

https://velog.io/@nikevapormax/django-generic-views

 

Django REST framework_GenericAPIView

Django REST framework Generic Views

velog.io

https://syujisu.tistory.com/entry/Django%EC%8B%AC%ED%99%943-viewset-router

 

[Django_심화]3 - viewset & router

1. Views of DRF 모델 기반의 viewset을 작성했었다. from rest_framework import viewsets from .models import Post from .serializer import PostSerializer class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = View

syujisu.tistory.com

 

댓글