Send tree for agenda items

This commit is contained in:
Oskar Hahn 2015-03-29 14:42:27 +02:00
parent 1bc6aef205
commit 505e587d6f
6 changed files with 145 additions and 7 deletions

View File

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

View File

@ -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',)

View File

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

View File

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

View File

View 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)