Permission system for MotionVersions

This commit is contained in:
Oskar Hahn 2013-02-03 13:24:29 +01:00
parent e7c65b3db1
commit ef7f6cf476
5 changed files with 141 additions and 30 deletions

View File

@ -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')

View File

@ -12,6 +12,7 @@
<p>Reason: {{ motion.reason }}</p>
<p>Submitter: {% for submitter in motion.submitter.all %}{{ submitter.person }} {% endfor %}</p>
<p>Supporter: {% for supporter in motion.supporter.all %}{{ supporter.person }} {% endfor %}</p>
<p>Active Version: {{ motion.active_version }}</p>
<p>State: {{ motion.state }}</p>
<h4>possible stats:</h4>
<ul>
@ -25,11 +26,19 @@
<h4>Versions</h4>
<ol>
{% for motion_version in motion.versions.all %}
<li>
{% if motion_version.id == motion.version.id %}
<li><strong><a href="{% model_url motion_version %}">{{ motion_version }}</a></strong></li>
<strong><a href="{% model_url motion_version %}">{{ motion_version }}</a></strong>
{% else %}
<li><a href="{% model_url motion_version %}">{{ motion_version }}</a></li>
<a href="{% model_url motion_version %}">{{ motion_version }}</a>
{% endif %}
{% if motion_version.active %}
(active)
{% endif %}
{% if motion_version.rejected %}
(rejected)
{% endif %}
</li>
{% endfor %}
</ol>

View File

@ -38,11 +38,21 @@ urlpatterns = patterns('openslides.motion.views',
name='motion_delete',
),
url(r'^(?P<pk>\d+)/version/(?P<version_id>[1-9]\d*)/$',
url(r'^(?P<pk>\d+)/version/(?P<version_number>\d+)/$',
'motion_detail',
name='motion_version_detail',
),
url(r'^(?P<pk>\d+)/version/(?P<version_number>\d+)/permit/$',
'version_permit',
name='motion_version_permit',
),
url(r'^(?P<pk>\d+)/version/(?P<version_number>\d+)/reject/$',
'version_reject',
name='motion_version_reject',
),
url(r'^(?P<pk>\d+)/support/$',
'motion_support',
name='motion_support',

View File

@ -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

View File

@ -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)'))]