2015-07-07 10:09:35 +02:00
|
|
|
from collections import OrderedDict
|
2017-08-24 12:26:55 +02:00
|
|
|
from typing import Any, Dict, Iterable, Optional, Type # noqa
|
2015-02-04 00:08:38 +01:00
|
|
|
|
2016-09-18 16:00:31 +02:00
|
|
|
from django.http import Http404
|
2015-09-16 00:55:27 +02:00
|
|
|
from rest_framework import status # noqa
|
2015-07-01 23:18:48 +02:00
|
|
|
from rest_framework.decorators import detail_route, list_route # noqa
|
2015-06-17 18:32:05 +02:00
|
|
|
from rest_framework.metadata import SimpleMetadata # noqa
|
2016-09-18 16:00:31 +02:00
|
|
|
from rest_framework.mixins import ListModelMixin as _ListModelMixin
|
|
|
|
from rest_framework.mixins import RetrieveModelMixin as _RetrieveModelMixin
|
2018-06-26 15:59:05 +02:00
|
|
|
from rest_framework.mixins import ( # noqa
|
|
|
|
CreateModelMixin,
|
|
|
|
DestroyModelMixin,
|
|
|
|
UpdateModelMixin,
|
|
|
|
)
|
2018-07-09 23:22:26 +02:00
|
|
|
from rest_framework.relations import MANY_RELATION_KWARGS
|
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
|
2015-07-07 10:09:35 +02:00
|
|
|
from rest_framework.serializers import ModelSerializer as _ModelSerializer
|
2015-02-12 18:48:14 +01:00
|
|
|
from rest_framework.serializers import ( # noqa
|
|
|
|
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,
|
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,
|
|
|
|
)
|
2015-07-01 23:18:48 +02:00
|
|
|
from rest_framework.viewsets import GenericViewSet as _GenericViewSet # noqa
|
2015-06-16 10:37:23 +02:00
|
|
|
from rest_framework.viewsets import ModelViewSet as _ModelViewSet # noqa
|
2015-07-01 23:18:48 +02:00
|
|
|
from rest_framework.viewsets import ViewSet as _ViewSet # noqa
|
2014-10-11 14:15:42 +02:00
|
|
|
|
2017-09-04 00:25:45 +02:00
|
|
|
from .access_permissions import BaseAccessPermissions
|
2017-01-26 15:34:24 +01:00
|
|
|
from .auth import user_to_collection_user
|
2016-09-18 16:00:31 +02:00
|
|
|
from .collection import Collection, CollectionElement
|
|
|
|
|
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.
|
|
|
|
"""
|
2017-08-23 20:51:06 +02:00
|
|
|
access_permissions = None # type: Optional[BaseAccessPermissions]
|
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.
|
|
|
|
"""
|
2017-08-24 12:26:55 +02:00
|
|
|
fields = OrderedDict() # type: Dict[str, Field]
|
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.
|
|
|
|
|
|
|
|
It is not allowed to use the method get_queryset() in derivated classes.
|
|
|
|
The attribute queryset has to be used in the following form:
|
|
|
|
|
|
|
|
queryset = Model.objects.all()
|
|
|
|
"""
|
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:
|
|
|
|
collection = Collection(collection_string)
|
2017-01-26 15:34:24 +01:00
|
|
|
user = user_to_collection_user(request.user)
|
|
|
|
response = Response(collection.as_list_for_user(user))
|
2016-09-18 16:00:31 +02:00
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
class RetrieveModelMixin(_RetrieveModelMixin):
|
|
|
|
"""
|
|
|
|
Mixin to add the caching system to retrieve requests.
|
|
|
|
|
|
|
|
It is not allowed to use the method get_queryset() in derivated classes.
|
|
|
|
The attribute queryset has to be used in the following form:
|
|
|
|
|
|
|
|
queryset = Model.objects.all()
|
|
|
|
"""
|
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
|
|
|
|
collection_element = CollectionElement.from_values(
|
|
|
|
collection_string, self.kwargs[lookup_url_kwarg])
|
2017-01-26 15:34:24 +01:00
|
|
|
user = user_to_collection_user(request.user)
|
2016-09-18 16:00:31 +02:00
|
|
|
try:
|
2017-09-04 00:25:45 +02:00
|
|
|
content = collection_element.as_dict_for_user(user)
|
2016-09-18 16:00:31 +02:00
|
|
|
except collection_element.get_model().DoesNotExist:
|
|
|
|
raise Http404
|
|
|
|
if content is None:
|
|
|
|
self.permission_denied(request)
|
|
|
|
response = Response(content)
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
2015-07-01 23:18:48 +02:00
|
|
|
class GenericViewSet(PermissionMixin, _GenericViewSet):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2016-09-18 16:00:31 +02:00
|
|
|
class ModelViewSet(PermissionMixin, ListModelMixin, RetrieveModelMixin, _ModelViewSet):
|
2016-02-11 22:58:32 +01:00
|
|
|
pass
|
2015-07-01 23:18:48 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ViewSet(PermissionMixin, _ViewSet):
|
|
|
|
pass
|