Add workflows for motions
This commit is contained in:
parent
3f02a28002
commit
fb82a1787b
@ -26,9 +26,12 @@ LOGIN_REDIRECT_URL = '/'
|
|||||||
|
|
||||||
SESSION_COOKIE_NAME = 'OpenSlidesSessionID'
|
SESSION_COOKIE_NAME = 'OpenSlidesSessionID'
|
||||||
|
|
||||||
|
|
||||||
ugettext = lambda s: s
|
ugettext = lambda s: s
|
||||||
|
|
||||||
|
MOTION_WORKFLOW = (
|
||||||
|
('default', ugettext('default'), 'openslides.motion.workflow.default_workflow'),
|
||||||
|
)
|
||||||
|
|
||||||
LANGUAGES = (
|
LANGUAGES = (
|
||||||
('de', ugettext('German')),
|
('de', ugettext('German')),
|
||||||
('en', ugettext('English')),
|
('en', ugettext('English')),
|
||||||
|
@ -16,6 +16,7 @@ from django.utils.translation import ugettext as _
|
|||||||
from openslides.utils.forms import CssClassMixin
|
from openslides.utils.forms import CssClassMixin
|
||||||
from openslides.utils.person import PersonFormField, MultiplePersonFormField
|
from openslides.utils.person import PersonFormField, MultiplePersonFormField
|
||||||
from .models import Motion
|
from .models import Motion
|
||||||
|
from .workflow import motion_workflow_choices
|
||||||
|
|
||||||
|
|
||||||
class BaseMotionForm(forms.ModelForm, CssClassMixin):
|
class BaseMotionForm(forms.ModelForm, CssClassMixin):
|
||||||
@ -117,3 +118,9 @@ class ConfigForm(forms.Form, CssClassMixin):
|
|||||||
('NEVER_CREATE_NEW_VERSION', _('create never a new version')),
|
('NEVER_CREATE_NEW_VERSION', _('create never a new version')),
|
||||||
('ASK_USER', _('Let the user choose if he wants to create a new version')))
|
('ASK_USER', _('Let the user choose if he wants to create a new version')))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
motion_workflow = forms.ChoiceField(
|
||||||
|
widget=forms.Select(),
|
||||||
|
label=_("Workflow for the motions"),
|
||||||
|
required=True,
|
||||||
|
choices=motion_workflow_choices())
|
||||||
|
@ -30,6 +30,8 @@ from openslides.projector.api import register_slidemodel
|
|||||||
from openslides.projector.models import SlideMixin
|
from openslides.projector.models import SlideMixin
|
||||||
from openslides.agenda.models import Item
|
from openslides.agenda.models import Item
|
||||||
|
|
||||||
|
from .workflow import motion_workflow_choices, get_state, State
|
||||||
|
|
||||||
|
|
||||||
# TODO: Save submitter and supporter in the same table
|
# TODO: Save submitter and supporter in the same table
|
||||||
class MotionSubmitter(models.Model):
|
class MotionSubmitter(models.Model):
|
||||||
@ -58,8 +60,7 @@ class Motion(SlideMixin, models.Model):
|
|||||||
# is deactivated. Maybe it has to be renamed.
|
# is deactivated. Maybe it has to be renamed.
|
||||||
permitted_version = models.ForeignKey(
|
permitted_version = models.ForeignKey(
|
||||||
'MotionVersion', null=True, blank=True, related_name="permitted")
|
'MotionVersion', null=True, blank=True, related_name="permitted")
|
||||||
# TODO: Define status
|
state_id = models.CharField(max_length=3)
|
||||||
status = models.CharField(max_length=3)
|
|
||||||
# Log (Translatable)
|
# Log (Translatable)
|
||||||
identifier = models.CharField(max_length=255, null=True, blank=True,
|
identifier = models.CharField(max_length=255, null=True, blank=True,
|
||||||
unique=True)
|
unique=True)
|
||||||
@ -86,6 +87,9 @@ class Motion(SlideMixin, models.Model):
|
|||||||
"""
|
"""
|
||||||
Saves the motion. Create or update a motion_version object
|
Saves the motion. Create or update a motion_version object
|
||||||
"""
|
"""
|
||||||
|
if not self.state_id:
|
||||||
|
self.state = 'default'
|
||||||
|
|
||||||
super(Motion, self).save(*args, **kwargs)
|
super(Motion, self).save(*args, **kwargs)
|
||||||
for attr in ['title', 'text', 'reason']:
|
for attr in ['title', 'text', 'reason']:
|
||||||
if getattr(self, attr) != getattr(self.last_version, attr):
|
if getattr(self, attr) != getattr(self.last_version, attr):
|
||||||
@ -268,12 +272,33 @@ class Motion(SlideMixin, models.Model):
|
|||||||
#self.writelog(_("Supporter: -%s") % (person))
|
#self.writelog(_("Supporter: -%s") % (person))
|
||||||
|
|
||||||
def create_poll(self):
|
def create_poll(self):
|
||||||
|
"""
|
||||||
|
Create a new poll for this motion
|
||||||
|
"""
|
||||||
# TODO: auto increment the poll_number in the Database
|
# TODO: auto increment the poll_number in the Database
|
||||||
poll_number = self.polls.aggregate(Max('poll_number'))['poll_number__max'] or 0
|
poll_number = self.polls.aggregate(Max('poll_number'))['poll_number__max'] or 0
|
||||||
poll = MotionPoll.objects.create(motion=self, poll_number=poll_number + 1)
|
poll = MotionPoll.objects.create(motion=self, poll_number=poll_number + 1)
|
||||||
poll.set_options()
|
poll.set_options()
|
||||||
return poll
|
return poll
|
||||||
|
|
||||||
|
def get_state(self):
|
||||||
|
"""
|
||||||
|
Get the state of this motion. Return a State object.
|
||||||
|
"""
|
||||||
|
return get_state(self.state_id)
|
||||||
|
|
||||||
|
def set_state(self, state):
|
||||||
|
"""
|
||||||
|
Set the state of this motion.
|
||||||
|
|
||||||
|
state has to be a valid state id or State object.
|
||||||
|
"""
|
||||||
|
if type(state) is not State:
|
||||||
|
state = get_state(state)
|
||||||
|
self.state_id = state.id
|
||||||
|
|
||||||
|
state = property(get_state, set_state)
|
||||||
|
|
||||||
|
|
||||||
class MotionVersion(models.Model):
|
class MotionVersion(models.Model):
|
||||||
title = models.CharField(max_length=255, verbose_name=ugettext_lazy("Title"))
|
title = models.CharField(max_length=255, verbose_name=ugettext_lazy("Title"))
|
||||||
|
@ -25,4 +25,4 @@ def default_config(sender, key, **kwargs):
|
|||||||
'motion_pdf_title': _('Motions'),
|
'motion_pdf_title': _('Motions'),
|
||||||
'motion_pdf_preamble': '',
|
'motion_pdf_preamble': '',
|
||||||
'motion_create_new_version': 'ALLWASY_CREATE_NEW_VERSION',
|
'motion_create_new_version': 'ALLWASY_CREATE_NEW_VERSION',
|
||||||
}.get(key)
|
'motion_workflow': 'default'}.get(key)
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
<p>Reason: {{ motion.reason }}</p>
|
<p>Reason: {{ motion.reason }}</p>
|
||||||
<p>Submitter: {% for submitter in motion.submitter.all %}{{ submitter.person }} {% endfor %}</p>
|
<p>Submitter: {% for submitter in motion.submitter.all %}{{ submitter.person }} {% endfor %}</p>
|
||||||
<p>Supporter: {% for supporter in motion.supporter.all %}{{ supporter.person }} {% endfor %}</p>
|
<p>Supporter: {% for supporter in motion.supporter.all %}{{ supporter.person }} {% endfor %}</p>
|
||||||
|
<p>State: {{ motion.state }}</p>
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
{% for motion_version in motion.versions.all %}
|
{% for motion_version in motion.versions.all %}
|
||||||
|
@ -248,6 +248,7 @@ class Config(FormView):
|
|||||||
'motion_pdf_title': config['motion_pdf_title'],
|
'motion_pdf_title': config['motion_pdf_title'],
|
||||||
'motion_pdf_preamble': config['motion_pdf_preamble'],
|
'motion_pdf_preamble': config['motion_pdf_preamble'],
|
||||||
'motion_create_new_version': config['motion_create_new_version'],
|
'motion_create_new_version': config['motion_create_new_version'],
|
||||||
|
'motion_workflow': config['motion_workflow'],
|
||||||
}
|
}
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
@ -258,6 +259,7 @@ class Config(FormView):
|
|||||||
config['motion_pdf_title'] = form.cleaned_data['motion_pdf_title']
|
config['motion_pdf_title'] = form.cleaned_data['motion_pdf_title']
|
||||||
config['motion_pdf_preamble'] = form.cleaned_data['motion_pdf_preamble']
|
config['motion_pdf_preamble'] = form.cleaned_data['motion_pdf_preamble']
|
||||||
config['motion_create_new_version'] = form.cleaned_data['motion_create_new_version']
|
config['motion_create_new_version'] = form.cleaned_data['motion_create_new_version']
|
||||||
|
config['motion_workflow'] = form.cleaned_data['motion_workflow']
|
||||||
messages.success(self.request, _('Motion settings successfully saved.'))
|
messages.success(self.request, _('Motion settings successfully saved.'))
|
||||||
return super(Config, self).form_valid(form)
|
return super(Config, self).form_valid(form)
|
||||||
|
|
||||||
|
74
openslides/motion/workflow.py
Normal file
74
openslides/motion/workflow.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.core import exceptions
|
||||||
|
from django.utils.importlib import import_module
|
||||||
|
|
||||||
|
from openslides.config.models import config
|
||||||
|
ugettext = lambda s: s
|
||||||
|
|
||||||
|
_workflow = None
|
||||||
|
|
||||||
|
class State(object):
|
||||||
|
def __init__(self, id, name, next_states=[], poll=False, support=False):
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.next_states = next_states
|
||||||
|
self.poll = poll
|
||||||
|
self.support = support
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
def motion_workflow_choices():
|
||||||
|
for workflow in settings.MOTION_WORKFLOW:
|
||||||
|
yield workflow[0], workflow[1]
|
||||||
|
|
||||||
|
|
||||||
|
def get_state(state='default'):
|
||||||
|
global _workflow
|
||||||
|
if _workflow is not None:
|
||||||
|
return _workflow[state]
|
||||||
|
_workflow = {}
|
||||||
|
for workflow in settings.MOTION_WORKFLOW:
|
||||||
|
if workflow[0] == config['motion_workflow']:
|
||||||
|
try:
|
||||||
|
wf_module, wf_default_state_name = workflow[2].rsplit('.', 1)
|
||||||
|
except ValueError:
|
||||||
|
raise exceptions.ImproperlyConfigured(
|
||||||
|
'%s isn\'t a workflow module' % workflow[2])
|
||||||
|
try:
|
||||||
|
mod = import_module(wf_module)
|
||||||
|
except ImportError as e:
|
||||||
|
raise exceptions.ImproperlyConfigured(
|
||||||
|
'Error importing workflow %s: "%s"' % (wf_module, e))
|
||||||
|
try:
|
||||||
|
default_state = getattr(mod, wf_default_state_name)
|
||||||
|
except AttributeError:
|
||||||
|
raise exceptions.ImproperlyConfigured(
|
||||||
|
'Workflow module "%s" does not define a "%s" State'
|
||||||
|
% (wf_module, wf_default_state_name))
|
||||||
|
_workflow['default'] = default_state
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ImproperlyConfigured('Unknown workflow %s' % conf['motion_workflow'])
|
||||||
|
|
||||||
|
populate_workflow(default_state, _workflow)
|
||||||
|
return get_state(state)
|
||||||
|
|
||||||
|
def populate_workflow(state, workflow):
|
||||||
|
workflow[state.id] = state
|
||||||
|
for s in state.next_states:
|
||||||
|
if s.id not in workflow:
|
||||||
|
populate_workflow(s, workflow)
|
||||||
|
|
||||||
|
|
||||||
|
default_workflow = State('pub', ugettext('Published'), support=True, next_states=[
|
||||||
|
State('per', ugettext('Permitted'), poll=True, next_states=[
|
||||||
|
State('acc', ugettext('Accepted')),
|
||||||
|
State('rej', ugettext('Rejected')),
|
||||||
|
State('wit', ugettext('Withdrawed')),
|
||||||
|
State('adj', ugettext('Adjourned')),
|
||||||
|
State('noc', ugettext('Not Concerned')),
|
||||||
|
State('com', ugettext('Commited a bill')),
|
||||||
|
State('rev', ugettext('Needs Review'))]),
|
||||||
|
State('nop', ugettext('Rejected (not authorized)'))])
|
Loading…
Reference in New Issue
Block a user