From 7281aa57b858d2f713c2d1aef023d3a4c94bfd78 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 1 Oct 2016 14:26:28 +0200 Subject: [PATCH] Removed old code needed be the server to serve pdf --- README.rst | 18 +- openslides/agenda/urls.py | 9 - openslides/agenda/views.py | 41 --- openslides/assignments/urls.py | 17 - openslides/assignments/views.py | 380 -------------------- openslides/motions/pdf.py | 378 ------------------- openslides/motions/urls.py | 12 - openslides/motions/views.py | 101 +----- openslides/urls.py | 2 - openslides/users/urls.py | 9 - openslides/users/views.py | 39 +- openslides/utils/pdf.py | 273 -------------- openslides/utils/views.py | 105 ------ requirements_production.txt | 4 - tests/integration/agenda/test_views.py | 15 - tests/integration/assignments/test_views.py | 25 -- tests/integration/motions/test_pdf.py | 28 -- tests/old/motions/test_pdf.py | 45 --- tests/unit/utils/test_views.py | 50 +-- 19 files changed, 5 insertions(+), 1546 deletions(-) delete mode 100644 openslides/agenda/urls.py delete mode 100644 openslides/assignments/urls.py delete mode 100644 openslides/motions/pdf.py delete mode 100644 openslides/utils/pdf.py delete mode 100644 tests/integration/agenda/test_views.py delete mode 100644 tests/integration/assignments/test_views.py delete mode 100644 tests/integration/motions/test_pdf.py delete mode 100644 tests/old/motions/test_pdf.py diff --git a/README.rst b/README.rst index 23d2c6cb6..5a100c867 100644 --- a/README.rst +++ b/README.rst @@ -28,13 +28,7 @@ a. Check requirements ''''''''''''''''''''' Make sure that you have installed `Python (>= 3.4) -`_ on your system. You also need the Python -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 +`_ on your system. b. Setup a virtual Python environment (optional) @@ -156,28 +150,18 @@ OpenSlides uses the following projects or parts of them: * `backports-abc `_, License: Python Software Foundation License -* `Beautiful Soup `_, - License: MIT - * `Django `_, License: BSD * `Django REST framework `_, License: BSD -* `html5lib `_, License: MIT - * `Django Channels `_, License: MIT * `django-jsonfield `_, License: MIT -* `natsort `_, License: MIT - * `PyPDF2 `_, License: BSD -* `ReportLab `_, - License: BSD - * `roman `_, License: Python 2.1.1 * `setuptools `_, diff --git a/openslides/agenda/urls.py b/openslides/agenda/urls.py deleted file mode 100644 index 954255738..000000000 --- a/openslides/agenda/urls.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.conf.urls import url - -from . import views - -urlpatterns = [ - url(r'^print/$', - views.AgendaPDF.as_view(), - name='agenda_pdf'), -] diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index bfe066c75..7e905e4dd 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -1,14 +1,9 @@ -from html import escape - from django.contrib.auth import get_user_model from django.db import transaction 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.utils.exceptions import OpenSlidesError -from openslides.utils.pdf import stylesheet from openslides.utils.rest_api import ( GenericViewSet, ListModelMixin, @@ -19,7 +14,6 @@ from openslides.utils.rest_api import ( detail_route, list_route, ) -from openslides.utils.views import PDFView from .access_permissions import ItemAccessPermissions 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']) 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 = " " * 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'])) diff --git a/openslides/assignments/urls.py b/openslides/assignments/urls.py deleted file mode 100644 index 1352510f5..000000000 --- a/openslides/assignments/urls.py +++ /dev/null @@ -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\d+)/print/$', - views.AssignmentPDF.as_view(), - name='assignments_single_pdf'), - - url(r'^poll/(?P\d+)/print/$', - views.AssignmentPollPDF.as_view(), - name='assignmentpoll_pdf'), -] diff --git a/openslides/assignments/views.py b/openslides/assignments/views.py index 8d0e80240..d265832c4 100644 --- a/openslides/assignments/views.py +++ b/openslides/assignments/views.py @@ -1,24 +1,7 @@ -from html import escape - -from django.conf import settings from django.contrib.auth import get_user_model from django.db import transaction 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 ( DestroyModelMixin, GenericViewSet, @@ -28,7 +11,6 @@ from openslides.utils.rest_api import ( ValidationError, detail_route, ) -from openslides.utils.views import PDFView from .access_permissions import AssignmentAccessPermissions from .models import Assignment, AssignmentPoll @@ -220,365 +202,3 @@ class AssignmentPollViewSet(UpdateModelMixin, DestroyModelMixin, GenericViewSet) """ return (self.request.user.has_perm('assignments.can_see') and 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', '
'), - 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:" % - _("Candidates"), stylesheet['Heading4']), - []]) - for candidate in assignment.candidates: - data.append([ - [], - Paragraph(".  %s" % candidate, - stylesheet['Signaturefield'])]) - if assignment.phase == assignment.PHASE_SEARCH: - for x in range(0, 7): - data.append([ - [], - Paragraph(".  " - "__________________________________________", - 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', '
'), - 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( - " ", stylesheet['Ballot_option_suffix_YNA'])) - cell.append(Paragraph("%(circle)s \ - %(yes)s     \ - %(circle)s \ - %(no)s     \ - %(circle)s \ - %(abstain)s" % - {'circle': circle, - 'yes': _("Yes"), - 'no': _("No"), - 'abstain': _("Abstain")}, - stylesheet['Ballot_option_circle_YNA'])) - else: - cell.append(Paragraph( - " ", stylesheet['Ballot_option_suffix_YNA'])) - cell.append(Paragraph("%(circle)s \ - %(yes)s     \ - %(circle)s \ - %(no)s    " % - {'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("%s \ - %s" % - (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( - " ", 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) diff --git a/openslides/motions/pdf.py b/openslides/motions/pdf.py deleted file mode 100644 index edc415421..000000000 --- a/openslides/motions/pdf.py +++ /dev/null @@ -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("%s:" % _("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("%s:" % - _("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("%s:" - % _("Supporters"), stylesheet['Heading4'])) - supporters = motion.supporters.all() - for supporter in supporters: - cell3b.append(Paragraph(".  %s" % str(supporter), - stylesheet['Normal'])) - cell3b.append(Spacer(0, 0.2 * cm)) - motion_data.append([cell3a, cell3b]) - - # Motion state - cell4a = [] - cell4b = [] - cell4a.append(Paragraph("%s:" % _("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("%s:" % _("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("%s:" % - _("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 = "
%s" % (_("Valid votes")) - if poll.votesinvalid is not None: - invalid = "
%s" % (_("Invalid votes")) - if poll.votescast is not None: - votescast = "
%s" % (_("Votes cast")) - if len(polls) > 1: - cell6b.append(Paragraph("%s. %s" % (ballotcounter, _("Vote")), - stylesheet['Bold'])) - cell6b.append(Paragraph( - "%s: %s
%s: %s
%s: %s
%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 ... - 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 .... - 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 to - for tag in soup.find_all('s'): - tag.name = "strike" - - # replace to - 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 tags - for tag in soup.find_all('span'): - if tag.get('style'): - # replace style attribute "text-decoration: line-through;" to 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 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 "..." - 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 "..." - 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 "
" in paragraph:
-            txt = paragraph.replace('\n', '
').replace(' ', ' ') - pdf.append(Paragraph(txt, stylesheet['InnerMonotypeParagraph'])) - elif "" in paragraph: - pdf.append(Paragraph(paragraph, stylesheet['InnerListParagraph'])) - elif "" in paragraph: - pdf.append(Paragraph(paragraph, stylesheet['InnerH1Paragraph'])) - elif "

" in paragraph: - pdf.append(Paragraph(paragraph, stylesheet['InnerH2Paragraph'])) - elif "

" 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', '
'), 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    %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("%s %s" - % (circle, _("Yes")), stylesheet['Ballot_option'])) - cell.append(Paragraph("%s %s" - % (circle, _("No")), stylesheet['Ballot_option'])) - cell.append(Paragraph("%s %s" - % (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) diff --git a/openslides/motions/urls.py b/openslides/motions/urls.py index d2359a1a2..6e7c41473 100644 --- a/openslides/motions/urls.py +++ b/openslides/motions/urls.py @@ -6,16 +6,4 @@ urlpatterns = [ url(r'^docxtemplate/$', views.MotionDocxTemplateView.as_view(), name='motions_docx_template'), - - url(r'^pdf/$', - views.MotionPDFView.as_view(print_all_motions=True), - name='motions_pdf'), - - url(r'^(?P\d+)/pdf/$', - views.MotionPDFView.as_view(print_all_motions=False), - name='motions_single_pdf'), - - url(r'^poll/(?P\d+)/print/$', - views.MotionPollPDF.as_view(), - name='motionpoll_pdf'), ] diff --git a/openslides/motions/views.py b/openslides/motions/views.py index ce77b21fb..7fb9cb167 100644 --- a/openslides/motions/views.py +++ b/openslides/motions/views.py @@ -3,10 +3,8 @@ import base64 from django.contrib.staticfiles import finders from django.db import IntegrityError, transaction from django.http import Http404 -from django.utils.text import slugify from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_noop -from reportlab.platypus import SimpleDocTemplate from rest_framework import status from ..core.config import config @@ -20,7 +18,8 @@ from ..utils.rest_api import ( ValidationError, detail_route, ) -from ..utils.views import APIView, PDFView, SingleObjectMixin +from ..utils.views import APIView + from .access_permissions import ( CategoryAccessPermissions, MotionAccessPermissions, @@ -39,7 +38,6 @@ from .models import ( State, Workflow, ) -from .pdf import motion_poll_to_pdf, motion_to_pdf, motions_to_pdf from .serializers import MotionPollSerializer @@ -523,101 +521,6 @@ class WorkflowViewSet(ModelViewSet): 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): """ Returns the template for motions docx export diff --git a/openslides/urls.py b/openslides/urls.py index 8c622add2..b804cbb30 100644 --- a/openslides/urls.py +++ b/openslides/urls.py @@ -12,8 +12,6 @@ urlpatterns += [ url(r'^%s(?P.*)$' % settings.MEDIA_URL.lstrip('/'), serve, {'document_root': settings.MEDIA_ROOT}), url(r'^(?P.*[^/])$', RedirectView.as_view(url='/%(url)s/', permanent=True)), 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'^users/', include('openslides.users.urls')), diff --git a/openslides/users/urls.py b/openslides/users/urls.py index a50683ef0..5b1bbeddb 100644 --- a/openslides/users/urls.py +++ b/openslides/users/urls.py @@ -19,13 +19,4 @@ urlpatterns = [ url(r'^setpassword/$', views.SetPasswordView.as_view(), 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'), ] diff --git a/openslides/users/views.py b/openslides/users/views.py index 558f2d10a..90842707e 100644 --- a/openslides/users/views.py +++ b/openslides/users/views.py @@ -3,7 +3,6 @@ from django.contrib.auth import logout as auth_logout from django.contrib.auth.forms import AuthenticationForm from django.utils.encoding import force_text from django.utils.translation import ugettext as _ -from django.utils.translation import ugettext_lazy from ..core.config import config from ..utils.rest_api import ( @@ -14,10 +13,9 @@ from ..utils.rest_api import ( detail_route, status, ) -from ..utils.views import APIView, PDFView +from ..utils.views import APIView from .access_permissions import UserAccessPermissions from .models import Group, User -from .pdf import users_passwords_to_pdf, users_to_pdf from .serializers import GroupSerializer @@ -251,38 +249,3 @@ class SetPasswordView(APIView): else: raise ValidationError({'detail': _('Old password does not match.')}) 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) diff --git a/openslides/utils/pdf.py b/openslides/utils/pdf.py deleted file mode 100644 index 921f31002..000000000 --- a/openslides/utils/pdf.py +++ /dev/null @@ -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() diff --git a/openslides/utils/views.py b/openslides/utils/views.py index a29ee3a84..bb0309dd0 100644 --- a/openslides/utils/views.py +++ b/openslides/utils/views.py @@ -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.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.views import APIView as _APIView -from .pdf import firstPage, laterPages - 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: """ Adds the csrf cookie to the response. @@ -52,76 +17,6 @@ class CSRFMixin: 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): """ The Django Rest framework APIView with improvements for OpenSlides. diff --git a/requirements_production.txt b/requirements_production.txt index fec42dad3..06c105bfd 100644 --- a/requirements_production.txt +++ b/requirements_production.txt @@ -1,13 +1,9 @@ # Requirements for OpenSlides in production in alphabetical order Django>=1.10.1,<1.11 -beautifulsoup4>=4.5,<4.6 channels>=0.15,<1.0 djangorestframework>=3.4,<3.5 -html5lib>=0.99,<1.0 jsonfield>=0.9.19,<1.1 -natsort>=3.2,<5.1 PyPDF2>=1.25.0,<1.27 -reportlab>=3.0,<3.4 roman>=2.0,<2.1 setuptools>=18.5,<28.0 twisted>=16.2,<16.4 diff --git a/tests/integration/agenda/test_views.py b/tests/integration/agenda/test_views.py deleted file mode 100644 index b0e9d0acd..000000000 --- a/tests/integration/agenda/test_views.py +++ /dev/null @@ -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) diff --git a/tests/integration/assignments/test_views.py b/tests/integration/assignments/test_views.py deleted file mode 100644 index a23775fcf..000000000 --- a/tests/integration/assignments/test_views.py +++ /dev/null @@ -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) diff --git a/tests/integration/motions/test_pdf.py b/tests/integration/motions/test_pdf.py deleted file mode 100644 index 87c3ffd11..000000000 --- a/tests/integration/motions/test_pdf.py +++ /dev/null @@ -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) diff --git a/tests/old/motions/test_pdf.py b/tests/old/motions/test_pdf.py deleted file mode 100644 index 322be02de..000000000 --- a/tests/old/motions/test_pdf.py +++ /dev/null @@ -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='
  • Element 1 aKaesieze6mahR2ielie' - '
    • Subelement 1 rel0liiGh0bi3ree6Jei
    • ' - '
    • Subelement 2 rel0liiGh0bi3ree6Jei
  • ' - '
  • Element 2 rel0liiGh0bi3ree6Jei
') - 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) diff --git a/tests/unit/utils/test_views.py b/tests/unit/utils/test_views.py index 6419d40c2..a47e2914e 100644 --- a/tests/unit/utils/test_views.py +++ b/tests/unit/utils/test_views.py @@ -1,57 +1,9 @@ from unittest import TestCase -from unittest.mock import MagicMock, patch +from unittest.mock import patch 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): def test_class_creation(self): """