diff --git a/.travis.yml b/.travis.yml index 9a87327fd..aa3f468b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,3 +8,4 @@ install: script: - coverage run ./manage.py test tests && coverage report -m - pep8 --max-line-length=150 --exclude="urls.py," --statistics openslides + - pep8 --max-line-length=150 --statistics tests diff --git a/openslides/config/views.py b/openslides/config/views.py index 997527384..09f0a6968 100644 --- a/openslides/config/views.py +++ b/openslides/config/views.py @@ -22,9 +22,6 @@ from openslides.utils.views import FormView, TemplateView from .forms import GeneralConfigForm 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): """ @@ -62,7 +59,6 @@ class GeneralConfig(FormView): # system if form.cleaned_data['system_enable_anonymous']: config['system_enable_anonymous'] = True - get_or_create_anonymous_group() else: config['system_enable_anonymous'] = False diff --git a/openslides/core/__init__.py b/openslides/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openslides/core/signals.py b/openslides/core/signals.py new file mode 100644 index 000000000..9d253035a --- /dev/null +++ b/openslides/core/signals.py @@ -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() diff --git a/openslides/main.py b/openslides/main.py index 1baa2c62e..b7107317d 100644 --- a/openslides/main.py +++ b/openslides/main.py @@ -27,6 +27,7 @@ from django.core.management import execute_from_command_line from openslides import get_version from openslides.utils.tornado_webserver import run_tornado + CONFIG_TEMPLATE = """#!/usr/bin/env python # -*- coding: utf-8 -*- @@ -270,7 +271,6 @@ def run_syncdb(): # now initialize the database argv = ["", "syncdb", "--noinput"] execute_from_command_line(argv) - execute_from_command_line(["", "loaddata", "groups_de"]) def set_system_url(url): diff --git a/openslides/motion/__init__.py b/openslides/motion/__init__.py index ea23517a7..e296be3ce 100644 --- a/openslides/motion/__init__.py +++ b/openslides/motion/__init__.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- """ openslides.motion diff --git a/openslides/motion/fixtures/initial_data.json b/openslides/motion/fixtures/initial_data.json deleted file mode 100644 index 6dc7091c5..000000000 --- a/openslides/motion/fixtures/initial_data.json +++ /dev/null @@ -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 - } - } -] diff --git a/openslides/motion/forms.py b/openslides/motion/forms.py index 1ddac7b97..7b81d6524 100644 --- a/openslides/motion/forms.py +++ b/openslides/motion/forms.py @@ -96,6 +96,12 @@ class MotionCategoryMixin(forms.ModelForm): 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): """Form for the configuration tab of OpenSlides.""" motion_min_supporters = forms.IntegerField( diff --git a/openslides/motion/models.py b/openslides/motion/models.py index ac9a41854..7ff707a7a 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -18,7 +18,7 @@ from datetime import datetime import difflib 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.dispatch import receiver from django.utils import formats @@ -67,6 +67,12 @@ class Motion(SlideMixin, models.Model): unique=True) """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) """ForeignKey to one category of motions.""" @@ -172,6 +178,33 @@ class Motion(SlideMixin, models.Model): if link == 'delete': 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): """Get the title of the motion. @@ -348,12 +381,25 @@ class Motion(SlideMixin, models.Model): else: 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): """Set the state to the default state. If the motion is new, it chooses the default workflow from config.""" if self.state: self.state = self.state.workflow.first_state 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): """Return the slide dict.""" @@ -535,7 +581,13 @@ class MotionSupporter(models.Model): class Category(models.Model): 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): return self.name @@ -702,6 +754,12 @@ class State(models.Model): dont_set_new_version_active = models.BooleanField(default=False) """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): """Returns the name of the state.""" return self.name @@ -734,7 +792,7 @@ class Workflow(models.Model): name = models.CharField(max_length=255) """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.""" def __unicode__(self): @@ -751,5 +809,5 @@ class Workflow(models.Model): def check_first_state(self): """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)) diff --git a/openslides/motion/signals.py b/openslides/motion/signals.py index 3b1cb38d3..ff662ddf1 100644 --- a/openslides/motion/signals.py +++ b/openslides/motion/signals.py @@ -11,9 +11,12 @@ """ 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.core.signals import post_database_setup + +from .models import Workflow, State @receiver(default_config_value, dispatch_uid="motion_default_config") @@ -28,3 +31,77 @@ def default_config(sender, key, **kwargs): 'motion_pdf_preamble': '', 'motion_allow_disable_versioning': False, '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() diff --git a/openslides/motion/templates/motion/motion_detail.html b/openslides/motion/templates/motion/motion_detail.html index 3548beba8..d392cb9bf 100644 --- a/openslides/motion/templates/motion/motion_detail.html +++ b/openslides/motion/templates/motion/motion_detail.html @@ -8,11 +8,11 @@ {% block content %}

