diff --git a/initial_data.json b/initial_data.json index ad17d7a56..5f7c8e30c 100644 --- a/initial_data.json +++ b/initial_data.json @@ -1,6 +1,6 @@ [ { - "pk": 2, + "pk": 1, "model": "auth.group", "fields": { "name": "Beobachter", @@ -38,7 +38,7 @@ [ "can_see_participant", "participant", - "openslidesuser" + "user" ], [ "can_see_projector", @@ -49,7 +49,7 @@ } }, { - "pk": 3, + "pk": 2, "model": "auth.group", "fields": { "name": "Delegierter", @@ -92,7 +92,7 @@ [ "can_see_participant", "participant", - "openslidesuser" + "user" ], [ "can_see_projector", @@ -103,7 +103,7 @@ } }, { - "pk": 4, + "pk": 3, "model": "auth.group", "fields": { "name": "Versammlungsleitung", @@ -161,12 +161,12 @@ [ "can_manage_participant", "participant", - "openslidesuser" + "user" ], [ "can_see_participant", "participant", - "openslidesuser" + "user" ], [ "can_manage_projector", @@ -182,7 +182,7 @@ } }, { - "pk": 5, + "pk": 4, "model": "auth.group", "fields": { "name": "Teilnehmerverwaltung", @@ -195,12 +195,12 @@ [ "can_manage_participant", "participant", - "openslidesuser" + "user" ], [ "can_see_participant", "participant", - "openslidesuser" + "user" ], [ "can_see_projector", diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 9e5f69ac3..24a21f1c8 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -167,69 +167,24 @@ class ItemDelete(DeleteView): model = Item url = 'item_overview' - def pre_post_redirect(self, request, *args, **kwargs): - self.object = self.get_object() + def get_answer_options(self): + if self.object.children.exists(): + return [('all', _("Yes, with all child items."))] + self.answer_options + else: + return self.answer_options - if 'all' in request.POST: + def pre_post_redirect(self, request, *args, **kwargs): + if self.get_answer() == 'all': self.object.delete(with_children=True) messages.success(request, _("Item %s and his children were successfully deleted.") \ % html_strong(self.object)) - else: + elif self.get_answer() == 'yes': self.object.delete(with_children=False) messages.success(request, _("Item %s was successfully deleted.") \ % html_strong(self.object)) - def gen_confirm_form(self, request, message, url, singleitem=False): - if singleitem: - messages.warning( - request, - """ - %s -
- - - -
- """ - % (message, url, csrf(request)['csrf_token'], _("Yes"), - _("No")) - ) - else: - messages.warning( - request, - """ - %s -
- - - - -
- """ - % (message, url, csrf(request)['csrf_token'], _("Yes"), - _("Yes, with all child items."), _("No")) - ) - - def confirm_form(self, request, object, item=None): - if item is None: - item = object - if item.get_children(): - self.gen_confirm_form( - request, - _('Do you really want to delete %s?') % html_strong(item), - item.get_absolute_url('delete'), - False, - ) - else: - self.gen_confirm_form( - request, - _('Do you really want to delete %s?') % html_strong(item), - item.get_absolute_url('delete'), - True, - ) - class AgendaPDF(PDFView): """ diff --git a/openslides/application/models.py b/openslides/application/models.py index 78ebd55b6..82d1099c8 100644 --- a/openslides/application/models.py +++ b/openslides/application/models.py @@ -12,7 +12,6 @@ from datetime import datetime -from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.db import models from django.db.models import Max @@ -26,8 +25,6 @@ from openslides.utils.person import PersonField from openslides.config.models import config from openslides.config.signals import default_config_value -from openslides.participant.models import OpenSlidesUser - from openslides.poll.models import (BaseOption, BasePoll, CountVotesCast, CountInvalid, BaseVote) @@ -360,15 +357,6 @@ class Application(models.Model, SlideMixin): Return a list of all the allowed status. """ actions = [] - is_admin = False - if user: - try: - user = user.openslidesuser - except OpenSlidesUser.DoesNotExist: - is_admin = True - except AttributeError: - # For the anonymous-user - pass # check if user allowed to withdraw an application if ((self.status == "pub" @@ -405,11 +393,10 @@ class Application(models.Model, SlideMixin): # Check if the user can delete the application (admin, manager, owner) # reworked as requiered in #100 - if is_admin \ - or (user.has_perm("application.can_manage_application") \ - and (self.status == "pub" or self.number is None)) \ - or (self.submitter == user \ - and (self.status == "pub" or self.number is None)): + if (user.has_perm("applicatoin.can_delete_all_applications") or + (user.has_perm("application.can_manage_application") and + self.number is None) or + (self.submitter == user and self.number is None)): actions.append("delete") #For the rest, all actions need the manage permission @@ -543,6 +530,7 @@ class Application(models.Model, SlideMixin): ('can_create_application', ugettext_noop("Can create application")), ('can_support_application', ugettext_noop("Can support application")), ('can_manage_application', ugettext_noop("Can manage application")), + ('can_delete_all_applications', ugettext_noop("Can delete all applications")), ) diff --git a/openslides/application/tests.py b/openslides/application/tests.py index 2f5e65e09..bd3ecb73a 100644 --- a/openslides/application/tests.py +++ b/openslides/application/tests.py @@ -12,15 +12,17 @@ from django.test import TestCase from django.test.client import Client -from django.contrib.auth.models import User +from openslides.participant.models import User from openslides.application.models import Application, AVersion class ApplicationTest(TestCase): def setUp(self): - self.admin = User.objects.create_user('testadmin', '', 'default') - self.anonym = User.objects.create_user('testanoym', '', 'default') - self.app1 = Application(submitter=self.admin.openslidesuser) + self.admin = User(username='testadmin') + self.admin.save() + self.anonym = User(username='testanoym') + self.anonym.save() + self.app1 = Application(submitter=self.admin) self.app1.save() def refresh(self): diff --git a/openslides/application/views.py b/openslides/application/views.py index d531b0cab..1163d6ea2 100644 --- a/openslides/application/views.py +++ b/openslides/application/views.py @@ -51,7 +51,7 @@ 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 OpenSlidesUser +from openslides.participant.models import User from openslides.agenda.models import Item @@ -124,7 +124,7 @@ def overview(request): for (i, application) in enumerate(applications): try: applications[i] = { - 'actions' : application.get_allowed_actions(request.user.openslidesuser), + 'actions' : application.get_allowed_actions(request.user), 'application' : application } except: @@ -152,8 +152,7 @@ def view(request, application_id, newest=False): else: version = application.public_version revisions = application.versions - user = request.user.openslidesuser - actions = application.get_allowed_actions(user=user) + actions = application.get_allowed_actions(user=request.user) return { 'application': application, @@ -183,11 +182,11 @@ def edit(request, application_id=None): if application_id is not None: application = Application.objects.get(id=application_id) if (not hasattr(application.submitter, 'user') or - not request.user.openslidesuser == application.submitter.user) \ + 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.openslidesuser) + actions = application.get_allowed_actions(user=request.user) else: application = None actions = None @@ -221,7 +220,7 @@ def edit(request, application_id=None): original_supporters = [] application = managerform.save(commit=False) elif application_id is None: - application = Application(submitter=request.user.openslidesuser) + application = Application(submitter=request.user) application.title = dataform.cleaned_data['title'] application.text = dataform.cleaned_data['text'] application.reason = dataform.cleaned_data['reason'] @@ -231,7 +230,7 @@ def edit(request, application_id=None): and dataform.cleaned_data['trivial_change'] except KeyError: trivial_change = False - application.save(request.user.openslidesuser, trivial_change=trivial_change) + application.save(request.user, trivial_change=trivial_change) if is_manager: try: new_supporters = set(managerform.cleaned_data['supporter']) @@ -273,7 +272,7 @@ def edit(request, application_id=None): dataform = formclass(initial=initial, prefix="data") if is_manager: if application_id is None: - initial = {'submitter': request.user.openslidesuser.person_id} + initial = {'submitter': request.user.person_id} else: initial = {'submitter': application.submitter.person_id, 'supporter': [supporter.person_id for supporter in application.supporters]} @@ -296,7 +295,7 @@ def set_number(request, application_id): set a number for an application. """ try: - Application.objects.get(pk=application_id).set_number(user=request.user.openslidesuser) + Application.objects.get(pk=application_id).set_number(user=request.user) messages.success(request, _("Application number was successfully set.")) except Application.DoesNotExist: pass @@ -312,7 +311,7 @@ def permit(request, application_id): permit an application. """ try: - Application.objects.get(pk=application_id).permit(user=request.user.openslidesuser) + Application.objects.get(pk=application_id).permit(user=request.user) messages.success(request, _("Application was successfully permitted.")) except Application.DoesNotExist: pass @@ -327,7 +326,7 @@ def notpermit(request, application_id): reject (not permit) an application. """ try: - Application.objects.get(pk=application_id).notpermit(user=request.user.openslidesuser) + Application.objects.get(pk=application_id).notpermit(user=request.user) messages.success(request, _("Application was successfully rejected.")) except Application.DoesNotExist: pass @@ -343,7 +342,7 @@ def set_status(request, application_id=None, status=None): try: if status is not None: application = Application.objects.get(pk=application_id) - application.set_status(user=request.user.openslidesuser, status=status) + 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 @@ -387,7 +386,7 @@ def unsupport(request, application_id): unsupport an application. """ try: - Application.objects.get(pk=application_id).unsupport(user=request.user.openslidesuser) + Application.objects.get(pk=application_id).unsupport(user=request.user) messages.success(request, _("You have unsupport the application successfully.") ) except Application.DoesNotExist: pass @@ -401,7 +400,7 @@ 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.openslidesuser) + 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 @@ -418,7 +417,7 @@ def delete_poll(request, poll_id): count = application.polls.filter(id__lte=poll_id).count() if request.method == 'POST': poll.delete() - application.writelog(_("Poll deleted"), request.user.openslidesuser) + 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])) @@ -458,7 +457,7 @@ class ApplicationDelete(DeleteView): if len(self.applications): for application in self.applications: - if not 'delete' in application.get_allowed_actions(user=request.user.openslidesuser): + if not 'delete' in application.get_allowed_actions(user=request.user): messages.error(request, _("You can not delete application %s.") % application) continue @@ -467,7 +466,7 @@ class ApplicationDelete(DeleteView): messages.success(request, _("Application %s was successfully deleted.") % title) elif self.object: - if not 'delete' in self.object.get_allowed_actions(user=request.user.openslidesuser): + 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 @@ -508,12 +507,12 @@ class ViewPoll(PollFormView): 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.openslidesuser) + 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.openslidesuser + user = self.request.user class ViewPollFormClass(cls): def save(self, commit = True): @@ -535,7 +534,7 @@ 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.openslidesuser) + 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])) @@ -547,7 +546,7 @@ 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.openslidesuser): + 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.") ) @@ -559,17 +558,6 @@ def reject_version(request, aversion_id): @permission_required('application.can_manage_application') @template('application/import.html') def application_import(request): - try: - request.user.openslidesuser - except OpenSlidesUser.DoesNotExist: - pass - except AttributeError: - # AnonymousUser - pass - else: - messages.error(request, _('The import function is available for the admin (without user profile) only.')) - return redirect(reverse('application_overview')) - if request.method == 'POST': form = ApplicationImportForm(request.POST, request.FILES) if form.is_valid(): diff --git a/openslides/assignment/views.py b/openslides/assignment/views.py index 96a37bb14..56dc26448 100644 --- a/openslides/assignment/views.py +++ b/openslides/assignment/views.py @@ -34,7 +34,7 @@ from openslides.utils.person import get_person from openslides.config.models import config -from openslides.participant.models import OpenSlidesUser +from openslides.participant.models import User from openslides.projector.projector import Widget @@ -97,13 +97,12 @@ def view(request, assignment_id=None): polls = assignment.poll_set.all() vote_results = assignment.vote_results(only_published=False) - user = request.user.openslidesuser return { 'assignment': assignment, 'form': form, 'vote_results': vote_results, 'polls': polls, - 'user_is_candidate': assignment.is_candidate(user) + 'user_is_candidate': assignment.is_candidate(request.user) } @@ -173,7 +172,7 @@ def set_status(request, assignment_id=None, status=None): def run(request, assignment_id): assignment = Assignment.objects.get(pk=assignment_id) try: - assignment.run(request.user.openslidesuser, request.user) + assignment.run(request.user, request.user) messages.success(request, _('You have set your candidature successfully.') ) except NameError, e: messages.error(request, e) @@ -185,7 +184,7 @@ def delrun(request, assignment_id): assignment = Assignment.objects.get(pk=assignment_id) try: if assignment.status == 'sea' or user.has_perm("assignment.can_manage_assignment"): - assignment.delrun(request.user.openslidesuser) + assignment.delrun(request.user) else: messages.error(request, _('The candidate list is already closed.')) except Exception, e: diff --git a/openslides/openslides_settings.py b/openslides/openslides_settings.py index b3f14315f..b14ff673e 100755 --- a/openslides/openslides_settings.py +++ b/openslides/openslides_settings.py @@ -101,7 +101,7 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'openslides.participant.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.locale.LocaleMiddleware', ) diff --git a/openslides/participant/api.py b/openslides/participant/api.py index d2919821b..6728016cd 100644 --- a/openslides/participant/api.py +++ b/openslides/participant/api.py @@ -10,13 +10,19 @@ :license: GNU GPL, see LICENSE for more details. """ +# for python 2.5 support +from __future__ import with_statement + from random import choice import string +import csv from django.contrib.auth.models import User +from django.db import transaction -from openslides.utils.person import get_person -from openslides.participant.models import OpenSlidesUser +from openslides.utils import csv_ext + +from openslides.participant.models import User def gen_password(): @@ -47,3 +53,44 @@ def gen_username(first_name, last_name): User.objects.get(username=testname) except User.DoesNotExist: return testname + + +def import_users(csv_file): + error_messages = [] + count_success = 0 + try: + # check for valid encoding (will raise UnicodeDecodeError if not) + csv_file.read().decode('utf-8') + csv_file.seek(0) + + with transaction.commit_on_success(): + dialect = csv.Sniffer().sniff(csv_file.readline()) + dialect = csv_ext.patchup(dialect) + csv_file.seek(0) + + for (line_no, line) in enumerate(csv.reader(csv_file, + dialect=dialect)): + if line_no: + try: + (first_name, last_name, gender, category, type, committee, comment) = line[:7] + except ValueError: + error_messages.append(_('Ignoring malformed line %d in import file.') % line_no + 1) + continue + user = User() + user.last_name = last_name + user.first_name = first_name + user.username = gen_username(first_name, last_name) + user.gender = gender + user.category = category + user.type = type + user.committee = committee + user.comment = comment + user.firstpassword = gen_password() + user.save() + user.reset_password() + count_success += 1 + except csv.Error: + error_messages.appen(_('Import aborted because of severe errors in the input file.')) + except UnicodeDecodeError: + error_messages.appen(_('Import file has wrong character encoding, only UTF-8 is supported!')) + return (count_success, error_messages) diff --git a/openslides/participant/forms.py b/openslides/participant/forms.py index f39db40fb..4fb13a570 100644 --- a/openslides/participant/forms.py +++ b/openslides/participant/forms.py @@ -11,79 +11,84 @@ """ from django import forms -from django.contrib.auth.forms import AdminPasswordChangeForm -from django.contrib.auth.models import User, Group, Permission -from django.utils.translation import ugettext_lazy as _, ugettext_noop +from django.contrib.auth.models import Permission +from django.utils.translation import ugettext_lazy as _ from openslides.utils.forms import ( CssClassMixin, LocalizedModelMultipleChoiceField) -from openslides.participant.models import OpenSlidesUser +from openslides.participant.models import User, Group -USER_APPLICATION_IMPORT_OPTIONS = [ - ('REASSIGN', _('Keep applications, try to reassign submitter')), - ('INREVIEW', _('Keep applications, set status to "needs review"')), - ('DISCARD', _('Discard applications')) -] - - -class UserNewForm(forms.ModelForm, CssClassMixin): - first_name = forms.CharField(label=_("First name")) - last_name = forms.CharField(label=_("Last name")) +class UserCreateForm(forms.ModelForm, CssClassMixin): groups = forms.ModelMultipleChoiceField( - queryset=Group.objects.all(), label=_("User groups"), required=False) - is_active = forms.BooleanField( - label=_("Active"), required=False, initial=True) + queryset=Group.objects.exclude(name__iexact='anonymous'), + label=_("User groups"), required=False) class Meta: model = User - exclude = ('username', 'password', 'is_staff', 'is_superuser', - 'last_login', 'date_joined', 'user_permissions') + fields = ('first_name', 'last_name', 'is_active', 'groups', 'category', + 'gender', 'type', 'committee', 'comment', 'default_password') -class UserEditForm(forms.ModelForm, CssClassMixin): - first_name = forms.CharField(label=_("First name")) - last_name = forms.CharField(label=_("Last name")) - groups = forms.ModelMultipleChoiceField( - queryset=Group.objects.all(), label=_("User groups"), required=False) - is_active = forms.BooleanField(label=_("Active"), required=False) - +class UserUpdateForm(UserCreateForm): class Meta: model = User - exclude = ('password', 'is_staff', 'is_superuser', 'last_login', - 'date_joined', 'user_permissions') - - -class UsernameForm(forms.ModelForm, CssClassMixin): - class Meta: - model = User - exclude = ('first_name', 'last_name', 'email', 'is_active', - 'is_superuser', 'groups', 'password', 'is_staff', - 'last_login', 'date_joined', 'user_permissions') - - -class OpenSlidesUserForm(forms.ModelForm, CssClassMixin): - class Meta: - model = OpenSlidesUser + fields = ('username', 'first_name', 'last_name', 'is_active', 'groups', + 'category', 'gender', 'type', 'committee', 'comment', + 'default_password') class GroupForm(forms.ModelForm, CssClassMixin): - as_user = forms.BooleanField( - initial=False, required=False, label=_("Treat Group as User"), - help_text=_("The Group will appear on any place, other user does.")) permissions = LocalizedModelMultipleChoiceField( - queryset=Permission.objects.all(), label=_("Persmissions")) + queryset=Permission.objects.all(), label=_("Persmissions"), + required=False) + users = forms.ModelMultipleChoiceField( + queryset=User.objects.all(), label=_("Users"), required=False) def __init__(self, *args, **kwargs): - super(GroupForm, self).__init__(*args, **kwargs) + # Initial users if kwargs.get('instance', None) is not None: - self.fields['permissions'].initial = ( - [p.pk for p in kwargs['instance'].permissions.all()]) + initial = kwargs.setdefault('initial', {}) + initial['users'] = [django_user.user.pk for django_user in kwargs['instance'].user_set.all()] + + super(GroupForm, self).__init__(*args, **kwargs) + + def save(self, commit=True): + instance = forms.ModelForm.save(self, False) + + old_save_m2m = self.save_m2m + def save_m2m(): + old_save_m2m() + + instance.user_set.clear() + for user in self.cleaned_data['users']: + instance.user_set.add(user) + self.save_m2m = save_m2m + + if commit: + instance.save() + self.save_m2m() + + return instance + + def clean_name(self): + # Do not allow to change the name "anonymous" or give another group + # this name + data = self.cleaned_data['name'] + if self.instance.name.lower() == 'anonymous': + # Editing the anonymous-user + if self.instance.name.lower() != data.lower(): + raise forms.ValidationError( + _('You can not edit the name for the anonymous user')) + else: + if data.lower() == 'anonymous': + raise forms.ValidationError( + _('Group name "%s" is reserved for internal use.') % data) + return data class Meta: model = Group - exclude = ('permissions',) class UsersettingsForm(forms.ModelForm, CssClassMixin): @@ -95,9 +100,6 @@ class UsersettingsForm(forms.ModelForm, CssClassMixin): class UserImportForm(forms.Form, CssClassMixin): csvfile = forms.FileField(widget=forms.FileInput(attrs={'size': '50'}), label=_("CSV File")) - application_handling = forms.ChoiceField( - required=True, choices=USER_APPLICATION_IMPORT_OPTIONS, - label=_("For existing applications")) class ConfigForm(forms.Form, CssClassMixin): diff --git a/openslides/participant/middleware.py b/openslides/participant/middleware.py new file mode 100644 index 000000000..b2341f00f --- /dev/null +++ b/openslides/participant/middleware.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.utils.middleware + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Additional definitions for OpenSlides forms. + + :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :license: GNU GPL, see LICENSE for more details. +""" + +from django.contrib.auth.middleware import AuthenticationMiddleware as _AuthenticationMiddleware +from django.contrib.auth.models import AnonymousUser + + +class AuthenticationMiddleware(_AuthenticationMiddleware): + def process_request(self, request): + super(AuthenticationMiddleware, self).process_request(request) + + if not isinstance(request.user, AnonymousUser): + request.user = request.user.user diff --git a/openslides/participant/models.py b/openslides/participant/models.py index f7685994e..8daf6fcdc 100644 --- a/openslides/participant/models.py +++ b/openslides/participant/models.py @@ -10,20 +10,20 @@ :license: GNU GPL, see LICENSE for more details. """ -from django.contrib.auth.models import User, Group +from django.contrib.auth.models import User as DjangoUser, Group as DjangoGroup from django.db import models -from django.db.models import Q, signals +from django.db.models import signals from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _, ugettext_noop from openslides.utils.person import PersonMixin -from openslides.utils.person.signals import receiv_persons +from openslides.utils.person.signals import receive_persons from openslides.config.signals import default_config_value -class OpenSlidesUser(models.Model, PersonMixin): - person_prefix = 'openslides_user' +class User(DjangoUser, PersonMixin): + person_prefix = 'user' GENDER_CHOICES = ( ('male', _('Male')), ('female', _('Female')), @@ -35,10 +35,10 @@ class OpenSlidesUser(models.Model, PersonMixin): ('guest', _('Guest')), ) - user = models.OneToOneField(User, unique=True, editable=False) - name_surfix = models.CharField( - max_length=100, null=True, blank=True, verbose_name=_("Name Surfix"), - help_text=_('Shown behind the name.')) + django_user = models.OneToOneField(DjangoUser, editable=False, parent_link=True) + category = models.CharField( + max_length=100, null=True, blank=True, verbose_name=_("Category"), + help_text=_('Will be shown behind the name.')) gender = models.CharField( max_length=50, choices=GENDER_CHOICES, blank=True, verbose_name=_("Gender"), help_text=_('Only for filter the userlist.')) @@ -51,21 +51,26 @@ class OpenSlidesUser(models.Model, PersonMixin): comment = models.TextField( null=True, blank=True, verbose_name=_('Comment'), help_text=_('Only for notes.')) - firstpassword = models.CharField( + default_password = models.CharField( max_length=100, null=True, blank=True, - verbose_name=_("First Password")) + verbose_name=_("Default password")) + + def get_name_suffix(self): + return self.category + + def set_name_suffix(self, value): + self.category = value + + name_suffix = property(get_name_suffix, set_name_suffix) def reset_password(self, password=None): """ Reset the password for the user to his default-password. """ if password is None: - password = self.firstpassword - self.user.set_password(password) - self.user.save() - - def has_perm(self, perm): - return self.user.has_perm(perm) + password = self.default_password + self.set_password(password) + self.save() @models.permalink def get_absolute_url(self, link='edit'): @@ -77,14 +82,15 @@ class OpenSlidesUser(models.Model, PersonMixin): * delete """ if link == 'edit': - return ('user_edit', [str(self.user.id)]) + return ('user_edit', [str(self.id)]) if link == 'delete': - return ('user_delete', [str(self.user.id)]) + return ('user_delete', [str(self.id)]) def __unicode__(self): - if self.name_surfix: - return "%s (%s)" % (self.user.get_full_name(), self.name_surfix) - return "%s" % self.user.get_full_name() + name = self.get_full_name() or self.username + if self.name_suffix: + return u"%s (%s)" % (name, self.name_suffix) + return u"%s" % name class Meta: # Rename permissions @@ -95,46 +101,63 @@ class OpenSlidesUser(models.Model, PersonMixin): ) -class OpenSlidesGroup(models.Model, PersonMixin): - person_prefix = 'openslides_group' +class Group(DjangoGroup, PersonMixin): + person_prefix = 'group' - group = models.OneToOneField(Group) + django_group = models.OneToOneField(DjangoGroup, editable=False, parent_link=True) group_as_person = models.BooleanField(default=False) + description = models.TextField(blank=True) + + @models.permalink + def get_absolute_url(self, link='edit'): + """ + Return the URL to this user. + + link can be: + * edit + * delete + """ + if link == 'edit': + return ('user_group_edit', [str(self.id)]) + if link == 'delete': + return ('user_group_delete', [str(self.id)]) def __unicode__(self): - return unicode(self.group) + return unicode(self.name) -class OpenSlidesUsersConnecter(object): - def __init__(self, person_prefix=None, id=None): - self.person_prefix = person_prefix - self.id = id +class UsersConnector(object): + def __init__(self, person_prefix_filter=None, id_filter=None): + self.person_prefix_filter = person_prefix_filter + self.id_filter = id_filter + self.users = User.objects.all() + self.groups = Group.objects.filter(group_as_person=True) def __iter__(self): - if (not self.person_prefix or - self.person_prefix == OpenSlidesUser.person_prefix): - if self.id: - yield OpenSlidesUser.objects.get(pk=self.id) + if (not self.person_prefix_filter or + self.person_prefix_filter == User.person_prefix): + if self.id_filter: + yield users.get(pk=self.id_filter) else: - for user in OpenSlidesUser.objects.all(): + for user in self.users: yield user - if (not self.person_prefix or - self.person_prefix == OpenSlidesGroup.person_prefix): - if self.id: - yield OpenSlidesGroup.objects.get(pk=self.id) + if (not self.person_prefix_filter or + self.person_prefix_filter == Group.person_prefix): + if self.id_filter: + yield groups.get(pk=self.id_filter) else: - for group in OpenSlidesGroup.objects.all(): + for group in self.groups: yield group def __getitem__(self, key): - return OpenSlidesUser.objects.get(pk=key) + return User.objects.get(pk=key) -@receiver(receiv_persons, dispatch_uid="participant") -def receiv_persons(sender, **kwargs): - return OpenSlidesUsersConnecter(person_prefix=kwargs['person_prefix'], - id=kwargs['id']) +@receiver(receive_persons, dispatch_uid="participant") +def receive_persons(sender, **kwargs): + return UsersConnecter(person_prefix_filter=kwargs['person_prefix_filter'], + id=kwargs['id_filter']) @receiver(default_config_value, dispatch_uid="participant_default_config") @@ -150,7 +173,17 @@ def default_config(sender, key, **kwargs): }.get(key) -@receiver(signals.post_save, sender=User) +@receiver(signals.post_save, sender=DjangoUser) def user_post_save(sender, instance, signal, *args, **kwargs): - # Creates OpenSlidesUser - profile, new = OpenSlidesUser.objects.get_or_create(user=instance) + try: + instance.user + except User.DoesNotExist: + User(django_user=instance).save_base(raw=True) + + +@receiver(signals.post_save, sender=DjangoGroup) +def group_post_save(sender, instance, signal, *args, **kwargs): + try: + instance.group + except Group.DoesNotExist: + Group(django_group=instance).save_base(raw=True) diff --git a/openslides/participant/static/javascript/participant.js b/openslides/participant/static/javascript/participant.js index f02e6c3f6..bf17d5202 100644 --- a/openslides/participant/static/javascript/participant.js +++ b/openslides/participant/static/javascript/participant.js @@ -9,17 +9,20 @@ $(function() { $('.status_link').click(function(event) { event.preventDefault(); link = $(this); + group = $(this).parent(); $.ajax({ type: 'GET', url: link.attr('href'), dataType: 'json', success: function(data) { if (data.active) { - link.addClass('active'); + group.children('.status_link.deactivate').show(); + group.children('.status_link.activate').hide(); } else { - link.removeClass('active'); + group.children('.status_link.deactivate').hide(); + group.children('.status_link.activate').show(); } } }); }); -}); \ No newline at end of file +}); diff --git a/openslides/participant/static/styles/participant.css b/openslides/participant/static/styles/participant.css index 3d2820f49..0d656c9c8 100644 --- a/openslides/participant/static/styles/participant.css +++ b/openslides/participant/static/styles/participant.css @@ -4,14 +4,19 @@ * :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. * :license: GNU GPL, see LICENSE for more details. */ + a.status_link span { - background-image: url(../images/icons/off.png); background-repeat: no-repeat; background-position: center; width: 16px; height: 16px; display: inline-block; } -a.status_link.active span { + +a.status_link.deactivate span { background-image: url(../images/icons/on.png); } + +a.status_link.activate span { + background-image: url(../images/icons/off.png); +} diff --git a/openslides/participant/templates/participant/edit.html b/openslides/participant/templates/participant/edit.html index ca7c2b649..da910b19e 100644 --- a/openslides/participant/templates/participant/edit.html +++ b/openslides/participant/templates/participant/edit.html @@ -4,7 +4,7 @@ {% block title %} {{ block.super }} – - {% if edituser %} + {% if edit_user %} {% trans "Edit participant" %} {% else %} {% trans "New participant" %} @@ -13,30 +13,31 @@ {% block content %} - {% if edituser %} + {% if edit_user %}

{% trans "Edit participant" %}

{% else %}

{% trans "New participant" %}

{% endif %}
{% csrf_token %} - {{ userform.as_p }} - {{ profileform.as_p }} - {% if edituser %} -

{% trans 'Reset to First Password' %}

+ {{ form.as_p }} + {% if edit_user %} +

+ {% trans 'Reset to First Password' %} +

{% endif %}

- - - - - + + + + +

* {% trans "required" %}
diff --git a/openslides/participant/templates/participant/overview.html b/openslides/participant/templates/participant/overview.html index f068dd69c..3e2762a37 100644 --- a/openslides/participant/templates/participant/overview.html +++ b/openslides/participant/templates/participant/overview.html @@ -8,99 +8,108 @@ {% block header %} {% if perms.agenda.can_manage_agenda %} - - + + {% endif %} {% endblock %} + {% block content %}

{% trans "Participants" %}

-

- {% csrf_token %} - {% trans "Filter" %}: - - - - - +

+ + {% trans "Filter" %}: + + + + +

- {% if users|length == allusers|length %} - {{ users|length }} - {% blocktrans count counter=users|length %}participant{% plural %}participants{% endblocktrans %} + {% if users.count == allusers %} + {{ users.count }} + {% blocktrans count counter=users.count %}participant{% plural %}participants{% endblocktrans %} {% else %} - {{ users|length }} {% trans "of" %} {{ allusers|length }} {% trans "Participants" %} (= {{ percent }} %) + {{ users.count }} {% trans "of" %} {{ allusers }} {% trans "Participants" %} (= {{ percent }} %) {% endif %} - + {% if perms.participant.can_manage_participant %} - - - + + + {% endif %} - {% for user in users %} - - - - - - - {% if perms.participant.can_manage_participant %} - - - - {% endif %} - - {% empty %} - - - - {% endfor %} + {% for user in users %} + + + + + + + {% if perms.participant.can_manage_participant %} + + + + {% endif %} + + {% empty %} + + + + {% endfor %}
{% trans "First Name" %} {% trans "Last Name" %}{% trans "Group" %}{% trans "Category" %} {% trans "Type" %} {% trans "Committee" %}{% trans "Comment" %}{% trans "Last Login" %}{% trans "Actions" %}{% trans "Comment" %}{% trans "Last Login" %}{% trans "Actions" %}
{{ user.first_name }}{{ user.last_name }}{{ user.openslidesuser.name_surfix }}{{ user.openslidesuser.get_type_display }}{{ user.openslidesuser.committee }}{{ user.openslidesuser.comment|first_line }}{% if user.last_login > user.date_joined %} - {{ user.last_login }} - {% endif %} - - - - - - -
{% trans "No participants available." %}
{{ user.first_name }}{{ user.last_name }}{{ user.category }}{{ user.get_type_display }}{{ user.committee }}{{ user.comment|first_line }} + {% if user.last_login > user.date_joined %} + {{ user.last_login }} + {% endif %} + + + + + + + + + + + +
{% trans "No participants available." %}
{% endblock %} diff --git a/openslides/participant/tests.py b/openslides/participant/tests.py index c7dbadf46..4bba1168d 100644 --- a/openslides/participant/tests.py +++ b/openslides/participant/tests.py @@ -12,61 +12,65 @@ from django.test import TestCase from django.test.client import Client -from django.contrib.auth.models import User, Group -from django.db.models.query import EmptyQuerySet from django.contrib.auth.hashers import check_password from openslides.utils.person import get_person, Persons from openslides.participant.api import gen_username, gen_password -from openslides.participant.models import OpenSlidesUser, OpenSlidesGroup +from openslides.participant.models import User, Group -class OpenSlidesUserTest(TestCase): +class UserTest(TestCase): def setUp(self): - self.user1 = User(first_name=u'Max', last_name=u'Mustermann') - self.user1.username = gen_username(self.user1.first_name, self.user1.last_name) + self.user1 = User() + self.user1.first_name = u'Max' + self.user1.last_name = u'Mustermann' + self.user1.username = gen_username( + self.user1.first_name, self.user1.last_name) + self.user1.firstpassword = gen_password() self.user1.save() - self.openslidesuser1 = self.user1.openslidesuser - self.openslidesuser1.firstpassword = gen_password() - self.openslidesuser1.save() - self.user1 = self.openslidesuser1.user + self.django_user1 = self.user1.django_user def test_participant_user(self): - self.assertEqual(self.user1.openslidesuser, self.openslidesuser1) - self.assertEqual(self.user1, self.openslidesuser1.user) + self.assertEqual(self.django_user1.user, self.user1) + self.assertEqual(self.django_user1, self.user1.django_user) def test_repr(self): - self.assertEqual(unicode(self.openslidesuser1), u'Max Mustermann') + self.assertEqual(unicode(self.user1), u'Max Mustermann') def test_name_surfix(self): - self.openslidesuser1.name_surfix = u'München' - self.openslidesuser1.save() - self.assertEqual(unicode(self.openslidesuser1), u'Max Mustermann (München)') + self.user1.category = u'München' + self.user1.save() + self.assertEqual(unicode(self.user1), u'Max Mustermann (München)') def test_reset_password(self): - self.assertIsInstance(self.openslidesuser1.firstpassword, basestring) - self.assertEqual(len(self.openslidesuser1.firstpassword), 8) + self.assertIsInstance(self.user1.firstpassword, basestring) + self.assertEqual(len(self.user1.firstpassword), 8) self.user1.set_unusable_password() - self.assertFalse(self.user1.check_password(self.openslidesuser1.firstpassword)) - self.openslidesuser1.reset_password() - self.assertTrue(self.user1.check_password(self.openslidesuser1.firstpassword)) + self.assertFalse(self.user1.check_password(self.user1.firstpassword)) + self.user1.reset_password() + self.assertTrue(self.user1.check_password(self.user1.firstpassword)) def test_person_api(self): - self.assertTrue(hasattr(self.openslidesuser1, 'person_id')) - self.assertEqual(self.openslidesuser1.person_id, 'openslides_user:1') - self.assertEqual(get_person('openslides_user:1'), self.openslidesuser1) - self.assertEqual(len(Persons()), 1) + self.assertTrue(hasattr(self.user1, 'person_id')) + self.assertEqual(self.user1.person_id, 'user:1') + self.assertEqual(get_person('user:1'), self.user1) + self.assertEqual(len(Persons(person_prefix='user')), 1) -class OpenSlidesGroupTest(TestCase): +class GroupTest(TestCase): def setUp(self): self.group1 = Group.objects.create(name='Test Group') - self.openslidesgroup1 = OpenSlidesGroup.objects.create(group=self.group1) + self.django_group1 = self.group1.django_group - def test_group_openslidesgroup(self): - self.assertEqual(self.openslidesgroup1.group, self.group1) + def test_group_group(self): + self.assertEqual(self.group1.django_group, self.django_group1) + self.assertEqual(self.group1, self.django_group1.group) def test_person_api(self): - self.assertTrue(hasattr(self.openslidesgroup1, 'person_id')) - self.assertEqual(self.openslidesgroup1.person_id, 'openslides_group:1') - self.assertEqual(get_person('openslides_group:1'), self.openslidesgroup1) + self.assertTrue(hasattr(self.group1, 'person_id')) + person_id = "group:%d" % self.group1.id + self.assertEqual(self.group1.person_id, person_id) + self.assertRaises(Group.DoesNotExist) + self.group1.group_as_person = True + self.group1.save() + self.assertEqual(get_person(person_id), self.group1) diff --git a/openslides/participant/urls.py b/openslides/participant/urls.py index e0245d575..f55c30756 100644 --- a/openslides/participant/urls.py +++ b/openslides/participant/urls.py @@ -13,70 +13,86 @@ from django.conf.urls.defaults import url, patterns from django.core.urlresolvers import reverse -from openslides.participant.views import (ParticipantsListPDF, - ParticipantsPasswordsPDF) +from openslides.participant.views import ( + ParticipantsListPDF, ParticipantsPasswordsPDF, Overview, UserCreateView, + UserUpdateView, UserDeleteView, SetUserStatusView, UserImportView, + ResetPasswordView, GroupOverviewView, GroupCreateView, GroupUpdateView, + GroupDeleteView) urlpatterns = patterns('openslides.participant.views', url(r'^$', - 'get_overview', + Overview.as_view(), name='user_overview', ), url(r'^new/$', - 'edit', + UserCreateView.as_view(), name='user_new', ), - url(r'^(?P\d+)/edit/$', - 'edit', + url(r'^(?P\d+)/edit/$', + UserUpdateView.as_view(), name='user_edit', ), + url(r'^(?P\d+)/del/$', + UserDeleteView.as_view(), + name='user_delete', + ), + + url(r'^(?P\d+)/reset_password/$', + ResetPasswordView.as_view(), + name='user_reset_password', + ), + + url(r'^(?P\d+)/status/toggle/$', + SetUserStatusView.as_view(), + {'action': 'toggle'}, + name='user_status_toggle', + ), + + url(r'^(?P\d+)/status/activate/$', + SetUserStatusView.as_view(), + {'action': 'activate'}, + name='user_status_activate', + ), + + url(r'^(?P\d+)/status/deactivate/$', + SetUserStatusView.as_view(), + {'action': 'deactivate'}, + name='user_status_deactivate', + ), + + url(r'^import/$', + UserImportView.as_view(), + name='user_import', + ), + + url(r'^group/$', + GroupOverviewView.as_view(), + name='user_group_overview', + ), + + url(r'^group/new/$', + GroupCreateView.as_view(), + name='user_group_new', + ), + + url(r'^group/(?P\d+)/edit/$', + GroupUpdateView.as_view(), + name='user_group_edit', + ), + + url(r'^group/(?P\d+)/del/$', + GroupDeleteView.as_view(), + name='user_group_delete', + ), + url(r'^print/$', ParticipantsListPDF.as_view(), name='user_print', ), - url(r'^(?P\d+)/del/$', - 'user_delete', - name='user_delete', - ), - - url(r'^(?P\d+)/status/$', - 'user_set_status', - name='user_status', - ), - - url(r'^import/$', - 'user_import', - name='user_import', - ), - - url(r'^group/$', - 'get_group_overview', - name='user_group_overview', - ), - - url(r'^group/new/$', - 'group_edit', - name='user_group_new', - ), - - url(r'^group/(?P\d+)/edit/$', - 'group_edit', - name='user_group_edit', - ), - - url(r'^group/(?P\d+)/del/$', - 'group_delete', - name='user_group_delete', - ), - - url(r'^resetpassword/(?P\d+)/$', - 'reset_password', - name='user_reset_password', - ), - url(r'^passwords/print/$', ParticipantsPasswordsPDF.as_view(), name='print_passwords', diff --git a/openslides/participant/views.py b/openslides/participant/views.py index 3fd5f6a55..64538db49 100644 --- a/openslides/participant/views.py +++ b/openslides/participant/views.py @@ -13,7 +13,6 @@ # for python 2.5 support from __future__ import with_statement -import csv from urllib import urlencode try: @@ -24,351 +23,405 @@ except ImportError: # python <= 2.5 grab it from cgi from reportlab.lib import colors from reportlab.lib.units import cm from reportlab.platypus import ( - SimpleDocTemplate, PageBreak, Paragraph, LongTable, Spacer, Table, - TableStyle) + SimpleDocTemplate, 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.models import User 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, ugettext_lazy +from django.utils.translation import ugettext as _, ugettext_lazy -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 + template, decodedict, encodedict, delete_default_permissions, html_strong) +from openslides.utils.views import ( + FormView, PDFView, CreateView, UpdateView, DeleteView, + RedirectView, SingleObjectMixin, ListView, QuestionMixin) from openslides.config.models import config -from openslides.participant.models import OpenSlidesUser, OpenSlidesGroup -from openslides.participant.api import gen_username, gen_password +from openslides.participant.api import gen_username, gen_password, import_users from openslides.participant.forms import ( - UserNewForm, UserEditForm, OpenSlidesUserForm, UsersettingsForm, - UserImportForm, GroupForm, AdminPasswordChangeForm, ConfigForm) + UserCreateForm, UserUpdateForm, UsersettingsForm, + UserImportForm, GroupForm, ConfigForm) +from openslides.participant.models import User, Group -@permission_required('participant.can_see_participant') -@template('participant/overview.html') -def get_overview(request): +class Overview(ListView): """ - Show all users. + Show all participants. """ - try: - sortfilter = encodedict(parse_qs( - request.COOKIES['participant_sortfilter'])) - except KeyError: - sortfilter = {} + permission_required = 'participant.can_see_participant' + template_name = 'participant/overview.html' + context_object_name = 'users' - 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( - openslidesuser__gender__iexact=sortfilter['gender'][0]) - if 'group' in sortfilter: - query = query.filter( - openslidesuser__name_surfix__iexact=sortfilter['group'][0]) - if 'type' in sortfilter: - query = query.filter( - openslidesuser__type__iexact=sortfilter['type'][0]) - if 'committee' in sortfilter: - query = query.filter( - openslidesuser__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 - ['name_surfix', 'type', 'committee', 'comment']): - query = query.order_by( - 'openslidesuser__%s' % sortfilter['sort'][0]) - else: - query = query.order_by('last_name') - if 'reverse' in sortfilter: - query = query.reverse() - - # list of filtered users - userlist = query.all() - users = [] - for user in userlist: + def get_queryset(self): try: - user.openslidesuser - except OpenSlidesUser.DoesNotExist: - pass + sortfilter = encodedict(parse_qs( + self.request.COOKIES['participant_sortfilter'])) + except KeyError: + sortfilter = {} + + for value in [u'gender', u'category', u'type', u'committee', u'status', + u'sort', u'reverse']: + if value in self.request.REQUEST: + if self.request.REQUEST[value] == '---': + try: + del sortfilter[value] + except KeyError: + pass + else: + sortfilter[value] = [self.request.REQUEST[value]] + + query = User.objects + if 'gender' in sortfilter: + query = query.filter(gender__iexact=sortfilter['gender'][0]) + if 'category' in sortfilter: + query = query.filter(category__iexact=sortfilter['category'][0]) + if 'type' in sortfilter: + query = query.filter(type__iexact=sortfilter['type'][0]) + if 'committee' in sortfilter: + query = query.filter(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 + ['category', 'type', 'committee', 'comment']): + query = query.order_by( + '%s' % sortfilter['sort'][0]) else: - users.append(user) - # list of all existing users - allusers = [] - for user in User.objects.all(): - try: - user.openslidesuser - except OpenSlidesUser.DoesNotExist: - pass + query = query.order_by('last_name') + + if 'reverse' in sortfilter: + query = query.reverse() + + self.sortfilter = sortfilter + + return query.all() + + def get_context_data(self, **kwargs): + context = super(Overview, self).get_context_data(**kwargs) + + all_users = User.objects.count() + + # quotient of selected users and all users + if all_users > 0: + percent = self.object_list.count() * 100 / float(all_users) else: - allusers.append(user) - # 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['name_surfix'] for p in OpenSlidesUser.objects.values('name_surfix') - .exclude(name_surfix='').distinct()] - # list of all existing committees - committees = [p['committee'] for p in OpenSlidesUser.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, - } + percent = 0 + + # list of all existing categories + categories = [p['category'] for p in User.objects.values('category') + .exclude(category='').distinct()] + + # list of all existing committees + committees = [p['committee'] for p in User.objects.values('committee') + .exclude(committee='').distinct()] + context.update({ + 'allusers': all_users, + 'percent': round(percent, 1), + 'categories': categories, + 'committees': committees, + 'cookie': ['participant_sortfilter', urlencode(decodedict(self.sortfilter), + doseq=True)], + 'sortfilter': self.sortfilter}) + return context -@permission_required('participant.can_manage_participant') -@template('participant/edit.html') -def edit(request, user_id=None): +class UserCreateView(CreateView): """ - View to create and edit users. + Create a new participant. """ - if user_id is not None: - user = User.objects.get(id=user_id) - else: - user = None + permission_required = 'participant.can_manage_participant' + template_name = 'participant/edit.html' + model = User + context_object_name = 'edit_user' + form_class = UserCreateForm + success_url = 'user_overview' + apply_url = 'user_edit' - if request.method == 'POST': - if user_id is None: - user_form = UserNewForm(request.POST, prefix="user") - openslides_user_form = OpenSlidesUserForm(request.POST, prefix="openslidesuser") - else: - user_form = UserEditForm(request.POST, instance=user, prefix="user") - openslides_user_form = OpenSlidesUserForm(request.POST, instance=user.openslidesuser, - prefix="openslidesuser") - - if user_form.is_valid() and openslides_user_form.is_valid(): - user = user_form.save(commit=False) - if user_id is None: - # TODO: call first_name and last_name though openslides_user - user.username = gen_username(user.first_name, user.last_name) - user.save() - openslides_user = user.openslidesuser - openslides_user_form = OpenSlidesUserForm(request.POST, instance=openslides_user, prefix="openslidesuser") - openslides_user_form.is_valid() - openslides_user = openslides_user_form.save(commit=False) - openslides_user.user = user - if user_id is None: - if not openslides_user.firstpassword: - openslides_user.firstpassword = gen_password() - openslides_user.user.set_password(openslides_user.firstpassword) - # TODO: Try not to save the user object - openslides_user.user.save() - openslides_user.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: - user_form = UserNewForm(prefix="user") - openslides_user_form = OpenSlidesUserForm(prefix="openslidesuser") - else: - user_form = UserEditForm(instance=user, prefix="user") - openslides_user_form = OpenSlidesUserForm(instance=user.openslidesuser, prefix="openslidesuser") - # TODO: rename template vars - return { - 'userform': user_form, - 'profileform': openslides_user_form, - 'edituser': user, - } + 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() -@permission_required('participant.can_manage_participant') -@template('confirm.html') -def user_delete(request, user_id): +class UserUpdateView(UpdateView): """ - Delete an user. + Update an existing participant. """ - 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_name = 'participant/edit.html' + model = User + context_object_name = 'edit_user' + form_class = UserUpdateForm + success_url = 'user_overview' + apply_url = 'participant_edit' -@permission_required('participant.can_manage_participant') -@template('confirm.html') -def user_set_status(request, user_id): +class UserDeleteView(DeleteView): """ - Set the status of an user. + Delete an participant. """ - 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' + model = User + url = 'user_overview' -@permission_required('participant.can_manage_participant') -@template('participant/group_overview.html') -def get_group_overview(request): +class SetUserStatusView(RedirectView, SingleObjectMixin): """ - Show all groups. + Activate or deactivate an user. """ - 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' + allow_ajax = True + url = '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': + 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 -@permission_required('participant.can_manage_participant') -@template('participant/group_edit.html') -def group_edit(request, group_id=None): +class ParticipantsListPDF(PDFView): """ - Edit a group. + Generate the userliste as PDF. """ - if group_id is not None: - try: - group = Group.objects.get(id=group_id) - except Group.DoesNotExist: - # TODO: return a 404 Object - raise NameError("There is no group %d" % group_id) - else: - group = None - delete_default_permissions() + permission_required = 'participant.can_see_participant' + filename = ugettext_lazy("Participant-list") + document_title = ugettext_lazy('List of Participants') - if request.method == 'POST': - form = GroupForm(request.POST, instance=group) - if form.is_valid(): - # TODO: This can be done inside the form - group_name = form.cleaned_data['name'].lower() - - # TODO: Why is this code called on any request and not only, if the - # anonymous_group is edited? - try: - anonymous_group = Group.objects.get(name='Anonymous') - except Group.DoesNotExist: - anonymous_group = None - - # special handling for anonymous auth - # TODO: This code should be a form validator. - 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() - try: - openslides_group = OpenSlidesGroup.objects.get(group=group) - except OpenSlidesGroup.DoesNotExist: - django_group = None - if form.cleaned_data['as_user'] and django_group is None: - OpenSlidesGroup(group=group).save() - elif not form.cleaned_data['as_user'] and django_group: - django_group.delete() - - 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: - if group and OpenSlidesGroup.objects.filter(group=group).exists(): - initial = {'as_user': True} - else: - initial = {'as_user': False} - - form = GroupForm(instance=group, initial=initial) - return { - 'form': form, - 'group': group, - } + 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): + counter += 1 + data.append([ + counter, + Paragraph(user.last_name, stylesheet['Tablecell']), + Paragraph(user.first_name, stylesheet['Tablecell']), + Paragraph(user.category, stylesheet['Tablecell']), + Paragraph(user.type, 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) -@permission_required('participant.can_manage_participant') -def group_delete(request, group_id): +class ParticipantsPasswordsPDF(PDFView): """ - Delete a group. + Generate the Welcomepaper for the users. """ - 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')) + 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"] + for user in User.objects.all().order_by('last_name'): + cell = [] + cell.append(Spacer(0, 0.8 * cm)) + cell.append(Paragraph(_("Account for OpenSlides"), + stylesheet['Ballot_title'])) + cell.append(Paragraph(_("for %s") % (user), + 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.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]) + + # 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([ + ('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 + + 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(RedirectView, SingleObjectMixin, QuestionMixin): + """ + Set the Passwort for a user to his firstpassword. + """ + permission_required = 'participant.can_manage_participant' + model = User + allow_ajax = True + question = 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 pre_redirect(self, request, *args, **kwargs): + self.confirm_form() + + def pre_post_redirect(self, request, *args, **kwargs): + if self.get_answer().lower() == 'yes': + self.object.reset_password() + messages.success(request, + _('The Password for %s was successfully reset.') % html_strong(self.object)) + + def get_answer_url(self): + return reverse('user_reset_password', args=[self.object.id]) + + +class GroupOverviewView(ListView): + """ + Overview over all groups. + """ + permission_required = 'participant.can_manage_participant' + template_name = 'participant/group_overview.html' + context_object_name = 'groups' + model = Group + + +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 = 'user_group_overview' + apply_url = 'user_group_edit' + + 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 = 'user_group_overview' + apply_url = 'user_group_edit' + + def get(self, request, *args, **kwargs): + delete_default_permissions() + return super(GroupUpdateView, self).get(request, *args, **kwargs) + + +class GroupDeleteView(DeleteView): + """ + Delete a Group. + """ + permission_required = 'participant.can_manage_participant' + model = Group + url = 'user_group_overview' + + +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) @login_required @@ -415,187 +468,6 @@ def user_settings_password(request): } -@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: @@ -605,10 +477,10 @@ def login(request): "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'])} + "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 @@ -623,134 +495,6 @@ def register_tab(request): 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 = ugettext_lazy("Participant-list") - document_title = ugettext_lazy('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 = 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"] - 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 OpenSlidesUser.DoesNotExist: - pass - # add empty table line if no participants available - if data == []: - data.append(['', '']) - # build table - 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) + permission=request.user.has_perm('participant.can_see_participant') or + request.user.has_perm('participant.can_manage_participant'), + selected=selected) diff --git a/openslides/urls.py b/openslides/urls.py index d3f0e9d91..0a21c503e 100644 --- a/openslides/urls.py +++ b/openslides/urls.py @@ -55,7 +55,6 @@ for plugin in settings.INSTALLED_PLUGINS: urlpatterns += patterns('', - (r'^500/$', 'openslides.utils.views.server_error'), (r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict), url(r'^login/$', diff --git a/openslides/utils/person/__init__.py b/openslides/utils/person/__init__.py index e54e6258a..da896616f 100644 --- a/openslides/utils/person/__init__.py +++ b/openslides/utils/person/__init__.py @@ -10,7 +10,7 @@ :license: GNU GPL, see LICENSE for more details. """ -from openslides.utils.person.signals import receiv_persons +from openslides.utils.person.signals import receive_persons from openslides.utils.person.api import generate_person_id, get_person, Persons from openslides.utils.person.forms import PersonFormField, MultiplePersonFormField from openslides.utils.person.models import PersonField, PersonMixin diff --git a/openslides/utils/person/api.py b/openslides/utils/person/api.py index 59e7d7a1a..55b4691a8 100644 --- a/openslides/utils/person/api.py +++ b/openslides/utils/person/api.py @@ -10,16 +10,16 @@ :license: GNU GPL, see LICENSE for more details. """ -from openslides.utils.person.signals import receiv_persons +from openslides.utils.person.signals import receive_persons class Persons(object): """ A Storage for a multiplicity of different Person-Objects. """ - def __init__(self, person_prefix=None, id=None): - self.person_prefix = person_prefix - self.id = id + def __init__(self, person_prefix_filter=None, id_filter=None): + self.person_prefix_filter = person_prefix_filter + self.id_filter = id_filter def __iter__(self): try: @@ -35,14 +35,16 @@ class Persons(object): def iter_persons(self): self._cache = list() - for receiver, persons in receiv_persons.send( - sender='persons', person_prefix=self.person_prefix, id=self.id): + for receiver, persons in receive_persons.send( + sender='persons', person_prefix_filter=self.person_prefix_filter, id_filter=self.id_filter): for person in persons: self._cache.append(person) yield person def generate_person_id(prefix, id): + assert prefix is not None + assert id is not None if ':' in prefix: raise ValueError("':' is not allowed in a the 'person_prefix'") return "%s:%d" % (prefix, id) @@ -61,4 +63,4 @@ def get_person(person_id): except TypeError: from openslides.utils.person import EmtyPerson return EmtyPerson() - return Persons(person_prefix=person_prefix, id=id)[0] + return Persons(person_prefix_filter=person_prefix, id_filter=id)[0] diff --git a/openslides/utils/person/models.py b/openslides/utils/person/models.py index 3df620dfd..cf16f7286 100644 --- a/openslides/utils/person/models.py +++ b/openslides/utils/person/models.py @@ -57,7 +57,7 @@ class PersonMixin(object): try: return generate_person_id(self.person_prefix, self.pk) except AttributeError: - raise AttributeError("%s has to have a attribute 'user_prefix'" + raise AttributeError("%s has to have a attribute 'person_prefix'" % self) def __repr__(self): diff --git a/openslides/utils/person/signals.py b/openslides/utils/person/signals.py index 5d8dba88b..32d5879b4 100644 --- a/openslides/utils/person/signals.py +++ b/openslides/utils/person/signals.py @@ -12,4 +12,4 @@ from django.dispatch import Signal -receiv_persons = Signal(providing_args=['person_prefix', 'id']) +receive_persons = Signal(providing_args=['person_prefix_filter', 'id_filter']) diff --git a/openslides/utils/views.py b/openslides/utils/views.py index 0c3667025..becdbfc8b 100644 --- a/openslides/utils/views.py +++ b/openslides/utils/views.py @@ -34,7 +34,7 @@ from django.conf import settings from django.dispatch import receiver from django.http import HttpResponseServerError, HttpResponse, HttpResponseRedirect from django.utils.decorators import method_decorator -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext as _, ugettext_noop, ugettext_lazy from django.utils.importlib import import_module from django.template import loader, RequestContext from django.template.loader import render_to_string @@ -105,6 +105,50 @@ class AjaxMixin(object): return HttpResponse(json.dumps(self.get_ajax_context(**kwargs))) +class QuestionMixin(object): + question = ugettext_lazy('Are you sure?') + success_message = ugettext_lazy('Thank you for your answer') + answer_options = [('yes', ugettext_lazy("Yes")), ('no', ugettext_lazy("No"))] + + def get_answer_options(self): + return self.answer_options + + def get_question(self): + return unicode(self.question) + + def get_answer(self): + for option in self.get_answer_options(): + if option[0] in self.request.POST: + return option[0] + return None + + def get_answer_url(self): + return self.answer_url + + def confirm_form(self): + option_fields = "\n".join([ + '' % (option[0], unicode(option[1])) + for option in self.get_answer_options()]) + messages.warning(self.request, + """ + %(message)s +
+ + %(option_fields)s +
+ """ % { + 'message': self.get_question(), + 'url': self.get_answer_url(), + 'csrf': csrf(self.request)['csrf_token'], + 'option_fields': option_fields}) + + def pre_redirect(self, request, *args, **kwargs): + self.confirm_form(request, self.object) + + def pre_post_redirect(self, request, *args, **kwargs): + messages.success(request) + + class TemplateView(PermissionMixin, _TemplateView): def get_context_data(self, **kwargs): context = super(TemplateView, self).get_context_data(**kwargs) @@ -208,42 +252,41 @@ class CreateView(PermissionMixin, _CreateView): messages.error(self.request, _('Please check the form for errors.')) return super(CreateView, self).form_invalid(form) + def form_valid(self, form): + self.object = form.save(commit=False) + self.manipulate_object(form) + self.object.save() + form.save_m2m() + return HttpResponseRedirect(self.get_success_url()) + def get_success_message(self): return _('%s was successfully created.') % html_strong(self.object) + def manipulate_object(self, form): + pass -class DeleteView(RedirectView, SingleObjectMixin): - def get_confirm_question(self): + +class DeleteView(RedirectView, SingleObjectMixin, QuestionMixin): + def get_question(self): return _('Do you really want to delete %s?') % html_strong(self.object) def get_success_message(self): return _('%s was successfully deleted.') % html_strong(self.object) def pre_redirect(self, request, *args, **kwargs): - self.confirm_form(request, self.object) + self.confirm_form() def pre_post_redirect(self, request, *args, **kwargs): - self.object.delete() - messages.success(request, self.get_success_message()) + if self.get_answer().lower() == 'yes': + self.object.delete() + messages.success(request, self.get_success_message()) def get(self, request, *args, **kwargs): self.object = self.get_object() return super(DeleteView, self).get(request, *args, **kwargs) - def confirm_form(self, request, object): - self.gen_confirm_form(request, self.get_confirm_question(), - object.get_absolute_url('delete')) - - def gen_confirm_form(self, request, message, url): - messages.warning(request, - """ - %s -
- - - -
- """ % (message, url, csrf(request)['csrf_token'], _("Yes"), _("No"))) + def get_answer_url(self): + return self.object.get_absolute_url('delete') class DetailView(TemplateView, SingleObjectMixin):