Merge remote branch 'upstream/master' into motiontemplate

Conflicts:
	openslides/motion/models.py
	openslides/motion/views.py
This commit is contained in:
Emanuel Schuetze 2013-03-14 21:36:02 +01:00
commit 247f6c9431
26 changed files with 385 additions and 629 deletions

View File

@ -8,3 +8,4 @@ install:
script: script:
- coverage run ./manage.py test tests && coverage report -m - coverage run ./manage.py test tests && coverage report -m
- pep8 --max-line-length=150 --exclude="urls.py," --statistics openslides - pep8 --max-line-length=150 --exclude="urls.py," --statistics openslides
- pep8 --max-line-length=150 --statistics tests

View File

@ -22,9 +22,6 @@ from openslides.utils.views import FormView, TemplateView
from .forms import GeneralConfigForm from .forms import GeneralConfigForm
from .models import config from .models import config
# TODO: Do not import the participant module in config
from openslides.participant.api import get_or_create_anonymous_group
class GeneralConfig(FormView): class GeneralConfig(FormView):
""" """
@ -62,7 +59,6 @@ class GeneralConfig(FormView):
# system # system
if form.cleaned_data['system_enable_anonymous']: if form.cleaned_data['system_enable_anonymous']:
config['system_enable_anonymous'] = True config['system_enable_anonymous'] = True
get_or_create_anonymous_group()
else: else:
config['system_enable_anonymous'] = False config['system_enable_anonymous'] = False

View File

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.core.signals
~~~~~~~~~~~~~~~~~~~~~~~~~
Core Signals.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.dispatch import Signal
post_database_setup = Signal()

View File

@ -27,6 +27,7 @@ from django.core.management import execute_from_command_line
from openslides import get_version from openslides import get_version
from openslides.utils.tornado_webserver import run_tornado from openslides.utils.tornado_webserver import run_tornado
CONFIG_TEMPLATE = """#!/usr/bin/env python CONFIG_TEMPLATE = """#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
@ -270,7 +271,6 @@ def run_syncdb():
# now initialize the database # now initialize the database
argv = ["", "syncdb", "--noinput"] argv = ["", "syncdb", "--noinput"]
execute_from_command_line(argv) execute_from_command_line(argv)
execute_from_command_line(["", "loaddata", "groups_de"])
def set_system_url(url): def set_system_url(url):

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
openslides.motion openslides.motion

View File

@ -1,280 +0,0 @@
[
{
"pk":1,
"model":"motion.workflow",
"fields":{
"name":"Simple Workflow",
"first_state":1
}
},
{
"pk":2,
"model":"motion.workflow",
"fields":{
"name":"Complex Workflow",
"first_state":5
}
},
{
"pk":1,
"model":"motion.state",
"fields":{
"name":"submitted",
"workflow":1,
"dont_set_new_version_active":false,
"allow_submitter_edit":true,
"next_states":[
2,
3,
4
],
"allow_support":true,
"action_word":"",
"icon":"",
"versioning":false,
"allow_create_poll":true
}
},
{
"pk":2,
"model":"motion.state",
"fields":{
"name":"accepted",
"workflow":1,
"dont_set_new_version_active":false,
"allow_submitter_edit":false,
"next_states":[
],
"allow_support":false,
"action_word":"accept",
"icon":"",
"versioning":false,
"allow_create_poll":false
}
},
{
"pk":3,
"model":"motion.state",
"fields":{
"name":"rejected",
"workflow":1,
"dont_set_new_version_active":false,
"allow_submitter_edit":false,
"next_states":[
],
"allow_support":false,
"action_word":"reject",
"icon":"",
"versioning":false,
"allow_create_poll":false
}
},
{
"pk":4,
"model":"motion.state",
"fields":{
"name":"not decided",
"workflow":1,
"dont_set_new_version_active":false,
"allow_submitter_edit":false,
"next_states":[
],
"allow_support":false,
"action_word":"do not decide",
"icon":"",
"versioning":false,
"allow_create_poll":false
}
},
{
"pk":5,
"model":"motion.state",
"fields":{
"name":"published",
"workflow":2,
"dont_set_new_version_active":false,
"allow_submitter_edit":true,
"next_states":[
6,
9,
14
],
"allow_support":true,
"action_word":"",
"icon":"",
"versioning":false,
"allow_create_poll":false
}
},
{
"pk":6,
"model":"motion.state",
"fields":{
"name":"permitted",
"workflow":2,
"dont_set_new_version_active":true,
"allow_submitter_edit":true,
"next_states":[
7,
8,
9,
10,
11,
12,
13
],
"allow_support":false,
"action_word":"permit",
"icon":"",
"versioning":true,
"allow_create_poll":true
}
},
{
"pk":7,
"model":"motion.state",
"fields":{
"name":"accepted",
"workflow":2,
"dont_set_new_version_active":false,
"allow_submitter_edit":false,
"next_states":[
],
"allow_support":false,
"action_word":"accept",
"icon":"",
"versioning":true,
"allow_create_poll":false
}
},
{
"pk":8,
"model":"motion.state",
"fields":{
"name":"rejected",
"workflow":2,
"dont_set_new_version_active":false,
"allow_submitter_edit":false,
"next_states":[
],
"allow_support":false,
"action_word":"reject",
"icon":"",
"versioning":true,
"allow_create_poll":false
}
},
{
"pk":9,
"model":"motion.state",
"fields":{
"name":"withdrawed",
"workflow":2,
"dont_set_new_version_active":false,
"allow_submitter_edit":false,
"next_states":[
],
"allow_support":false,
"action_word":"withdraw",
"icon":"",
"versioning":true,
"allow_create_poll":false
}
},
{
"pk":10,
"model":"motion.state",
"fields":{
"name":"adjourned",
"workflow":2,
"dont_set_new_version_active":false,
"allow_submitter_edit":false,
"next_states":[
],
"allow_support":false,
"action_word":"adjourn",
"icon":"",
"versioning":true,
"allow_create_poll":false
}
},
{
"pk":11,
"model":"motion.state",
"fields":{
"name":"not concerned",
"workflow":2,
"dont_set_new_version_active":false,
"allow_submitter_edit":false,
"next_states":[
],
"allow_support":false,
"action_word":"",
"icon":"",
"versioning":true,
"allow_create_poll":false
}
},
{
"pk":12,
"model":"motion.state",
"fields":{
"name":"commited a bill",
"workflow":2,
"dont_set_new_version_active":false,
"allow_submitter_edit":false,
"next_states":[
],
"allow_support":false,
"action_word":"commit a bill",
"icon":"",
"versioning":true,
"allow_create_poll":false
}
},
{
"pk":13,
"model":"motion.state",
"fields":{
"name":"needs review",
"workflow":2,
"dont_set_new_version_active":false,
"allow_submitter_edit":false,
"next_states":[
],
"allow_support":false,
"action_word":"",
"icon":"",
"versioning":true,
"allow_create_poll":false
}
},
{
"pk":14,
"model":"motion.state",
"fields":{
"name":"rejected (not authorized)",
"workflow":2,
"dont_set_new_version_active":false,
"allow_submitter_edit":false,
"next_states":[
],
"allow_support":false,
"action_word":"reject (not authorized)",
"icon":"",
"versioning":true,
"allow_create_poll":false
}
}
]

