Merge pull request #1618 from normanjaeckel/Projector
Added UUID to projector elements. Added update view.
This commit is contained in:
commit
973d3fa653
@ -14,7 +14,7 @@ def add_default_projector_2(apps, schema_editor):
|
||||
'name': 'core/countdown',
|
||||
'stable': True,
|
||||
'status': 'stop',
|
||||
'countdown_time': 60
|
||||
'countdown_time': 60,
|
||||
})
|
||||
projector.config = config
|
||||
projector.save()
|
||||
|
39
openslides/core/migrations/0003_uuid.py
Normal file
39
openslides/core/migrations/0003_uuid.py
Normal file
@ -0,0 +1,39 @@
|
||||
import uuid
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def add_default_projector_3(apps, schema_editor):
|
||||
"""
|
||||
Adds UUIDs to projector config.
|
||||
"""
|
||||
# We get the model from the versioned app registry;
|
||||
# if we directly import it, it will be the wrong version.
|
||||
Projector = apps.get_model('core', 'Projector')
|
||||
projector = Projector.objects.get()
|
||||
|
||||
def add_uuid(self):
|
||||
"""
|
||||
Adds an UUID to every element.
|
||||
"""
|
||||
for element in self.config:
|
||||
if element.get('uuid') is None:
|
||||
element['uuid'] = uuid.uuid4().hex
|
||||
|
||||
add_uuid(projector)
|
||||
projector.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0002_countdown'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
code=add_default_projector_3,
|
||||
reverse_code=None,
|
||||
atomic=True,
|
||||
),
|
||||
]
|
@ -1,3 +1,5 @@
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||
@ -40,6 +42,22 @@ class Projector(RESTModelMixin, models.Model):
|
||||
('can_see_dashboard', ugettext_noop('Can see the dashboard')),
|
||||
('can_use_chat', ugettext_noop('Can use the chat')))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Saves the projector. Ensures that every projector element in config
|
||||
has an UUID.
|
||||
"""
|
||||
self.add_uuid()
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def add_uuid(self):
|
||||
"""
|
||||
Adds an UUID to every element.
|
||||
"""
|
||||
for element in self.config:
|
||||
if element.get('uuid') is None:
|
||||
element['uuid'] = uuid.uuid4().hex
|
||||
|
||||
@property
|
||||
def elements(self):
|
||||
"""
|
||||
|
@ -1,4 +1,5 @@
|
||||
import re
|
||||
import uuid
|
||||
from collections import OrderedDict
|
||||
from operator import attrgetter
|
||||
|
||||
@ -194,30 +195,66 @@ class ProjectorViewSet(ReadOnlyModelViewSet):
|
||||
serializer.save()
|
||||
return Response(serializer.data)
|
||||
|
||||
@detail_route(methods=['post'])
|
||||
def update_elements(self, request, pk):
|
||||
"""
|
||||
REST API operation to update projector elements. It expects a POST
|
||||
request to /rest/core/projector/<pk>/deactivate_elements/ with a
|
||||
list of dictonaries. Every dictonary contains the hex UUID (key
|
||||
'uuid') and the new element data (key 'data').
|
||||
"""
|
||||
# Check the data. It must be a list of dictionaries. Get config
|
||||
# entry from projector model. Change the entries that should be
|
||||
# changed and try to serialize. This raises ValidationError if the
|
||||
# data is invalid.
|
||||
if not isinstance(request.data, list):
|
||||
raise ValidationError({'config': ['Data must be a list of dictionaries.']})
|
||||
error = {'config': ['Data must be a list of dictionaries with special keys and values. See docstring.']}
|
||||
for item in request.data:
|
||||
try:
|
||||
uuid.UUID(hex=str(item.get('uuid')))
|
||||
except ValueError:
|
||||
raise ValidationError(error)
|
||||
if not isinstance(item['data'], dict):
|
||||
raise ValidationError(error)
|
||||
|
||||
projector_instance = self.get_object()
|
||||
projector_config = projector_instance.config
|
||||
for entry_to_be_changed in request.data:
|
||||
for index, element in enumerate(projector_config):
|
||||
if element['uuid'] == entry_to_be_changed['uuid']:
|
||||
projector_config[index] = entry_to_be_changed['data']
|
||||
serializer = self.get_serializer(projector_instance, data={'config': projector_config}, partial=False)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return Response(serializer.data)
|
||||
|
||||
@detail_route(methods=['post'])
|
||||
def deactivate_elements(self, request, pk):
|
||||
"""
|
||||
REST API operation to deactivate projector elements. It expects a
|
||||
POST request to /rest/core/projector/<pk>/deactivate_elements/ with
|
||||
a list of dictionaries. These are exactly the projector_elements in
|
||||
the config that should be deleted.
|
||||
a list of hex UUIDs. These are the projector_elements in the config
|
||||
that should be deleted.
|
||||
"""
|
||||
# Check the data. It must be a list of dictionaries. Get config
|
||||
# Check the data. It must be a list of hex UUIDs. Get config
|
||||
# entry from projector model. Pop out the entries that should be
|
||||
# deleted and try to serialize. This raises ValidationErrors if the
|
||||
# deleted and try to serialize. This raises ValidationError if the
|
||||
# data is invalid.
|
||||
if not isinstance(request.data, list) or list(filter(lambda item: not isinstance(item, dict), request.data)):
|
||||
raise ValidationError({'config': ['Data must be a list of dictionaries.']})
|
||||
if not isinstance(request.data, list):
|
||||
raise ValidationError({'config': ['Data must be a list of hex UUIDs.']})
|
||||
for item in request.data:
|
||||
try:
|
||||
uuid.UUID(hex=str(item))
|
||||
except ValueError:
|
||||
raise ValidationError({'config': ['Data must be a list of hex UUIDs.']})
|
||||
|
||||
projector_instance = self.get_object()
|
||||
projector_config = projector_instance.config
|
||||
for entry_to_be_deleted in request.data:
|
||||
try:
|
||||
projector_config.remove(entry_to_be_deleted)
|
||||
except ValueError:
|
||||
# The entry that should be deleted is not on the projector.
|
||||
pass
|
||||
serializer = self.get_serializer(projector_instance, data={'config': projector_config}, partial=False)
|
||||
elements = []
|
||||
for element in projector_instance.config:
|
||||
if not element['uuid'] in request.data:
|
||||
elements.append(element)
|
||||
serializer = self.get_serializer(projector_instance, data={'config': elements}, partial=False)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return Response(serializer.data)
|
||||
|
@ -23,13 +23,14 @@ class ProjectorAPI(TestCase):
|
||||
default_projector = Projector.objects.get(pk=1)
|
||||
default_projector.config = [{'name': 'core/customslide', 'id': customslide.id}]
|
||||
default_projector.save()
|
||||
element_uuid = Projector.objects.get(pk=1).config[0]['uuid']
|
||||
|
||||
response = self.client.get(reverse('projector-detail', args=['1']))
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(json.loads(response.content.decode()), {
|
||||
'id': 1,
|
||||
'config': [{'name': 'core/customslide', 'id': customslide.id}],
|
||||
'config': [{'name': 'core/customslide', 'id': customslide.id, 'uuid': element_uuid}],
|
||||
'elements': [
|
||||
{'name': 'core/customslide',
|
||||
'context': {'id': customslide.id}}]})
|
||||
@ -39,13 +40,14 @@ class ProjectorAPI(TestCase):
|
||||
default_projector = Projector.objects.get(pk=1)
|
||||
default_projector.config = [{'name': 'invalid_slide'}]
|
||||
default_projector.save()
|
||||
element_uuid = Projector.objects.get(pk=1).config[0]['uuid']
|
||||
|
||||
response = self.client.get(reverse('projector-detail', args=['1']))
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(json.loads(response.content.decode()), {
|
||||
'id': 1,
|
||||
'config': [{'name': 'invalid_slide'}],
|
||||
'config': [{'name': 'invalid_slide', 'uuid': element_uuid}],
|
||||
'elements': [
|
||||
{'name': 'invalid_slide',
|
||||
'error': 'Projector element does not exist.'}]})
|
||||
|
@ -83,14 +83,43 @@ class ProjectorAPI(TestCase):
|
||||
self.viewset.prune_elements(request=request, pk=MagicMock())
|
||||
self.assertEqual(len(mock_object.return_value.config), 2)
|
||||
|
||||
def test_update_elements(self, mock_object):
|
||||
mock_object.return_value.config = [{
|
||||
'name': 'test_projector_element_jbmgfnf657djcnsjdfkm',
|
||||
'test_key_7mibir1Uoee7uhilohB1': 'test_value_mbhfn5zwhakbigjrns88',
|
||||
'uuid': 'aacbb64acafc4ccc957240c871d4e77d'}]
|
||||
request = MagicMock()
|
||||
request.data = [{
|
||||
'uuid': 'aacbb64acafc4ccc957240c871d4e77d',
|
||||
'data': {
|
||||
'name': 'test_projector_element_wdsexrvhgn67ezfjnfje'}}]
|
||||
self.viewset.request = request
|
||||
self.viewset.update_elements(request=request, pk=MagicMock())
|
||||
self.assertEqual(len(mock_object.return_value.config), 1)
|
||||
self.assertEqual(mock_object.return_value.config[0]['name'], 'test_projector_element_wdsexrvhgn67ezfjnfje')
|
||||
|
||||
def test_update_elements_wrong_element(self, mock_object):
|
||||
mock_object.return_value.config = [{
|
||||
'name': 'test_projector_element_njb657djcsjdmgfnffkm',
|
||||
'test_key_uhilo7mir1Uoee7ibhB1': 'test_value_hjrnsmbhfn5zwakbig88',
|
||||
'uuid': '5b5e5d3b35de4fff873925296c3093fc'}]
|
||||
request = MagicMock()
|
||||
request.data = [{
|
||||
'uuid': '255fda68ca6f4f3f803b98405abfb710',
|
||||
'data': {
|
||||
'name': 'test_projector_element_wxrvhn67eebmfjjnkvds'}}]
|
||||
self.viewset.request = request
|
||||
self.viewset.update_elements(request=request, pk=MagicMock())
|
||||
self.assertEqual(len(mock_object.return_value.config), 1)
|
||||
self.assertNotEqual(mock_object.return_value.config[0]['name'], 'test_projector_element_wxrvhn67eebmfjjnkvds')
|
||||
|
||||
def test_deactivate_elements(self, mock_object):
|
||||
mock_object.return_value.config = [{
|
||||
'name': 'test_projector_element_c6oohooxugiphuuM6Wee',
|
||||
'test_key_eehiloh7mibi7ur1UoB1': 'test_value_o8eig1AeSajieTh6aiwo'}]
|
||||
'test_key_eehiloh7mibi7ur1UoB1': 'test_value_o8eig1AeSajieTh6aiwo',
|
||||
'uuid': '874aaf279be346ff85a9b456ce1d1128'}]
|
||||
request = MagicMock()
|
||||
request.data = [{
|
||||
'name': 'test_projector_element_c6oohooxugiphuuM6Wee',
|
||||
'test_key_eehiloh7mibi7ur1UoB1': 'test_value_o8eig1AeSajieTh6aiwo'}]
|
||||
request.data = ['874aaf279be346ff85a9b456ce1d1128']
|
||||
self.viewset.request = request
|
||||
self.viewset.deactivate_elements(request=request, pk=MagicMock())
|
||||
self.assertEqual(len(mock_object.return_value.config), 0)
|
||||
@ -98,9 +127,10 @@ class ProjectorAPI(TestCase):
|
||||
def test_deactivate_elements_wrong_element(self, mock_object):
|
||||
mock_object.return_value.config = [{
|
||||
'name': 'test_projector_element_c6oohooxugiphuuM6Wee',
|
||||
'test_key_eehiloh7mibi7ur1UoB1': 'test_value_o8eig1AeSajieTh6aiwo'}]
|
||||
'test_key_eehiloh7mibi7ur1UoB1': 'test_value_o8eig1AeSajieTh6aiwo',
|
||||
'uuid': 'd867b2557ad041b8848e95981c5671b7'}]
|
||||
request = MagicMock()
|
||||
request.data = [{'name': 'wrong name'}]
|
||||
request.data = ['1179ea09ba2b4559a41272efb1346c86'] # Wrong UUID.
|
||||
self.viewset.request = request
|
||||
self.viewset.deactivate_elements(request=request, pk=MagicMock())
|
||||
self.assertEqual(len(mock_object.return_value.config), 1)
|
||||
@ -108,7 +138,8 @@ class ProjectorAPI(TestCase):
|
||||
def test_deactivate_elements_no_list(self, mock_object):
|
||||
mock_object.return_value.config = [{
|
||||
'name': 'test_projector_element_Au1ce9nevaeX7zo4ye2w',
|
||||
'test_key_we9biiZ7bah4Sha2haS5': 'test_value_eehoipheik6aiNgeegor'}]
|
||||
'test_key_we9biiZ7bah4Sha2haS5': 'test_value_eehoipheik6aiNgeegor',
|
||||
'uuid': '0f3b8f8df38b4bbc90f4beba9393d2db'}]
|
||||
request = MagicMock()
|
||||
request.data = 'bad_value_no_list_ohchohWee1fie0SieTha'
|
||||
self.viewset.request = request
|
||||
@ -118,7 +149,8 @@ class ProjectorAPI(TestCase):
|
||||
def test_deactivate_elements_bad_list(self, mock_object):
|
||||
mock_object.return_value.config = [{
|
||||
'name': 'test_projector_element_teibaeRaim1heiCh6Ohv',
|
||||
'test_key_uk7wai7eiZieQu0ief3': 'test_value_eeghisei3ieGh3ieb6ae'}]
|
||||
'test_key_uk7wai7eiZieQu0ief3': 'test_value_eeghisei3ieGh3ieb6ae',
|
||||
'uuid': '8ae42a09f585480e8b4a53194d4d1fba'}]
|
||||
request = MagicMock()
|
||||
# Value 1 is not an dictionary so we expect ValidationError.
|
||||
request.data = [1]
|
||||
|
Loading…
Reference in New Issue
Block a user