diff --git a/openslides/core/migrations/0001_initial.py b/openslides/core/migrations/0001_initial.py index c1a8f29be..79267669e 100644 --- a/openslides/core/migrations/0001_initial.py +++ b/openslides/core/migrations/0001_initial.py @@ -18,7 +18,7 @@ def add_default_projector(apps, schema_editor): weight=-500) Projector = apps.get_model('core', 'Projector') projector_config = [ - {'name': 'core/clock'}, + {'name': 'core/clock', 'stable': True}, {'name': 'core/customslide', 'id': custom_slide.id}] Projector.objects.create(config=projector_config) diff --git a/openslides/core/models.py b/openslides/core/models.py index 991840a71..c1d36f8ab 100644 --- a/openslides/core/models.py +++ b/openslides/core/models.py @@ -52,7 +52,7 @@ class Projector(RESTModelMixin, models.Model): for element in ProjectorElement.get_all(): elements[element.name] = element for config_entry in self.config: - name = config_entry.get('name') + name = config_entry['name'] element = elements.get(name) data = {'name': name} if element is None: @@ -66,6 +66,22 @@ class Projector(RESTModelMixin, models.Model): data['error'] = str(e) yield data + @classmethod + def get_all_requirements(cls): + """ + Generator which returns all ProjectorRequirement instances of all + active projector elements. + """ + elements = {} + for element in ProjectorElement.get_all(): + elements[element.name] = element + for projector in cls.objects.all(): + for config_entry in projector.config: + element = elements.get(config_entry['name']) + if element is not None: + for requirement in element.get_requirements(config_entry): + yield requirement + class CustomSlide(RESTModelMixin, AbsoluteUrlMixin, models.Model): """ diff --git a/openslides/core/projector.py b/openslides/core/projector.py index 9c7c0315e..0ba63fa37 100644 --- a/openslides/core/projector.py +++ b/openslides/core/projector.py @@ -1,10 +1,11 @@ from django.utils.timezone import now from django.utils.translation import ugettext as _ -from openslides.utils.projector import ProjectorElement +from openslides.utils.projector import ProjectorElement, ProjectorRequirement from .exceptions import ProjectorException from .models import CustomSlide +from .views import CustomSlideViewSet class CustomSlideSlide(ProjectorElement): @@ -22,6 +23,19 @@ class CustomSlideSlide(ProjectorElement): 'collection': 'core/customslide', 'id': pk}] + def get_requirements(self, config_entry): + self.config_entry = config_entry + try: + pk = self.get_context()[0]['id'] + except ProjectorException: + # Custom slide does not exist so just do nothing. + pass + else: + yield ProjectorRequirement( + view_class=CustomSlideViewSet, + view_action='retrieve', + pk=str(pk)) + class Clock(ProjectorElement): """ diff --git a/openslides/users/projector.py b/openslides/users/projector.py index a35ecacaf..8457ded81 100644 --- a/openslides/users/projector.py +++ b/openslides/users/projector.py @@ -1,9 +1,10 @@ from django.utils.translation import ugettext as _ from openslides.core.exceptions import ProjectorException -from openslides.utils.projector import ProjectorElement +from openslides.utils.projector import ProjectorElement, ProjectorRequirement from .models import User +from .views import GroupViewSet, UserViewSet class UserSlide(ProjectorElement): @@ -27,3 +28,17 @@ class UserSlide(ProjectorElement): 'collection': 'users/group', 'id': group.pk}) return result + + def get_requirements(self, config_entry): + self.config_entry = config_entry + try: + context = self.get_context() + except ProjectorException: + # User does not exist so just do nothing. + pass + else: + for item in context: + yield ProjectorRequirement( + view_class=UserViewSet if item['collection'] == 'users/user' else GroupViewSet, + view_action='retrieve', + pk=str(item['id'])) diff --git a/openslides/utils/projector.py b/openslides/utils/projector.py index 629abe9b4..4dc805cc4 100644 --- a/openslides/utils/projector.py +++ b/openslides/utils/projector.py @@ -64,3 +64,39 @@ class ProjectorElement(object, metaclass=SignalConnectMetaClass): Returns the context of the projector element. """ return None + + def get_requirements(self, config_entry): + """ + Returns an iterable of ProjectorRequirement instances to setup + which views should be accessable for projector clients if the + projector element is active. The config_entry has to be given. + """ + return () + + +class ProjectorRequirement: + """ + Container for required views. Such a view is defined by its class, its + action and its kwargs which come from the URL path. + """ + def __init__(self, view_class, view_action, **kwargs): + self.view_class = view_class + self.view_action = view_action + self.kwargs = kwargs + + def is_currently_required(self, view_instance): + """ + Returns True if the view_instance matches the initiated data of this + requirement. + """ + if not type(view_instance) == self.view_class: + result = False + elif not view_instance.action == self.view_action: + result = False + else: + result = True + for key in view_instance.kwargs: + if not self.kwargs[key] == view_instance.kwargs[key]: + result = False + break + return result diff --git a/openslides/utils/rest_api.py b/openslides/utils/rest_api.py index 9f9b09ac0..0840b988a 100644 --- a/openslides/utils/rest_api.py +++ b/openslides/utils/rest_api.py @@ -19,7 +19,8 @@ from rest_framework.serializers import ( # 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.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet, ViewSet # noqa +from rest_framework.viewsets import ModelViewSet as _ModelViewSet +from rest_framework.viewsets import GenericViewSet, ReadOnlyModelViewSet, ViewSet # noqa from rest_framework.decorators import list_route # noqa from .exceptions import OpenSlidesError @@ -51,6 +52,44 @@ class RESTModelMixin: return reverse(rest_url, args=[str(root_instance.pk)]) +class ModelViewSet(_ModelViewSet): + """ + Viewset for models. Before the method check_permission is called we + check projector requirements. If access for projector client users is + not currently required, check_permission is called, else not. + """ + def initial(self, request, *args, **kwargs): + """ + Runs anything that needs to occur prior to calling the method handler. + """ + self.format_kwarg = self.get_format_suffix(**kwargs) + + # Ensure that the incoming request is permitted + self.perform_authentication(request) + if not self.check_projector_requirements(): + self.check_permissions(request) + self.check_throttles(request) + + # Perform content negotiation and store the accepted info on the request + neg = self.perform_content_negotiation(request) + request.accepted_renderer, request.accepted_media_type = neg + + 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 + + def get_collection_and_id_from_url(url): """ Helper function. Returns a tuple containing the collection name and the id