Updated the README and the mixing to provide better asserts.
All checks were successful
StarFields Django Rest Framework Generics / build (push) Successful in 36s
All checks were successful
StarFields Django Rest Framework Generics / build (push) Successful in 36s
This commit is contained in:
12
README.md
12
README.md
@@ -2,7 +2,17 @@ This repository holds the django library that StarFields uses for the django-res
|
||||
|
||||
# Differences with the DRF generic views
|
||||
|
||||
It changes the generic lifecycles of all the CRUD operations to fit within them automated caching functionality. Caching and deleting cache keys is handled by the library in a way that the cache keys have no duplicates. The generic views offered include single item CRUD and list-based CRUD.
|
||||
The generic views of DRF use the serializers in such a way that the model serializers directly integrate the functionality of a specific model (db table) with CRUD. As a result simple operations such as deleting a list of n table rows ends up using n queries. This serves automated CRUD well but control and performance suffer, in particular changing the view and serializer methods to use a single query in order to perform a write operation becomes a very non-uniform experience between request methods. This library changes the DRF generic views to be more uniform in the way they use the serializers, in particular a single query is made for GET requests with elaborate filtering capabilities and calls to serializer .create(), .update() and .destroy() methods for write methods.
|
||||
|
||||
In particular:
|
||||
- Single Create operations create the model instance as expected
|
||||
- Single Retrive, Update and Destroy work with the .get_object() callable to find and manipulate the instance
|
||||
- List Retrieve operations work through elaborate filters to get results starting with the .get_queryset() callable.
|
||||
- List Create, Update and Destroy need to implement ListSerializer .create(), .update() and .destroy() methods for bulk operations. None of those methods use the .get_queryset() callable.
|
||||
|
||||
It is easy to notice that the single item apis are useful but limited. List apis allow you to perform much more flexible operations and organize frontends better as well, allowing for example out of step syncing (such as shopping carts). As a result it is recommended to use strictly list based generic views for all apis that are supposed to be flexibly used and restrict yourself to using single item generic views to apis whose access you want to partially restrict to the outside world from scanning (such as users) or are inherently simple.
|
||||
|
||||
The generics are also enhanced to fit within them automated caching functionality. Caching and deleting cache keys is handled by the library in a way that the cache keys have no duplicates. The generic views offered include single item CRUD and list-based CRUD.
|
||||
|
||||
To manage automated caching this the library replaces (and appends to) the DRF filters. These filters need a get_unique_dict method in order to avoid the duplicate cache keys problem.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "starfields-drf-generics"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
authors = [
|
||||
{ name="Anastasios Svolis", email="support@starfields.gr" },
|
||||
]
|
||||
|
||||
@@ -123,8 +123,24 @@ class CachedListCreateModelMixin(CacheDeleteMixin):
|
||||
headers=headers)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
" Generic save hook "
|
||||
serializer.save()
|
||||
"""
|
||||
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 "
|
||||
@@ -179,9 +195,7 @@ class CachedListUpdateModelMixin(CacheDeleteMixin):
|
||||
" 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,
|
||||
serializer = self.get_serializer(data=request.data,
|
||||
partial=partial, many=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
self.perform_update(serializer)
|
||||
@@ -192,8 +206,24 @@ class CachedListUpdateModelMixin(CacheDeleteMixin):
|
||||
return Response(serializer.data)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
" Generic save hook "
|
||||
serializer.save()
|
||||
"""
|
||||
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 "
|
||||
@@ -211,16 +241,19 @@ class CachedListDestroyModelMixin(CacheDeleteMixin):
|
||||
# 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)
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user