View File

@ -96,6 +96,12 @@ class MotionCategoryMixin(forms.ModelForm):
category = forms.ModelChoiceField(queryset=Category.objects.all(), required=False) category = forms.ModelChoiceField(queryset=Category.objects.all(), required=False)
class MotionIdentifierMixin(forms.ModelForm):
"""Mixin to let the user choose the identifier for the motion."""
identifier = forms.CharField(required=False)
class ConfigForm(CssClassMixin, forms.Form): class ConfigForm(CssClassMixin, forms.Form):
"""Form for the configuration tab of OpenSlides.""" """Form for the configuration tab of OpenSlides."""
motion_min_supporters = forms.IntegerField( motion_min_supporters = forms.IntegerField(

View File

@ -18,7 +18,7 @@ from datetime import datetime
import difflib import difflib
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import models from django.db import models, IntegrityError
from django.db.models import Max from django.db.models import Max
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import formats from django.utils import formats
@ -67,6 +67,12 @@ class Motion(SlideMixin, models.Model):
unique=True) unique=True)
"""A string as human readable identifier for the motion.""" """A string as human readable identifier for the motion."""
identifier_number = models.IntegerField(null=True)
"""Counts the number of the motion in one category.
Needed to find the next free motion-identifier.
"""
category = models.ForeignKey('Category', null=True, blank=True) category = models.ForeignKey('Category', null=True, blank=True)
"""ForeignKey to one category of motions.""" """ForeignKey to one category of motions."""
@ -172,6 +178,33 @@ class Motion(SlideMixin, models.Model):
if link == 'delete': if link == 'delete':
return reverse('motion_delete', args=[str(self.id)]) return reverse('motion_delete', args=[str(self.id)])
def set_identifier(self):
if config['motion_identifier'] == 'manually':
# Do not set an identifier.
return
elif config['motion_identifier'] == 'per_category':
motions = Motion.objects.filter(category=self.category)
else:
motions = Motion.objects.all()
number = motions.aggregate(Max('identifier_number'))['identifier_number__max'] or 0
if self.category is None or not self.category.prefix:
prefix = ''
else:
prefix = self.category.prefix + ' '
while True:
number += 1
self.identifier = '%s%d' % (prefix, number)
try:
self.save()
except IntegrityError:
continue
else:
self.number = number
self.save()
break
def get_title(self): def get_title(self):
"""Get the title of the motion. """Get the title of the motion.
@ -348,12 +381,25 @@ class Motion(SlideMixin, models.Model):
else: else:
raise WorkflowError('You can not create a poll in state %s.' % self.state.name) raise WorkflowError('You can not create a poll in state %s.' % self.state.name)
def set_state(self, state):
"""Set the state of the motion.
State can be the id of a state object or a state object.
"""
if type(state) is int:
state = State.objects.get(pk=state)
if not state.dont_set_identifier:
self.set_identifier()
self.state = state
def reset_state(self): def reset_state(self):
"""Set the state to the default state. If the motion is new, it chooses the default workflow from config.""" """Set the state to the default state. If the motion is new, it chooses the default workflow from config."""
if self.state: if self.state:
self.state = self.state.workflow.first_state self.state = self.state.workflow.first_state
else: else:
self.state = Workflow.objects.get(pk=config['motion_workflow']).first_state self.state = (Workflow.objects.get(pk=config['motion_workflow']).first_state or
Workflow.objects.get(pk=config['motion_workflow']).state_set.all()[0])
def slide(self): def slide(self):
"""Return the slide dict.""" """Return the slide dict."""
@ -535,7 +581,13 @@ class MotionSupporter(models.Model):
class Category(models.Model): class Category(models.Model):
name = models.CharField(max_length=255, verbose_name=ugettext_lazy("Category name")) name = models.CharField(max_length=255, verbose_name=ugettext_lazy("Category name"))
prefix = models.CharField(max_length=32, verbose_name=ugettext_lazy("Prefix")) """Name of the category."""
prefix = models.CharField(blank=True, max_length=32, verbose_name=ugettext_lazy("Prefix"))
"""Prefix of the category.
Used to build the identifier of a motion.
"""
def __unicode__(self): def __unicode__(self):
return self.name return self.name
@ -702,6 +754,12 @@ class State(models.Model):
dont_set_new_version_active = models.BooleanField(default=False) dont_set_new_version_active = models.BooleanField(default=False)
"""If true, new versions are not automaticly set active.""" """If true, new versions are not automaticly set active."""
dont_set_identifier = models.BooleanField(default=False)
"""Decides if the motion gets an identifier.
If true, the motion does not get an identifier if the state change to
this one, else it does."""
def __unicode__(self): def __unicode__(self):
"""Returns the name of the state.""" """Returns the name of the state."""
return self.name return self.name
@ -734,7 +792,7 @@ class Workflow(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
"""A string representing the workflow.""" """A string representing the workflow."""
first_state = models.OneToOneField(State, related_name='+') first_state = models.OneToOneField(State, related_name='+', null=True)
"""A one-to-one relation to a state, the starting point for the workflow.""" """A one-to-one relation to a state, the starting point for the workflow."""
def __unicode__(self): def __unicode__(self):
@ -751,5 +809,5 @@ class Workflow(models.Model):
def check_first_state(self): def check_first_state(self):
"""Checks whether the first_state itself belongs to the workflow.""" """Checks whether the first_state itself belongs to the workflow."""
if not self.first_state.workflow == self: if self.first_state and not self.first_state.workflow == self:
raise WorkflowError('%s can not be first state of %s because it does not belong to it.' % (self.first_state, self)) raise WorkflowError('%s can not be first state of %s because it does not belong to it.' % (self.first_state, self))

View File

@ -11,9 +11,12 @@
""" """
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _, ugettext_noop
from openslides.config.signals import default_config_value from openslides.config.signals import default_config_value
from openslides.core.signals import post_database_setup
from .models import Workflow, State
@receiver(default_config_value, dispatch_uid="motion_default_config") @receiver(default_config_value, dispatch_uid="motion_default_config")
@ -28,3 +31,77 @@ def default_config(sender, key, **kwargs):
'motion_pdf_preamble': '', 'motion_pdf_preamble': '',
'motion_allow_disable_versioning': False, 'motion_allow_disable_versioning': False,
'motion_workflow': 1}.get(key) 'motion_workflow': 1}.get(key)
@receiver(post_database_setup, dispatch_uid='motion_create_builtin_workflows')
def create_builtin_workflows(sender, **kwargs):
"""
Creates a simple and a complex workflow.
"""
workflow_1 = Workflow.objects.create(name=ugettext_noop('Simple Workflow'))
state_1_1 = State.objects.create(name=ugettext_noop('submitted'),
workflow=workflow_1,
allow_create_poll=True,
allow_support=True,
allow_submitter_edit=True)
state_1_2 = State.objects.create(name=ugettext_noop('accepted'),
workflow=workflow_1,
action_word=ugettext_noop('Accept'))
state_1_3 = State.objects.create(name=ugettext_noop('rejected'),
workflow=workflow_1,
action_word=ugettext_noop('Reject'))
state_1_4 = State.objects.create(name=ugettext_noop('not decided'),
workflow=workflow_1,
action_word=ugettext_noop('Do not decide'))
state_1_1.next_states.add(state_1_2, state_1_3, state_1_4)
workflow_1.first_state = state_1_1
workflow_1.save()
workflow_2 = Workflow.objects.create(name=ugettext_noop('Complex Workflow'))
state_2_1 = State.objects.create(name=ugettext_noop('published'),
workflow=workflow_2,
allow_support=True,
allow_submitter_edit=True,
dont_set_identifier=True)
state_2_2 = State.objects.create(name=ugettext_noop('permitted'),
workflow=workflow_2,
action_word=ugettext_noop('Permit'),
allow_create_poll=True,
allow_submitter_edit=True,
versioning=True,
dont_set_new_version_active=True)
state_2_3 = State.objects.create(name=ugettext_noop('accepted'),
workflow=workflow_2,
action_word=ugettext_noop('Accept'),
versioning=True)
state_2_4 = State.objects.create(name=ugettext_noop('rejected'),
workflow=workflow_2,
action_word=ugettext_noop('Reject'),
versioning=True)
state_2_5 = State.objects.create(name=ugettext_noop('withdrawed'),
workflow=workflow_2,
action_word=ugettext_noop('Withdraw'),
versioning=True)
state_2_6 = State.objects.create(name=ugettext_noop('adjourned'),
workflow=workflow_2,
action_word=ugettext_noop('Adjourn'),
versioning=True)
state_2_7 = State.objects.create(name=ugettext_noop('not concerned'),
workflow=workflow_2,
action_word=ugettext_noop('Do not concern'),
versioning=True)
state_2_8 = State.objects.create(name=ugettext_noop('commited a bill'),
workflow=workflow_2,
action_word=ugettext_noop('Commit a bill'),
versioning=True)
state_2_9 = State.objects.create(name=ugettext_noop('needs review'),
workflow=workflow_2,
versioning=True)
state_2_10 = State.objects.create(name=ugettext_noop('rejected (not authorized)'),
workflow=workflow_2,
action_word=ugettext_noop('reject (not authorized)'),
versioning=True)
state_2_1.next_states.add(state_2_2, state_2_5, state_2_10)
state_2_2.next_states.add(state_2_3, state_2_4, state_2_5, state_2_6, state_2_7, state_2_8, state_2_9)
workflow_2.first_state = state_2_1
workflow_2.save()

