Merge pull request #1572 from ostcar/fix_agenda_pdf

Moved agenda tree code to the Item-Manager and used it at the AgendaPDFView.
This commit is contained in:
Norman Jäckel 2015-06-28 19:22:01 +02:00
commit 34befe8f7b
3 changed files with 109 additions and 65 deletions

View File

@ -1,3 +1,4 @@
from collections import defaultdict
from datetime import datetime from datetime import datetime
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
@ -17,10 +18,75 @@ from openslides.utils.rest_api import RESTModelMixin
from openslides.utils.utils import to_roman from openslides.utils.utils import to_roman
class ItemManager(models.Manager):
def get_tree(self, only_agenda_items=False, include_content=False):
"""
Generator that yields dictonaries. Each dictonary has two keys, id
and children, where id is the id of one agenda item and children is a
generator that yields dictonaries like the one discribed.
If only_agenda_items is True, the tree hides ORGANIZATIONAL_ITEMs.
If include_content is True, the yielded dictonaries have no key 'id'
but a key 'item' with the entire object.
"""
item_queryset = self.order_by('weight')
if only_agenda_items:
item_queryset = item_queryset.filter(type__exact=Item.AGENDA_ITEM)
# Index the items to get the children for each item
item_children = defaultdict(list)
for item in item_queryset:
if item.parent:
item_children[item.parent_id].append(item)
def get_children(items):
"""
Generator that yields the descibed diconaries.
"""
for item in items:
if include_content:
yield dict(item=item, children=get_children(item_children[item.pk]))
else:
yield dict(id=item.pk, children=get_children(item_children[item.pk]))
yield from get_children(filter(lambda item: item.parent is None, item_queryset))
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:
raise ValueError("Item %d is more then once in the tree" % item_pk)
touched_items.add(item_pk)
Item.objects.filter(pk=item_pk).update(
parent_id=parent_pk,
weight=weight)
class Item(RESTModelMixin, SlideMixin, models.Model): class Item(RESTModelMixin, SlideMixin, models.Model):
""" """
An Agenda Item An Agenda Item
""" """
objects = ItemManager()
slide_callback_name = 'agenda' slide_callback_name = 'agenda'
AGENDA_ITEM = 1 AGENDA_ITEM = 1

View File

@ -1,5 +1,4 @@
from cgi import escape from cgi import escape
from collections import defaultdict
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -30,10 +29,20 @@ class AgendaPDF(PDFView):
document_title = ugettext_lazy('Agenda') document_title = ugettext_lazy('Agenda')
def append_to_pdf(self, story): def append_to_pdf(self, story):
for item in Item.objects.filter(type__exact=Item.AGENDA_ITEM): tree = Item.objects.get_tree(only_agenda_items=True, include_content=True)
ancestors = item.get_ancestors()
def walk_tree(tree, ancestors=0):
"""
Generator that yields a two-element-tuple. The first element is an
agenda-item and the second a number for steps to the root element.
"""
for element in tree:
yield element['item'], ancestors
yield from walk_tree(element['children'], ancestors + 1)
for item, ancestors in walk_tree(tree):
if ancestors: if ancestors:
space = " " * 6 * ancestors.count() space = " " * 6 * ancestors
story.append(Paragraph( story.append(Paragraph(
"%s%s" % (space, escape(item.get_title())), "%s%s" % (space, escape(item.get_title())),
stylesheet['Subitem'])) stylesheet['Subitem']))
@ -218,64 +227,10 @@ class ItemViewSet(ModelViewSet):
if not (request.user.has_perm('agenda.can_manage') and if not (request.user.has_perm('agenda.can_manage') and
request.user.has_perm('agenda.can_see_orga_items')): request.user.has_perm('agenda.can_see_orga_items')):
self.permission_denied(request) self.permission_denied(request)
return self.set_tree(request.data['tree']) try:
return self.get_tree() Item.objects.set_tree(request.data['tree'])
except ValueError as error:
def get_tree(self): return Response({'detail': str(error)}, status=400)
"""
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: else:
# Everithing is fine. Return a response with status_code 200 an no content return Response({'detail': 'Agenda tree successfully updated.'})
return Response() return Response(Item.objects.get_tree())
return Response({'detail': detail}, status=400)

View File

@ -69,3 +69,26 @@ class AgendaTreeTest(TestCase):
response = self.client.put('/rest/agenda/item/tree/', {'tree': tree}, format='json') response = self.client.put('/rest/agenda/item/tree/', {'tree': tree}, format='json')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_tree_with_unknown_item(self):
"""
Tests that unknown items are ignored.
"""
tree = [{'id': 500}]
response = self.client.put('/rest/agenda/item/tree/', {'tree': tree}, format='json')
self.assertEqual(response.status_code, 200)
class TestAgendaPDF(TestCase):
def test_get(self):
"""
Tests that a requst on the pdf-page returns with statuscode 200.
"""
Item.objects.create(title='item1')
self.client.login(username='admin', password='admin')
response = self.client.get('/agenda/print/')
self.assertEqual(response.status_code, 200)