diff --git a/openslides/motion/pdf.py b/openslides/motion/pdf.py index 27d254a4f..8ea7a0279 100644 --- a/openslides/motion/pdf.py +++ b/openslides/motion/pdf.py @@ -11,6 +11,7 @@ """ import random +import os from bs4 import BeautifulSoup from reportlab.lib import colors @@ -18,15 +19,21 @@ from reportlab.lib.units import cm from reportlab.platypus import ( SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle) from django.utils.translation import ugettext as _ +from django.conf import settings from openslides.config.api import config from openslides.utils.pdf import stylesheet from .models import Motion +# Needed to count the delegates +# TODO: find another way to do this. +from openslides.participant.models import User + def motions_to_pdf(pdf): - """Create a PDF with all motions.""" - + """ + Create a PDF with all motions. + """ motions = Motion.objects.all() all_motion_cover(pdf, motions) for motion in motions: @@ -35,8 +42,9 @@ def motions_to_pdf(pdf): def motion_to_pdf(pdf, motion): - """Create a PDF for one motion.""" - + """ + Create a PDF for one motion. + """ pdf.append(Paragraph(_("Motion: %s") % motion.title, stylesheet['Heading1'])) motion_data = [] @@ -231,7 +239,9 @@ def convert_html_to_reportlab(pdf, text): def all_motion_cover(pdf, motions): - """Create a coverpage for all motions.""" + """ + Create a coverpage for all motions. + """ pdf.append(Paragraph(config["motion_pdf_title"], stylesheet['Heading1'])) preamble = config["motion_pdf_preamble"] @@ -245,3 +255,46 @@ def all_motion_cover(pdf, motions): else: for motion in motions: pdf.append(Paragraph(motion.title, stylesheet['Heading3'])) + + +def motion_poll_to_pdf(pdf, poll): + imgpath = os.path.join(settings.SITE_ROOT, 'static/img/circle.png') + circle = "  " % imgpath + cell = [] + cell.append(Spacer(0, 0.8 * cm)) + cell.append(Paragraph(_("Motion No. %s") % poll.motion.identifier, stylesheet['Ballot_title'])) + cell.append(Paragraph(poll.motion.title, stylesheet['Ballot_subtitle'])) + cell.append(Paragraph(_("%d. Vote") % poll.poll_number, stylesheet['Ballot_description'])) + cell.append(Spacer(0, 0.5 * cm)) + cell.append(Paragraph(circle + unicode(_("Yes")), stylesheet['Ballot_option'])) + cell.append(Paragraph(circle + unicode(_("No")), stylesheet['Ballot_option'])) + cell.append(Paragraph(circle + unicode(_("Abstention")), stylesheet['Ballot_option'])) + data = [] + # get ballot papers config values + ballot_papers_selection = config["motion_pdf_ballot_papers_selection"] + ballot_papers_number = config["motion_pdf_ballot_papers_number"] + + # set number of ballot papers + if ballot_papers_selection == "NUMBER_OF_DELEGATES": + # TODO: get this number from persons + number = User.objects.filter(type__iexact="delegate").count() + elif ballot_papers_selection == "NUMBER_OF_ALL_PARTICIPANTS": + # TODO: get the number from the persons + number = int(User.objects.count()) + else: # ballot_papers_selection == "CUSTOM_NUMBER" + number = int(ballot_papers_number) + number = max(1, number) + + # print ballot papers + if number > 0: + # TODO: try [cell, cell] * (number / 2) + for user in xrange(number / 2): + data.append([cell, cell]) + rest = number % 2 + if rest: + data.append([cell, '']) + t = Table(data, 10.5 * cm, 7.42 * cm) + t.setStyle(TableStyle( + [('GRID', (0, 0), (-1, -1), 0.25, colors.grey), + ('VALIGN', (0, 0), (-1, -1), 'TOP')])) + pdf.append(t) diff --git a/openslides/motion/templates/motion/poll_form.html b/openslides/motion/templates/motion/poll_form.html index 70030bf28..db0733a9f 100644 --- a/openslides/motion/templates/motion/poll_form.html +++ b/openslides/motion/templates/motion/poll_form.html @@ -3,7 +3,7 @@ {% load i18n %} {% block title %} - {{ block.super }} - {% trans "Motion" %} {{ motion.identifier }}, {{ ballot }}. {% trans "Vote" %} + {{ block.super }} - {% trans "Motion" %} {{ motion.identifier }}, {{ poll }}. {% trans "Vote" %} {% endblock %} {% block content %} @@ -11,7 +11,7 @@ {{ motion }}
- {% trans "Motion" %} {{ motion.identifier }}, {{ ballot }}. {% trans "Vote" %} + {% trans "Motion" %} {{ motion.identifier }}, {{ poll }}. {% trans "Vote" %}
@@ -57,8 +57,7 @@

-{# TODO: create ballot paper instead of motion_detail_pdf ! #} - + {% trans 'Ballot paper as PDF' %}

diff --git a/openslides/motion/urls.py b/openslides/motion/urls.py index 46a50dc9b..666d824e1 100644 --- a/openslides/motion/urls.py +++ b/openslides/motion/urls.py @@ -90,6 +90,11 @@ urlpatterns = patterns('openslides.motion.views', name='motion_poll_delete', ), + url(r'^(?P\d+)/poll/(?P\d+)/pdf/$', + 'poll_pdf', + name='motion_poll_pdf', + ), + url(r'^(?P\d+)/set_state/(?P\d+)/$', 'set_state', name='motion_set_state', diff --git a/openslides/motion/views.py b/openslides/motion/views.py index ae59b5b75..b459e27f5 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -37,7 +37,7 @@ from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll, from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin, MotionDisableVersioningMixin, MotionCategoryMixin, MotionIdentifierMixin) -from .pdf import motions_to_pdf, motion_to_pdf +from .pdf import motions_to_pdf, motion_to_pdf, motion_poll_to_pdf class MotionListView(ListView): @@ -422,12 +422,16 @@ poll_create = PollCreateView.as_view() class PollMixin(object): - """Mixin for the PollUpdateView and the PollDeleteView.""" + """ + Mixin for the PollUpdateView and the PollDeleteView. + """ + permission_required = 'motion.can_manage_motion' success_url_name = 'motion_detail' def get_object(self): - """Return a MotionPoll object. + """ + Return a MotionPoll object. Use the motion id and the poll_number from the url kwargs to get the object. @@ -437,30 +441,40 @@ class PollMixin(object): poll_number=self.kwargs['poll_number']).get() def get_url_name_args(self): - """Return the arguments to create the url to the success_url""" + """ + Return the arguments to create the url to the success_url. + """ return [self.object.motion.pk] class PollUpdateView(PollMixin, PollFormView): - """View to update a MotionPoll.""" + """ + View to update a MotionPoll. + """ poll_class = MotionPoll - """Poll Class to use for this view.""" + """ + Poll Class to use for this view. + """ template_name = 'motion/poll_form.html' def get_context_data(self, **kwargs): - """Return the template context. + """ + Return the template context. Append the motion object to the context. """ context = super(PollUpdateView, self).get_context_data(**kwargs) context.update({ - 'motion': self.poll.motion}) + 'motion': self.poll.motion, + 'poll': self.poll}) return context def form_valid(self, form): - """Write a log message, if the form is valid.""" + """ + Write a log message, if the form is valid. + """ value = super(PollUpdateView, self).form_valid(form) self.object.write_log(ugettext_noop('Poll updated'), self.request.user) return value @@ -469,21 +483,54 @@ poll_edit = PollUpdateView.as_view() class PollDeleteView(PollMixin, DeleteView): - """View to delete a MotionPoll.""" + """ + View to delete a MotionPoll. + """ + model = MotionPoll def case_yes(self): - """Write a log message, if the form is valid.""" + """ + Write a log message, if the form is valid. + """ super(PollDeleteView, self).case_yes() self.object.write_log(ugettext_noop('Poll deleted'), self.request.user) def get_redirect_url(self, **kwargs): - """Return the URL to the DetailView of the motion.""" + """ + Return the URL to the DetailView of the motion. + """ return reverse('motion_detail', args=[self.object.motion.pk]) poll_delete = PollDeleteView.as_view() +class PollPDFView(PollMixin, PDFView): + """ + Generates a ballotpaper. + """ + + permission_required = 'motion.can_manage_motion' + + def get(self, *args, **kwargs): + self.object = self.get_object() + return super(PollPDFView, self).get(*args, **kwargs) + + def get_filename(self): + """ + Return the filename for the PDF. + """ + return u'%s%s_%s' % (_("Motion"), str(self.object.poll_number), _("Poll")) + + def append_to_pdf(self, pdf): + """ + Append PDF objects. + """ + motion_poll_to_pdf(pdf, self.object) + +poll_pdf = PollPDFView.as_view() + + class MotionSetStateView(SingleObjectMixin, RedirectView): """View to set the state of a motion.