View File

@ -8,11 +8,11 @@
{% block content %} {% block content %}
<h1> <h1>
{{ motion.title }} {{ motion.title }} {{ motion.category }}
<br> <br>
<small> <small>
{% if motion.number != None %} {% if motion.identifier != None %}
{% trans "Motion" %} {{ motion.number }}, {% trans "Motion" %} {{ motion.identifier }},
{% else %} {% else %}
<i>[{% trans "no number" %}]</i>, <i>[{% trans "no number" %}]</i>,
{% endif %} {% endif %}
@ -56,14 +56,12 @@
<div class="row-fluid"> <div class="row-fluid">
<div class="span8"> <div class="span8">
<span class="label label-warning"><i class="icon-warning-sign icon-white"></i>
<span class="label label-warning"><i class="icon-warning-sign icon-white"></i> {% if motion.version.version_number < motion.last_version.version_number %}
{% if motion.version.version_number < motion.last_version.version_number %} {% trans "This is not the newest version." %}</span> <a href="{% model_url motion.last_version %}">{% trans "Go to version" %} {{ motion.last_version.version_number }}.</a>
{% trans "This is not the newest version." %}</span> <a href="{% model_url motion.last_version %}">{% trans "Go to version" %} {{ motion.last_version.version_number }}.</a> {% else %}
{% else %} {% trans "This is not the authorized version." %}</span> <a href="{% model_url motion.active_version %}">{% trans "Go to version" %} {{ motion.active_version.version_number }}.</a>
{% trans "This is not the authorized version." %}</span> <a href="{% model_url motion.active_version %}">{% trans "Go to version" %} {{ motion.active_version.version_number }}.</a> {% endif %}
{% endif %}
<!-- Text --> <!-- Text -->
<h4>{% trans "Motion text" %}:</h4> <h4>{% trans "Motion text" %}:</h4>
@ -283,5 +281,4 @@
</div> <!--/span--> </div> <!--/span-->
</div> <!--/row--> </div> <!--/row-->
{% endblock %} {% endblock %}

