Permission system for MotionVersions
This commit is contained in:
parent
e7c65b3db1
commit
ef7f6cf476
@ -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')
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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)'))]
|
||||
|
Loading…
Reference in New Issue
Block a user