diff --git a/openslides/application/forms.py b/openslides/application/forms.py index 9da1f3a83..34a7ef9b9 100644 --- a/openslides/application/forms.py +++ b/openslides/application/forms.py @@ -15,7 +15,7 @@ from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _, ugettext_noop from openslides.utils.forms import CssClassMixin -from openslides.utils.user import UserFormField +from openslides.utils.user import UserFormField, MultipleUserFormField from openslides.application.models import Application @@ -59,11 +59,8 @@ class ApplicationManagerForm(forms.ModelForm, CssClassMixin): class ApplicationManagerFormSupporter(ApplicationManagerForm): - supporter = UserModelMultipleChoiceField( - queryset=User.objects.all().exclude(profile=None). - order_by("first_name"), - required=False, label=_("Supporters"), - ) + # TODO: Do not show the submitter in the user-list + supporter = MultipleUserFormField(required=False, label=_("Supporters")) class ApplicationImportForm(forms.Form, CssClassMixin): diff --git a/openslides/application/models.py b/openslides/application/models.py index e0d8b738b..4b45a5ed8 100644 --- a/openslides/application/models.py +++ b/openslides/application/models.py @@ -37,6 +37,11 @@ from openslides.projector.models import SlideMixin from openslides.agenda.models import Item +class ApplicationSupporter(models.Model): + application = models.ForeignKey("Application") + user = UserField() + + class Application(models.Model, SlideMixin): prefix = "application" STATUS = ( @@ -62,8 +67,6 @@ class Application(models.Model, SlideMixin): ) submitter = UserField(verbose_name=_("Submitter")) - supporter = models.ManyToManyField(User, related_name='supporter', \ - null=True, blank=True, verbose_name=_("Supporters")) number = models.PositiveSmallIntegerField(blank=True, null=True, unique=True) status = models.CharField(max_length=3, choices=STATUS, default='pub') @@ -158,6 +161,13 @@ class Application(models.Model, SlideMixin): else: return False + @property + def supporters(self): + return [object.user for object in self.applicationsupporter_set.all()] + + def is_supporter(self, user): + return self.applicationsupporter_set.filter(user=user).exists() + @property def enough_supporters(self): """ @@ -165,17 +175,20 @@ class Application(models.Model, SlideMixin): """ min_supporters = int(config['application_min_supporters']) if self.status == "pub": - return self.supporter.count() >= min_supporters + return self.count_supporters() >= min_supporters else: return True + def count_supporters(self): + return self.applicationsupporter_set.count() + @property def missing_supporters(self): """ Return number of missing supporters """ min_supporters = int(config['application_min_supporters']) - delta = min_supporters - self.supporter.count() + delta = min_supporters - self.count_supporters() if delta > 0: return delta else: @@ -222,10 +235,11 @@ class Application(models.Model, SlideMixin): except AttributeError: is_manager = False + supporters = self.applicationsupporter_set.all() if (self.status == "pub" - and self.supporter.exists() + and supporters and not is_manager): - self.supporter.clear() + supporters.delete() self.writelog(_("Supporters removed"), user) def reset(self, user): @@ -242,29 +256,36 @@ class Application(models.Model, SlideMixin): Add a Supporter to the list of supporters of the application. """ if user == self.submitter: + # TODO: Use own Exception raise NameError('Supporter can not be the submitter of a ' \ 'application.') if self.permitted is not None: + # TODO: Use own Exception raise NameError('This application is already permitted.') - if user not in self.supporter.all(): - self.supporter.add(user) - self.writelog(_("Supporter: +%s") % (user)) + if not self.is_supporter(user): + ApplicationSupporter(application=self, user=user).save() + self.writelog(_("Supporter: +%s") % (user)) def unsupport(self, user): """ remove a supporter from the list of supporters of the application """ if self.permitted is not None: + # TODO: Use own Exception raise NameError('This application is already permitted.') - if user in self.supporter.all(): - self.supporter.remove(user) - self.writelog(_("Supporter: -%s") % (user)) + try: + object = self.applicationsupporter_set.get(user=user).delete() + except ApplicationSupporter.DoesNotExist: + pass + else: + self.writelog(_("Supporter: -%s") % (user)) def set_number(self, number=None, user=None): """ Set a number for ths application. """ if self.number is not None: + # TODO: Use own Exception raise NameError('This application has already a number.') if number is None: try: @@ -333,7 +354,7 @@ class Application(models.Model, SlideMixin): self.writelog(_("Status modified")+": %s -> %s" \ % (oldstatus, self.get_status_display()), user) - def get_allowed_actions(self, user=None): + def get_allowed_actions(self, user): """ Return a list of all the allowed status. """ @@ -367,16 +388,12 @@ class Application(models.Model, SlideMixin): actions.append("pub") # Check if the user can support and unspoort the application - try: - if (self.status == "pub" - and user != self.submitter - and user not in self.supporter.all() - and getattr(user, 'profile', None)): - actions.append("support") - except Profile.DoesNotExist: - pass + if (self.status == "pub" + and user != self.submitter + and not self.is_supporter(user)): + actions.append("support") - if self.status == "pub" and user in self.supporter.all(): + if self.status == "pub" and self.is_supporter(user): actions.append("unsupport") #Check if the user can edit the application diff --git a/openslides/application/templates/application/view.html b/openslides/application/templates/application/view.html index dd9ad9c69..183f76b50 100644 --- a/openslides/application/templates/application/view.html +++ b/openslides/application/templates/application/view.html @@ -22,16 +22,16 @@ {% endif %} {% if min_supporters > 0 %} -