View File

@ -40,6 +40,11 @@ urlpatterns = patterns('openslides.motion.views',
name='motion_delete', name='motion_delete',
), ),
url(r'^(?P<pk>\d+)/set_identifier/',
'set_identifier',
name='motion_set_identifier',
),
url(r'^(?P<pk>\d+)/version/(?P<version_number>\d+)/$', url(r'^(?P<pk>\d+)/version/(?P<version_number>\d+)/$',
'motion_detail', 'motion_detail',
name='motion_version_detail', name='motion_version_detail',

View File

@ -35,10 +35,15 @@ from openslides.agenda.models import Item
from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll, from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll,
MotionVersion, State, WorkflowError, Category) MotionVersion, State, WorkflowError, Category)
from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin, from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin,
MotionDisableVersioningMixin, ConfigForm, MotionCategoryMixin) MotionDisableVersioningMixin, ConfigForm, MotionCategoryMixin,
MotionIdentifierMixin)
from .pdf import motions_to_pdf, motion_to_pdf from .pdf import motions_to_pdf, motion_to_pdf
# TODO: into the config-tab
config['motion_identifier'] = ('manually', 'per_category', 'serially_numbered')[2]
class MotionListView(ListView): class MotionListView(ListView):
"""View, to list all motions.""" """View, to list all motions."""
permission_required = 'motion.can_see_motion' permission_required = 'motion.can_see_motion'
@ -98,6 +103,16 @@ class MotionMixin(object):
except KeyError: except KeyError:
pass pass
try:
self.object.category = form.cleaned_data['category']
except KeyError:
pass
try:
self.object.identifier = form.cleaned_data['identifier']
except KeyError:
pass
def post_save(self, form): def post_save(self, form):
"""Save the submitter an the supporter so the motion.""" """Save the submitter an the supporter so the motion."""
super(MotionMixin, self).post_save(form) super(MotionMixin, self).post_save(form)
@ -120,8 +135,14 @@ class MotionMixin(object):
will be mixed in dependence of some config values. See motion.forms will be mixed in dependence of some config values. See motion.forms
for more information on the mixins. for more information on the mixins.
""" """
form_classes = []
if (self.request.user.has_perm('motion.can_manage_motion') and
config['motion_identifier'] == 'manually'):
form_classes.append(MotionIdentifierMixin)
form_classes.append(BaseMotionForm)
form_classes = [BaseMotionForm]
if self.request.user.has_perm('motion.can_manage_motion'): if self.request.user.has_perm('motion.can_manage_motion'):
form_classes.append(MotionSubmitterMixin) form_classes.append(MotionSubmitterMixin)
form_classes.append(MotionCategoryMixin) form_classes.append(MotionCategoryMixin)
@ -262,7 +283,32 @@ class VersionDiffView(GetVersionMixin, DetailView):
version_diff = VersionDiffView.as_view() version_diff = VersionDiffView.as_view()
class SupportView(SingleObjectMixin, RedirectView):
class SetIdentifierView(SingleObjectMixin, RedirectView):
"""Set the identifier of the motion.
See motion.set_identifier for more informations
"""
permission_required = 'motion.can_manage_motion'
model = Motion
url_name = 'motion_detail'
def get(self, request, *args, **kwargs):
"""Set self.object to a motion."""
self.object = self.get_object()
return super(SetIdentifierView, self).get(request, *args, **kwargs)
def pre_redirect(self, request, *args, **kwargs):
"""Set the identifier."""
self.object.set_identifier()
def get_url_name_args(self):
return [self.object.id]
set_identifier = SetIdentifierView.as_view()
class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
"""View to support or unsupport a motion. """View to support or unsupport a motion.
If self.support is True, the view will append a request.user to the supporter list. If self.support is True, the view will append a request.user to the supporter list.
@ -427,7 +473,7 @@ class MotionSetStateView(SingleObjectMixin, RedirectView):
if self.reset: if self.reset:
self.object.reset_state() self.object.reset_state()
else: else:
self.object.state = State.objects.get(pk=kwargs['state']) self.object.set_state(int(kwargs['state']))
except WorkflowError, e: # TODO: Is a WorkflowError still possible here? except WorkflowError, e: # TODO: Is a WorkflowError still possible here?
messages.error(request, e) messages.error(request, e)
else: else:

