188 lines
5.8 KiB
Python
188 lines
5.8 KiB
Python
import re
|
|
from collections import OrderedDict
|
|
from urllib.parse import urlparse
|
|
|
|
from rest_framework.decorators import detail_route, list_route # noqa
|
|
from rest_framework.metadata import SimpleMetadata # noqa
|
|
from rest_framework.mixins import DestroyModelMixin, UpdateModelMixin # noqa
|
|
from rest_framework.response import Response # noqa
|
|
from rest_framework.routers import DefaultRouter
|
|
from rest_framework.serializers import ModelSerializer as _ModelSerializer
|
|
from rest_framework.serializers import ( # noqa
|
|
MANY_RELATION_KWARGS,
|
|
CharField,
|
|
DictField,
|
|
Field,
|
|
FileField,
|
|
IntegerField,
|
|
ListField,
|
|
ListSerializer,
|
|
ManyRelatedField,
|
|
PrimaryKeyRelatedField,
|
|
RelatedField,
|
|
SerializerMethodField,
|
|
ValidationError,
|
|
)
|
|
from rest_framework.viewsets import GenericViewSet as _GenericViewSet # noqa
|
|
from rest_framework.viewsets import ModelViewSet as _ModelViewSet # noqa
|
|
from rest_framework.viewsets import \
|
|
ReadOnlyModelViewSet as _ReadOnlyModelViewSet # noqa
|
|
from rest_framework.viewsets import ViewSet as _ViewSet # noqa
|
|
|
|
from .exceptions import OpenSlidesError
|
|
|
|
router = DefaultRouter()
|
|
|
|
|
|
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'
|
|
|
|
def bind(self, field_name, parent):
|
|
"""
|
|
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'
|
|
|
|
def bind(self, field_name, parent):
|
|
"""
|
|
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
|
|
def many_init(cls, *args, **kwargs):
|
|
"""
|
|
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)
|
|
|
|
|
|
class ModelSerializer(_ModelSerializer):
|
|
"""
|
|
ModelSerializer that changes the field names of related fields to
|
|
FIELD_NAME_id.
|
|
"""
|
|
serializer_related_field = IdPrimaryKeyRelatedField
|
|
|
|
def get_fields(self):
|
|
"""
|
|
Returns all fields of the serializer.
|
|
"""
|
|
fields = OrderedDict()
|
|
|
|
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
|
|
|
|
|
|
class PermissionMixin:
|
|
"""
|
|
Mixin for subclasses of APIView like GenericViewSet and ModelViewSet.
|
|
|
|
The methods check_view_permissions or check_projector_requirements are
|
|
evaluated. If both return False self.permission_denied() is called.
|
|
Django REST framework's permission system is disabled.
|
|
"""
|
|
|
|
def get_permissions(self):
|
|
"""
|
|
Overriden method to check view and projector permissions. Returns an
|
|
empty interable so Django REST framework won't do any other
|
|
permission checks by evaluating Django REST framework style permission
|
|
classes and the request passes.
|
|
"""
|
|
if not self.check_view_permissions() and not self.check_projector_requirements():
|
|
self.permission_denied(self.request)
|
|
return ()
|
|
|
|
def check_view_permissions(self):
|
|
"""
|
|
Override this and return True if the requesting user should be able to
|
|
get access to your view.
|
|
"""
|
|
return False
|
|
|
|
def check_projector_requirements(self):
|
|
"""
|
|
Helper method which returns True if the current request (on this
|
|
view instance) is required for at least one active projector element.
|
|
"""
|
|
from openslides.core.models import Projector
|
|
|
|
result = False
|
|
if self.request.user.has_perm('core.can_see_projector'):
|
|
for requirement in Projector.get_all_requirements():
|
|
if requirement.is_currently_required(view_instance=self):
|
|
result = True
|
|
break
|
|
return result
|
|
|
|
|
|
class GenericViewSet(PermissionMixin, _GenericViewSet):
|
|
pass
|
|
|
|
|
|
class ModelViewSet(PermissionMixin, _ModelViewSet):
|
|
pass
|
|
|
|
|
|
class ReadOnlyModelViewSet(PermissionMixin, _ReadOnlyModelViewSet):
|
|
pass
|
|
|
|
|
|
class ViewSet(PermissionMixin, _ViewSet):
|
|
pass
|
|
|
|
|
|
def get_collection_and_id_from_url(url):
|
|
"""
|
|
Helper function. Returns a tuple containing the collection name and the id
|
|
extracted out of the given REST api URL.
|
|
|
|
For example get_collection_and_id_from_url('http://localhost/api/users/user/3/')
|
|
returns ('users/user', '3').
|
|
|
|
Raises OpenSlidesError if the URL is invalid.
|
|
"""
|
|
path = urlparse(url).path
|
|
match = re.match(r'^/rest/(?P<collection>[-\w]+/[-\w]+)/(?P<id>[-\w]+)/$', path)
|
|
if not match:
|
|
raise OpenSlidesError('Invalid REST api URL: %s' % url)
|
|
return match.group('collection'), match.group('id')
|