Protect element fields from the projector model. Fixes creation of

projectors.
This commit is contained in:
FinnStutzenstein 2019-04-09 16:13:40 +02:00
parent fc5f6f4e54
commit 5a1f638f8d
7 changed files with 189 additions and 26 deletions

View File

@ -148,11 +148,7 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
public create(): void { public create(): void {
if (this.createForm.valid && this.projectorToCreate) { if (this.createForm.valid && this.projectorToCreate) {
this.projectorToCreate.patchValues(this.createForm.value as Projector); this.projectorToCreate.patchValues(this.createForm.value as Projector);
// TODO: the server shouldn't want to have element data..
this.projectorToCreate.patchValues({ this.projectorToCreate.patchValues({
elements: [{ name: 'core/clock', stable: true }],
elements_preview: [],
elements_history: [],
reference_projector_id: this.projectors[0].reference_projector_id reference_projector_id: this.projectors[0].reference_projector_id
}); });
this.repo.create(this.projectorToCreate).then(() => (this.projectorToCreate = null), this.raiseError); this.repo.create(this.projectorToCreate).then(() => (this.projectorToCreate = null), this.raiseError);

View File

@ -91,7 +91,7 @@ class Projector(RESTModelMixin, models.Model):
show_title = models.BooleanField(default=True) show_title = models.BooleanField(default=True)
show_logo = 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( reference_projector = models.ForeignKey(
"self", "self",

View File

@ -1,7 +1,13 @@
from typing import Any from typing import Any
from ..utils.projector import projector_slides 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 ..utils.validate import validate_html
from .models import ( from .models import (
ChatMessage, ChatMessage,
@ -77,13 +83,17 @@ class ProjectorSerializer(ModelSerializer):
Serializer for core.models.Projector objects. Serializer for core.models.Projector objects.
""" """
elements = JSONSerializerField(validators=[elements_validator]) elements = JSONSerializerField(read_only=True)
elements_preview = JSONSerializerField(validators=[elements_validator]) elements_preview = JSONSerializerField(read_only=True)
elements_history = JSONSerializerField(validators=[elements_array_validator]) elements_history = JSONSerializerField(read_only=True)
width = IntegerField(min_value=800, max_value=3840, required=False) width = IntegerField(min_value=800, max_value=3840, required=False)
height = IntegerField(min_value=340, max_value=2880, required=False) height = IntegerField(min_value=340, max_value=2880, required=False)
projectiondefaults = IdPrimaryKeyRelatedField(
many=True, required=False, queryset=ProjectionDefault.objects.all()
)
class Meta: class Meta:
model = Projector model = Projector
fields = ( fields = (

View File

@ -57,6 +57,7 @@ from .models import (
ProjectorMessage, ProjectorMessage,
Tag, Tag,
) )
from .serializers import elements_array_validator, elements_validator
# Special Django views # Special Django views
@ -139,6 +140,11 @@ class ProjectorViewSet(ModelViewSet):
result = False result = False
return result return result
def perform_create(self, serializer):
projector = serializer.save()
projector.elements = [{"name": "core/clock", "stable": True}]
projector.save()
def destroy(self, *args, **kwargs): def destroy(self, *args, **kwargs):
""" """
REST API operation for DELETE requests. REST API operation for DELETE requests.
@ -184,22 +190,24 @@ class ProjectorViewSet(ModelViewSet):
"delete_last_history_element", False "delete_last_history_element", False
) )
changed_data = {}
if elements is not None: if elements is not None:
changed_data["elements"] = elements elements_validator(elements)
projector.elements = elements
if preview is not None: 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: if history_element is not None and delete_last_history_element is False:
history = projector.elements_history + [history_element] elements_history = projector.elements_history + [history_element]
changed_data["elements_history"] = history
if history_element is None and delete_last_history_element is True: if history_element is None and delete_last_history_element is True:
history = projector.elements_history[:-1] elements_history = projector.elements_history[:-1]
changed_data["elements_history"] = history if elements_history is not None:
elements_array_validator(elements_history)
serializer = self.get_serializer(projector, data=changed_data, partial=True) projector.elements_history = elements_history
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
projector.save()
return Response() return Response()
@detail_route(methods=["post"]) @detail_route(methods=["post"])

6
tests/common_groups.py Normal file
View File

@ -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

View File

@ -1,11 +1,14 @@
import pytest import pytest
from django.urls import reverse from django.urls import reverse
from rest_framework import status from rest_framework import status
from rest_framework.test import APIClient
from openslides.core.config import config from openslides.core.config import config
from openslides.core.models import ChatMessage, Projector, Tag from openslides.core.models import ChatMessage, Projector, Tag
from openslides.users.models import User from openslides.users.models import User
from openslides.utils.autoupdate import inform_changed_data
from openslides.utils.test import TestCase from openslides.utils.test import TestCase
from tests.common_groups import GROUP_ADMIN_PK, GROUP_DELEGATE_PK
from ..helpers import count_queries from ..helpers import count_queries
@ -59,6 +62,146 @@ def test_config_db_queries():
assert count_queries(Tag.get_elements) == 1 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): class ChatMessageViewSet(TestCase):
""" """
Tests requests to deal with chat messages. Tests requests to deal with chat messages.

View File

@ -23,16 +23,16 @@ from openslides.motions.models import (
from openslides.utils.auth import get_group_model from openslides.utils.auth import get_group_model
from openslides.utils.autoupdate import inform_changed_data from openslides.utils.autoupdate import inform_changed_data
from openslides.utils.test import TestCase 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 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) @pytest.mark.django_db(transaction=False)
def test_motion_db_queries(): def test_motion_db_queries():
""" """