View File

@ -1,55 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.motion.workflow
~~~~~~~~~~~~~~~~~~~~~~~~~~
This file is only for development. It will be moved out of
the openslides module before the next release.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.utils.translation import ugettext_noop
from .models import Workflow, State
def _init_builtin_workflows():
"""
Saves a simple and a complex workflow into the database.
This function is only called manually and lives here only for development.
"""
workflow_1 = Workflow(name=ugettext_noop('Simple Workflow'), id=1)
state_1_1 = State.objects.create(name=ugettext_noop('submitted'), workflow=workflow_1,
allow_create_poll=True, allow_support=True, allow_submitter_edit=True)
state_1_2 = State.objects.create(name=ugettext_noop('accepted'), workflow=workflow_1, action_word=ugettext_noop('accept'))
state_1_3 = State.objects.create(name=ugettext_noop('rejected'), workflow=workflow_1, action_word=ugettext_noop('reject'))
state_1_4 = State.objects.create(name=ugettext_noop('not decided'), workflow=workflow_1, action_word=ugettext_noop('do not decide'))
state_1_1.next_states.add(state_1_2, state_1_3, state_1_4)
state_1_1.save() # Is this neccessary?
workflow_1.first_state = state_1_1
workflow_1.save()
workflow_2 = Workflow(name=ugettext_noop('Complex Workflow'), id=2)
state_2_1 = State.objects.create(name=ugettext_noop('published'), workflow=workflow_2, allow_support=True, allow_submitter_edit=True)
state_2_2 = State.objects.create(name=ugettext_noop('permitted'), workflow=workflow_2, action_word=ugettext_noop('permit'),
allow_create_poll=True, allow_submitter_edit=True, versioning=True, dont_set_new_version_active=True)
state_2_3 = State.objects.create(name=ugettext_noop('accepted'), workflow=workflow_2, action_word=ugettext_noop('accept'), versioning=True)
state_2_4 = State.objects.create(name=ugettext_noop('rejected'), workflow=workflow_2, action_word=ugettext_noop('reject'), versioning=True)
state_2_5 = State.objects.create(name=ugettext_noop('withdrawed'), workflow=workflow_2,
action_word=ugettext_noop('withdraw'), versioning=True)
state_2_6 = State.objects.create(name=ugettext_noop('adjourned'), workflow=workflow_2, action_word=ugettext_noop('adjourn'), versioning=True)
state_2_7 = State.objects.create(name=ugettext_noop('not concerned'), workflow=workflow_2, versioning=True)
state_2_8 = State.objects.create(name=ugettext_noop('commited a bill'), workflow=workflow_2,
action_word=ugettext_noop('commit a bill'), versioning=True)
state_2_9 = State.objects.create(name=ugettext_noop('needs review'), workflow=workflow_2, versioning=True)
state_2_10 = State.objects.create(name=ugettext_noop('rejected (not authorized)'), workflow=workflow_2,
action_word=ugettext_noop('reject (not authorized)'), versioning=True)
state_2_1.next_states.add(state_2_2, state_2_5, state_2_10)
state_2_2.next_states.add(state_2_3, state_2_4, state_2_5, state_2_6, state_2_7, state_2_8, state_2_9)
state_2_1.save() # Is this neccessary?
state_2_2.save() # Is this neccessary?
workflow_2.first_state = state_2_1
workflow_2.save()

