Removed old code needed be the server to serve pdf

This commit is contained in:
Oskar Hahn 2016-10-01 14:26:28 +02:00 committed by Emanuel Schütze
parent 2824a6b3d2
commit 7281aa57b8
19 changed files with 5 additions and 1546 deletions

View File

@ -28,13 +28,7 @@ a. Check requirements
''''''''''''''''''''' '''''''''''''''''''''
Make sure that you have installed `Python (>= 3.4) Make sure that you have installed `Python (>= 3.4)
<https://www.python.org/>`_ on your system. You also need the Python <https://www.python.org/>`_ on your system.
development headers, the Independent JPEG Group's JPEG runtime library
(dependency package) and the compression library (development).
\E. g. for Ubuntu run::
$ sudo apt-get install python3-dev libjpeg-dev zlib1g-dev
b. Setup a virtual Python environment (optional) b. Setup a virtual Python environment (optional)
@ -156,28 +150,18 @@ OpenSlides uses the following projects or parts of them:
* `backports-abc <https://github.com/cython/backports_abc>`_, * `backports-abc <https://github.com/cython/backports_abc>`_,
License: Python Software Foundation License License: Python Software Foundation License
* `Beautiful Soup <http://www.crummy.com/software/BeautifulSoup/>`_,
License: MIT
* `Django <https://www.djangoproject.com>`_, License: BSD * `Django <https://www.djangoproject.com>`_, License: BSD
* `Django REST framework <http://www.django-rest-framework.org>`_, License: * `Django REST framework <http://www.django-rest-framework.org>`_, License:
BSD BSD
* `html5lib <https://github.com/html5lib/html5lib-python>`_, License: MIT
* `Django Channels <https://github.com/andrewgodwin/channels/>`_, License: MIT * `Django Channels <https://github.com/andrewgodwin/channels/>`_, License: MIT
* `django-jsonfield <https://github.com/bradjasper/django-jsonfield/>`_, * `django-jsonfield <https://github.com/bradjasper/django-jsonfield/>`_,
License: MIT License: MIT
* `natsort <https://pypi.python.org/pypi/natsort>`_, License: MIT
* `PyPDF2 <http://mstamy2.github.io/PyPDF2/>`_, License: BSD * `PyPDF2 <http://mstamy2.github.io/PyPDF2/>`_, License: BSD
* `ReportLab <http://www.reportlab.com/opensource/>`_,
License: BSD
* `roman <https://pypi.python.org/pypi/roman>`_, License: Python 2.1.1 * `roman <https://pypi.python.org/pypi/roman>`_, License: Python 2.1.1
* `setuptools <https://pypi.python.org/pypi/setuptools>`_, * `setuptools <https://pypi.python.org/pypi/setuptools>`_,

View File

@ -1,9 +0,0 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^print/$',
views.AgendaPDF.as_view(),
name='agenda_pdf'),
]

View File

@ -1,14 +1,9 @@
from html import escape
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db import transaction from django.db import transaction
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from reportlab.platypus import Paragraph
from openslides.core.config import config from openslides.core.config import config
from openslides.utils.exceptions import OpenSlidesError from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.pdf import stylesheet
from openslides.utils.rest_api import ( from openslides.utils.rest_api import (
GenericViewSet, GenericViewSet,
ListModelMixin, ListModelMixin,
@ -19,7 +14,6 @@ from openslides.utils.rest_api import (
detail_route, detail_route,
list_route, list_route,
) )
from openslides.utils.views import PDFView
from .access_permissions import ItemAccessPermissions from .access_permissions import ItemAccessPermissions
from .models import Item, Speaker from .models import Item, Speaker
@ -231,38 +225,3 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
""" """
Item.objects.number_all(numeral_system=config['agenda_numeral_system']) Item.objects.number_all(numeral_system=config['agenda_numeral_system'])
return Response({'detail': _('The agenda has been numbered.')}) return Response({'detail': _('The agenda has been numbered.')})
# Views to generate PDFs
class AgendaPDF(PDFView):
"""
Create a full agenda-PDF.
"""
required_permission = 'agenda.can_see'
filename = ugettext_lazy('Agenda')
document_title = ugettext_lazy('Agenda')
def append_to_pdf(self, story):
tree = Item.objects.get_tree(only_agenda_items=True, include_content=True)
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):
item_number = "{} ".format(item.item_number) if item.item_number else ''
if ancestors:
space = "&nbsp;" * 6 * ancestors
story.append(Paragraph(
"%s%s%s" % (space, item_number, escape(item.title)),
stylesheet['Subitem']))
else:
story.append(Paragraph(
"%s%s" % (item_number, escape(item.title)),
stylesheet['Item']))

View File

@ -1,17 +0,0 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^print/$',
views.AssignmentPDF.as_view(),
name='assignments_pdf'),
url(r'^(?P<pk>\d+)/print/$',
views.AssignmentPDF.as_view(),
name='assignments_single_pdf'),
url(r'^poll/(?P<poll_pk>\d+)/print/$',
views.AssignmentPollPDF.as_view(),
name='assignmentpoll_pdf'),
]

View File

