#!/usr/bin/env python # -*- coding: utf-8 -*- """ openslides.participant.views ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Views for the participant app. :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ try: import qrcode except ImportError: draw_qrcode = False else: draw_qrcode = True from cStringIO import StringIO from django.contrib import messages from django.contrib.auth.decorators import login_required 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 _ from django.utils.translation import activate, ugettext_lazy from reportlab.lib import colors from reportlab.lib.units import cm from reportlab.platypus import (Image, LongTable, Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle) from openslides.config.api import config from openslides.projector.projector import Widget from openslides.utils.pdf import stylesheet from openslides.utils.template import Tab from openslides.utils.utils import (delete_default_permissions, html_strong, template) from openslides.utils.views import (CreateView, DeleteView, DetailView, FormView, ListView, PDFView, PermissionMixin, QuestionView, RedirectView, SingleObjectMixin, UpdateView) from .api import gen_password, gen_username, import_users from .forms import (GroupForm, UserCreateForm, UserImportForm, UsersettingsForm, UserUpdateForm) from .models import get_protected_perm, Group, User class UserOverview(ListView): """ Show all participants (users). """ permission_required = 'participant.can_see_participant' template_name = 'participant/overview.html' context_object_name = 'users' def get_queryset(self): query = User.objects if config['participant_sort_users_by_first_name']: query = query.order_by('first_name') else: query = query.order_by('last_name') return query.all() def get_context_data(self, **kwargs): context = super(UserOverview, self).get_context_data(**kwargs) all_users = User.objects.count() # context vars context.update({ 'allusers': all_users, 'request_user': self.request.user}) return context class UserDetailView(DetailView, PermissionMixin): """ Classed based view to show a specific user in the interface. """ permission_required = 'participant.can_see_participant' model = User template_name = 'participant/user_detail.html' context_object_name = 'shown_user' class UserCreateView(CreateView): """ Create a new participant. """ permission_required = 'participant.can_manage_participant' template_name = 'participant/edit.html' model = User context_object_name = 'edit_user' form_class = UserCreateForm success_url_name = 'user_overview' url_name_args = [] def manipulate_object(self, form): self.object.username = gen_username( form.cleaned_data['first_name'], form.cleaned_data['last_name']) if not self.object.default_password: self.object.default_password = gen_password() self.object.set_password(self.object.default_password) def post_save(self, form): super(UserCreateView, self).post_save(form) # TODO: find a better solution that makes the following lines obsolete # Background: motion.models.use_post_save adds already the registerd group # to new user but super(..).post_save(form) removes it and sets only the # groups selected in the form (without 'registered') # workaround: add registered group again manually from openslides.participant.api import get_registered_group # TODO: Test, if global import is possible registered = get_registered_group() self.object.groups.add(registered) class UserUpdateView(UpdateView): """ Update an existing participant. """ permission_required = 'participant.can_manage_participant' template_name = 'participant/edit.html' model = User context_object_name = 'edit_user' form_class = UserUpdateForm success_url_name = 'user_overview' url_name_args = [] def get_form_kwargs(self, *args, **kwargs): form_kwargs = super(UserUpdateView, self).get_form_kwargs(*args, **kwargs) form_kwargs.update({'request': self.request}) return form_kwargs def manipulate_object(self, form): self.object.username = form.cleaned_data['user_name'] def post_save(self, form): super(UserUpdateView, self).post_save(form) # TODO: find a better solution that makes the following lines obsolete # Background: motion.models.use_post_save adds already the registerd group # to new user but super(..).post_save(form) removes it and sets only the # groups selected in the form (without 'registered') # workaround: add registered group again manually from openslides.participant.api import get_registered_group # TODO: Test, if global import is possible registered = get_registered_group() self.object.groups.add(registered) class UserDeleteView(DeleteView): """ Delete an participant. """ permission_required = 'participant.can_manage_participant' model = User success_url_name = 'user_overview' url_name_args = [] def pre_redirect(self, request, *args, **kwargs): if self.object == self.request.user: messages.error(request, _("You can not delete yourself.")) else: super(UserDeleteView, self).pre_redirect(request, *args, **kwargs) def pre_post_redirect(self, request, *args, **kwargs): if self.object == self.request.user: messages.error(self.request, _("You can not delete yourself.")) else: super(UserDeleteView, self).pre_post_redirect(request, *args, **kwargs) class SetUserStatusView(RedirectView, SingleObjectMixin): """ Activate or deactivate an user. """ permission_required = 'participant.can_manage_participant' allow_ajax = True url_name = 'user_overview' model = User def pre_redirect(self, request, *args, **kwargs): self.object = self.get_object() action = kwargs['action'] if action == 'activate': self.object.is_active = True elif action == 'deactivate': if self.object.user == self.request.user: messages.error(request, _("You can not deactivate yourself.")) return self.object.is_active = False elif action == 'toggle': self.object.is_active = not self.object.is_active self.object.save() return super(SetUserStatusView, self).pre_redirect(request, *args, **kwargs) def get_ajax_context(self, **kwargs): context = super(SetUserStatusView, self).get_ajax_context(**kwargs) context['active'] = self.object.is_active return context class ParticipantsListPDF(PDFView): """ Generate the userliste as PDF. """ permission_required = 'participant.can_see_participant' filename = ugettext_lazy("Participant-list") document_title = ugettext_lazy('List of Participants') def append_to_pdf(self, story): data = [['#', _('Title'), _('Last Name'), _('First Name'), _('Structure level'), _('Group'), _('Committee')]] if config['participant_sort_users_by_first_name']: sort = 'first_name' else: sort = 'last_name' counter = 0 for user in User.objects.all().order_by(sort): counter += 1 groups = '' for group in user.groups.all(): if group.pk != 2: groups += "%s
" % unicode(_(group.name)) data.append([ counter, Paragraph(user.title, stylesheet['Tablecell']), Paragraph(user.last_name, stylesheet['Tablecell']), Paragraph(user.first_name, stylesheet['Tablecell']), Paragraph(user.structure_level, stylesheet['Tablecell']), Paragraph(groups, stylesheet['Tablecell']), Paragraph(user.committee, stylesheet['Tablecell'])]) 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 = ugettext_lazy("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"] if config['participant_sort_users_by_first_name']: sort = 'first_name' else: sort = 'last_name' # create qrcode image object from system url if draw_qrcode: qrcode_img = qrcode.make(participant_pdf_system_url) img_stream = StringIO() qrcode_img.save(img_stream, 'PNG') img_stream.seek(0) size = 2 * cm I = Image(img_stream, width=size, height=size) for user in User.objects.all().order_by(sort): cell = [] cell.append(Spacer(0, 0.8 * cm)) cell.append(Paragraph(_("Account for OpenSlides"), stylesheet['Password_title'])) cell.append(Paragraph(_("for %s") % (user), stylesheet['Password_subtitle'])) cell.append(Spacer(0, 0.5 * cm)) cell.append(Paragraph(_("User: %s") % (user.username), stylesheet['Monotype'])) cell.append( Paragraph( _("Password: %s") % (user.default_password), stylesheet['Monotype'])) cell.append( Paragraph(participant_pdf_system_url, stylesheet['Monotype'])) if draw_qrcode: cell.append(I) 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]) # add empty table line if no participants available if not data: data.append(['', '']) # build table t = Table(data, 10.5 * cm, 7.42 * cm) t.setStyle(TableStyle([ ('LEFTPADDING', (0, 0), (0, -1), 30), ('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 UserImportView(FormView): """ Import Users via csv. """ permission_required = 'participant.can_manage_participant' template_name = 'participant/import.html' form_class = UserImportForm success_url_name = 'user_overview' def form_valid(self, form): # check for valid encoding (will raise UnicodeDecodeError if not) success, error_messages = import_users(self.request.FILES['csvfile']) for message in error_messages: messages.error(self.request, message) if success: messages.success( self.request, _('%d new participants were successfully imported.') % success) return super(UserImportView, self).form_valid(form) class ResetPasswordView(SingleObjectMixin, QuestionView): """ Set the Passwort for a user to his default password. """ permission_required = 'participant.can_manage_participant' model = User allow_ajax = True question_message = ugettext_lazy('Do you really want to reset the password?') def get(self, request, *args, **kwargs): self.object = self.get_object() return super(ResetPasswordView, self).get(request, *args, **kwargs) def get_redirect_url(self, **kwargs): return reverse('user_edit', args=[self.object.id]) def on_clicked_yes(self): self.object.reset_password() def get_final_message(self): return _('The Password for %s was successfully reset.') % html_strong(self.object) class GroupOverview(ListView): """ Overview over all groups. """ permission_required = 'participant.can_manage_participant' template_name = 'participant/group_overview.html' context_object_name = 'groups' model = Group class GroupDetailView(DetailView, PermissionMixin): """ Classed based view to show a specific group in the interface. """ permission_required = 'participant.can_manage_participant' model = Group template_name = 'participant/group_detail.html' context_object_name = 'group' def get_context_data(self, *args, **kwargs): context = super(GroupDetailView, self).get_context_data(*args, **kwargs) query = User.objects if config['participant_sort_users_by_first_name']: query = query.order_by('first_name') else: query = query.order_by('last_name') context['group_members'] = query.filter(django_user__groups__in=[context['group']]) return context class GroupCreateView(CreateView): """ Create a new group. """ permission_required = 'participant.can_manage_participant' template_name = 'participant/group_edit.html' context_object_name = 'group' model = Group form_class = GroupForm success_url_name = 'user_group_overview' url_name_args = [] def get(self, request, *args, **kwargs): delete_default_permissions() return super(GroupCreateView, self).get(request, *args, **kwargs) class GroupUpdateView(UpdateView): """ Update an existing group. """ permission_required = 'participant.can_manage_participant' template_name = 'participant/group_edit.html' model = Group context_object_name = 'group' form_class = GroupForm success_url_name = 'user_group_overview' url_name_args = [] def get(self, request, *args, **kwargs): delete_default_permissions() return super(GroupUpdateView, self).get(request, *args, **kwargs) def get_form_kwargs(self, *args, **kwargs): form_kwargs = super(GroupUpdateView, self).get_form_kwargs(*args, **kwargs) form_kwargs.update({'request': self.request}) return form_kwargs class GroupDeleteView(DeleteView): """ Delete a group. """ permission_required = 'participant.can_manage_participant' model = Group success_url_name = 'user_group_overview' url_name_args = [] def pre_redirect(self, request, *args, **kwargs): if not self.is_protected_from_deleting(): super(GroupDeleteView, self).pre_redirect(request, *args, **kwargs) def pre_post_redirect(self, request, *args, **kwargs): if not self.is_protected_from_deleting(): super(GroupDeleteView, self).pre_post_redirect(request, *args, **kwargs) def is_protected_from_deleting(self): """ Checks whether the group is protected. """ if self.object.pk in [1, 2]: messages.error(self.request, _('You can not delete this group.')) return True if (not self.request.user.is_superuser and get_protected_perm() in self.object.permissions.all() and not Group.objects.exclude(pk=self.object.pk).filter( permissions__in=[get_protected_perm()], user__pk=self.request.user.pk).exists()): messages.error( self.request, _('You can not delete the last group containing the permission ' 'to manage participants you are in.')) return True return False def login(request): extra_content = {} try: admin = User.objects.get(pk=1) if admin.check_password(admin.default_password): user_data = { 'user': html_strong(admin.username), 'password': html_strong(admin.default_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_data extra_content['next'] = reverse('password_change') except User.DoesNotExist: pass return django_login(request, template_name='participant/login.html', extra_context=extra_content) @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(): user = form_user.save(commit=False) user.username = form_user.cleaned_data['user_name'] user.save() language = request.LANGUAGE_CODE = \ request.session['django_language'] = form_user.cleaned_data['language'] activate(language) messages.success(request, _('User settings successfully saved.')) else: messages.error(request, _('Please check the form for errors.')) else: language = request.session.get('django_language', request.LANGUAGE_CODE) form_user = UsersettingsForm(instance=request.user, initial={'language': language}) return { 'form': 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('dashboard')) else: messages.error(request, _('Please check the form for errors.')) else: form = PasswordChangeForm(user=request.user) return { 'form': form, } def register_tab(request): """ Registers the participant tab. """ selected = request.path.startswith('/participant/') return Tab( title=_('Participants'), app='participant', url=reverse('user_overview'), permission=( request.user.has_perm('participant.can_see_participant') or request.user.has_perm('participant.can_manage_participant')), selected=selected) def get_widgets(request): """ Returns all widgets of the participant app. This is a user_widget and a group_widget. """ return [get_user_widget(request), get_group_widget(request)] def get_user_widget(request): """ Provides a widget with all users. This is for short activation of user slides. """ return Widget( request, name='user', display_name=_('Participants'), template='participant/user_widget.html', context={'users': User.objects.all()}, permission_required='projector.can_manage_projector', default_column=1, default_weight=60) def get_group_widget(request): """ Provides a widget with all groups. This is for short activation of group slides. """ return Widget( request, name='group', display_name=_('Groups'), template='participant/group_widget.html', context={'groups': Group.objects.all()}, permission_required='projector.can_manage_projector', default_column=1, default_weight=70)