Merge pull request #4235 from normanjaeckel/SubmitterPermissions
Added new flag for states to hide motions from submitters.
This commit is contained in:
commit
448ea6df28
@ -38,6 +38,7 @@ Motions:
|
|||||||
recommendations [#4037, #4132].
|
recommendations [#4037, #4132].
|
||||||
- Added timestampes for motions [#4134].
|
- Added timestampes for motions [#4134].
|
||||||
- New config option to set reason as required field [#4232]
|
- New config option to set reason as required field [#4232]
|
||||||
|
- Added new flag to motion state to control access for different users [#4235].
|
||||||
|
|
||||||
User:
|
User:
|
||||||
- Added new admin group which grants all permissions. Users of existing group
|
- Added new admin group which grants all permissions. Users of existing group
|
||||||
|
@ -37,13 +37,17 @@ class MotionAccessPermissions(BaseAccessPermissions):
|
|||||||
is_submitter = False
|
is_submitter = False
|
||||||
|
|
||||||
# Check see permission for this motion.
|
# Check see permission for this motion.
|
||||||
required_permission_to_see = full["state_required_permission_to_see"]
|
from .models import State
|
||||||
permission = (
|
|
||||||
not required_permission_to_see
|
if await async_has_perm(user_id, "motions.can_manage"):
|
||||||
or await async_has_perm(user_id, required_permission_to_see)
|
level = State.MANAGERS_ONLY
|
||||||
or await async_has_perm(user_id, "motions.can_manage")
|
elif await async_has_perm(user_id, "motions.can_manage_metadata"):
|
||||||
or is_submitter
|
level = State.EXTENDED_MANAGERS
|
||||||
)
|
elif is_submitter:
|
||||||
|
level = State.EXTENDED_MANAGERS_AND_SUBMITTER
|
||||||
|
else:
|
||||||
|
level = State.ALL
|
||||||
|
permission = level >= full["state_access_level"]
|
||||||
|
|
||||||
# Parse single motion.
|
# Parse single motion.
|
||||||
if permission:
|
if permission:
|
||||||
@ -57,7 +61,6 @@ class MotionAccessPermissions(BaseAccessPermissions):
|
|||||||
data.append(full_copy)
|
data.append(full_copy)
|
||||||
else:
|
else:
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
43
openslides/motions/migrations/0021_state_access_level.py
Normal file
43
openslides/motions/migrations/0021_state_access_level.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Generated by Django 2.1.5 on 2019-02-01 11:58
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def transform_required_permission_to_see_field(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Sets new access_level of states to EXTENDED_MANAGERS_AND_SUBMITTER
|
||||||
|
if required_permission_to_see is given
|
||||||
|
"""
|
||||||
|
# We get the model from the versioned app registry;
|
||||||
|
# if we directly import it, it will be the wrong version.
|
||||||
|
State = apps.get_model("motions", "State")
|
||||||
|
for state in State.objects.all():
|
||||||
|
if state.required_permission_to_see:
|
||||||
|
state.access_level = 1
|
||||||
|
state.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [("motions", "0020_auto_20190119_1425")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="state",
|
||||||
|
name="access_level",
|
||||||
|
field=models.IntegerField(
|
||||||
|
choices=[
|
||||||
|
(0, "All users with permission to see motions"),
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
"Submitters, managers and users with permission to manage metadata",
|
||||||
|
),
|
||||||
|
(2, "Only managers and users with permission to manage metadata"),
|
||||||
|
(3, "Only managers"),
|
||||||
|
],
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(transform_required_permission_to_see_field),
|
||||||
|
migrations.RemoveField(model_name="state", name="required_permission_to_see"),
|
||||||
|
]
|
@ -1054,6 +1054,24 @@ class State(RESTModelMixin, models.Model):
|
|||||||
state.
|
state.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ALL = 0
|
||||||
|
EXTENDED_MANAGERS_AND_SUBMITTER = 1
|
||||||
|
EXTENDED_MANAGERS = 2
|
||||||
|
MANAGERS_ONLY = 3
|
||||||
|
|
||||||
|
ACCESS_LEVELS = (
|
||||||
|
(ALL, "All users with permission to see motions"),
|
||||||
|
(
|
||||||
|
EXTENDED_MANAGERS_AND_SUBMITTER,
|
||||||
|
"Submitters, managers and users with permission to manage metadata",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
EXTENDED_MANAGERS,
|
||||||
|
"Only managers and users with permission to manage metadata",
|
||||||
|
),
|
||||||
|
(MANAGERS_ONLY, "Only managers"),
|
||||||
|
)
|
||||||
|
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
"""A string representing the state."""
|
"""A string representing the state."""
|
||||||
|
|
||||||
@ -1075,14 +1093,10 @@ class State(RESTModelMixin, models.Model):
|
|||||||
Default value is 'primary' (blue).
|
Default value is 'primary' (blue).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
required_permission_to_see = models.CharField(max_length=255, blank=True)
|
access_level = models.IntegerField(choices=ACCESS_LEVELS, default=0)
|
||||||
"""
|
"""
|
||||||
A permission string. If not empty, the user has to have this permission to
|
Defines which users may see motions in this state e. g. only managers,
|
||||||
see a motion in this state.
|
users with permission to manage metadata and submitters.
|
||||||
|
|
||||||
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/motions/signals.py.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
allow_support = models.BooleanField(default=False)
|
allow_support = models.BooleanField(default=False)
|
||||||
|
@ -102,7 +102,7 @@ class StateSerializer(ModelSerializer):
|
|||||||
"name",
|
"name",
|
||||||
"recommendation_label",
|
"recommendation_label",
|
||||||
"css_class",
|
"css_class",
|
||||||
"required_permission_to_see",
|
"access_level",
|
||||||
"allow_support",
|
"allow_support",
|
||||||
"allow_create_poll",
|
"allow_create_poll",
|
||||||
"allow_submitter_edit",
|
"allow_submitter_edit",
|
||||||
@ -389,7 +389,7 @@ class MotionSerializer(ModelSerializer):
|
|||||||
polls = MotionPollSerializer(many=True, read_only=True)
|
polls = MotionPollSerializer(many=True, read_only=True)
|
||||||
modified_final_version = CharField(allow_blank=True, required=False)
|
modified_final_version = CharField(allow_blank=True, required=False)
|
||||||
reason = CharField(allow_blank=True, required=False)
|
reason = CharField(allow_blank=True, required=False)
|
||||||
state_required_permission_to_see = SerializerMethodField()
|
state_access_level = SerializerMethodField()
|
||||||
text = CharField(allow_blank=True)
|
text = CharField(allow_blank=True)
|
||||||
title = CharField(max_length=255)
|
title = CharField(max_length=255)
|
||||||
amendment_paragraphs = AmendmentParagraphsJSONSerializerField(required=False)
|
amendment_paragraphs = AmendmentParagraphsJSONSerializerField(required=False)
|
||||||
@ -424,7 +424,7 @@ class MotionSerializer(ModelSerializer):
|
|||||||
"supporters",
|
"supporters",
|
||||||
"state",
|
"state",
|
||||||
"state_extension",
|
"state_extension",
|
||||||
"state_required_permission_to_see",
|
"state_access_level",
|
||||||
"statute_paragraph",
|
"statute_paragraph",
|
||||||
"workflow_id",
|
"workflow_id",
|
||||||
"recommendation",
|
"recommendation",
|
||||||
@ -531,12 +531,9 @@ class MotionSerializer(ModelSerializer):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_state_required_permission_to_see(self, motion):
|
def get_state_access_level(self, motion):
|
||||||
"""
|
"""
|
||||||
Returns the permission (as string) that is required for non
|
Returns the access level of this state. The default is 0 so everybody
|
||||||
managers that are not submitters to see this motion in this state.
|
with permission to see motions can see this motion.
|
||||||
|
|
||||||
Hint: Most states have and empty string here so this restriction is
|
|
||||||
disabled.
|
|
||||||
"""
|
"""
|
||||||
return motion.state.required_permission_to_see
|
return motion.state.access_level
|
||||||
|
@ -479,13 +479,11 @@ class RetrieveMotion(TestCase):
|
|||||||
username=f"user_{index}", password="password"
|
username=f"user_{index}", password="password"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_guest_state_with_required_permission_to_see(self):
|
def test_guest_state_with_access_level(self):
|
||||||
config["general_system_enable_anonymous"] = True
|
config["general_system_enable_anonymous"] = True
|
||||||
guest_client = APIClient()
|
guest_client = APIClient()
|
||||||
state = self.motion.state
|
state = self.motion.state
|
||||||
state.required_permission_to_see = (
|
state.access_level = State.MANAGERS_ONLY
|
||||||
"permission_that_the_user_does_not_have_leeceiz9hi7iuta4ahY2"
|
|
||||||
)
|
|
||||||
state.save()
|
state.save()
|
||||||
# The cache has to be cleared, see:
|
# The cache has to be cleared, see:
|
||||||
# https://github.com/OpenSlides/OpenSlides/issues/3396
|
# https://github.com/OpenSlides/OpenSlides/issues/3396
|
||||||
@ -494,20 +492,17 @@ class RetrieveMotion(TestCase):
|
|||||||
response = guest_client.get(reverse("motion-detail", args=[self.motion.pk]))
|
response = guest_client.get(reverse("motion-detail", args=[self.motion.pk]))
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_admin_state_with_required_permission_to_see(self):
|
def test_admin_state_with_access_level(self):
|
||||||
state = self.motion.state
|
state = self.motion.state
|
||||||
state.required_permission_to_see = (
|
state.access_level = State.MANAGERS_ONLY
|
||||||
"permission_that_the_user_does_not_have_coo1Iewu8Eing2xahfoo"
|
|
||||||
)
|
|
||||||
state.save()
|
state.save()
|
||||||
response = self.client.get(reverse("motion-detail", args=[self.motion.pk]))
|
response = self.client.get(reverse("motion-detail", args=[self.motion.pk]))
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
def test_submitter_state_with_required_permission_to_see(self):
|
def test_submitter_state_with_access_level(self):
|
||||||
state = self.motion.state
|
state = self.motion.state
|
||||||
state.required_permission_to_see = (
|
state.access_level = State.EXTENDED_MANAGERS_AND_SUBMITTER
|
||||||
"permission_that_the_user_does_not_have_eiW8af9caizoh1thaece"
|
|
||||||
)
|
|
||||||
state.save()
|
state.save()
|
||||||
user = get_user_model().objects.create_user(
|
user = get_user_model().objects.create_user(
|
||||||
username="username_ohS2opheikaSa5theijo",
|
username="username_ohS2opheikaSa5theijo",
|
||||||
|
@ -29,7 +29,7 @@ def all_data():
|
|||||||
"supporters_id": [],
|
"supporters_id": [],
|
||||||
"state_id": 1,
|
"state_id": 1,
|
||||||
"state_extension": None,
|
"state_extension": None,
|
||||||
"state_required_permission_to_see": "",
|
"state_access_level": 0,
|
||||||
"statute_paragraph_id": None,
|
"statute_paragraph_id": None,
|
||||||
"workflow_id": 1,
|
"workflow_id": 1,
|
||||||
"recommendation_id": None,
|
"recommendation_id": None,
|
||||||
@ -86,7 +86,7 @@ def all_data():
|
|||||||
"name": "submitted",
|
"name": "submitted",
|
||||||
"recommendation_label": None,
|
"recommendation_label": None,
|
||||||
"css_class": "primary",
|
"css_class": "primary",
|
||||||
"required_permission_to_see": "",
|
"access_level": 0,
|
||||||
"allow_support": True,
|
"allow_support": True,
|
||||||
"allow_create_poll": True,
|
"allow_create_poll": True,
|
||||||
"allow_submitter_edit": True,
|
"allow_submitter_edit": True,
|
||||||
@ -102,7 +102,7 @@ def all_data():
|
|||||||
"name": "accepted",
|
"name": "accepted",
|
||||||
"recommendation_label": "Acceptance",
|
"recommendation_label": "Acceptance",
|
||||||
"css_class": "success",
|
"css_class": "success",
|
||||||
"required_permission_to_see": "",
|
"access_level": 0,
|
||||||
"allow_support": False,
|
"allow_support": False,
|
||||||
"allow_create_poll": False,
|
"allow_create_poll": False,
|
||||||
"allow_submitter_edit": False,
|
"allow_submitter_edit": False,
|
||||||
@ -118,7 +118,7 @@ def all_data():
|
|||||||
"name": "rejected",
|
"name": "rejected",
|
||||||
"recommendation_label": "Rejection",
|
"recommendation_label": "Rejection",
|
||||||
"css_class": "danger",
|
"css_class": "danger",
|
||||||
"required_permission_to_see": "",
|
"access_level": 0,
|
||||||
"allow_support": False,
|
"allow_support": False,
|
||||||
"allow_create_poll": False,
|
"allow_create_poll": False,
|
||||||
"allow_submitter_edit": False,
|
"allow_submitter_edit": False,
|
||||||
@ -134,7 +134,7 @@ def all_data():
|
|||||||
"name": "not decided",
|
"name": "not decided",
|
||||||
"recommendation_label": "No decision",
|
"recommendation_label": "No decision",
|
||||||
"css_class": "default",
|
"css_class": "default",
|
||||||
"required_permission_to_see": "",
|
"access_level": 0,
|
||||||
"allow_support": False,
|
"allow_support": False,
|
||||||
"allow_create_poll": False,
|
"allow_create_poll": False,
|
||||||
"allow_submitter_edit": False,
|
"allow_submitter_edit": False,
|
||||||
|
Loading…
Reference in New Issue
Block a user