View File

@ -1,3 +1,18 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.participant
~~~~~~~~~~~~~~~~~~~~~~
The OpenSlides participant app.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.utils.translation import ugettext_noop from django.utils.translation import ugettext_noop
import openslides.participant.signals
NAME = ugettext_noop('Participant') NAME = ugettext_noop('Participant')

View File

@ -97,21 +97,8 @@ def import_users(csv_file):
return (count_success, error_messages) return (count_success, error_messages)
def get_or_create_registered_group(): def get_registered_group():
registered, created = Group.objects.get_or_create( """
name__iexact='Registered', defaults={'name': 'Registered'}) Returns the Group 'Registered'. Upper and lower case is possible.
if created: """
registered.permissions = Permission.objects.filter( return Group.objects.get(name__iexact='Registered')
codename__in=DEFAULT_PERMS)
registered.save()
return registered
def get_or_create_anonymous_group():
anonymous, created = Group.objects.get_or_create(
name__iexact='Anonymous', defaults={'name': 'Anonymous'})
if created:
anonymous.permissions = Permission.objects.filter(
codename__in=DEFAULT_PERMS)
anonymous.save()
return anonymous

View File

@ -1,233 +0,0 @@
[
{
"pk": 1,
"model": "auth.group",
"fields": {
"name": "Beobachter/in",
"permissions": [
[
"can_see_agenda",
"agenda",
"item"
],
[
"can_create_motion",
"motion",
"motion"
],
[
"can_see_motion",
"motion",
"motion"
],
[
"can_nominate_other",
"assignment",
"assignment"
],
[
"can_nominate_self",
"assignment",
"assignment"
],
[
"can_see_assignment",
"assignment",
"assignment"
],
[
"can_see_participant",
"participant",
"user"
],
[
"can_see_projector",
"projector",
"projectorslide"
],
[
"can_see_dashboard",
"projector",
"projectorslide"
]
]
}
},
{
"pk": 2,
"model": "auth.group",
"fields": {
"name": "Delegierte/r",
"permissions": [
[
"can_see_agenda",
"agenda",
"item"
],
[
"can_create_motion",
"motion",
"motion"
],
[
"can_see_motion",
"motion",
"motion"
],
[
"can_support_motion",
"motion",
"motion"
],
[
"can_nominate_other",
"assignment",
"assignment"
],
[
"can_nominate_self",
"assignment",
"assignment"
],
[
"can_see_assignment",
"assignment",
"assignment"
],
[
"can_see_participant",
"participant",
"user"
],
[
"can_see_projector",
"projector",
"projectorslide"
],
[
"can_see_dashboard",
"projector",
"projectorslide"
]
]
}
},
{
"pk": 3,
"model": "auth.group",
"fields": {
"name": "Versammlungsleitung",
"permissions": [
[
"can_manage_agenda",
"agenda",
"item"
],
[
"can_see_agenda",
"agenda",
"item"
],
[
"can_create_motion",
"motion",
"motion"
],
[
"can_manage_motion",
"motion",
"motion"
],
[
"can_see_motion",
"motion",
"motion"
],
[
"can_manage_assignment",
"assignment",
"assignment"
],
[
"can_nominate_other",
"assignment",
"assignment"
],
[
"can_nominate_self",
"assignment",
"assignment"
],
[
"can_see_assignment",
"assignment",
"assignment"
],
[
"can_manage_config",
"config",
"configstore"
],
[
"can_manage_participant",
"participant",
"user"
],
[
"can_see_participant",
"participant",
"user"
],
[
"can_manage_projector",
"projector",
"projectorslide"
],
[
"can_see_projector",
"projector",
"projectorslide"
],
[
"can_see_dashboard",
"projector",
"projectorslide"
]
]
}
},
{
"pk": 4,
"model": "auth.group",
"fields": {
"name": "Teilnehmerverwaltung",
"permissions": [
[
"can_see_agenda",
"agenda",
"item"
],
[
"can_manage_participant",
"participant",
"user"
],
[
"can_see_participant",
"participant",
"user"
],
[
"can_see_projector",
"projector",
"projectorslide"
],
[
"can_see_dashboard",
"projector",
"projectorslide"
]
]
}
}
]

View File

@ -19,7 +19,7 @@ from openslides.utils.forms import (
CssClassMixin, LocalizedModelMultipleChoiceField) CssClassMixin, LocalizedModelMultipleChoiceField)
from openslides.participant.models import User, Group from openslides.participant.models import User, Group
from openslides.participant.api import get_or_create_registered_group from openslides.participant.api import get_registered_group
class UserCreateForm(forms.ModelForm, CssClassMixin): class UserCreateForm(forms.ModelForm, CssClassMixin):
@ -30,7 +30,7 @@ class UserCreateForm(forms.ModelForm, CssClassMixin):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if kwargs.get('instance', None) is None: if kwargs.get('instance', None) is None:
initial = kwargs.setdefault('initial', {}) initial = kwargs.setdefault('initial', {})
registered = get_or_create_registered_group() registered = get_registered_group()
initial['groups'] = [registered.pk] initial['groups'] = [registered.pk]
super(UserCreateForm, self).__init__(*args, **kwargs) super(UserCreateForm, self).__init__(*args, **kwargs)

