diff --git a/initial_data.json b/initial_data.json
index e7451296e..ad17d7a56 100644
--- a/initial_data.json
+++ b/initial_data.json
@@ -38,7 +38,7 @@
[
"can_see_participant",
"participant",
- "profile"
+ "openslidesuser"
],
[
"can_see_projector",
@@ -92,7 +92,7 @@
[
"can_see_participant",
"participant",
- "profile"
+ "openslidesuser"
],
[
"can_see_projector",
@@ -161,12 +161,12 @@
[
"can_manage_participant",
"participant",
- "profile"
+ "openslidesuser"
],
[
"can_see_participant",
"participant",
- "profile"
+ "openslidesuser"
],
[
"can_manage_projector",
@@ -195,12 +195,12 @@
[
"can_manage_participant",
"participant",
- "profile"
+ "openslidesuser"
],
[
"can_see_participant",
"participant",
- "profile"
+ "openslidesuser"
],
[
"can_see_projector",
@@ -210,4 +210,4 @@
]
}
}
-]
\ No newline at end of file
+]
diff --git a/openslides/__init__.py b/openslides/__init__.py
index 77f92322b..f3f4c88a3 100644
--- a/openslides/__init__.py
+++ b/openslides/__init__.py
@@ -5,10 +5,11 @@
:license: GNU GPL, see LICENSE for more details.
"""
-VERSION = (1, 3, 0, 'alpha', 0)
+VERSION = (1, 2, 0, 'final', 1)
def get_version(version=None):
"""Derives a PEP386-compliant version number from VERSION."""
+ # TODO: Get the Version Hash from GIT.
if version is None:
version = VERSION
assert len(version) == 5
diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py
index cd67de1a6..d887d553c 100644
--- a/openslides/agenda/models.py
+++ b/openslides/agenda/models.py
@@ -52,7 +52,14 @@ class Item(MPTTModel, SlideMixin):
"""
return the object, of which the item points.
"""
- return get_slide_from_sid(self.related_sid, True)
+ object = get_slide_from_sid(self.related_sid, element=True)
+ if object is None:
+ self.title = 'Item for deleted slide: %s' % self.related_sid
+ self.related_sid = None
+ self.save()
+ return self
+ else:
+ return object
def get_related_type(self):
"""
diff --git a/openslides/agenda/tests.py b/openslides/agenda/tests.py
index 0906eaef2..ca56e009d 100644
--- a/openslides/agenda/tests.py
+++ b/openslides/agenda/tests.py
@@ -15,9 +15,9 @@ from django.test.client import Client
from django.contrib.auth.models import User
from django.db.models.query import EmptyQuerySet
-from projector.api import get_active_slide
+from openslides.projector.api import get_active_slide
-from agenda.models import Item
+from openslides.agenda.models import Item
class ItemTest(TestCase):
def setUp(self):
@@ -60,6 +60,10 @@ class ItemTest(TestCase):
self.assertEqual(initial['parent'], 0)
self.assertEqual(initial['weight'], item.weight)
+ def testRelated_sid(self):
+ self.item1.related_sid = 'foobar'
+ self.assertFalse(self.item1.get_related_slide() is None)
+
class ViewTest(TestCase):
def setUp(self):
diff --git a/openslides/application/forms.py b/openslides/application/forms.py
index 2d4bf5518..963e0f531 100644
--- a/openslides/application/forms.py
+++ b/openslides/application/forms.py
@@ -11,31 +11,13 @@
"""
from django import forms
-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.person import PersonFormField, MultiplePersonFormField
from openslides.application.models import Application
-class UserModelChoiceField(forms.ModelChoiceField):
- """
- Extend ModelChoiceField for users so that the choices are
- listed as 'first_name last_name' instead of just 'username'.
- """
- def label_from_instance(self, obj):
- return obj.get_full_name()
-
-
-class UserModelMultipleChoiceField(forms.ModelMultipleChoiceField):
- """
- Extend ModelMultipleChoiceField for users so that the choices are
- listed as 'first_name last_name' instead of just 'username'.
- """
- def label_from_instance(self, obj):
- return obj.get_full_name()
-
-
class ApplicationForm(forms.Form, CssClassMixin):
title = forms.CharField(widget=forms.TextInput(), label=_("Title"))
text = forms.CharField(widget=forms.Textarea(), label=_("Text"))
@@ -50,11 +32,7 @@ class ApplicationFormTrivialChanges(ApplicationForm):
class ApplicationManagerForm(forms.ModelForm, CssClassMixin):
- submitter = UserModelChoiceField(
- queryset=User.objects.all().exclude(profile=None).
- order_by("first_name"),
- label=_("Submitter"),
- )
+ submitter = PersonFormField()
class Meta:
model = Application
@@ -62,11 +40,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 = MultiplePersonFormField(required=False, label=_("Supporters"))
class ApplicationImportForm(forms.Form, CssClassMixin):
diff --git a/openslides/application/models.py b/openslides/application/models.py
index c06b4716e..78ebd55b6 100644
--- a/openslides/application/models.py
+++ b/openslides/application/models.py
@@ -21,11 +21,12 @@ from django.utils.translation import pgettext
from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext
from openslides.utils.utils import _propper_unicode
+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 Profile
+from openslides.participant.models import OpenSlidesUser
from openslides.poll.models import (BaseOption, BasePoll, CountVotesCast,
CountInvalid, BaseVote)
@@ -36,6 +37,11 @@ from openslides.projector.models import SlideMixin
from openslides.agenda.models import Item
+class ApplicationSupporter(models.Model):
+ application = models.ForeignKey("Application")
+ person = PersonField()
+
+
class Application(models.Model, SlideMixin):
prefix = "application"
STATUS = (
@@ -60,9 +66,7 @@ class Application(models.Model, SlideMixin):
# genpoll
)
- submitter = models.ForeignKey(User, verbose_name=_("Submitter"))
- supporter = models.ManyToManyField(User, related_name='supporter', \
- null=True, blank=True, verbose_name=_("Supporters"))
+ submitter = PersonField(verbose_name=_("Submitter"))
number = models.PositiveSmallIntegerField(blank=True, null=True,
unique=True)
status = models.CharField(max_length=3, choices=STATUS, default='pub')
@@ -157,6 +161,14 @@ class Application(models.Model, SlideMixin):
else:
return False
+ @property
+ def supporters(self):
+ for object in self.applicationsupporter_set.all():
+ yield object.person
+
+ def is_supporter(self, person):
+ return self.applicationsupporter_set.filter(person=person).exists()
+
@property
def enough_supporters(self):
"""
@@ -164,17 +176,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:
@@ -221,10 +236,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):
@@ -236,34 +252,41 @@ class Application(models.Model, SlideMixin):
self.save()
self.writelog(_("Status reseted to: %s") % (self.get_status_display()), user)
- def support(self, user):
+ def support(self, person):
"""
Add a Supporter to the list of supporters of the application.
"""
- if user == self.submitter:
+ if person == 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(person):
+ ApplicationSupporter(application=self, person=person).save()
+ self.writelog(_("Supporter: +%s") % (person))
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:
@@ -332,7 +355,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.
"""
@@ -340,8 +363,8 @@ class Application(models.Model, SlideMixin):
is_admin = False
if user:
try:
- user.profile
- except Profile.DoesNotExist:
+ user = user.openslidesuser
+ except OpenSlidesUser.DoesNotExist:
is_admin = True
except AttributeError:
# For the anonymous-user
@@ -366,16 +389,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/overview.html b/openslides/application/templates/application/overview.html
index 2e72d568d..16336453d 100644
--- a/openslides/application/templates/application/overview.html
+++ b/openslides/application/templates/application/overview.html
@@ -59,7 +59,7 @@
{% if not forloop.last %}
{%endif%}
{% endfor %}
-
{{ application.submitter.profile }} |
+ {{ application.submitter }} |
{{ application.creation_time }} |
diff --git a/openslides/application/templates/application/view.html b/openslides/application/templates/application/view.html
index 0ae213ab0..183f76b50 100644
--- a/openslides/application/templates/application/view.html
+++ b/openslides/application/templates/application/view.html
@@ -16,22 +16,22 @@
diff --git a/openslides/application/tests.py b/openslides/application/tests.py
index 0d6cf4a49..2f5e65e09 100644
--- a/openslides/application/tests.py
+++ b/openslides/application/tests.py
@@ -20,7 +20,7 @@ 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)
+ self.app1 = Application(submitter=self.admin.openslidesuser)
self.app1.save()
def refresh(self):
diff --git a/openslides/application/views.py b/openslides/application/views.py
index 8e8fd39c7..d531b0cab 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.person import get_person
from openslides.config.models import config
@@ -50,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 Profile
+from openslides.participant.models import OpenSlidesUser
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(request.user.openslidesuser),
'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 = request.user.openslidesuser
+ actions = application.get_allowed_actions(user=user)
return {
'application': application,
@@ -180,10 +182,12 @@ def edit(request, application_id=None):
return redirect(reverse('application_overview'))
if application_id is not None:
application = Application.objects.get(id=application_id)
- if not request.user == application.submitter and not is_manager:
+ if (not hasattr(application.submitter, 'user') or
+ not request.user.openslidesuser == 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=request.user.openslidesuser)
else:
application = None
actions = None
@@ -210,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)
+ application = Application(submitter=request.user.openslidesuser)
application.title = dataform.cleaned_data['title']
application.text = dataform.cleaned_data['text']
application.reason = dataform.cleaned_data['reason']
@@ -227,30 +231,21 @@ 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(request.user.openslidesuser, 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)
+ try:
+ new_supporters = set(managerform.cleaned_data['supporter'])
+ except KeyError:
+ # The managerform has no field for the supporters
+ pass
+ else:
+ old_supporters = set(application.supporters)
+ # 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:
@@ -266,11 +261,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}
@@ -278,12 +273,12 @@ def edit(request, application_id=None):
dataform = formclass(initial=initial, prefix="data")
if is_manager:
if application_id is None:
- initial = {'submitter': str(request.user.id)}
+ initial = {'submitter': request.user.openslidesuser.person_id}
else:
- initial = {}
- managerform = managerformclass(initial=initial, \
- instance=application, \
- prefix="manager")
+ initial = {'submitter': application.submitter.person_id,
+ 'supporter': [supporter.person_id for supporter in application.supporters]}
+ managerform = managerformclass(initial=initial,
+ instance=application, prefix="manager")
else:
managerform = None
return {
@@ -301,7 +296,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=request.user.openslidesuser)
messages.success(request, _("Application number was successfully set."))
except Application.DoesNotExist:
pass
@@ -317,7 +312,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=request.user.openslidesuser)
messages.success(request, _("Application was successfully permitted."))
except Application.DoesNotExist:
pass
@@ -332,7 +327,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=request.user.openslidesuser)
messages.success(request, _("Application was successfully rejected."))
except Application.DoesNotExist:
pass
@@ -348,7 +343,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=request.user.openslidesuser, status=status)
messages.success(request, _("Application status was set to: %s.") % application.get_status_display())
except Application.DoesNotExist:
pass
@@ -364,7 +359,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=request.user.openslides.user)
messages.success(request, _("Application status was reset.") )
except Application.DoesNotExist:
pass
@@ -378,7 +373,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=request.user.openslides.user)
messages.success(request, _("You have support the application successfully.") )
except Application.DoesNotExist:
pass
@@ -392,7 +387,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=request.user.openslidesuser)
messages.success(request, _("You have unsupport the application successfully.") )
except Application.DoesNotExist:
pass
@@ -406,7 +401,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=request.user.openslidesuser)
messages.success(request, _("New vote was successfully created.") )
except Application.DoesNotExist:
pass # TODO: do not call poll after this excaption
@@ -423,7 +418,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"), request.user.openslidesuser)
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]))
@@ -463,7 +458,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=request.user.openslidesuser):
messages.error(request, _("You can not delete application %s.") % application)
continue
@@ -472,12 +467,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=request.user.openslidesuser):
+ 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"))
@@ -513,12 +508,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=self.request.user.openslidesuser)
return context
def get_modelform_class(self):
cls = super(ViewPoll, self).get_modelform_class()
- user = self.request.user
+ user = self.request.user.openslidesuser
class ViewPollFormClass(cls):
def save(self, commit = True):
@@ -540,7 +535,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=request.user.openslidesuser)
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]))
@@ -552,7 +547,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=request.user.openslidesuser):
messages.success(request, _("Version %s rejected.") % (aversion.aid))
else:
messages.error(request, _("ERROR by rejecting the version.") )
@@ -565,14 +560,15 @@ def reject_version(request, aversion_id):
@template('application/import.html')
def application_import(request):
try:
- request.user.profile
- messages.error(request, _('The import function is available for the admin (without user profile) only.'))
- return redirect(reverse('application_overview'))
- except Profile.DoesNotExist:
+ 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)
@@ -752,13 +748,10 @@ class ApplicationPDF(PDFView):
if application.status == "pub":
cell1b.append(Paragraph("__________________________________________",stylesheet['Signaturefield']))
cell1b.append(Spacer(0,0.1*cm))
- cell1b.append(Paragraph(" "+unicode(application.submitter.profile), stylesheet['Small']))
+ cell1b.append(Paragraph(" "+unicode(application.submitter), stylesheet['Small']))
cell1b.append(Spacer(0,0.2*cm))
else:
- try:
- cell1b.append(Paragraph(unicode(application.submitter.profile), stylesheet['Normal']))
- except Profile.DoesNotExist:
- pass
+ cell1b.append(Paragraph(unicode(application.submitter), stylesheet['Normal']))
# supporters
cell2a = []
@@ -767,8 +760,8 @@ class ApplicationPDF(PDFView):
cell2a.append(Paragraph("%s:" % _("Supporters"), stylesheet['Heading4']))
- for s in application.supporter.all():
- cell2b.append(Paragraph(". %s" % unicode(s.profile), stylesheet['Signaturefield']))
+ for supporter in application.supporters:
+ cell2b.append(Paragraph(". %s" % unicode(supporter), stylesheet['Signaturefield']))
if application.status == "pub":
for x in range(0,application.missing_supporters):
cell2b.append(Paragraph(". __________________________________________",stylesheet['Signaturefield']))
diff --git a/openslides/assignment/forms.py b/openslides/assignment/forms.py
index 839e53422..cf4dfcba2 100644
--- a/openslides/assignment/forms.py
+++ b/openslides/assignment/forms.py
@@ -14,8 +14,8 @@ from django import forms
from django.utils.translation import ugettext_lazy as _, ugettext_noop
from openslides.utils.forms import CssClassMixin
+from openslides.utils.person import PersonFormField
-from openslides.participant.models import Profile
from openslides.assignment.models import Assignment
@@ -25,13 +25,12 @@ class AssignmentForm(forms.ModelForm, CssClassMixin):
class Meta:
model = Assignment
- exclude = ('status', 'profile', 'elected')
+ exclude = ('status', 'elected')
class AssignmentRunForm(forms.Form, CssClassMixin):
- candidate = forms.ModelChoiceField(
+ candidate = PersonFormField(
widget=forms.Select(attrs={'class': 'medium-input'}),
- queryset=Profile.objects.all().order_by('user__first_name'),
label=_("Nominate a participant"),
)
diff --git a/openslides/assignment/models.py b/openslides/assignment/models.py
index 5598ad73f..bf288cd8e 100644
--- a/openslides/assignment/models.py
+++ b/openslides/assignment/models.py
@@ -15,20 +15,29 @@ from django.db import models
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _, ugettext_noop
+from openslides.utils.person import PersonField
+
from openslides.config.models import config
from openslides.config.signals import default_config_value
from openslides.projector.api import register_slidemodel
from openslides.projector.projector import SlideMixin
-from openslides.participant.models import Profile
-
from openslides.poll.models import (BasePoll, CountInvalid, CountVotesCast,
BaseOption, PublishPollMixin, BaseVote)
from openslides.agenda.models import Item
+class AssignmentCandidate(models.Model):
+ assignment = models.ForeignKey("Assignment")
+ person = PersonField(db_index=True)
+ elected = models.BooleanField(default=False)
+
+ def __unicode__(self):
+ return unicode(self.person)
+
+
class Assignment(models.Model, SlideMixin):
prefix = 'assignment'
STATUS = (
@@ -44,9 +53,6 @@ class Assignment(models.Model, SlideMixin):
verbose_name=_("Number of available posts"))
polldescription = models.CharField(max_length=100, null=True, blank=True,
verbose_name=_("Comment on the ballot paper"))
- profile = models.ManyToManyField(Profile, null=True, blank=True)
- elected = models.ManyToManyField(Profile, null=True, blank=True,
- related_name='elected_set')
status = models.CharField(max_length=3, choices=STATUS, default='sea')
def set_status(self, status):
@@ -63,61 +69,75 @@ class Assignment(models.Model, SlideMixin):
self.status = status
self.save()
- def run(self, profile, user=None):
+ def run(self, candidate, person=None):
"""
run for a vote
"""
- if self.is_candidate(profile):
- raise NameError(_('%s is already a candidate.') % profile)
- if not user.has_perm("assignment.can_manage_assignment") and self.status != 'sea':
+ # TODO: don't make any permission checks here.
+ # Use other Exceptions
+ if self.is_candidate(candidate):
+ raise NameError(_('%s is already a candidate.') % candidate)
+ if not person.has_perm("assignment.can_manage_assignment") and self.status != 'sea':
raise NameError(_('The candidate list is already closed.'))
- self.profile.add(profile)
+ AssignmentCandidate(assignment=self, person=candidate, elected=False).save()
- def delrun(self, profile, user=None):
+ def delrun(self, candidate):
"""
stop running for a vote
"""
- if not user.has_perm("assignment.can_manage_assignment") and self.status != 'sea':
- raise NameError(_('The candidate list is already closed.'))
- if self.is_candidate(profile):
- self.profile.remove(profile)
- self.elected.remove(profile)
+ if self.is_candidate(candidate):
+ self.assignment_candidats.get(person=candidate).delete()
else:
- raise NameError(_('%s is no candidate') % profile)
+ # TODO: Use an OpenSlides Error
+ raise Exception(_('%s is no candidate') % candidate)
- def is_candidate(self, profile):
- if profile in self.profile.get_query_set():
+ def is_candidate(self, person):
+ if self.assignment_candidats.filter(person=person).exists():
return True
else:
return False
+ @property
+ def assignment_candidats(self):
+ return AssignmentCandidate.objects.filter(assignment=self)
+
@property
def candidates(self):
- return self.profile.get_query_set()
+ return self.get_participants(only_candidate=True)
+
+ @property
+ def elected(self):
+ return self.get_participants(only_elected=True)
+
+ def get_participants(self, only_elected=False, only_candidate=False):
+ candidates = self.assignment_candidats
+
+ if only_elected and only_candidate:
+ # TODO: Use right Exception
+ raise Exception("only_elected and only_candidate can not both be Treu")
+
+ if only_elected:
+ candidates = candidates.filter(elected=True)
+
+ if only_candidate:
+ candidates = candidates.filter(elected=False)
+
+ for candidate in candidates.all():
+ yield candidate.person
- def set_elected(self, profile, value=True):
- if profile in self.candidates:
- if value and not self.is_elected(profile):
- self.elected.add(profile)
- elif not value:
- self.elected.remove(profile)
+ def set_elected(self, person, value=True):
+ candidate = self.assignment_candidats.get(person=person)
+ candidate.elected = value
+ candidate.save()
- def is_elected(self, profile):
- if profile in self.elected.all():
- return True
- return False
+ def is_elected(self, person):
+ return person in self.elected
def gen_poll(self):
poll = AssignmentPoll(assignment=self)
poll.save()
- candidates = list(self.profile.all())
- for elected in self.elected.all():
- try:
- candidates.remove(elected)
- except ValueError:
- pass
- poll.set_options([{'candidate': profile} for profile in candidates])
+ poll.set_options([{'candidate': person} for person in self.candidates])
return poll
@@ -159,6 +179,7 @@ class Assignment(models.Model, SlideMixin):
return self.name
def delete(self):
+ # Remove any Agenda-Item, which is related to this application.
for item in Item.objects.filter(related_sid=self.sid):
item.delete()
super(Assignment, self).delete()
@@ -206,7 +227,7 @@ class AssignmentVote(BaseVote):
class AssignmentOption(BaseOption):
poll = models.ForeignKey('AssignmentPoll')
- candidate = models.ForeignKey(Profile)
+ candidate = PersonField()
vote_class = AssignmentVote
def __unicode__(self):
@@ -230,8 +251,7 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin):
self.yesnoabstain = True
else:
# candidates <= available posts -> yes/no/abstain
- if self.assignment.candidates.count() <= (self.assignment.posts
- - self.assignment.elected.count()):
+ if self.assignment.assignment_candidats.filter(elected=False).count() <= (self.assignment.posts):
self.yesnoabstain = True
else:
self.yesnoabstain = False
@@ -260,8 +280,6 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin):
return _("Ballot %d") % self.get_ballot()
-
-
@receiver(default_config_value, dispatch_uid="assignment_default_config")
def default_config(sender, key, **kwargs):
return {
diff --git a/openslides/assignment/templates/assignment/overview.html b/openslides/assignment/templates/assignment/overview.html
index 85e850c18..2d9e3b8e0 100644
--- a/openslides/assignment/templates/assignment/overview.html
+++ b/openslides/assignment/templates/assignment/overview.html
@@ -31,7 +31,7 @@
{{ assignment }} |
- {{ assignment.profile.count }} / {{ assignment.posts }} |
+ {{ assignment.candidates|length }} / {{ assignment.posts }} |
{{ assignment.get_status_display }} |
diff --git a/openslides/assignment/templates/assignment/poll_view.html b/openslides/assignment/templates/assignment/poll_view.html
index 5176bbdbf..64ff83866 100644
--- a/openslides/assignment/templates/assignment/poll_view.html
+++ b/openslides/assignment/templates/assignment/poll_view.html
@@ -21,36 +21,36 @@
{% endfor %}
|
{% for form in forms %}
-
- {{ form.option }} |
- {% for value in form %}
-
- {{ value.errors }}
- {{ value }}
- |
+
+ {{ form.option }} |
+ {% for value in form %}
+
+ {{ value.errors }}
+ {{ value }}
+ |
+ {% endfor %}
+
+ {% endfor %}
+
+ {% trans "Invalid votes" %} |
+ {% for value in poll.get_vote_values %}
+ {% if forloop.first %}
+ {{ pollform.votesinvalid.errors }}{{ pollform.votesinvalid }} |
+ {% else %}
+ |
+ {% endif %}
+ {% endfor %}
+
+
+ {% trans "Votes cast" %} |
+ {% for value in poll.get_vote_values %}
+ {% if forloop.first %}
+ {{ pollform.votescast.errors }}{{ pollform.votescast }} |
+ {% else %}
+ |
+ {% endif %}
{% endfor %}
- {% endfor %}
-
- {% trans "Invalid votes" %} |
- {% for value in poll.get_vote_values %}
- {% if forloop.first %}
- {{ pollform.votesinvalid.errors }}{{ pollform.votesinvalid }} |
- {% else %}
- |
- {% endif %}
- {% endfor %}
-
-
- {% trans "Votes cast" %} |
- {% for value in poll.get_vote_values %}
- {% if forloop.first %}
- {{ pollform.votescast.errors }}{{ pollform.votescast }} |
- {% else %}
- |
- {% endif %}
- {% endfor %}
-
diff --git a/openslides/assignment/templates/assignment/view.html b/openslides/assignment/templates/assignment/view.html
index 5f3530a0d..fc4feb615 100644
--- a/openslides/assignment/templates/assignment/view.html
+++ b/openslides/assignment/templates/assignment/view.html
@@ -35,13 +35,14 @@
{% trans "Candidates" %}
- {% for profile in assignment.profile.all|dictsort:"user.first_name" %}
- - {{ profile }}
- {% if perms.assignment.can_manage_assignment %}
- {% if assignment.status == "sea" or assignment.status == "vot" %}
-
- {% endif %}
- {% endif %}
+ {% for person in assignment.candidates %}
+
-
+ {{ person }}
+ {% if perms.assignment.can_manage_assignment %}
+ {% if assignment.status == "sea" or assignment.status == "vot" %}
+
+ {% endif %}
+ {% endif %}
{% empty %}
- {% trans "No candidates available." %}
@@ -53,21 +54,18 @@
|
{% endif %}
{% endfor %}
- {% if assignment.profile.exists and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
+ {% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
{% endif %}
@@ -202,7 +200,7 @@
{% endif %}
{% endfor %}
- {% if assignment.profile.exists and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
+ {% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
{% endif %}
@@ -212,7 +210,7 @@
{% trans "No ballots available." %}
- {% if assignment.profile.count > 0 and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
+ {% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
{% trans 'New ballot' %}
diff --git a/openslides/assignment/templates/projector/Assignment.html b/openslides/assignment/templates/projector/Assignment.html
index d516baa88..8627d6022 100644
--- a/openslides/assignment/templates/projector/Assignment.html
+++ b/openslides/assignment/templates/projector/Assignment.html
@@ -28,17 +28,17 @@
{% endblock %}
{% block scrollcontent %}
- {% if not assignment.profile.exists %}
+ {% if not assignment.candidates %}
{{ assignment.description|linebreaks }}
{% endif %}
- {% if assignment.profile.exists and assignment.status != "fin" %}
+ {% if assignment.candidates and assignment.status != "fin" %}
{% trans "Candidates" %}
- {% for profile in assignment.profile.all|dictsort:"user.first_name" %}
- - {{ profile }}
+ {% for candidate in assignment.candidates %}
+ - {{ candidate }}
{% empty %}
-
{% trans "No candidates available." %}
@@ -122,7 +122,7 @@
- {% elif assignment.profile.exists %}
+ {% elif assignment.candidates %}
{% trans "No ballots available." %}
{% endif %}
diff --git a/openslides/assignment/urls.py b/openslides/assignment/urls.py
index 0b6e63798..5540b8a06 100644
--- a/openslides/assignment/urls.py
+++ b/openslides/assignment/urls.py
@@ -55,7 +55,7 @@ urlpatterns = patterns('openslides.assignment.views',
name='assignment_delrun',
),
- url(r'^(?P\d+)/delother/(?P\d+)/$',
+ url(r'^(?P\d+)/delother/(?P[^/]+)/$',
'delother',
name='assignment_delother',
),
@@ -105,13 +105,13 @@ urlpatterns = patterns('openslides.assignment.views',
name='assignment_poll_publish_status',
),
- url(r'^(?P\d+)/elected/(?P\d+)/$',
+ url(r'^(?P\d+)/elected/(?P[^/]+)/$',
'set_elected',
{'elected': True},
name='assignment_user_elected',
),
- url(r'^(?P\d+)/notelected/(?P\d+)/$',
+ url(r'^(?P\d+)/notelected/(?P[^/]+)/$',
'set_elected',
{'elected': False},
name='assignment_user_not_elected',
diff --git a/openslides/assignment/views.py b/openslides/assignment/views.py
index 03bd55516..96a37bb14 100644
--- a/openslides/assignment/views.py
+++ b/openslides/assignment/views.py
@@ -30,9 +30,11 @@ from openslides.utils.template import Tab
from openslides.utils.utils import (template, permission_required,
gen_confirm_form, del_confirm_form, ajax_request)
from openslides.utils.views import FormView, DeleteView, PDFView, RedirectView
+from openslides.utils.person import get_person
from openslides.config.models import config
-from openslides.participant.models import Profile
+
+from openslides.participant.models import OpenSlidesUser
from openslides.projector.projector import Widget
@@ -94,11 +96,14 @@ def view(request, assignment_id=None):
else:
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)
}
@@ -168,13 +173,10 @@ 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.profile, request.user)
+ assignment.run(request.user.openslidesuser, request.user)
messages.success(request, _('You have set your candidature successfully.') )
except NameError, e:
messages.error(request, e)
- except Profile.DoesNotExist:
- messages.error(request,
- _("You can't candidate. Your user account is only for administration."))
return redirect(reverse('assignment_view', args=[assignment_id]))
@@ -182,28 +184,33 @@ def run(request, assignment_id):
def delrun(request, assignment_id):
assignment = Assignment.objects.get(pk=assignment_id)
try:
- assignment.delrun(request.user.profile, request.user)
- messages.success(request, _("You have withdrawn your candidature successfully.") )
- except NameError, e:
+ if assignment.status == 'sea' or user.has_perm("assignment.can_manage_assignment"):
+ assignment.delrun(request.user.openslidesuser)
+ else:
+ messages.error(request, _('The candidate list is already closed.'))
+ except Exception, e:
messages.error(request, e)
+ else:
+ messages.success(request, _("You have withdrawn your candidature successfully.") )
return redirect(reverse('assignment_view', args=[assignment_id]))
@permission_required('assignment.can_manage_assignment')
-def delother(request, assignment_id, profile_id):
+def delother(request, assignment_id, user_id):
assignment = Assignment.objects.get(pk=assignment_id)
- profile = Profile.objects.get(pk=profile_id)
+ person = get_person(user_id)
if request.method == 'POST':
try:
- assignment.delrun(profile, request.user)
- messages.success(request, _("Candidate %s was withdrawn successfully.") % (profile))
- except NameError, e:
+ assignment.delrun(person)
+ except Exception, e:
messages.error(request, e)
+ else:
+ messages.success(request, _("Candidate %s was withdrawn successfully.") % (person))
else:
gen_confirm_form(request,
- _("Do you really want to withdraw %s from the election?") \
- % profile, reverse('assignment_delother', args=[assignment_id, profile_id]))
+ _("Do you really want to withdraw %s from the election?") \
+ % person, reverse('assignment_delother', args=[assignment_id, user_id]))
return redirect(reverse('assignment_view', args=[assignment_id]))
@@ -263,21 +270,19 @@ def set_publish_status(request, poll_id):
@permission_required('assignment.can_manage_assignment')
-def set_elected(request, assignment_id, profile_id, elected=True):
+def set_elected(request, assignment_id, user_id, elected=True):
assignment = Assignment.objects.get(pk=assignment_id)
- profile = Profile.objects.get(pk=profile_id)
- assignment.set_elected(profile, elected)
+ person = get_person(user_id)
+ assignment.set_elected(person, elected)
if request.is_ajax():
if elected:
- link = reverse('assignment_user_not_elected', args=[assignment.id, profile.id])
+ link = reverse('assignment_user_not_elected', args=[assignment.id, person.person_id])
text = _('not elected')
else:
- link = reverse('assignment_user_elected', args=[assignment.id, profile.id])
+ link = reverse('assignment_user_elected', args=[assignment.id, person.person_id])
text = _('elected')
- return ajax_request({'elected': elected,
- 'link': link,
- 'text': text})
+ return ajax_request({'elected': elected, 'link': link, 'text': text})
return redirect(reverse('assignment_view', args=[assignment_id]))
@@ -369,7 +374,7 @@ class AssignmentPDF(PDFView):
cell2a.append(Paragraph("%s:" % _("Candidates"), stylesheet['Heading4']))
cell2b = []
- for candidate in assignment.profile.all():
+ for candidate in assignment.candidates:
cell2b.append(Paragraph(". %s" % candidate,
stylesheet['Signaturefield']))
if assignment.status == "sea":
@@ -407,7 +412,7 @@ class AssignmentPDF(PDFView):
# Add result rows
- elected_candidates = assignment.elected.all()
+ elected_candidates = list(assignment.elected)
for candidate, poll_list in vote_results.iteritems():
row = []
@@ -548,8 +553,8 @@ class AssignmentPollPDF(PDFView):
candidate = option.candidate
cell.append(Paragraph(candidate.user.get_full_name(),
stylesheet['Ballot_option_name']))
- if candidate.group:
- cell.append(Paragraph("(%s)" % candidate.group,
+ if candidate.name_surfix:
+ cell.append(Paragraph("(%s)" % candidate.name_surfix,
stylesheet['Ballot_option_group']))
else:
cell.append(Paragraph(" ",
diff --git a/openslides/config/views.py b/openslides/config/views.py
index 7cd479f26..07756193a 100644
--- a/openslides/config/views.py
+++ b/openslides/config/views.py
@@ -67,16 +67,16 @@ class GeneralConfig(FormView):
anonymous = Group.objects.get(name='Anonymous')
except Group.DoesNotExist:
default_perms = [u'can_see_agenda', u'can_see_projector',
- u'can_see_application']
+ u'can_see_application', 'can_see_assignment']
anonymous = Group()
anonymous.name = 'Anonymous'
anonymous.save()
anonymous.permissions = Permission.objects.filter(
codename__in=default_perms)
anonymous.save()
- messages.success(self.request,
- _('Anonymous access enabled. Please modify the "Anonymous" ' \
- 'group to fit your required permissions.'))
+ messages.success(self.request,
+ _('Anonymous access enabled. Please modify the "Anonymous" ' \
+ 'group to fit your required permissions.'))
else:
config['system_enable_anonymous'] = False
diff --git a/openslides/openslides_settings.py b/openslides/openslides_settings.py
index 8de303633..3cf5cfa33 100755
--- a/openslides/openslides_settings.py
+++ b/openslides/openslides_settings.py
@@ -21,7 +21,6 @@ def _fs2unicode(s):
SITE_ROOT = os.path.realpath(os.path.dirname(__file__))
-AUTH_PROFILE_MODULE = 'participant.Profile'
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',
'openslides.utils.auth.AnonymousAuth',)
diff --git a/openslides/participant/api.py b/openslides/participant/api.py
index 3dde39c14..d2919821b 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.person import get_person
+from openslides.participant.models import OpenSlidesUser
+
def gen_password():
"""
diff --git a/openslides/participant/forms.py b/openslides/participant/forms.py
index 972f3b27c..f39db40fb 100644
--- a/openslides/participant/forms.py
+++ b/openslides/participant/forms.py
@@ -15,67 +15,71 @@ 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 openslides.utils.forms import CssClassMixin, LocalizedModelMultipleChoiceField
+from openslides.utils.forms import (
+ CssClassMixin, LocalizedModelMultipleChoiceField)
-from openslides.participant.models import Profile
+from openslides.participant.models import OpenSlidesUser
USER_APPLICATION_IMPORT_OPTIONS = [
('REASSIGN', _('Keep applications, try to reassign submitter')),
('INREVIEW', _('Keep applications, set status to "needs review"')),
- ('DISCARD' , _('Discard applications'))
+ ('DISCARD', _('Discard applications'))
]
class UserNewForm(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,
- initial=True)
+ groups = forms.ModelMultipleChoiceField(
+ queryset=Group.objects.all(), label=_("User groups"), required=False)
+ is_active = forms.BooleanField(
+ label=_("Active"), required=False, initial=True)
class Meta:
model = User
exclude = ('username', 'password', 'is_staff', 'is_superuser',
- 'last_login', 'date_joined', 'user_permissions')
+ 'last_login', 'date_joined', 'user_permissions')
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)
+ groups = forms.ModelMultipleChoiceField(
+ queryset=Group.objects.all(), label=_("User groups"), required=False)
is_active = forms.BooleanField(label=_("Active"), required=False)
class Meta:
model = User
exclude = ('password', 'is_staff', 'is_superuser', 'last_login',
- 'date_joined', 'user_permissions')
+ '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')
+ 'is_superuser', 'groups', 'password', 'is_staff',
+ 'last_login', 'date_joined', 'user_permissions')
-class ProfileForm(forms.ModelForm, CssClassMixin):
+class OpenSlidesUserForm(forms.ModelForm, CssClassMixin):
class Meta:
- model = Profile
+ model = OpenSlidesUser
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"))
def __init__(self, *args, **kwargs):
super(GroupForm, self).__init__(*args, **kwargs)
if kwargs.get('instance', None) is not None:
- self.fields['permissions'].initial = \
- [p.pk for p in kwargs['instance'].permissions.all()]
+ self.fields['permissions'].initial = (
+ [p.pk for p in kwargs['instance'].permissions.all()])
class Meta:
model = Group
@@ -87,14 +91,13 @@ 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"))
+ 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"),
- )
+ required=True, choices=USER_APPLICATION_IMPORT_OPTIONS,
+ label=_("For existing applications"))
class ConfigForm(forms.Form, CssClassMixin):
@@ -102,11 +105,9 @@ class ConfigForm(forms.Form, CssClassMixin):
widget=forms.TextInput(),
required=False,
label=_("System URL"),
- help_text=_("Printed in PDF of first time passwords only."),
- )
+ help_text=_("Printed in PDF of first time passwords only."))
participant_pdf_welcometext = forms.CharField(
widget=forms.Textarea(),
required=False,
label=_("Welcome text"),
- help_text=_("Printed in PDF of first time passwords only."),
- )
+ help_text=_("Printed in PDF of first time passwords only."))
diff --git a/openslides/participant/models.py b/openslides/participant/models.py
index 46c7e56b0..f7685994e 100644
--- a/openslides/participant/models.py
+++ b/openslides/participant/models.py
@@ -10,19 +10,20 @@
:license: GNU GPL, see LICENSE for more details.
"""
-from django.contrib.auth.models import User
+from django.contrib.auth.models import User, Group
from django.db import models
-from django.db.models import Q
+from django.db.models import Q, 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.config.signals import default_config_value
-from openslides.participant.api import gen_password
-
-
-class Profile(models.Model):
+class OpenSlidesUser(models.Model, PersonMixin):
+ person_prefix = 'openslides_user'
GENDER_CHOICES = (
('male', _('Male')),
('female', _('Female')),
@@ -35,29 +36,36 @@ class Profile(models.Model):
)
user = models.OneToOneField(User, unique=True, editable=False)
- group = models.CharField(max_length=100, null=True, blank=True,
- verbose_name = _("Group"), help_text=_('Shown behind the name.'))
- gender = models.CharField(max_length=50, choices=GENDER_CHOICES, blank=True,
- verbose_name = _("Gender"),
+ name_surfix = models.CharField(
+ max_length=100, null=True, blank=True, verbose_name=_("Name Surfix"),
+ help_text=_('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.'))
+ type = models.CharField(
+ max_length=100, choices=TYPE_CHOICE, blank=True,
+ verbose_name=_("Typ"), help_text=_('Only for filter the userlist.'))
+ committee = models.CharField(
+ max_length=100, null=True, blank=True, verbose_name=_("Committee"),
help_text=_('Only for filter the userlist.'))
- type = models.CharField(max_length=100, choices=TYPE_CHOICE, blank=True,
- verbose_name = _("Typ"), help_text=_('Only for filter the userlist.'))
- committee = models.CharField(max_length=100, null=True, blank=True,
- verbose_name = _("Committee"),
- help_text=_('Only for filter the userlist.'))
- comment = models.TextField(null=True, blank=True,
- verbose_name = _('Comment'), help_text=_('Only for notes.'))
- firstpassword = models.CharField(max_length=100, null=True, blank=True,
- verbose_name = _("First Password"))
+ comment = models.TextField(
+ null=True, blank=True, verbose_name=_('Comment'),
+ help_text=_('Only for notes.'))
+ firstpassword = models.CharField(
+ max_length=100, null=True, blank=True,
+ verbose_name=_("First Password"))
-
- def reset_password(self):
+ def reset_password(self, password=None):
"""
Reset the password for the user to his default-password.
"""
- self.user.set_password(self.firstpassword)
+ 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)
@models.permalink
def get_absolute_url(self, link='edit'):
@@ -74,25 +82,75 @@ class Profile(models.Model):
return ('user_delete', [str(self.user.id)])
def __unicode__(self):
- if self.group:
- return "%s (%s)" % (self.user.get_full_name(), self.group)
+ if self.name_surfix:
+ return "%s (%s)" % (self.user.get_full_name(), self.name_surfix)
return "%s" % self.user.get_full_name()
-
class Meta:
+ # Rename permissions
permissions = (
('can_see_participant', ugettext_noop("Can see participant")),
- ('can_manage_participant', ugettext_noop("Can manage participant")),
+ ('can_manage_participant',
+ ugettext_noop("Can manage participant")),
)
+class OpenSlidesGroup(models.Model, PersonMixin):
+ person_prefix = 'openslides_group'
+
+ group = models.OneToOneField(Group)
+ group_as_person = models.BooleanField(default=False)
+
+ def __unicode__(self):
+ return unicode(self.group)
+
+
+class OpenSlidesUsersConnecter(object):
+ def __init__(self, person_prefix=None, id=None):
+ self.person_prefix = person_prefix
+ self.id = id
+
+ 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)
+ else:
+ for user in OpenSlidesUser.objects.all():
+ yield user
+
+ if (not self.person_prefix or
+ self.person_prefix == OpenSlidesGroup.person_prefix):
+ if self.id:
+ yield OpenSlidesGroup.objects.get(pk=self.id)
+ else:
+ for group in OpenSlidesGroup.objects.all():
+ yield group
+
+ def __getitem__(self, key):
+ return OpenSlidesUser.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(default_config_value, dispatch_uid="participant_default_config")
def default_config(sender, key, **kwargs):
"""
Default values for the participant app.
"""
+ # TODO: Rename config-vars
return {
'participant_pdf_system_url': 'http://example.com:8000',
'participant_pdf_welcometext': _('Welcome to OpenSlides!'),
'admin_password': None,
}.get(key)
+
+
+@receiver(signals.post_save, sender=User)
+def user_post_save(sender, instance, signal, *args, **kwargs):
+ # Creates OpenSlidesUser
+ profile, new = OpenSlidesUser.objects.get_or_create(user=instance)
diff --git a/openslides/participant/templates/participant/overview.html b/openslides/participant/templates/participant/overview.html
index dadbb48df..f068dd69c 100644
--- a/openslides/participant/templates/participant/overview.html
+++ b/openslides/participant/templates/participant/overview.html
@@ -77,11 +77,11 @@
{{ user.first_name }} |
{{ user.last_name }} |
- {{ user.profile.group }} |
- {{ user.profile.get_type_display }} |
- {{ user.profile.committee }} |
+ {{ user.openslidesuser.name_surfix }} |
+ {{ user.openslidesuser.get_type_display }} |
+ {{ user.openslidesuser.committee }} |
{% if perms.participant.can_manage_participant %}
- {{ user.profile.comment|first_line }} |
+ {{ user.openslidesuser.comment|first_line }} |
{% if user.last_login > user.date_joined %}
{{ user.last_login }}
{% endif %} |
diff --git a/openslides/participant/tests.py b/openslides/participant/tests.py
new file mode 100644
index 000000000..c7dbadf46
--- /dev/null
+++ b/openslides/participant/tests.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ openslides.participant.tests
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Unit test for the participant app.
+
+ :copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
+ :license: GNU GPL, see LICENSE for more details.
+"""
+
+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
+
+
+class OpenSlidesUserTest(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.save()
+ self.openslidesuser1 = self.user1.openslidesuser
+ self.openslidesuser1.firstpassword = gen_password()
+ self.openslidesuser1.save()
+ self.user1 = self.openslidesuser1.user
+
+ def test_participant_user(self):
+ self.assertEqual(self.user1.openslidesuser, self.openslidesuser1)
+ self.assertEqual(self.user1, self.openslidesuser1.user)
+
+ def test_repr(self):
+ self.assertEqual(unicode(self.openslidesuser1), 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)')
+
+ def test_reset_password(self):
+ self.assertIsInstance(self.openslidesuser1.firstpassword, basestring)
+ self.assertEqual(len(self.openslidesuser1.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))
+
+ 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)
+
+
+class OpenSlidesGroupTest(TestCase):
+ def setUp(self):
+ self.group1 = Group.objects.create(name='Test Group')
+ self.openslidesgroup1 = OpenSlidesGroup.objects.create(group=self.group1)
+
+ def test_group_openslidesgroup(self):
+ self.assertEqual(self.openslidesgroup1.group, self.group1)
+
+ 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)
diff --git a/openslides/participant/views.py b/openslides/participant/views.py
index 5b7c237ab..3fd5f6a55 100644
--- a/openslides/participant/views.py
+++ b/openslides/participant/views.py
@@ -18,13 +18,14 @@ from urllib import urlencode
try:
from urlparse import parse_qs
-except ImportError: # python <= 2.5 grab it from cgi
+except ImportError: # python <= 2.5 grab it from cgi
from cgi import parse_qs
from reportlab.lib import colors
from reportlab.lib.units import cm
-from reportlab.platypus import (SimpleDocTemplate, PageBreak, Paragraph,
- LongTable, Spacer, Table, TableStyle)
+from reportlab.platypus import (
+ SimpleDocTemplate, PageBreak, Paragraph, LongTable, Spacer, Table,
+ TableStyle)
from django.db import transaction
from django.contrib import messages
@@ -39,18 +40,18 @@ from django.utils.translation import ugettext as _, ungettext, 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.utils import (
+ template, permission_required, gen_confirm_form, ajax_request, decodedict,
+ encodedict, delete_default_permissions, html_strong)
from openslides.utils.views import FormView, PDFView
from openslides.config.models import config
-from openslides.participant.models import Profile
+from openslides.participant.models import OpenSlidesUser, OpenSlidesGroup
from openslides.participant.api import gen_username, gen_password
-from openslides.participant.forms import (UserNewForm, UserEditForm,
- ProfileForm, UsersettingsForm, UserImportForm, GroupForm,
- AdminPasswordChangeForm, ConfigForm)
+from openslides.participant.forms import (
+ UserNewForm, UserEditForm, OpenSlidesUserForm, UsersettingsForm,
+ UserImportForm, GroupForm, AdminPasswordChangeForm, ConfigForm)
@permission_required('participant.can_see_participant')
@@ -78,53 +79,60 @@ def get_overview(request):
query = User.objects
if 'gender' in sortfilter:
- query = query.filter(profile__gender__iexact=sortfilter['gender'][0])
+ query = query.filter(
+ openslidesuser__gender__iexact=sortfilter['gender'][0])
if 'group' in sortfilter:
- query = query.filter(profile__group__iexact=sortfilter['group'][0])
+ query = query.filter(
+ openslidesuser__name_surfix__iexact=sortfilter['group'][0])
if 'type' in sortfilter:
- query = query.filter(profile__type__iexact=sortfilter['type'][0])
+ query = query.filter(
+ openslidesuser__type__iexact=sortfilter['type'][0])
if 'committee' in sortfilter:
- query = query. \
- filter(profile__committee__iexact=sortfilter['committee'][0])
+ 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 ['group', 'type', 'committee', 'comment']:
- query = query.order_by('profile__%s' % 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 (with profile)
+ # list of filtered users
userlist = query.all()
users = []
for user in userlist:
try:
- user.get_profile()
- users.append(user)
- except Profile.DoesNotExist:
+ user.openslidesuser
+ except OpenSlidesUser.DoesNotExist:
pass
- # list of all existing users (with profile)
+ else:
+ users.append(user)
+ # list of all existing users
allusers = []
for user in User.objects.all():
try:
- user.get_profile()
- allusers.append(user)
- except Profile.DoesNotExist:
+ user.openslidesuser
+ except OpenSlidesUser.DoesNotExist:
pass
+ 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['group'] for p in Profile.objects.values('group') \
- .exclude(group='').distinct()]
+ 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 Profile.objects.values('committee') \
+ committees = [p['committee'] for p in OpenSlidesUser.objects.values('committee')
.exclude(committee='').distinct()]
return {
'users': users,
@@ -142,7 +150,7 @@ def get_overview(request):
@template('participant/edit.html')
def edit(request, user_id=None):
"""
- View to create and edit users with profile.
+ View to create and edit users.
"""
if user_id is not None:
user = User.objects.get(id=user_id)
@@ -151,26 +159,31 @@ def edit(request, user_id=None):
if request.method == 'POST':
if user_id is None:
- userform = UserNewForm(request.POST, prefix="user")
- profileform = ProfileForm(request.POST, prefix="profile")
+ user_form = UserNewForm(request.POST, prefix="user")
+ openslides_user_form = OpenSlidesUserForm(request.POST, prefix="openslidesuser")
else:
- userform = UserEditForm(request.POST, instance=user, prefix="user")
- profileform = ProfileForm(request.POST, instance=user.profile,
- prefix="profile")
+ user_form = UserEditForm(request.POST, instance=user, prefix="user")
+ openslides_user_form = OpenSlidesUserForm(request.POST, instance=user.openslidesuser,
+ prefix="openslidesuser")
- if userform.is_valid() and profileform.is_valid():
- user = userform.save()
+ 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()
- profile = profileform.save(commit=False)
- profile.user = user
+ 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 profile.firstpassword:
- profile.firstpassword = gen_password()
- profile.user.set_password(profile.firstpassword)
- profile.user.save()
- profile.save()
+ 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.'))
@@ -185,15 +198,15 @@ def edit(request, user_id=None):
messages.error(request, _('Please check the form for errors.'))
else:
if user_id is None:
- userform = UserNewForm(prefix="user")
- profileform = ProfileForm(prefix="profile")
+ user_form = UserNewForm(prefix="user")
+ openslides_user_form = OpenSlidesUserForm(prefix="openslidesuser")
else:
- userform = UserEditForm(instance=user, prefix="user")
- profileform = ProfileForm(instance=user.profile, prefix="profile")
-
+ user_form = UserEditForm(instance=user, prefix="user")
+ openslides_user_form = OpenSlidesUserForm(instance=user.openslidesuser, prefix="openslidesuser")
+ # TODO: rename template vars
return {
- 'userform': userform,
- 'profileform': profileform,
+ 'userform': user_form,
+ 'profileform': openslides_user_form,
'edituser': user,
}
@@ -269,6 +282,7 @@ def group_edit(request, group_id=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
@@ -277,25 +291,38 @@ def group_edit(request, group_id=None):
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,
+ '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 -
@@ -315,7 +342,12 @@ def group_edit(request, group_id=None):
else:
messages.error(request, _('Please check the form for errors.'))
else:
- form = GroupForm(instance=group)
+ 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,
@@ -346,7 +378,7 @@ def user_settings(request):
Edit own user account.
"""
if request.method == 'POST':
- form_user = UsersettingsForm(request.POST,instance=request.user)
+ form_user = UsersettingsForm(request.POST, instance=request.user)
if form_user.is_valid():
form_user.save()
messages.success(request, _('User settings successfully saved.'))
@@ -606,7 +638,7 @@ class ParticipantsListPDF(PDFView):
document_title = ugettext_lazy('List of Participants')
def append_to_pdf(self, story):
- data= [['#', _('Last Name'), _('First Name'), _('Group'), _('Type'),
+ data = [['#', _('Last Name'), _('First Name'), _('Group'), _('Type'),
_('Committee')]]
sort = 'last_name'
counter = 0
@@ -614,27 +646,27 @@ class ParticipantsListPDF(PDFView):
try:
counter += 1
user.get_profile()
- data.append([counter,
+ 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']),
- ])
+ 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),
+ ('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
+ (colors.white, (.9, .9, .9)))])
+ t._argW[0] = 0.75 * cm
story.append(t)
@@ -654,48 +686,48 @@ class ParticipantsPasswordsPDF(PDFView):
pdf_document.build(story)
def append_to_pdf(self, story):
- data= []
+ 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(Spacer(0, 0.8 * cm))
cell.append(Paragraph(_("Account for OpenSlides"),
- stylesheet['Ballot_title']))
+ stylesheet['Ballot_title']))
cell.append(Paragraph(_("for %s") % (user.profile),
- stylesheet['Ballot_subtitle']))
- cell.append(Spacer(0,0.5*cm))
+ stylesheet['Ballot_subtitle']))
+ cell.append(Spacer(0, 0.5 * cm))
cell.append(Paragraph(_("User: %s") % (user.username),
- stylesheet['Monotype']))
+ stylesheet['Monotype']))
cell.append(Paragraph(_("Password: %s")
% (user.profile.firstpassword), stylesheet['Monotype']))
- cell.append(Spacer(0,0.5*cm))
+ 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))
+ cell.append(Spacer(0, 0.5 * cm))
cell2 = []
- cell2.append(Spacer(0,0.8*cm))
+ cell2.append(Spacer(0, 0.8 * cm))
if participant_pdf_welcometext is not None:
cell2.append(Paragraph(
- participant_pdf_welcometext.replace('\r\n','
'),
+ participant_pdf_welcometext.replace('\r\n', '
'),
stylesheet['Ballot_subtitle']))
- data.append([cell,cell2])
- except Profile.DoesNotExist:
+ data.append([cell, cell2])
+ except OpenSlidesUser.DoesNotExist:
pass
# add empty table line if no participants available
if data == []:
- data.append(['',''])
+ data.append(['', ''])
# build table
- t=Table(data, 10.5*cm, 7.42*cm)
+ 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'),
+ ('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)
diff --git a/openslides/utils/person/__init__.py b/openslides/utils/person/__init__.py
new file mode 100644
index 000000000..e54e6258a
--- /dev/null
+++ b/openslides/utils/person/__init__.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ openslides.utils.person
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ Person api for OpenSlides
+
+ :copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
+ :license: GNU GPL, see LICENSE for more details.
+"""
+
+from openslides.utils.person.signals import receiv_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
+
+
+class EmtyPerson(PersonMixin):
+ @property
+ def person_id(self):
+ return 'emtyuser'
diff --git a/openslides/utils/person/api.py b/openslides/utils/person/api.py
new file mode 100644
index 000000000..59e7d7a1a
--- /dev/null
+++ b/openslides/utils/person/api.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ openslides.utils.person.api
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Usefull functions for the OpenSlides person api.
+
+ :copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
+ :license: GNU GPL, see LICENSE for more details.
+"""
+
+from openslides.utils.person.signals import receiv_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 __iter__(self):
+ try:
+ return iter(self._cache)
+ except AttributeError:
+ return iter(self.iter_persons())
+
+ def __len__(self):
+ return len(list(self.__iter__()))
+
+ def __getitem__(self, key):
+ return list(self)[key]
+
+ 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 person in persons:
+ self._cache.append(person)
+ yield person
+
+
+def generate_person_id(prefix, id):
+ if ':' in prefix:
+ raise ValueError("':' is not allowed in a the 'person_prefix'")
+ return "%s:%d" % (prefix, id)
+
+
+def split_person_id(person_id):
+ data = person_id.split(':', 1)
+ if len(data) == 2 and data[0] and data[1]:
+ return data
+ raise TypeError("Invalid person_id: '%s'" % person_id)
+
+
+def get_person(person_id):
+ try:
+ person_prefix, id = split_person_id(person_id)
+ except TypeError:
+ from openslides.utils.person import EmtyPerson
+ return EmtyPerson()
+ return Persons(person_prefix=person_prefix, id=id)[0]
diff --git a/openslides/utils/person/forms.py b/openslides/utils/person/forms.py
new file mode 100644
index 000000000..2330443dc
--- /dev/null
+++ b/openslides/utils/person/forms.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ openslides.utils.person.forms
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Forms and FormFields for the OpenSlides person api.
+
+ :copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
+ :license: GNU GPL, see LICENSE for more details.
+"""
+
+from django import forms
+
+from openslides.utils.person.api import Persons, get_person
+
+
+class PersonChoices(object):
+ def __init__(self, field):
+ self.field = field
+
+ def __iter__(self):
+ if self.field.empty_label is not None:
+ yield (u"", self.field.empty_label)
+ for person in Persons():
+ yield (person.person_id, person)
+
+
+class PersonFormField(forms.fields.ChoiceField):
+ def __init__(self, required=True, initial=None, empty_label=u"---------",
+ *args, **kwargs):
+ if required and (initial is not None):
+ self.empty_label = None
+ else:
+ self.empty_label = empty_label
+ forms.fields.Field.__init__(self, required=required, initial=initial,
+ *args, **kwargs)
+ self.widget.choices = self.choices
+
+ def __deepcopy__(self, memo):
+ result = super(forms.fields.ChoiceField, self).__deepcopy__(memo)
+ return result
+
+ def _get_choices(self):
+ # If self._choices is set, then somebody must have manually set
+ # the property self.choices. In this case, just return self._choices.
+ if hasattr(self, '_choices'):
+ return self._choices
+ return PersonChoices(self)
+
+ choices = property(_get_choices, forms.fields.ChoiceField._set_choices)
+
+ def to_python(self, value):
+ return get_person(value)
+
+ def valid_value(self, value):
+ return super(PersonFormField, self).valid_value(value.person_id)
+
+
+class MultiplePersonFormField(PersonFormField):
+ widget = forms.widgets.SelectMultiple
+
+ def __init__(self, *args, **kwargs):
+ super(MultiplePersonFormField, self).__init__(empty_label=None,
+ *args, **kwargs)
+
+ def to_python(self, value):
+ if hasattr(value, '__iter__'):
+ return [super(MultiplePersonFormField, self).to_python(v)
+ for v in value]
+ return super(MultiplePersonFormField, self).to_python(value)
+
+ def valid_value(self, value):
+ if hasattr(value, '__iter__'):
+ return [super(MultiplePersonFormField, self).valid_value(v)
+ for v in value]
+ return super(MultiplePersonFormField, self).valid_value(value)
diff --git a/openslides/utils/person/models.py b/openslides/utils/person/models.py
new file mode 100644
index 000000000..3df620dfd
--- /dev/null
+++ b/openslides/utils/person/models.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ openslides.utils.person.models
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Models and ModelFields for the OpenSlides person api.
+
+ :copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
+ :license: GNU GPL, see LICENSE for more details.
+"""
+from django.db import models
+
+from openslides.utils.person.forms import PersonFormField
+from openslides.utils.person.api import get_person, generate_person_id
+
+
+class PersonField(models.fields.Field):
+ __metaclass__ = models.SubfieldBase
+
+ def __init__(self, *args, **kwargs):
+ super(PersonField, self).__init__(max_length=255, *args, **kwargs)
+ # TODO: Validate the uid
+
+ def get_internal_type(self):
+ return "CharField"
+
+ def to_python(self, value):
+ """
+ Convert string value to a User Object.
+ """
+ if hasattr(value, 'person_id'):
+ person = value
+ else:
+ person = get_person(value)
+
+ person.prepare_database_save = (
+ lambda unused: PersonField().get_prep_value(person))
+ return person
+
+ def get_prep_value(self, value):
+ return value.person_id
+
+ def value_to_string(self, obj):
+ value = self._get_val_from_obj(obj)
+ return self.get_prep_value(value)
+
+ def formfield(self, **kwargs):
+ defaults = {'form_class': PersonFormField}
+ defaults.update(kwargs)
+ return super(PersonField, self).formfield(**defaults)
+
+
+class PersonMixin(object):
+ @property
+ def person_id(self):
+ try:
+ return generate_person_id(self.person_prefix, self.pk)
+ except AttributeError:
+ raise AttributeError("%s has to have a attribute 'user_prefix'"
+ % self)
+
+ def __repr__(self):
+ return 'Person: %s' % self.person_id
diff --git a/openslides/utils/person/signals.py b/openslides/utils/person/signals.py
new file mode 100644
index 000000000..5d8dba88b
--- /dev/null
+++ b/openslides/utils/person/signals.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ openslides.utils.user.signals
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Defines Signals for the user.
+
+ :copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
+ :license: GNU GPL, see LICENSE for more details.
+"""
+
+from django.dispatch import Signal
+
+receiv_persons = Signal(providing_args=['person_prefix', 'id'])