Files
starfields-drf-generics/starfields_drf_generics/mixins.py

227 lines
7.6 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):
" Generic save hook "
serializer.save()
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)
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, 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):
" Generic save hook "
serializer.save()
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)
validated_data = serializer.validated_data
# TODO does this new stuff work even? need to check on the frontend
serializer.delete(validated_data)
# for instance in self.get_objects():
# if instance is not None:
# self.perform_destroy(instance)
# Delete the related caches
self.delete_cache(request)
return Response(status=status.HTTP_204_NO_CONTENT)