View File

@ -257,9 +257,9 @@ def djangogroup_post_save(sender, instance, signal, *args, **kwargs):
@receiver(signals.post_save, sender=User) @receiver(signals.post_save, sender=User)
def user_post_save(sender, instance, *args, **kwargs): def user_post_save(sender, instance, *args, **kwargs):
from openslides.participant.api import get_or_create_registered_group
if not kwargs['created']: if not kwargs['created']:
return return
registered = get_or_create_registered_group() from openslides.participant.api import get_registered_group # TODO: Test, if global import is possible
registered = get_registered_group()
instance.groups.add(registered) instance.groups.add(registered)
instance.save() instance.save()

View File

@ -0,0 +1,70 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.participant.signals
~~~~~~~~~~~~~~~~~~~~~~~~~
Signals for the participant app.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.dispatch import receiver
from django.utils.translation import ugettext_noop
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission
from openslides.core.signals import post_database_setup
from .models import Group
@receiver(post_database_setup, dispatch_uid='participant_create_builtin_groups')
def create_builtin_groups(sender, **kwargs):
"""
Creates the builtin groups: Anonymous, Registered, Delegates and Staff.
"""
# Anonymous and Registered
ct_projector = ContentType.objects.get(app_label='projector', model='projectorslide')
perm_1 = Permission.objects.get(content_type=ct_projector, codename='can_see_projector')
perm_2 = Permission.objects.get(content_type=ct_projector, codename='can_see_dashboard')
ct_agenda = ContentType.objects.get(app_label='agenda', model='item')
perm_3 = Permission.objects.get(content_type=ct_agenda, codename='can_see_agenda')
ct_motion = ContentType.objects.get(app_label='motion', model='motion')
perm_4 = Permission.objects.get(content_type=ct_motion, codename='can_see_motion')
ct_assignment = ContentType.objects.get(app_label='assignment', model='assignment')
perm_5 = Permission.objects.get(content_type=ct_assignment, codename='can_see_assignment')
ct_participant = ContentType.objects.get(app_label='participant', model='user')
perm_6 = Permission.objects.get(content_type=ct_participant, codename='can_see_participant')
group_anonymous = Group.objects.create(name=ugettext_noop('Anonymous'))
group_anonymous.permissions.add(perm_1, perm_2, perm_3, perm_4, perm_5, perm_6)
group_registered = Group.objects.create(name=ugettext_noop('Registered'))
group_registered.permissions.add(perm_1, perm_2, perm_3, perm_4, perm_5, perm_6)
# Delegates
perm_7 = Permission.objects.get(content_type=ct_motion, codename='can_create_motion')
perm_8 = Permission.objects.get(content_type=ct_motion, codename='can_support_motion')
perm_9 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_other')
perm_10 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_self')
group_delegates = Group.objects.create(name=ugettext_noop('Delegates'))
group_delegates.permissions.add(perm_7, perm_8, perm_9, perm_10)
# Staff
perm_11 = Permission.objects.get(content_type=ct_agenda, codename='can_manage_agenda')
perm_12 = Permission.objects.get(content_type=ct_motion, codename='can_manage_motion')
perm_13 = Permission.objects.get(content_type=ct_assignment, codename='can_manage_assignment')
perm_14 = Permission.objects.get(content_type=ct_participant, codename='can_manage_participant')
perm_15 = Permission.objects.get(content_type=ct_projector, codename='can_manage_projector')
ct_config = ContentType.objects.get(app_label='config', model='configstore')
perm_16 = Permission.objects.get(content_type=ct_config, codename='can_manage_config')
group_staff = Group.objects.create(name=ugettext_noop('Staff'))
group_staff.permissions.add(perm_7, perm_9, perm_10, perm_11, perm_12, perm_13, perm_14, perm_15, perm_16)

