#!/usr/bin/env python # -*- coding: utf-8 -*- """ openslides.application.views ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Views for the application app. :copyright: 2011 by the OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ from __future__ import with_statement import csv import utils.csv_ext import os from django.shortcuts import redirect from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User, Group from django.core.urlresolvers import reverse from django.utils.translation import ugettext as _ from django.utils.translation import ungettext from django.db import transaction from django.views.generic.base import RedirectView from reportlab.lib import colors from reportlab.lib.units import cm from reportlab.platypus import PageBreak, Paragraph, Spacer, Table, TableStyle from config.models import config from settings import SITE_ROOT from utils.pdf import stylesheet from utils.views import PDFView from agenda.models import Item from application.models import Application, AVersion, ApplicationPoll from application.forms import ( ApplicationForm, ApplicationManagerForm, ApplicationImportForm, ConfigForm, ) from participant.models import Profile from poll.views import PollFormView from utils.utils import template, permission_required, \ render_to_forbitten, del_confirm_form, gen_confirm_form from utils.views import FormView from utils.template import Tab from participant.api import gen_username, gen_password @permission_required('application.can_see_application') @template('application/overview.html') def overview(request): """ View all applications """ query = Application.objects if 'number' in request.GET: query = query.filter(number=None) if 'status' in request.GET: if 'statusvalue' in request.GET and 'on' in request.GET['status']: query = query.filter(status__iexact=request.GET['statusvalue']) try: sort = request.GET['sort'] if sort in ['number', 'supporter', 'status', 'submitter', \ 'aversion__time', 'aversion__title']: query = query.order_by(sort) except KeyError: query = query.order_by("number") if 'reverse' in request.GET: query = query.reverse() if 'needsup' in request.GET: applications = [] for application in query.all(): if not application.enough_supporters: applications.append(application) else: applications = query.all() return { 'applications': applications, 'min_supporters': int(config['application_min_supporters']), } @permission_required('application.can_see_application') @template('application/view.html') def view(request, application_id, newest=False): """ View one application. """ application = Application.objects.get(pk=application_id) if newest: version = application.last_version else: version = application.public_version revisions = application.versions actions = application.get_allowed_actions(user=request.user) return { 'application': application, 'revisions': revisions, 'actions': actions, 'min_supporters': int(config['application_min_supporters']), 'version': version, #'results': application.results } @login_required @template('application/edit.html') def edit(request, application_id=None): """ View a form to edit or create a application. """ if request.user.has_perm('application.can_manage_application'): is_manager = True else: is_manager = False if not is_manager \ and not request.user.has_perm('application.can_create_application'): messages.error(request, _("You have not the necessary rights to create or edit applications.")) return redirect(reverse('application_overview')) if application_id is not None: application = Application.objects.get(id=application_id) if not request.user == application.submitter and not is_manager: messages.error(request, _("You can not edit this application. You are not the submitter.")) return redirect(reverse('application_view', args=[application.id])) else: application = None if request.method == 'POST': dataform = ApplicationForm(request.POST, prefix="data") valid = dataform.is_valid() if is_manager: managerform = ApplicationManagerForm(request.POST, \ instance=application, \ prefix="manager") valid = valid and managerform.is_valid() else: managerform = None if valid: del_supporters = True original_supporters = [] if is_manager: if application: for s in application.supporter.all(): original_supporters.append(s) application = managerform.save() elif application_id is None: application = Application(submitter=request.user) application.title = dataform.cleaned_data['title'] application.text = dataform.cleaned_data['text'] application.reason = dataform.cleaned_data['reason'] application.save(request.user, trivial_change=dataform.cleaned_data['trivial_change']) if is_manager: # log added supporters supporters_added = [] for s in application.supporter.all(): if s not in original_supporters: try: supporters_added.append(unicode(s.profile)) except Profile.DoesNotExist: pass if len(supporters_added) > 0: log_added = ", ".join(supporters_added) application.writelog(_("Supporter: +%s") % log_added, request.user) # log removed supporters supporters_removed = [] for s in original_supporters: if s not in application.supporter.all(): try: supporters_removed.append(unicode(s.profile)) except Profile.DoesNotExist: pass if len(supporters_removed) > 0: log_removed = ", ".join(supporters_removed) application.writelog(_("Supporter: -%s") % log_removed, request.user) if application_id is None: messages.success(request, _('New application was successfully created.')) else: messages.success(request, _('Application was successfully modified.')) if not 'apply' in request.POST: return redirect(reverse('application_view', args=[application.id])) if application_id is None: return redirect(reverse('application_edit', args=[application.id])) else: messages.error(request, _('Please check the form for errors.')) else: if application_id is None: initial = {'text': config['application_preamble']} else: if application.status == "pub" and application.supporter.count() > 0: if request.user.has_perm('application.can_manage_application'): messages.warning(request, _("Attention: Do you really want to edit this application? The supporters will not be removed automatically because you can manage applications. Please check if the supports are valid after your changing!")) else: messages.warning(request, _("Attention: Do you really want to edit this application? All %s supporters will be removed! Try to convince the supporters again.") % application.supporter.count() ) initial = {'title': application.title, 'text': application.text, 'reason': application.reason} dataform = ApplicationForm(initial=initial, prefix="data") if is_manager: if application_id is None: initial = {'submitter': str(request.user.id)} else: initial = {} managerform = ApplicationManagerForm(initial=initial, \ instance=application, \ prefix="manager") else: managerform = None return { 'form': dataform, 'managerform': managerform, 'application': application, } @login_required @template('application/view.html') def delete(request, application_id): """ delete a application. """ application = Application.objects.get(id=application_id) if not 'delete' in application.get_allowed_actions(user=request.user): messages.error(request, _("You can not delete application %s.") % application) else: if request.method == 'POST': try: title = str(application) application.delete() messages.success(request, _("Application %s was successfully deleted.") % title) except NameError, name: messages.error(request, name) else: del_confirm_form(request, application) return redirect(reverse('application_overview')) @permission_required('application.can_manage_application') @template('application/view.html') def set_number(request, application_id): """ set a number for an application. """ try: Application.objects.get(pk=application_id).set_number(user=request.user) messages.success(request, _("Application number was successfully set.")) except Application.DoesNotExist: pass except NameError: pass return redirect(reverse('application_view', args=[application_id])) @permission_required('application.can_manage_application') @template('application/view.html') def permit(request, application_id): """ permit an application. """ try: Application.objects.get(pk=application_id).permit(user=request.user) messages.success(request, _("Application was successfully permitted.")) except Application.DoesNotExist: pass return redirect(reverse('application_view', args=[application_id])) @permission_required('application.can_manage_application') @template('application/view.html') def notpermit(request, application_id): """ reject (not permit) an application. """ try: Application.objects.get(pk=application_id).notpermit(user=request.user) messages.success(request, _("Application was successfully rejected.")) except Application.DoesNotExist: pass return redirect(reverse('application_view', args=[application_id])) @template('application/view.html') def set_status(request, application_id=None, status=None): """ set a status of an application. """ try: if status is not None: application = Application.objects.get(pk=application_id) application.set_status(user=request.user, status=status) messages.success(request, _("Application status was set to: %s.") % application.get_status_display()) except Application.DoesNotExist: pass return redirect(reverse('application_view', args=[application_id])) @permission_required('application.can_manage_application') @template('application/view.html') def reset(request, application_id): """ reset an application. """ try: Application.objects.get(pk=application_id).reset(user=request.user) messages.success(request, _("Application status was reset.") ) except Application.DoesNotExist: pass return redirect(reverse('application_view', args=[application_id])) @permission_required('application.can_support_application') @template('application/view.html') def support(request, application_id): """ support an application. """ try: Application.objects.get(pk=application_id).support(user=request.user) messages.success(request, _("You have support the application successfully.") ) except Application.DoesNotExist: pass return redirect(reverse('application_view', args=[application_id])) @permission_required('application.can_support_application') @template('application/view.html') def unsupport(request, application_id): """ unsupport an application. """ try: Application.objects.get(pk=application_id).unsupport(user=request.user) messages.success(request, _("You have unsupport the application successfully.") ) except Application.DoesNotExist: pass return redirect(reverse('application_view', args=[application_id])) @permission_required('application.can_manage_application') def set_active(request, application_id): application = Application.objects.get(pk=application_id) application.set_active() return redirect(reverse('application_view', args=[application_id])) @permission_required('application.can_manage_application') @template('application/view.html') def gen_poll(request, application_id): """ gen a poll for this application. """ try: poll = Application.objects.get(pk=application_id).gen_poll(user=request.user) messages.success(request, _("New vote was successfully created.") ) except Application.DoesNotExist: pass # TODO: do not call poll after this excaption return redirect(reverse('application_poll_view', args=[poll.id])) @permission_required('application.can_manage_application') def delete_poll(request, poll_id): """ delete a poll from this application """ poll = ApplicationPoll.objects.get(pk=poll_id) application = poll.application count = application.polls.filter(id__lte=poll_id).count() if request.method == 'POST': poll.delete() messages.success(request, _('Poll was successfully deleted.')) else: del_confirm_form(request, poll, name=_("the %s. poll") % count, delete_link=reverse('application_poll_delete', args=[poll_id])) return redirect(reverse('application_view', args=[application.id])) class ViewPoll(PollFormView): permission_required = 'application.can_manage_application' poll_class = ApplicationPoll template_name = 'application/poll_view.html' def get_context_data(self, **kwargs): context = super(ViewPoll, self).get_context_data(**kwargs) self.application = self.poll.get_application() context['application'] = self.application context['ballot'] = self.poll.get_ballot() return context def get_success_url(self): if not 'apply' in self.request.POST: return reverse('application_view', args=[self.poll.application.id]) return '' @permission_required('application.can_manage_application') def permit_version(request, aversion_id): aversion = AVersion.objects.get(pk=aversion_id) application = aversion.application if request.method == 'POST': application.accept_version(aversion) messages.success(request, _("Version %s accepted.") % (aversion.aid)) else: gen_confirm_form(request, _('Do you really want to permit version %s?') % aversion.aid, reverse('application_version_permit', args=[aversion.id])) return redirect(reverse('application_view', args=[application.id])) @permission_required('application.can_manage_application') def reject_version(request, aversion_id): aversion = AVersion.objects.get(pk=aversion_id) application = aversion.application if request.method == 'POST': if application.reject_version(aversion): messages.success(request, _("Version %s rejected.") % (aversion.aid)) else: messages.error(request, _("ERROR by rejecting the version.") ) else: gen_confirm_form(request, _('Do you really want to reject version %s?') % aversion.aid, reverse('application_version_reject', args=[aversion.id])) return redirect(reverse('application_view', args=[application.id])) @permission_required('application.can_manage_application') @template('application/import.html') def application_import(request): try: request.user.profile messages.error(request, _('The import function is available for the superuser (without user profile) only.')) return redirect(reverse('application_overview')) except Profile.DoesNotExist: pass except AttributeError: # AnonymousUser pass if request.method == 'POST': form = ApplicationImportForm(request.POST, request.FILES) if form.is_valid(): import_permitted = form.cleaned_data['import_permitted'] try: # check for valid encoding (will raise UnicodeDecodeError if not) request.FILES['csvfile'].read().decode('utf-8') request.FILES['csvfile'].seek(0) users_generated = 0 applications_generated = 0 applications_modified = 0 with transaction.commit_on_success(): dialect = csv.Sniffer().sniff(request.FILES['csvfile'].readline()) dialect = utils.csv_ext.patchup(dialect) request.FILES['csvfile'].seek(0) for (lno, line) in enumerate(csv.reader(request.FILES['csvfile'], dialect=dialect)): # basic input verification if lno < 1: continue try: (number, title, text, reason, first_name, last_name) = line[:6] except ValueError: messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1)) continue form = ApplicationForm({'title': title, 'text': text, 'reason': reason}) if not form.is_valid(): messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1)) continue if number: try: number = abs(long(number)) if number < 1: messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1)) continue except ValueError: messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1)) continue # fetch existing users or create new users as needed try: user = User.objects.get(first_name=first_name, last_name=last_name) except User.DoesNotExist: user = None if user is None: user = User() user.last_name = last_name user.first_name = first_name user.username = gen_username(first_name, last_name) user.save() profile = Profile() profile.user = user profile.group = '' profile.committee = '' profile.gender = 'none' profile.type = 'guest' profile.firstpassword = gen_password() profile.user.set_password(profile.firstpassword) profile.save() users_generated += 1 # create / modify the application application = None if number: try: application = Application.objects.get(number=number) applications_modified += 1 except Application.DoesNotExist: application = None if application is None: application = Application(submitter=user) if number: application.number = number applications_generated += 1 application.title = form.cleaned_data['title'] application.text = form.cleaned_data['text'] application.reason = form.cleaned_data['reason'] if import_permitted: application.status = 'per' application.save(user, trivial_change=True) if applications_generated: messages.success(request, ungettext('%d application was successfully imported.', '%d applications were successfully imported.', applications_generated) % applications_generated) if applications_modified: messages.success(request, ungettext('%d application was successfully modified.', '%d applications were successfully modified.', applications_modified) % applications_modified) if users_generated: messages.success(request, ungettext('%d new user was added.', '%d new users were added.', users_generated) % users_generated) return redirect(reverse('application_overview')) except csv.Error: message.error(request, _('Import aborted because of severe errors in the input file.')) except UnicodeDecodeError: messages.error(request, _('Import file has wrong character encoding, only UTF-8 is supported!')) else: messages.error(request, _('Please check the form for errors.')) else: messages.warning(request, _("Attention: Existing applications will be modified if you import new applications with the same number.")) messages.warning(request, _("Attention: Importing an application without a number multiple times will create duplicates.")) form = ApplicationImportForm() return { 'form': form, } class ApplicationPDF(PDFView): permission_required = 'application.can_manage_application' top_space = 0 def get_filename(self): application_id = self.kwargs['application_id'] if application_id is None: filename = _("Applications") else: application = Application.objects.get(id=application_id) if application.number: number = application.number else: number = "" filename = u'%s%s' % (_("Application"), str(number)) return filename def append_to_pdf(self, story): application_id = self.kwargs['application_id'] if application_id is None: #print all applications title = config["application_pdf_title"] story.append(Paragraph(title, stylesheet['Heading1'])) preamble = config["application_pdf_preamble"] if preamble: story.append(Paragraph("%s" % preamble.replace('\r\n','
'), stylesheet['Paragraph'])) story.append(Spacer(0,0.75*cm)) # List of applications for application in Application.objects.order_by('number'): if application.number: story.append(Paragraph(_("Application No.")+" %s: %s" % (application.number, application.title), stylesheet['Heading3'])) else: story.append(Paragraph(_("Application No.")+"   : %s" % (application.title), stylesheet['Heading3'])) # Applications details (each application on single page) for application in Application.objects.order_by('number'): story.append(PageBreak()) story = self.get_application(application, story) else: # print selected application application = Application.objects.get(id=application_id) story = self.get_application(application, story) def get_application(self, application, story): # application number if application.number: story.append(Paragraph(_("Application No.")+" %s" % application.number, stylesheet['Heading1'])) else: story.append(Paragraph(_("Application No."), stylesheet['Heading1'])) # submitter cell1a = [] cell1a.append(Spacer(0,0.2*cm)) cell1a.append(Paragraph("%s:" % _("Submitter"), stylesheet['Heading4'])) cell1b = [] cell1b.append(Spacer(0,0.2*cm)) if application.status == "pub": cell1b.append(Paragraph("__________________________________________",stylesheet['Signaturefield'])) cell1b.append(Spacer(0,0.1*cm)) cell1b.append(Paragraph("       "+unicode(application.submitter.profile), stylesheet['Small'])) cell1b.append(Spacer(0,0.2*cm)) else: cell1b.append(Paragraph(unicode(application.submitter.profile), stylesheet['Normal'])) # supporters cell2a = [] cell2a.append(Paragraph("%s:" % _("Supporters"), stylesheet['Heading4'])) cell2b = [] for s in application.supporter.all(): cell2b.append(Paragraph(".  %s" % unicode(s.profile), stylesheet['Signaturefield'])) if application.status == "pub": for x in range(0,application.missing_supporters): cell2b.append(Paragraph(".  __________________________________________",stylesheet['Signaturefield'])) cell2b.append(Spacer(0,0.2*cm)) # status note = "" for n in application.notes: note += "%s " % unicode(n) cell3a = [] cell3a.append(Paragraph("%s:" % _("Status"), stylesheet['Heading4'])) cell3b = [] if note != "": if application.status == "pub": cell3b.append(Paragraph(note, stylesheet['Normal'])) else: cell3b.append(Paragraph("%s | %s" % (application.get_status_display(), note), stylesheet['Normal'])) else: cell3b.append(Paragraph("%s" % application.get_status_display(), stylesheet['Normal'])) # table data = [] data.append([cell1a,cell1b]) data.append([cell2a,cell2b]) data.append([cell3a,cell3b]) poll_results = application.get_poll_results() # voting results if poll_results: cell4a = [] cell4a.append(Paragraph("%s:" % _("Vote results"), stylesheet['Heading4'])) cell4b = [] ballotcounter = 0 for result in poll_results: ballotcounter += 1 if len(poll_results) > 1: cell4b.append(Paragraph("%s. %s" % (ballotcounter, _("Vote")), stylesheet['Bold'])) cell4b.append(Paragraph("%s: %s
%s: %s
%s: %s
%s: %s
%s: %s" % (_("Yes"), result[0], _("No"), result[1], _("Abstention"), result[2], _("Invalid"), result[3], _("Votes cast"), result[4]), stylesheet['Normal'])) cell4b.append(Spacer(0,0.2*cm)) data.append([cell4a,cell4b]) t=Table(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)) # title story.append(Paragraph(application.title, stylesheet['Heading3'])) # text story.append(Paragraph("%s" % application.text.replace('\r\n','
'), stylesheet['Paragraph'])) # reason story.append(Paragraph(_("Reason")+":", stylesheet['Heading3'])) story.append(Paragraph("%s" % application.reason.replace('\r\n','
'), stylesheet['Paragraph'])) return story class ApplicationPollPDF(PDFView): permission_required = 'application.can_manage_application' top_space = 0 def get(self, request, *args, **kwargs): self.poll = ApplicationPoll.objects.get(id=self.kwargs['poll_id']) return super(ApplicationPollPDF, self).get(request, *args, **kwargs) def get_filename(self): filename = u'%s%s_%s' % (_("Application"), str(self.poll.application.number), _("Poll")) return filename def append_to_pdf(self, story): imgpath = os.path.join(SITE_ROOT, 'static/images/circle.png') circle = "  " % imgpath cell = [] cell.append(Spacer(0,0.8*cm)) cell.append(Paragraph(_("Application No.")+" "+str(self.poll.application.number), stylesheet['Ballot_title'])) cell.append(Paragraph(self.poll.application.title, stylesheet['Ballot_subtitle'])) cell.append(Paragraph(str(self.poll.get_ballot())+". "+_("Vote"), stylesheet['Ballot_description'])) cell.append(Spacer(0,0.5*cm)) cell.append(Paragraph(circle+_("Yes"), stylesheet['Ballot_option'])) cell.append(Paragraph(circle+_("No"), stylesheet['Ballot_option'])) cell.append(Paragraph(circle+_("Abstention"), stylesheet['Ballot_option'])) data= [] number = 1 # get ballot papers config values ballot_papers_selection = config["application_pdf_ballot_papers_selection"] ballot_papers_number = config["application_pdf_ballot_papers_number"] # set number of ballot papers if ballot_papers_selection == "1": number = User.objects.filter(profile__type__iexact="delegate").count() if ballot_papers_selection == "2": number = int(User.objects.count() - 1) if ballot_papers_selection == "0": number = int(ballot_papers_number) # print ballot papers if number > 0: for user in xrange(number/2): data.append([cell,cell]) rest = number % 2 if rest: data.append([cell,'']) t=Table(data, 10.5*cm, 7.42*cm) t.setStyle(TableStyle([ ('GRID', (0,0), (-1,-1), 0.25, colors.grey), ('VALIGN', (0,0), (-1,-1), 'TOP'), ])) story.append(t) class Config(FormView): permission_required = 'config.can_manage_config' form_class = ConfigForm template_name = 'application/config.html' def get_initial(self): return { 'application_min_supporters': config['application_min_supporters'], 'application_preamble': config['application_preamble'], 'application_pdf_ballot_papers_selection': config['application_pdf_ballot_papers_selection'], 'application_pdf_ballot_papers_number': config['application_pdf_ballot_papers_number'], 'application_pdf_title': config['application_pdf_title'], 'application_pdf_preamble': config['application_pdf_preamble'], } def form_valid(self, form): config['application_min_supporters'] = form.cleaned_data['application_min_supporters'] config['application_preamble'] = form.cleaned_data['application_preamble'] config['application_pdf_ballot_papers_selection'] = form.cleaned_data['application_pdf_ballot_papers_selection'] config['application_pdf_ballot_papers_number'] = form.cleaned_data['application_pdf_ballot_papers_number'] config['application_pdf_title'] = form.cleaned_data['application_pdf_title'] config['application_pdf_preamble'] = form.cleaned_data['application_pdf_preamble'] messages.success(self.request, _('Application settings successfully saved.')) return super(Config, self).form_valid(form) def register_tab(request): selected = True if request.path.startswith('/application/') else False return Tab( title=_('Applications'), url=reverse('application_overview'), permission=request.user.has_perm('application.can_see_application') or request.user.has_perm('application.can_support_application') or request.user.has_perm('application.can_support_application') or request.user.has_perm('application.can_manage_application'), selected=selected, )