diff --git a/openslides/motion/models.py b/openslides/motion/models.py
index 723ff4648..8796d862a 100644
--- a/openslides/motion/models.py
+++ b/openslides/motion/models.py
@@ -57,10 +57,8 @@ class Motion(SlideMixin, models.Model):
"""
prefix = "motion"
- # TODO: Use this attribute for the default_version, if the permission system
- # is deactivated. Maybe it has to be renamed.
- permitted_version = models.ForeignKey(
- 'MotionVersion', null=True, blank=True, related_name="permitted")
+ active_version = models.ForeignKey(
+ 'MotionVersion', null=True, related_name="active_version")
state_id = models.CharField(max_length=3)
# Log (Translatable)
identifier = models.CharField(max_length=255, null=True, blank=True,
@@ -110,6 +108,7 @@ class Motion(SlideMixin, models.Model):
else:
# We do not need to save the motion version
return
+
for attr in ['title', 'text', 'reason']:
_attr = '_%s' % attr
try:
@@ -118,8 +117,17 @@ class Motion(SlideMixin, models.Model):
except AttributeError:
# If the _attr was not set, use the value from last_version
setattr(version, attr, getattr(self.last_version, attr))
+
+ if version.id is None:
+ # TODO: auto increment the version_number in the Database
+ version_number = self.versions.aggregate(Max('version_number'))['version_number__max'] or 0
+ version.version_number = version_number + 1
version.save()
+ if not self.state.version_permission or self.active_version is None:
+ self.active_version = version
+ self.save()
+
def get_absolute_url(self, link='detail'):
if link == 'view' or link == 'detail':
return reverse('motion_detail', args=[str(self.id)])
@@ -218,7 +226,7 @@ class Motion(SlideMixin, models.Model):
pass
else:
if type(version) is int:
- version = self.versions.all()[version]
+ version = self.versions.get(version_number=version)
elif type(version) is not MotionVersion:
raise ValueError('The argument \'version\' has to be int or '
'MotionVersion, not %s' % type(version))
@@ -234,7 +242,7 @@ class Motion(SlideMixin, models.Model):
"""
# TODO: Fix the case, that the motion has no Version
try:
- return self.versions.order_by('id').reverse()[0]
+ return self.versions.order_by('-version_number')[0]
except IndexError:
return self.new_version
@@ -360,8 +368,38 @@ class Motion(SlideMixin, models.Model):
def write_log(self, message, person=None):
MotionLog.objects.create(motion=self, message=message, person=person)
+ def activate_version(self, version):
+ """
+ Activate a version of this motion.
+
+ 'version' can be a version object, or the version_number of a version.
+ """
+ if type(version) is int:
+ version = self.versions.get(version_number=version)
+ self.active_version = version
+
+ if version.rejected:
+ version.rejected = False
+ version.save()
+
+ def reject_version(self, version):
+ """
+ Reject a version of this motion.
+
+ 'version' can be a version object, or the version_number of a version.
+ """
+ if type(version) is int:
+ version = self.versions.get(version_number=version)
+
+ if version.active:
+ raise MotionError('The active version can not be rejected')
+
+ version.rejected = True
+ version.save()
+
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"))
@@ -371,8 +409,12 @@ class MotionVersion(models.Model):
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 "%s Version %s" % (self.motion, self.version_number)
+ counter = self.version_number or _('new')
+ return "%s Version %s" % (self.motion, counter)
def get_absolute_url(self, link='detail'):
if link == 'view' or link == 'detail':
@@ -380,11 +422,8 @@ class MotionVersion(models.Model):
str(self.version_number)])
@property
- def version_number(self):
- if self.pk is None:
- return 'new'
- return (MotionVersion.objects.filter(motion=self.motion)
- .filter(id__lte=self.pk).count())
+ def active(self):
+ return self.active_version.exists()
class Category(models.Model):
@@ -418,6 +457,9 @@ class MotionLog(models.Model):
else:
return "%s %s by %s" % (self.time, _(self.message), self.person)
+class MotionError(Exception):
+ pass
+
class MotionVote(BaseVote):
option = models.ForeignKey('MotionOption')
diff --git a/openslides/motion/templates/motion/motion_detail.html b/openslides/motion/templates/motion/motion_detail.html
index d671b472f..dcafd3878 100644
--- a/openslides/motion/templates/motion/motion_detail.html
+++ b/openslides/motion/templates/motion/motion_detail.html
@@ -12,6 +12,7 @@
Reason: {{ motion.reason }}
Submitter: {% for submitter in motion.submitter.all %}{{ submitter.person }} {% endfor %}
Supporter: {% for supporter in motion.supporter.all %}{{ supporter.person }} {% endfor %}
+Active Version: {{ motion.active_version }}
State: {{ motion.state }}
possible stats:
@@ -25,11 +26,19 @@
Versions
{% for motion_version in motion.versions.all %}
+ -
{% if motion_version.id == motion.version.id %}
-
- {{ motion_version }}
+ {{ motion_version }}
{% else %}
- - {{ motion_version }}
+ {{ motion_version }}
{% endif %}
+ {% if motion_version.active %}
+ (active)
+ {% endif %}
+ {% if motion_version.rejected %}
+ (rejected)
+ {% endif %}
+
{% endfor %}
diff --git a/openslides/motion/urls.py b/openslides/motion/urls.py
index f1781650c..6331439b0 100644
--- a/openslides/motion/urls.py
+++ b/openslides/motion/urls.py
@@ -38,11 +38,21 @@ urlpatterns = patterns('openslides.motion.views',
name='motion_delete',
),
- url(r'^(?P\d+)/version/(?P[1-9]\d*)/$',
+ url(r'^(?P\d+)/version/(?P\d+)/$',
'motion_detail',
name='motion_version_detail',
),
+ url(r'^(?P\d+)/version/(?P\d+)/permit/$',
+ 'version_permit',
+ name='motion_version_permit',
+ ),
+
+ url(r'^(?P\d+)/version/(?P\d+)/reject/$',
+ 'version_reject',
+ name='motion_version_reject',
+ ),
+
url(r'^(?P\d+)/support/$',
'motion_support',
name='motion_support',
diff --git a/openslides/motion/views.py b/openslides/motion/views.py
index a8f8f36c9..3b13f9629 100644
--- a/openslides/motion/views.py
+++ b/openslides/motion/views.py
@@ -30,7 +30,7 @@ from openslides.projector.api import get_active_slide
from openslides.projector.projector import Widget, SLIDE
from openslides.config.models import config
from openslides.agenda.models import Item
-from .models import Motion, MotionSubmitter, MotionSupporter, MotionPoll
+from .models import Motion, MotionSubmitter, MotionSupporter, MotionPoll, MotionVersion
from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin,
MotionCreateNewVersionMixin, ConfigForm)
from .workflow import WorkflowError
@@ -46,7 +46,19 @@ class MotionListView(ListView):
motion_list = MotionListView.as_view()
-class MotionDetailView(DetailView):
+class GetVersionMixin(object):
+ def get_object(self):
+ object = super(GetVersionMixin, self).get_object()
+ version_number = self.kwargs.get('version_number', None)
+ if version_number is not None:
+ try:
+ object.version = int(version_number)
+ except MotionVersion.DoesNotExist:
+ raise Http404('Version %s not found' % version_number)
+ return object
+
+
+class MotionDetailView(GetVersionMixin, DetailView):
"""
Show the details of one motion.
"""
@@ -54,16 +66,6 @@ class MotionDetailView(DetailView):
model = Motion
template_name = 'motion/motion_detail.html'
- def get_object(self):
- object = super(MotionDetailView, self).get_object()
- version_id = self.kwargs.get('version_id', None)
- if version_id is not None:
- try:
- object.version = int(version_id) - 1
- except IndexError:
- raise Http404
- return object
-
def get_context_data(self, **kwargs):
context = super(MotionDetailView, self).get_context_data(**kwargs)
context['allowed_actions'] = self.object.get_allowed_actions(self.request.user)
@@ -157,6 +159,50 @@ class MotionDeleteView(DeleteView):
motion_delete = MotionDeleteView.as_view()
+class VersionPermitView(GetVersionMixin, SingleObjectMixin, QuestionMixin, RedirectView):
+ model = Motion
+ question_url_name = 'motion_version_detail'
+ success_url_name = 'motion_version_detail'
+
+ def get(self, *args, **kwargs):
+ self.object = self.get_object()
+ return super(VersionPermitView, self).get(*args, **kwargs)
+
+ def get_url_name_args(self):
+ return [self.object.pk, self.object.version.version_number]
+
+ def get_question(self):
+ return _('Are you sure you want permit Version %s?') % self.object.version.version_number
+
+ def case_yes(self):
+ self.object.activate_version(self.object.version)
+ self.object.save()
+
+version_permit = VersionPermitView.as_view()
+
+
+class VersionRejectView(GetVersionMixin, SingleObjectMixin, QuestionMixin, RedirectView):
+ model = Motion
+ question_url_name = 'motion_version_detail'
+ success_url_name = 'motion_version_detail'
+
+ def get(self, *args, **kwargs):
+ self.object = self.get_object()
+ return super(VersionRejectView, self).get(*args, **kwargs)
+
+ def get_url_name_args(self):
+ 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):
+ self.object.reject_version(self.object.version)
+ self.object.save()
+
+version_reject = VersionRejectView.as_view()
+
+
class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
"""
Classed based view to support or unsupport a motion. Use
diff --git a/openslides/motion/workflow.py b/openslides/motion/workflow.py
index 51ddd65c2..4e25fee7c 100644
--- a/openslides/motion/workflow.py
+++ b/openslides/motion/workflow.py
@@ -10,13 +10,14 @@ _workflow = None
class State(object):
def __init__(self, id, name, next_states=[], create_poll=False, support=False,
- edit_as_submitter=False):
+ edit_as_submitter=False, version_permission=True):
self.id = id
self.name = name
self.next_states = next_states
self.create_poll = create_poll
self.support = support
self.edit_as_submitter = edit_as_submitter
+ self.version_permission = version_permission
def __unicode__(self):
return self.name
@@ -75,7 +76,10 @@ def populate_workflow(state, workflow):
DUMMY_STATE = State('dummy', ugettext('Unknwon state'))
-default_workflow = State('pub', ugettext('Published'), support=True, edit_as_submitter=True, next_states=[
+default_workflow = State('pub', ugettext('Published'), support=True,
+ edit_as_submitter=True, version_permission=False)
+
+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')),
@@ -84,4 +88,4 @@ default_workflow = State('pub', ugettext('Published'), support=True, edit_as_sub
State('noc', ugettext('Not Concerned')),
State('com', ugettext('Commited a bill')),
State('rev', ugettext('Needs Review'))]),
- State('nop', ugettext('Rejected (not authorized)'))])
+ State('nop', ugettext('Rejected (not authorized)'))]