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
|
||||
|
||||
|
||||
# TODO: remove mptt after removing the django views and forms
|
||||
class Item(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
||||
"""
|
||||
An Agenda Item
|
||||
|
@ -61,10 +61,4 @@ class ItemSerializer(ModelSerializer):
|
||||
'speaker_set',
|
||||
'speaker_list_closed',
|
||||
'content_object',
|
||||
'weight',
|
||||
'lft',
|
||||
'rght',
|
||||
'tree_id',
|
||||
'level',
|
||||
'parent',
|
||||
'tags',)
|
||||
|
@ -1,6 +1,7 @@
|
||||
# TODO: Rename all views and template names
|
||||
|
||||
from cgi import escape
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from json import dumps
|
||||
|
||||
@ -25,7 +26,7 @@ from openslides.projector.api import (
|
||||
get_overlays)
|
||||
from openslides.utils.exceptions import OpenSlidesError
|
||||
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.views import (
|
||||
AjaxMixin,
|
||||
@ -811,3 +812,74 @@ class ItemViewSet(ModelViewSet):
|
||||
if not self.request.user.has_perm('agenda.can_see_orga_items'):
|
||||
queryset = queryset.exclude(type__exact=Item.ORGANIZATIONAL_ITEM)
|
||||
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.routers import DefaultRouter
|
||||
from rest_framework.viewsets import ModelViewSet, ViewSet # noqa
|
||||
from rest_framework.decorators import list_route # noqa
|
||||
|
||||
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