from cgi import escape from django.contrib import messages from django.core.urlresolvers import reverse 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 (PageBreak, Paragraph, SimpleDocTemplate, Spacer, LongTable, Table, TableStyle) from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView from openslides.config.api import config from openslides.users.models import Group, User # TODO: remove this from openslides.poll.views import PollFormView from openslides.utils.pdf import stylesheet from openslides.utils.rest_api import ModelViewSet from openslides.utils.utils import html_strong from openslides.utils.views import (CreateView, DeleteView, DetailView, ListView, PDFView, QuestionView, RedirectView, SingleObjectMixin, UpdateView) from .forms import AssignmentForm, AssignmentRunForm from .models import Assignment, AssignmentPoll from .serializers import AssignmentFullSerializer, AssignmentShortSerializer class AssignmentListView(ListView): """ Lists all assignments. """ required_permission = 'assignments.can_see' model = Assignment class AssignmentDetail(DetailView): """ Shows one assignment. """ # TODO: use another view as 'run form' when updating this to angular required_permission = 'assignments.can_see' model = Assignment form_class = AssignmentRunForm def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) assignment = self.get_object() if self.request.method == 'POST': context['form'] = self.form_class(self.request.POST) else: context['form'] = self.form_class() polls = assignment.polls.all() if not self.request.user.has_perm('assignments.can_manage'): polls = polls.filter(published=True) vote_results = assignment.vote_results(only_published=True) else: polls = self.get_object().polls.all() vote_results = assignment.vote_results(only_published=False) context['polls'] = polls context['vote_results'] = vote_results context['blocked_candidates'] = assignment.blocked context['user_is_candidate'] = assignment.is_candidate(self.request.user) return context def post(self, *args, **kwargs): if self.request.user.has_perm('assignments.can_nominate_other'): assignment = self.get_object() form = self.form_class(self.request.POST) if form.is_valid(): user = form.cleaned_data['candidate'] if (assignment.phase == assignment.PHASE_SEARCH or self.request.user.has_perm('assignments.can_manage')): if (assignment.is_blocked(user) and not self.request.user.has_perm('assignments.can_manage')): messages.error( self.request, _("User %s does not want to be an candidate") % user) elif assignment.is_elected(user): messages.error( self.request, _("User %s is already elected") % html_strong(user)) elif assignment.is_candidate(user): messages.error( self.request, _("User %s is already an candidate") % html_strong(user)) else: assignment.set_candidate(user) messages.success( self.request, _("User %s was nominated successfully.") % html_strong(user)) else: messages.error( self.request, _("You can not add candidates to this assignment")) return super(AssignmentDetail, self).get(*args, **kwargs) class AssignmentCreateView(CreateView): required_permission = 'assignments.can_manage' model = Assignment form_class = AssignmentForm class AssignmentUpdateView(UpdateView): required_permission = 'assignments.can_manage' model = Assignment form_class = AssignmentForm class AssignmentDeleteView(DeleteView): required_permission = 'assignments.can_manage' model = Assignment success_url_name = 'assignment_list' class AssignmentSetPhaseView(SingleObjectMixin, RedirectView): required_permission = 'assignments.can_manage' model = Assignment url_name = 'assignment_detail' def pre_redirect(self, *args, **kwargs): phase = int(kwargs.get('phase')) assignment = self.get_object() try: assignment.set_phase(phase) except ValueError as e: messages.error(self.request, e) else: assignment.save() messages.success( self.request, _('Election status was set to: %s.') % html_strong(assignment.get_phase_display())) class AssignmentCandidateView(SingleObjectMixin, RedirectView): required_permission = 'assignments.can_nominate_self' model = Assignment url_name = 'assignment_detail' def pre_redirect(self, *args, **kwargs): assignment = self.get_object() if (assignment.phase == assignment.PHASE_SEARCH or self.request.user.has_perm('assignments.can_manage')): user = self.request.user if assignment.is_elected(user): messages.error( self.request, _("You are already elected")) elif assignment.is_candidate(user): messages.error( self.request, _("You are already an candidate")) else: assignment.set_candidate(user) messages.success( self.request, _("You were nominated successfully.")) else: messages.error( self.request, _("You can not candidate to this assignment")) class AssignmentDeleteCandidateshipView(SingleObjectMixin, RedirectView): required_permission = None # Any user can withdraw his candidature model = Assignment url_name = 'assignment_detail' def pre_redirect(self, *args, **kwargs): assignment = self.get_object() if (assignment.phase == assignment.PHASE_SEARCH or self.request.user.has_perm('assignments.can_manage')): user = self.request.user assignment.set_blocked(user) messages.success(self.request, _( 'You have withdrawn your candidature successfully. ' 'You can not be nominated by other participants anymore.')) else: messages.error(self.request, _('The candidate list is already closed.')) class AssignmentDeleteCandidateshipOtherView(SingleObjectMixin, QuestionView): required_permission = 'assignments.can_manage' model = Assignment def get_question_message(self): self.user = User.objects.get(pk=self.kwargs.get('user_pk')) assignment = self.get_object() if assignment.is_blocked: question = _("Do you really want to unblock %s for the election?") % html_strong(self.user) else: question = _("Do you really want to withdraw %s from the election?") % html_strong(self.user) return question def on_clicked_yes(self): self.user = User.objects.get(pk=self.kwargs.get('user_pk')) assignment = self.get_object() if not assignment.is_elected(self.user): assignment.delete_related_user(self.user) self.error = False else: self.error = _("User %s is already elected") % html_strong(self.user) def create_final_message(self): if self.error: messages.error(self.request, self.error) else: messages.success(self.request, self.get_final_message()) def get_final_message(self): return _("Candidate %s was withdrawn successfully.") % html_strong(self.user) class AssignmentViewSet(ModelViewSet): """ API endpoint to list, retrieve, create, update and destroy assignments. """ queryset = Assignment.objects.all() def check_permissions(self, request): """ Calls self.permission_denied() if the requesting user has not the permission to see assignments and in case of create, update or destroy requests the permission to manage assignments. """ if (not request.user.has_perm('assignments.can_see') or (self.action in ('create', 'update', 'destroy') and not request.user.has_perm('assignments.can_manage'))): self.permission_denied(request) def get_serializer_class(self): """ Returns different serializer classes with respect to users permissions. """ if self.request.user.has_perm('assignments.can_manage'): serializer_class = AssignmentFullSerializer else: serializer_class = AssignmentShortSerializer return serializer_class class PollCreateView(SingleObjectMixin, RedirectView): required_permission = 'assignments.can_manage' model = Assignment url_name = 'assignment_detail' def pre_redirect(self, *args, **kwargs): self.get_object().create_poll() messages.success(self.request, _("New ballot was successfully created.")) class PollUpdateView(PollFormView): required_permission = 'assignments.can_manage' poll_class = AssignmentPoll template_name = 'assignments/assignmentpoll_form.html' def get_context_data(self, **kwargs): context = super(PollUpdateView, self).get_context_data(**kwargs) self.assignment = self.poll.get_assignment() context['assignment'] = self.assignment context['poll'] = self.poll context['polls'] = self.assignment.polls.all() context['ballotnumber'] = self.poll.get_ballot() return context def get_success_url(self): if 'apply' not in self.request.POST: return_url = reverse('assignment_detail', args=[self.poll.assignment.id]) else: return_url = '' return return_url class SetPublishPollView(SingleObjectMixin, RedirectView): required_permission = 'assignments.can_manage' model = AssignmentPoll url_name = 'assignment_detail' allow_ajax = True publish = False def get_ajax_context(self, **context): return super().get_ajax_context( published=self.object.published, **context) def pre_redirect(self, *args, **kwargs): poll = self.get_object() poll.set_published(kwargs['publish']) class SetElectedView(SingleObjectMixin, RedirectView): required_permission = 'assignments.can_manage' model = Assignment url_name = 'assignment_detail' allow_ajax = True def pre_redirect(self, *args, **kwargs): self.person = User.objects.get(pk=kwargs['user_id']) self.elected = kwargs['elected'] # TODO: un-elect users if self.elected is False self.get_object().set_elected(self.person) def get_ajax_context(self, **kwargs): if self.elected: link = reverse('assignment_user_not_elected', args=[self.get_object().id, self.person.person_id]) text = _('not elected') else: link = reverse('assignment_user_elected', args=[self.get_object().id, self.person.person_id]) text = _('elected') return {'elected': self.elected, 'link': link, 'text': text} class AssignmentPollDeleteView(DeleteView): """ Delete an assignment poll object. """ required_permission = 'assignments.can_manage' model = AssignmentPoll def pre_redirect(self, request, *args, **kwargs): self.set_assignment() super().pre_redirect(request, *args, **kwargs) def pre_post_redirect(self, request, *args, **kwargs): self.set_assignment() super().pre_post_redirect(request, *args, **kwargs) def set_assignment(self): self.assignment = self.get_object().assignment def get_redirect_url(self, **kwargs): return reverse('assignment_detail', args=[self.assignment.id]) def get_final_message(self): return _('Ballot was successfully deleted.') 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' % ( _("Assignment"), 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["assignment_pdf_title"]) story.append(Paragraph(title, stylesheet['Heading1'])) preamble = escape(config["assignment_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 assignments 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.iteritems(): row = [] candidate_string = candidate.clean_name if candidate in elected_candidates: candidate_string = "* " + candidate_string if candidate.name_suffix and length < 20: candidate_string += "\n(%s)" % candidate.name_suffix 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 '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.print_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.print_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.print_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 CreateRelatedAgendaItemView(_CreateRelatedAgendaItemView): """ View to create and agenda item for an assignment. """ model = Assignment 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["assignment_pdf_ballot_papers_selection"] ballot_papers_number = config["assignment_pdf_ballot_papers_number"] # set number of ballot papers if ballot_papers_selection == "NUMBER_OF_DELEGATES": try: if Group.objects.get(pk=3): number = User.objects.filter(groups__pk=3).count() except Group.DoesNotExist: number = 0 elif ballot_papers_selection == "NUMBER_OF_ALL_PARTICIPANTS": number = int(User.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 or Yes) if self.poll.yesnoabstain: # YesNoAbstain 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'])) else: 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': _("Abstention")}, 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.clean_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)