- {{ motion.title }} + {{ motion.title }} {{ motion.category }}
- {% if motion.number != None %} - {% trans "Motion" %} {{ motion.number }}, + {% if motion.identifier != None %} + {% trans "Motion" %} {{ motion.identifier }}, {% else %} [{% trans "no number" %}], {% endif %} @@ -56,14 +56,12 @@
- - - {% if motion.version.version_number < motion.last_version.version_number %} - {% trans "This is not the newest version." %} {% trans "Go to version" %} {{ motion.last_version.version_number }}. - {% else %} - {% trans "This is not the authorized version." %} {% trans "Go to version" %} {{ motion.active_version.version_number }}. - {% endif %} - + + {% if motion.version.version_number < motion.last_version.version_number %} + {% trans "This is not the newest version." %} {% trans "Go to version" %} {{ motion.last_version.version_number }}. + {% else %} + {% trans "This is not the authorized version." %} {% trans "Go to version" %} {{ motion.active_version.version_number }}. + {% endif %}

{% trans "Motion text" %}:

@@ -283,5 +281,4 @@
- {% endblock %} diff --git a/openslides/motion/urls.py b/openslides/motion/urls.py index 946aab95b..f230a8dd9 100644 --- a/openslides/motion/urls.py +++ b/openslides/motion/urls.py @@ -40,6 +40,11 @@ urlpatterns = patterns('openslides.motion.views', name='motion_delete', ), + url(r'^(?P\d+)/set_identifier/', + 'set_identifier', + name='motion_set_identifier', + ), + url(r'^(?P\d+)/version/(?P\d+)/$', 'motion_detail', name='motion_version_detail', diff --git a/openslides/motion/views.py b/openslides/motion/views.py index 27df47795..77e946112 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -35,10 +35,15 @@ from openslides.agenda.models import Item from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll, MotionVersion, State, WorkflowError, Category) from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin, - MotionDisableVersioningMixin, ConfigForm, MotionCategoryMixin) + MotionDisableVersioningMixin, ConfigForm, MotionCategoryMixin, + MotionIdentifierMixin) 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): """View, to list all motions.""" permission_required = 'motion.can_see_motion' @@ -98,6 +103,16 @@ class MotionMixin(object): except KeyError: 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): """Save the submitter an the supporter so the motion.""" 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 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'): form_classes.append(MotionSubmitterMixin) form_classes.append(MotionCategoryMixin) @@ -262,7 +283,32 @@ class VersionDiffView(GetVersionMixin, DetailView): 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. 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: self.object.reset_state() 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? messages.error(request, e) else: diff --git a/openslides/motion/workflow.py b/openslides/motion/workflow.py deleted file mode 100644 index 42d1e1814..000000000 --- a/openslides/motion/workflow.py +++ /dev/null @@ -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() diff --git a/openslides/participant/__init__.py b/openslides/participant/__init__.py index f04c201d4..12b9658f8 100644 --- a/openslides/participant/__init__.py +++ b/openslides/participant/__init__.py @@ -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 +import openslides.participant.signals + + NAME = ugettext_noop('Participant') diff --git a/openslides/participant/api.py b/openslides/participant/api.py index f7b5a80a2..7ffd2fe93 100644 --- a/openslides/participant/api.py +++ b/openslides/participant/api.py @@ -97,21 +97,8 @@ def import_users(csv_file): return (count_success, error_messages) -def get_or_create_registered_group(): - registered, created = Group.objects.get_or_create( - name__iexact='Registered', defaults={'name': 'Registered'}) - if created: - registered.permissions = Permission.objects.filter( - 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 +def get_registered_group(): + """ + Returns the Group 'Registered'. Upper and lower case is possible. + """ + return Group.objects.get(name__iexact='Registered') diff --git a/openslides/participant/fixtures/groups_de.json b/openslides/participant/fixtures/groups_de.json deleted file mode 100644 index b662beff6..000000000 --- a/openslides/participant/fixtures/groups_de.json +++ /dev/null @@ -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" - ] - ] - } - } -] diff --git a/openslides/participant/forms.py b/openslides/participant/forms.py index 854abe91e..2da72bafe 100644 --- a/openslides/participant/forms.py +++ b/openslides/participant/forms.py @@ -19,7 +19,7 @@ from openslides.utils.forms import ( CssClassMixin, LocalizedModelMultipleChoiceField) 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): @@ -30,7 +30,7 @@ class UserCreateForm(forms.ModelForm, CssClassMixin): def __init__(self, *args, **kwargs): if kwargs.get('instance', None) is None: initial = kwargs.setdefault('initial', {}) - registered = get_or_create_registered_group() + registered = get_registered_group() initial['groups'] = [registered.pk] super(UserCreateForm, self).__init__(*args, **kwargs) diff --git a/openslides/participant/models.py b/openslides/participant/models.py index 6cad7364f..754096386 100644 --- a/openslides/participant/models.py +++ b/openslides/participant/models.py @@ -257,9 +257,9 @@ def djangogroup_post_save(sender, instance, signal, *args, **kwargs): @receiver(signals.post_save, sender=User) def user_post_save(sender, instance, *args, **kwargs): - from openslides.participant.api import get_or_create_registered_group if not kwargs['created']: 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.save() diff --git a/openslides/participant/signals.py b/openslides/participant/signals.py new file mode 100644 index 000000000..7cace7b4f --- /dev/null +++ b/openslides/participant/signals.py @@ -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) diff --git a/openslides/utils/management/commands/syncdb.py b/openslides/utils/management/commands/syncdb.py new file mode 100644 index 000000000..2835fb220 --- /dev/null +++ b/openslides/utils/management/commands/syncdb.py @@ -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 diff --git a/openslides/utils/test.py b/openslides/utils/test.py new file mode 100644 index 000000000..9d7ca3df9 --- /dev/null +++ b/openslides/utils/test.py @@ -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 diff --git a/tests/agenda/tests.py b/tests/agenda/tests.py index d28c5f993..d1a76cf6f 100644 --- a/tests/agenda/tests.py +++ b/tests/agenda/tests.py @@ -10,10 +10,10 @@ :license: GNU GPL, see LICENSE for more details. """ -from django.test import TestCase from django.test.client import Client from django.db.models.query import EmptyQuerySet +from openslides.utils.test import TestCase from openslides.projector.api import get_active_slide from openslides.participant.models import User from openslides.agenda.models import Item @@ -95,7 +95,6 @@ class ItemTest(TestCase): self.assertEqual(self.item5.print_related_type(), 'Releateditem') - class ViewTest(TestCase): def setUp(self): self.item1 = Item.objects.create(title='item1') diff --git a/tests/motion/test_models.py b/tests/motion/test_models.py index 9c647f296..8e7ddcd4d 100644 --- a/tests/motion/test_models.py +++ b/tests/motion/test_models.py @@ -8,8 +8,7 @@ :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.config.models import config from openslides.motion.models import Motion, Workflow, State @@ -76,7 +75,7 @@ class ModelTest(TestCase): self.assertEqual(motion.title, 'v2') 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') with self.assertRaises(ValueError): @@ -113,18 +112,18 @@ class ModelTest(TestCase): self.motion.state = State.objects.get(pk=6) 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): self.motion.support(self.test_user) with self.assertRaises(WorkflowError): self.motion.unsupport(self.test_user) 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) workflow_1.first_state = state_1 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) workflow_2.first_state = state_2 workflow_2.save() diff --git a/tests/test_init.py b/tests/test_init.py index 411860d79..aa665c62f 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -7,9 +7,9 @@ :license: GNU GPL, see LICENSE for more details. """ -from django.test import TestCase - from openslides import get_version, get_git_commit_id +from openslides.utils.test import TestCase + class InitTest(TestCase): def test_get_version(self): diff --git a/tests/test_participant.py b/tests/test_participant.py index 4227dc66b..608ee686f 100644 --- a/tests/test_participant.py +++ b/tests/test_participant.py @@ -10,8 +10,7 @@ :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.participant.api import gen_username, gen_password from openslides.participant.models import User, Group