View File

@ -0,0 +1,25 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.utils.management.commands.syncdb
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Overrides the Django syncdb command to setup the database.
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.core.management.commands.syncdb import Command as _Command
from openslides.core.signals import post_database_setup
class Command(_Command):
"""
Setup the database and sends the signal post_database_setup.
"""
def handle_noargs(self, *args, **kwargs):
return_value = super(Command, self).handle_noargs(*args, **kwargs)
post_database_setup.send(sender=self)
return return_value

27
openslides/utils/test.py Normal file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.utils.test
~~~~~~~~~~~~~~~~~~~~~
Unit test class.
:copyright: 2011-2013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.test import TestCase as _TestCase
from openslides.core.signals import post_database_setup
class TestCase(_TestCase):
"""
Overwrites Django's TestCase class to call the post_database_setup
signal after the preparation of every test.
"""
def _pre_setup(self, *args, **kwargs):
return_value = super(TestCase, self)._pre_setup(*args, **kwargs)
post_database_setup.send(sender=self)
return return_value

View File

@ -10,10 +10,10 @@
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.test import TestCase
from django.test.client import Client from django.test.client import Client
from django.db.models.query import EmptyQuerySet from django.db.models.query import EmptyQuerySet
from openslides.utils.test import TestCase
from openslides.projector.api import get_active_slide from openslides.projector.api import get_active_slide
from openslides.participant.models import User from openslides.participant.models import User
from openslides.agenda.models import Item from openslides.agenda.models import Item
@ -95,7 +95,6 @@ class ItemTest(TestCase):
self.assertEqual(self.item5.print_related_type(), 'Releateditem') self.assertEqual(self.item5.print_related_type(), 'Releateditem')
class ViewTest(TestCase): class ViewTest(TestCase):
def setUp(self): def setUp(self):
self.item1 = Item.objects.create(title='item1') self.item1 = Item.objects.create(title='item1')

View File

@ -8,8 +8,7 @@
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.test import TestCase from openslides.utils.test import TestCase
from openslides.participant.models import User from openslides.participant.models import User
from openslides.config.models import config from openslides.config.models import config
from openslides.motion.models import Motion, Workflow, State from openslides.motion.models import Motion, Workflow, State
@ -76,7 +75,7 @@ class ModelTest(TestCase):
self.assertEqual(motion.title, 'v2') self.assertEqual(motion.title, 'v2')
motion.version = None motion.version = None
motion.version = None # Test to set a version to None, which is already None motion.version = None # Test to set a version to None, which is already None
self.assertEqual(motion.title, 'v3') self.assertEqual(motion.title, 'v3')
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
@ -113,18 +112,18 @@ class ModelTest(TestCase):
self.motion.state = State.objects.get(pk=6) self.motion.state = State.objects.get(pk=6)
self.assertEqual(self.motion.state.name, 'permitted') self.assertEqual(self.motion.state.name, 'permitted')
self.assertEqual(self.motion.state.get_action_word(), 'permit') self.assertEqual(self.motion.state.get_action_word(), 'Permit')
with self.assertRaises(WorkflowError): with self.assertRaises(WorkflowError):
self.motion.support(self.test_user) self.motion.support(self.test_user)
with self.assertRaises(WorkflowError): with self.assertRaises(WorkflowError):
self.motion.unsupport(self.test_user) self.motion.unsupport(self.test_user)
def test_new_states_or_workflows(self): def test_new_states_or_workflows(self):
workflow_1 = Workflow(name='W1', id=1000) workflow_1 = Workflow.objects.create(name='W1')
state_1 = State.objects.create(name='S1', workflow=workflow_1) state_1 = State.objects.create(name='S1', workflow=workflow_1)
workflow_1.first_state = state_1 workflow_1.first_state = state_1
workflow_1.save() workflow_1.save()
workflow_2 = Workflow(name='W2', id=2000) workflow_2 = Workflow.objects.create(name='W2')
state_2 = State.objects.create(name='S2', workflow=workflow_2) state_2 = State.objects.create(name='S2', workflow=workflow_2)
workflow_2.first_state = state_2 workflow_2.first_state = state_2
workflow_2.save() workflow_2.save()

View File

@ -7,9 +7,9 @@
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.test import TestCase
from openslides import get_version, get_git_commit_id from openslides import get_version, get_git_commit_id
from openslides.utils.test import TestCase
class InitTest(TestCase): class InitTest(TestCase):
def test_get_version(self): def test_get_version(self):

View File

@ -10,8 +10,7 @@
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.test import TestCase from openslides.utils.test import TestCase
from openslides.utils.person import get_person, Persons from openslides.utils.person import get_person, Persons
from openslides.participant.api import gen_username, gen_password from openslides.participant.api import gen_username, gen_password
from openslides.participant.models import User, Group from openslides.participant.models import User, Group