Wrote docstrings to the motion app
This commit is contained in:
parent
a34731c00e
commit
a08cf84ab8
@ -1,2 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.motion
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The OpenSlides motion app appends the functionality to OpenSlides, to
|
||||
manage motions.
|
||||
|
||||
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import openslides.motion.signals
|
||||
import openslides.motion.slides
|
||||
|
@ -2,11 +2,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.motion.forms
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Forms for the motion app.
|
||||
Defines the DjangoForms for the motion app.
|
||||
|
||||
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
||||
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
@ -20,14 +20,30 @@ from .workflow import motion_workflow_choices
|
||||
|
||||
|
||||
class BaseMotionForm(forms.ModelForm, CssClassMixin):
|
||||
"""Base FormClass for a Motion.
|
||||
|
||||
For it's own, it append the version data es fields.
|
||||
|
||||
The Class can be mixed with the following Mixins to add fields for the
|
||||
submitter, supporters etc.
|
||||
"""
|
||||
Form to automaticly save the version data for a motion.
|
||||
"""
|
||||
|
||||
title = forms.CharField(widget=forms.TextInput(), label=_("Title"))
|
||||
"""Title of the Motion. Will be saved in a MotionVersion object."""
|
||||
|
||||
text = forms.CharField(widget=forms.Textarea(), label=_("Text"))
|
||||
"""Text of the Motion. Will be saved in a MotionVersion object."""
|
||||
|
||||
reason = forms.CharField(
|
||||
widget=forms.Textarea(), required=False, label=_("Reason"))
|
||||
"""Reason of the Motion. will be saved in a MotionVersion object."""
|
||||
|
||||
class Meta:
|
||||
model = Motion
|
||||
fields = ()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Fill the FormFields releated to the version data with initial data."""
|
||||
self.motion = kwargs.get('instance', None)
|
||||
self.initial = kwargs.setdefault('initial', {})
|
||||
if self.motion is not None:
|
||||
@ -36,16 +52,15 @@ class BaseMotionForm(forms.ModelForm, CssClassMixin):
|
||||
self.initial['reason'] = self.motion.reason
|
||||
super(BaseMotionForm, self).__init__(*args, **kwargs)
|
||||
|
||||
title = forms.CharField(widget=forms.TextInput(), label=_("Title"))
|
||||
text = forms.CharField(widget=forms.Textarea(), label=_("Text"))
|
||||
reason = forms.CharField(
|
||||
widget=forms.Textarea(), required=False, label=_("Reason"))
|
||||
|
||||
|
||||
class MotionSubmitterMixin(forms.ModelForm):
|
||||
"""Mixin to append the submitter field to a MotionForm."""
|
||||
|
||||
submitter = MultiplePersonFormField(label=_("Submitter"))
|
||||
"""Submitter of the Motion. Can be one or more persons."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Fill in the submitter of the motion as default value."""
|
||||
if self.motion is not None:
|
||||
submitter = [submitter.person.person_id for submitter in self.motion.submitter.all()]
|
||||
self.initial['submitter'] = submitter
|
||||
@ -53,9 +68,13 @@ class MotionSubmitterMixin(forms.ModelForm):
|
||||
|
||||
|
||||
class MotionSupporterMixin(forms.ModelForm):
|
||||
"""Mixin to append the supporter field to a Motionform."""
|
||||
|
||||
supporter = MultiplePersonFormField(required=False, label=_("Supporters"))
|
||||
"""Supporter of the Motion. Can be one or more persons."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Fill in the supporter of the motions as default value."""
|
||||
if self.motion is not None:
|
||||
supporter = [supporter.person.person_id for supporter in self.motion.supporter.all()]
|
||||
self.initial['supporter'] = supporter
|
||||
@ -63,18 +82,21 @@ class MotionSupporterMixin(forms.ModelForm):
|
||||
|
||||
|
||||
class MotionCreateNewVersionMixin(forms.ModelForm):
|
||||
"""Mixin to add the option to the form, to choose, to create a new version."""
|
||||
|
||||
new_version = forms.BooleanField(
|
||||
required=False, label=_("Create new version"), initial=True,
|
||||
help_text=_("Trivial changes don't create a new version."))
|
||||
"""BooleanField to decide, if a new version will be created, or the
|
||||
last_version will be used."""
|
||||
|
||||
|
||||
class ConfigForm(forms.Form, CssClassMixin):
|
||||
class ConfigForm(CssClassMixin, forms.Form):
|
||||
"""Form for the configuration tab of OpenSlides."""
|
||||
motion_min_supporters = forms.IntegerField(
|
||||
widget=forms.TextInput(attrs={'class': 'small-input'}),
|
||||
label=_("Number of (minimum) required supporters for a motion"),
|
||||
initial=4,
|
||||
min_value=0,
|
||||
max_value=8,
|
||||
initial=4, min_value=0, max_value=8,
|
||||
help_text=_("Choose 0 to disable the supporting system"),
|
||||
)
|
||||
motion_preamble = forms.CharField(
|
||||
|
@ -6,7 +6,10 @@
|
||||
|
||||
Models for the motion app.
|
||||
|
||||
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
||||
To use a motion object, you only have to import the Motion class. Any
|
||||
functionality can be reached from a motion object.
|
||||
|
||||
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
@ -34,39 +37,38 @@ from .workflow import (motion_workflow_choices, get_state, State, WorkflowError,
|
||||
DUMMY_STATE)
|
||||
|
||||
|
||||
# TODO: Save submitter and supporter in the same table
|
||||
class MotionSubmitter(models.Model):
|
||||
person = PersonField()
|
||||
motion = models.ForeignKey('Motion', related_name="submitter")
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.person)
|
||||
|
||||
|
||||
class MotionSupporter(models.Model):
|
||||
person = PersonField()
|
||||
motion = models.ForeignKey('Motion', related_name="supporter")
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.person)
|
||||
|
||||
|
||||
class Motion(SlideMixin, models.Model):
|
||||
"""
|
||||
The Motion-Model.
|
||||
"""
|
||||
prefix = "motion"
|
||||
"""The Motion Class.
|
||||
|
||||
This class is the main entry point to all other classes related to a motion.
|
||||
"""
|
||||
|
||||
prefix = "motion"
|
||||
"""Prefix for the slide system."""
|
||||
|
||||
active_version = models.ForeignKey('MotionVersion', null=True,
|
||||
related_name="active_version")
|
||||
"""Points to a specific version.
|
||||
|
||||
Used be the permitted-version-system to deside witch version is the active
|
||||
Version. Could also be used to only choose a specific version as a default
|
||||
version. Like the Sighted versions on Wikipedia.
|
||||
"""
|
||||
|
||||
active_version = models.ForeignKey(
|
||||
'MotionVersion', null=True, related_name="active_version")
|
||||
state_id = models.CharField(max_length=3)
|
||||
# Log (Translatable)
|
||||
"""The id of a state object.
|
||||
|
||||
This Attribute is used be motion.state to identify the current state of the
|
||||
motion.
|
||||
"""
|
||||
|
||||
identifier = models.CharField(max_length=255, null=True, blank=True,
|
||||
unique=True)
|
||||
category = models.ForeignKey('Category', null=True, blank=True)
|
||||
# TODO proposal
|
||||
# Maybe rename to master_copy
|
||||
master = models.ForeignKey('self', null=True, blank=True)
|
||||
"""A string as human readable identifier for the motion."""
|
||||
|
||||
# category = models.ForeignKey('Category', null=True, blank=True)
|
||||
# TODO: proposal
|
||||
#master = models.ForeignKey('self', null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
@ -79,17 +81,35 @@ class Motion(SlideMixin, models.Model):
|
||||
# ordering = ('number',)
|
||||
|
||||
def __unicode__(self):
|
||||
"""Return a human readable name of this motion."""
|
||||
return self.get_title()
|
||||
|
||||
# TODO: Use transaction
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Saves the motion. Create or update a motion_version object
|
||||
"""Save the motion.
|
||||
|
||||
1. Set the state of a new motion to the default motion.
|
||||
2. Save the motion object.
|
||||
3. Save the version Data.
|
||||
4. Set the active version for the motion.
|
||||
|
||||
A new version will be saved if motion.new_version was called
|
||||
between the creation of this object and the last call of motion.save()
|
||||
|
||||
or
|
||||
|
||||
If the motion has new version data (title, text, reason)
|
||||
|
||||
and
|
||||
|
||||
the config 'motion_create_new_version' is set to
|
||||
'ALLWASY_CREATE_NEW_VERSION'.
|
||||
"""
|
||||
if not self.state_id:
|
||||
self.reset_state()
|
||||
|
||||
super(Motion, self).save(*args, **kwargs)
|
||||
|
||||
# Find out if the version data has changed
|
||||
for attr in ['title', 'text', 'reason']:
|
||||
if not self.versions.exists():
|
||||
@ -109,7 +129,7 @@ class Motion(SlideMixin, models.Model):
|
||||
elif new_data and not need_new_version:
|
||||
version = self.last_version
|
||||
else:
|
||||
# We do not need to save the motion version
|
||||
# We do not need to save the motion version.
|
||||
return
|
||||
|
||||
# Save title, text and reason in the version object
|
||||
@ -137,6 +157,10 @@ class Motion(SlideMixin, models.Model):
|
||||
self.save()
|
||||
|
||||
def get_absolute_url(self, link='detail'):
|
||||
"""Return an URL for this version.
|
||||
|
||||
The keywordargument 'link' can be 'detail', 'view', 'edit' or 'delete'.
|
||||
"""
|
||||
if link == 'view' or link == 'detail':
|
||||
return reverse('motion_detail', args=[str(self.id)])
|
||||
if link == 'edit':
|
||||
@ -145,8 +169,9 @@ class Motion(SlideMixin, models.Model):
|
||||
return reverse('motion_delete', args=[str(self.id)])
|
||||
|
||||
def get_title(self):
|
||||
"""
|
||||
Get the title of the motion. The titel is taken from motion.version
|
||||
"""Get the title of the motion.
|
||||
|
||||
The titel is taken from motion.version.
|
||||
"""
|
||||
try:
|
||||
return self._title
|
||||
@ -154,16 +179,23 @@ class Motion(SlideMixin, models.Model):
|
||||
return self.version.title
|
||||
|
||||
def set_title(self, title):
|
||||
"""
|
||||
Set the titel of the motion. The titel will me saved in motion.save()
|
||||
"""Set the titel of the motion.
|
||||
|
||||
The titel will me saved into the version object, wenn motion.save() is
|
||||
called.
|
||||
"""
|
||||
self._title = title
|
||||
|
||||
title = property(get_title, set_title)
|
||||
"""The title of the motion.
|
||||
|
||||
Is saved in a MotionVersion object.
|
||||
"""
|
||||
|
||||
def get_text(self):
|
||||
"""
|
||||
Get the text of the motion. Simular to get_title()
|
||||
"""Get the text of the motion.
|
||||
|
||||
Simular to get_title().
|
||||
"""
|
||||
try:
|
||||
return self._text
|
||||
@ -171,16 +203,22 @@ class Motion(SlideMixin, models.Model):
|
||||
return self.version.text
|
||||
|
||||
def set_text(self, text):
|
||||
"""
|
||||
Set the text of the motion. Simular to set_title()
|
||||
""" Set the text of the motion.
|
||||
|
||||
Simular to set_title().
|
||||
"""
|
||||
self._text = text
|
||||
|
||||
text = property(get_text, set_text)
|
||||
"""The text of a motin.
|
||||
|
||||
Is saved in a MotionVersion object.
|
||||
"""
|
||||
|
||||
def get_reason(self):
|
||||
"""
|
||||
Get the reason of the motion. Simular to get_title()
|
||||
"""Get the reason of the motion.
|
||||
|
||||
Simular to get_title().
|
||||
"""
|
||||
try:
|
||||
return self._reason
|
||||
@ -188,20 +226,26 @@ class Motion(SlideMixin, models.Model):
|
||||
return self.version.reason
|
||||
|
||||
def set_reason(self, reason):
|
||||
"""
|
||||
Set the reason of the motion. Simular to set_title()
|
||||
"""Set the reason of the motion.
|
||||
|
||||
Simular to set_title().
|
||||
"""
|
||||
self._reason = reason
|
||||
|
||||
reason = property(get_reason, set_reason)
|
||||
"""The reason for the motion.
|
||||
|
||||
Is saved in a MotionVersion object.
|
||||
"""
|
||||
|
||||
@property
|
||||
def new_version(self):
|
||||
"""
|
||||
"""Return a Version object, not saved in the database.
|
||||
|
||||
On the first call, it creates a new version. On any later call, it
|
||||
use the existing new version.
|
||||
|
||||
The new_version object will be deleted when it is saved into the db
|
||||
The new_version object will be deleted when it is saved into the db.
|
||||
"""
|
||||
try:
|
||||
return self._new_version
|
||||
@ -210,9 +254,9 @@ class Motion(SlideMixin, models.Model):
|
||||
return self._new_version
|
||||
|
||||
def get_version(self):
|
||||
"""
|
||||
Get the "active" version object. This version will be used to get the
|
||||
data for this motion.
|
||||
"""Get the 'active' version object.
|
||||
|
||||
This version will be used to get the data for this motion.
|
||||
"""
|
||||
try:
|
||||
return self._version
|
||||
@ -220,12 +264,12 @@ class Motion(SlideMixin, models.Model):
|
||||
return self.last_version
|
||||
|
||||
def set_version(self, version):
|
||||
"""
|
||||
Set the "active" version object.
|
||||
"""Set the 'active' version object.
|
||||
|
||||
If version is None, the last_version will be used.
|
||||
If version is a version object, this object will be used.
|
||||
If version is Int, the N version of this motion will be used.
|
||||
The keyargument 'version' can be a MotionVersion object or the
|
||||
version_number of a VersionObject or None.
|
||||
|
||||
If the argument is None, the newest version will be used.
|
||||
"""
|
||||
if version is None:
|
||||
try:
|
||||
@ -242,12 +286,11 @@ class Motion(SlideMixin, models.Model):
|
||||
self._version = version
|
||||
|
||||
version = property(get_version, set_version)
|
||||
"""The active version of this motion."""
|
||||
|
||||
@property
|
||||
def last_version(self):
|
||||
"""
|
||||
Get the newest version of the motion
|
||||
"""
|
||||
"""Return the newest version of the motion."""
|
||||
# TODO: Fix the case, that the motion has no Version
|
||||
try:
|
||||
return self.versions.order_by('-version_number')[0]
|
||||
@ -255,15 +298,15 @@ class Motion(SlideMixin, models.Model):
|
||||
return self.new_version
|
||||
|
||||
def is_submitter(self, person):
|
||||
"""Return True, if person is a submitter of this motion. Else: False."""
|
||||
self.submitter.filter(person=person).exists()
|
||||
|
||||
def is_supporter(self, person):
|
||||
"""Return True, if person is a supporter of this motion. Else: False."""
|
||||
return self.supporter.filter(person=person).exists()
|
||||
|
||||
def support(self, person):
|
||||
"""
|
||||
Add a Supporter to the list of supporters of the motion.
|
||||
"""
|
||||
"""Add 'person' as a supporter of this motion."""
|
||||
if self.state.support:
|
||||
if not self.is_supporter(person):
|
||||
MotionSupporter(motion=self, person=person).save()
|
||||
@ -271,17 +314,16 @@ class Motion(SlideMixin, models.Model):
|
||||
raise WorkflowError("You can not support a motion in state %s" % self.state.name)
|
||||
|
||||
def unsupport(self, person):
|
||||
"""
|
||||
Remove a supporter from the list of supporters of the motion
|
||||
"""
|
||||
"""Remove 'person' as supporter from this motion."""
|
||||
if self.state.support:
|
||||
self.supporter.filter(person=person).delete()
|
||||
else:
|
||||
raise WorkflowError("You can not unsupport a motion in state %s" % self.state.name)
|
||||
|
||||
def create_poll(self):
|
||||
"""
|
||||
Create a new poll for this motion
|
||||
"""Create a new poll for this motion.
|
||||
|
||||
Return the new poll object.
|
||||
"""
|
||||
if self.state.create_poll:
|
||||
# TODO: auto increment the poll_number in the Database
|
||||
@ -293,8 +335,9 @@ class Motion(SlideMixin, models.Model):
|
||||
raise WorkflowError("You can not create a poll in state %s" % self.state.name)
|
||||
|
||||
def get_state(self):
|
||||
"""
|
||||
Get the state of this motion. Return a State object.
|
||||
"""Return the state of the motion.
|
||||
|
||||
State is a State object. See openslides.motion.workflow for more informations.
|
||||
"""
|
||||
try:
|
||||
return get_state(self.state_id)
|
||||
@ -302,10 +345,10 @@ class Motion(SlideMixin, models.Model):
|
||||
return DUMMY_STATE
|
||||
|
||||
def set_state(self, next_state):
|
||||
"""
|
||||
Set the state of this motion.
|
||||
"""Set the state of this motion.
|
||||
|
||||
next_state has to be a valid state id or State object.
|
||||
The keyargument 'next_state' has to be a State object or an id of a
|
||||
State object.
|
||||
"""
|
||||
if not isinstance(next_state, State):
|
||||
next_state = get_state(next_state)
|
||||
@ -315,17 +358,14 @@ class Motion(SlideMixin, models.Model):
|
||||
raise WorkflowError('%s is not a valid next_state' % next_state)
|
||||
|
||||
state = property(get_state, set_state)
|
||||
"""The state of the motion as Ste object."""
|
||||
|
||||
def reset_state(self):
|
||||
"""
|
||||
Set the state to the default state.
|
||||
"""
|
||||
"""Set the state to the default state."""
|
||||
self.state_id = get_state('default').id
|
||||
|
||||
def slide(self):
|
||||
"""
|
||||
return the slide dict
|
||||
"""
|
||||
"""Return the slide dict."""
|
||||
data = super(Motion, self).slide()
|
||||
data['motion'] = self
|
||||
data['title'] = self.title
|
||||
@ -333,6 +373,7 @@ class Motion(SlideMixin, models.Model):
|
||||
return data
|
||||
|
||||
def get_agenda_title(self):
|
||||
"""Return a title for the Agenda."""
|
||||
return self.last_version.title
|
||||
|
||||
## def get_agenda_title_supplement(self):
|
||||
@ -340,8 +381,7 @@ class Motion(SlideMixin, models.Model):
|
||||
## return '(%s %s)' % (ugettext('motion'), number)
|
||||
|
||||
def get_allowed_actions(self, person):
|
||||
"""
|
||||
Gets a dictonary with all allowed actions for a specific person.
|
||||
"""Return a dictonary with all allowed actions for a specific person.
|
||||
|
||||
The dictonary contains the following actions.
|
||||
|
||||
@ -374,11 +414,16 @@ class Motion(SlideMixin, models.Model):
|
||||
return actions
|
||||
|
||||
def write_log(self, message, person=None):
|
||||
"""Write a log message.
|
||||
|
||||
Message should be in english and translatable.
|
||||
|
||||
e.G: motion.write_log(ugettext_noob('Message Text'))
|
||||
"""
|
||||
MotionLog.objects.create(motion=self, message=message, person=person)
|
||||
|
||||
def activate_version(self, version):
|
||||
"""
|
||||
Activate a version of this motion.
|
||||
"""Set the active state of a version to True.
|
||||
|
||||
'version' can be a version object, or the version_number of a version.
|
||||
"""
|
||||
@ -391,8 +436,7 @@ class Motion(SlideMixin, models.Model):
|
||||
version.save()
|
||||
|
||||
def reject_version(self, version):
|
||||
"""
|
||||
Reject a version of this motion.
|
||||
"""Reject a version of this motion.
|
||||
|
||||
'version' can be a version object, or the version_number of a version.
|
||||
"""
|
||||
@ -407,58 +451,126 @@ class Motion(SlideMixin, models.Model):
|
||||
|
||||
|
||||
class MotionVersion(models.Model):
|
||||
version_number = models.PositiveIntegerField(default=1)
|
||||
title = models.CharField(max_length=255, verbose_name=ugettext_lazy("Title"))
|
||||
text = models.TextField(verbose_name=_("Text"))
|
||||
reason = models.TextField(null=True, blank=True, verbose_name=ugettext_lazy("Reason"))
|
||||
rejected = models.BooleanField(default=False)
|
||||
creation_time = models.DateTimeField(auto_now=True)
|
||||
"""
|
||||
A MotionVersion object saves some date of the motion."""
|
||||
|
||||
motion = models.ForeignKey(Motion, related_name='versions')
|
||||
identifier = models.CharField(max_length=255, verbose_name=ugettext_lazy("Version identifier"))
|
||||
note = models.TextField(null=True, blank=True)
|
||||
"""The Motion, to witch the version belongs."""
|
||||
|
||||
version_number = models.PositiveIntegerField(default=1)
|
||||
"""An id for this version in realation to a motion.
|
||||
|
||||
Is unique for each motion.
|
||||
"""
|
||||
|
||||
title = models.CharField(max_length=255, verbose_name=ugettext_lazy("Title"))
|
||||
"""The Title of a motion."""
|
||||
|
||||
text = models.TextField(verbose_name=_("Text"))
|
||||
"""The text of a motion."""
|
||||
|
||||
reason = models.TextField(null=True, blank=True, verbose_name=ugettext_lazy("Reason"))
|
||||
"""The reason for a motion."""
|
||||
|
||||
rejected = models.BooleanField(default=False)
|
||||
"""Saves, if the version is rejected."""
|
||||
|
||||
creation_time = models.DateTimeField(auto_now=True)
|
||||
"""Time, when the version was saved."""
|
||||
|
||||
#identifier = models.CharField(max_length=255, verbose_name=ugettext_lazy("Version identifier"))
|
||||
#note = models.TextField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ("motion", "version_number")
|
||||
|
||||
def __unicode__(self):
|
||||
"""Return a string, representing this object."""
|
||||
counter = self.version_number or _('new')
|
||||
return "%s Version %s" % (self.motion, counter)
|
||||
|
||||
def get_absolute_url(self, link='detail'):
|
||||
"""Return the URL of this Version.
|
||||
|
||||
The keyargument link can be 'view' or 'detail'.
|
||||
"""
|
||||
if link == 'view' or link == 'detail':
|
||||
return reverse('motion_version_detail', args=[str(self.motion.id),
|
||||
str(self.version_number)])
|
||||
|
||||
@property
|
||||
def active(self):
|
||||
"""Return True, if the version is the active version of a motion. Else: False."""
|
||||
return self.active_version.exists()
|
||||
|
||||
|
||||
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("Category prefix"))
|
||||
class MotionSubmitter(models.Model):
|
||||
"""Save the submitter of a Motion."""
|
||||
|
||||
motion = models.ForeignKey('Motion', related_name="submitter")
|
||||
"""The motion to witch the object belongs."""
|
||||
|
||||
person = PersonField()
|
||||
"""The person, who is the submitter."""
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
"""Return the name of the submitter as string."""
|
||||
return unicode(self.person)
|
||||
|
||||
|
||||
class Comment(models.Model):
|
||||
motion_version = models.ForeignKey(MotionVersion)
|
||||
text = models.TextField()
|
||||
author = PersonField()
|
||||
creation_time = models.DateTimeField(auto_now=True)
|
||||
class MotionSupporter(models.Model):
|
||||
"""Save the submitter of a Motion."""
|
||||
|
||||
motion = models.ForeignKey('Motion', related_name="supporter")
|
||||
"""The motion to witch the object belongs."""
|
||||
|
||||
person = PersonField()
|
||||
"""The person, who is the supporter."""
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
"""Return the name of the supporter as string."""
|
||||
return unicode(self.person)
|
||||
|
||||
|
||||
## 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("Category prefix"))
|
||||
|
||||
## def __unicode__(self):
|
||||
## return self.name
|
||||
|
||||
|
||||
## class Comment(models.Model):
|
||||
## motion_version = models.ForeignKey(MotionVersion)
|
||||
## text = models.TextField()
|
||||
## author = PersonField()
|
||||
## creation_time = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
||||
class MotionLog(models.Model):
|
||||
"""Save a logmessage for a motion."""
|
||||
|
||||
motion = models.ForeignKey(Motion, related_name='log_messages')
|
||||
"""The motion to witch the object belongs."""
|
||||
|
||||
message = models.CharField(max_length=255)
|
||||
"""The log message.
|
||||
|
||||
Should be in english.
|
||||
"""
|
||||
|
||||
person = PersonField(null=True)
|
||||
"""A person object, who created the log message. Optional."""
|
||||
|
||||
time = models.DateTimeField(auto_now=True)
|
||||
"""The Time, when the loged action was performed."""
|
||||
|
||||
class Meta:
|
||||
ordering = ['-time']
|
||||
|
||||
def __unicode__(self):
|
||||
"""Return a string, representing the log message."""
|
||||
# TODO: write time in the local time format.
|
||||
if self.person is None:
|
||||
return "%s %s" % (self.time, _(self.message))
|
||||
@ -467,33 +579,63 @@ class MotionLog(models.Model):
|
||||
|
||||
|
||||
class MotionError(Exception):
|
||||
"""Exception raised when errors in the motion accure."""
|
||||
pass
|
||||
|
||||
|
||||
class MotionVote(BaseVote):
|
||||
"""Saves the votes for a MotionPoll.
|
||||
|
||||
There should allways be three MotionVote objects for each poll,
|
||||
one for 'yes', 'no', and 'abstain'."""
|
||||
|
||||
option = models.ForeignKey('MotionOption')
|
||||
"""The option object, to witch the vote belongs."""
|
||||
|
||||
|
||||
class MotionOption(BaseOption):
|
||||
"""Links between the MotionPollClass and the MotionVoteClass.
|
||||
|
||||
There should be one MotionOption object for each poll."""
|
||||
|
||||
poll = models.ForeignKey('MotionPoll')
|
||||
"""The poll object, to witch the object belongs."""
|
||||
|
||||
vote_class = MotionVote
|
||||
"""The VoteClass, to witch this Class links."""
|
||||
|
||||
|
||||
class MotionPoll(CountInvalid, CountVotesCast, BasePoll):
|
||||
option_class = MotionOption
|
||||
vote_values = [
|
||||
ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
|
||||
"""The Class to saves the poll results for a motion poll."""
|
||||
|
||||
motion = models.ForeignKey(Motion, related_name='polls')
|
||||
"""The motion to witch the object belongs."""
|
||||
|
||||
option_class = MotionOption
|
||||
"""The option class, witch links between this object the the votes."""
|
||||
|
||||
vote_values = [
|
||||
ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
|
||||
"""The possible anwers for the poll. 'Yes, 'No' and 'Abstain'."""
|
||||
|
||||
poll_number = models.PositiveIntegerField(default=1)
|
||||
"""An id for this poll in realation to a motion.
|
||||
|
||||
Is unique for each motion.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
unique_together = ("motion", "poll_number")
|
||||
|
||||
def __unicode__(self):
|
||||
"""Return a string, representing the poll."""
|
||||
return _('Ballot %d') % self.poll_number
|
||||
|
||||
def get_absolute_url(self, link='edit'):
|
||||
"""Return an URL for the poll.
|
||||
|
||||
The keyargument 'link' can be 'edit' or 'delete'.
|
||||
"""
|
||||
if link == 'edit':
|
||||
return reverse('motion_poll_edit', args=[str(self.motion.pk),
|
||||
str(self.poll_number)])
|
||||
@ -501,16 +643,13 @@ class MotionPoll(CountInvalid, CountVotesCast, BasePoll):
|
||||
return reverse('motion_poll_delete', args=[str(self.motion.pk),
|
||||
str(self.poll_number)])
|
||||
|
||||
def get_motion(self):
|
||||
return self.motion
|
||||
|
||||
def set_options(self):
|
||||
"""Create the option class for this poll."""
|
||||
#TODO: maybe it is possible with .create() to call this without poll=self
|
||||
# or call this in save()
|
||||
self.get_option_class()(poll=self).save()
|
||||
|
||||
def append_pollform_fields(self, fields):
|
||||
"""Apend the fields for invalid and votecast to the ModelForm."""
|
||||
CountInvalid.append_pollform_fields(self, fields)
|
||||
CountVotesCast.append_pollform_fields(self, fields)
|
||||
|
||||
def get_ballot(self):
|
||||
return self.motion.motionpoll_set.filter(id__lte=self.id).count()
|
||||
|
@ -1,3 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.motion.pdf
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Functions to generate the PDFs for the motion app.
|
||||
"""
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.units import cm
|
||||
from reportlab.platypus import (
|
||||
@ -11,6 +20,8 @@ from .models import Motion
|
||||
|
||||
|
||||
def motions_to_pdf(pdf):
|
||||
"""Create a PDF with all motions."""
|
||||
|
||||
motions = Motion.objects.all()
|
||||
all_motion_cover(pdf, motions)
|
||||
for motion in motions:
|
||||
@ -19,6 +30,8 @@ def motions_to_pdf(pdf):
|
||||
|
||||
|
||||
def motion_to_pdf(pdf, motion):
|
||||
"""Create a PDF for one motion."""
|
||||
|
||||
pdf.append(Paragraph(_("Motion: %s") % motion.title, stylesheet['Heading1']))
|
||||
|
||||
motion_data = []
|
||||
@ -141,6 +154,7 @@ def motion_to_pdf(pdf, motion):
|
||||
|
||||
|
||||
def all_motion_cover(pdf, motions):
|
||||
"""Create a coverpage for all motions."""
|
||||
pdf.append(Paragraph(config["motion_pdf_title"], stylesheet['Heading1']))
|
||||
|
||||
preamble = config["motion_pdf_preamble"]
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
Signals for the motion app.
|
||||
|
||||
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
||||
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
@ -18,6 +18,7 @@ from openslides.config.signals import default_config_value
|
||||
|
||||
@receiver(default_config_value, dispatch_uid="motion_default_config")
|
||||
def default_config(sender, key, **kwargs):
|
||||
"""Return the default config values for the motion app."""
|
||||
return {
|
||||
'motion_min_supporters': 0,
|
||||
'motion_preamble': _('The assembly may decide,'),
|
||||
|
@ -1,3 +1,15 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.motion.slides
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Defines the Slides for the motion app.
|
||||
|
||||
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from openslides.projector.api import register_slidemodel
|
||||
from .models import Motion
|
||||
|
||||
|
@ -2,11 +2,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.motion.urls
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
URL list for the motion app.
|
||||
Defines the URL patterns for the motion app.
|
||||
|
||||
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
|
||||
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
@ -6,10 +6,11 @@
|
||||
|
||||
Views for the motion app.
|
||||
|
||||
:copyright: 2011, 2012 by the OpenSlides team, see AUTHORS.
|
||||
Will automaticly imported from openslides.motion.urls.py
|
||||
|
||||
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
from reportlab.platypus import Paragraph
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib import messages
|
||||
@ -39,9 +40,7 @@ from .pdf import motions_to_pdf, motion_to_pdf
|
||||
|
||||
|
||||
class MotionListView(ListView):
|
||||
"""
|
||||
List all motion.
|
||||
"""
|
||||
"""View, to list all motions."""
|
||||
permission_required = 'motion.can_see_motion'
|
||||
model = Motion
|
||||
|
||||
@ -49,7 +48,11 @@ motion_list = MotionListView.as_view()
|
||||
|
||||
|
||||
class GetVersionMixin(object):
|
||||
"""Mixin to set a specific version to a motion."""
|
||||
|
||||
def get_object(self):
|
||||
"""Return a Motion object. The id is taken from the url and the version
|
||||
is set to the version with the 'version_number' from the URL."""
|
||||
object = super(GetVersionMixin, self).get_object()
|
||||
version_number = self.kwargs.get('version_number', None)
|
||||
if version_number is not None:
|
||||
@ -61,14 +64,15 @@ class GetVersionMixin(object):
|
||||
|
||||
|
||||
class MotionDetailView(GetVersionMixin, DetailView):
|
||||
"""
|
||||
Show the details of one motion.
|
||||
"""
|
||||
"""Show one motion."""
|
||||
permission_required = 'motion.can_see_motion'
|
||||
model = Motion
|
||||
template_name = 'motion/motion_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Return the template context.
|
||||
|
||||
Append the allowed actions for the motion to the context.
|
||||
"""
|
||||
context = super(MotionDetailView, self).get_context_data(**kwargs)
|
||||
context['allowed_actions'] = self.object.get_allowed_actions(self.request.user)
|
||||
return context
|
||||
@ -77,10 +81,12 @@ motion_detail = MotionDetailView.as_view()
|
||||
|
||||
|
||||
class MotionMixin(object):
|
||||
"""
|
||||
Mixin to add save the version-data to the motion-object
|
||||
"""
|
||||
"""Mixin for MotionViewsClasses, to save the version data."""
|
||||
|
||||
def manipulate_object(self, form):
|
||||
"""Save the version data into the motion object before it is saved in
|
||||
the Database."""
|
||||
|
||||
super(MotionMixin, self).manipulate_object(form)
|
||||
for attr in ['title', 'text', 'reason']:
|
||||
setattr(self.object, attr, form.cleaned_data[attr])
|
||||
@ -92,6 +98,7 @@ class MotionMixin(object):
|
||||
pass
|
||||
|
||||
def post_save(self, form):
|
||||
"""Save the submitter an the supporter so the motion."""
|
||||
super(MotionMixin, self).post_save(form)
|
||||
# TODO: only delete and save neccessary submitters and supporter
|
||||
if 'submitter' in form.cleaned_data:
|
||||
@ -106,6 +113,13 @@ class MotionMixin(object):
|
||||
for person in form.cleaned_data['supporter']])
|
||||
|
||||
def get_form_class(self):
|
||||
"""Return the FormClass to Create or Update the Motion.
|
||||
|
||||
forms.BaseMotionForm is the base for the Class, and some FormMixins
|
||||
will be mixed in dependence of some config values. See motion.forms
|
||||
for more information on the mixins.
|
||||
"""
|
||||
|
||||
form_classes = [BaseMotionForm]
|
||||
if self.request.user.has_perm('motion.can_manage_motion'):
|
||||
form_classes.append(MotionSubmitterMixin)
|
||||
@ -117,13 +131,12 @@ class MotionMixin(object):
|
||||
|
||||
|
||||
class MotionCreateView(MotionMixin, CreateView):
|
||||
"""
|
||||
Create a motion.
|
||||
"""
|
||||
"""View to create a motion."""
|
||||
permission_required = 'motion.can_create_motion'
|
||||
model = Motion
|
||||
|
||||
def form_valid(self, form):
|
||||
"""Write a log message, if the form is valid."""
|
||||
value = super(MotionCreateView, self).form_valid(form)
|
||||
self.object.write_log(ugettext_noop('Motion created'), self.request.user)
|
||||
return value
|
||||
@ -132,15 +145,15 @@ motion_create = MotionCreateView.as_view()
|
||||
|
||||
|
||||
class MotionUpdateView(MotionMixin, UpdateView):
|
||||
"""
|
||||
Update a motion.
|
||||
"""
|
||||
"""View to update a motion."""
|
||||
model = Motion
|
||||
|
||||
def has_permission(self, request, *args, **kwargs):
|
||||
"""Check, if the request.user has the permission to edit the motion."""
|
||||
return self.get_object().get_allowed_actions(request.user)['edit']
|
||||
|
||||
def form_valid(self, form):
|
||||
"""Write a log message, if the form is valid."""
|
||||
value = super(MotionUpdateView, self).form_valid(form)
|
||||
self.object.write_log(ugettext_noop('Motion updated'), self.request.user)
|
||||
return value
|
||||
@ -149,34 +162,39 @@ motion_edit = MotionUpdateView.as_view()
|
||||
|
||||
|
||||
class MotionDeleteView(DeleteView):
|
||||
"""
|
||||
Delete one Motion.
|
||||
"""
|
||||
"""View to delete a motion."""
|
||||
model = Motion
|
||||
success_url_name = 'motion_list'
|
||||
|
||||
def has_permission(self, request, *args, **kwargs):
|
||||
"""Check if the request.user has the permission to delete the motion."""
|
||||
return self.get_object().get_allowed_actions(request.user)['delete']
|
||||
|
||||
motion_delete = MotionDeleteView.as_view()
|
||||
|
||||
|
||||
class VersionPermitView(GetVersionMixin, SingleObjectMixin, QuestionMixin, RedirectView):
|
||||
"""View to permit a version of a motion."""
|
||||
|
||||
model = Motion
|
||||
question_url_name = 'motion_version_detail'
|
||||
success_url_name = 'motion_version_detail'
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""Set self.object to a motion."""
|
||||
self.object = self.get_object()
|
||||
return super(VersionPermitView, self).get(*args, **kwargs)
|
||||
|
||||
def get_url_name_args(self):
|
||||
"""Return a list with arguments to create the success- and question_url."""
|
||||
return [self.object.pk, self.object.version.version_number]
|
||||
|
||||
def get_question(self):
|
||||
"""Return a string, shown to the user as question to permit the version."""
|
||||
return _('Are you sure you want permit Version %s?') % self.object.version.version_number
|
||||
|
||||
def case_yes(self):
|
||||
"""Activate the version, if the user chooses 'yes'."""
|
||||
self.object.activate_version(self.object.version)
|
||||
self.object.save()
|
||||
|
||||
@ -184,21 +202,25 @@ version_permit = VersionPermitView.as_view()
|
||||
|
||||
|
||||
class VersionRejectView(GetVersionMixin, SingleObjectMixin, QuestionMixin, RedirectView):
|
||||
"""View to reject a version."""
|
||||
model = Motion
|
||||
question_url_name = 'motion_version_detail'
|
||||
success_url_name = 'motion_version_detail'
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""Set self.object to a motion."""
|
||||
self.object = self.get_object()
|
||||
return super(VersionRejectView, self).get(*args, **kwargs)
|
||||
|
||||
def get_url_name_args(self):
|
||||
"""Return a list with arguments to create the success- and question_url."""
|
||||
return [self.object.pk, self.object.version.version_number]
|
||||
|
||||
def get_question(self):
|
||||
return _('Are you sure you want reject Version %s?') % self.object.version.version_number
|
||||
|
||||
def case_yes(self):
|
||||
"""Reject the version, if the user chooses 'yes'."""
|
||||
self.object.reject_version(self.object.version)
|
||||
self.object.save()
|
||||
|
||||
@ -206,23 +228,25 @@ version_reject = VersionRejectView.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.
|
||||
|
||||
If self.support is False, the view will remove a request.user from the supporter list.
|
||||
"""
|
||||
Classed based view to support or unsupport a motion. Use
|
||||
support=True or support=False in urls.py
|
||||
"""
|
||||
|
||||
permission_required = 'motion.can_support_motion'
|
||||
model = Motion
|
||||
support = True
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Set self.object to a motion."""
|
||||
self.object = self.get_object()
|
||||
return super(SupportView, self).get(request, *args, **kwargs)
|
||||
|
||||
def check_permission(self, request):
|
||||
"""
|
||||
Checks whether request.user can support or unsupport the motion.
|
||||
Returns True or False.
|
||||
"""
|
||||
"""Return True if the user can support or unsupport the motion. Else: False."""
|
||||
|
||||
allowed_actions = self.object.get_allowed_actions(request.user)
|
||||
if self.support and not allowed_actions['support']:
|
||||
messages.error(request, _('You can not support this motion.'))
|
||||
@ -233,17 +257,19 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
|
||||
else:
|
||||
return True
|
||||
|
||||
def pre_redirect(self, request, *args, **kwargs):
|
||||
if self.check_permission(request):
|
||||
super(SupportView, self).pre_redirect(request, *args, **kwargs)
|
||||
|
||||
def get_question(self):
|
||||
"""Return the question string."""
|
||||
if self.support:
|
||||
return _('Do you really want to support this motion?')
|
||||
else:
|
||||
return _('Do you really want to unsupport this motion?')
|
||||
|
||||
def case_yes(self):
|
||||
"""Append or remove the request.user from the motion.
|
||||
|
||||
First the methode checks the permissions, and writes a log message after
|
||||
appending or removing the user.
|
||||
"""
|
||||
if self.check_permission(self.request):
|
||||
user = self.request.user
|
||||
if self.support:
|
||||
@ -254,12 +280,14 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
|
||||
self.object.write_log(ugettext_noop("Supporter: -%s") % user, user)
|
||||
|
||||
def get_success_message(self):
|
||||
"""Return the success message."""
|
||||
if self.support:
|
||||
return _("You have supported this motion successfully.")
|
||||
else:
|
||||
return _("You have unsupported this motion successfully.")
|
||||
|
||||
def get_redirect_url(self, **kwargs):
|
||||
"""Return the url, the view should redirect to."""
|
||||
return self.object.get_absolute_url()
|
||||
|
||||
motion_support = SupportView.as_view(support=True)
|
||||
@ -267,48 +295,68 @@ motion_unsupport = SupportView.as_view(support=False)
|
||||
|
||||
|
||||
class PollCreateView(SingleObjectMixin, RedirectView):
|
||||
"""View to create a poll for a motion."""
|
||||
permission_required = 'motion.can_manage_motion'
|
||||
model = Motion
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Set self.object to a motion."""
|
||||
self.object = self.get_object()
|
||||
return super(PollCreateView, self).get(request, *args, **kwargs)
|
||||
|
||||
def pre_redirect(self, request, *args, **kwargs):
|
||||
"""Create the poll for the motion."""
|
||||
self.poll = self.object.create_poll()
|
||||
self.object.write_log(ugettext_noop("Poll created"), request.user)
|
||||
messages.success(request, _("New vote was successfully created."))
|
||||
|
||||
def get_redirect_url(self, **kwargs):
|
||||
"""Return the URL to the EditView of the poll."""
|
||||
return reverse('motion_poll_edit', args=[self.object.pk, self.poll.poll_number])
|
||||
|
||||
poll_create = PollCreateView.as_view()
|
||||
|
||||
|
||||
class PollMixin(object):
|
||||
"""Mixin for the PollUpdateView and the PollDeleteView."""
|
||||
permission_required = 'motion.can_manage_motion'
|
||||
success_url_name = 'motion_detail'
|
||||
|
||||
def get_object(self):
|
||||
"""Return a MotionPoll object.
|
||||
|
||||
Use the motion id and the poll_number from the url kwargs to get the
|
||||
object.
|
||||
"""
|
||||
return MotionPoll.objects.filter(
|
||||
motion=self.kwargs['pk'],
|
||||
poll_number=self.kwargs['poll_number']).get()
|
||||
|
||||
def get_url_name_args(self):
|
||||
"""Return the arguments to create the url to the success_url"""
|
||||
return [self.object.motion.pk]
|
||||
|
||||
|
||||
class PollUpdateView(PollMixin, PollFormView):
|
||||
"""View to update a MotionPoll."""
|
||||
|
||||
poll_class = MotionPoll
|
||||
"""Poll Class to use for this view."""
|
||||
|
||||
template_name = 'motion/poll_form.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Return the template context.
|
||||
|
||||
Append the motion object to the context.
|
||||
"""
|
||||
context = super(PollUpdateView, self).get_context_data(**kwargs)
|
||||
context.update({
|
||||
'motion': self.poll.motion})
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
"""Write a log message, if the form is valid."""
|
||||
value = super(PollUpdateView, self).form_valid(form)
|
||||
self.object.write_log(ugettext_noop('Poll updated'), self.request.user)
|
||||
return value
|
||||
@ -317,9 +365,11 @@ poll_edit = PollUpdateView.as_view()
|
||||
|
||||
|
||||
class PollDeleteView(PollMixin, DeleteView):
|
||||
"""View to delete a MotionPoll."""
|
||||
model = MotionPoll
|
||||
|
||||
def case_yes(self):
|
||||
"""Write a log message, if the form is valid."""
|
||||
super(PollDeleteView, self).case_yes()
|
||||
self.object.write_log(ugettext_noop('Poll deleted'), self.request.user)
|
||||
|
||||
@ -327,12 +377,19 @@ poll_delete = PollDeleteView.as_view()
|
||||
|
||||
|
||||
class MotionSetStateView(SingleObjectMixin, RedirectView):
|
||||
"""View to set the state of a motion.
|
||||
|
||||
If self.reset is False, the new state is taken from url.
|
||||
|
||||
If self.reset is True, the default state is taken.
|
||||
"""
|
||||
permission_required = 'motion.can_manage_motion'
|
||||
url_name = 'motion_detail'
|
||||
model = Motion
|
||||
reset = False
|
||||
|
||||
def pre_redirect(self, request, *args, **kwargs):
|
||||
"""Save the new state and write a log message."""
|
||||
self.object = self.get_object()
|
||||
try:
|
||||
if self.reset:
|
||||
@ -350,6 +407,7 @@ class MotionSetStateView(SingleObjectMixin, RedirectView):
|
||||
% html_strong(self.object.state)))
|
||||
|
||||
def get_url_name_args(self):
|
||||
"""Return the arguments to generate the redirect_url."""
|
||||
return [self.object.pk]
|
||||
|
||||
set_state = MotionSetStateView.as_view()
|
||||
@ -357,15 +415,18 @@ reset_state = MotionSetStateView.as_view(reset=True)
|
||||
|
||||
|
||||
class CreateAgendaItemView(SingleObjectMixin, RedirectView):
|
||||
"""View to create and agenda item for a motion."""
|
||||
permission_required = 'agenda.can_manage_agenda'
|
||||
url_name = 'item_overview'
|
||||
model = Motion
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Set self.object to a motion."""
|
||||
self.object = self.get_object()
|
||||
return super(CreateAgendaItemView, self).get(request, *args, **kwargs)
|
||||
|
||||
def pre_redirect(self, request, *args, **kwargs):
|
||||
"""Create the agenda item."""
|
||||
self.item = Item.objects.create(related_sid=self.object.sid)
|
||||
self.object.write_log(ugettext_noop('Created Agenda Item'), self.request.user)
|
||||
|
||||
@ -373,23 +434,32 @@ create_agenda_item = CreateAgendaItemView.as_view()
|
||||
|
||||
|
||||
class MotionPDFView(SingleObjectMixin, PDFView):
|
||||
"""Create the PDF for one, or all motions.
|
||||
|
||||
If self.print_all_motions is True, the view returns a PDF with all motions.
|
||||
|
||||
If self.print_all_motions is False, the view returns a PDF with only one
|
||||
motion."""
|
||||
permission_required = 'motion.can_manage_motion'
|
||||
model = Motion
|
||||
top_space = 0
|
||||
print_all_motions = False
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Set self.object to a motion."""
|
||||
if not self.print_all_motions:
|
||||
self.object = self.get_object()
|
||||
return super(MotionPDFView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_filename(self):
|
||||
"""Return the filename for the PDF."""
|
||||
if self.print_all_motions:
|
||||
return _("Motions")
|
||||
else:
|
||||
return _("Motion: %s") % unicode(self.object)
|
||||
|
||||
def append_to_pdf(self, pdf):
|
||||
"""Append PDF objects."""
|
||||
if self.print_all_motions:
|
||||
motions_to_pdf(pdf)
|
||||
else:
|
||||
@ -400,6 +470,7 @@ motion_detail_pdf = MotionPDFView.as_view(print_all_motions=False)
|
||||
|
||||
|
||||
class Config(FormView):
|
||||
"""The View for the config tab."""
|
||||
permission_required = 'config.can_manage_config'
|
||||
form_class = ConfigForm
|
||||
template_name = 'motion/config.html'
|
||||
@ -431,9 +502,8 @@ class Config(FormView):
|
||||
|
||||
|
||||
def register_tab(request):
|
||||
"""
|
||||
Register the projector tab.
|
||||
"""
|
||||
"""Return the motion tab."""
|
||||
# TODO: Find a bether way to set the selected var.
|
||||
selected = request.path.startswith('/motion/')
|
||||
return Tab(
|
||||
title=_('Motions'),
|
||||
@ -445,6 +515,10 @@ def register_tab(request):
|
||||
|
||||
|
||||
def get_widgets(request):
|
||||
"""Return the motion widgets for the dashboard.
|
||||
|
||||
There is only one widget. It shows all motions.
|
||||
"""
|
||||
return [Widget(
|
||||
name='motions',
|
||||
display_name=_('Motions'),
|
||||
|
@ -1,16 +1,44 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
openslides.utils.workflow
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Defines the States for motions. All States are linked together with there
|
||||
'next_state' attributes. Together there are a workflow.
|
||||
|
||||
:copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import exceptions
|
||||
from django.utils.importlib import import_module
|
||||
from django.utils.translation import ugettext_noop,
|
||||
|
||||
from openslides.config.models import config
|
||||
ugettext = lambda s: s
|
||||
|
||||
_workflow = None
|
||||
|
||||
|
||||
class State(object):
|
||||
"""Define a state for a motion."""
|
||||
def __init__(self, id, name, next_states=[], create_poll=False, support=False,
|
||||
edit_as_submitter=False, version_permission=True):
|
||||
"""Set attributes for the state.
|
||||
|
||||
The Arguments are:
|
||||
- 'id' a unique id for the state.
|
||||
- 'name' a string representing the state.
|
||||
- 'next_states' a list with all states, that can be choosen from this state.
|
||||
|
||||
All the other arguments are boolean values. If True, the specific action for
|
||||
motions in this state.
|
||||
- 'create_poll': polls can be created in this state.
|
||||
- 'support': persons can support the motion in this state.
|
||||
- 'edit_as_submitter': the submitter can edit the motion in this state.
|
||||
- 'version_permission': new versions are not permitted.
|
||||
"""
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.next_states = next_states
|
||||
@ -20,19 +48,34 @@ class State(object):
|
||||
self.version_permission = version_permission
|
||||
|
||||
def __unicode__(self):
|
||||
"""Return the name of the state."""
|
||||
return self.name
|
||||
|
||||
|
||||
class WorkflowError(Exception):
|
||||
"""Exception raised when errors in a state accure."""
|
||||
pass
|
||||
|
||||
|
||||
def motion_workflow_choices():
|
||||
"""Return all possible workflows.
|
||||
|
||||
The possible workflows can be set in the settings with the setting
|
||||
'MOTION_WORKFLOW'.
|
||||
"""
|
||||
for workflow in settings.MOTION_WORKFLOW:
|
||||
yield workflow[0], workflow[1]
|
||||
|
||||
|
||||
def get_state(state='default'):
|
||||
"""Return a state object.
|
||||
|
||||
The argument 'state' has to be a state_id.
|
||||
|
||||
If the argument 'state' is 'default', the default state is returned.
|
||||
|
||||
The default state is the state object choosen in the config tab.
|
||||
"""
|
||||
global _workflow
|
||||
if _workflow is not None:
|
||||
try:
|
||||
@ -68,24 +111,34 @@ def get_state(state='default'):
|
||||
|
||||
|
||||
def populate_workflow(state, workflow):
|
||||
"""Append all 'next_states' from state to the workflow.
|
||||
|
||||
The argument state has to be a state object.
|
||||
|
||||
The argument workflow has to be a dictonary.
|
||||
|
||||
Calls this function recrusiv with all next_states from the next_states states.
|
||||
"""
|
||||
workflow[state.id] = state
|
||||
for s in state.next_states:
|
||||
if s.id not in workflow:
|
||||
populate_workflow(s, workflow)
|
||||
|
||||
|
||||
DUMMY_STATE = State('dummy', ugettext('Unknwon state'))
|
||||
DUMMY_STATE = State('dummy', ugettext_noop('Unknwon state'))
|
||||
"""A dummy state object. Returned, if the state_id is not known."""
|
||||
|
||||
default_workflow = State('pub', ugettext('Published'), support=True,
|
||||
default_workflow = State('pub', ugettext_noop('Published'), support=True,
|
||||
edit_as_submitter=True, version_permission=False)
|
||||
"""Default Workflow for OpenSlides."""
|
||||
|
||||
default_workflow.next_states = [
|
||||
State('per', ugettext('Permitted'), create_poll=True, edit_as_submitter=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)'))]
|
||||
State('per', ugettext_noop('Permitted'), create_poll=True, edit_as_submitter=True, next_states=[
|
||||
State('acc', ugettext_noop('Accepted')),
|
||||
State('rej', ugettext_noop('Rejected')),
|
||||
State('wit', ugettext_noop('Withdrawed')),
|
||||
State('adj', ugettext_noop('Adjourned')),
|
||||
State('noc', ugettext_noop('Not Concerned')),
|
||||
State('com', ugettext_noop('Commited a bill')),
|
||||
State('rev', ugettext_noop('Needs Review'))]),
|
||||
State('nop', ugettext_noop('Rejected (not authorized)'))]
|
||||
|
Loading…
Reference in New Issue
Block a user