2015-07-07 10:09:35 +02:00
|
|
|
from collections import OrderedDict
|
2021-03-04 16:15:57 +01:00
|
|
|
from typing import Any, Dict, Iterable, Type
|
2015-02-04 00:08:38 +01:00
|
|
|
|
2018-11-05 09:04:41 +01:00
|
|
|
from django.db.models import Model
|
2018-08-22 22:00:08 +02:00
|
|
|
from rest_framework import status
|
|
|
|
from rest_framework.decorators import detail_route, list_route
|
2020-05-25 09:16:37 +02:00
|
|
|
from rest_framework.exceptions import APIException
|
2018-08-22 22:00:08 +02:00
|
|
|
from rest_framework.metadata import SimpleMetadata
|
|
|
|
from rest_framework.mixins import (
|
2018-10-15 21:25:41 +02:00
|
|
|
CreateModelMixin as _CreateModelMixin,
|
2021-03-04 16:15:57 +01:00
|
|
|
DestroyModelMixin as _DestroyModelMixin,
|
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 (
|
2019-06-03 17:04:30 +02:00
|
|
|
BooleanField,
|
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,
|
2018-11-05 09:04:41 +01:00
|
|
|
SerializerMetaclass,
|
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
|
2021-03-04 16:15:57 +01:00
|
|
|
from rest_framework.viewsets import GenericViewSet as _GenericViewSet
|
2014-10-11 14:15:42 +02:00
|
|
|
|
2020-05-25 09:16:37 +02:00
|
|
|
from . import logging
|
2016-09-18 16:00:31 +02:00
|
|
|
|
2018-07-09 23:22:26 +02:00
|
|
|
|
2019-01-06 16:22:33 +01:00
|
|
|
__all__ = [
|
2020-11-30 13:00:03 +01:00
|
|
|
"APIException",
|
2019-01-06 16:22:33 +01:00
|
|
|
"detail_route",
|
|
|
|
"DecimalField",
|
|
|
|
"list_route",
|
|
|
|
"SimpleMetadata",
|
|
|
|
"DestroyModelMixin",
|
|
|
|
"CharField",
|
|
|
|
"DictField",
|
2019-06-03 17:04:30 +02:00
|
|
|
"BooleanField",
|
2019-01-06 16:22:33 +01:00
|
|
|
"FileField",
|
|
|
|
"IntegerField",
|
|
|
|
"JSONField",
|
|
|
|
"ListField",
|
|
|
|
"ListSerializer",
|
|
|
|
"status",
|
|
|
|
"RelatedField",
|
|
|
|
"SerializerMethodField",
|
|
|
|
"ValidationError",
|
|
|
|
]
|
2018-08-22 22:00:08 +02:00
|
|
|
|
|
|
|
|
2015-02-12 18:48:14 +01:00
|
|
|
router = DefaultRouter()
|
2020-05-25 09:16:37 +02:00
|
|
|
error_logger = logging.getLogger("openslides.requests.errors")
|
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.
|
|
|
|
"""
|
2019-01-06 16:22:33 +01:00
|
|
|
|
|
|
|
field_name_suffix = "_id"
|
2015-07-07 10:09:35 +02:00
|
|
|
|
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.
|
|
|
|
"""
|
2019-01-06 16:22:33 +01:00
|
|
|
self.source = field_name[: -len(self.field_name_suffix)]
|
2015-07-07 10:09:35 +02:00
|
|
|
super().bind(field_name, parent)
|
|
|
|
|
|
|
|
|
|
|
|
class IdPrimaryKeyRelatedField(PrimaryKeyRelatedField):
|
|
|
|
"""
|
|
|
|
Field, that renames the field name to FIELD_NAME_id.
|
|
|
|
|
|
|
|
Only works together the our ModelSerializer.
|
|
|
|
"""
|
2019-01-06 16:22:33 +01:00
|
|
|
|
|
|
|
field_name_suffix = "_id"
|
2015-07-07 10:09:35 +02:00
|
|
|
|
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.
|
2019-01-06 16:22:33 +01:00
|
|
|
self.source = field_name[: -len(self.field_name_suffix)]
|
2015-07-07 10:09:35 +02:00
|
|
|
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.
|
|
|
|
"""
|
2019-01-06 16:22:33 +01:00
|
|
|
list_kwargs = {"child_relation": cls(*args, **kwargs)}
|
2015-07-07 10:09:35 +02:00
|
|
|
for key in kwargs.keys():
|
|
|
|
if key in MANY_RELATION_KWARGS:
|
|
|
|
list_kwargs[key] = kwargs[key]
|
|
|
|
return IdManyRelatedField(**list_kwargs)
|
|
|
|
|
|
|
|
|
2020-05-25 09:16:37 +02:00
|
|
|
class ErrorLoggingMixin:
|
|
|
|
def handle_exception(self, exc: Any) -> Response:
|
|
|
|
user_id = self.request.user.pk or 0 # type: ignore
|
|
|
|
path = self.request._request.get_full_path() # type: ignore
|
|
|
|
prefix = f"{path} {user_id}"
|
|
|
|
if isinstance(exc, APIException):
|
|
|
|
detail = self._detail_to_string(exc.detail)
|
2020-06-03 14:11:25 +02:00
|
|
|
error_logger.warning(f"{prefix} {str(detail)}")
|
2020-05-25 09:16:37 +02:00
|
|
|
else:
|
2020-06-03 14:11:25 +02:00
|
|
|
error_logger.warning(f"{prefix} unknown exception: {exc}")
|
2020-05-25 09:16:37 +02:00
|
|
|
return super().handle_exception(exc) # type: ignore
|
|
|
|
|
|
|
|
def _detail_to_string(self, detail: Any) -> Any:
|
|
|
|
if isinstance(detail, list):
|
|
|
|
return [self._detail_to_string(item) for item in detail]
|
|
|
|
elif isinstance(detail, dict):
|
|
|
|
return {key: self._detail_to_string(value) for key, value in detail.items()}
|
|
|
|
else:
|
|
|
|
return str(detail)
|
|
|
|
|
|
|
|
|
2015-07-01 23:18:48 +02:00
|
|
|
class PermissionMixin:
|
2015-06-12 21:08:57 +02:00
|
|
|
"""
|
2021-03-04 16:15:57 +01:00
|
|
|
Mixin for subclasses of APIView like GenericViewSet.
|
2015-07-01 23:18:48 +02:00
|
|
|
|
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.
|
|
|
|
"""
|
2019-01-06 16:22:33 +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_serializer_class(self) -> Type[Serializer]:
|
2016-02-11 22:58:32 +01:00
|
|
|
"""
|
2018-11-05 09:04:41 +01:00
|
|
|
Overridden method to return the serializer class for the model.
|
2016-02-11 22:58:32 +01:00
|
|
|
"""
|
2018-11-05 09:04:41 +01:00
|
|
|
model = self.get_queryset().model # type: ignore
|
|
|
|
try:
|
|
|
|
return model_serializer_classes[model]
|
|
|
|
except AttributeError:
|
|
|
|
# If there is no known serializer class for the model, return the
|
|
|
|
# default serializer class.
|
|
|
|
return super().get_serializer_class() # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
model_serializer_classes: Dict[Type[Model], Serializer] = {}
|
|
|
|
|
|
|
|
|
|
|
|
class ModelSerializerRegisterer(SerializerMetaclass):
|
|
|
|
"""
|
|
|
|
Meta class for model serializer that detects the corresponding model
|
|
|
|
and saves it.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __new__(cls, name, bases, attrs): # type: ignore
|
|
|
|
"""
|
|
|
|
Detects the corresponding model from the ModelSerializer by
|
|
|
|
looking into the Meta-class.
|
|
|
|
|
|
|
|
Does nothing, if the Meta-class does not have the model attribute.
|
|
|
|
"""
|
|
|
|
serializer_class = super().__new__(cls, name, bases, attrs)
|
|
|
|
try:
|
|
|
|
model = serializer_class.Meta.model
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
2016-02-11 22:58:32 +01:00
|
|
|
else:
|
2019-04-05 10:44:12 +02:00
|
|
|
if model_serializer_classes.get(model) is not None:
|
|
|
|
error = (
|
|
|
|
f"Model {model} is already used for the serializer class "
|
|
|
|
f"{model_serializer_classes[model]} and cannot be registered "
|
|
|
|
f"for serializer class {serializer_class}."
|
|
|
|
)
|
|
|
|
raise RuntimeError(error)
|
2018-11-05 09:04:41 +01:00
|
|
|
model_serializer_classes[model] = serializer_class
|
2016-02-11 22:58:32 +01:00
|
|
|
return serializer_class
|
|
|
|
|
2015-06-12 21:08:57 +02:00
|
|
|
|
2018-11-05 09:04:41 +01:00
|
|
|
class ModelSerializer(_ModelSerializer, metaclass=ModelSerializerRegisterer):
|
2016-02-11 11:29:19 +01:00
|
|
|
"""
|
|
|
|
ModelSerializer that changes the field names of related fields to
|
|
|
|
FIELD_NAME_id.
|
|
|
|
"""
|
2019-01-06 16:22:33 +01:00
|
|
|
|
2016-02-11 11:29:19 +01:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2018-10-15 21:25:41 +02:00
|
|
|
class CreateModelMixin(_CreateModelMixin):
|
|
|
|
"""
|
|
|
|
Mixin to override create requests.
|
|
|
|
"""
|
2019-01-06 16:22:33 +01:00
|
|
|
|
2018-10-15 21:25:41 +02:00
|
|
|
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(
|
2019-01-06 16:22:33 +01:00
|
|
|
id=response.data.get("id"),
|
|
|
|
serializer=response.data.serializer, # This kwarg is not send to the client.
|
2018-10-15 21:25:41 +02:00
|
|
|
)
|
|
|
|
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.
|
|
|
|
"""
|
2019-01-06 16:22:33 +01:00
|
|
|
|
2018-10-15 21:25:41 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2021-03-04 16:15:57 +01:00
|
|
|
class DestroyModelMixin(_DestroyModelMixin):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2020-05-25 09:16:37 +02:00
|
|
|
class GenericViewSet(ErrorLoggingMixin, PermissionMixin, _GenericViewSet):
|
2016-02-11 22:58:32 +01:00
|
|
|
pass
|
2015-07-01 23:18:48 +02:00
|
|
|
|
|
|
|
|
2019-01-06 16:22:33 +01:00
|
|
|
class ModelViewSet(
|
2020-05-25 09:16:37 +02:00
|
|
|
ErrorLoggingMixin,
|
2019-01-06 16:22:33 +01:00
|
|
|
PermissionMixin,
|
|
|
|
CreateModelMixin,
|
|
|
|
UpdateModelMixin,
|
2021-03-04 16:15:57 +01:00
|
|
|
DestroyModelMixin,
|
|
|
|
_GenericViewSet,
|
2019-01-06 16:22:33 +01:00
|
|
|
):
|
2015-07-01 23:18:48 +02:00
|
|
|
pass
|