{% trans "Supporters" %}: *

- {% if application.supporter.count == 0 %} - - - {% else %} -
    - {% for supporter in application.supporter.all %} -
  1. {{ supporter.profile }}
  2. - {% endfor %} -
- {% endif %} +

{% trans "Supporters" %}: *

+ {% if not application.supporters %} + - + {% else %} +
    + {% for supporter in application.supporters %} +
  1. {{ supporter }}
  2. + {% endfor %} +
+ {% endif %} {% endif %}

{% trans "Status" %}:

diff --git a/openslides/application/views.py b/openslides/application/views.py index f9a92a5f5..f4d06cab9 100644 --- a/openslides/application/views.py +++ b/openslides/application/views.py @@ -42,6 +42,7 @@ from openslides.utils.template import Tab from openslides.utils.utils import (template, permission_required, del_confirm_form, gen_confirm_form) from openslides.utils.views import PDFView, RedirectView, DeleteView, FormView +from openslides.utils.user import get_user from openslides.config.models import config @@ -49,7 +50,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.api import gen_username, gen_password, user2djangouser from openslides.participant.models import Profile from openslides.agenda.models import Item @@ -123,7 +124,7 @@ def overview(request): for (i, application) in enumerate(applications): try: applications[i] = { - 'actions' : application.get_allowed_actions(request.user), + 'actions' : application.get_allowed_actions(user2djangouser(request.user)), 'application' : application } except: @@ -151,7 +152,8 @@ def view(request, application_id, newest=False): else: version = application.public_version revisions = application.versions - actions = application.get_allowed_actions(user=request.user) + user = user2djangouser(request.user) + actions = application.get_allowed_actions(user=user) return { 'application': application, @@ -181,11 +183,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 == application.submitter.user) \ + not user2djangouser(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) + actions = application.get_allowed_actions(user=user2djangouser(request.user)) else: application = None actions = None @@ -212,14 +214,14 @@ def edit(request, application_id=None): if valid: del_supporters = True - original_supporters = [] if is_manager: if application: # Edit application - for s in application.supporter.all(): - original_supporters.append(s) + original_supporters = list(application.supporters) + else: + original_supporters = [] application = managerform.save(commit=False) elif application_id is None: - application = Application(submitter=request.user.profile) + application = Application(submitter=user2djangouser(request.user)) application.title = dataform.cleaned_data['title'] application.text = dataform.cleaned_data['text'] application.reason = dataform.cleaned_data['reason'] @@ -229,30 +231,17 @@ def edit(request, application_id=None): and dataform.cleaned_data['trivial_change'] except KeyError: trivial_change = False - application.save(request.user, trivial_change=trivial_change) + application.save(user2djangouser(request.user), trivial_change=trivial_change) if is_manager: - # log added supporters - supporters_added = [] - for s in application.supporter.all(): - if s not in original_supporters: - try: - supporters_added.append(unicode(s.profile)) - except Profile.DoesNotExist: - pass - if len(supporters_added) > 0: - log_added = ", ".join(supporters_added) - application.writelog(_("Supporter: +%s") % log_added, request.user) - # log removed supporters - supporters_removed = [] - for s in original_supporters: - if s not in application.supporter.all(): - try: - supporters_removed.append(unicode(s.profile)) - except Profile.DoesNotExist: - pass - if len(supporters_removed) > 0: - log_removed = ", ".join(supporters_removed) - application.writelog(_("Supporter: -%s") % log_removed, request.user) + # TODO: Deal with the case, that the form has no field 'supporters' + old_supporters = set(application.supporters) + new_supporters = set(managerform.cleaned_data['supporter']) + # add new supporters + for supporter in new_supporters.difference(old_supporters): + application.support(supporter) + # remove old supporters + for supporter in old_supporters.difference(new_supporters): + application.unsupport(supporter) if application_id is None: messages.success(request, _('New application was successfully created.')) else: @@ -268,11 +257,11 @@ def edit(request, application_id=None): if application_id is None: initial = {'text': config['application_preamble']} else: - if application.status == "pub" and application.supporter.exists(): + if application.status == "pub" and application.supporters: if request.user.has_perm('application.can_manage_application'): messages.warning(request, _("Attention: Do you really want to edit this application? The supporters will not be removed automatically because you can manage applications. Please check if the supports are valid after your changing!")) else: - messages.warning(request, _("Attention: Do you really want to edit this application? All %s supporters will be removed! Try to convince the supporters again.") % application.supporter.count() ) + messages.warning(request, _("Attention: Do you really want to edit this application? All %s supporters will be removed! Try to convince the supporters again.") % len(application.supporters) ) initial = {'title': application.title, 'text': application.text, 'reason': application.reason} @@ -280,12 +269,10 @@ def edit(request, application_id=None): dataform = formclass(initial=initial, prefix="data") if is_manager: if application_id is None: - try: - initial = {'submitter': request.user.profile.uid} - except Profile.DoesNotExist: - initial = {} + initial = {'submitter': user2djangouser(request.user).uid} else: initial = {'submitter': application.submitter.uid} + initial['supporter'] = [supporter.uid for supporter in application.supporters] managerform = managerformclass(initial=initial, instance=application, prefix="manager") else: @@ -305,7 +292,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) + Application.objects.get(pk=application_id).set_number(user=user2djangouser(request.user)) messages.success(request, _("Application number was successfully set.")) except Application.DoesNotExist: pass @@ -321,7 +308,7 @@ def permit(request, application_id): permit an application. """ try: - Application.objects.get(pk=application_id).permit(user=request.user) + Application.objects.get(pk=application_id).permit(user=user2djangouser(request.user)) messages.success(request, _("Application was successfully permitted.")) except Application.DoesNotExist: pass @@ -336,7 +323,7 @@ def notpermit(request, application_id): reject (not permit) an application. """ try: - Application.objects.get(pk=application_id).notpermit(user=request.user) + Application.objects.get(pk=application_id).notpermit(user=user2djangouser(request.user)) messages.success(request, _("Application was successfully rejected.")) except Application.DoesNotExist: pass @@ -352,7 +339,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, status=status) + application.set_status(user=user2djangouser(request.user), status=status) messages.success(request, _("Application status was set to: %s.") % application.get_status_display()) except Application.DoesNotExist: pass @@ -368,7 +355,7 @@ def reset(request, application_id): reset an application. """ try: - Application.objects.get(pk=application_id).reset(user=request.user) + Application.objects.get(pk=application_id).reset(user=user2djangouser(request.user)) messages.success(request, _("Application status was reset.") ) except Application.DoesNotExist: pass @@ -382,7 +369,7 @@ def support(request, application_id): support an application. """ try: - Application.objects.get(pk=application_id).support(user=request.user) + Application.objects.get(pk=application_id).support(user=user2djangouser(request.user)) messages.success(request, _("You have support the application successfully.") ) except Application.DoesNotExist: pass @@ -396,7 +383,7 @@ def unsupport(request, application_id): unsupport an application. """ try: - Application.objects.get(pk=application_id).unsupport(user=request.user) + Application.objects.get(pk=application_id).unsupport(user=user2djangouser(request.user)) messages.success(request, _("You have unsupport the application successfully.") ) except Application.DoesNotExist: pass @@ -410,7 +397,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) + poll = Application.objects.get(pk=application_id).gen_poll(user=user2djangouser(request.user)) messages.success(request, _("New vote was successfully created.") ) except Application.DoesNotExist: pass # TODO: do not call poll after this excaption @@ -427,7 +414,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) + application.writelog(_("Poll deleted"), user2djangouser(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])) @@ -467,7 +454,7 @@ class ApplicationDelete(DeleteView): if len(self.applications): for application in self.applications: - if not 'delete' in application.get_allowed_actions(user=request.user): + if not 'delete' in application.get_allowed_actions(user=user2djangouser(request.user)): messages.error(request, _("You can not delete application %s.") % application) continue @@ -476,12 +463,12 @@ 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): - messages.error(request, _("You can not delete application %s.") % self.object) - else: - title = self.object.title - self.object.delete(force=True) - messages.success(request, _("Application %s was successfully deleted.") % title) + if not 'delete' in self.object.get_allowed_actions(user=user2djangouser(request.user)): + messages.error(request, _("You can not delete application %s.") % self.object) + else: + title = self.object.title + self.object.delete(force=True) + messages.success(request, _("Application %s was successfully deleted.") % title) else: messages.error(request, _("Invalid request")) @@ -517,12 +504,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) + context['actions'] = self.application.get_allowed_actions(user=user2djangouser(self.request.user)) return context def get_modelform_class(self): cls = super(ViewPoll, self).get_modelform_class() - user = self.request.user + user = user2djangouser(self.request.user) class ViewPollFormClass(cls): def save(self, commit = True): @@ -544,7 +531,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) + application.accept_version(aversion, user=user2djangouser(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])) @@ -556,7 +543,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): + if application.reject_version(aversion, user=user2djangouser(request.user)): messages.success(request, _("Version %s rejected.") % (aversion.aid)) else: messages.error(request, _("ERROR by rejecting the version.") ) @@ -570,13 +557,14 @@ def reject_version(request, aversion_id): def application_import(request): try: request.user.profile - messages.error(request, _('The import function is available for the admin (without user profile) only.')) - return redirect(reverse('application_overview')) except Profile.DoesNotExist: pass except AttributeError: # AnonymousUser pass + 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) @@ -771,7 +759,7 @@ class ApplicationPDF(PDFView): cell2a.append(Paragraph("%s:" % _("Supporters"), stylesheet['Heading4'])) - for s in application.supporter.all(): + for supporter in application.supporters: cell2b.append(Paragraph(".  %s" % unicode(s.profile), stylesheet['Signaturefield'])) if application.status == "pub": for x in range(0,application.missing_supporters): diff --git a/openslides/participant/api.py b/openslides/participant/api.py index 3dde39c14..77104b98b 100644 --- a/openslides/participant/api.py +++ b/openslides/participant/api.py @@ -15,6 +15,9 @@ import string from django.contrib.auth.models import User +from openslides.utils.user import get_user +from openslides.participant.models import Profile + def gen_password(): """ @@ -44,3 +47,11 @@ def gen_username(first_name, last_name): User.objects.get(username=testname) except User.DoesNotExist: return testname + + +def user2djangouser(user): + u = get_user('djangouser:%d' % user.id) + try: + return u.profile + except Profile.DoesNotExist: + return u diff --git a/openslides/participant/forms.py b/openslides/participant/forms.py index 2d33bb960..610e17b2b 100644 --- a/openslides/participant/forms.py +++ b/openslides/participant/forms.py @@ -90,6 +90,7 @@ class UsersettingsForm(forms.ModelForm, CssClassMixin): model = User fields = ('username', 'first_name', 'last_name', 'email') + class UserImportForm(forms.Form, CssClassMixin): csvfile = forms.FileField(widget=forms.FileInput(attrs={'size':'50'}), label=_("CSV File")) diff --git a/openslides/participant/models.py b/openslides/participant/models.py index fe19ff9de..919e03b95 100644 --- a/openslides/participant/models.py +++ b/openslides/participant/models.py @@ -21,8 +21,6 @@ from openslides.utils.user.signals import receiv_users from openslides.config.signals import default_config_value -from openslides.participant.api import gen_password - class Profile(models.Model, UserMixin): user_prefix = 'participant' @@ -53,7 +51,6 @@ class Profile(models.Model, UserMixin): firstpassword = models.CharField(max_length=100, null=True, blank=True, verbose_name = _("First Password")) - def reset_password(self): """ Reset the password for the user to his default-password. @@ -61,7 +58,6 @@ class Profile(models.Model, UserMixin): self.user.set_password(self.firstpassword) self.user.save() - @models.permalink def get_absolute_url(self, link='edit'): """ @@ -98,6 +94,17 @@ class DjangoGroup(models.Model, UserMixin): return unicode(self.group) +class DjangoUser(User, UserMixin): + user_prefix = 'djangouser' + + def has_no_profile(self): + # TODO: Make ths with a Manager, so it does manipulate the sql query + return not hasattr(self, 'profile') + + class Meta: + proxy = True + + class ParticipantUsers(object): def __init__(self, user_prefix=None, id=None): self.user_prefix = user_prefix @@ -118,6 +125,17 @@ class ParticipantUsers(object): for group in DjangoGroup.objects.all(): yield group + if not self.user_prefix or self.user_prefix == DjangoUser.user_prefix: + if self.id: + yield DjangoUser.objects.get(pk=self.id) + else: + for user in DjangoUser.objects.all(): + if user.has_no_profile(): + yield user + elif self.user_prefix: + # If only users where requested, return the profile object. + yield user.profile + def __getitem__(self, key): return Profile.objects.get(pk=key) diff --git a/openslides/utils/user/__init__.py b/openslides/utils/user/__init__.py index 3a77a827d..df711f6c6 100644 --- a/openslides/utils/user/__init__.py +++ b/openslides/utils/user/__init__.py @@ -57,6 +57,23 @@ class UserFormField(forms.fields.ChoiceField): return super(UserFormField, self).valid_value(value.uid) +class MultipleUserFormField(UserFormField): + widget = forms.widgets.SelectMultiple + + def __init__(self, *args, **kwargs): + super(MultipleUserFormField, self).__init__(empty_label=None, *args, **kwargs) + + def to_python(self, value): + if hasattr(value, '__iter__'): + return [super(MultipleUserFormField, self).to_python(v) for v in value] + return super(MultipleUserFormField, self).to_python(value) + + def valid_value(self, value): + if hasattr(value, '__iter__'): + return [super(MultipleUserFormField, self).valid_value(v) for v in value] + return super(MultipleUserFormField, self).valid_value(value) + + class UserField(models.fields.Field): __metaclass__ = models.SubfieldBase