Merge pull request #1377 from normanjaeckel/WorkflowAndPermissions
Hide motions from non staff users in early workflow state.
This commit is contained in:
commit
a93e5aa785
@ -7,6 +7,8 @@ http://openslides.org
|
||||
Version 1.6.2 (unreleased)
|
||||
==========================
|
||||
[https://github.com/OpenSlides/OpenSlides/milestones/1.6.2]
|
||||
Motions:
|
||||
- Added possibility to hide motions from non staff users in some states.
|
||||
|
||||
Other:
|
||||
- Cleaned up utils.views to increase performance when fetching single objects
|
||||
|
@ -216,6 +216,21 @@ Komplexer Arbeitsablauf
|
||||
+---------------------+-----------------+---------------+------------+---------------+------------------------+---------------+
|
||||
|
||||
|
||||
Experteneinstellung
|
||||
'''''''''''''''''''
|
||||
|
||||
Es kann eingestellt werden, dass ein Antrag in einem bestimmten Status nur
|
||||
für bestimmte Benutzer sichtbar ist. Die Anpassung kann vor dem ersten
|
||||
Anlegen der Datenbank in der Datei ``openslides/motion/signals.py`` in der
|
||||
Funktion ``create_builtin_workflows()`` erfolgen: Dazu beim gewünschten
|
||||
Status beispielsweise die Zeile
|
||||
``required_permission_to_see='motion.can_manage_motion',`` einfügen. Die
|
||||
Anpassung kann aber auch bei bestehender Datenbank erfolgen: Dazu mit einem
|
||||
entsprechenden Tool direkt auf die Datenbank zugreifen und beim gewünschten
|
||||
Status das Feld ``required_permission_to_see`` beispielsweise mit der
|
||||
Zeichenkette ``motion.can_manage_motion`` überschreiben.
|
||||
|
||||
|
||||
Versionierung
|
||||
-------------
|
||||
|
||||
|
@ -475,6 +475,7 @@ class Motion(SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
|
||||
The dictonary contains the following actions.
|
||||
|
||||
* see
|
||||
* update / edit
|
||||
* delete
|
||||
* create_poll
|
||||
@ -484,9 +485,14 @@ class Motion(SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
* reset_state
|
||||
"""
|
||||
actions = {
|
||||
'update': ((self.is_submitter(person) and
|
||||
self.state.allow_submitter_edit) or
|
||||
person.has_perm('motion.can_manage_motion')),
|
||||
'see': (person.has_perm('motion.can_see_motion') and
|
||||
(not self.state.required_permission_to_see or
|
||||
person.has_perm(self.state.required_permission_to_see) or
|
||||
self.is_submitter(person))),
|
||||
|
||||
'update': (person.has_perm('motion.can_manage_motion') or
|
||||
(self.is_submitter(person) and
|
||||
self.state.allow_submitter_edit)),
|
||||
|
||||
'delete': person.has_perm('motion.can_manage_motion'),
|
||||
|
||||
@ -783,6 +789,16 @@ class State(models.Model):
|
||||
icon = models.CharField(max_length=255)
|
||||
"""A string representing the url to the icon-image."""
|
||||
|
||||
required_permission_to_see = models.CharField(max_length=255, blank=True)
|
||||
"""
|
||||
A permission string. If not empty, the user has to have this permission to
|
||||
see a motion in this state.
|
||||
|
||||
To use this feature change the database entry of a state object and add
|
||||
your favourite permission string. You can do this e. g. by editing the
|
||||
definitions in create_builtin_workflows() in openslides/motion/signals.py.
|
||||
"""
|
||||
|
||||
allow_support = models.BooleanField(default=False)
|
||||
"""If true, persons can support the motion in this state."""
|
||||
|
||||
|
@ -14,17 +14,16 @@ from openslides.config.api import config
|
||||
from openslides.participant.models import Group, User
|
||||
from openslides.utils.pdf import stylesheet
|
||||
|
||||
from .models import Category, Motion
|
||||
from .models import Category
|
||||
|
||||
# Needed to count the delegates
|
||||
# TODO: find another way to do this.
|
||||
|
||||
|
||||
def motions_to_pdf(pdf):
|
||||
def motions_to_pdf(pdf, motions):
|
||||
"""
|
||||
Create a PDF with all motions.
|
||||
"""
|
||||
motions = Motion.objects.all()
|
||||
motions = natsorted(motions, key=attrgetter('identifier'))
|
||||
all_motion_cover(pdf, motions)
|
||||
for motion in motions:
|
||||
|
@ -32,8 +32,30 @@ class MotionListView(ListView):
|
||||
"""
|
||||
View, to list all motions.
|
||||
"""
|
||||
required_permission = 'motion.can_see_motion'
|
||||
model = Motion
|
||||
required_permission = 'motion.can_see_motion'
|
||||
# The template name must be set explicitly because the overridden method
|
||||
# get_queryset() does not return a QuerySet any more so that Django can't
|
||||
# generate the template name from the name of the model.
|
||||
template_name = 'motion/motion_list.html'
|
||||
# The attribute context_object_name must be set explicitly because the
|
||||
# overridden method get_queryset() does not return a QuerySet any more so
|
||||
# that Django can't generate the context_object_name from the name of the
|
||||
# model.
|
||||
context_object_name = 'motion_list'
|
||||
|
||||
def get_queryset(self, *args, **kwargs):
|
||||
"""
|
||||
Returns not a QuerySet but a filtered list of motions. Excludes motions
|
||||
that the user is not allowed to see.
|
||||
"""
|
||||
queryset = super(MotionListView, self).get_queryset(*args, **kwargs)
|
||||
motions = []
|
||||
for motion in queryset:
|
||||
if (not motion.state.required_permission_to_see or
|
||||
self.request.user.has_perm(motion.state.required_permission_to_see)):
|
||||
motions.append(motion)
|
||||
return motions
|
||||
|
||||
motion_list = MotionListView.as_view()
|
||||
|
||||
@ -42,9 +64,14 @@ class MotionDetailView(DetailView):
|
||||
"""
|
||||
Show one motion.
|
||||
"""
|
||||
required_permission = 'motion.can_see_motion'
|
||||
model = Motion
|
||||
|
||||
def check_permission(self, request, *args, **kwargs):
|
||||
"""
|
||||
Check if the request.user has the permission to see the motion.
|
||||
"""
|
||||
return self.get_object().get_allowed_actions(request.user)['see']
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
Return the template context.
|
||||
@ -376,10 +403,15 @@ class VersionDiffView(DetailView):
|
||||
"""
|
||||
Show diff between two versions of a motion.
|
||||
"""
|
||||
required_permission = 'motion.can_see_motion'
|
||||
model = Motion
|
||||
template_name = 'motion/motion_diff.html'
|
||||
|
||||
def check_permission(self, request, *args, **kwargs):
|
||||
"""
|
||||
Check if the request.user has the permission to see the motion.
|
||||
"""
|
||||
return self.get_object().get_allowed_actions(request.user)['see']
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
Return the template context with versions and html diff strings.
|
||||
@ -678,18 +710,28 @@ create_agenda_item = CreateRelatedAgendaItemView.as_view()
|
||||
|
||||
class MotionPDFView(SingleObjectMixin, PDFView):
|
||||
"""
|
||||
Create the PDF for one, or all motions.
|
||||
Create the PDF for one or for 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.
|
||||
"""
|
||||
required_permission = 'motion.can_see_motion'
|
||||
model = Motion
|
||||
top_space = 0
|
||||
print_all_motions = False
|
||||
|
||||
def check_permission(self, request, *args, **kwargs):
|
||||
"""
|
||||
Checks if the requesting user has the permission to see the motion as
|
||||
PDF.
|
||||
"""
|
||||
if self.print_all_motions:
|
||||
is_allowed = request.user.has_perm('motion.can_see_motion')
|
||||
else:
|
||||
is_allowed = self.get_object().get_allowed_actions(request.user)['see']
|
||||
return is_allowed
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
if self.print_all_motions:
|
||||
obj = None
|
||||
@ -716,7 +758,12 @@ class MotionPDFView(SingleObjectMixin, PDFView):
|
||||
Append PDF objects.
|
||||
"""
|
||||
if self.print_all_motions:
|
||||
motions_to_pdf(pdf)
|
||||
motions = []
|
||||
for motion in Motion.objects.all():
|
||||
if (not motion.state.required_permission_to_see or
|
||||
self.request.user.has_perm(motion.state.required_permission_to_see)):
|
||||
motions.append(motion)
|
||||
motions_to_pdf(pdf, motions)
|
||||
else:
|
||||
motion_to_pdf(pdf, self.get_object())
|
||||
|
||||
|
@ -15,6 +15,11 @@ class MotionPDFTest(TestCase):
|
||||
self.admin_client = Client()
|
||||
self.admin_client.login(username='admin', password='admin')
|
||||
|
||||
# Registered
|
||||
self.registered = User.objects.create_user('registered', 'registered@user.user', 'registered')
|
||||
self.registered_client = Client()
|
||||
self.registered_client.login(username='registered', password='registered')
|
||||
|
||||
def test_render_nested_list(self):
|
||||
Motion.objects.create(
|
||||
title='Test Title chieM6Aing8Eegh9ePhu',
|
||||
@ -24,3 +29,17 @@ class MotionPDFTest(TestCase):
|
||||
'<li>Element 2 rel0liiGh0bi3ree6Jei</li></ul>')
|
||||
response = self.admin_client.get('/motion/1/pdf/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_without_required_permission_from_state(self):
|
||||
motion = Motion.objects.create(title='motion_title_zthguis8qqespgknme52')
|
||||
motion.state.required_permission_to_see = 'motion.can_manage_motion'
|
||||
motion.state.save()
|
||||
response = self.registered_client.get('/motion/1/pdf/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_with_filtered_motion_list(self):
|
||||
motion = Motion.objects.create(title='motion_title_qwgvzf6487guni0oikcc')
|
||||
motion.state.required_permission_to_see = 'motion.can_manage_motion'
|
||||
motion.state.save()
|
||||
response = self.registered_client.get('/motion/pdf/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -54,6 +54,20 @@ class TestMotionListView(MotionViewTestCase):
|
||||
def test_get(self):
|
||||
self.check_url('/motion/', self.admin_client, 200)
|
||||
|
||||
def test_get_with_motion(self):
|
||||
self.motion1.title = 'motion1_iozaixeeDuMah8sheGhe'
|
||||
self.motion1.save()
|
||||
response = self.admin_client.get('/motion/')
|
||||
self.assertContains(response, 'motion1_iozaixeeDuMah8sheGhe')
|
||||
|
||||
def test_get_with_filtered_motion_list(self):
|
||||
self.motion1.state.required_permission_to_see = 'motion.can_manage_motion'
|
||||
self.motion1.state.save()
|
||||
self.motion1.title = 'motion1_djfplquczyxasvvgdnmbr'
|
||||
self.motion1.save()
|
||||
response = self.registered_client.get('/motion/')
|
||||
self.assertNotContains(response, 'motion1_djfplquczyxasvvgdnmbr')
|
||||
|
||||
|
||||
class TestMotionDetailView(MotionViewTestCase):
|
||||
def test_get(self):
|
||||
@ -99,6 +113,21 @@ class TestMotionDetailView(MotionViewTestCase):
|
||||
self.assertNotContains(self.admin_client.get('/motion/1/'), 'registered')
|
||||
self.assertContains(self.admin_client.get('/motion/1/'), 'empty')
|
||||
|
||||
def test_get_without_required_permission_from_state(self):
|
||||
self.motion1.state.required_permission_to_see = 'motion.can_manage_motion'
|
||||
self.motion1.state.save()
|
||||
self.check_url('/motion/1/', self.admin_client, 200)
|
||||
self.check_url('/motion/1/', self.registered_client, 403)
|
||||
self.motion1.set_state(state=State.objects.get(name='permitted'))
|
||||
self.motion1.save()
|
||||
self.check_url('/motion/1/', self.registered_client, 200)
|
||||
|
||||
def test_get_without_required_permission_from_state_but_by_submitter(self):
|
||||
self.motion1.state.required_permission_to_see = 'motion.can_manage_motion'
|
||||
self.motion1.state.save()
|
||||
self.motion1.add_submitter(self.registered)
|
||||
self.check_url('/motion/1/', self.registered_client, 200)
|
||||
|
||||
|
||||
class TestMotionDetailVersionView(MotionViewTestCase):
|
||||
def test_get(self):
|
||||
@ -110,6 +139,28 @@ class TestMotionDetailVersionView(MotionViewTestCase):
|
||||
self.check_url('/motion/1/version/500/', self.admin_client, 404)
|
||||
|
||||
|
||||
class TestMotionVersionDiffView(MotionViewTestCase):
|
||||
def test_get_without_required_permission_from_state(self):
|
||||
self.motion1.reason = 'reason1_bnmkjiutufjbnvcde334'
|
||||
self.motion1.save()
|
||||
self.motion1.title = 'motion1_bnvhfzqsgxcyvasfr57t'
|
||||
self.motion1.save(use_version=self.motion1.get_new_version())
|
||||
|
||||
response = self.registered_client.get(
|
||||
'/motion/1/diff/',
|
||||
{'rev1': '1', 'rev2': '2'})
|
||||
self.assertNotContains(response, 'At least one version number is not valid.')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.motion1.state.required_permission_to_see = 'motion.can_manage_motion'
|
||||
self.motion1.state.save()
|
||||
|
||||
response = self.registered_client.get(
|
||||
'/motion/1/diff/',
|
||||
{'rev1': '1', 'rev2': '2'})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
|
||||
class TestMotionCreateView(MotionViewTestCase):
|
||||
url = '/motion/new/'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user