#!/usr/bin/env python # -*- coding: utf-8 -*- """ openslides.participant.views ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Views for the participant 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 from urllib import urlencode try: from urlparse import parse_qs except ImportError: # python <= 2.5 grab it from cgi from cgi import parse_qs from reportlab.lib import colors from reportlab.lib.units import cm from reportlab.platypus import (SimpleDocTemplate, PageBreak, Paragraph, LongTable, Spacer, Table, TableStyle) from django.db import transaction from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User, Group from django.contrib.auth.forms import PasswordChangeForm from django.contrib.auth.views import login as django_login from django.core.urlresolvers import reverse 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, gen_confirm_form, ajax_request, decodedict, encodedict, delete_default_permissions, html_strong) from openslides.utils.views import FormView, PDFView from openslides.config.models import config from openslides.participant.models import Profile from openslides.participant.api import gen_username, gen_password from openslides.participant.forms import (UserNewForm, UserEditForm, ProfileForm, UsersettingsForm, UserImportForm, GroupForm, AdminPasswordChangeForm, ConfigForm) @permission_required('participant.can_see_participant') @template('participant/overview.html') def get_overview(request): """ Show all users. """ try: sortfilter = encodedict(parse_qs( request.COOKIES['participant_sortfilter'])) except KeyError: sortfilter = {} for value in [u'gender', u'group', u'type', u'committee', u'status', u'sort', u'reverse']: if value in request.REQUEST: if request.REQUEST[value] == '---': try: del sortfilter[value] except KeyError: pass else: sortfilter[value] = [request.REQUEST[value]] query = User.objects if 'gender' in sortfilter: query = query.filter(profile__gender__iexact=sortfilter['gender'][0]) if 'group' in sortfilter: query = query.filter(profile__group__iexact=sortfilter['group'][0]) if 'type' in sortfilter: query = query.filter(profile__type__iexact=sortfilter['type'][0]) if 'committee' in sortfilter: query = query. \ filter(profile__committee__iexact=sortfilter['committee'][0]) if 'status' in sortfilter: query = query.filter(is_active=sortfilter['status'][0]) if 'sort' in sortfilter: if sortfilter['sort'][0] in ['first_name', 'last_name', 'last_login']: query = query.order_by(sortfilter['sort'][0]) elif sortfilter['sort'][0] in ['group', 'type', 'committee', 'comment']: query = query.order_by('profile__%s' % sortfilter['sort'][0]) else: query = query.order_by('first_name') if 'reverse' in sortfilter: query = query.reverse() # list of filtered users (with profile) userlist = query.all() users = [] for user in userlist: try: user.get_profile() users.append(user) except Profile.DoesNotExist: pass # list of all existing users (with profile) allusers = [] for user in User.objects.all(): try: user.get_profile() allusers.append(user) except Profile.DoesNotExist: pass # quotient of selected users and all users if len(allusers) > 0: percent = float(len(users)) * 100 / float(len(allusers)) else: percent = 0 # list of all existing groups groups = [p['group'] for p in Profile.objects.values('group') \ .exclude(group='').distinct()] # list of all existing committees committees = [p['committee'] for p in Profile.objects.values('committee') \ .exclude(committee='').distinct()] return { 'users': users, 'allusers': allusers, 'percent': round(percent, 1), 'groups': groups, 'committees': committees, 'cookie': ['participant_sortfilter', urlencode(decodedict(sortfilter), doseq=True)], 'sortfilter': sortfilter, } @permission_required('participant.can_manage_participant') @template('participant/edit.html') def edit(request, user_id=None): """ View to create and edit users with profile. """ if user_id is not None: user = User.objects.get(id=user_id) else: user = None if request.method == 'POST': if user_id is None: userform = UserNewForm(request.POST, prefix="user") profileform = ProfileForm(request.POST, prefix="profile") else: userform = UserEditForm(request.POST, instance=user, prefix="user") profileform = ProfileForm(request.POST, instance=user.profile, prefix="profile") if userform.is_valid() and profileform.is_valid(): user = userform.save() if user_id is None: user.username = gen_username(user.first_name, user.last_name) user.save() profile = profileform.save(commit=False) profile.user = user if user_id is None: if not profile.firstpassword: profile.firstpassword = gen_password() profile.user.set_password(profile.firstpassword) profile.user.save() profile.save() if user_id is None: messages.success(request, _('New participant was successfully created.')) else: messages.success(request, _('Participant was successfully modified.')) if not 'apply' in request.POST: return redirect(reverse('user_overview')) if user_id is None: return redirect(reverse('user_edit', args=[user.id])) else: messages.error(request, _('Please check the form for errors.')) else: if user_id is None: userform = UserNewForm(prefix="user") profileform = ProfileForm(prefix="profile") else: userform = UserEditForm(instance=user, prefix="user") profileform = ProfileForm(instance=user.profile, prefix="profile") return { 'userform': userform, 'profileform': profileform, 'edituser': user, } @permission_required('participant.can_manage_participant') @template('confirm.html') def user_delete(request, user_id): """ Delete an user. """ user = User.objects.get(pk=user_id) if request.method == 'POST': user.delete() messages.success(request, _('Participant %s was successfully deleted.') % user) else: gen_confirm_form(request, _('Do you really want to delete %s?') % user, reverse('user_delete', args=[user_id])) return redirect(reverse('user_overview')) @permission_required('participant.can_manage_participant') @template('confirm.html') def user_set_status(request, user_id): """ Set the status of an user. """ try: user = User.objects.get(pk=user_id) if user.is_active: user.is_active = False else: user.is_active = True user.save() except User.DoesNotExist: messages.error(request, _('Participant ID %d does not exist.') % int(user_id)) return redirect(reverse('user_overview')) if request.is_ajax(): return ajax_request({'active': user.is_active}) # set success messages for page reload only (= not ajax request) if user.is_active: messages.success(request, _('%s is now present.') % user) else: messages.success(request, _('%s is now absent.') % user) return redirect(reverse('user_overview')) @permission_required('participant.can_manage_participant') @template('participant/group_overview.html') def get_group_overview(request): """ Show all groups. """ if config['system_enable_anonymous']: groups = Group.objects.all() else: groups = Group.objects.exclude(name='Anonymous') return { 'groups': groups, } @permission_required('participant.can_manage_participant') @template('participant/group_edit.html') def group_edit(request, group_id=None): """ Edit a group. """ if group_id is not None: try: group = Group.objects.get(id=group_id) except Group.DoesNotExist: raise NameError("There is no group %d" % group_id) else: group = None delete_default_permissions() if request.method == 'POST': form = GroupForm(request.POST, instance=group) if form.is_valid(): group_name = form.cleaned_data['name'].lower() try: anonymous_group = Group.objects.get(name='Anonymous') except Group.DoesNotExist: anonymous_group = None # special handling for anonymous auth if group is None and group_name.strip().lower() == 'anonymous': # don't allow to create this group messages.error(request, _('Group name "%s" is reserved for internal use.') % group_name) return { 'form' : form, 'group': group } group = form.save() if anonymous_group is not None and \ anonymous_group.id == group.id: # prevent name changes - # XXX: I'm sure this could be done as *one* group.save() group.name = 'Anonymous' group.save() if group_id is None: messages.success(request, _('New group was successfully created.')) else: messages.success(request, _('Group was successfully modified.')) if not 'apply' in request.POST: return redirect(reverse('user_group_overview')) if group_id is None: return redirect(reverse('user_group_edit', args=[group.id])) else: messages.error(request, _('Please check the form for errors.')) else: form = GroupForm(instance=group) return { 'form': form, 'group': group, } @permission_required('participant.can_manage_participant') def group_delete(request, group_id): """ Delete a group. """ group = Group.objects.get(pk=group_id) if request.method == 'POST': group.delete() messages.success(request, _('Group %s was successfully deleted.') % group) else: gen_confirm_form(request, _('Do you really want to delete %s?') % group, reverse('user_group_delete', args=[group_id])) return redirect(reverse('user_group_overview')) @login_required @template('participant/settings.html') def user_settings(request): """ Edit own user account. """ if request.method == 'POST': form_user = UsersettingsForm(request.POST,instance=request.user) if form_user.is_valid(): form_user.save() messages.success(request, _('User settings successfully saved.')) else: messages.error(request, _('Please check the form for errors.')) else: form_user = UsersettingsForm(instance=request.user) return { 'form_user': form_user, 'edituser': request.user, } @login_required @template('participant/password_change.html') def user_settings_password(request): """ Edit own password. """ if request.method == 'POST': form = PasswordChangeForm(request.user, request.POST) if form.is_valid(): form.save() messages.success(request, _('Password successfully changed.')) return redirect(reverse('user_settings')) else: messages.error(request, _('Please check the form for errors.')) else: form = PasswordChangeForm(user=request.user) return { 'form': form, } @permission_required('participant.can_manage_participant') @template('participant/import.html') def user_import(request): """ Import Users via csv. """ from openslides.application.models import Application try: request.user.profile messages.error(request, _('The import function is available for the admin (without user profile) only.')) return redirect(reverse('user_overview')) except Profile.DoesNotExist: pass except AttributeError: # AnonymousUser pass if request.method == 'POST': form = UserImportForm(request.POST, request.FILES) if form.is_valid(): try: # check for valid encoding (will raise UnicodeDecodeError if not) request.FILES['csvfile'].read().decode('utf-8') request.FILES['csvfile'].seek(0) with transaction.commit_on_success(): old_users = {} applications_mapped = 0 applications_review = 0 applications_removed = 0 try: janitor = User.objects.get(username='__system__.janitor') except User.DoesNotExist: janitor = User() janitor.first_name = '' janitor.last_name = '' janitor.username = '__system__.janitor' janitor.save() applications = Application.objects.all() for application in applications: if form.cleaned_data['application_handling'] == 'DISCARD': # need to do this explicit since some applications may belong # to __system__.janitor which is a permanent user application.delete(force=True) applications_removed += 1 else: # collect all applications and map them to their submitters submitter = application.submitter skey = '%s_%s' % (submitter.first_name, submitter.last_name) if not skey in old_users: old_users[skey] = [] old_users[skey].append(application.id) application.submitter = janitor application.save() if application.supporter.all(): application.writelog(_('Supporters removed after user import.'), user=request.user) profiles = Profile.objects.all() for profile in profiles: profile.user.delete() profile.delete() i = -1 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)): i += 1 if i > 0: try: (first_name, last_name, gender, group, type, committee, comment) = line[:7] except ValueError: messages.error(request, _('Ignoring malformed line %d in import file.') % (lno + 1)) i -= 1 continue user = User() user.last_name = last_name user.first_name = first_name user.username = gen_username(first_name, last_name) #user.email = email user.save() profile = Profile() profile.user = user profile.gender = gender profile.group = group profile.type = type profile.committee = committee profile.comment = comment profile.firstpassword = gen_password() profile.user.set_password(profile.firstpassword) profile.user.save() profile.save() if type == 'delegate': delegate = Group.objects.get(name='Delegierter') user.groups.add(delegate) else: observer = Group.objects.get(name='Beobachter') user.groups.add(observer) if form.cleaned_data['application_handling'] == 'REASSIGN': # live remap skey = '%s_%s' % (user.first_name, user.last_name) if skey in old_users: for appid in old_users[skey]: try: application = Application.objects.get(id=appid) application.submitter = user application.save() application.writelog(_('Reassigned to "%s" after (re)importing users.') % ("%s %s" % (user.first_name, user.last_name)), user=request.user) applications_mapped += 1 except Application.DoesNotExist: messages.error(request, _('Could not reassing application %d - object not found!') % appid) del old_users[skey] if old_users: # mark all applications without a valid user as 'needs review' # this will account for *all* applications if application_mode == 'INREVIEW' for skey in old_users: for appid in old_users[skey]: try: application = Application.objects.get(id=appid) if application.status != 'rev': application.set_status(user=request.user, status='rev', force=True) applications_review += 1 except Application.DoesNotExist: messages.error(request, _('Could not reassing application %d - object not found!') % appid) if applications_review: messages.warning(request, ungettext('%d application could not be reassigned and needs a review!', '%d applications could not be reassigned and need a review!', applications_review) % applications_review) if applications_mapped: messages.success(request, ungettext('%d application was successfully reassigned.', '%d applications were successfully reassigned.', applications_mapped) % applications_mapped) if applications_removed: messages.warning(request, ungettext('%d application was discarded.', '%d applications were discarded.', applications_removed) % applications_removed) if i > 0: messages.success(request, _('%d new participants were successfully imported.') % i) return redirect(reverse('user_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: All existing participants will be removed if you import new participants.")) if Application.objects.all(): messages.warning(request, _("Attention: Supporters from all existing applications will be removed.")) messages.warning(request, _("Attention: Applications which can't be mapped to new users will be set to 'Needs Review'.")) form = UserImportForm() return { 'form': form, } @permission_required('participant.can_manage_participant') def reset_password(request, user_id): """ Reset the Password. """ user = User.objects.get(pk=user_id) if request.method == 'POST': user.profile.reset_password() messages.success(request, _('The Password for %s was successfully reset.') % user) else: gen_confirm_form(request, _('Do you really want to reset the password for %s?') % user, reverse('user_reset_password', args=[user_id])) return redirect(reverse('user_edit', args=[user_id])) def login(request): extra_content = {} try: admin = User.objects.get(pk=1) if admin.check_password(config['admin_password']): extra_content['first_time_message'] = _( "Installation was successfully! Use %(user)s " "(password: %(password)s) for first login.
" "Important: Please change the password after " "first login! Otherwise this message still appears for everyone " "and could be a security risk.") % { 'user': html_strong(admin.username), 'password': html_strong(config['admin_password'])} extra_content['next'] = reverse('password_change') except User.DoesNotExist: pass return django_login(request, template_name='participant/login.html', extra_context=extra_content) def register_tab(request): """ Register the participant tab. """ selected = request.path.startswith('/participant/') return Tab( title=_('Participants'), url=reverse('user_overview'), permission=request.user.has_perm('participant.can_see_participant') or request.user.has_perm('participant.can_manage_participant'), selected=selected, ) class ParticipantsListPDF(PDFView): """ Generate the userliste as PDF. """ permission_required = 'participant.can_see_participant' filename = _("Participant-list") document_title = _('List of Participants') def append_to_pdf(self, story): data= [['#', _('Last Name'), _('First Name'), _('Group'), _('Type'), _('Committee')]] sort = 'last_name' counter = 0 for user in User.objects.all().order_by(sort): try: counter += 1 user.get_profile() data.append([counter, Paragraph(user.last_name, stylesheet['Tablecell']), Paragraph(user.first_name, stylesheet['Tablecell']), Paragraph(user.profile.group, stylesheet['Tablecell']), Paragraph(user.profile.get_type_display(), stylesheet['Tablecell']), Paragraph(user.profile.committee, stylesheet['Tablecell']), ]) except Profile.DoesNotExist: counter -= 1 pass t = LongTable(data, style=[ ('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))), ]) t._argW[0]=0.75*cm story.append(t) class ParticipantsPasswordsPDF(PDFView): """ Generate the Welcomepaper for the users. """ permission_required = 'participant.can_manage_participant' filename = _("Participant-passwords") top_space = 0 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): data= [] participant_pdf_system_url = config["participant_pdf_system_url"] participant_pdf_welcometext = config["participant_pdf_welcometext"] for user in User.objects.all().order_by('last_name'): try: user.get_profile() cell = [] cell.append(Spacer(0,0.8*cm)) cell.append(Paragraph(_("Account for OpenSlides"), stylesheet['Ballot_title'])) cell.append(Paragraph(_("for %s") % (user.profile), stylesheet['Ballot_subtitle'])) cell.append(Spacer(0,0.5*cm)) cell.append(Paragraph(_("User: %s") % (user.username), stylesheet['Monotype'])) cell.append(Paragraph(_("Password: %s") % (user.profile.firstpassword), stylesheet['Monotype'])) cell.append(Spacer(0,0.5*cm)) cell.append(Paragraph(_("URL: %s") % (participant_pdf_system_url), stylesheet['Ballot_option'])) cell.append(Spacer(0,0.5*cm)) cell2 = [] cell2.append(Spacer(0,0.8*cm)) if participant_pdf_welcometext is not None: cell2.append(Paragraph( participant_pdf_welcometext.replace('\r\n','
'), stylesheet['Ballot_subtitle'])) data.append([cell,cell2]) except Profile.DoesNotExist: pass t=Table(data, 10.5*cm, 7.42*cm) t.setStyle(TableStyle([ ('LINEBELOW', (0,0), (-1,0), 0.25, colors.grey), ('LINEBELOW', (0,1), (-1,1), 0.25, colors.grey), ('LINEBELOW', (0,1), (-1,-1), 0.25, colors.grey), ('VALIGN', (0,0), (-1,-1), 'TOP'), ])) story.append(t) class Config(FormView): """ Config page for the participant app. """ permission_required = 'config.can_manage_config' form_class = ConfigForm template_name = 'participant/config.html' def get_initial(self): return { 'participant_pdf_system_url': config['participant_pdf_system_url'], 'participant_pdf_welcometext': config['participant_pdf_welcometext'] } def form_valid(self, form): config['participant_pdf_system_url'] = \ form.cleaned_data['participant_pdf_system_url'] config['participant_pdf_welcometext'] = \ form.cleaned_data['participant_pdf_welcometext'] messages.success(self.request, _('Participants settings successfully saved.')) return super(Config, self).form_valid(form)