Send tree for agenda items
This commit is contained in:
parent
1bc6aef205
commit
505e587d6f
@ -22,6 +22,7 @@ from openslides.utils.utils import to_roman
|
|||||||
from openslides.users.models import User
|
from openslides.users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: remove mptt after removing the django views and forms
|
||||||
class Item(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
class Item(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
||||||
"""
|
"""
|
||||||
An Agenda Item
|
An Agenda Item
|
||||||
|
@ -61,10 +61,4 @@ class ItemSerializer(ModelSerializer):
|
|||||||
'speaker_set',
|
'speaker_set',
|
||||||
'speaker_list_closed',
|
'speaker_list_closed',
|
||||||
'content_object',
|
'content_object',
|
||||||
'weight',
|
|
||||||
'lft',
|
|
||||||
'rght',
|
|
||||||
'tree_id',
|
|
||||||
'level',
|
|
||||||
'parent',
|
|
||||||
'tags',)
|
'tags',)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# TODO: Rename all views and template names
|
# TODO: Rename all views and template names
|
||||||
|
|
||||||
from cgi import escape
|
from cgi import escape
|
||||||
|
from collections import defaultdict
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ from openslides.projector.api import (
|
|||||||
get_overlays)
|
get_overlays)
|
||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.pdf import stylesheet
|
from openslides.utils.pdf import stylesheet
|
||||||
from openslides.utils.rest_api import ModelViewSet
|
from openslides.utils.rest_api import ModelViewSet, list_route, Response
|
||||||
from openslides.utils.utils import html_strong
|
from openslides.utils.utils import html_strong
|
||||||
from openslides.utils.views import (
|
from openslides.utils.views import (
|
||||||
AjaxMixin,
|
AjaxMixin,
|
||||||
@ -811,3 +812,74 @@ class ItemViewSet(ModelViewSet):
|
|||||||
if not self.request.user.has_perm('agenda.can_see_orga_items'):
|
if not self.request.user.has_perm('agenda.can_see_orga_items'):
|
||||||
queryset = queryset.exclude(type__exact=Item.ORGANIZATIONAL_ITEM)
|
queryset = queryset.exclude(type__exact=Item.ORGANIZATIONAL_ITEM)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
@list_route(methods=['get', 'put'])
|
||||||
|
def tree(self, request):
|
||||||
|
"""
|
||||||
|
Returns or sets the agenda tree.
|
||||||
|
"""
|
||||||
|
if request.method == 'PUT':
|
||||||
|
if not (request.user.has_perm('agenda.can_manage') and
|
||||||
|
request.user.has_perm('agenda.can_see_orga_items')):
|
||||||
|
self.permission_denied(request)
|
||||||
|
return self.set_tree(request.data['tree'])
|
||||||
|
return self.get_tree()
|
||||||
|
|
||||||
|
def get_tree(self):
|
||||||
|
"""
|
||||||
|
Returns the agenda tree.
|
||||||
|
"""
|
||||||
|
item_list = Item.objects.order_by('weight')
|
||||||
|
|
||||||
|
# Index the items to get the children for each item
|
||||||
|
item_children = defaultdict(list)
|
||||||
|
for item in item_list:
|
||||||
|
if item.parent:
|
||||||
|
item_children[item.parent_id].append(item)
|
||||||
|
|
||||||
|
def get_children(item):
|
||||||
|
"""
|
||||||
|
Returns a list with all the children for item.
|
||||||
|
|
||||||
|
Returns an empty list if item has no children.
|
||||||
|
"""
|
||||||
|
return [dict(id=child.pk, children=get_children(child))
|
||||||
|
for child in item_children[item.pk]]
|
||||||
|
|
||||||
|
return Response(dict(id=item.pk, children=get_children(item))
|
||||||
|
for item in item_list if not item.parent)
|
||||||
|
|
||||||
|
def set_tree(self, tree):
|
||||||
|
"""
|
||||||
|
Sets the agenda tree.
|
||||||
|
|
||||||
|
The tree has to be a nested object. For example:
|
||||||
|
[{"id": 1}, {"id": 2, "children": [{"id": 3}]}]
|
||||||
|
"""
|
||||||
|
|
||||||
|
def walk_items(tree, parent=None):
|
||||||
|
"""
|
||||||
|
Generator that returns each item in the tree as tuple.
|
||||||
|
|
||||||
|
This tuples have tree values. The item id, the item parent and the
|
||||||
|
weight of the item.
|
||||||
|
"""
|
||||||
|
for weight, element in enumerate(tree):
|
||||||
|
yield (element['id'], parent, weight)
|
||||||
|
yield from walk_items(element.get('children', []), element['id'])
|
||||||
|
|
||||||
|
touched_items = set()
|
||||||
|
for item_pk, parent_pk, weight in walk_items(tree):
|
||||||
|
# Check that the item is only once in the tree to prevent invalid trees
|
||||||
|
if item_pk in touched_items:
|
||||||
|
detail = "Item %d is more then once in the tree" % item_pk
|
||||||
|
break
|
||||||
|
touched_items.add(item_pk)
|
||||||
|
|
||||||
|
Item.objects.filter(pk=item_pk).update(
|
||||||
|
parent_id=parent_pk,
|
||||||
|
weight=weight)
|
||||||
|
else:
|
||||||
|
# Everithing is fine. Return a response with status_code 200 an no content
|
||||||
|
return Response()
|
||||||
|
return Response({'detail': detail}, status=400)
|
||||||
|
@ -14,6 +14,7 @@ from rest_framework.serializers import ( # noqa
|
|||||||
from rest_framework.response import Response # noqa
|
from rest_framework.response import Response # noqa
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from rest_framework.viewsets import ModelViewSet, ViewSet # noqa
|
from rest_framework.viewsets import ModelViewSet, ViewSet # noqa
|
||||||
|
from rest_framework.decorators import list_route # noqa
|
||||||
|
|
||||||
from .exceptions import OpenSlidesError
|
from .exceptions import OpenSlidesError
|
||||||
|
|
||||||
|
0
tests/integration/agenda/__init__.py
Normal file
0
tests/integration/agenda/__init__.py
Normal file
70
tests/integration/agenda/test_views.py
Normal file
70
tests/integration/agenda/test_views.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import json
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
|
from openslides.utils.test import TestCase
|
||||||
|
from openslides.agenda.models import Item
|
||||||
|
|
||||||
|
|
||||||
|
class AgendaTreeTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
Item.objects.create(title='item1')
|
||||||
|
item2 = Item.objects.create(title='item2')
|
||||||
|
Item.objects.create(title='item2a', parent=item2)
|
||||||
|
self.client = APIClient()
|
||||||
|
self.client.login(username='admin', password='admin')
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
response = self.client.get('/rest/agenda/item/tree/')
|
||||||
|
|
||||||
|
self.assertEqual(json.loads(response.content.decode()),
|
||||||
|
[{'children': [], 'id': 1},
|
||||||
|
{'children': [{'children': [], 'id': 3}], 'id': 2}])
|
||||||
|
|
||||||
|
def test_set(self):
|
||||||
|
tree = [{'id': 3},
|
||||||
|
{'children': [{'id': 1}], 'id': 2}]
|
||||||
|
|
||||||
|
response = self.client.put('/rest/agenda/item/tree/', {'tree': tree}, format='json')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
item1 = Item.objects.get(pk=1)
|
||||||
|
item2 = Item.objects.get(pk=2)
|
||||||
|
item3 = Item.objects.get(pk=3)
|
||||||
|
self.assertEqual(item1.parent_id, 2)
|
||||||
|
self.assertEqual(item1.weight, 0)
|
||||||
|
self.assertEqual(item2.parent_id, None)
|
||||||
|
self.assertEqual(item2.weight, 1)
|
||||||
|
self.assertEqual(item3.parent_id, None)
|
||||||
|
self.assertEqual(item3.weight, 0)
|
||||||
|
|
||||||
|
def test_set_without_perm(self):
|
||||||
|
self.client = APIClient()
|
||||||
|
|
||||||
|
response = self.client.put('/rest/agenda/item/tree/', {'tree': []}, format='json')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
def test_tree_with_double_item(self):
|
||||||
|
"""
|
||||||
|
Test to send a tree that has an item-pk more then once in it.
|
||||||
|
|
||||||
|
It is expected, that the responsecode 400 is returned with a specific
|
||||||
|
content
|
||||||
|
"""
|
||||||
|
tree = [{'id': 1}, {'id': 1}]
|
||||||
|
|
||||||
|
response = self.client.put('/rest/agenda/item/tree/', {'tree': tree}, format='json')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
self.assertEqual(response.data, {'detail': "Item 1 is more then once in the tree"})
|
||||||
|
|
||||||
|
def test_tree_with_empty_children(self):
|
||||||
|
"""
|
||||||
|
Test that the chrildren element is not required in the tree
|
||||||
|
"""
|
||||||
|
tree = [{'id': 1}]
|
||||||
|
|
||||||
|
response = self.client.put('/rest/agenda/item/tree/', {'tree': tree}, format='json')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
Loading…
Reference in New Issue
Block a user