Merge branch 'master' into install

Conflicts:
	openslides/__init__.py
This commit is contained in:
Oskar Hahn 2012-08-08 13:45:32 +02:00
commit b19f6a7d74
32 changed files with 863 additions and 437 deletions

View File

@ -38,7 +38,7 @@
[ [
"can_see_participant", "can_see_participant",
"participant", "participant",
"profile" "openslidesuser"
], ],
[ [
"can_see_projector", "can_see_projector",
@ -92,7 +92,7 @@
[ [
"can_see_participant", "can_see_participant",
"participant", "participant",
"profile" "openslidesuser"
], ],
[ [
"can_see_projector", "can_see_projector",
@ -161,12 +161,12 @@
[ [
"can_manage_participant", "can_manage_participant",
"participant", "participant",
"profile" "openslidesuser"
], ],
[ [
"can_see_participant", "can_see_participant",
"participant", "participant",
"profile" "openslidesuser"
], ],
[ [
"can_manage_projector", "can_manage_projector",
@ -195,12 +195,12 @@
[ [
"can_manage_participant", "can_manage_participant",
"participant", "participant",
"profile" "openslidesuser"
], ],
[ [
"can_see_participant", "can_see_participant",
"participant", "participant",
"profile" "openslidesuser"
], ],
[ [
"can_see_projector", "can_see_projector",

View File

@ -5,10 +5,11 @@
:license: GNU GPL, see LICENSE for more details. :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): def get_version(version=None):
"""Derives a PEP386-compliant version number from VERSION.""" """Derives a PEP386-compliant version number from VERSION."""
# TODO: Get the Version Hash from GIT.
if version is None: if version is None:
version = VERSION version = VERSION
assert len(version) == 5 assert len(version) == 5

View File

@ -52,7 +52,14 @@ class Item(MPTTModel, SlideMixin):
""" """
return the object, of which the item points. 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): def get_related_type(self):
""" """

View File

@ -15,9 +15,9 @@ from django.test.client import Client
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models.query import EmptyQuerySet 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): class ItemTest(TestCase):
def setUp(self): def setUp(self):
@ -60,6 +60,10 @@ class ItemTest(TestCase):
self.assertEqual(initial['parent'], 0) self.assertEqual(initial['parent'], 0)
self.assertEqual(initial['weight'], item.weight) 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): class ViewTest(TestCase):
def setUp(self): def setUp(self):

View File

@ -11,31 +11,13 @@
""" """
from django import forms from django import forms
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _, ugettext_noop
from openslides.utils.forms import CssClassMixin from openslides.utils.forms import CssClassMixin
from openslides.utils.person import PersonFormField, MultiplePersonFormField
from openslides.application.models import Application 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): class ApplicationForm(forms.Form, CssClassMixin):
title = forms.CharField(widget=forms.TextInput(), label=_("Title")) title = forms.CharField(widget=forms.TextInput(), label=_("Title"))
text = forms.CharField(widget=forms.Textarea(), label=_("Text")) text = forms.CharField(widget=forms.Textarea(), label=_("Text"))
@ -50,11 +32,7 @@ class ApplicationFormTrivialChanges(ApplicationForm):
class ApplicationManagerForm(forms.ModelForm, CssClassMixin): class ApplicationManagerForm(forms.ModelForm, CssClassMixin):
submitter = UserModelChoiceField( submitter = PersonFormField()
queryset=User.objects.all().exclude(profile=None).
order_by("first_name"),
label=_("Submitter"),
)
class Meta: class Meta:
model = Application model = Application
@ -62,11 +40,8 @@ class ApplicationManagerForm(forms.ModelForm, CssClassMixin):
class ApplicationManagerFormSupporter(ApplicationManagerForm): class ApplicationManagerFormSupporter(ApplicationManagerForm):
supporter = UserModelMultipleChoiceField( # TODO: Do not show the submitter in the user-list
queryset=User.objects.all().exclude(profile=None). supporter = MultiplePersonFormField(required=False, label=_("Supporters"))
order_by("first_name"),
required=False, label=_("Supporters"),
)
class ApplicationImportForm(forms.Form, CssClassMixin): class ApplicationImportForm(forms.Form, CssClassMixin):

View File

@ -21,11 +21,12 @@ from django.utils.translation import pgettext
from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext from django.utils.translation import ugettext_lazy as _, ugettext_noop, ugettext
from openslides.utils.utils import _propper_unicode from openslides.utils.utils import _propper_unicode
from openslides.utils.person import PersonField
from openslides.config.models import config from openslides.config.models import config
from openslides.config.signals import default_config_value 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, from openslides.poll.models import (BaseOption, BasePoll, CountVotesCast,
CountInvalid, BaseVote) CountInvalid, BaseVote)
@ -36,6 +37,11 @@ from openslides.projector.models import SlideMixin
from openslides.agenda.models import Item from openslides.agenda.models import Item
class ApplicationSupporter(models.Model):
application = models.ForeignKey("Application")
person = PersonField()
class Application(models.Model, SlideMixin): class Application(models.Model, SlideMixin):
prefix = "application" prefix = "application"
STATUS = ( STATUS = (
@ -60,9 +66,7 @@ class Application(models.Model, SlideMixin):
# genpoll # genpoll
) )
submitter = models.ForeignKey(User, verbose_name=_("Submitter")) submitter = PersonField(verbose_name=_("Submitter"))
supporter = models.ManyToManyField(User, related_name='supporter', \
null=True, blank=True, verbose_name=_("Supporters"))
number = models.PositiveSmallIntegerField(blank=True, null=True, number = models.PositiveSmallIntegerField(blank=True, null=True,
unique=True) unique=True)
status = models.CharField(max_length=3, choices=STATUS, default='pub') status = models.CharField(max_length=3, choices=STATUS, default='pub')
@ -157,6 +161,14 @@ class Application(models.Model, SlideMixin):
else: else:
return False 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 @property
def enough_supporters(self): def enough_supporters(self):
""" """
@ -164,17 +176,20 @@ class Application(models.Model, SlideMixin):
""" """
min_supporters = int(config['application_min_supporters']) min_supporters = int(config['application_min_supporters'])
if self.status == "pub": if self.status == "pub":
return self.supporter.count() >= min_supporters return self.count_supporters() >= min_supporters
else: else:
return True return True
def count_supporters(self):
return self.applicationsupporter_set.count()
@property @property
def missing_supporters(self): def missing_supporters(self):
""" """
Return number of missing supporters Return number of missing supporters
""" """
min_supporters = int(config['application_min_supporters']) min_supporters = int(config['application_min_supporters'])
delta = min_supporters - self.supporter.count() delta = min_supporters - self.count_supporters()
if delta > 0: if delta > 0:
return delta return delta
else: else:
@ -221,10 +236,11 @@ class Application(models.Model, SlideMixin):
except AttributeError: except AttributeError:
is_manager = False is_manager = False
supporters = self.applicationsupporter_set.all()
if (self.status == "pub" if (self.status == "pub"
and self.supporter.exists() and supporters
and not is_manager): and not is_manager):
self.supporter.clear() supporters.delete()
self.writelog(_("Supporters removed"), user) self.writelog(_("Supporters removed"), user)
def reset(self, user): def reset(self, user):
@ -236,34 +252,41 @@ class Application(models.Model, SlideMixin):
self.save() self.save()
self.writelog(_("Status reseted to: %s") % (self.get_status_display()), user) 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. 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 ' \ raise NameError('Supporter can not be the submitter of a ' \
'application.') 'application.')
if self.permitted is not None: if self.permitted is not None:
# TODO: Use own Exception
raise NameError('This application is already permitted.') raise NameError('This application is already permitted.')
if user not in self.supporter.all(): if not self.is_supporter(person):
self.supporter.add(user) ApplicationSupporter(application=self, person=person).save()
self.writelog(_("Supporter: +%s") % (user)) self.writelog(_("Supporter: +%s") % (person))
def unsupport(self, user): def unsupport(self, user):
""" """
remove a supporter from the list of supporters of the application remove a supporter from the list of supporters of the application
""" """
if self.permitted is not None: if self.permitted is not None:
# TODO: Use own Exception
raise NameError('This application is already permitted.') raise NameError('This application is already permitted.')
if user in self.supporter.all(): try:
self.supporter.remove(user) object = self.applicationsupporter_set.get(user=user).delete()
self.writelog(_("Supporter: -%s") % (user)) except ApplicationSupporter.DoesNotExist:
pass
else:
self.writelog(_("Supporter: -%s") % (user))
def set_number(self, number=None, user=None): def set_number(self, number=None, user=None):
""" """
Set a number for ths application. Set a number for ths application.
""" """
if self.number is not None: if self.number is not None:
# TODO: Use own Exception
raise NameError('This application has already a number.') raise NameError('This application has already a number.')
if number is None: if number is None:
try: try:
@ -332,7 +355,7 @@ class Application(models.Model, SlideMixin):
self.writelog(_("Status modified")+": %s -> %s" \ self.writelog(_("Status modified")+": %s -> %s" \
% (oldstatus, self.get_status_display()), user) % (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. Return a list of all the allowed status.
""" """
@ -340,8 +363,8 @@ class Application(models.Model, SlideMixin):
is_admin = False is_admin = False
if user: if user:
try: try:
user.profile user = user.openslidesuser
except Profile.DoesNotExist: except OpenSlidesUser.DoesNotExist:
is_admin = True is_admin = True
except AttributeError: except AttributeError:
# For the anonymous-user # For the anonymous-user
@ -366,16 +389,12 @@ class Application(models.Model, SlideMixin):
actions.append("pub") actions.append("pub")
# Check if the user can support and unspoort the application # Check if the user can support and unspoort the application
try: if (self.status == "pub"
if (self.status == "pub" and user != self.submitter
and user != self.submitter and not self.is_supporter(user)):
and user not in self.supporter.all() actions.append("support")
and getattr(user, 'profile', None)):
actions.append("support")
except Profile.DoesNotExist:
pass
if self.status == "pub" and user in self.supporter.all(): if self.status == "pub" and self.is_supporter(user):
actions.append("unsupport") actions.append("unsupport")
#Check if the user can edit the application #Check if the user can edit the application

View File

@ -59,7 +59,7 @@
{% if not forloop.last %}<br>{%endif%} {% if not forloop.last %}<br>{%endif%}
{% endfor %} {% endfor %}
</td> </td>
<td>{{ application.submitter.profile }}</td> <td>{{ application.submitter }}</td>
<td>{{ application.creation_time }}</td> <td>{{ application.creation_time }}</td>
<td> <td>
<span style="width: 1px; white-space: nowrap;"> <span style="width: 1px; white-space: nowrap;">

View File

@ -16,22 +16,22 @@
<div id="sidebar"> <div id="sidebar">
<div class="box"> <div class="box">
<h4>{% trans "Submitter" %}:</h4> <h4>{% trans "Submitter" %}:</h4>
{{ application.submitter.profile }} {{ application.submitter }}
{% if user == application.submitter %} {% if user == application.submitter.user %}
<img src="{% static 'images/icons/user-information.png' %}" title="{% trans 'You!' %}"> <img src="{% static 'images/icons/user-information.png' %}" title="{% trans 'You!' %}">
{% endif %} {% endif %}
{% if min_supporters > 0 %} {% if min_supporters > 0 %}
<h4>{% trans "Supporters" %}: *</h4> <h4>{% trans "Supporters" %}: *</h4>
{% if application.supporter.count == 0 %} {% if not application.supporters %}
- -
{% else %} {% else %}
<ol> <ol>
{% for supporter in application.supporter.all %} {% for supporter in application.supporters %}
<li> {{ supporter.profile }}</li> <li> {{ supporter }}</li>
{% endfor %} {% endfor %}
</ol> </ol>
{% endif %} {% endif %}
{% endif %} {% endif %}
<h4>{% trans "Status" %}:</h4> <h4>{% trans "Status" %}:</h4>
@ -113,7 +113,7 @@
{{ application.creation_time }} {{ application.creation_time }}
<p></p> <p></p>
{% if "wit" in actions and user == application.submitter %} {% if "wit" in actions and user == application.submitter.user %}
<p></p> <p></p>
<a href='{% url application_set_status application.id 'wit' %}'> <a href='{% url application_set_status application.id 'wit' %}'>
<span class="button"><span class="icon revert">{% trans 'Withdraw' %}</span></span> <span class="button"><span class="icon revert">{% trans 'Withdraw' %}</span></span>

View File

@ -53,7 +53,7 @@
{% endwith %} {% endwith %}
<p><b>{% trans "Submitter" %}:</b><br> <p><b>{% trans "Submitter" %}:</b><br>
{{ application.submitter.profile }} {{ application.submitter }}
</p> </p>
</div> </div>
</div> </div>

View File

@ -20,7 +20,7 @@ class ApplicationTest(TestCase):
def setUp(self): def setUp(self):
self.admin = User.objects.create_user('testadmin', '', 'default') self.admin = User.objects.create_user('testadmin', '', 'default')
self.anonym = User.objects.create_user('testanoym', '', '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() self.app1.save()
def refresh(self): def refresh(self):

View File

@ -42,6 +42,7 @@ from openslides.utils.template import Tab
from openslides.utils.utils import (template, permission_required, from openslides.utils.utils import (template, permission_required,
del_confirm_form, gen_confirm_form) del_confirm_form, gen_confirm_form)
from openslides.utils.views import PDFView, RedirectView, DeleteView, FormView from openslides.utils.views import PDFView, RedirectView, DeleteView, FormView
from openslides.utils.person import get_person
from openslides.config.models import config from openslides.config.models import config
@ -50,7 +51,7 @@ from openslides.projector.projector import Widget
from openslides.poll.views import PollFormView from openslides.poll.views import PollFormView
from openslides.participant.api import gen_username, gen_password 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 from openslides.agenda.models import Item
@ -123,7 +124,7 @@ def overview(request):
for (i, application) in enumerate(applications): for (i, application) in enumerate(applications):
try: try:
applications[i] = { applications[i] = {
'actions' : application.get_allowed_actions(request.user), 'actions' : application.get_allowed_actions(request.user.openslidesuser),
'application' : application 'application' : application
} }
except: except:
@ -151,7 +152,8 @@ def view(request, application_id, newest=False):
else: else:
version = application.public_version version = application.public_version
revisions = application.versions revisions = application.versions
actions = application.get_allowed_actions(user=request.user) user = request.user.openslidesuser
actions = application.get_allowed_actions(user=user)
return { return {
'application': application, 'application': application,
@ -180,10 +182,12 @@ def edit(request, application_id=None):
return redirect(reverse('application_overview')) return redirect(reverse('application_overview'))
if application_id is not None: if application_id is not None:
application = Application.objects.get(id=application_id) 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.")) messages.error(request, _("You can not edit this application. You are not the submitter."))
return redirect(reverse('application_view', args=[application.id])) 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: else:
application = None application = None
actions = None actions = None
@ -210,14 +214,14 @@ def edit(request, application_id=None):
if valid: if valid:
del_supporters = True del_supporters = True
original_supporters = []
if is_manager: if is_manager:
if application: # Edit application if application: # Edit application
for s in application.supporter.all(): original_supporters = list(application.supporters)
original_supporters.append(s) else:
original_supporters = []
application = managerform.save(commit=False) application = managerform.save(commit=False)
elif application_id is None: elif application_id is None:
application = Application(submitter=request.user) application = Application(submitter=request.user.openslidesuser)
application.title = dataform.cleaned_data['title'] application.title = dataform.cleaned_data['title']
application.text = dataform.cleaned_data['text'] application.text = dataform.cleaned_data['text']
application.reason = dataform.cleaned_data['reason'] application.reason = dataform.cleaned_data['reason']
@ -227,30 +231,21 @@ def edit(request, application_id=None):
and dataform.cleaned_data['trivial_change'] and dataform.cleaned_data['trivial_change']
except KeyError: except KeyError:
trivial_change = False trivial_change = False
application.save(request.user, trivial_change=trivial_change) application.save(request.user.openslidesuser, trivial_change=trivial_change)
if is_manager: if is_manager:
# log added supporters try:
supporters_added = [] new_supporters = set(managerform.cleaned_data['supporter'])
for s in application.supporter.all(): except KeyError:
if s not in original_supporters: # The managerform has no field for the supporters
try: pass
supporters_added.append(unicode(s.profile)) else:
except Profile.DoesNotExist: old_supporters = set(application.supporters)
pass # add new supporters
if len(supporters_added) > 0: for supporter in new_supporters.difference(old_supporters):
log_added = ", ".join(supporters_added) application.support(supporter)
application.writelog(_("Supporter: +%s") % log_added, request.user) # remove old supporters
# log removed supporters for supporter in old_supporters.difference(new_supporters):
supporters_removed = [] application.unsupport(supporter)
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)
if application_id is None: if application_id is None:
messages.success(request, _('New application was successfully created.')) messages.success(request, _('New application was successfully created.'))
else: else:
@ -266,11 +261,11 @@ def edit(request, application_id=None):
if application_id is None: if application_id is None:
initial = {'text': config['application_preamble']} initial = {'text': config['application_preamble']}
else: 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'): if request.user.has_perm('application.can_manage_application'):
messages.warning(request, _("Attention: Do you really want to edit this application? The supporters will <b>not</b> be removed automatically because you can manage applications. Please check if the supports are valid after your changing!")) messages.warning(request, _("Attention: Do you really want to edit this application? The supporters will <b>not</b> be removed automatically because you can manage applications. Please check if the supports are valid after your changing!"))
else: else:
messages.warning(request, _("Attention: Do you really want to edit this application? All <b>%s</b> 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 <b>%s</b> supporters will be removed! Try to convince the supporters again.") % len(application.supporters) )
initial = {'title': application.title, initial = {'title': application.title,
'text': application.text, 'text': application.text,
'reason': application.reason} 'reason': application.reason}
@ -278,12 +273,12 @@ def edit(request, application_id=None):
dataform = formclass(initial=initial, prefix="data") dataform = formclass(initial=initial, prefix="data")
if is_manager: if is_manager:
if application_id is None: if application_id is None:
initial = {'submitter': str(request.user.id)} initial = {'submitter': request.user.openslidesuser.person_id}
else: else:
initial = {} initial = {'submitter': application.submitter.person_id,
managerform = managerformclass(initial=initial, \ 'supporter': [supporter.person_id for supporter in application.supporters]}
instance=application, \ managerform = managerformclass(initial=initial,
prefix="manager") instance=application, prefix="manager")
else: else:
managerform = None managerform = None
return { return {
@ -301,7 +296,7 @@ def set_number(request, application_id):
set a number for an application. set a number for an application.
""" """
try: 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.")) messages.success(request, _("Application number was successfully set."))
except Application.DoesNotExist: except Application.DoesNotExist:
pass pass
@ -317,7 +312,7 @@ def permit(request, application_id):
permit an application. permit an application.
""" """
try: 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.")) messages.success(request, _("Application was successfully permitted."))
except Application.DoesNotExist: except Application.DoesNotExist:
pass pass
@ -332,7 +327,7 @@ def notpermit(request, application_id):
reject (not permit) an application. reject (not permit) an application.
""" """
try: 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.")) messages.success(request, _("Application was successfully rejected."))
except Application.DoesNotExist: except Application.DoesNotExist:
pass pass
@ -348,7 +343,7 @@ def set_status(request, application_id=None, status=None):
try: try:
if status is not None: if status is not None:
application = Application.objects.get(pk=application_id) 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: <b>%s</b>.") % application.get_status_display()) messages.success(request, _("Application status was set to: <b>%s</b>.") % application.get_status_display())
except Application.DoesNotExist: except Application.DoesNotExist:
pass pass
@ -364,7 +359,7 @@ def reset(request, application_id):
reset an application. reset an application.
""" """
try: 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.") ) messages.success(request, _("Application status was reset.") )
except Application.DoesNotExist: except Application.DoesNotExist:
pass pass
@ -378,7 +373,7 @@ def support(request, application_id):
support an application. support an application.
""" """
try: 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.") ) messages.success(request, _("You have support the application successfully.") )
except Application.DoesNotExist: except Application.DoesNotExist:
pass pass
@ -392,7 +387,7 @@ def unsupport(request, application_id):
unsupport an application. unsupport an application.
""" """
try: 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.") ) messages.success(request, _("You have unsupport the application successfully.") )
except Application.DoesNotExist: except Application.DoesNotExist:
pass pass
@ -406,7 +401,7 @@ def gen_poll(request, application_id):
gen a poll for this application. gen a poll for this application.
""" """
try: 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.") ) messages.success(request, _("New vote was successfully created.") )
except Application.DoesNotExist: except Application.DoesNotExist:
pass # TODO: do not call poll after this excaption 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() count = application.polls.filter(id__lte=poll_id).count()
if request.method == 'POST': if request.method == 'POST':
poll.delete() poll.delete()
application.writelog(_("Poll deleted"), request.user) application.writelog(_("Poll deleted"), request.user.openslidesuser)
messages.success(request, _('Poll was successfully deleted.')) messages.success(request, _('Poll was successfully deleted.'))
else: else:
del_confirm_form(request, poll, name=_("the %s. poll") % count, delete_link=reverse('application_poll_delete', args=[poll_id])) 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): if len(self.applications):
for application in 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 <b>%s</b>.") % application) messages.error(request, _("You can not delete application <b>%s</b>.") % application)
continue continue
@ -472,12 +467,12 @@ class ApplicationDelete(DeleteView):
messages.success(request, _("Application <b>%s</b> was successfully deleted.") % title) messages.success(request, _("Application <b>%s</b> was successfully deleted.") % title)
elif self.object: elif self.object:
if not 'delete' in self.object.get_allowed_actions(user=request.user): if not 'delete' in self.object.get_allowed_actions(user=request.user.openslidesuser):
messages.error(request, _("You can not delete application <b>%s</b>.") % self.object) messages.error(request, _("You can not delete application <b>%s</b>.") % self.object)
else: else:
title = self.object.title title = self.object.title
self.object.delete(force=True) self.object.delete(force=True)
messages.success(request, _("Application <b>%s</b> was successfully deleted.") % title) messages.success(request, _("Application <b>%s</b> was successfully deleted.") % title)
else: else:
messages.error(request, _("Invalid request")) messages.error(request, _("Invalid request"))
@ -513,12 +508,12 @@ class ViewPoll(PollFormView):
self.application = self.poll.get_application() self.application = self.poll.get_application()
context['application'] = self.application context['application'] = self.application
context['ballot'] = self.poll.get_ballot() 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 return context
def get_modelform_class(self): def get_modelform_class(self):
cls = super(ViewPoll, self).get_modelform_class() cls = super(ViewPoll, self).get_modelform_class()
user = self.request.user user = self.request.user.openslidesuser
class ViewPollFormClass(cls): class ViewPollFormClass(cls):
def save(self, commit = True): def save(self, commit = True):
@ -540,7 +535,7 @@ def permit_version(request, aversion_id):
aversion = AVersion.objects.get(pk=aversion_id) aversion = AVersion.objects.get(pk=aversion_id)
application = aversion.application application = aversion.application
if request.method == 'POST': if request.method == 'POST':
application.accept_version(aversion, user = request.user) application.accept_version(aversion, user=request.user.openslidesuser)
messages.success(request, _("Version <b>%s</b> accepted.") % (aversion.aid)) messages.success(request, _("Version <b>%s</b> accepted.") % (aversion.aid))
else: else:
gen_confirm_form(request, _('Do you really want to permit version <b>%s</b>?') % aversion.aid, reverse('application_version_permit', args=[aversion.id])) gen_confirm_form(request, _('Do you really want to permit version <b>%s</b>?') % 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) aversion = AVersion.objects.get(pk=aversion_id)
application = aversion.application application = aversion.application
if request.method == 'POST': 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 <b>%s</b> rejected.") % (aversion.aid)) messages.success(request, _("Version <b>%s</b> rejected.") % (aversion.aid))
else: else:
messages.error(request, _("ERROR by rejecting the version.") ) messages.error(request, _("ERROR by rejecting the version.") )
@ -565,14 +560,15 @@ def reject_version(request, aversion_id):
@template('application/import.html') @template('application/import.html')
def application_import(request): def application_import(request):
try: try:
request.user.profile request.user.openslidesuser
messages.error(request, _('The import function is available for the admin (without user profile) only.')) except OpenSlidesUser.DoesNotExist:
return redirect(reverse('application_overview'))
except Profile.DoesNotExist:
pass pass
except AttributeError: except AttributeError:
# AnonymousUser # AnonymousUser
pass 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': if request.method == 'POST':
form = ApplicationImportForm(request.POST, request.FILES) form = ApplicationImportForm(request.POST, request.FILES)
@ -752,13 +748,10 @@ class ApplicationPDF(PDFView):
if application.status == "pub": if application.status == "pub":
cell1b.append(Paragraph("__________________________________________",stylesheet['Signaturefield'])) cell1b.append(Paragraph("__________________________________________",stylesheet['Signaturefield']))
cell1b.append(Spacer(0,0.1*cm)) cell1b.append(Spacer(0,0.1*cm))
cell1b.append(Paragraph("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"+unicode(application.submitter.profile), stylesheet['Small'])) cell1b.append(Paragraph("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"+unicode(application.submitter), stylesheet['Small']))
cell1b.append(Spacer(0,0.2*cm)) cell1b.append(Spacer(0,0.2*cm))
else: else:
try: cell1b.append(Paragraph(unicode(application.submitter), stylesheet['Normal']))
cell1b.append(Paragraph(unicode(application.submitter.profile), stylesheet['Normal']))
except Profile.DoesNotExist:
pass
# supporters # supporters
cell2a = [] cell2a = []
@ -767,8 +760,8 @@ class ApplicationPDF(PDFView):
cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset id='counter'>" % _("Supporters"), stylesheet['Heading4'])) cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset id='counter'>" % _("Supporters"), stylesheet['Heading4']))
for s in application.supporter.all(): for supporter in application.supporters:
cell2b.append(Paragraph("<seq id='counter'/>.&nbsp; %s" % unicode(s.profile), stylesheet['Signaturefield'])) cell2b.append(Paragraph("<seq id='counter'/>.&nbsp; %s" % unicode(supporter), stylesheet['Signaturefield']))
if application.status == "pub": if application.status == "pub":
for x in range(0,application.missing_supporters): for x in range(0,application.missing_supporters):
cell2b.append(Paragraph("<seq id='counter'/>.&nbsp; __________________________________________",stylesheet['Signaturefield'])) cell2b.append(Paragraph("<seq id='counter'/>.&nbsp; __________________________________________",stylesheet['Signaturefield']))

View File

@ -14,8 +14,8 @@ from django import forms
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _, ugettext_noop
from openslides.utils.forms import CssClassMixin from openslides.utils.forms import CssClassMixin
from openslides.utils.person import PersonFormField
from openslides.participant.models import Profile
from openslides.assignment.models import Assignment from openslides.assignment.models import Assignment
@ -25,13 +25,12 @@ class AssignmentForm(forms.ModelForm, CssClassMixin):
class Meta: class Meta:
model = Assignment model = Assignment
exclude = ('status', 'profile', 'elected') exclude = ('status', 'elected')
class AssignmentRunForm(forms.Form, CssClassMixin): class AssignmentRunForm(forms.Form, CssClassMixin):
candidate = forms.ModelChoiceField( candidate = PersonFormField(
widget=forms.Select(attrs={'class': 'medium-input'}), widget=forms.Select(attrs={'class': 'medium-input'}),
queryset=Profile.objects.all().order_by('user__first_name'),
label=_("Nominate a participant"), label=_("Nominate a participant"),
) )

View File

@ -15,20 +15,29 @@ from django.db import models
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _, ugettext_noop 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.models import config
from openslides.config.signals import default_config_value from openslides.config.signals import default_config_value
from openslides.projector.api import register_slidemodel from openslides.projector.api import register_slidemodel
from openslides.projector.projector import SlideMixin from openslides.projector.projector import SlideMixin
from openslides.participant.models import Profile
from openslides.poll.models import (BasePoll, CountInvalid, CountVotesCast, from openslides.poll.models import (BasePoll, CountInvalid, CountVotesCast,
BaseOption, PublishPollMixin, BaseVote) BaseOption, PublishPollMixin, BaseVote)
from openslides.agenda.models import Item 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): class Assignment(models.Model, SlideMixin):
prefix = 'assignment' prefix = 'assignment'
STATUS = ( STATUS = (
@ -44,9 +53,6 @@ class Assignment(models.Model, SlideMixin):
verbose_name=_("Number of available posts")) verbose_name=_("Number of available posts"))
polldescription = models.CharField(max_length=100, null=True, blank=True, polldescription = models.CharField(max_length=100, null=True, blank=True,
verbose_name=_("Comment on the ballot paper")) 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') status = models.CharField(max_length=3, choices=STATUS, default='sea')
def set_status(self, status): def set_status(self, status):
@ -63,61 +69,75 @@ class Assignment(models.Model, SlideMixin):
self.status = status self.status = status
self.save() self.save()
def run(self, profile, user=None): def run(self, candidate, person=None):
""" """
run for a vote run for a vote
""" """
if self.is_candidate(profile): # TODO: don't make any permission checks here.
raise NameError(_('<b>%s</b> is already a candidate.') % profile) # Use other Exceptions
if not user.has_perm("assignment.can_manage_assignment") and self.status != 'sea': if self.is_candidate(candidate):
raise NameError(_('<b>%s</b> 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.')) 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 stop running for a vote
""" """
if not user.has_perm("assignment.can_manage_assignment") and self.status != 'sea': if self.is_candidate(candidate):
raise NameError(_('The candidate list is already closed.')) self.assignment_candidats.get(person=candidate).delete()
if self.is_candidate(profile):
self.profile.remove(profile)
self.elected.remove(profile)
else: 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): def is_candidate(self, person):
if profile in self.profile.get_query_set(): if self.assignment_candidats.filter(person=person).exists():
return True return True
else: else:
return False return False
@property
def assignment_candidats(self):
return AssignmentCandidate.objects.filter(assignment=self)
@property @property
def candidates(self): 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): def set_elected(self, person, value=True):
if profile in self.candidates: candidate = self.assignment_candidats.get(person=person)
if value and not self.is_elected(profile): candidate.elected = value
self.elected.add(profile) candidate.save()
elif not value:
self.elected.remove(profile)
def is_elected(self, profile): def is_elected(self, person):
if profile in self.elected.all(): return person in self.elected
return True
return False
def gen_poll(self): def gen_poll(self):
poll = AssignmentPoll(assignment=self) poll = AssignmentPoll(assignment=self)
poll.save() poll.save()
candidates = list(self.profile.all()) poll.set_options([{'candidate': person} for person in self.candidates])
for elected in self.elected.all():
try:
candidates.remove(elected)
except ValueError:
pass
poll.set_options([{'candidate': profile} for profile in candidates])
return poll return poll
@ -159,6 +179,7 @@ class Assignment(models.Model, SlideMixin):
return self.name return self.name
def delete(self): def delete(self):
# Remove any Agenda-Item, which is related to this application.
for item in Item.objects.filter(related_sid=self.sid): for item in Item.objects.filter(related_sid=self.sid):
item.delete() item.delete()
super(Assignment, self).delete() super(Assignment, self).delete()
@ -206,7 +227,7 @@ class AssignmentVote(BaseVote):
class AssignmentOption(BaseOption): class AssignmentOption(BaseOption):
poll = models.ForeignKey('AssignmentPoll') poll = models.ForeignKey('AssignmentPoll')
candidate = models.ForeignKey(Profile) candidate = PersonField()
vote_class = AssignmentVote vote_class = AssignmentVote
def __unicode__(self): def __unicode__(self):
@ -230,8 +251,7 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin):
self.yesnoabstain = True self.yesnoabstain = True
else: else:
# candidates <= available posts -> yes/no/abstain # candidates <= available posts -> yes/no/abstain
if self.assignment.candidates.count() <= (self.assignment.posts if self.assignment.assignment_candidats.filter(elected=False).count() <= (self.assignment.posts):
- self.assignment.elected.count()):
self.yesnoabstain = True self.yesnoabstain = True
else: else:
self.yesnoabstain = False self.yesnoabstain = False
@ -260,8 +280,6 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin):
return _("Ballot %d") % self.get_ballot() return _("Ballot %d") % self.get_ballot()
@receiver(default_config_value, dispatch_uid="assignment_default_config") @receiver(default_config_value, dispatch_uid="assignment_default_config")
def default_config(sender, key, **kwargs): def default_config(sender, key, **kwargs):
return { return {

View File

@ -31,7 +31,7 @@
<tr class="{% cycle '' 'odd' %} <tr class="{% cycle '' 'odd' %}
{% if assignment.active %}activeline{% endif %}"> {% if assignment.active %}activeline{% endif %}">
<td><a href="{% url assignment_view assignment.id %}">{{ assignment }}</a></td> <td><a href="{% url assignment_view assignment.id %}">{{ assignment }}</a></td>
<td>{{ assignment.profile.count }} / {{ assignment.posts }}</td> <td>{{ assignment.candidates|length }} / {{ assignment.posts }}</td>
<td>{{ assignment.get_status_display }}</td> <td>{{ assignment.get_status_display }}</td>
<td> <td>
<span style="width: 1px; white-space: nowrap;"> <span style="width: 1px; white-space: nowrap;">

View File

@ -21,36 +21,36 @@
{% endfor %} {% endfor %}
</tr> </tr>
{% for form in forms %} {% for form in forms %}
<tr> <tr>
<td>{{ form.option }}</td> <td>{{ form.option }}</td>
{% for value in form %} {% for value in form %}
<td> <td>
{{ value.errors }} {{ value.errors }}
{{ value }} {{ value }}
</td> </td>
{% endfor %}
</tr>
{% endfor %}
<tr class="total">
<td>{% trans "Invalid votes" %}</td>
{% for value in poll.get_vote_values %}
{% if forloop.first %}
<td>{{ pollform.votesinvalid.errors }}{{ pollform.votesinvalid }}</td>
{% else %}
<td></td>
{% endif %}
{% endfor %}
</tr>
<tr class="total">
<td>{% trans "Votes cast" %}</td>
{% for value in poll.get_vote_values %}
{% if forloop.first %}
<td>{{ pollform.votescast.errors }}{{ pollform.votescast }}</td>
{% else %}
<td></td>
{% endif %}
{% endfor %} {% endfor %}
</tr> </tr>
{% endfor %}
<tr class="total">
<td>{% trans "Invalid votes" %}</td>
{% for value in poll.get_vote_values %}
{% if forloop.first %}
<td>{{ pollform.votesinvalid.errors }}{{ pollform.votesinvalid }}</td>
{% else %}
<td></td>
{% endif %}
{% endfor %}
</tr>
<tr class="total">
<td>{% trans "Votes cast" %}</td>
{% for value in poll.get_vote_values %}
{% if forloop.first %}
<td>{{ pollform.votescast.errors }}{{ pollform.votescast }}</td>
{% else %}
<td></td>
{% endif %}
{% endfor %}
</tr>
</table> </table>
<p> <p>

View File

@ -35,13 +35,14 @@
<h3>{% trans "Candidates" %}</h3> <h3>{% trans "Candidates" %}</h3>
<ol> <ol>
{% for profile in assignment.profile.all|dictsort:"user.first_name" %} {% for person in assignment.candidates %}
<li>{{ profile }} <li>
{% if perms.assignment.can_manage_assignment %} {{ person }}
{% if assignment.status == "sea" or assignment.status == "vot" %} {% if perms.assignment.can_manage_assignment %}
<a href="{% url assignment_delother assignment.id profile.id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Remove candidate' %}"></a> {% if assignment.status == "sea" or assignment.status == "vot" %}
{% endif %} <a href="{% url assignment_delother assignment.id person.person_id %}"><img src="{% static 'images/icons/delete.png' %}" title="{% trans 'Remove candidate' %}"></a>
{% endif %} {% endif %}
{% endif %}
</li> </li>
{% empty %} {% empty %}
<li style="list-style: none outside none;"><i>{% trans "No candidates available." %}</i></li> <li style="list-style: none outside none;"><i>{% trans "No candidates available." %}</i></li>
@ -53,21 +54,18 @@
<form action="" method="post">{% csrf_token %} <form action="" method="post">{% csrf_token %}
{% if perms.assignment.can_nominate_self %} {% if perms.assignment.can_nominate_self %}
<p> <p>
{% if user.profile in assignment.profile.all %} {% if user_is_candidate %}
<a href='{% url assignment_delrun assignment.id %}'>
<a href='{% url assignment_delrun assignment.id %}'> <span class="button">
<span class="button"> <span class="icon removeuser">{% trans 'Withdraw self candidature' %}</span>
<span class="icon removeuser">{% trans 'Withdraw self candidature' %}</span> </span>
</span> </a>
</a>
{% else %} {% else %}
{% if user.profile %} <a href='{% url assignment_run assignment.id %}'>
<a href='{% url assignment_run assignment.id %}'> <span class="button">
<span class="button"> <span class="icon adduser">{% trans 'Self candidature' %}</span>
<span class="icon adduser">{% trans 'Self candidature' %}</span> </span>
</span> </a>
</a>
{% endif %}
{% endif %} {% endif %}
</p> </p>
{% endif %} {% endif %}
@ -123,7 +121,7 @@
{% endif %} {% endif %}
</th> </th>
{% endfor %} {% 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" %}
<th> <th>
<a href='{% url assignment_gen_poll assignment.id %}'> <a href='{% url assignment_gen_poll assignment.id %}'>
<span class="button"> <span class="button">
@ -137,9 +135,9 @@
{% for candidate, poll_list in vote_results.items %} {% for candidate, poll_list in vote_results.items %}
<tr class="{% cycle 'odd' '' %}"> <tr class="{% cycle 'odd' '' %}">
<td class="candidate"> <td class="candidate">
{% if candidate in assignment.elected.all %} {% if candidate in assignment.elected %}
{% if perms.assignment.can_manage_assignment %} {% if perms.assignment.can_manage_assignment %}
<a class="election_link elected" href='{% url assignment_user_not_elected assignment.id candidate.id %}'></a> <a class="election_link elected" href='{% url assignment_user_not_elected assignment.id candidate.person_id %}'></a>
{% else %} {% else %}
<a class="elected"> <a class="elected">
<img src="{% static 'images/icons/voting-yes.png' %}" title="{% trans 'Candidate is elected' %}"> <img src="{% static 'images/icons/voting-yes.png' %}" title="{% trans 'Candidate is elected' %}">
@ -147,7 +145,7 @@
{% endif %} {% endif %}
{% else %} {% else %}
{% if perms.assignment.can_manage_assignment %} {% if perms.assignment.can_manage_assignment %}
<a class="election_link" href='{% url assignment_user_elected assignment.id candidate.id %}'></a> <a class="election_link" href='{% url assignment_user_elected assignment.id candidate.person_id %}'></a>
{% endif %} {% endif %}
{% endif %} {% endif %}
{{ candidate }} {{ candidate }}
@ -167,7 +165,7 @@
{% endif %} {% endif %}
</td> </td>
{% endfor %} {% 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" %}
<td></td> <td></td>
{% endif %} {% endif %}
</tr> </tr>
@ -185,7 +183,7 @@
</td> </td>
{% endif %} {% endif %}
{% endfor %} {% 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" %}
<td></td> <td></td>
{% endif %} {% endif %}
</tr> </tr>
@ -202,7 +200,7 @@
</td> </td>
{% endif %} {% endif %}
{% endfor %} {% 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" %}
<td></td> <td></td>
{% endif %} {% endif %}
</tr> </tr>
@ -212,7 +210,7 @@
<i>{% trans "No ballots available." %}</i> <i>{% trans "No ballots available." %}</i>
{% 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" %}
<p><a href='{% url assignment_gen_poll assignment.id %}'> <p><a href='{% url assignment_gen_poll assignment.id %}'>
<span class="button"> <span class="button">
<span class="icon statistics">{% trans 'New ballot' %}</span> <span class="icon statistics">{% trans 'New ballot' %}</span>

View File

@ -28,17 +28,17 @@
{% endblock %} {% endblock %}
{% block scrollcontent %} {% block scrollcontent %}
{% if not assignment.profile.exists %} {% if not assignment.candidates %}
<p> <p>
<div class="text">{{ assignment.description|linebreaks }}</div> <div class="text">{{ assignment.description|linebreaks }}</div>
</p> </p>
{% endif %} {% endif %}
{% if assignment.profile.exists and assignment.status != "fin" %} {% if assignment.candidates and assignment.status != "fin" %}
<h3>{% trans "Candidates" %}</h3> <h3>{% trans "Candidates" %}</h3>
<ol> <ol>
{% for profile in assignment.profile.all|dictsort:"user.first_name" %} {% for candidate in assignment.candidates %}
<li>{{ profile }} </li> <li>{{ candidate }} </li>
{% empty %} {% empty %}
<li style="list-style: none outside none;"> <li style="list-style: none outside none;">
<i>{% trans "No candidates available." %}</i> <i>{% trans "No candidates available." %}</i>
@ -122,7 +122,7 @@
</tr> </tr>
</table> </table>
{% elif assignment.profile.exists %} {% elif assignment.candidates %}
<i>{% trans "No ballots available." %}</i> <i>{% trans "No ballots available." %}</i>
{% endif %} {% endif %}
<br> <br>

View File

@ -55,7 +55,7 @@ urlpatterns = patterns('openslides.assignment.views',
name='assignment_delrun', name='assignment_delrun',
), ),
url(r'^(?P<assignment_id>\d+)/delother/(?P<profile_id>\d+)/$', url(r'^(?P<assignment_id>\d+)/delother/(?P<user_id>[^/]+)/$',
'delother', 'delother',
name='assignment_delother', name='assignment_delother',
), ),
@ -105,13 +105,13 @@ urlpatterns = patterns('openslides.assignment.views',
name='assignment_poll_publish_status', name='assignment_poll_publish_status',
), ),
url(r'^(?P<assignment_id>\d+)/elected/(?P<profile_id>\d+)/$', url(r'^(?P<assignment_id>\d+)/elected/(?P<user_id>[^/]+)/$',
'set_elected', 'set_elected',
{'elected': True}, {'elected': True},
name='assignment_user_elected', name='assignment_user_elected',
), ),
url(r'^(?P<assignment_id>\d+)/notelected/(?P<profile_id>\d+)/$', url(r'^(?P<assignment_id>\d+)/notelected/(?P<user_id>[^/]+)/$',
'set_elected', 'set_elected',
{'elected': False}, {'elected': False},
name='assignment_user_not_elected', name='assignment_user_not_elected',

View File

@ -30,9 +30,11 @@ from openslides.utils.template import Tab
from openslides.utils.utils import (template, permission_required, from openslides.utils.utils import (template, permission_required,
gen_confirm_form, del_confirm_form, ajax_request) gen_confirm_form, del_confirm_form, ajax_request)
from openslides.utils.views import FormView, DeleteView, PDFView, RedirectView from openslides.utils.views import FormView, DeleteView, PDFView, RedirectView
from openslides.utils.person import get_person
from openslides.config.models import config from openslides.config.models import config
from openslides.participant.models import Profile
from openslides.participant.models import OpenSlidesUser
from openslides.projector.projector import Widget from openslides.projector.projector import Widget
@ -94,11 +96,14 @@ def view(request, assignment_id=None):
else: else:
polls = assignment.poll_set.all() polls = assignment.poll_set.all()
vote_results = assignment.vote_results(only_published=False) vote_results = assignment.vote_results(only_published=False)
user = request.user.openslidesuser
return { return {
'assignment': assignment, 'assignment': assignment,
'form': form, 'form': form,
'vote_results': vote_results, 'vote_results': vote_results,
'polls': polls, '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): def run(request, assignment_id):
assignment = Assignment.objects.get(pk=assignment_id) assignment = Assignment.objects.get(pk=assignment_id)
try: try:
assignment.run(request.user.profile, request.user) assignment.run(request.user.openslidesuser, request.user)
messages.success(request, _('You have set your candidature successfully.') ) messages.success(request, _('You have set your candidature successfully.') )
except NameError, e: except NameError, e:
messages.error(request, 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])) return redirect(reverse('assignment_view', args=[assignment_id]))
@ -182,28 +184,33 @@ def run(request, assignment_id):
def delrun(request, assignment_id): def delrun(request, assignment_id):
assignment = Assignment.objects.get(pk=assignment_id) assignment = Assignment.objects.get(pk=assignment_id)
try: try:
assignment.delrun(request.user.profile, request.user) if assignment.status == 'sea' or user.has_perm("assignment.can_manage_assignment"):
messages.success(request, _("You have withdrawn your candidature successfully.") ) assignment.delrun(request.user.openslidesuser)
except NameError, e: else:
messages.error(request, _('The candidate list is already closed.'))
except Exception, e:
messages.error(request, e) messages.error(request, e)
else:
messages.success(request, _("You have withdrawn your candidature successfully.") )
return redirect(reverse('assignment_view', args=[assignment_id])) return redirect(reverse('assignment_view', args=[assignment_id]))
@permission_required('assignment.can_manage_assignment') @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) assignment = Assignment.objects.get(pk=assignment_id)
profile = Profile.objects.get(pk=profile_id) person = get_person(user_id)
if request.method == 'POST': if request.method == 'POST':
try: try:
assignment.delrun(profile, request.user) assignment.delrun(person)
messages.success(request, _("Candidate <b>%s</b> was withdrawn successfully.") % (profile)) except Exception, e:
except NameError, e:
messages.error(request, e) messages.error(request, e)
else:
messages.success(request, _("Candidate <b>%s</b> was withdrawn successfully.") % (person))
else: else:
gen_confirm_form(request, gen_confirm_form(request,
_("Do you really want to withdraw <b>%s</b> from the election?") \ _("Do you really want to withdraw <b>%s</b> from the election?") \
% profile, reverse('assignment_delother', args=[assignment_id, profile_id])) % person, reverse('assignment_delother', args=[assignment_id, user_id]))
return redirect(reverse('assignment_view', args=[assignment_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') @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) assignment = Assignment.objects.get(pk=assignment_id)
profile = Profile.objects.get(pk=profile_id) person = get_person(user_id)
assignment.set_elected(profile, elected) assignment.set_elected(person, elected)
if request.is_ajax(): if request.is_ajax():
if elected: 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') text = _('not elected')
else: else:
link = reverse('assignment_user_elected', args=[assignment.id, profile.id]) link = reverse('assignment_user_elected', args=[assignment.id, person.person_id])
text = _('elected') text = _('elected')
return ajax_request({'elected': elected, return ajax_request({'elected': elected, 'link': link, 'text': text})
'link': link,
'text': text})
return redirect(reverse('assignment_view', args=[assignment_id])) return redirect(reverse('assignment_view', args=[assignment_id]))
@ -369,7 +374,7 @@ class AssignmentPDF(PDFView):
cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset" \ cell2a.append(Paragraph("<font name='Ubuntu-Bold'>%s:</font><seqreset" \
" id='counter'>" % _("Candidates"), stylesheet['Heading4'])) " id='counter'>" % _("Candidates"), stylesheet['Heading4']))
cell2b = [] cell2b = []
for candidate in assignment.profile.all(): for candidate in assignment.candidates:
cell2b.append(Paragraph("<seq id='counter'/>.&nbsp; %s" % candidate, cell2b.append(Paragraph("<seq id='counter'/>.&nbsp; %s" % candidate,
stylesheet['Signaturefield'])) stylesheet['Signaturefield']))
if assignment.status == "sea": if assignment.status == "sea":
@ -407,7 +412,7 @@ class AssignmentPDF(PDFView):
# Add result rows # Add result rows
elected_candidates = assignment.elected.all() elected_candidates = list(assignment.elected)
for candidate, poll_list in vote_results.iteritems(): for candidate, poll_list in vote_results.iteritems():
row = [] row = []
@ -548,8 +553,8 @@ class AssignmentPollPDF(PDFView):
candidate = option.candidate candidate = option.candidate
cell.append(Paragraph(candidate.user.get_full_name(), cell.append(Paragraph(candidate.user.get_full_name(),
stylesheet['Ballot_option_name'])) stylesheet['Ballot_option_name']))
if candidate.group: if candidate.name_surfix:
cell.append(Paragraph("(%s)" % candidate.group, cell.append(Paragraph("(%s)" % candidate.name_surfix,
stylesheet['Ballot_option_group'])) stylesheet['Ballot_option_group']))
else: else:
cell.append(Paragraph("&nbsp;", cell.append(Paragraph("&nbsp;",

View File

@ -67,16 +67,16 @@ class GeneralConfig(FormView):
anonymous = Group.objects.get(name='Anonymous') anonymous = Group.objects.get(name='Anonymous')
except Group.DoesNotExist: except Group.DoesNotExist:
default_perms = [u'can_see_agenda', u'can_see_projector', default_perms = [u'can_see_agenda', u'can_see_projector',
u'can_see_application'] u'can_see_application', 'can_see_assignment']
anonymous = Group() anonymous = Group()
anonymous.name = 'Anonymous' anonymous.name = 'Anonymous'
anonymous.save() anonymous.save()
anonymous.permissions = Permission.objects.filter( anonymous.permissions = Permission.objects.filter(
codename__in=default_perms) codename__in=default_perms)
anonymous.save() anonymous.save()
messages.success(self.request, messages.success(self.request,
_('Anonymous access enabled. Please modify the "Anonymous" ' \ _('Anonymous access enabled. Please modify the "Anonymous" ' \
'group to fit your required permissions.')) 'group to fit your required permissions.'))
else: else:
config['system_enable_anonymous'] = False config['system_enable_anonymous'] = False

View File

@ -21,7 +21,6 @@ def _fs2unicode(s):
SITE_ROOT = os.path.realpath(os.path.dirname(__file__)) SITE_ROOT = os.path.realpath(os.path.dirname(__file__))
AUTH_PROFILE_MODULE = 'participant.Profile'
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend', AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',
'openslides.utils.auth.AnonymousAuth',) 'openslides.utils.auth.AnonymousAuth',)

View File

@ -15,6 +15,9 @@ import string
from django.contrib.auth.models import User from django.contrib.auth.models import User
from openslides.utils.person import get_person
from openslides.participant.models import OpenSlidesUser
def gen_password(): def gen_password():
""" """

View File

@ -15,67 +15,71 @@ from django.contrib.auth.forms import AdminPasswordChangeForm
from django.contrib.auth.models import User, Group, Permission from django.contrib.auth.models import User, Group, Permission
from django.utils.translation import ugettext_lazy as _, ugettext_noop 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 = [ USER_APPLICATION_IMPORT_OPTIONS = [
('REASSIGN', _('Keep applications, try to reassign submitter')), ('REASSIGN', _('Keep applications, try to reassign submitter')),
('INREVIEW', _('Keep applications, set status to "needs review"')), ('INREVIEW', _('Keep applications, set status to "needs review"')),
('DISCARD' , _('Discard applications')) ('DISCARD', _('Discard applications'))
] ]
class UserNewForm(forms.ModelForm, CssClassMixin): class UserNewForm(forms.ModelForm, CssClassMixin):
first_name = forms.CharField(label=_("First name")) first_name = forms.CharField(label=_("First name"))
last_name = forms.CharField(label=_("Last name")) last_name = forms.CharField(label=_("Last name"))
groups = forms.ModelMultipleChoiceField(queryset=Group.objects.all(), groups = forms.ModelMultipleChoiceField(
label=_("User groups"), required=False) queryset=Group.objects.all(), label=_("User groups"), required=False)
is_active = forms.BooleanField(label=_("Active"), required=False, is_active = forms.BooleanField(
initial=True) label=_("Active"), required=False, initial=True)
class Meta: class Meta:
model = User model = User
exclude = ('username', 'password', 'is_staff', 'is_superuser', exclude = ('username', 'password', 'is_staff', 'is_superuser',
'last_login', 'date_joined', 'user_permissions') 'last_login', 'date_joined', 'user_permissions')
class UserEditForm(forms.ModelForm, CssClassMixin): class UserEditForm(forms.ModelForm, CssClassMixin):
first_name = forms.CharField(label=_("First name")) first_name = forms.CharField(label=_("First name"))
last_name = forms.CharField(label=_("Last name")) last_name = forms.CharField(label=_("Last name"))
groups = forms.ModelMultipleChoiceField(queryset=Group.objects.all(), groups = forms.ModelMultipleChoiceField(
label=_("User groups"), required=False) queryset=Group.objects.all(), label=_("User groups"), required=False)
is_active = forms.BooleanField(label=_("Active"), required=False) is_active = forms.BooleanField(label=_("Active"), required=False)
class Meta: class Meta:
model = User model = User
exclude = ('password', 'is_staff', 'is_superuser', 'last_login', exclude = ('password', 'is_staff', 'is_superuser', 'last_login',
'date_joined', 'user_permissions') 'date_joined', 'user_permissions')
class UsernameForm(forms.ModelForm, CssClassMixin): class UsernameForm(forms.ModelForm, CssClassMixin):
class Meta: class Meta:
model = User model = User
exclude = ('first_name', 'last_name', 'email', 'is_active', exclude = ('first_name', 'last_name', 'email', 'is_active',
'is_superuser', 'groups', 'password', 'is_staff', 'last_login', 'is_superuser', 'groups', 'password', 'is_staff',
'date_joined', 'user_permissions') 'last_login', 'date_joined', 'user_permissions')
class ProfileForm(forms.ModelForm, CssClassMixin): class OpenSlidesUserForm(forms.ModelForm, CssClassMixin):
class Meta: class Meta:
model = Profile model = OpenSlidesUser
class GroupForm(forms.ModelForm, CssClassMixin): 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( permissions = LocalizedModelMultipleChoiceField(
queryset=Permission.objects.all(), label=_("Persmissions")) queryset=Permission.objects.all(), label=_("Persmissions"))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(GroupForm, self).__init__(*args, **kwargs) super(GroupForm, self).__init__(*args, **kwargs)
if kwargs.get('instance', None) is not None: if kwargs.get('instance', None) is not None:
self.fields['permissions'].initial = \ self.fields['permissions'].initial = (
[p.pk for p in kwargs['instance'].permissions.all()] [p.pk for p in kwargs['instance'].permissions.all()])
class Meta: class Meta:
model = Group model = Group
@ -87,14 +91,13 @@ class UsersettingsForm(forms.ModelForm, CssClassMixin):
model = User model = User
fields = ('username', 'first_name', 'last_name', 'email') fields = ('username', 'first_name', 'last_name', 'email')
class UserImportForm(forms.Form, CssClassMixin): class UserImportForm(forms.Form, CssClassMixin):
csvfile = forms.FileField(widget=forms.FileInput(attrs={'size':'50'}), csvfile = forms.FileField(widget=forms.FileInput(attrs={'size': '50'}),
label=_("CSV File")) label=_("CSV File"))
application_handling = forms.ChoiceField( application_handling = forms.ChoiceField(
required=True, required=True, choices=USER_APPLICATION_IMPORT_OPTIONS,
choices=USER_APPLICATION_IMPORT_OPTIONS, label=_("For existing applications"))
label=_("For existing applications"),
)
class ConfigForm(forms.Form, CssClassMixin): class ConfigForm(forms.Form, CssClassMixin):
@ -102,11 +105,9 @@ class ConfigForm(forms.Form, CssClassMixin):
widget=forms.TextInput(), widget=forms.TextInput(),
required=False, required=False,
label=_("System URL"), 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( participant_pdf_welcometext = forms.CharField(
widget=forms.Textarea(), widget=forms.Textarea(),
required=False, required=False,
label=_("Welcome text"), label=_("Welcome text"),
help_text=_("Printed in PDF of first time passwords only."), help_text=_("Printed in PDF of first time passwords only."))
)

View File

@ -10,19 +10,20 @@
:license: GNU GPL, see LICENSE for more details. :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 import models
from django.db.models import Q from django.db.models import Q, signals
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _, ugettext_noop 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.config.signals import default_config_value
from openslides.participant.api import gen_password
class OpenSlidesUser(models.Model, PersonMixin):
person_prefix = 'openslides_user'
class Profile(models.Model):
GENDER_CHOICES = ( GENDER_CHOICES = (
('male', _('Male')), ('male', _('Male')),
('female', _('Female')), ('female', _('Female')),
@ -35,29 +36,36 @@ class Profile(models.Model):
) )
user = models.OneToOneField(User, unique=True, editable=False) user = models.OneToOneField(User, unique=True, editable=False)
group = models.CharField(max_length=100, null=True, blank=True, name_surfix = models.CharField(
verbose_name = _("Group"), help_text=_('Shown behind the name.')) max_length=100, null=True, blank=True, verbose_name=_("Name Surfix"),
gender = models.CharField(max_length=50, choices=GENDER_CHOICES, blank=True, help_text=_('Shown behind the name.'))
verbose_name = _("Gender"), 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.')) help_text=_('Only for filter the userlist.'))
type = models.CharField(max_length=100, choices=TYPE_CHOICE, blank=True, comment = models.TextField(
verbose_name = _("Typ"), help_text=_('Only for filter the userlist.')) null=True, blank=True, verbose_name=_('Comment'),
committee = models.CharField(max_length=100, null=True, blank=True, help_text=_('Only for notes.'))
verbose_name = _("Committee"), firstpassword = models.CharField(
help_text=_('Only for filter the userlist.')) max_length=100, null=True, blank=True,
comment = models.TextField(null=True, blank=True, verbose_name=_("First Password"))
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, password=None):
def reset_password(self):
""" """
Reset the password for the user to his default-password. 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() self.user.save()
def has_perm(self, perm):
return self.user.has_perm(perm)
@models.permalink @models.permalink
def get_absolute_url(self, link='edit'): def get_absolute_url(self, link='edit'):
@ -74,25 +82,75 @@ class Profile(models.Model):
return ('user_delete', [str(self.user.id)]) return ('user_delete', [str(self.user.id)])
def __unicode__(self): def __unicode__(self):
if self.group: if self.name_surfix:
return "%s (%s)" % (self.user.get_full_name(), self.group) return "%s (%s)" % (self.user.get_full_name(), self.name_surfix)
return "%s" % self.user.get_full_name() return "%s" % self.user.get_full_name()
class Meta: class Meta:
# Rename permissions
permissions = ( permissions = (
('can_see_participant', ugettext_noop("Can see participant")), ('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") @receiver(default_config_value, dispatch_uid="participant_default_config")
def default_config(sender, key, **kwargs): def default_config(sender, key, **kwargs):
""" """
Default values for the participant app. Default values for the participant app.
""" """
# TODO: Rename config-vars
return { return {
'participant_pdf_system_url': 'http://example.com:8000', 'participant_pdf_system_url': 'http://example.com:8000',
'participant_pdf_welcometext': _('Welcome to OpenSlides!'), 'participant_pdf_welcometext': _('Welcome to OpenSlides!'),
'admin_password': None, 'admin_password': None,
}.get(key) }.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)

View File

@ -77,11 +77,11 @@
<tr class="{% cycle '' 'odd' %}"> <tr class="{% cycle '' 'odd' %}">
<td>{{ user.first_name }}</td> <td>{{ user.first_name }}</td>
<td>{{ user.last_name }}</td> <td>{{ user.last_name }}</td>
<td>{{ user.profile.group }}</td> <td>{{ user.openslidesuser.name_surfix }}</td>
<td>{{ user.profile.get_type_display }}</td> <td>{{ user.openslidesuser.get_type_display }}</td>
<td>{{ user.profile.committee }}</td> <td>{{ user.openslidesuser.committee }}</td>
{% if perms.participant.can_manage_participant %} {% if perms.participant.can_manage_participant %}
<td>{{ user.profile.comment|first_line }}</td> <td>{{ user.openslidesuser.comment|first_line }}</td>
<td>{% if user.last_login > user.date_joined %} <td>{% if user.last_login > user.date_joined %}
{{ user.last_login }} {{ user.last_login }}
{% endif %}</td> {% endif %}</td>

View File

@ -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)

View File

@ -18,13 +18,14 @@ from urllib import urlencode
try: try:
from urlparse import parse_qs 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 cgi import parse_qs
from reportlab.lib import colors from reportlab.lib import colors
from reportlab.lib.units import cm from reportlab.lib.units import cm
from reportlab.platypus import (SimpleDocTemplate, PageBreak, Paragraph, from reportlab.platypus import (
LongTable, Spacer, Table, TableStyle) SimpleDocTemplate, PageBreak, Paragraph, LongTable, Spacer, Table,
TableStyle)
from django.db import transaction from django.db import transaction
from django.contrib import messages 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 import csv_ext
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet
from openslides.utils.template import Tab from openslides.utils.template import Tab
from openslides.utils.utils import (template, permission_required, from openslides.utils.utils import (
gen_confirm_form, ajax_request, decodedict, encodedict, template, permission_required, gen_confirm_form, ajax_request, decodedict,
delete_default_permissions, html_strong) encodedict, delete_default_permissions, html_strong)
from openslides.utils.views import FormView, PDFView from openslides.utils.views import FormView, PDFView
from openslides.config.models import config 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.api import gen_username, gen_password
from openslides.participant.forms import (UserNewForm, UserEditForm, from openslides.participant.forms import (
ProfileForm, UsersettingsForm, UserImportForm, GroupForm, UserNewForm, UserEditForm, OpenSlidesUserForm, UsersettingsForm,
AdminPasswordChangeForm, ConfigForm) UserImportForm, GroupForm, AdminPasswordChangeForm, ConfigForm)
@permission_required('participant.can_see_participant') @permission_required('participant.can_see_participant')
@ -78,53 +79,60 @@ def get_overview(request):
query = User.objects query = User.objects
if 'gender' in sortfilter: 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: 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: 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: if 'committee' in sortfilter:
query = query. \ query = query.filter(
filter(profile__committee__iexact=sortfilter['committee'][0]) openslidesuser__committee__iexact=sortfilter['committee'][0])
if 'status' in sortfilter: if 'status' in sortfilter:
query = query.filter(is_active=sortfilter['status'][0]) query = query.filter(is_active=sortfilter['status'][0])
if 'sort' in sortfilter: if 'sort' in sortfilter:
if sortfilter['sort'][0] in ['first_name', 'last_name', 'last_login']: if sortfilter['sort'][0] in ['first_name', 'last_name', 'last_login']:
query = query.order_by(sortfilter['sort'][0]) query = query.order_by(sortfilter['sort'][0])
elif sortfilter['sort'][0] in ['group', 'type', 'committee', 'comment']: elif (sortfilter['sort'][0] in
query = query.order_by('profile__%s' % sortfilter['sort'][0]) ['name_surfix', 'type', 'committee', 'comment']):
query = query.order_by(
'openslidesuser__%s' % sortfilter['sort'][0])
else: else:
query = query.order_by('last_name') query = query.order_by('last_name')
if 'reverse' in sortfilter: if 'reverse' in sortfilter:
query = query.reverse() query = query.reverse()
# list of filtered users (with profile) # list of filtered users
userlist = query.all() userlist = query.all()
users = [] users = []
for user in userlist: for user in userlist:
try: try:
user.get_profile() user.openslidesuser
users.append(user) except OpenSlidesUser.DoesNotExist:
except Profile.DoesNotExist:
pass pass
# list of all existing users (with profile) else:
users.append(user)
# list of all existing users
allusers = [] allusers = []
for user in User.objects.all(): for user in User.objects.all():
try: try:
user.get_profile() user.openslidesuser
allusers.append(user) except OpenSlidesUser.DoesNotExist:
except Profile.DoesNotExist:
pass pass
else:
allusers.append(user)
# quotient of selected users and all users # quotient of selected users and all users
if len(allusers) > 0: if len(allusers) > 0:
percent = float(len(users)) * 100 / float(len(allusers)) percent = float(len(users)) * 100 / float(len(allusers))
else: else:
percent = 0 percent = 0
# list of all existing groups # list of all existing groups
groups = [p['group'] for p in Profile.objects.values('group') \ groups = [p['name_surfix'] for p in OpenSlidesUser.objects.values('name_surfix')
.exclude(group='').distinct()] .exclude(name_surfix='').distinct()]
# list of all existing committees # 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()] .exclude(committee='').distinct()]
return { return {
'users': users, 'users': users,
@ -142,7 +150,7 @@ def get_overview(request):
@template('participant/edit.html') @template('participant/edit.html')
def edit(request, user_id=None): 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: if user_id is not None:
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
@ -151,26 +159,31 @@ def edit(request, user_id=None):
if request.method == 'POST': if request.method == 'POST':
if user_id is None: if user_id is None:
userform = UserNewForm(request.POST, prefix="user") user_form = UserNewForm(request.POST, prefix="user")
profileform = ProfileForm(request.POST, prefix="profile") openslides_user_form = OpenSlidesUserForm(request.POST, prefix="openslidesuser")
else: else:
userform = UserEditForm(request.POST, instance=user, prefix="user") user_form = UserEditForm(request.POST, instance=user, prefix="user")
profileform = ProfileForm(request.POST, instance=user.profile, openslides_user_form = OpenSlidesUserForm(request.POST, instance=user.openslidesuser,
prefix="profile") prefix="openslidesuser")
if userform.is_valid() and profileform.is_valid(): if user_form.is_valid() and openslides_user_form.is_valid():
user = userform.save() user = user_form.save(commit=False)
if user_id is None: 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.username = gen_username(user.first_name, user.last_name)
user.save() user.save()
profile = profileform.save(commit=False) openslides_user = user.openslidesuser
profile.user = user 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 user_id is None:
if not profile.firstpassword: if not openslides_user.firstpassword:
profile.firstpassword = gen_password() openslides_user.firstpassword = gen_password()
profile.user.set_password(profile.firstpassword) openslides_user.user.set_password(openslides_user.firstpassword)
profile.user.save() # TODO: Try not to save the user object
profile.save() openslides_user.user.save()
openslides_user.save()
if user_id is None: if user_id is None:
messages.success(request, messages.success(request,
_('New participant was successfully created.')) _('New participant was successfully created.'))
@ -185,15 +198,15 @@ def edit(request, user_id=None):
messages.error(request, _('Please check the form for errors.')) messages.error(request, _('Please check the form for errors.'))
else: else:
if user_id is None: if user_id is None:
userform = UserNewForm(prefix="user") user_form = UserNewForm(prefix="user")
profileform = ProfileForm(prefix="profile") openslides_user_form = OpenSlidesUserForm(prefix="openslidesuser")
else: else:
userform = UserEditForm(instance=user, prefix="user") user_form = UserEditForm(instance=user, prefix="user")
profileform = ProfileForm(instance=user.profile, prefix="profile") openslides_user_form = OpenSlidesUserForm(instance=user.openslidesuser, prefix="openslidesuser")
# TODO: rename template vars
return { return {
'userform': userform, 'userform': user_form,
'profileform': profileform, 'profileform': openslides_user_form,
'edituser': user, 'edituser': user,
} }
@ -269,6 +282,7 @@ def group_edit(request, group_id=None):
try: try:
group = Group.objects.get(id=group_id) group = Group.objects.get(id=group_id)
except Group.DoesNotExist: except Group.DoesNotExist:
# TODO: return a 404 Object
raise NameError("There is no group %d" % group_id) raise NameError("There is no group %d" % group_id)
else: else:
group = None group = None
@ -277,25 +291,38 @@ def group_edit(request, group_id=None):
if request.method == 'POST': if request.method == 'POST':
form = GroupForm(request.POST, instance=group) form = GroupForm(request.POST, instance=group)
if form.is_valid(): if form.is_valid():
# TODO: This can be done inside the form
group_name = form.cleaned_data['name'].lower() 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: try:
anonymous_group = Group.objects.get(name='Anonymous') anonymous_group = Group.objects.get(name='Anonymous')
except Group.DoesNotExist: except Group.DoesNotExist:
anonymous_group = None anonymous_group = None
# special handling for anonymous auth # special handling for anonymous auth
# TODO: This code should be a form validator.
if group is None and group_name.strip().lower() == 'anonymous': if group is None and group_name.strip().lower() == 'anonymous':
# don't allow to create this group # don't allow to create this group
messages.error(request, messages.error(request,
_('Group name "%s" is reserved for internal use.') _('Group name "%s" is reserved for internal use.')
% group_name) % group_name)
return { return {
'form' : form, 'form': form,
'group': group 'group': group
} }
group = form.save() 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 \ if anonymous_group is not None and \
anonymous_group.id == group.id: anonymous_group.id == group.id:
# prevent name changes - # prevent name changes -
@ -315,7 +342,12 @@ def group_edit(request, group_id=None):
else: else:
messages.error(request, _('Please check the form for errors.')) messages.error(request, _('Please check the form for errors.'))
else: 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 { return {
'form': form, 'form': form,
'group': group, 'group': group,
@ -346,7 +378,7 @@ def user_settings(request):
Edit own user account. Edit own user account.
""" """
if request.method == 'POST': 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(): if form_user.is_valid():
form_user.save() form_user.save()
messages.success(request, _('User settings successfully saved.')) messages.success(request, _('User settings successfully saved.'))
@ -606,7 +638,7 @@ class ParticipantsListPDF(PDFView):
document_title = ugettext_lazy('List of Participants') document_title = ugettext_lazy('List of Participants')
def append_to_pdf(self, story): def append_to_pdf(self, story):
data= [['#', _('Last Name'), _('First Name'), _('Group'), _('Type'), data = [['#', _('Last Name'), _('First Name'), _('Group'), _('Type'),
_('Committee')]] _('Committee')]]
sort = 'last_name' sort = 'last_name'
counter = 0 counter = 0
@ -614,27 +646,27 @@ class ParticipantsListPDF(PDFView):
try: try:
counter += 1 counter += 1
user.get_profile() user.get_profile()
data.append([counter, data.append([
counter,
Paragraph(user.last_name, stylesheet['Tablecell']), Paragraph(user.last_name, stylesheet['Tablecell']),
Paragraph(user.first_name, stylesheet['Tablecell']), Paragraph(user.first_name, stylesheet['Tablecell']),
Paragraph(user.profile.group, stylesheet['Tablecell']), Paragraph(user.profile.group, stylesheet['Tablecell']),
Paragraph(user.profile.get_type_display(), Paragraph(user.profile.get_type_display(),
stylesheet['Tablecell']), stylesheet['Tablecell']),
Paragraph(user.profile.committee, stylesheet['Tablecell']), Paragraph(user.profile.committee, stylesheet['Tablecell'])
]) ])
except Profile.DoesNotExist: except Profile.DoesNotExist:
counter -= 1 counter -= 1
pass pass
t = LongTable(data, t = LongTable(data,
style=[ style=[
('VALIGN',(0,0),(-1,-1), 'TOP'), ('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LINEABOVE',(0,0),(-1,0),2,colors.black), ('LINEABOVE', (0, 0), (-1, 0), 2, colors.black),
('LINEABOVE',(0,1),(-1,1),1,colors.black), ('LINEABOVE', (0, 1), (-1, 1), 1, colors.black),
('LINEBELOW',(0,-1),(-1,-1),2,colors.black), ('LINEBELOW', (0, -1), (-1, -1), 2, colors.black),
('ROWBACKGROUNDS', (0, 1), (-1, -1), ('ROWBACKGROUNDS', (0, 1), (-1, -1),
(colors.white, (.9, .9, .9))), (colors.white, (.9, .9, .9)))])
]) t._argW[0] = 0.75 * cm
t._argW[0]=0.75*cm
story.append(t) story.append(t)
@ -654,48 +686,48 @@ class ParticipantsPasswordsPDF(PDFView):
pdf_document.build(story) pdf_document.build(story)
def append_to_pdf(self, story): def append_to_pdf(self, story):
data= [] data = []
participant_pdf_system_url = config["participant_pdf_system_url"] participant_pdf_system_url = config["participant_pdf_system_url"]
participant_pdf_welcometext = config["participant_pdf_welcometext"] participant_pdf_welcometext = config["participant_pdf_welcometext"]
for user in User.objects.all().order_by('last_name'): for user in User.objects.all().order_by('last_name'):
try: try:
user.get_profile() user.get_profile()
cell = [] cell = []
cell.append(Spacer(0,0.8*cm)) cell.append(Spacer(0, 0.8 * cm))
cell.append(Paragraph(_("Account for OpenSlides"), cell.append(Paragraph(_("Account for OpenSlides"),
stylesheet['Ballot_title'])) stylesheet['Ballot_title']))
cell.append(Paragraph(_("for %s") % (user.profile), cell.append(Paragraph(_("for %s") % (user.profile),
stylesheet['Ballot_subtitle'])) stylesheet['Ballot_subtitle']))
cell.append(Spacer(0,0.5*cm)) cell.append(Spacer(0, 0.5 * cm))
cell.append(Paragraph(_("User: %s") % (user.username), cell.append(Paragraph(_("User: %s") % (user.username),
stylesheet['Monotype'])) stylesheet['Monotype']))
cell.append(Paragraph(_("Password: %s") cell.append(Paragraph(_("Password: %s")
% (user.profile.firstpassword), stylesheet['Monotype'])) % (user.profile.firstpassword), stylesheet['Monotype']))
cell.append(Spacer(0,0.5*cm)) cell.append(Spacer(0, 0.5 * cm))
cell.append(Paragraph(_("URL: %s") cell.append(Paragraph(_("URL: %s")
% (participant_pdf_system_url), % (participant_pdf_system_url),
stylesheet['Ballot_option'])) stylesheet['Ballot_option']))
cell.append(Spacer(0,0.5*cm)) cell.append(Spacer(0, 0.5 * cm))
cell2 = [] cell2 = []
cell2.append(Spacer(0,0.8*cm)) cell2.append(Spacer(0, 0.8 * cm))
if participant_pdf_welcometext is not None: if participant_pdf_welcometext is not None:
cell2.append(Paragraph( cell2.append(Paragraph(
participant_pdf_welcometext.replace('\r\n','<br/>'), participant_pdf_welcometext.replace('\r\n', '<br/>'),
stylesheet['Ballot_subtitle'])) stylesheet['Ballot_subtitle']))
data.append([cell,cell2]) data.append([cell, cell2])
except Profile.DoesNotExist: except OpenSlidesUser.DoesNotExist:
pass pass
# add empty table line if no participants available # add empty table line if no participants available
if data == []: if data == []:
data.append(['','']) data.append(['', ''])
# build table # build table
t=Table(data, 10.5*cm, 7.42*cm) t = Table(data, 10.5 * cm, 7.42 * cm)
t.setStyle(TableStyle([ t.setStyle(TableStyle([
('LINEBELOW', (0,0), (-1,0), 0.25, colors.grey), ('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),
('LINEBELOW', (0,1), (-1,-1), 0.25, colors.grey), ('LINEBELOW', (0, 1), (-1, -1), 0.25, colors.grey),
('VALIGN', (0,0), (-1,-1), 'TOP'), ('VALIGN', (0, 0), (-1, -1), 'TOP'),
])) ]))
story.append(t) story.append(t)

View File

@ -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'

View File

@ -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]

View File

@ -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)

View File

@ -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

View File

@ -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'])