2015-07-07 10:09:35 +02:00
|
|
|
from collections import OrderedDict
|
2018-08-22 22:00:08 +02:00
|
|
|
from typing import Any, Dict, Iterable, Optional, Type
|
2015-02-04 00:08:38 +01:00
|
|
|
|
2018-11-01 17:30:18 +01:00
|
|
|
from asgiref.sync import async_to_sync
|
2016-09-18 16:00:31 +02:00
|
|
|
from django.http import Http404
|
2018-08-22 22:00:08 +02:00
|
|
|
from rest_framework import status
|
|
|
|
from rest_framework.decorators import detail_route, list_route
|
|
|
|
from rest_framework.metadata import SimpleMetadata
|
|
|
|
from rest_framework.mixins import (
|
2018-10-15 21:25:41 +02:00
|
|
|
CreateModelMixin as _CreateModelMixin,
|
2018-06-26 15:59:05 +02:00
|
|
|
DestroyModelMixin,
|
2018-07-09 23:22:26 +02:00
|
|
|
ListModelMixin as _ListModelMixin,
|
|
|
|
RetrieveModelMixin as _RetrieveModelMixin,
|
2018-10-15 21:25:41 +02:00
|
|
|
UpdateModelMixin as _UpdateModelMixin,
|
2018-06-26 15:59:05 +02:00
|
|
|
)
|
2018-07-09 23:22:26 +02:00
|
|
|
from rest_framework.relations import MANY_RELATION_KWARGS
|
2018-10-15 21:25:41 +02:00
|
|
|
from rest_framework.request import Request
|
2016-09-18 16:00:31 +02:00
|
|
|
from rest_framework.response import Response
|
2015-06-16 10:37:23 +02:00
|
|
|
from rest_framework.routers import DefaultRouter
|
2018-08-22 22:00:08 +02:00
|
|
|
from rest_framework.serializers import (
|
2015-02-12 18:48:14 +01:00
|
|
|
CharField,
|
2018-08-22 17:34:16 +02:00
|
|
|
DecimalField,
|
2015-06-14 23:26:06 +02:00
|
|
|
DictField,
|
2015-02-18 01:45:39 +01:00
|
|
|
Field,
|
2015-09-06 14:12:34 +02:00
|
|
|
FileField,
|
2015-04-30 19:13:28 +02:00
|
|
|
IntegerField,
|
2017-05-23 14:07:06 +02:00
|
|
|
JSONField,
|
2015-06-14 23:26:06 +02:00
|
|
|
ListField,
|
2015-02-12 18:48:14 +01:00
|
|
|
ListSerializer,
|
2015-07-07 10:09:35 +02:00
|
|
|
ManyRelatedField,
|
2018-07-09 23:22:26 +02:00
|
|
|
ModelSerializer as _ModelSerializer,
|
2015-02-12 18:48:14 +01:00
|
|
|
PrimaryKeyRelatedField,
|
|
|
|
RelatedField,
|
2017-08-24 12:26:55 +02:00
|
|
|
Serializer,
|
2015-02-12 20:57:05 +01:00
|
|
|
SerializerMethodField,
|
2015-06-16 10:37:23 +02:00
|
|
|
ValidationError,
|
|
|
|
)
|
2018-10-15 21:25:41 +02:00
|
|
|
from rest_framework.utils.serializer_helpers import ReturnDict
|
2018-08-22 22:00:08 +02:00
|
|
|
from rest_framework.viewsets import (
|
2018-07-09 23:22:26 +02:00
|
|
|
GenericViewSet as _GenericViewSet,
|
|
|
|
ModelViewSet as _ModelViewSet,
|
|
|
|
)
|
2014-10-11 14:15:42 +02:00
|
|
|
|
2017-09-04 00:25:45 +02:00
|
|
|
from .access_permissions import BaseAccessPermissions
|
2018-11-01 17:30:18 +01:00
|
|
|
from .cache import element_cache
|
2016-09-18 16:00:31 +02:00
|
|
|
|
2018-07-09 23:22:26 +02:00
|
|
|
|
2018-10-15 21:25:41 +02:00
|
|
|
__all__ = ['detail_route', 'DecimalField', 'list_route', 'SimpleMetadata',
|
|
|
|
'DestroyModelMixin', 'CharField', 'DictField', 'FileField',
|
2018-08-23 21:26:24 +02:00
|
|
|
'IntegerField', 'JSONField', 'ListField', 'ListSerializer', 'status', 'RelatedField',
|
2018-08-22 22:00:08 +02:00
|
|
|
'SerializerMethodField', 'ValidationError']
|
|
|
|
|
|
|
|
|
2015-02-12 18:48:14 +01:00
|
|
|
router = DefaultRouter()
|
2015-01-17 14:01:44 +01:00
|
|
|
|
|
|
|
|
2015-07-07 10:09:35 +02:00
|
|
|
class IdManyRelatedField(ManyRelatedField):
|
|
|
|
"""
|
|
|
|
ManyRelatedField that appends an suffix to the sub-fields.
|
|
|
|
|
|
|
|
Only works together with the IdPrimaryKeyRelatedField and our
|
|
|
|
ModelSerializer.
|
|
|
|
"""
|
|
|
|
field_name_suffix = '_id'
|
|
|
|
|
2017-08-24 12:26:55 +02:00
|
|
|
def bind(self, field_name: str, parent: Any) -> None:
|
2015-07-07 10:09:35 +02:00
|
|
|
"""
|
|
|
|
Called when the field is bound to the serializer.
|
|
|
|
|
|
|
|
See IdPrimaryKeyRelatedField for more informations.
|
|
|
|
"""
|
|
|
|
self.source = field_name[:-len(self.field_name_suffix)]
|
|
|
|
super().bind(field_name, parent)
|
|
|
|
|
|
|
|
|
|
|
|
class IdPrimaryKeyRelatedField(PrimaryKeyRelatedField):
|
|
|
|
"""
|
|
|
|
Field, that renames the field name to FIELD_NAME_id.
|
|
|
|
|
|
|
|
Only works together the our ModelSerializer.
|
|
|
|
"""
|
|
|
|
field_name_suffix = '_id'
|
|
|
|
|
2017-08-24 12:26:55 +02:00
|
|
|
def bind(self, field_name: str, parent: Any) -> None:
|
2015-07-07 10:09:35 +02:00
|
|
|
"""
|
|
|
|
Called when the field is bound to the serializer.
|
|
|
|
|
|
|
|
Changes the source so that the original field name is used (removes
|
|
|
|
the _id suffix).
|
|
|
|
"""
|
|
|
|
if field_name:
|
|
|
|
# field_name is an empty string when the field is created with the
|
|
|
|
# attribute many=True. In this case the suffix is added with the
|
|
|
|
# IdManyRelatedField class.
|
|
|
|
self.source = field_name[:-len(self.field_name_suffix)]
|
|
|
|
super().bind(field_name, parent)
|
|
|
|
|
|
|
|
@classmethod
|
2017-08-24 12:26:55 +02:00
|
|
|
def many_init(cls, *args: Any, **kwargs: Any) -> IdManyRelatedField:
|
2015-07-07 10:09:35 +02:00
|
|
|
"""
|
|
|
|
Method from rest_framework.relations.RelatedField That uses our
|
|
|
|
IdManyRelatedField class instead of
|
|
|
|
rest_framework.relations.ManyRelatedField class.
|
|
|
|
"""
|
|
|
|
list_kwargs = {'child_relation': cls(*args, **kwargs)}
|
|
|
|
for key in kwargs.keys():
|
|
|
|
if key in MANY_RELATION_KWARGS:
|
|
|
|
list_kwargs[key] = kwargs[key]
|
|
|
|
return IdManyRelatedField(**list_kwargs)
|
|
|
|
|
|
|
|
|
2015-07-01 23:18:48 +02:00
|
|
|
class PermissionMixin:
|
2015-06-12 21:08:57 +02:00
|
|
|
"""
|
2015-07-01 23:18:48 +02:00
|
|
|
Mixin for subclasses of APIView like GenericViewSet and ModelViewSet.
|
|
|
|
|
2016-09-17 22:26:23 +02:00
|
|
|
The method check_view_permissions is evaluated. If it returns False
|
|
|
|
self.permission_denied() is called. Django REST Framework's permission
|
|
|
|
system is disabled.
|
2015-07-01 23:18:48 +02:00
|
|
|
|
2016-02-11 22:58:32 +01:00
|
|
|
Also connects container to handle access permissions for model and
|
|
|
|
viewset.
|
|
|
|
"""
|
2018-08-22 22:00:08 +02:00
|
|
|
access_permissions: Optional[BaseAccessPermissions] = None
|
2016-02-11 11:29:19 +01:00
|
|
|
|
2017-08-24 12:26:55 +02:00
|
|
|
def get_permissions(self) -> Iterable[str]:
|
2015-06-12 21:08:57 +02:00
|
|
|
"""
|
2016-09-17 22:26:23 +02:00
|
|
|
Overridden method to check view permissions. Returns an empty
|
|
|
|
iterable so Django REST framework won't do any other permission
|
|
|
|
checks by evaluating Django REST framework style permission classes
|
|
|
|
and the request passes.
|
2015-06-12 21:08:57 +02:00
|
|
|
"""
|
2016-09-17 22:26:23 +02:00
|
|
|
if not self.check_view_permissions():
|
2017-08-24 12:26:55 +02:00
|
|
|
self.permission_denied(self.request) # type: ignore
|
2015-07-01 23:18:48 +02:00
|
|
|
return ()
|
2015-06-12 21:08:57 +02:00
|
|
|
|
2017-08-24 12:26:55 +02:00
|
|
|
def check_view_permissions(self) -> bool:
|
2015-07-01 23:18:48 +02:00
|
|
|
"""
|
|
|
|
Override this and return True if the requesting user should be able to
|
|
|
|
get access to your view.
|
2016-02-11 22:58:32 +01:00
|
|
|
|
2016-09-17 22:26:23 +02:00
|
|
|
Don't forget to use access permissions container for list and retrieve
|
|
|
|
requests.
|
2015-07-01 23:18:48 +02:00
|
|
|
"""
|
|
|
|
return False
|
2015-06-12 21:08:57 +02:00
|
|
|
|
2017-08-24 12:26:55 +02:00
|
|
|
def get_access_permissions(self) -> BaseAccessPermissions:
|
2016-02-11 22:58:32 +01:00
|
|
|
"""
|
|
|
|
Returns a container to handle access permissions for this viewset and
|
|
|
|
its corresponding model.
|
|
|
|
"""
|
2017-08-24 12:26:55 +02:00
|
|
|
return self.access_permissions # type: ignore
|
2016-02-11 22:58:32 +01:00
|
|
|
|
2017-08-24 12:26:55 +02:00
|
|
|
def get_serializer_class(self) -> Type[Serializer]:
|
2016-02-11 22:58:32 +01:00
|
|
|
"""
|
|
|
|
Overridden method to return the serializer class given by the
|
|
|
|
access permissions container.
|
|
|
|
"""
|
|
|
|
if self.get_access_permissions() is not None:
|
2017-08-24 12:26:55 +02:00
|
|
|
serializer_class = self.get_access_permissions().get_serializer_class(self.request.user) # type: ignore
|
2016-02-11 22:58:32 +01:00
|
|
|
else:
|
2017-08-24 12:26:55 +02:00
|
|
|
serializer_class = super().get_serializer_class() # type: ignore
|
2016-02-11 22:58:32 +01:00
|
|
|
return serializer_class
|
|
|
|
|
2015-06-12 21:08:57 +02:00
|
|
|
|
2016-02-11 11:29:19 +01:00
|
|
|
class ModelSerializer(_ModelSerializer):
|
|
|
|
"""
|
|
|
|
ModelSerializer that changes the field names of related fields to
|
|
|
|
FIELD_NAME_id.
|
|
|
|
"""
|
|
|
|
serializer_related_field = IdPrimaryKeyRelatedField
|
|
|
|
|
2017-08-24 12:26:55 +02:00
|
|
|
def get_fields(self) -> Any:
|
2016-02-11 11:29:19 +01:00
|
|
|
"""
|
|
|
|
Returns all fields of the serializer.
|
|
|
|
"""
|
2018-08-22 22:00:08 +02:00
|
|
|
fields: Dict[str, Field] = OrderedDict()
|
2016-02-11 11:29:19 +01:00
|
|
|
|
|
|
|
for field_name, field in super().get_fields().items():
|
|
|
|
try:
|
|
|
|
field_name += field.field_name_suffix
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
fields[field_name] = field
|
|
|
|
return fields
|
|
|
|
|
|
|
|
|
2016-09-18 16:00:31 +02:00
|
|
|
class ListModelMixin(_ListModelMixin):
|
|
|
|
"""
|
|
|
|
Mixin to add the caching system to list requests.
|
|
|
|
"""
|
2017-08-24 12:26:55 +02:00
|
|
|
def list(self, request: Any, *args: Any, **kwargs: Any) -> Response:
|
2016-09-18 16:00:31 +02:00
|
|
|
model = self.get_queryset().model
|
|
|
|
try:
|
|
|
|
collection_string = model.get_collection_string()
|
|
|
|
except AttributeError:
|
|
|
|
# The corresponding queryset does not support caching.
|
|
|
|
response = super().list(request, *args, **kwargs)
|
|
|
|
else:
|
2018-11-03 23:40:20 +01:00
|
|
|
all_restricted_data = async_to_sync(element_cache.get_all_restricted_data)(request.user.pk or 0)
|
2018-11-01 17:30:18 +01:00
|
|
|
response = Response(all_restricted_data.get(collection_string, []))
|
2016-09-18 16:00:31 +02:00
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
class RetrieveModelMixin(_RetrieveModelMixin):
|
|
|
|
"""
|
|
|
|
Mixin to add the caching system to retrieve requests.
|
|
|
|
"""
|
2017-08-24 12:26:55 +02:00
|
|
|
def retrieve(self, request: Any, *args: Any, **kwargs: Any) -> Response:
|
2016-09-18 16:00:31 +02:00
|
|
|
model = self.get_queryset().model
|
|
|
|
try:
|
|
|
|
collection_string = model.get_collection_string()
|
|
|
|
except AttributeError:
|
|
|
|
# The corresponding queryset does not support caching.
|
|
|
|
response = super().retrieve(request, *args, **kwargs)
|
|
|
|
else:
|
|
|
|
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
2018-11-03 23:40:20 +01:00
|
|
|
user_id = request.user.pk or 0
|
|
|
|
content = async_to_sync(element_cache.get_element_restricted_data)(user_id, collection_string, self.kwargs[lookup_url_kwarg])
|
2016-09-18 16:00:31 +02:00
|
|
|
if content is None:
|
2018-11-01 17:30:18 +01:00
|
|
|
raise Http404
|
2016-09-18 16:00:31 +02:00
|
|
|
response = Response(content)
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
2018-10-15 21:25:41 +02:00
|
|
|
class CreateModelMixin(_CreateModelMixin):
|
|
|
|
"""
|
|
|
|
Mixin to override create requests.
|
|
|
|
"""
|
|
|
|
def create(self, request: Request, *args: Any, **kwargs: Any) -> Response:
|
|
|
|
"""
|
|
|
|
Just remove all response data (except 'id') so nobody may get
|
|
|
|
unrestricted data.
|
2015-07-01 23:18:48 +02:00
|
|
|
|
2018-10-15 21:25:41 +02:00
|
|
|
Special viewsets may override this.
|
|
|
|
"""
|
|
|
|
response = super().create(request, *args, **kwargs)
|
|
|
|
response.data = ReturnDict(
|
|
|
|
id=response.data.get('id'),
|
|
|
|
serializer=response.data.serializer # This kwarg is not send to the client.
|
|
|
|
)
|
|
|
|
return response
|
2015-07-01 23:18:48 +02:00
|
|
|
|
2018-10-15 21:25:41 +02:00
|
|
|
|
|
|
|
class UpdateModelMixin(_UpdateModelMixin):
|
|
|
|
"""
|
|
|
|
Mixin to override update requests.
|
|
|
|
"""
|
|
|
|
def update(self, request: Request, *args: Any, **kwargs: Any) -> Response:
|
|
|
|
"""
|
|
|
|
Just remove all response data so nobody may get unrestricted data.
|
|
|
|
|
|
|
|
Special viewsets may override this.
|
|
|
|
"""
|
|
|
|
response = super().update(request, *args, **kwargs)
|
|
|
|
response.data = None
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
class GenericViewSet(PermissionMixin, _GenericViewSet):
|
2016-02-11 22:58:32 +01:00
|
|
|
pass
|
2015-07-01 23:18:48 +02:00
|
|
|
|
|
|
|
|
2018-10-15 21:25:41 +02:00
|
|
|
class ModelViewSet(PermissionMixin, ListModelMixin, RetrieveModelMixin,
|
|
|
|
CreateModelMixin, UpdateModelMixin, _ModelViewSet):
|
2015-07-01 23:18:48 +02:00
|
|
|
pass
|