""" 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): # 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): # Attempt to get the request from the cache cache_attempt = self.get_cache(request) if cache_attempt: return Response(cache_attempt) else: # 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): 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): 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, *args, **kwargs): # 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): serializer.save() def get_success_headers(self, data): 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, *args, **kwargs): # Attempt to get the request from the cache cache_attempt = self.get_cache(request) if cache_attempt: return Response(cache_attempt) else: # 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, *args, **kwargs): 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): serializer.save() def list_partial_update(self, request, *args, **kwargs): 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, *args, **kwargs): # 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) #def perform_destroy(self, instance): # instance.delete() #def get_objects(self): # """ # The custom list version of get_object that retrieves one instance from the #database. It yields model instances with each call. # """ # queryset = self.filter_queryset(self.get_queryset()) # # if len(queryset): # for obj in queryset.all(): # # May raise a permission denied # self.check_object_permissions(self.request, obj) # yield obj #yield None