@ -1,24 +1,7 @@
from html import escape
from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db import transaction from django.db import transaction
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from reportlab.lib import colors
from reportlab.lib.units import cm
from reportlab.platypus import (
LongTable,
PageBreak,
Paragraph,
SimpleDocTemplate,
Spacer,
Table,
TableStyle,
)
from openslides.core.config import config
from openslides.utils.pdf import stylesheet
from openslides.utils.rest_api import ( from openslides.utils.rest_api import (
DestroyModelMixin, DestroyModelMixin,
GenericViewSet, GenericViewSet,
@ -28,7 +11,6 @@ from openslides.utils.rest_api import (
ValidationError, ValidationError,
detail_route, detail_route,
) )
from openslides.utils.views import PDFView
from .access_permissions import AssignmentAccessPermissions from .access_permissions import AssignmentAccessPermissions
from .models import Assignment, AssignmentPoll from .models import Assignment, AssignmentPoll
@ -220,365 +202,3 @@ class AssignmentPollViewSet(UpdateModelMixin, DestroyModelMixin, GenericViewSet)
""" """
return (self.request.user.has_perm('assignments.can_see') and return (self.request.user.has_perm('assignments.can_see') and
self.request.user.has_perm('assignments.can_manage')) self.request.user.has_perm('assignments.can_manage'))
# Views to generate PDFs
class AssignmentPDF(PDFView):
required_permission = 'assignments.can_see'
top_space = 0
def get_filename(self):
try:
assignment = Assignment.objects.get(pk=self.kwargs['pk'])
filename = u'%s-%s' % (
_("Election"),
assignment.title.replace(' ', '_'))
except:
filename = _("Elections")
return filename
def append_to_pdf(self, story):
try:
assignment_pk = self.kwargs['pk']
except KeyError:
assignment_pk = None
if assignment_pk is None: # print all assignments
title = escape(config["assignments_pdf_title"])
story.append(Paragraph(title, stylesheet['Heading1']))
preamble = escape(config["assignments_pdf_preamble"])
if preamble:
story.append(Paragraph(
"%s" % preamble.replace('\r\n', '<br/>'),
stylesheet['Paragraph']))
story.append(Spacer(0, 0.75 * cm))
assignments = Assignment.objects.all()
if not assignments: # No assignments existing
story.append(Paragraph(
_("No elections available."), stylesheet['Heading3']))
else: # Print all assignments
# List of assignments
for assignment in assignments:
story.append(Paragraph(
escape(assignment.title), stylesheet['Heading3']))
# Assignment details (each assignment on single page)
for assignment in assignments:
story.append(PageBreak())
# append the assignment to the story-object
self.get_assignment(assignment, story)
else: # print selected assignment
assignment = Assignment.objects.get(pk=assignment_pk)
# append the assignment to the story-object
self.get_assignment(assignment, story)
def get_assignment(self, assignment, story):
# title
story.append(Paragraph(
_("Election: %s") % escape(assignment.title), stylesheet['Heading1']))
story.append(Spacer(0, 0.5 * cm))
# Filling table rows...
data = []
polls = assignment.polls.filter(published=True)
# 1. posts
data.append([
Paragraph("%s:" %
_("Number of members to be elected"), stylesheet['Bold']),
Paragraph(str(assignment.open_posts), stylesheet['Paragraph'])])
# 2a. if no polls available print candidates
if not polls:
data.append([
Paragraph("%s:<seqreset id='counter'>" %
_("Candidates"), stylesheet['Heading4']),
[]])
for candidate in assignment.candidates:
data.append([
[],
Paragraph("<seq id='counter'/>.&nbsp; %s" % candidate,
stylesheet['Signaturefield'])])
if assignment.phase == assignment.PHASE_SEARCH:
for x in range(0, 7):
data.append([
[],
Paragraph("<seq id='counter'/>.&nbsp; "
"__________________________________________",
stylesheet['Signaturefield'])])
# 2b. if polls available print election result
if polls:
# Preparing
vote_results = assignment.vote_results(only_published=True)
data_votes = []
# Left side
cell = []
cell.append(Paragraph(
"%s:" % (_("Election result")), stylesheet['Heading4']))
# Add table head row
headrow = []
headrow.append(_("Candidates"))
for poll in polls:
headrow.append("%s. %s" % (poll.get_ballot(), _("ballot")))
data_votes.append(headrow)
# Add result rows
elected_candidates = list(assignment.elected)
length = len(vote_results)
for candidate, poll_list in vote_results.items():
row = []
candidate_string = candidate.get_short_name()
if candidate in elected_candidates:
candidate_string = "* " + candidate_string
if candidate.structure_level and length < 20:
candidate_string += "\n(%s)" % candidate.structure_level
row.append(candidate_string)
for vote in poll_list:
if vote is None:
row.append('')
elif 'Yes' in vote and 'No' in vote and 'Abstain' in vote:
row.append(
_("Y: %(YES)s\nN: %(NO)s\nA: %(ABSTAIN)s")
% {'YES': vote['Yes'], 'NO': vote['No'],
'ABSTAIN': vote['Abstain']})
elif 'Yes' in vote and 'No' in vote:
row.append(
_("Y: %(YES)s\nN: %(NO)s")
% {'YES': vote['Yes'], 'NO': vote['No']})
elif 'Votes' in vote:
row.append(vote['Votes'])
else:
pass
data_votes.append(row)
# Add valid votes row
footrow_one = []
footrow_one.append(_("Valid votes"))
votesvalid_is_used = False
for poll in polls:
footrow_one.append(poll.votesvalid)
if poll.votesvalid is not None:
votesvalid_is_used = True
if votesvalid_is_used:
data_votes.append(footrow_one)
# Add invalid votes row
footrow_two = []
footrow_two.append(_("Invalid votes"))
votesinvalid_is_used = False
for poll in polls:
footrow_two.append(poll.votesinvalid)
if poll.votesinvalid is not None:
votesinvalid_is_used = True
if votesinvalid_is_used:
data_votes.append(footrow_two)
# Add votes cast row
footrow_three = []
footrow_three.append(_("Votes cast"))
votescast_is_used = False
for poll in polls:
footrow_three.append(poll.votescast)
if poll.votescast is not None:
votescast_is_used = True
if votescast_is_used:
data_votes.append(footrow_three)
table_votes = Table(data_votes)
table_votes.setStyle(
TableStyle([
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LINEABOVE', (0, 0), (-1, 0), 2, colors.black),
('LINEABOVE', (0, 1), (-1, 1), 1, colors.black),
('LINEBELOW', (0, -1), (-1, -1), 2, colors.black),
('ROWBACKGROUNDS', (0, 1), (-1, -1), (colors.white, (.9, .9, .9)))
])
)
data.append([cell, table_votes])
if elected_candidates:
data.append(['', '* = ' + _('elected')])
# table style
data.append(['', ''])
t = LongTable(data)
t._argW[0] = 4.5 * cm
t._argW[1] = 11 * cm
t.setStyle(TableStyle([
('BOX', (0, 0), (-1, -1), 1, colors.black),
('VALIGN', (0, 0), (-1, -1), 'TOP')]))
story.append(t)
story.append(Spacer(0, 1 * cm))
# election description
story.append(
Paragraph("%s" % escape(assignment.description).replace('\r\n', '<br/>'),
stylesheet['Paragraph']))
class AssignmentPollPDF(PDFView):
required_permission = 'assignments.can_manage'
top_space = 0
def get(self, request, *args, **kwargs):
self.poll = AssignmentPoll.objects.get(pk=self.kwargs['poll_pk'])
return super().get(request, *args, **kwargs)
def get_filename(self):
filename = u'%s-%s_%s' % (
_("Election"), self.poll.assignment.title.replace(' ', '_'),
self.poll.get_ballot())
return filename
def get_template(self, buffer):
return SimpleDocTemplate(
buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0,
showBoundary=False)
def build_document(self, pdf_document, story):
pdf_document.build(story)
def append_to_pdf(self, story):
circle = "*" # = Unicode Character 'HEAVY LARGE CIRCLE' (U+2B55)
cell = []
cell.append(Spacer(0, 0.8 * cm))
cell.append(Paragraph(
_("Election") + ": " + self.poll.assignment.title,
stylesheet['Ballot_title']))
cell.append(Paragraph(
self.poll.description or '',
stylesheet['Ballot_subtitle']))
options = self.poll.get_options()
ballot_string = _("%d. ballot") % self.poll.get_ballot()
candidate_string = ungettext(
"%d candidate", "%d candidates", len(options)) % len(options)
available_posts_string = ungettext(
"%d available post", "%d available posts",
self.poll.assignment.open_posts) % self.poll.assignment.open_posts
cell.append(Paragraph(
"%s, %s, %s" % (ballot_string, candidate_string, available_posts_string),
stylesheet['Ballot_description']))
cell.append(Spacer(0, 0.4 * cm))
data = []
# get ballot papers config values
ballot_papers_selection = config["assignments_pdf_ballot_papers_selection"]
ballot_papers_number = config["assignments_pdf_ballot_papers_number"]
# set number of ballot papers
if ballot_papers_selection == "NUMBER_OF_DELEGATES":
if 'openslides.users' in settings.INSTALLED_APPS:
from openslides.users.models import Group
try:
if Group.objects.get(pk=3):
number = get_user_model().objects.filter(groups__pk=3).count()
except Group.DoesNotExist:
number = 0
else:
number = 0
elif ballot_papers_selection == "NUMBER_OF_ALL_PARTICIPANTS":
number = int(get_user_model().objects.count())
else: # ballot_papers_selection == "CUSTOM_NUMBER"
number = int(ballot_papers_number)
number = max(1, number)
counter = 0
cellcolumnA = []
# Choose kind of ballot paper (YesNoAbstain, YesNo or Yes)
if self.poll.pollmethod in ['yna', 'yn']: # YesNoAbstain/YesNo ballot: max 27 candidates
for option in options:
counter += 1
candidate = option.candidate
cell.append(Paragraph(
candidate.get_short_name(), stylesheet['Ballot_option_name_YNA']))
if candidate.structure_level:
cell.append(Paragraph(
"(%s)" % candidate.structure_level,
stylesheet['Ballot_option_suffix_YNA']))
if self.poll.pollmethod == 'yna':
cell.append(Paragraph(
"&nbsp;", stylesheet['Ballot_option_suffix_YNA']))
cell.append(Paragraph("<font name='circlefont' size='15'>%(circle)s</font> \
<font name='Ubuntu'>%(yes)s &nbsp;&nbsp;&nbsp;</font> \
<font name='circlefont' size='15'>%(circle)s</font> \
<font name='Ubuntu'>%(no)s &nbsp;&nbsp;&nbsp;</font> \
<font name='circlefont' size='15'>%(circle)s</font> \
<font name='Ubuntu'>%(abstain)s</font>" %
{'circle': circle,
'yes': _("Yes"),
'no': _("No"),
'abstain': _("Abstain")},
stylesheet['Ballot_option_circle_YNA']))
else:
cell.append(Paragraph(
"&nbsp;", stylesheet['Ballot_option_suffix_YNA']))
cell.append(Paragraph("<font name='circlefont' size='15'>%(circle)s</font> \
<font name='Ubuntu'>%(yes)s &nbsp;&nbsp;&nbsp;</font> \
<font name='circlefont' size='15'>%(circle)s</font> \
<font name='Ubuntu'>%(no)s &nbsp;&nbsp;&nbsp;</font>" %
{'circle': circle,
'yes': _("Yes"),
'no': _("No")},
stylesheet['Ballot_option_circle_YNA']))
if counter == 13:
cellcolumnA = cell
cell = []
cell.append(Spacer(0, 1.3 * cm))
# print ballot papers
for user in range(number // 2):
if len(options) > 13:
data.append([cellcolumnA, cell])
else:
data.append([cell, cell])
rest = number % 2
if rest:
data.append([cell, ''])
if len(options) <= 2:
t = Table(data, 10.5 * cm, 7.42 * cm)
elif len(options) <= 5:
t = Table(data, 10.5 * cm, 14.84 * cm)
else:
t = Table(data, 10.5 * cm, 29.7 * cm)
else: # Yes ballot: max 46 candidates
for option in options:
counter += 1
candidate = option.candidate
cell.append(Paragraph("<font name='circlefont' size='15'>%s</font> \
<font name='Ubuntu'>%s</font>" %
(circle, candidate.get_short_name()), stylesheet['Ballot_option_name']))
if candidate.structure_level:
cell.append(Paragraph(
"(%s)" % candidate.structure_level,
stylesheet['Ballot_option_suffix']))
else:
cell.append(Paragraph(
"&nbsp;", stylesheet['Ballot_option_suffix']))
if counter == 22:
cellcolumnA = cell
cell = []
cell.append(Spacer(0, 0.75 * cm))
# print ballot papers
for user in range(number // 2):
if len(options) > 22:
data.append([cellcolumnA, cell])
else:
data.append([cell, cell])
rest = number % 2
if rest:
data.append([cell, ''])
if len(options) <= 4:
t = Table(data, 10.5 * cm, 7.42 * cm)
elif len(options) <= 8:
t = Table(data, 10.5 * cm, 14.84 * cm)
else:
t = Table(data, 10.5 * cm, 29.7 * cm)
t.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
('VALIGN', (0, 0), (-1, -1), 'TOP')]))
story.append(t)

