#!/usr/bin/env python # -*- coding: utf-8 -*- """ openslides.application.views ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Views for the application app. :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ # for python 2.5 support from __future__ import with_statement import csv import os try: from urlparse import parse_qs except ImportError: # python <= 2.5 from cgi import parse_qs from reportlab.lib import colors from reportlab.lib.units import cm from reportlab.platypus import (SimpleDocTemplate, PageBreak, Paragraph, Spacer, Table, TableStyle) from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User from django.core.context_processors import csrf from django.core.urlresolvers import reverse from django.db import transaction from django.shortcuts import redirect from django.utils.translation import ugettext as _, ungettext from openslides.utils import csv_ext from openslides.utils.pdf import stylesheet from openslides.utils.template import Tab from openslides.utils.utils import (template, permission_required, del_confirm_form, gen_confirm_form) from openslides.utils.views import PDFView, RedirectView, DeleteView, FormView from openslides.config.models import config from openslides.projector.projector import Widget from openslides.poll.views import PollFormView from openslides.participant.api import gen_username, gen_password from openslides.participant.models import Profile from openslides.agenda.models import Item from openslides.application.models import Application, AVersion, ApplicationPoll from openslides.application.forms import (ApplicationForm, ApplicationFormTrivialChanges, ApplicationManagerForm, ApplicationManagerFormSupporter, ApplicationImportForm, ConfigForm) @permission_required('application.can_see_application') @template('application/overview.html') def overview(request): """ View all applications """ try: sortfilter = parse_qs(request.COOKIES['votecollector_sortfilter']) for value in sortfilter: sortfilter[value] = sortfilter[value][0] except KeyError: sortfilter = {} for value in [u'sort', u'reverse', u'number', u'status', u'needsup', u'statusvalue']: if value in request.REQUEST: if request.REQUEST[value] == '0': try: del sortfilter[value] except KeyError: pass else: sortfilter[value] = request.REQUEST[value] query = Application.objects.all() if 'number' in sortfilter: query = query.filter(number=None) if 'status' in sortfilter: if 'statusvalue' in sortfilter and 'on' in sortfilter['status']: query = query.filter(status__iexact=sortfilter['statusvalue']) if 'sort' in sortfilter: if sortfilter['sort'] == 'title': sort = 'aversion__title' elif sortfilter['sort'] == 'time': sort = 'aversion__time' else: sort = sortfilter['sort'] query = query.order_by(sort) if sort.startswith('aversion_'): # limit result to last version of an application query = query.filter(aversion__id__in=[x.last_version.id for x in Application.objects.all()]) if 'reverse' in sortfilter: query = query.reverse() # todo: rewrite this with a .filter() if 'needsup' in sortfilter: applications = [] for application in query.all(): if not application.enough_supporters: applications.append(application) else: applications = query if type(applications) is not list: applications = list(query.all()) # not the most efficient way to do this but 'get_allowed_actions' # is not callable from within djangos templates.. for (i, application) in enumerate(applications): try: applications[i] = { 'actions' : application.get_allowed_actions(request.user), 'application' : application } except: # todo: except what? applications[i] = { 'actions' : [], 'application' : application } 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 hasattr(application.submitter, 'user') or not request.user == application.submitter.user) \ 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])) actions = application.get_allowed_actions(user=request.user) else: application = None actions = None formclass = ApplicationFormTrivialChanges \ if config['application_allow_trivial_change'] and application_id \ else ApplicationForm managerformclass = ApplicationManagerFormSupporter \ if config['application_min_supporters'] \ else ApplicationManagerForm if request.method == 'POST': dataform = formclass(request.POST, prefix="data") valid = dataform.is_valid() if is_manager: managerform = managerformclass(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: # Edit application for s in application.supporter.all(): original_supporters.append(s) application = managerform.save(commit=False) elif application_id is None: application = Application(submitter=request.user.profile) application.title = dataform.cleaned_data['title'] application.text = dataform.cleaned_data['text'] application.reason = dataform.cleaned_data['reason'] try: trivial_change = config['application_allow_trivial_change'] \ and dataform.cleaned_data['trivial_change'] except KeyError: trivial_change = False application.save(request.user, trivial_change=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.exists(): 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 = formclass(initial=initial, prefix="data") if is_manager: if application_id is None: try: initial = {'submitter': request.user.profile.uid} except Profile.DoesNotExist: initial = {} else: initial = {'submitter': application.submitter.uid} managerform = managerformclass(initial=initial, instance=application, prefix="manager") else: managerform = None return { 'form': dataform, 'managerform': managerform, 'application': application, 'actions': actions, } @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 except NameError, e: messages.error(request, e) 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 except NameError, e: messages.error(request, e) 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 except NameError, e: messages.error(request, e) 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') @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() application.writelog(_("Poll deleted"), request.user) 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 ApplicationDelete(DeleteView): """ Delete one or more Applications. """ permission_required = 'application.can_manage_application' model = Application url = 'application_overview' def get_object(self): self.applications = [] if self.kwargs.get('application_id', None): try: return Application.objects.get(id=int(self.kwargs['application_id'])) except Application.DoesNotExist: return None if self.kwargs.get('application_ids', []): for appid in self.kwargs['application_ids']: try: self.applications.append(Application.objects.get(id=int(appid))) except Application.DoesNotExist: pass if self.applications: return self.applications[0] return None def pre_post_redirect(self, request, *args, **kwargs): self.object = self.get_object() if len(self.applications): for application in self.applications: if not 'delete' in application.get_allowed_actions(user=request.user): messages.error(request, _("You can not delete application %s.") % application) continue title = application.title application.delete(force=True) messages.success(request, _("Application %s was successfully deleted.") % title) elif self.object: if not 'delete' in self.object.get_allowed_actions(user=request.user): messages.error(request, _("You can not delete application %s.") % self.object) else: title = self.object.title self.object.delete(force=True) messages.success(request, _("Application %s was successfully deleted.") % title) else: messages.error(request, _("Invalid request")) def gen_confirm_form(self, request, message, url): formbase = '%s
' % (_("Yes"), _("No")) messages.warning(request, formbase) def confirm_form(self, request, object, item=None): self.object = self.get_object() if len(self.applications): self.gen_confirm_form(request, _('Do you really want to delete multiple applications?') % self.object.get_absolute_url('delete')) else: self.gen_confirm_form(request, _('Do you really want to delete %s?') % self.object, self.object.get_absolute_url('delete')) 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() context['actions'] = self.application.get_allowed_actions(user=self.request.user) return context def get_modelform_class(self): cls = super(ViewPoll, self).get_modelform_class() user = self.request.user class ViewPollFormClass(cls): def save(self, commit = True): instance = super(ViewPollFormClass, self).save(commit) application = instance.application application.writelog(_("Poll was updated"), user) return instance return ViewPollFormClass 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, user = request.user) 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, user = request.user): 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 admin (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 = 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 CreateAgendaItem(RedirectView): permission_required = 'agenda.can_manage_agenda' def pre_redirect(self, request, *args, **kwargs): self.application = Application.objects.get(pk=kwargs['application_id']) self.item = Item(related_sid=self.application.sid) self.item.save() def get_redirect_url(self, **kwargs): return reverse('item_overview') class ApplicationPDF(PDFView): permission_required = 'application.can_see_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','