[DRF] 공식 문서 - views의 정리 3 - CBV(Generic views)
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
- reference :
https://velog.io/@mechauk418/DRF-Filtering-%ED%95%84%ED%84%B0%EB%A7%81
https://velog.io/@nikevapormax/django-generic-views
https://syujisu.tistory.com/entry/Django%EC%8B%AC%ED%99%943-viewset-router