All checks were successful
StarFields Django Rest Framework Generics / build (push) Successful in 36s
260 lines
9.1 KiB
Python
260 lines
9.1 KiB
Python
"""
|
|
Basic building blocks for generic class based views.
|
|
|
|
We don't bind behaviour to http method handlers yet,
|
|
which allows mixin classes to be composed in interesting ways.
|
|
"""
|
|
from rest_framework import status
|
|
from rest_framework.response import Response
|
|
from rest_framework.settings import api_settings
|
|
from rest_framework import mixins
|
|
from starfields_drf_generics.cache_mixins import (
|
|
CacheGetMixin, CacheSetMixin, CacheDeleteMixin)
|
|
|
|
|
|
# Mixin classes to be included in the generic classes
|
|
class CachedCreateModelMixin(CacheDeleteMixin, mixins.CreateModelMixin):
|
|
"""
|
|
A slightly modified version of rest_framework.mixins.CreateModelMixin
|
|
that handles cache deletions.
|
|
"""
|
|
|
|
def create(self, request, *args, **kwargs):
|
|
" Creates the entry in the request "
|
|
# Go on with the creation as normal
|
|
serializer = self.get_serializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
self.perform_create(serializer)
|
|
headers = self.get_success_headers(serializer.data)
|
|
|
|
# Delete the cache
|
|
self.delete_cache(request)
|
|
return Response(serializer.data, status=status.HTTP_201_CREATED,
|
|
headers=headers)
|
|
|
|
|
|
class CachedRetrieveModelMixin(CacheGetMixin, CacheSetMixin):
|
|
"""
|
|
A slightly modified version of rest_framework.mixins.RetrieveModelMixin
|
|
that handles cache attempts.
|
|
mixins.RetrieveModelMixin only has the retrieve method so it doesn't stand
|
|
to inherit anything from it.
|
|
"""
|
|
|
|
def retrieve(self, request, *args, **kwargs):
|
|
" Retrieves the entry in the request "
|
|
# Attempt to get the request from the cache
|
|
cache_attempt = self.get_cache(request)
|
|
|
|
if cache_attempt:
|
|
return Response(cache_attempt)
|
|
|
|
# The cache get attempt failed so we have to get the results from
|
|
# the database
|
|
instance = self.get_object()
|
|
|
|
serializer = self.get_serializer(instance)
|
|
response = Response(serializer.data)
|
|
|
|
self.set_cache(request, response)
|
|
return response
|
|
|
|
|
|
class CachedUpdateModelMixin(CacheDeleteMixin, mixins.UpdateModelMixin):
|
|
"""
|
|
A slightly modified version of rest_framework.mixins.UpdateModelMixin that
|
|
handles cache deletes.
|
|
"""
|
|
|
|
def update(self, request, *args, **kwargs):
|
|
" Updates the entry in the request "
|
|
partial = kwargs.pop('partial', False)
|
|
instance = self.get_object()
|
|
serializer = self.get_serializer(instance, data=request.data,
|
|
partial=partial)
|
|
serializer.is_valid(raise_exception=True)
|
|
self.perform_update(serializer)
|
|
|
|
if getattr(instance, '_prefetched_objects_cache', None):
|
|
# If 'prefetch_related' has been applied to a queryset, we need to
|
|
# forcibly invalidate the prefetch cache on the instance.
|
|
instance._prefetched_objects_cache = {}
|
|
|
|
# Delete the related caches
|
|
self.delete_cache(request)
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
class CachedDestroyModelMixin(CacheDeleteMixin, mixins.DestroyModelMixin):
|
|
"""
|
|
A slightly modified version of rest_framework.mixins.DestroyModelMixin
|
|
that handles cache deletes.
|
|
"""
|
|
|
|
def destroy(self, request, *args, **kwargs):
|
|
" Deletes the entry in the request "
|
|
instance = self.get_object()
|
|
self.perform_destroy(instance)
|
|
|
|
# Delete the related caches
|
|
self.delete_cache(request)
|
|
|
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
|
|
|
|
# List mixin classes to be included with list generic classes
|
|
class CachedListCreateModelMixin(CacheDeleteMixin):
|
|
"""
|
|
A fully custom mixin that handles mutiple instance cration.
|
|
"""
|
|
|
|
def list_create(self, request, **kwargs):
|
|
" Creates the list of entries in the request "
|
|
# Go on with the creation as normal
|
|
serializer = self.get_serializer(data=request.data, many=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
self.perform_create(serializer)
|
|
headers = self.get_success_headers(serializer.data)
|
|
|
|
# Delete the cache
|
|
self.delete_cache(request)
|
|
return Response(serializer.data, status=status.HTTP_201_CREATED,
|
|
headers=headers)
|
|
|
|
def perform_create(self, serializer):
|
|
"""
|
|
Uses serializer.create instead of serializer.save to avoid making a
|
|
query. We save the returned instance list to the serializer in order to
|
|
be used as serializer.data during rendering
|
|
"""
|
|
assert hasattr(serializer, 'create'), (
|
|
f'Cannot call .create() on serializer {serializer.__class__} as'
|
|
' no such attribute exists.'
|
|
)
|
|
validated_data = serializer.validated_data
|
|
instance_list = serializer.create(validated_data)
|
|
# Check whatever you can
|
|
assert hasattr(instance_list, '__iter__'), (
|
|
'Method .create() on serializer on serializer '
|
|
f'{serializer.__class__} should return a list of serializable'
|
|
' model instances.'
|
|
)
|
|
serializer.instance = instance_list
|
|
|
|
def get_success_headers(self, data):
|
|
" Returns extra success headers "
|
|
try:
|
|
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
|
|
except (TypeError, KeyError):
|
|
return {}
|
|
|
|
|
|
class CachedListRetrieveModelMixin(CacheGetMixin, CacheSetMixin):
|
|
"""
|
|
A slightly modified version of rest_framework.mixins.ListModelMixin that
|
|
handles cache saves.
|
|
mixins.ListModelMixin only has the list method so it doesn't stand to
|
|
inherit anything from it.
|
|
"""
|
|
|
|
def list(self, request, **kwargs):
|
|
" Retrieves the listing of entries "
|
|
# Attempt to get the request from the cache
|
|
cache_attempt = self.get_cache(request)
|
|
|
|
if cache_attempt:
|
|
return Response(cache_attempt)
|
|
|
|
# The cache get attempt failed so we have to get the results from
|
|
# the database
|
|
queryset = self.filter_queryset(self.get_queryset())
|
|
|
|
if self.paged:
|
|
page = self.paginate_queryset(queryset)
|
|
if page is not None:
|
|
serializer = self.get_serializer(page, many=True)
|
|
response = self.get_paginated_response(serializer.data)
|
|
else:
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
response = Response(serializer.data)
|
|
else:
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
response = Response(serializer.data)
|
|
|
|
self.set_cache(request, response)
|
|
return response
|
|
|
|
|
|
class CachedListUpdateModelMixin(CacheDeleteMixin):
|
|
"""
|
|
A fully custom mixin that handles mutiple instance updates.
|
|
"""
|
|
|
|
def list_update(self, request, **kwargs):
|
|
" Updates the list of entries in the request "
|
|
partial = kwargs.pop('partial', False)
|
|
|
|
serializer = self.get_serializer(data=request.data,
|
|
partial=partial, many=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
self.perform_update(serializer)
|
|
|
|
# Delete the related caches
|
|
self.delete_cache(request)
|
|
|
|
return Response(serializer.data)
|
|
|
|
def perform_update(self, serializer):
|
|
"""
|
|
Uses serializer.update instead of serializer.save to avoid making a
|
|
query. We save the returned instance list to the serializer in order to
|
|
be used as serializer.data during rendering
|
|
"""
|
|
assert hasattr(serializer, 'update'), (
|
|
f'Cannot call .update() on serializer {serializer.__class__} as'
|
|
' no such attribute exists.'
|
|
)
|
|
validated_data = serializer.validated_data
|
|
instance_list = serializer.update(None, validated_data)
|
|
# Check whatever you can
|
|
assert hasattr(instance_list, '__iter__'), (
|
|
'Method .update() on serializer on serializer '
|
|
f'{serializer.__class__} should return a list of serializable '
|
|
' model instances.'
|
|
)
|
|
serializer.instance = instance_list
|
|
|
|
def list_partial_update(self, request, *args, **kwargs):
|
|
" Needs to be called on partial updates "
|
|
kwargs['partial'] = True
|
|
return self.list_update(request, *args, **kwargs)
|
|
|
|
|
|
class CachedListDestroyModelMixin(CacheDeleteMixin):
|
|
"""
|
|
A fully custom mixin that handles mutiple instance deletions.
|
|
"""
|
|
|
|
def list_destroy(self, request, **kwargs):
|
|
" Deletes the list of entries in the request "
|
|
# Go on with the validation as normal
|
|
serializer = self.get_serializer(data=request.data, many=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
self.perform_destroy(serializer)
|
|
|
|
# Delete the related caches
|
|
self.delete_cache(request)
|
|
|
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
|
|
def perform_destroy(self, serializer):
|
|
" Custom generic destroy hook "
|
|
assert hasattr(serializer, 'destroy'), (
|
|
f'Cannot call .destroy() on serializer {serializer.__class__} as'
|
|
' no such attribute exists.'
|
|
)
|
|
validated_data = serializer.validated_data
|
|
serializer.destroy(validated_data)
|