View File

@ -1,378 +0,0 @@
import random
import re
from html import escape
from bs4 import BeautifulSoup
from django.conf import settings
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext as _
from natsort import natsorted
from reportlab.lib import colors
from reportlab.lib.units import cm
from reportlab.platypus import PageBreak, Paragraph, Spacer, Table, TableStyle
from openslides.core.config import config
from openslides.utils.pdf import stylesheet
from .models import Category
def motions_to_pdf(pdf, motions):
"""
Create a PDF with all motions.
"""
motions = natsorted(motions, key=lambda motion: motion.identifier or '')
all_motion_cover(pdf, motions)
for motion in motions:
pdf.append(PageBreak())
motion_to_pdf(pdf, motion)
def motion_to_pdf(pdf, motion):
"""
Create a PDF for one motion.
"""
identifier = ''
if motion.identifier:
identifier = ' %s' % motion.identifier
pdf.append(Paragraph('%s%s: %s' % (_('Motion'), identifier, escape(motion.title)), stylesheet['Heading1']))
motion_data = []
# submitter
cell1a = []
cell1a.append(Spacer(0, 0.2 * cm))
cell1a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Submitter"),
stylesheet['Heading4']))
cell1b = []
cell1b.append(Spacer(0, 0.2 * cm))
for submitter in motion.submitters.all():
cell1b.append(Paragraph(str(submitter), stylesheet['Normal']))
motion_data.append([cell1a, cell1b])
# TODO: choose this in workflow
if motion.state.allow_submitter_edit:
# Cell for the signature
cell2a = []
cell2b = []
cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" %
_("Signature"), stylesheet['Heading4']))
cell2b.append(Paragraph(42 * "_", stylesheet['Signaturefield']))
cell2b.append(Spacer(0, 0.1 * cm))
cell2b.append(Spacer(0, 0.2 * cm))
motion_data.append([cell2a, cell2b])
# supporters
if config['motions_min_supporters']:
cell3a = []
cell3b = []
cell3a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset id='counter'>"
% _("Supporters"), stylesheet['Heading4']))
supporters = motion.supporters.all()
for supporter in supporters:
cell3b.append(Paragraph("<seq id='counter'/>.&nbsp; %s" % str(supporter),
stylesheet['Normal']))
cell3b.append(Spacer(0, 0.2 * cm))
motion_data.append([cell3a, cell3b])
# Motion state
cell4a = []
cell4b = []
cell4a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("State"),
stylesheet['Heading4']))
cell4b.append(Paragraph(_(motion.state.name), stylesheet['Normal']))
motion_data.append([cell4a, cell4b])
# Version number
if motion.versions.count() > 1:
version = motion.get_active_version()
cell5a = []
cell5b = []
cell5a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" % _("Version"),
stylesheet['Heading4']))
cell5b.append(Paragraph("%s" % version.version_number, stylesheet['Normal']))
motion_data.append([cell5a, cell5b])
# voting result
polls = []
for poll in motion.polls.all():
if not poll.has_votes():
continue
polls.append(poll)
if polls:
cell6a = []
cell6b = []
cell6a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font>" %
_("Vote result"), stylesheet['Heading4']))
ballotcounter = 0
for poll in polls:
ballotcounter += 1
option = poll.get_options()[0]
yes, no, abstain = (option['Yes'], option['No'], option['Abstain'])
valid, invalid, votescast = ('', '', '')
if poll.votesvalid is not None:
valid = "<br/>%s" % (_("Valid votes"))
if poll.votesinvalid is not None:
invalid = "<br/>%s" % (_("Invalid votes"))
if poll.votescast is not None:
votescast = "<br/>%s" % (_("Votes cast"))
if len(polls) > 1:
cell6b.append(Paragraph("%s. %s" % (ballotcounter, _("Vote")),
stylesheet['Bold']))
cell6b.append(Paragraph(
"%s: %s <br/> %s: %s <br/> %s: %s <br/> %s %s %s" %
(_("Yes"), yes, _("No"), no, _("Abstain"), abstain, valid, invalid, votescast),
stylesheet['Normal']))
cell6b.append(Spacer(0, 0.2 * cm))
motion_data.append([cell6a, cell6b])
# Creating Table
table = Table(motion_data)
table._argW[0] = 4.5 * cm
table._argW[1] = 11 * cm
table.setStyle(TableStyle([('BOX', (0, 0), (-1, -1), 1, colors.black),
('VALIGN', (0, 0), (-1, -1), 'TOP')]))
pdf.append(table)
pdf.append(Spacer(0, 1 * cm))
# motion title
pdf.append(Paragraph(escape(motion.title), stylesheet['Heading3']))
# motion text
convert_html_to_reportlab(pdf, motion.text)
pdf.append(Spacer(0, 1 * cm))
# motion reason
if motion.reason:
pdf.append(Paragraph(_("Reason") + ":", stylesheet['Heading3']))
convert_html_to_reportlab(pdf, motion.reason)
return pdf
def convert_html_to_reportlab(pdf, text):
# parsing and replacing not supported html tags for reportlab...
soup = BeautifulSoup(text, "html5lib")
# number ol list elements
ols = soup.find_all('ol')
for ol in ols:
counter = 0
for li in ol.children:
if li.name == 'li':
# if start attribute is available set counter for first list element
if li.parent.get('start') and not li.find_previous_sibling():
counter = int(ol.get('start'))
else:
counter += 1
if li.get('value'):
counter = li.get('value')
else:
li['value'] = counter
# read all list elements...
for element in soup.find_all('li'):
# ... and replace ul list elements with <para><bullet>&bull;</bullet>...<para>
if element.parent.name == "ul":
# nested lists
if element.ul or element.ol:
for i in element.find_all('li'):
element.insert_before(i)
element.clear()
else:
element.name = "para"
bullet_tag = soup.new_tag("bullet")
bullet_tag.string = u""
element.insert(0, bullet_tag)
# ... and replace ol list elements with <para><bullet><seqreset id="%id" base="value"><seq id="%id"></seq>.</bullet>...</para>
if element.parent.name == "ol":
counter = None
# set list id if element is the first of numbered list
if not element.find_previous_sibling():
id = random.randrange(0, 101)
if element.parent.get('start'):
counter = element.parent.get('start')
if element.get('value'):
counter = element.get('value')
# nested lists
if element.ul or element.ol:
nested_list = element.find_all('li')
for i in reversed(nested_list):
element.insert_after(i)
element.attrs = {}
element.name = "para"
element.insert(0, soup.new_tag("bullet"))
element.bullet.insert(0, soup.new_tag("seq"))
element.bullet.seq['id'] = id
if counter:
element.bullet.insert(0, soup.new_tag("seqreset"))
element.bullet.seqreset['id'] = id
element.bullet.seqreset['base'] = int(counter) - 1
element.bullet.insert(2, ".")
# remove tags which are not supported by reportlab (replace tags with their children tags)
for tag in soup.find_all('ul'):
tag.unwrap()
for tag in soup.find_all('ol'):
tag.unwrap()
for tag in soup.find_all('li'):
tag.unwrap()
# use tags which are supported by reportlab
# replace <s> to <strike>
for tag in soup.find_all('s'):
tag.name = "strike"
# replace <del> to <strike>
for tag in soup.find_all('del'):
tag.name = "strike"
for tag in soup.find_all('a'):
# remove a tags without href attribute
if not tag.get('href'):
tag.extract()
for tag in soup.find_all('img'):
# remove img tags without src attribute
if not tag.get('src'):
tag.extract()
# replace style attributes in <span> tags
for tag in soup.find_all('span'):
if tag.get('style'):
# replace style attribute "text-decoration: line-through;" to <strike> tag
if 'text-decoration: line-through' in str(tag['style']):
strike_tag = soup.new_tag("strike")
strike_tag.string = tag.string
tag.replace_with(strike_tag)
# replace style attribute "text-decoration: underline;" to <u> tag
elif 'text-decoration: underline' in str(tag['style']):
u_tag = soup.new_tag("u")
u_tag.string = tag.string
tag.replace_with(u_tag)
# replace style attribute "color: #xxxxxx;" to "<font backcolor='#xxxxxx'>...</font>"
elif 'background-color: ' in str(tag['style']):
font_tag = soup.new_tag("font")
color = re.findall('background-color: (.*?);', str(tag['style']))
if color:
font_tag['backcolor'] = color
if tag.string:
font_tag.string = tag.string
tag.replace_with(font_tag)
# replace style attribute "color: #xxxxxx;" to "<font color='#xxxxxx'>...</font>"
elif 'color: ' in str(tag['style']):
font_tag = soup.new_tag("font")
color = re.findall('color: (.*?);', str(tag['style']))
if color:
font_tag['color'] = color
if tag.string:
font_tag.string = tag.string
tag.replace_with(font_tag)
else:
tag.unwrap()
else:
tag.unwrap()
# print paragraphs with numbers
text = soup.body.contents
for paragraph in text:
paragraph = str(paragraph)
# ignore empty paragraphs (created by newlines/tabs of ckeditor)
if paragraph == '\n' or paragraph == '\n\n' or paragraph == '\n\t':
continue
if "<pre>" in paragraph:
txt = paragraph.replace('\n', '<br/>').replace(' ', '&nbsp;')
pdf.append(Paragraph(txt, stylesheet['InnerMonotypeParagraph']))
elif "<para>" in paragraph:
pdf.append(Paragraph(paragraph, stylesheet['InnerListParagraph']))
elif "<seqreset" in paragraph:
pass
elif "<h1>" in paragraph:
pdf.append(Paragraph(paragraph, stylesheet['InnerH1Paragraph']))
elif "<h2>" in paragraph:
pdf.append(Paragraph(paragraph, stylesheet['InnerH2Paragraph']))
elif "<h3>" in paragraph:
pdf.append(Paragraph(paragraph, stylesheet['InnerH3Paragraph']))
else:
pdf.append(Paragraph(paragraph, stylesheet['InnerParagraph']))
def all_motion_cover(pdf, motions):
"""
Create a coverpage for all motions.
"""
pdf.append(Paragraph(escape(config["motions_export_title"]), stylesheet['Heading1']))
preamble = escape(config["motions_export_preamble"])
if preamble:
pdf.append(Paragraph("%s" % preamble.replace('\r\n', '<br/>'), stylesheet['Paragraph']))
pdf.append(Spacer(0, 0.75 * cm))
# list of categories
categories = False
for i, category in enumerate(Category.objects.all()):
categories = True
if i == 0:
pdf.append(Paragraph(_("Categories"), stylesheet['Heading2']))
pdf.append(Paragraph("%s &nbsp;&nbsp; %s" % (escape(category.prefix), escape(category.name)), stylesheet['Paragraph']))
if categories:
pdf.append(PageBreak())
# list of motions
if not motions:
pdf.append(Paragraph(_("No motions available."), stylesheet['Heading3']))
else:
for motion in motions:
identifier = ''
if motion.identifier:
identifier = ' %s' % motion.identifier
pdf.append(Paragraph('%s%s: %s' % (_('Motion'), identifier, escape(motion.title)), stylesheet['Heading3']))
def motion_poll_to_pdf(pdf, poll):
circle = "*" # = Unicode Character 'HEAVY LARGE CIRCLE' (U+2B55)
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(Spacer(0, 0.5 * cm))
cell.append(Paragraph("<font name='circlefont' size='15'>%s</font> <font name='Ubuntu'>%s</font>"
% (circle, _("Yes")), stylesheet['Ballot_option']))
cell.append(Paragraph("<font name='circlefont' size='15'>%s</font> <font name='Ubuntu'>%s</font>"
% (circle, _("No")), stylesheet['Ballot_option']))
cell.append(Paragraph("<font name='circlefont' size='15'>%s</font> <font name='Ubuntu'>%s</font>"
% (circle, _("Abstain")), stylesheet['Ballot_option']))
data = []
# get ballot papers config values
ballot_papers_selection = config["motions_pdf_ballot_papers_selection"]
ballot_papers_number = config["motions_pdf_ballot_papers_number"]
# set number of ballot papers
if ballot_papers_selection == "NUMBER_OF_DELEGATES":
if 'openslides.users' in settings.INSTALLED_APPS:
from openslides.users.models import Group
try:
if Group.objects.get(pk=3):
number = get_user_model().objects.filter(groups__pk=3).count()
except Group.DoesNotExist:
number = 0
else:
number = 0
elif ballot_papers_selection == "NUMBER_OF_ALL_PARTICIPANTS":
number = int(get_user_model().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 range(int(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)

View File

@ -6,16 +6,4 @@ urlpatterns = [
url(r'^docxtemplate/$', url(r'^docxtemplate/$',
views.MotionDocxTemplateView.as_view(), views.MotionDocxTemplateView.as_view(),
name='motions_docx_template'), name='motions_docx_template'),
url(r'^pdf/$',
views.MotionPDFView.as_view(print_all_motions=True),
name='motions_pdf'),
url(r'^(?P<pk>\d+)/pdf/$',
views.MotionPDFView.as_view(print_all_motions=False),
name='motions_single_pdf'),
url(r'^poll/(?P<poll_pk>\d+)/print/$',
views.MotionPollPDF.as_view(),
name='motionpoll_pdf'),
] ]

View File

@ -3,10 +3,8 @@ import base64
from django.contrib.staticfiles import finders from django.contrib.staticfiles import finders
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.http import Http404 from django.http import Http404
from django.utils.text import slugify
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_noop from django.utils.translation import ugettext_noop
from reportlab.platypus import SimpleDocTemplate
from rest_framework import status from rest_framework import status
from ..core.config import config from ..core.config import config
@ -20,7 +18,8 @@ from ..utils.rest_api import (
ValidationError, ValidationError,
detail_route, detail_route,
) )
from ..utils.views import APIView, PDFView, SingleObjectMixin from ..utils.views import APIView
from .access_permissions import ( from .access_permissions import (
CategoryAccessPermissions, CategoryAccessPermissions,
MotionAccessPermissions, MotionAccessPermissions,
@ -39,7 +38,6 @@ from .models import (
State, State,
Workflow, Workflow,
) )
from .pdf import motion_poll_to_pdf, motion_to_pdf, motions_to_pdf
from .serializers import MotionPollSerializer from .serializers import MotionPollSerializer
@ -523,101 +521,6 @@ class WorkflowViewSet(ModelViewSet):
return result return result
# Views to generate PDFs and for the DOCX template
class MotionPollPDF(PDFView):
"""
Generates a ballotpaper.
"""
required_permission = 'motions.can_manage'
top_space = 0
def get(self, request, *args, **kwargs):
self.poll = MotionPoll.objects.get(pk=self.kwargs['poll_pk'])
return super().get(request, *args, **kwargs)
def get_filename(self):
"""
Return the filename for the PDF.
"""
return u'%s_%s' % (_("Motion"), _("Vote"))
def get_template(self, buffer):
return SimpleDocTemplate(
buffer, topMargin=-6, bottomMargin=-6, leftMargin=0, rightMargin=0,
showBoundary=False)
def build_document(self, pdf_document, story):
pdf_document.build(story)
def append_to_pdf(self, pdf):
"""
Append PDF objects.
"""
motion_poll_to_pdf(pdf, self.poll)
class MotionPDFView(SingleObjectMixin, PDFView):
"""
Create the PDF for one or for all motions.
If self.print_all_motions is True, the view returns a PDF with all motions.
If self.print_all_motions is False, the view returns a PDF with only one
motion.
"""
model = Motion
top_space = 0
print_all_motions = False
def check_permission(self, request, *args, **kwargs):
"""
Checks if the requesting user has the permission to see the motion as
PDF.
"""
if self.print_all_motions:
is_allowed = request.user.has_perm('motions.can_see')
else:
is_allowed = self.get_object().get_allowed_actions(request.user)['see']
return is_allowed
def get_object(self, *args, **kwargs):
if self.print_all_motions:
obj = None
else:
obj = super().get_object(*args, **kwargs)
return obj
def get_filename(self):
"""
Return the filename for the PDF.
"""
if self.print_all_motions:
return _("Motions")
else:
if self.get_object().identifier:
suffix = self.get_object().identifier.replace(' ', '')
else:
suffix = self.get_object().title.replace(' ', '_')
suffix = slugify(suffix)
return '%s-%s' % (_("Motion"), suffix)
def append_to_pdf(self, pdf):
"""
Append PDF objects.
"""
if self.print_all_motions:
motions = []
for motion in Motion.objects.all():
if (not motion.state.required_permission_to_see or
self.request.user.has_perm(motion.state.required_permission_to_see)):
motions.append(motion)
motions_to_pdf(pdf, motions)
else:
motion_to_pdf(pdf, self.get_object())
class MotionDocxTemplateView(APIView): class MotionDocxTemplateView(APIView):
""" """
Returns the template for motions docx export Returns the template for motions docx export

View File

@ -12,8 +12,6 @@ urlpatterns += [
url(r'^%s(?P<path>.*)$' % settings.MEDIA_URL.lstrip('/'), serve, {'document_root': settings.MEDIA_ROOT}), url(r'^%s(?P<path>.*)$' % settings.MEDIA_URL.lstrip('/'), serve, {'document_root': settings.MEDIA_ROOT}),
url(r'^(?P<url>.*[^/])$', RedirectView.as_view(url='/%(url)s/', permanent=True)), url(r'^(?P<url>.*[^/])$', RedirectView.as_view(url='/%(url)s/', permanent=True)),
url(r'^rest/', include(router.urls)), url(r'^rest/', include(router.urls)),
url(r'^agenda/', include('openslides.agenda.urls')),
url(r'^assignments/', include('openslides.assignments.urls')),
url(r'^motions/', include('openslides.motions.urls')), url(r'^motions/', include('openslides.motions.urls')),
url(r'^users/', include('openslides.users.urls')), url(r'^users/', include('openslides.users.urls')),

View File

@ -19,13 +19,4 @@ urlpatterns = [
url(r'^setpassword/$', url(r'^setpassword/$',
views.SetPasswordView.as_view(), views.SetPasswordView.as_view(),
name='user_setpassword'), name='user_setpassword'),
# PDF
url(r'^print/$',
views.UsersListPDF.as_view(),
name='user_listpdf'),
url(r'^passwords/print/$',
views.UsersPasswordsPDF.as_view(),
name='user_passwordspdf'),
] ]

View File

@ -3,7 +3,6 @@ from django.contrib.auth import logout as auth_logout
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from ..core.config import config from ..core.config import config
from ..utils.rest_api import ( from ..utils.rest_api import (
@ -14,10 +13,9 @@ from ..utils.rest_api import (
detail_route, detail_route,
status, status,
) )
from ..utils.views import APIView, PDFView from ..utils.views import APIView
from .access_permissions import UserAccessPermissions from .access_permissions import UserAccessPermissions
from .models import Group, User from .models import Group, User
from .pdf import users_passwords_to_pdf, users_to_pdf
from .serializers import GroupSerializer from .serializers import GroupSerializer
@ -251,38 +249,3 @@ class SetPasswordView(APIView):
else: else:
raise ValidationError({'detail': _('Old password does not match.')}) raise ValidationError({'detail': _('Old password does not match.')})
return super().post(request, *args, **kwargs) return super().post(request, *args, **kwargs)
# Views to generate PDFs
class UsersListPDF(PDFView):
"""
Generate a list of all users as PDF.
"""
required_permission = 'users.can_see_extra_data'
filename = ugettext_lazy('user-list')
document_title = ugettext_lazy('List of users')
def append_to_pdf(self, pdf):
"""
Append PDF objects.
"""
users_to_pdf(pdf)
class UsersPasswordsPDF(PDFView):
"""
Generate the access data welcome paper for all users as PDF.
"""
required_permission = 'users.can_manage'
filename = ugettext_lazy('user-access-data')
top_space = 0
def build_document(self, pdf_document, story):
pdf_document.build(story)
def append_to_pdf(self, pdf):
"""
Append PDF objects.
"""
users_passwords_to_pdf(pdf)

View File

@ -1,273 +0,0 @@
from datetime import datetime
from os.path import join as path_join
from django.conf import settings
from django.utils import formats
from django.utils.translation import ugettext as _
from reportlab.lib import colors
from reportlab.lib.styles import ParagraphStyle, StyleSheet1
from reportlab.lib.units import cm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.rl_config import defaultPageSize
from openslides.core.config import config
# register new truetype fonts
pdfmetrics.registerFont(TTFont(
'Ubuntu', path_join(settings.MODULE_DIR, 'core', 'static', 'fonts', 'Ubuntu-R.ttf')))
pdfmetrics.registerFont(TTFont(
'Ubuntu-Bold', path_join(settings.MODULE_DIR, 'core', 'static', 'fonts', 'Ubuntu-B.ttf')))
pdfmetrics.registerFont(TTFont(
'Ubuntu-Italic', path_join(settings.MODULE_DIR, 'core', 'static', 'fonts', 'Ubuntu-RI.ttf')))
pdfmetrics.registerFont(TTFont(
'circlefont', path_join(settings.MODULE_DIR, 'core', 'static', 'fonts', 'circle.ttf')))
pdfmetrics.registerFontFamily('Ubuntu', normal='Ubuntu', bold='Ubuntu-Bold', italic='Ubuntu-Italic')
# set style information
PAGE_HEIGHT = defaultPageSize[1]
PAGE_WIDTH = defaultPageSize[0]
# set custom stylesheets
stylesheet = StyleSheet1()
stylesheet.add(ParagraphStyle(
name='Normal',
fontName='Ubuntu',
fontSize=10,
leading=12,
))
stylesheet.add(ParagraphStyle(
name='Paragraph',
parent=stylesheet['Normal'],
leading=14,
spaceAfter=15
))
stylesheet.add(ParagraphStyle(
name='Paragraph12',
parent=stylesheet['Paragraph'],
fontSize=12
))
stylesheet.add(ParagraphStyle(
name='InnerParagraph',
parent=stylesheet['Normal'],
leading=14,
spaceBefore=5,
spaceAfter=5,
bulletIndent=-15,
bulletFontSize=8,
bulletColor=colors.grey
))
stylesheet.add(ParagraphStyle(
name='InnerListParagraph',
parent=stylesheet['InnerParagraph'],
bulletIndent=10,
bulletFontSize=10,
bulletColor=colors.black,
leftIndent=30
))
stylesheet.add(ParagraphStyle(
name='InnerMonotypeParagraph',
parent=stylesheet['InnerParagraph'],
fontName='Courier',
))
stylesheet.add(ParagraphStyle(
name='InnerH1Paragraph',
parent=stylesheet['InnerParagraph'],
fontName='Ubuntu-Bold',
fontSize=16,
spaceBefore=20,
spaceAfter=10,
))
stylesheet.add(ParagraphStyle(
name='InnerH2Paragraph',
parent=stylesheet['InnerH1Paragraph'],
fontSize=12,
spaceBefore=20,
spaceAfter=10,
))
stylesheet.add(ParagraphStyle(
name='InnerH3Paragraph',
parent=stylesheet['InnerH2Paragraph'],
fontSize=10,
spaceBefore=15,
spaceAfter=5,
))
stylesheet.add(ParagraphStyle(
name='Small',
parent=stylesheet['Normal'],
fontSize=8
))
stylesheet.add(ParagraphStyle(
name='Italic',
parent=stylesheet['Normal'],
fontName='Ubuntu-Italic',
spaceAfter=5
))
stylesheet.add(ParagraphStyle(
name='Bold',
parent=stylesheet['Normal'],
fontName='Ubuntu-Bold',
))
stylesheet.add(ParagraphStyle(
name='Heading1',
parent=stylesheet['Bold'],
fontSize=24,
leading=30,
spaceAfter=6,
), alias='h1')
stylesheet.add(ParagraphStyle(
name='Heading2',
parent=stylesheet['Bold'],
fontSize=14,
leading=24,
spaceAfter=10,
), alias='h2')
stylesheet.add(ParagraphStyle(
name='Heading3',
parent=stylesheet['Bold'],
fontSize=12,
leading=20,
), alias='h3')
stylesheet.add(ParagraphStyle(
name='Heading4',
parent=stylesheet['Bold'],
fontSize=10,
leading=20,
), alias='h4')
stylesheet.add(ParagraphStyle(
name='Item',
parent=stylesheet['Normal'],
fontSize=14,
leading=14,
leftIndent=0,
spaceAfter=15,
))
stylesheet.add(ParagraphStyle(
name='Subitem',
parent=stylesheet['Normal'],
fontSize=10,
leading=10,
leftIndent=20,
spaceAfter=15))
stylesheet.add(ParagraphStyle(
name='Tablecell',
parent=stylesheet['Normal'],
fontSize=9))
stylesheet.add(ParagraphStyle(name='Signaturefield',
parent=stylesheet['Normal'],
spaceBefore=15)
)
# Ballot stylesheets
stylesheet.add(ParagraphStyle(name='Ballot_title',
parent=stylesheet['Bold'],
fontSize=12,
leading=14,
leftIndent=30),
)
stylesheet.add(ParagraphStyle(name='Ballot_subtitle',
parent=stylesheet['Normal'],
fontSize=10,
leading=12,
leftIndent=30,
rightIndent=20,
spaceAfter=5),
)
stylesheet.add(ParagraphStyle(name='Ballot_description',
parent=stylesheet['Normal'],
fontSize=7,
leading=10,
leftIndent=30),
)
stylesheet.add(ParagraphStyle(name='Ballot_option',
parent=stylesheet['Normal'],
fontSize=12,
leading=24,
leftIndent=30),
)
stylesheet.add(ParagraphStyle(name='Ballot_option_name_YNA',
parent=stylesheet['Ballot_option'],
leading=14),
)
stylesheet.add(ParagraphStyle(name='Ballot_option_name',
parent=stylesheet['Ballot_option_name_YNA'],
leading=17),
)
stylesheet.add(ParagraphStyle(name='Ballot_option_suffix_YNA',
parent=stylesheet['Ballot_option_name_YNA'],
fontSize=8,
leading=11),
)
stylesheet.add(ParagraphStyle(name='Ballot_option_suffix',
parent=stylesheet['Ballot_option_suffix_YNA'],
leading=16,
leftIndent=48),
)
stylesheet.add(ParagraphStyle(name='Ballot_option_circle_YNA',
parent=stylesheet['Ballot_option_name_YNA'],
leftIndent=48,
spaceAfter=18),
)
# Password paper stylesheets
stylesheet.add(ParagraphStyle(name='formfield',
parent=stylesheet['Normal'],
fontSize=12,
leading=18,
leftIndent=0),
)
stylesheet.add(ParagraphStyle(name='formfield_value',
parent=stylesheet['Normal'],
fontName='Courier',
fontSize=12,
leading=28,
leftIndent=10),
)
stylesheet.add(ParagraphStyle(name='qrcode_comment',
parent=stylesheet['Small'],
spaceBefore=6),
)
def firstPage(canvas, doc):
canvas.saveState()
# page header (with event information)
canvas.setFont('Ubuntu', 10)
canvas.setFillGray(0.4)
title_line = u"%s | %s" % (config["general_event_name"],
config["general_event_description"])
if len(title_line) > 75:
title_line = "%s ..." % title_line[:70]
canvas.drawString(2.75 * cm, 28 * cm, title_line)
if config["general_event_date"] and config["general_event_location"]:
canvas.drawString(2.75 * cm, 27.6 * cm, u"%s, %s"
% (config["general_event_date"], config["general_event_location"]))
# time
canvas.setFont('Ubuntu', 7)
time = formats.date_format(datetime.now(), 'DATETIME_FORMAT')
canvas.drawString(15 * cm, 28 * cm, _("As of: %s") % time)
# title
if doc.title:
canvas.setFont('Ubuntu-Bold', 24)
canvas.setFillGray(0)
canvas.drawString(2.75 * cm, PAGE_HEIGHT - 108, doc.title)
# footer (with page number)
canvas.setFont('Ubuntu', 8)
canvas.setFillGray(0.4)
canvas.drawString(10 * cm, 1 * cm, _("Page %s") % doc.page)
canvas.restoreState()
def laterPages(canvas, doc):
canvas.saveState()
# footer (with page number)
canvas.setFont('Ubuntu', 7)
canvas.setFillGray(0.4)
canvas.drawString(10 * cm, 1 * cm, _("Page %s") % doc.page)
canvas.restoreState()

View File

@ -1,46 +1,11 @@
from io import BytesIO
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.utils.translation import ugettext_lazy
from django.views import generic as django_views from django.views import generic as django_views
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from reportlab.lib.units import cm
from reportlab.platypus import SimpleDocTemplate, Spacer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView as _APIView from rest_framework.views import APIView as _APIView
from .pdf import firstPage, laterPages
View = django_views.View View = django_views.View
class SingleObjectMixin(django_views.detail.SingleObjectMixin):
"""
Mixin for single objects from the database.
"""
def dispatch(self, *args, **kwargs):
if not hasattr(self, 'object'):
# Save the object not only in the cache but in the public
# attribute self.object because Django expects this later.
# Because get_object() has an internal cache this line is not a
# performance problem.
self.object = self.get_object()
return super().dispatch(*args, **kwargs)
def get_object(self, *args, **kwargs):
"""
Returns the single object from database or cache.
"""
try:
obj = self._object
except AttributeError:
obj = super().get_object(*args, **kwargs)
self._object = obj
return obj
class CSRFMixin: class CSRFMixin:
""" """
Adds the csrf cookie to the response. Adds the csrf cookie to the response.
@ -52,76 +17,6 @@ class CSRFMixin:
return ensure_csrf_cookie(view) return ensure_csrf_cookie(view)
class PDFView(View):
"""
View to generate an PDF.
"""
filename = ugettext_lazy('undefined-filename')
top_space = 3
document_title = None
required_permission = None
def check_permission(self, request, *args, **kwargs):
"""
Checks if the user has the required permission.
"""
if self.required_permission is None:
return True
else:
return request.user.has_perm(self.required_permission)
def dispatch(self, request, *args, **kwargs):
"""
Check if the user has the permission.
If the user is not logged in, redirect the user to the login page.
"""
if not self.check_permission(request, *args, **kwargs):
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
def get_top_space(self):
return self.top_space
def get_document_title(self):
if self.document_title:
return str(self.document_title)
else:
return ''
def get_filename(self):
return self.filename
def get_template(self, buffer):
return SimpleDocTemplate(buffer)
def build_document(self, pdf_document, story):
pdf_document.build(
story, onFirstPage=firstPage, onLaterPages=laterPages)
def render_to_response(self, filename):
response = HttpResponse(content_type='application/pdf')
filename = 'filename=%s.pdf;' % self.get_filename()
response['Content-Disposition'] = filename.encode('utf-8')
buffer = BytesIO()
pdf_document = self.get_template(buffer)
pdf_document.title = self.get_document_title()
story = [Spacer(1, self.get_top_space() * cm)]
self.append_to_pdf(story)
self.build_document(pdf_document, story)
pdf = buffer.getvalue()
buffer.close()
response.write(pdf)
return response
def get(self, request, *args, **kwargs):
return self.render_to_response(self.get_filename())
class APIView(_APIView): class APIView(_APIView):
""" """
The Django Rest framework APIView with improvements for OpenSlides. The Django Rest framework APIView with improvements for OpenSlides.

View File

@ -1,13 +1,9 @@
# Requirements for OpenSlides in production in alphabetical order # Requirements for OpenSlides in production in alphabetical order
Django>=1.10.1,<1.11 Django>=1.10.1,<1.11
beautifulsoup4>=4.5,<4.6
channels>=0.15,<1.0 channels>=0.15,<1.0
djangorestframework>=3.4,<3.5 djangorestframework>=3.4,<3.5
html5lib>=0.99,<1.0
jsonfield>=0.9.19,<1.1 jsonfield>=0.9.19,<1.1
natsort>=3.2,<5.1
PyPDF2>=1.25.0,<1.27 PyPDF2>=1.25.0,<1.27
reportlab>=3.0,<3.4
roman>=2.0,<2.1 roman>=2.0,<2.1
setuptools>=18.5,<28.0 setuptools>=18.5,<28.0
twisted>=16.2,<16.4 twisted>=16.2,<16.4

View File

@ -1,15 +0,0 @@
from openslides.topics.models import Topic
from openslides.utils.test import TestCase
class TestAgendaPDF(TestCase):
def test_get(self):
"""
Tests that a requst on the pdf-page returns with statuscode 200.
"""
Topic.objects.create(title='item1')
self.client.login(username='admin', password='admin')
response = self.client.get('/agenda/print/')
self.assertEqual(response.status_code, 200)

View File

@ -1,25 +0,0 @@
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from rest_framework import status
from rest_framework.test import APIClient
from openslides.assignments.models import Assignment
from openslides.utils.test import TestCase
class PDF(TestCase):
"""
Tests assignment PDF.
"""
def setUp(self):
self.client = APIClient()
self.client.login(username='admin', password='admin')
self.admin = get_user_model().objects.get(username='admin')
self.assignment = Assignment.objects.create(title='test_assignment_OxieG7BioChahteY4aeM', open_posts=1)
def test_pdf_with_ballot(self):
self.assignment.set_candidate(self.admin)
self.assignment.create_poll()
self.assignment.polls.all()[0].set_published(True)
response = self.client.get(reverse('assignments_pdf'))
self.assertEqual(response.status_code, status.HTTP_200_OK)

View File

@ -1,28 +0,0 @@
from django.core.urlresolvers import reverse
from rest_framework import status
from openslides.core.config import config
from openslides.motions.models import Motion
from openslides.utils.test import TestCase
class AllMotionPDF(TestCase):
"""
Tests creating a PDF of all motions.
"""
def setUp(self):
self.client.login(username='admin', password='admin')
config['motions_identifier'] = 'manually'
self.motion = Motion(
title='test_title_Dik4jaey5ku6axee7Dai',
text='test_text_Auvie4euf2oang8ahcie')
self.motion.save()
self.motion2 = Motion(
title='test_title_AeTheech6euf9siM8uey',
text='test_text_Cohsh2egaexae8eebiot',
identifier='42')
self.motion2.save()
def test_pdf_all_motions(self):
response = self.client.get(reverse('motions_pdf'))
self.assertEqual(response.status_code, status.HTTP_200_OK)

View File

@ -1,45 +0,0 @@
from django.test.client import Client
from openslides.motions.models import Motion
from openslides.users.models import User
from openslides.utils.test import TestCase
class MotionPDFTest(TestCase):
"""
Tests for motion PDF.
"""
def setUp(self):
# Admin
self.admin = User.objects.get(pk=1)
self.admin_client = Client()
self.admin_client.login(username='admin', password='admin')
# Registered
self.registered = User.objects.create_user('registered', 'registered')
self.registered_client = Client()
self.registered_client.login(username='registered', password='registered')
def test_render_nested_list(self):
Motion.objects.create(
title='Test Title chieM6Aing8Eegh9ePhu',
text='<ul><li>Element 1 aKaesieze6mahR2ielie'
'<ul><li>Subelement 1 rel0liiGh0bi3ree6Jei</li>'
'<li>Subelement 2 rel0liiGh0bi3ree6Jei</li></ul></li>'
'<li>Element 2 rel0liiGh0bi3ree6Jei</li></ul>')
response = self.admin_client.get('/motions/1/pdf/')
self.assertEqual(response.status_code, 200)
def test_get_without_required_permission_from_state(self):
motion = Motion.objects.create(title='motion_title_zthguis8qqespgknme52')
motion.state.required_permission_to_see = 'motions.can_manage'
motion.state.save()
response = self.registered_client.get('/motions/1/pdf/')
self.assertEqual(response.status_code, 403)
def test_get_with_filtered_motion_list(self):
motion = Motion.objects.create(title='motion_title_qwgvzf6487guni0oikcc')
motion.state.required_permission_to_see = 'motions.can_manage'
motion.state.save()
response = self.registered_client.get('/motions/pdf/')
self.assertEqual(response.status_code, 200)

View File

@ -1,57 +1,9 @@
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import patch
from openslides.utils import views from openslides.utils import views
@patch('builtins.super')
class SingleObjectMixinTest(TestCase):
def test_get_object_cache(self, mock_super):
"""
Test that the method get_object caches his result.
Tests that get_object from the django view is only called once, even if
get_object on our class is called twice.
"""
view = views.SingleObjectMixin()
view.get_object()
view.get_object()
mock_super().get_object.assert_called_once_with()
def test_dispatch_with_existin_object(self, mock_super):
view = views.SingleObjectMixin()
view.object = 'old_object'
view.get_object = MagicMock()
view.dispatch()
mock_super().dispatch.assert_called_with()
self.assertEqual(
view.object,
'old_object',
"view.object should not be changed")
self.assertFalse(
view.get_object.called,
"view.get_object() should not be called")
def test_dispatch_without_existin_object(self, mock_super):
view = views.SingleObjectMixin()
view.get_object = MagicMock(return_value='new_object')
view.dispatch()
mock_super().dispatch.assert_called_with()
self.assertEqual(
view.object,
'new_object',
"view.object should be changed")
self.assertTrue(
view.get_object.called,
"view.get_object() should be called")
class TestAPIView(TestCase): class TestAPIView(TestCase):
def test_class_creation(self): def test_class_creation(self):
""" """