Wrote docstrings to the motion app

This commit is contained in:
Oskar Hahn 2013-02-05 18:46:46 +01:00
parent a34731c00e
commit a08cf84ab8
9 changed files with 500 additions and 173 deletions

View File

@ -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.signals
import openslides.motion.slides import openslides.motion.slides

View File

@ -2,11 +2,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
openslides.motion.forms 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. :license: GNU GPL, see LICENSE for more details.
""" """
@ -20,14 +20,30 @@ from .workflow import motion_workflow_choices
class BaseMotionForm(forms.ModelForm, CssClassMixin): 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: class Meta:
model = Motion model = Motion
fields = () fields = ()
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Fill the FormFields releated to the version data with initial data."""
self.motion = kwargs.get('instance', None) self.motion = kwargs.get('instance', None)
self.initial = kwargs.setdefault('initial', {}) self.initial = kwargs.setdefault('initial', {})
if self.motion is not None: if self.motion is not None:
@ -36,16 +52,15 @@ class BaseMotionForm(forms.ModelForm, CssClassMixin):
self.initial['reason'] = self.motion.reason self.initial['reason'] = self.motion.reason
super(BaseMotionForm, self).__init__(*args, **kwargs) 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): class MotionSubmitterMixin(forms.ModelForm):
"""Mixin to append the submitter field to a MotionForm."""
submitter = MultiplePersonFormField(label=_("Submitter")) submitter = MultiplePersonFormField(label=_("Submitter"))
"""Submitter of the Motion. Can be one or more persons."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Fill in the submitter of the motion as default value."""
if self.motion is not None: if self.motion is not None:
submitter = [submitter.person.person_id for submitter in self.motion.submitter.all()] submitter = [submitter.person.person_id for submitter in self.motion.submitter.all()]
self.initial['submitter'] = submitter self.initial['submitter'] = submitter
@ -53,9 +68,13 @@ class MotionSubmitterMixin(forms.ModelForm):
class MotionSupporterMixin(forms.ModelForm): class MotionSupporterMixin(forms.ModelForm):
"""Mixin to append the supporter field to a Motionform."""
supporter = MultiplePersonFormField(required=False, label=_("Supporters")) supporter = MultiplePersonFormField(required=False, label=_("Supporters"))
"""Supporter of the Motion. Can be one or more persons."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Fill in the supporter of the motions as default value."""
if self.motion is not None: if self.motion is not None:
supporter = [supporter.person.person_id for supporter in self.motion.supporter.all()] supporter = [supporter.person.person_id for supporter in self.motion.supporter.all()]
self.initial['supporter'] = supporter self.initial['supporter'] = supporter
@ -63,18 +82,21 @@ class MotionSupporterMixin(forms.ModelForm):
class MotionCreateNewVersionMixin(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( new_version = forms.BooleanField(
required=False, label=_("Create new version"), initial=True, required=False, label=_("Create new version"), initial=True,
help_text=_("Trivial changes don't create a new version.")) 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( motion_min_supporters = forms.IntegerField(
widget=forms.TextInput(attrs={'class': 'small-input'}), widget=forms.TextInput(attrs={'class': 'small-input'}),
label=_("Number of (minimum) required supporters for a motion"), label=_("Number of (minimum) required supporters for a motion"),
initial=4, initial=4, min_value=0, max_value=8,
min_value=0,
max_value=8,
help_text=_("Choose 0 to disable the supporting system"), help_text=_("Choose 0 to disable the supporting system"),
) )
motion_preamble = forms.CharField( motion_preamble = forms.CharField(

View File

@ -6,7 +6,10 @@
Models for the motion app. 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. :license: GNU GPL, see LICENSE for more details.
""" """
@ -34,39 +37,38 @@ from .workflow import (motion_workflow_choices, get_state, State, WorkflowError,
DUMMY_STATE) 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): class Motion(SlideMixin, models.Model):
""" """The Motion Class.
The Motion-Model.
""" This class is the main entry point to all other classes related to a motion.
prefix = "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) 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, identifier = models.CharField(max_length=255, null=True, blank=True,
unique=True) unique=True)
category = models.ForeignKey('Category', null=True, blank=True) """A string as human readable identifier for the motion."""
# TODO proposal
# Maybe rename to master_copy # category = models.ForeignKey('Category', null=True, blank=True)
master = models.ForeignKey('self', null=True, blank=True) # TODO: proposal
#master = models.ForeignKey('self', null=True, blank=True)
class Meta: class Meta:
permissions = ( permissions = (
@ -79,17 +81,35 @@ class Motion(SlideMixin, models.Model):
# ordering = ('number',) # ordering = ('number',)
def __unicode__(self): def __unicode__(self):
"""Return a human readable name of this motion."""
return self.get_title() return self.get_title()
# TODO: Use transaction # TODO: Use transaction
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" """Save the motion.
Saves the motion. Create or update a motion_version object
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: if not self.state_id:
self.reset_state() self.reset_state()
super(Motion, self).save(*args, **kwargs) super(Motion, self).save(*args, **kwargs)
# Find out if the version data has changed # Find out if the version data has changed
for attr in ['title', 'text', 'reason']: for attr in ['title', 'text', 'reason']:
if not self.versions.exists(): if not self.versions.exists():
@ -109,7 +129,7 @@ class Motion(SlideMixin, models.Model):
elif new_data and not need_new_version: elif new_data and not need_new_version:
version = self.last_version version = self.last_version
else: else:
# We do not need to save the motion version # We do not need to save the motion version.
return return
# Save title, text and reason in the version object # Save title, text and reason in the version object
@ -137,6 +157,10 @@ class Motion(SlideMixin, models.Model):
self.save() self.save()
def get_absolute_url(self, link='detail'): 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': if link == 'view' or link == 'detail':
return reverse('motion_detail', args=[str(self.id)]) return reverse('motion_detail', args=[str(self.id)])
if link == 'edit': if link == 'edit':
@ -145,8 +169,9 @@ class Motion(SlideMixin, models.Model):
return reverse('motion_delete', args=[str(self.id)]) return reverse('motion_delete', args=[str(self.id)])
def get_title(self): def get_title(self):
""" """Get the title of the motion.
Get the title of the motion. The titel is taken from motion.version
The titel is taken from motion.version.
""" """
try: try:
return self._title return self._title
@ -154,16 +179,23 @@ class Motion(SlideMixin, models.Model):
return self.version.title return self.version.title
def set_title(self, title): def set_title(self, title):
""" """Set the titel of the motion.
Set the titel of the motion. The titel will me saved in motion.save()
The titel will me saved into the version object, wenn motion.save() is
called.
""" """
self._title = title self._title = title
title = property(get_title, set_title) title = property(get_title, set_title)
"""The title of the motion.
Is saved in a MotionVersion object.
"""
def get_text(self): def get_text(self):
""" """Get the text of the motion.
Get the text of the motion. Simular to get_title()
Simular to get_title().
""" """
try: try:
return self._text return self._text
@ -171,16 +203,22 @@ class Motion(SlideMixin, models.Model):
return self.version.text return self.version.text
def set_text(self, text): def set_text(self, text):
""" """ Set the text of the motion.
Set the text of the motion. Simular to set_title()
Simular to set_title().
""" """
self._text = text self._text = text
text = property(get_text, set_text) text = property(get_text, set_text)
"""The text of a motin.
Is saved in a MotionVersion object.
"""
def get_reason(self): def get_reason(self):
""" """Get the reason of the motion.
Get the reason of the motion. Simular to get_title()
Simular to get_title().
""" """
try: try:
return self._reason return self._reason
@ -188,20 +226,26 @@ class Motion(SlideMixin, models.Model):
return self.version.reason return self.version.reason
def set_reason(self, reason): def set_reason(self, reason):
""" """Set the reason of the motion.
Set the reason of the motion. Simular to set_title()
Simular to set_title().
""" """
self._reason = reason self._reason = reason
reason = property(get_reason, set_reason) reason = property(get_reason, set_reason)
"""The reason for the motion.
Is saved in a MotionVersion object.
"""
@property @property
def new_version(self): 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 On the first call, it creates a new version. On any later call, it
use the existing new version. 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: try:
return self._new_version return self._new_version
@ -210,9 +254,9 @@ class Motion(SlideMixin, models.Model):
return self._new_version return self._new_version
def get_version(self): def get_version(self):
""" """Get the 'active' version object.
Get the "active" version object. This version will be used to get the
data for this motion. This version will be used to get the data for this motion.
""" """
try: try:
return self._version return self._version
@ -220,12 +264,12 @@ class Motion(SlideMixin, models.Model):
return self.last_version return self.last_version
def set_version(self, 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. The keyargument 'version' can be a MotionVersion object or the
If version is a version object, this object will be used. version_number of a VersionObject or None.
If version is Int, the N version of this motion will be used.
If the argument is None, the newest version will be used.
""" """
if version is None: if version is None:
try: try:
@ -242,12 +286,11 @@ class Motion(SlideMixin, models.Model):
self._version = version self._version = version
version = property(get_version, set_version) version = property(get_version, set_version)
"""The active version of this motion."""
@property @property
def last_version(self): def last_version(self):
""" """Return the newest version of the motion."""
Get the newest version of the motion
"""
# TODO: Fix the case, that the motion has no Version # TODO: Fix the case, that the motion has no Version
try: try:
return self.versions.order_by('-version_number')[0] return self.versions.order_by('-version_number')[0]
@ -255,15 +298,15 @@ class Motion(SlideMixin, models.Model):
return self.new_version return self.new_version
def is_submitter(self, person): def is_submitter(self, person):
"""Return True, if person is a submitter of this motion. Else: False."""
self.submitter.filter(person=person).exists() self.submitter.filter(person=person).exists()
def is_supporter(self, person): def is_supporter(self, person):
"""Return True, if person is a supporter of this motion. Else: False."""
return self.supporter.filter(person=person).exists() return self.supporter.filter(person=person).exists()
def support(self, person): def support(self, person):
""" """Add 'person' as a supporter of this motion."""
Add a Supporter to the list of supporters of the motion.
"""
if self.state.support: if self.state.support:
if not self.is_supporter(person): if not self.is_supporter(person):
MotionSupporter(motion=self, person=person).save() 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) raise WorkflowError("You can not support a motion in state %s" % self.state.name)
def unsupport(self, person): def unsupport(self, person):
""" """Remove 'person' as supporter from this motion."""
Remove a supporter from the list of supporters of the motion
"""
if self.state.support: if self.state.support:
self.supporter.filter(person=person).delete() self.supporter.filter(person=person).delete()
else: else:
raise WorkflowError("You can not unsupport a motion in state %s" % self.state.name) raise WorkflowError("You can not unsupport a motion in state %s" % self.state.name)
def create_poll(self): 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: if self.state.create_poll:
# TODO: auto increment the poll_number in the Database # 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) raise WorkflowError("You can not create a poll in state %s" % self.state.name)
def get_state(self): def get_state(self):
""" """Return the state of the motion.
Get the state of this motion. Return a State object.
State is a State object. See openslides.motion.workflow for more informations.
""" """
try: try:
return get_state(self.state_id) return get_state(self.state_id)
@ -302,10 +345,10 @@ class Motion(SlideMixin, models.Model):
return DUMMY_STATE return DUMMY_STATE
def set_state(self, next_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): if not isinstance(next_state, State):
next_state = get_state(next_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) raise WorkflowError('%s is not a valid next_state' % next_state)
state = property(get_state, set_state) state = property(get_state, set_state)
"""The state of the motion as Ste object."""
def reset_state(self): 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 self.state_id = get_state('default').id
def slide(self): def slide(self):
""" """Return the slide dict."""
return the slide dict
"""
data = super(Motion, self).slide() data = super(Motion, self).slide()
data['motion'] = self data['motion'] = self
data['title'] = self.title data['title'] = self.title
@ -333,6 +373,7 @@ class Motion(SlideMixin, models.Model):
return data return data
def get_agenda_title(self): def get_agenda_title(self):
"""Return a title for the Agenda."""
return self.last_version.title return self.last_version.title
## def get_agenda_title_supplement(self): ## def get_agenda_title_supplement(self):
@ -340,8 +381,7 @@ class Motion(SlideMixin, models.Model):
## return '(%s %s)' % (ugettext('motion'), number) ## return '(%s %s)' % (ugettext('motion'), number)
def get_allowed_actions(self, person): def get_allowed_actions(self, person):
""" """Return a dictonary with all allowed actions for a specific person.
Gets a dictonary with all allowed actions for a specific person.
The dictonary contains the following actions. The dictonary contains the following actions.
@ -374,11 +414,16 @@ class Motion(SlideMixin, models.Model):
return actions return actions
def write_log(self, message, person=None): 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) MotionLog.objects.create(motion=self, message=message, person=person)
def activate_version(self, version): def activate_version(self, version):
""" """Set the active state of a version to True.
Activate a version of this motion.
'version' can be a version object, or the version_number of a version. 'version' can be a version object, or the version_number of a version.
""" """
@ -391,8 +436,7 @@ class Motion(SlideMixin, models.Model):
version.save() version.save()
def reject_version(self, version): 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. '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): class MotionVersion(models.Model):
version_number = models.PositiveIntegerField(default=1) """
title = models.CharField(max_length=255, verbose_name=ugettext_lazy("Title")) A MotionVersion object saves some date of the motion."""
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)
motion = models.ForeignKey(Motion, related_name='versions') motion = models.ForeignKey(Motion, related_name='versions')
identifier = models.CharField(max_length=255, verbose_name=ugettext_lazy("Version identifier")) """The Motion, to witch the version belongs."""
note = models.TextField(null=True, blank=True)
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: class Meta:
unique_together = ("motion", "version_number") unique_together = ("motion", "version_number")
def __unicode__(self): def __unicode__(self):
"""Return a string, representing this object."""
counter = self.version_number or _('new') counter = self.version_number or _('new')
return "%s Version %s" % (self.motion, counter) return "%s Version %s" % (self.motion, counter)
def get_absolute_url(self, link='detail'): 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': if link == 'view' or link == 'detail':
return reverse('motion_version_detail', args=[str(self.motion.id), return reverse('motion_version_detail', args=[str(self.motion.id),
str(self.version_number)]) str(self.version_number)])
@property @property
def active(self): def active(self):
"""Return True, if the version is the active version of a motion. Else: False."""
return self.active_version.exists() return self.active_version.exists()
class Category(models.Model): class MotionSubmitter(models.Model):
name = models.CharField(max_length=255, verbose_name=ugettext_lazy("Category name")) """Save the submitter of a Motion."""
prefix = models.CharField(max_length=32, verbose_name=ugettext_lazy("Category prefix"))
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): def __unicode__(self):
return self.name """Return the name of the submitter as string."""
return unicode(self.person)
class Comment(models.Model): class MotionSupporter(models.Model):
motion_version = models.ForeignKey(MotionVersion) """Save the submitter of a Motion."""
text = models.TextField()
author = PersonField() motion = models.ForeignKey('Motion', related_name="supporter")
creation_time = models.DateTimeField(auto_now=True) """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): class MotionLog(models.Model):
"""Save a logmessage for a motion."""
motion = models.ForeignKey(Motion, related_name='log_messages') motion = models.ForeignKey(Motion, related_name='log_messages')
"""The motion to witch the object belongs."""
message = models.CharField(max_length=255) message = models.CharField(max_length=255)
"""The log message.
Should be in english.
"""
person = PersonField(null=True) person = PersonField(null=True)
"""A person object, who created the log message. Optional."""
time = models.DateTimeField(auto_now=True) time = models.DateTimeField(auto_now=True)
"""The Time, when the loged action was performed."""
class Meta: class Meta:
ordering = ['-time'] ordering = ['-time']
def __unicode__(self): def __unicode__(self):
"""Return a string, representing the log message."""
# TODO: write time in the local time format. # TODO: write time in the local time format.
if self.person is None: if self.person is None:
return "%s %s" % (self.time, _(self.message)) return "%s %s" % (self.time, _(self.message))
@ -467,33 +579,63 @@ class MotionLog(models.Model):
class MotionError(Exception): class MotionError(Exception):
"""Exception raised when errors in the motion accure."""
pass pass
class MotionVote(BaseVote): 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') option = models.ForeignKey('MotionOption')
"""The option object, to witch the vote belongs."""
class MotionOption(BaseOption): class MotionOption(BaseOption):
"""Links between the MotionPollClass and the MotionVoteClass.
There should be one MotionOption object for each poll."""
poll = models.ForeignKey('MotionPoll') poll = models.ForeignKey('MotionPoll')
"""The poll object, to witch the object belongs."""
vote_class = MotionVote vote_class = MotionVote
"""The VoteClass, to witch this Class links."""
class MotionPoll(CountInvalid, CountVotesCast, BasePoll): class MotionPoll(CountInvalid, CountVotesCast, BasePoll):
option_class = MotionOption """The Class to saves the poll results for a motion poll."""
vote_values = [
ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
motion = models.ForeignKey(Motion, related_name='polls') 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) poll_number = models.PositiveIntegerField(default=1)
"""An id for this poll in realation to a motion.
Is unique for each motion.
"""
class Meta: class Meta:
unique_together = ("motion", "poll_number") unique_together = ("motion", "poll_number")
def __unicode__(self): def __unicode__(self):
"""Return a string, representing the poll."""
return _('Ballot %d') % self.poll_number return _('Ballot %d') % self.poll_number
def get_absolute_url(self, link='edit'): def get_absolute_url(self, link='edit'):
"""Return an URL for the poll.
The keyargument 'link' can be 'edit' or 'delete'.
"""
if link == 'edit': if link == 'edit':
return reverse('motion_poll_edit', args=[str(self.motion.pk), return reverse('motion_poll_edit', args=[str(self.motion.pk),
str(self.poll_number)]) str(self.poll_number)])
@ -501,16 +643,13 @@ class MotionPoll(CountInvalid, CountVotesCast, BasePoll):
return reverse('motion_poll_delete', args=[str(self.motion.pk), return reverse('motion_poll_delete', args=[str(self.motion.pk),
str(self.poll_number)]) str(self.poll_number)])
def get_motion(self):
return self.motion
def set_options(self): def set_options(self):
"""Create the option class for this poll."""
#TODO: maybe it is possible with .create() to call this without poll=self #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() self.get_option_class()(poll=self).save()
def append_pollform_fields(self, fields): def append_pollform_fields(self, fields):
"""Apend the fields for invalid and votecast to the ModelForm."""
CountInvalid.append_pollform_fields(self, fields) CountInvalid.append_pollform_fields(self, fields)
CountVotesCast.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()

View File

@ -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 import colors
from reportlab.lib.units import cm from reportlab.lib.units import cm
from reportlab.platypus import ( from reportlab.platypus import (
@ -11,6 +20,8 @@ from .models import Motion
def motions_to_pdf(pdf): def motions_to_pdf(pdf):
"""Create a PDF with all motions."""
motions = Motion.objects.all() motions = Motion.objects.all()
all_motion_cover(pdf, motions) all_motion_cover(pdf, motions)
for motion in motions: for motion in motions:
@ -19,6 +30,8 @@ def motions_to_pdf(pdf):
def motion_to_pdf(pdf, motion): def motion_to_pdf(pdf, motion):
"""Create a PDF for one motion."""
pdf.append(Paragraph(_("Motion: %s") % motion.title, stylesheet['Heading1'])) pdf.append(Paragraph(_("Motion: %s") % motion.title, stylesheet['Heading1']))
motion_data = [] motion_data = []
@ -141,6 +154,7 @@ def motion_to_pdf(pdf, motion):
def all_motion_cover(pdf, motions): def all_motion_cover(pdf, motions):
"""Create a coverpage for all motions."""
pdf.append(Paragraph(config["motion_pdf_title"], stylesheet['Heading1'])) pdf.append(Paragraph(config["motion_pdf_title"], stylesheet['Heading1']))
preamble = config["motion_pdf_preamble"] preamble = config["motion_pdf_preamble"]

View File

@ -6,7 +6,7 @@
Signals for the motion app. 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. :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") @receiver(default_config_value, dispatch_uid="motion_default_config")
def default_config(sender, key, **kwargs): def default_config(sender, key, **kwargs):
"""Return the default config values for the motion app."""
return { return {
'motion_min_supporters': 0, 'motion_min_supporters': 0,
'motion_preamble': _('The assembly may decide,'), 'motion_preamble': _('The assembly may decide,'),

View File

@ -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 openslides.projector.api import register_slidemodel
from .models import Motion from .models import Motion

View File

@ -2,11 +2,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
openslides.motion.urls 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. :license: GNU GPL, see LICENSE for more details.
""" """

View File

@ -6,10 +6,11 @@
Views for the motion app. 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. :license: GNU GPL, see LICENSE for more details.
""" """
from reportlab.platypus import Paragraph
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib import messages from django.contrib import messages
@ -39,9 +40,7 @@ from .pdf import motions_to_pdf, motion_to_pdf
class MotionListView(ListView): class MotionListView(ListView):
""" """View, to list all motions."""
List all motion.
"""
permission_required = 'motion.can_see_motion' permission_required = 'motion.can_see_motion'
model = Motion model = Motion
@ -49,7 +48,11 @@ motion_list = MotionListView.as_view()
class GetVersionMixin(object): class GetVersionMixin(object):
"""Mixin to set a specific version to a motion."""
def get_object(self): 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() object = super(GetVersionMixin, self).get_object()
version_number = self.kwargs.get('version_number', None) version_number = self.kwargs.get('version_number', None)
if version_number is not None: if version_number is not None:
@ -61,14 +64,15 @@ class GetVersionMixin(object):
class MotionDetailView(GetVersionMixin, DetailView): class MotionDetailView(GetVersionMixin, DetailView):
""" """Show one motion."""
Show the details of one motion.
"""
permission_required = 'motion.can_see_motion' permission_required = 'motion.can_see_motion'
model = Motion model = Motion
template_name = 'motion/motion_detail.html'
def get_context_data(self, **kwargs): 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 = super(MotionDetailView, self).get_context_data(**kwargs)
context['allowed_actions'] = self.object.get_allowed_actions(self.request.user) context['allowed_actions'] = self.object.get_allowed_actions(self.request.user)
return context return context
@ -77,10 +81,12 @@ motion_detail = MotionDetailView.as_view()
class MotionMixin(object): class MotionMixin(object):
""" """Mixin for MotionViewsClasses, to save the version data."""
Mixin to add save the version-data to the motion-object
"""
def manipulate_object(self, form): 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) super(MotionMixin, self).manipulate_object(form)
for attr in ['title', 'text', 'reason']: for attr in ['title', 'text', 'reason']:
setattr(self.object, attr, form.cleaned_data[attr]) setattr(self.object, attr, form.cleaned_data[attr])
@ -92,6 +98,7 @@ class MotionMixin(object):
pass pass
def post_save(self, form): def post_save(self, form):
"""Save the submitter an the supporter so the motion."""
super(MotionMixin, self).post_save(form) super(MotionMixin, self).post_save(form)
# TODO: only delete and save neccessary submitters and supporter # TODO: only delete and save neccessary submitters and supporter
if 'submitter' in form.cleaned_data: if 'submitter' in form.cleaned_data:
@ -106,6 +113,13 @@ class MotionMixin(object):
for person in form.cleaned_data['supporter']]) for person in form.cleaned_data['supporter']])
def get_form_class(self): 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] form_classes = [BaseMotionForm]
if self.request.user.has_perm('motion.can_manage_motion'): if self.request.user.has_perm('motion.can_manage_motion'):
form_classes.append(MotionSubmitterMixin) form_classes.append(MotionSubmitterMixin)
@ -117,13 +131,12 @@ class MotionMixin(object):
class MotionCreateView(MotionMixin, CreateView): class MotionCreateView(MotionMixin, CreateView):
""" """View to create a motion."""
Create a motion.
"""
permission_required = 'motion.can_create_motion' permission_required = 'motion.can_create_motion'
model = Motion model = Motion
def form_valid(self, form): def form_valid(self, form):
"""Write a log message, if the form is valid."""
value = super(MotionCreateView, self).form_valid(form) value = super(MotionCreateView, self).form_valid(form)
self.object.write_log(ugettext_noop('Motion created'), self.request.user) self.object.write_log(ugettext_noop('Motion created'), self.request.user)
return value return value
@ -132,15 +145,15 @@ motion_create = MotionCreateView.as_view()
class MotionUpdateView(MotionMixin, UpdateView): class MotionUpdateView(MotionMixin, UpdateView):
""" """View to update a motion."""
Update a motion.
"""
model = Motion model = Motion
def has_permission(self, request, *args, **kwargs): 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'] return self.get_object().get_allowed_actions(request.user)['edit']
def form_valid(self, form): def form_valid(self, form):
"""Write a log message, if the form is valid."""
value = super(MotionUpdateView, self).form_valid(form) value = super(MotionUpdateView, self).form_valid(form)
self.object.write_log(ugettext_noop('Motion updated'), self.request.user) self.object.write_log(ugettext_noop('Motion updated'), self.request.user)
return value return value
@ -149,34 +162,39 @@ motion_edit = MotionUpdateView.as_view()
class MotionDeleteView(DeleteView): class MotionDeleteView(DeleteView):
""" """View to delete a motion."""
Delete one Motion.
"""
model = Motion model = Motion
success_url_name = 'motion_list' success_url_name = 'motion_list'
def has_permission(self, request, *args, **kwargs): 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'] return self.get_object().get_allowed_actions(request.user)['delete']
motion_delete = MotionDeleteView.as_view() motion_delete = MotionDeleteView.as_view()
class VersionPermitView(GetVersionMixin, SingleObjectMixin, QuestionMixin, RedirectView): class VersionPermitView(GetVersionMixin, SingleObjectMixin, QuestionMixin, RedirectView):
"""View to permit a version of a motion."""
model = Motion model = Motion
question_url_name = 'motion_version_detail' question_url_name = 'motion_version_detail'
success_url_name = 'motion_version_detail' success_url_name = 'motion_version_detail'
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
"""Set self.object to a motion."""
self.object = self.get_object() self.object = self.get_object()
return super(VersionPermitView, self).get(*args, **kwargs) return super(VersionPermitView, self).get(*args, **kwargs)
def get_url_name_args(self): 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] return [self.object.pk, self.object.version.version_number]
def get_question(self): 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 return _('Are you sure you want permit Version %s?') % self.object.version.version_number
def case_yes(self): def case_yes(self):
"""Activate the version, if the user chooses 'yes'."""
self.object.activate_version(self.object.version) self.object.activate_version(self.object.version)
self.object.save() self.object.save()
@ -184,21 +202,25 @@ version_permit = VersionPermitView.as_view()
class VersionRejectView(GetVersionMixin, SingleObjectMixin, QuestionMixin, RedirectView): class VersionRejectView(GetVersionMixin, SingleObjectMixin, QuestionMixin, RedirectView):
"""View to reject a version."""
model = Motion model = Motion
question_url_name = 'motion_version_detail' question_url_name = 'motion_version_detail'
success_url_name = 'motion_version_detail' success_url_name = 'motion_version_detail'
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
"""Set self.object to a motion."""
self.object = self.get_object() self.object = self.get_object()
return super(VersionRejectView, self).get(*args, **kwargs) return super(VersionRejectView, self).get(*args, **kwargs)
def get_url_name_args(self): 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] return [self.object.pk, self.object.version.version_number]
def get_question(self): def get_question(self):
return _('Are you sure you want reject Version %s?') % self.object.version.version_number return _('Are you sure you want reject Version %s?') % self.object.version.version_number
def case_yes(self): def case_yes(self):
"""Reject the version, if the user chooses 'yes'."""
self.object.reject_version(self.object.version) self.object.reject_version(self.object.version)
self.object.save() self.object.save()
@ -206,23 +228,25 @@ version_reject = VersionRejectView.as_view()
class SupportView(SingleObjectMixin, QuestionMixin, RedirectView): 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' permission_required = 'motion.can_support_motion'
model = Motion model = Motion
support = True support = True
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Set self.object to a motion."""
self.object = self.get_object() self.object = self.get_object()
return super(SupportView, self).get(request, *args, **kwargs) return super(SupportView, self).get(request, *args, **kwargs)
def check_permission(self, request): def check_permission(self, request):
""" """Return True if the user can support or unsupport the motion. Else: False."""
Checks whether request.user can support or unsupport the motion.
Returns True or False.
"""
allowed_actions = self.object.get_allowed_actions(request.user) allowed_actions = self.object.get_allowed_actions(request.user)
if self.support and not allowed_actions['support']: if self.support and not allowed_actions['support']:
messages.error(request, _('You can not support this motion.')) messages.error(request, _('You can not support this motion.'))
@ -233,17 +257,19 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
else: else:
return True 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): def get_question(self):
"""Return the question string."""
if self.support: if self.support:
return _('Do you really want to support this motion?') return _('Do you really want to support this motion?')
else: else:
return _('Do you really want to unsupport this motion?') return _('Do you really want to unsupport this motion?')
def case_yes(self): 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): if self.check_permission(self.request):
user = self.request.user user = self.request.user
if self.support: if self.support:
@ -254,12 +280,14 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
self.object.write_log(ugettext_noop("Supporter: -%s") % user, user) self.object.write_log(ugettext_noop("Supporter: -%s") % user, user)
def get_success_message(self): def get_success_message(self):
"""Return the success message."""
if self.support: if self.support:
return _("You have supported this motion successfully.") return _("You have supported this motion successfully.")
else: else:
return _("You have unsupported this motion successfully.") return _("You have unsupported this motion successfully.")
def get_redirect_url(self, **kwargs): def get_redirect_url(self, **kwargs):
"""Return the url, the view should redirect to."""
return self.object.get_absolute_url() return self.object.get_absolute_url()
motion_support = SupportView.as_view(support=True) motion_support = SupportView.as_view(support=True)
@ -267,48 +295,68 @@ motion_unsupport = SupportView.as_view(support=False)
class PollCreateView(SingleObjectMixin, RedirectView): class PollCreateView(SingleObjectMixin, RedirectView):
"""View to create a poll for a motion."""
permission_required = 'motion.can_manage_motion' permission_required = 'motion.can_manage_motion'
model = Motion model = Motion
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Set self.object to a motion."""
self.object = self.get_object() self.object = self.get_object()
return super(PollCreateView, self).get(request, *args, **kwargs) return super(PollCreateView, self).get(request, *args, **kwargs)
def pre_redirect(self, request, *args, **kwargs): def pre_redirect(self, request, *args, **kwargs):
"""Create the poll for the motion."""
self.poll = self.object.create_poll() self.poll = self.object.create_poll()
self.object.write_log(ugettext_noop("Poll created"), request.user) self.object.write_log(ugettext_noop("Poll created"), request.user)
messages.success(request, _("New vote was successfully created.")) messages.success(request, _("New vote was successfully created."))
def get_redirect_url(self, **kwargs): 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]) return reverse('motion_poll_edit', args=[self.object.pk, self.poll.poll_number])
poll_create = PollCreateView.as_view() poll_create = PollCreateView.as_view()
class PollMixin(object): class PollMixin(object):
"""Mixin for the PollUpdateView and the PollDeleteView."""
permission_required = 'motion.can_manage_motion' permission_required = 'motion.can_manage_motion'
success_url_name = 'motion_detail' success_url_name = 'motion_detail'
def get_object(self): 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( return MotionPoll.objects.filter(
motion=self.kwargs['pk'], motion=self.kwargs['pk'],
poll_number=self.kwargs['poll_number']).get() poll_number=self.kwargs['poll_number']).get()
def get_url_name_args(self): def get_url_name_args(self):
"""Return the arguments to create the url to the success_url"""
return [self.object.motion.pk] return [self.object.motion.pk]
class PollUpdateView(PollMixin, PollFormView): class PollUpdateView(PollMixin, PollFormView):
"""View to update a MotionPoll."""
poll_class = MotionPoll poll_class = MotionPoll
"""Poll Class to use for this view."""
template_name = 'motion/poll_form.html' template_name = 'motion/poll_form.html'
def get_context_data(self, **kwargs): 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 = super(PollUpdateView, self).get_context_data(**kwargs)
context.update({ context.update({
'motion': self.poll.motion}) 'motion': self.poll.motion})
return context return context
def form_valid(self, form): def form_valid(self, form):
"""Write a log message, if the form is valid."""
value = super(PollUpdateView, self).form_valid(form) value = super(PollUpdateView, self).form_valid(form)
self.object.write_log(ugettext_noop('Poll updated'), self.request.user) self.object.write_log(ugettext_noop('Poll updated'), self.request.user)
return value return value
@ -317,9 +365,11 @@ poll_edit = PollUpdateView.as_view()
class PollDeleteView(PollMixin, DeleteView): class PollDeleteView(PollMixin, DeleteView):
"""View to delete a MotionPoll."""
model = MotionPoll model = MotionPoll
def case_yes(self): def case_yes(self):
"""Write a log message, if the form is valid."""
super(PollDeleteView, self).case_yes() super(PollDeleteView, self).case_yes()
self.object.write_log(ugettext_noop('Poll deleted'), self.request.user) self.object.write_log(ugettext_noop('Poll deleted'), self.request.user)
@ -327,12 +377,19 @@ poll_delete = PollDeleteView.as_view()
class MotionSetStateView(SingleObjectMixin, RedirectView): 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' permission_required = 'motion.can_manage_motion'
url_name = 'motion_detail' url_name = 'motion_detail'
model = Motion model = Motion
reset = False reset = False
def pre_redirect(self, request, *args, **kwargs): def pre_redirect(self, request, *args, **kwargs):
"""Save the new state and write a log message."""
self.object = self.get_object() self.object = self.get_object()
try: try:
if self.reset: if self.reset:
@ -350,6 +407,7 @@ class MotionSetStateView(SingleObjectMixin, RedirectView):
% html_strong(self.object.state))) % html_strong(self.object.state)))
def get_url_name_args(self): def get_url_name_args(self):
"""Return the arguments to generate the redirect_url."""
return [self.object.pk] return [self.object.pk]
set_state = MotionSetStateView.as_view() set_state = MotionSetStateView.as_view()
@ -357,15 +415,18 @@ reset_state = MotionSetStateView.as_view(reset=True)
class CreateAgendaItemView(SingleObjectMixin, RedirectView): class CreateAgendaItemView(SingleObjectMixin, RedirectView):
"""View to create and agenda item for a motion."""
permission_required = 'agenda.can_manage_agenda' permission_required = 'agenda.can_manage_agenda'
url_name = 'item_overview' url_name = 'item_overview'
model = Motion model = Motion
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Set self.object to a motion."""
self.object = self.get_object() self.object = self.get_object()
return super(CreateAgendaItemView, self).get(request, *args, **kwargs) return super(CreateAgendaItemView, self).get(request, *args, **kwargs)
def pre_redirect(self, 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.item = Item.objects.create(related_sid=self.object.sid)
self.object.write_log(ugettext_noop('Created Agenda Item'), self.request.user) 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): 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' permission_required = 'motion.can_manage_motion'
model = Motion model = Motion
top_space = 0 top_space = 0
print_all_motions = False print_all_motions = False
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Set self.object to a motion."""
if not self.print_all_motions: if not self.print_all_motions:
self.object = self.get_object() self.object = self.get_object()
return super(MotionPDFView, self).get(request, *args, **kwargs) return super(MotionPDFView, self).get(request, *args, **kwargs)
def get_filename(self): def get_filename(self):
"""Return the filename for the PDF."""
if self.print_all_motions: if self.print_all_motions:
return _("Motions") return _("Motions")
else: else:
return _("Motion: %s") % unicode(self.object) return _("Motion: %s") % unicode(self.object)
def append_to_pdf(self, pdf): def append_to_pdf(self, pdf):
"""Append PDF objects."""
if self.print_all_motions: if self.print_all_motions:
motions_to_pdf(pdf) motions_to_pdf(pdf)
else: else:
@ -400,6 +470,7 @@ motion_detail_pdf = MotionPDFView.as_view(print_all_motions=False)
class Config(FormView): class Config(FormView):
"""The View for the config tab."""
permission_required = 'config.can_manage_config' permission_required = 'config.can_manage_config'
form_class = ConfigForm form_class = ConfigForm
template_name = 'motion/config.html' template_name = 'motion/config.html'
@ -431,9 +502,8 @@ class Config(FormView):
def register_tab(request): def register_tab(request):
""" """Return the motion tab."""
Register the projector tab. # TODO: Find a bether way to set the selected var.
"""
selected = request.path.startswith('/motion/') selected = request.path.startswith('/motion/')
return Tab( return Tab(
title=_('Motions'), title=_('Motions'),
@ -445,6 +515,10 @@ def register_tab(request):
def get_widgets(request): def get_widgets(request):
"""Return the motion widgets for the dashboard.
There is only one widget. It shows all motions.
"""
return [Widget( return [Widget(
name='motions', name='motions',
display_name=_('Motions'), display_name=_('Motions'),

View File

@ -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.conf import settings
from django.core import exceptions from django.core import exceptions
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.utils.translation import ugettext_noop,
from openslides.config.models import config from openslides.config.models import config
ugettext = lambda s: s
_workflow = None _workflow = None
class State(object): class State(object):
"""Define a state for a motion."""
def __init__(self, id, name, next_states=[], create_poll=False, support=False, def __init__(self, id, name, next_states=[], create_poll=False, support=False,
edit_as_submitter=False, version_permission=True): 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.id = id
self.name = name self.name = name
self.next_states = next_states self.next_states = next_states
@ -20,19 +48,34 @@ class State(object):
self.version_permission = version_permission self.version_permission = version_permission
def __unicode__(self): def __unicode__(self):
"""Return the name of the state."""
return self.name return self.name
class WorkflowError(Exception): class WorkflowError(Exception):
"""Exception raised when errors in a state accure."""
pass pass
def motion_workflow_choices(): 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: for workflow in settings.MOTION_WORKFLOW:
yield workflow[0], workflow[1] yield workflow[0], workflow[1]
def get_state(state='default'): 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 global _workflow
if _workflow is not None: if _workflow is not None:
try: try:
@ -68,24 +111,34 @@ def get_state(state='default'):
def populate_workflow(state, workflow): 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 workflow[state.id] = state
for s in state.next_states: for s in state.next_states:
if s.id not in workflow: if s.id not in workflow:
populate_workflow(s, 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) edit_as_submitter=True, version_permission=False)
"""Default Workflow for OpenSlides."""
default_workflow.next_states = [ default_workflow.next_states = [
State('per', ugettext('Permitted'), create_poll=True, edit_as_submitter=True, next_states=[ State('per', ugettext_noop('Permitted'), create_poll=True, edit_as_submitter=True, next_states=[
State('acc', ugettext('Accepted')), State('acc', ugettext_noop('Accepted')),
State('rej', ugettext('Rejected')), State('rej', ugettext_noop('Rejected')),
State('wit', ugettext('Withdrawed')), State('wit', ugettext_noop('Withdrawed')),
State('adj', ugettext('Adjourned')), State('adj', ugettext_noop('Adjourned')),
State('noc', ugettext('Not Concerned')), State('noc', ugettext_noop('Not Concerned')),
State('com', ugettext('Commited a bill')), State('com', ugettext_noop('Commited a bill')),
State('rev', ugettext('Needs Review'))]), State('rev', ugettext_noop('Needs Review'))]),
State('nop', ugettext('Rejected (not authorized)'))] State('nop', ugettext_noop('Rejected (not authorized)'))]