From 5a1f638f8d7683caf02feb408c8b75feb25d91e3 Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Tue, 9 Apr 2019 16:13:40 +0200 Subject: [PATCH] Protect element fields from the projector model. Fixes creation of projectors. --- .../projector-list.component.ts | 4 - openslides/core/models.py | 2 +- openslides/core/serializers.py | 18 ++- openslides/core/views.py | 30 ++-- tests/common_groups.py | 6 + tests/integration/core/test_viewset.py | 143 ++++++++++++++++++ tests/integration/motions/test_viewset.py | 12 +- 7 files changed, 189 insertions(+), 26 deletions(-) create mode 100644 tests/common_groups.py diff --git a/client/src/app/site/projector/components/projector-list/projector-list.component.ts b/client/src/app/site/projector/components/projector-list/projector-list.component.ts index 289990dba..8709400af 100644 --- a/client/src/app/site/projector/components/projector-list/projector-list.component.ts +++ b/client/src/app/site/projector/components/projector-list/projector-list.component.ts @@ -148,11 +148,7 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit public create(): void { if (this.createForm.valid && this.projectorToCreate) { this.projectorToCreate.patchValues(this.createForm.value as Projector); - // TODO: the server shouldn't want to have element data.. this.projectorToCreate.patchValues({ - elements: [{ name: 'core/clock', stable: true }], - elements_preview: [], - elements_history: [], reference_projector_id: this.projectors[0].reference_projector_id }); this.repo.create(this.projectorToCreate).then(() => (this.projectorToCreate = null), this.raiseError); diff --git a/openslides/core/models.py b/openslides/core/models.py index 90ecf7059..512d3a6b6 100644 --- a/openslides/core/models.py +++ b/openslides/core/models.py @@ -91,7 +91,7 @@ class Projector(RESTModelMixin, models.Model): show_title = models.BooleanField(default=True) show_logo = models.BooleanField(default=True) - name = models.CharField(max_length=255, unique=True, blank=True) + name = models.CharField(max_length=255, unique=True) reference_projector = models.ForeignKey( "self", diff --git a/openslides/core/serializers.py b/openslides/core/serializers.py index 4b0456f43..1dfd72a4b 100644 --- a/openslides/core/serializers.py +++ b/openslides/core/serializers.py @@ -1,7 +1,13 @@ from typing import Any from ..utils.projector import projector_slides -from ..utils.rest_api import Field, IntegerField, ModelSerializer, ValidationError +from ..utils.rest_api import ( + Field, + IdPrimaryKeyRelatedField, + IntegerField, + ModelSerializer, + ValidationError, +) from ..utils.validate import validate_html from .models import ( ChatMessage, @@ -77,13 +83,17 @@ class ProjectorSerializer(ModelSerializer): Serializer for core.models.Projector objects. """ - elements = JSONSerializerField(validators=[elements_validator]) - elements_preview = JSONSerializerField(validators=[elements_validator]) - elements_history = JSONSerializerField(validators=[elements_array_validator]) + elements = JSONSerializerField(read_only=True) + elements_preview = JSONSerializerField(read_only=True) + elements_history = JSONSerializerField(read_only=True) width = IntegerField(min_value=800, max_value=3840, required=False) height = IntegerField(min_value=340, max_value=2880, required=False) + projectiondefaults = IdPrimaryKeyRelatedField( + many=True, required=False, queryset=ProjectionDefault.objects.all() + ) + class Meta: model = Projector fields = ( diff --git a/openslides/core/views.py b/openslides/core/views.py index e17e258ea..e15087e55 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -57,6 +57,7 @@ from .models import ( ProjectorMessage, Tag, ) +from .serializers import elements_array_validator, elements_validator # Special Django views @@ -139,6 +140,11 @@ class ProjectorViewSet(ModelViewSet): result = False return result + def perform_create(self, serializer): + projector = serializer.save() + projector.elements = [{"name": "core/clock", "stable": True}] + projector.save() + def destroy(self, *args, **kwargs): """ REST API operation for DELETE requests. @@ -184,22 +190,24 @@ class ProjectorViewSet(ModelViewSet): "delete_last_history_element", False ) - changed_data = {} if elements is not None: - changed_data["elements"] = elements + elements_validator(elements) + projector.elements = elements + if preview is not None: - changed_data["elements_preview"] = preview + elements_validator(preview) + projector.elements_preview = preview + + elements_history = None if history_element is not None and delete_last_history_element is False: - history = projector.elements_history + [history_element] - changed_data["elements_history"] = history + elements_history = projector.elements_history + [history_element] if history_element is None and delete_last_history_element is True: - history = projector.elements_history[:-1] - changed_data["elements_history"] = history - - serializer = self.get_serializer(projector, data=changed_data, partial=True) - serializer.is_valid(raise_exception=True) - self.perform_update(serializer) + elements_history = projector.elements_history[:-1] + if elements_history is not None: + elements_array_validator(elements_history) + projector.elements_history = elements_history + projector.save() return Response() @detail_route(methods=["post"]) diff --git a/tests/common_groups.py b/tests/common_groups.py new file mode 100644 index 000000000..0a6ae1f3f --- /dev/null +++ b/tests/common_groups.py @@ -0,0 +1,6 @@ +# In this file are the default group ids which should be used in all tests. + +GROUP_DEFAULT_PK = 1 +GROUP_ADMIN_PK = 2 +GROUP_DELEGATE_PK = 3 +GROUP_STAFF_PK = 4 diff --git a/tests/integration/core/test_viewset.py b/tests/integration/core/test_viewset.py index 9e7082b58..83fe863b0 100644 --- a/tests/integration/core/test_viewset.py +++ b/tests/integration/core/test_viewset.py @@ -1,11 +1,14 @@ import pytest from django.urls import reverse from rest_framework import status +from rest_framework.test import APIClient from openslides.core.config import config from openslides.core.models import ChatMessage, Projector, Tag from openslides.users.models import User +from openslides.utils.autoupdate import inform_changed_data from openslides.utils.test import TestCase +from tests.common_groups import GROUP_ADMIN_PK, GROUP_DELEGATE_PK from ..helpers import count_queries @@ -59,6 +62,146 @@ def test_config_db_queries(): assert count_queries(Tag.get_elements) == 1 +class ProjectorViewSet(TestCase): + """ + Tests (currently just parts) of the ProjectorViewSet. + """ + + def setUp(self): + self.client = APIClient() + self.client.login(username="admin", password="admin") + + def test_create(self): + response = self.client.post( + reverse("projector-list"), {"name": "test_name_efIOLJHF32f&EF)NG3fw"} + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + # pk=1 should be the default projector and pk=2 the new one + self.assertEqual(Projector.objects.all().count(), 2) + self.assertTrue(Projector.objects.filter(pk=2).exists()) + projector = Projector.objects.get(pk=2) + self.assertEqual(projector.name, "test_name_efIOLJHF32f&EF)NG3fw") + self.assertEqual(projector.elements, [{"name": "core/clock", "stable": True}]) + + def test_create_no_data(self): + response = self.client.post(reverse("projector-list")) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(Projector.objects.all().count(), 1) + + def test_no_permission(self): + admin = User.objects.get(username="admin") + admin.groups.add(GROUP_DELEGATE_PK) + admin.groups.remove(GROUP_ADMIN_PK) + inform_changed_data(admin) + + response = self.client.post(reverse("projector-list")) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(Projector.objects.all().count(), 1) + + +class Projection(TestCase): + """ + Tests the projection view. + """ + + def setUp(self): + self.client = APIClient() + self.client.login(username="admin", password="admin") + self.projector = Projector.objects.get(pk=1) # the default projector + + def test_add_element(self): + elements = [{"name": "core/clock"}] + response = self.client.post( + reverse("projector-project", args=[self.projector.pk]), + {"elements": elements}, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.projector = Projector.objects.get(pk=1) + self.assertEqual(self.projector.elements, elements) + self.assertEqual(self.projector.elements_preview, []) + self.assertEqual(self.projector.elements_history, []) + + def test_add_element_without_name(self): + response = self.client.post( + reverse("projector-project", args=[self.projector.pk]), + {"elements": [{}]}, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.projector = Projector.objects.get(pk=1) + self.assertEqual(self.projector.elements, []) + self.assertEqual(self.projector.elements_preview, []) + self.assertEqual(self.projector.elements_history, []) + + def test_no_permissions(self): + admin = User.objects.get(username="admin") + admin.groups.add(GROUP_DELEGATE_PK) + admin.groups.remove(GROUP_ADMIN_PK) + inform_changed_data(admin) + + response = self.client.post( + reverse("projector-project", args=[self.projector.pk]), {}, format="json" + ) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_remove_element(self): + self.projector.elements = [{"name": "core/clock"}] + self.projector.save() + response = self.client.post( + reverse("projector-project", args=[self.projector.pk]), + {"elements": []}, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.projector = Projector.objects.get(pk=1) + self.assertEqual(self.projector.elements, []) + self.assertEqual(self.projector.elements_preview, []) + self.assertEqual(self.projector.elements_history, []) + + def test_add_element_to_history(self): + element = [{"name": "core/clock"}] + response = self.client.post( + reverse("projector-project", args=[self.projector.pk]), + {"append_to_history": element}, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.projector = Projector.objects.get(pk=1) + self.assertEqual(self.projector.elements, []) + self.assertEqual(self.projector.elements_preview, []) + self.assertEqual(self.projector.elements_history, [element]) + + def test_remove_last_history_element(self): + element1 = [{"name": "core/clock"}] + element2 = [{"name": "motions/motion"}] + self.projector.elements_history = [element1, element2] + self.projector.save() + response = self.client.post( + reverse("projector-project", args=[self.projector.pk]), + {"delete_last_history_element": True}, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.projector = Projector.objects.get(pk=1) + self.assertEqual(self.projector.elements, []) + self.assertEqual(self.projector.elements_preview, []) + self.assertEqual(self.projector.elements_history, [element1]) + + def test_set_preview(self): + elements = [{"name": "core/clock"}] + response = self.client.post( + reverse("projector-project", args=[self.projector.pk]), + {"preview": elements}, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.projector = Projector.objects.get(pk=1) + self.assertEqual(self.projector.elements, []) + self.assertEqual(self.projector.elements_preview, elements) + self.assertEqual(self.projector.elements_history, []) + + class ChatMessageViewSet(TestCase): """ Tests requests to deal with chat messages. diff --git a/tests/integration/motions/test_viewset.py b/tests/integration/motions/test_viewset.py index 08ea4ab75..65f6cbe27 100644 --- a/tests/integration/motions/test_viewset.py +++ b/tests/integration/motions/test_viewset.py @@ -23,16 +23,16 @@ from openslides.motions.models import ( from openslides.utils.auth import get_group_model from openslides.utils.autoupdate import inform_changed_data from openslides.utils.test import TestCase +from tests.common_groups import ( + GROUP_ADMIN_PK, + GROUP_DEFAULT_PK, + GROUP_DELEGATE_PK, + GROUP_STAFF_PK, +) from ..helpers import count_queries -GROUP_DEFAULT_PK = 1 -GROUP_ADMIN_PK = 2 -GROUP_DELEGATE_PK = 3 -GROUP_STAFF_PK = 4 - - @pytest.mark.django_db(transaction=False) def test_motion_db_queries(): """