Merge pull request #2995 from FinnStutzenstein/Issue2957
Autoupdate on permission change
This commit is contained in:
commit
f105ecd68c
@ -28,6 +28,6 @@ script:
|
|||||||
- coverage report --fail-under=44
|
- coverage report --fail-under=44
|
||||||
|
|
||||||
- DJANGO_SETTINGS_MODULE='tests.settings' coverage run ./manage.py test tests.integration
|
- DJANGO_SETTINGS_MODULE='tests.settings' coverage run ./manage.py test tests.integration
|
||||||
- coverage report --fail-under=73
|
- coverage report --fail-under=74
|
||||||
|
|
||||||
- DJANGO_SETTINGS_MODULE='tests.old.settings' ./manage.py test tests.old
|
- DJANGO_SETTINGS_MODULE='tests.old.settings' ./manage.py test tests.old
|
||||||
|
@ -87,6 +87,7 @@ Elections:
|
|||||||
|
|
||||||
Users:
|
Users:
|
||||||
- Added new matrix-interface for managing groups and their permissions.
|
- Added new matrix-interface for managing groups and their permissions.
|
||||||
|
- Added autoupdate on permission change (permission added).
|
||||||
- Improved password reset view for administrators.
|
- Improved password reset view for administrators.
|
||||||
- Changed field for initial password to an unchangeable field.
|
- Changed field for initial password to an unchangeable field.
|
||||||
- Added new field for participant number.
|
- Added new field for participant number.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
from ..utils.collection import Collection
|
||||||
|
|
||||||
|
|
||||||
class AgendaAppConfig(AppConfig):
|
class AgendaAppConfig(AppConfig):
|
||||||
name = 'openslides.agenda'
|
name = 'openslides.agenda'
|
||||||
@ -15,9 +17,11 @@ class AgendaAppConfig(AppConfig):
|
|||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from django.db.models.signals import pre_delete, post_save
|
from django.db.models.signals import pre_delete, post_save
|
||||||
from openslides.core.config import config
|
from openslides.core.config import config
|
||||||
|
from openslides.core.signals import permission_change
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
from .config_variables import get_config_variables
|
from .config_variables import get_config_variables
|
||||||
from .signals import (
|
from .signals import (
|
||||||
|
get_permission_change_data,
|
||||||
listen_to_related_object_post_delete,
|
listen_to_related_object_post_delete,
|
||||||
listen_to_related_object_post_save)
|
listen_to_related_object_post_save)
|
||||||
from .views import ItemViewSet
|
from .views import ItemViewSet
|
||||||
@ -32,10 +36,16 @@ class AgendaAppConfig(AppConfig):
|
|||||||
pre_delete.connect(
|
pre_delete.connect(
|
||||||
listen_to_related_object_post_delete,
|
listen_to_related_object_post_delete,
|
||||||
dispatch_uid='listen_to_related_object_post_delete')
|
dispatch_uid='listen_to_related_object_post_delete')
|
||||||
|
permission_change.connect(
|
||||||
|
get_permission_change_data,
|
||||||
|
dispatch_uid='agenda_get_permission_change_data')
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register(self.get_model('Item').get_collection_string(), ItemViewSet)
|
router.register(self.get_model('Item').get_collection_string(), ItemViewSet)
|
||||||
|
|
||||||
def get_startup_elements(self):
|
def get_startup_elements(self):
|
||||||
from ..utils.collection import Collection
|
"""
|
||||||
return [Collection(self.get_model('Item').get_collection_string())]
|
Yields all collections required on startup i. e. opening the websocket
|
||||||
|
connection.
|
||||||
|
"""
|
||||||
|
yield Collection(self.get_model('Item').get_collection_string())
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from django.apps import apps
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from openslides.utils.autoupdate import inform_changed_data
|
from openslides.utils.autoupdate import inform_changed_data
|
||||||
@ -36,3 +37,17 @@ def listen_to_related_object_post_delete(sender, instance, **kwargs):
|
|||||||
except Item.DoesNotExist:
|
except Item.DoesNotExist:
|
||||||
# Item does not exist so we do not have to delete it.
|
# Item does not exist so we do not have to delete it.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_permission_change_data(sender, permissions, **kwargs):
|
||||||
|
"""
|
||||||
|
Yields all necessary collections if 'agenda.can_see' or
|
||||||
|
'agenda.can_see_hidden_items' permissions changes.
|
||||||
|
"""
|
||||||
|
agenda_app = apps.get_app_config(app_label='agenda')
|
||||||
|
for permission in permissions:
|
||||||
|
# There could be only one 'agenda.can_see' and then we want to return data.
|
||||||
|
if (permission.content_type.app_label == agenda_app.label
|
||||||
|
and permission.codename in ('can_see', 'can_see_hidden_items')):
|
||||||
|
yield from agenda_app.get_startup_elements()
|
||||||
|
break
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
from ..utils.collection import Collection
|
||||||
|
|
||||||
|
|
||||||
class AssignmentsAppConfig(AppConfig):
|
class AssignmentsAppConfig(AppConfig):
|
||||||
name = 'openslides.assignments'
|
name = 'openslides.assignments'
|
||||||
@ -14,20 +16,30 @@ class AssignmentsAppConfig(AppConfig):
|
|||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from openslides.core.config import config
|
from openslides.core.config import config
|
||||||
|
from openslides.core.signals import permission_change
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
from .config_variables import get_config_variables
|
from .config_variables import get_config_variables
|
||||||
|
from .signals import get_permission_change_data
|
||||||
from .views import AssignmentViewSet, AssignmentPollViewSet
|
from .views import AssignmentViewSet, AssignmentPollViewSet
|
||||||
|
|
||||||
# Define config variables
|
# Define config variables
|
||||||
config.update_config_variables(get_config_variables())
|
config.update_config_variables(get_config_variables())
|
||||||
|
|
||||||
|
# Connect signals.
|
||||||
|
permission_change.connect(
|
||||||
|
get_permission_change_data,
|
||||||
|
dispatch_uid='assignments_get_permission_change_data')
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register(self.get_model('Assignment').get_collection_string(), AssignmentViewSet)
|
router.register(self.get_model('Assignment').get_collection_string(), AssignmentViewSet)
|
||||||
router.register('assignments/poll', AssignmentPollViewSet)
|
router.register('assignments/poll', AssignmentPollViewSet)
|
||||||
|
|
||||||
def get_startup_elements(self):
|
def get_startup_elements(self):
|
||||||
from ..utils.collection import Collection
|
"""
|
||||||
return [Collection(self.get_model('Assignment').get_collection_string())]
|
Yields all collections required on startup i. e. opening the websocket
|
||||||
|
connection.
|
||||||
|
"""
|
||||||
|
yield Collection(self.get_model('Assignment').get_collection_string())
|
||||||
|
|
||||||
def get_angular_constants(self):
|
def get_angular_constants(self):
|
||||||
assignment = self.get_model('Assignment')
|
assignment = self.get_model('Assignment')
|
||||||
|
12
openslides/assignments/signals.py
Normal file
12
openslides/assignments/signals.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from django.apps import apps
|
||||||
|
|
||||||
|
|
||||||
|
def get_permission_change_data(sender, permissions=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Yields all necessary collections if 'assignments.can_see' permission changes.
|
||||||
|
"""
|
||||||
|
assignments_app = apps.get_app_config(app_label='assignments')
|
||||||
|
for permission in permissions:
|
||||||
|
# There could be only one 'assignment.can_see' and then we want to return data.
|
||||||
|
if permission.content_type.app_label == assignments_app.label and permission.codename == 'can_see':
|
||||||
|
yield from assignments_app.get_startup_elements()
|
@ -1,6 +1,8 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ..utils.collection import Collection
|
||||||
|
|
||||||
|
|
||||||
class CoreAppConfig(AppConfig):
|
class CoreAppConfig(AppConfig):
|
||||||
name = 'openslides.core'
|
name = 'openslides.core'
|
||||||
@ -18,7 +20,10 @@ class CoreAppConfig(AppConfig):
|
|||||||
from .signals import post_permission_creation
|
from .signals import post_permission_creation
|
||||||
from ..utils.rest_api import router
|
from ..utils.rest_api import router
|
||||||
from .config_variables import get_config_variables
|
from .config_variables import get_config_variables
|
||||||
from .signals import delete_django_app_permissions
|
from .signals import (
|
||||||
|
delete_django_app_permissions,
|
||||||
|
get_permission_change_data,
|
||||||
|
permission_change)
|
||||||
from .views import (
|
from .views import (
|
||||||
ChatMessageViewSet,
|
ChatMessageViewSet,
|
||||||
ConfigViewSet,
|
ConfigViewSet,
|
||||||
@ -35,6 +40,9 @@ class CoreAppConfig(AppConfig):
|
|||||||
post_permission_creation.connect(
|
post_permission_creation.connect(
|
||||||
delete_django_app_permissions,
|
delete_django_app_permissions,
|
||||||
dispatch_uid='delete_django_app_permissions')
|
dispatch_uid='delete_django_app_permissions')
|
||||||
|
permission_change.connect(
|
||||||
|
get_permission_change_data,
|
||||||
|
dispatch_uid='core_get_permission_change_data')
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register(self.get_model('Projector').get_collection_string(), ProjectorViewSet)
|
router.register(self.get_model('Projector').get_collection_string(), ProjectorViewSet)
|
||||||
@ -45,8 +53,11 @@ class CoreAppConfig(AppConfig):
|
|||||||
router.register(self.get_model('Countdown').get_collection_string(), CountdownViewSet)
|
router.register(self.get_model('Countdown').get_collection_string(), CountdownViewSet)
|
||||||
|
|
||||||
def get_startup_elements(self):
|
def get_startup_elements(self):
|
||||||
|
"""
|
||||||
|
Yields all collections required on startup i. e. opening the websocket
|
||||||
|
connection.
|
||||||
|
"""
|
||||||
from .config import config
|
from .config import config
|
||||||
from ..utils.collection import Collection
|
|
||||||
for model in ('Projector', 'ChatMessage', 'Tag', 'ProjectorMessage', 'Countdown'):
|
for model in ('Projector', 'ChatMessage', 'Tag', 'ProjectorMessage', 'Countdown'):
|
||||||
yield Collection(self.get_model(model).get_collection_string())
|
yield Collection(self.get_model(model).get_collection_string())
|
||||||
yield Collection(config.get_collection_string())
|
yield Collection(config.get_collection_string())
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
|
from django.apps import apps
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.dispatch import Signal
|
from django.dispatch import Signal
|
||||||
|
|
||||||
# This signal is sent when the migrate command is done. That means it is sent
|
from ..utils.collection import Collection
|
||||||
|
|
||||||
|
# This signal is send when the migrate command is done. That means it is sent
|
||||||
# after post_migrate sending and creating all Permission objects. Don't use it
|
# after post_migrate sending and creating all Permission objects. Don't use it
|
||||||
# for other things than dealing with Permission objects.
|
# for other things than dealing with Permission objects.
|
||||||
post_permission_creation = Signal()
|
post_permission_creation = Signal()
|
||||||
|
|
||||||
|
# This signal is sent if a permission is changed (e. g. a group gets a new
|
||||||
|
# permission). Connected receivers may yield Collections.
|
||||||
|
permission_change = Signal()
|
||||||
|
|
||||||
|
|
||||||
def delete_django_app_permissions(sender, **kwargs):
|
def delete_django_app_permissions(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -19,3 +26,19 @@ def delete_django_app_permissions(sender, **kwargs):
|
|||||||
Q(app_label='contenttypes') |
|
Q(app_label='contenttypes') |
|
||||||
Q(app_label='sessions'))
|
Q(app_label='sessions'))
|
||||||
Permission.objects.filter(content_type__in=contenttypes).delete()
|
Permission.objects.filter(content_type__in=contenttypes).delete()
|
||||||
|
|
||||||
|
|
||||||
|
def get_permission_change_data(sender, permissions, **kwargs):
|
||||||
|
"""
|
||||||
|
Yields all necessary collections if the respective permissions change.
|
||||||
|
"""
|
||||||
|
core_app = apps.get_app_config(app_label='core')
|
||||||
|
for permission in permissions:
|
||||||
|
if permission.content_type.app_label == core_app.label:
|
||||||
|
if permission.codename == 'can_see_projector':
|
||||||
|
yield Collection(core_app.get_model('Projector').get_collection_string())
|
||||||
|
elif permission.codename == 'can_manage_projector':
|
||||||
|
yield Collection(core_app.get_model('ProjectorMessage').get_collection_string())
|
||||||
|
yield Collection(core_app.get_model('Countdown').get_collection_string())
|
||||||
|
elif permission.codename == 'can_use_chat':
|
||||||
|
yield Collection(core_app.get_model('ChatMessage').get_collection_string())
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
from ..utils.collection import Collection
|
||||||
|
|
||||||
|
|
||||||
class MediafilesAppConfig(AppConfig):
|
class MediafilesAppConfig(AppConfig):
|
||||||
name = 'openslides.mediafiles'
|
name = 'openslides.mediafiles'
|
||||||
@ -13,12 +15,22 @@ class MediafilesAppConfig(AppConfig):
|
|||||||
from . import projector # noqa
|
from . import projector # noqa
|
||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
|
from openslides.core.signals import permission_change
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
|
from .signals import get_permission_change_data
|
||||||
from .views import MediafileViewSet
|
from .views import MediafileViewSet
|
||||||
|
|
||||||
|
# Connect signals.
|
||||||
|
permission_change.connect(
|
||||||
|
get_permission_change_data,
|
||||||
|
dispatch_uid='mediafiles_get_permission_change_data')
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register(self.get_model('Mediafile').get_collection_string(), MediafileViewSet)
|
router.register(self.get_model('Mediafile').get_collection_string(), MediafileViewSet)
|
||||||
|
|
||||||
def get_startup_elements(self):
|
def get_startup_elements(self):
|
||||||
from ..utils.collection import Collection
|
"""
|
||||||
return [Collection(self.get_model('Mediafile').get_collection_string())]
|
Yields all collections required on startup i. e. opening the websocket
|
||||||
|
connection.
|
||||||
|
"""
|
||||||
|
yield Collection(self.get_model('Mediafile').get_collection_string())
|
||||||
|
12
openslides/mediafiles/signals.py
Normal file
12
openslides/mediafiles/signals.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from django.apps import apps
|
||||||
|
|
||||||
|
|
||||||
|
def get_permission_change_data(sender, permissions=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Yields all necessary collections if 'mediafiles.can_see' permission changes.
|
||||||
|
"""
|
||||||
|
mediafiles_app = apps.get_app_config(app_label='mediafiles')
|
||||||
|
for permission in permissions:
|
||||||
|
# There could be only one 'mediafiles.can_see' and then we want to return data.
|
||||||
|
if permission.content_type.app_label == mediafiles_app.label and permission.codename == 'can_see':
|
||||||
|
yield from mediafiles_app.get_startup_elements()
|
@ -1,6 +1,8 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.db.models.signals import post_migrate
|
from django.db.models.signals import post_migrate
|
||||||
|
|
||||||
|
from ..utils.collection import Collection
|
||||||
|
|
||||||
|
|
||||||
class MotionsAppConfig(AppConfig):
|
class MotionsAppConfig(AppConfig):
|
||||||
name = 'openslides.motions'
|
name = 'openslides.motions'
|
||||||
@ -15,9 +17,10 @@ class MotionsAppConfig(AppConfig):
|
|||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from openslides.core.config import config
|
from openslides.core.config import config
|
||||||
|
from openslides.core.signals import permission_change
|
||||||
from openslides.utils.rest_api import router
|
from openslides.utils.rest_api import router
|
||||||
from .config_variables import get_config_variables
|
from .config_variables import get_config_variables
|
||||||
from .signals import create_builtin_workflows
|
from .signals import create_builtin_workflows, get_permission_change_data
|
||||||
from .views import CategoryViewSet, MotionViewSet, MotionBlockViewSet, MotionPollViewSet, MotionChangeRecommendationViewSet, WorkflowViewSet
|
from .views import CategoryViewSet, MotionViewSet, MotionBlockViewSet, MotionPollViewSet, MotionChangeRecommendationViewSet, WorkflowViewSet
|
||||||
|
|
||||||
# Define config variables
|
# Define config variables
|
||||||
@ -25,6 +28,9 @@ class MotionsAppConfig(AppConfig):
|
|||||||
|
|
||||||
# Connect signals.
|
# Connect signals.
|
||||||
post_migrate.connect(create_builtin_workflows, dispatch_uid='motion_create_builtin_workflows')
|
post_migrate.connect(create_builtin_workflows, dispatch_uid='motion_create_builtin_workflows')
|
||||||
|
permission_change.connect(
|
||||||
|
get_permission_change_data,
|
||||||
|
dispatch_uid='motions_get_permission_change_data')
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register(self.get_model('Category').get_collection_string(), CategoryViewSet)
|
router.register(self.get_model('Category').get_collection_string(), CategoryViewSet)
|
||||||
@ -36,6 +42,9 @@ class MotionsAppConfig(AppConfig):
|
|||||||
router.register('motions/motionpoll', MotionPollViewSet)
|
router.register('motions/motionpoll', MotionPollViewSet)
|
||||||
|
|
||||||
def get_startup_elements(self):
|
def get_startup_elements(self):
|
||||||
from ..utils.collection import Collection
|
"""
|
||||||
|
Yields all collections required on startup i. e. opening the websocket
|
||||||
|
connection.
|
||||||
|
"""
|
||||||
for model in ('Category', 'Motion', 'MotionBlock', 'Workflow', 'MotionChangeRecommendation'):
|
for model in ('Category', 'Motion', 'MotionBlock', 'Workflow', 'MotionChangeRecommendation'):
|
||||||
yield Collection(self.get_model(model).get_collection_string())
|
yield Collection(self.get_model(model).get_collection_string())
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from django.apps import apps
|
||||||
from django.utils.translation import ugettext_noop
|
from django.utils.translation import ugettext_noop
|
||||||
|
|
||||||
from .models import State, Workflow
|
from .models import State, Workflow
|
||||||
@ -102,3 +103,14 @@ def create_builtin_workflows(sender, **kwargs):
|
|||||||
state_2_2.next_states.add(state_2_3, state_2_4, state_2_5, state_2_6, state_2_7, state_2_8, state_2_9)
|
state_2_2.next_states.add(state_2_3, state_2_4, state_2_5, state_2_6, state_2_7, state_2_8, state_2_9)
|
||||||
workflow_2.first_state = state_2_1
|
workflow_2.first_state = state_2_1
|
||||||
workflow_2.save()
|
workflow_2.save()
|
||||||
|
|
||||||
|
|
||||||
|
def get_permission_change_data(sender, permissions, **kwargs):
|
||||||
|
"""
|
||||||
|
Yields all necessary collections if 'motions.can_see' permission changes.
|
||||||
|
"""
|
||||||
|
motions_app = apps.get_app_config(app_label='motions')
|
||||||
|
for permission in permissions:
|
||||||
|
# There could be only one 'motions.can_see' and then we want to return data.
|
||||||
|
if permission.content_type.app_label == motions_app.label and permission.codename == 'can_see':
|
||||||
|
yield from motions_app.get_startup_elements()
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
from ..utils.collection import Collection
|
||||||
|
|
||||||
|
|
||||||
class TopicsAppConfig(AppConfig):
|
class TopicsAppConfig(AppConfig):
|
||||||
name = 'openslides.topics'
|
name = 'openslides.topics'
|
||||||
@ -13,12 +15,22 @@ class TopicsAppConfig(AppConfig):
|
|||||||
from . import projector # noqa
|
from . import projector # noqa
|
||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
|
from openslides.core.signals import permission_change
|
||||||
from ..utils.rest_api import router
|
from ..utils.rest_api import router
|
||||||
|
from .signals import get_permission_change_data
|
||||||
from .views import TopicViewSet
|
from .views import TopicViewSet
|
||||||
|
|
||||||
|
# Connect signals.
|
||||||
|
permission_change.connect(
|
||||||
|
get_permission_change_data,
|
||||||
|
dispatch_uid='topics_get_permission_change_data')
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register(self.get_model('Topic').get_collection_string(), TopicViewSet)
|
router.register(self.get_model('Topic').get_collection_string(), TopicViewSet)
|
||||||
|
|
||||||
def get_startup_elements(self):
|
def get_startup_elements(self):
|
||||||
from ..utils.collection import Collection
|
"""
|
||||||
return [Collection(self.get_model('Topic').get_collection_string())]
|
Yields all collections required on startup i. e. opening the websocket
|
||||||
|
connection.
|
||||||
|
"""
|
||||||
|
yield Collection(self.get_model('Topic').get_collection_string())
|
||||||
|
14
openslides/topics/signals.py
Normal file
14
openslides/topics/signals.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from django.apps import apps
|
||||||
|
|
||||||
|
|
||||||
|
def get_permission_change_data(sender, permissions, **kwargs):
|
||||||
|
"""
|
||||||
|
Yields all necessary collections from the topics app if
|
||||||
|
'agenda.can_see' permission changes, because topics are strongly
|
||||||
|
connected to the agenda items.
|
||||||
|
"""
|
||||||
|
topics_app = apps.get_app_config(app_label='topics')
|
||||||
|
for permission in permissions:
|
||||||
|
# There could be only one 'agenda.can_see' and then we want to return data.
|
||||||
|
if permission.content_type.app_label == 'agenda' and permission.codename == 'can_see':
|
||||||
|
yield from topics_app.get_startup_elements()
|
@ -1,5 +1,7 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
from ..utils.collection import Collection
|
||||||
|
|
||||||
|
|
||||||
class UsersAppConfig(AppConfig):
|
class UsersAppConfig(AppConfig):
|
||||||
name = 'openslides.users'
|
name = 'openslides.users'
|
||||||
@ -14,10 +16,10 @@ class UsersAppConfig(AppConfig):
|
|||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from ..core.config import config
|
from ..core.config import config
|
||||||
from ..core.signals import post_permission_creation
|
from ..core.signals import post_permission_creation, permission_change
|
||||||
from ..utils.rest_api import router
|
from ..utils.rest_api import router
|
||||||
from .config_variables import get_config_variables
|
from .config_variables import get_config_variables
|
||||||
from .signals import create_builtin_groups_and_admin
|
from .signals import create_builtin_groups_and_admin, get_permission_change_data
|
||||||
from .views import GroupViewSet, UserViewSet
|
from .views import GroupViewSet, UserViewSet
|
||||||
|
|
||||||
# Define config variables
|
# Define config variables
|
||||||
@ -27,12 +29,18 @@ class UsersAppConfig(AppConfig):
|
|||||||
post_permission_creation.connect(
|
post_permission_creation.connect(
|
||||||
create_builtin_groups_and_admin,
|
create_builtin_groups_and_admin,
|
||||||
dispatch_uid='create_builtin_groups_and_admin')
|
dispatch_uid='create_builtin_groups_and_admin')
|
||||||
|
permission_change.connect(
|
||||||
|
get_permission_change_data,
|
||||||
|
dispatch_uid='users_get_permission_change_data')
|
||||||
|
|
||||||
# Register viewsets.
|
# Register viewsets.
|
||||||
router.register(self.get_model('User').get_collection_string(), UserViewSet)
|
router.register(self.get_model('User').get_collection_string(), UserViewSet)
|
||||||
router.register(self.get_model('Group').get_collection_string(), GroupViewSet)
|
router.register(self.get_model('Group').get_collection_string(), GroupViewSet)
|
||||||
|
|
||||||
def get_startup_elements(self):
|
def get_startup_elements(self):
|
||||||
from ..utils.collection import Collection
|
"""
|
||||||
|
Yields all collections required on startup i. e. opening the websocket
|
||||||
|
connection.
|
||||||
|
"""
|
||||||
for model in ('User', 'Group'):
|
for model in ('User', 'Group'):
|
||||||
yield Collection(self.get_model(model).get_collection_string())
|
yield Collection(self.get_model(model).get_collection_string())
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from django.apps import apps
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
@ -5,6 +6,17 @@ from ..utils.autoupdate import inform_changed_data
|
|||||||
from .models import Group, User
|
from .models import Group, User
|
||||||
|
|
||||||
|
|
||||||
|
def get_permission_change_data(sender, permissions=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Yields all necessary collections if 'users.can_see_name' permission changes.
|
||||||
|
"""
|
||||||
|
users_app = apps.get_app_config(app_label='users')
|
||||||
|
for permission in permissions:
|
||||||
|
# There could be only one 'users.can_see_name' and then we want to return data.
|
||||||
|
if permission.content_type.app_label == users_app.label and permission.codename == 'can_see_name':
|
||||||
|
yield from users_app.get_startup_elements()
|
||||||
|
|
||||||
|
|
||||||
def create_builtin_groups_and_admin(**kwargs):
|
def create_builtin_groups_and_admin(**kwargs):
|
||||||
"""
|
"""
|
||||||
Creates the builtin groups: Default, Delegates, Staff and Committees.
|
Creates the builtin groups: Default, Delegates, Staff and Committees.
|
||||||
|
@ -6,8 +6,10 @@ from django.utils.encoding import force_text
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from ..core.config import config
|
from ..core.config import config
|
||||||
|
from ..core.signals import permission_change
|
||||||
from ..utils.auth import anonymous_is_enabled, has_perm
|
from ..utils.auth import anonymous_is_enabled, has_perm
|
||||||
from ..utils.collection import CollectionElement
|
from ..utils.autoupdate import inform_data_collection_element_list
|
||||||
|
from ..utils.collection import CollectionElement, CollectionElementList
|
||||||
from ..utils.rest_api import (
|
from ..utils.rest_api import (
|
||||||
ModelViewSet,
|
ModelViewSet,
|
||||||
Response,
|
Response,
|
||||||
@ -19,7 +21,7 @@ from ..utils.rest_api import (
|
|||||||
from ..utils.views import APIView
|
from ..utils.views import APIView
|
||||||
from .access_permissions import GroupAccessPermissions, UserAccessPermissions
|
from .access_permissions import GroupAccessPermissions, UserAccessPermissions
|
||||||
from .models import Group, User
|
from .models import Group, User
|
||||||
from .serializers import GroupSerializer
|
from .serializers import GroupSerializer, PermissionRelatedField
|
||||||
|
|
||||||
|
|
||||||
# Viewsets for the REST API
|
# Viewsets for the REST API
|
||||||
@ -159,6 +161,51 @@ class GroupViewSet(ModelViewSet):
|
|||||||
result = False
|
result = False
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Customized endpoint to update a group. Send the signal
|
||||||
|
'permission_change' if group permissions change.
|
||||||
|
"""
|
||||||
|
group = self.get_object()
|
||||||
|
|
||||||
|
# Collect old and new (given) permissions to get the difference.
|
||||||
|
old_permissions = list(group.permissions.all()) # Force evaluation so the perms don't change anymore.
|
||||||
|
permission_names = request.data['permissions']
|
||||||
|
if isinstance(permission_names, str):
|
||||||
|
permission_names = [permission_names]
|
||||||
|
given_permissions = [
|
||||||
|
PermissionRelatedField(read_only=True).to_internal_value(data=perm) for perm in permission_names]
|
||||||
|
|
||||||
|
# Run super to update the group.
|
||||||
|
response = super().update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
# Check status code and send 'permission_change' signal.
|
||||||
|
if response.status_code == 200:
|
||||||
|
|
||||||
|
def diff(full, part):
|
||||||
|
"""
|
||||||
|
This helper function calculates the difference of two lists:
|
||||||
|
The result is a list of all elements of 'full' that are
|
||||||
|
not in 'part'.
|
||||||
|
"""
|
||||||
|
part = set(part)
|
||||||
|
return [item for item in full if item not in part]
|
||||||
|
|
||||||
|
new_permissions = diff(given_permissions, old_permissions)
|
||||||
|
|
||||||
|
# Some permissions are added.
|
||||||
|
if len(new_permissions) > 0:
|
||||||
|
collection_elements = CollectionElementList()
|
||||||
|
signal_results = permission_change.send(None, permissions=new_permissions, action='added')
|
||||||
|
for receiver, signal_collections in signal_results:
|
||||||
|
for collection in signal_collections:
|
||||||
|
collection_elements.extend(collection.element_generator())
|
||||||
|
inform_data_collection_element_list(collection_elements)
|
||||||
|
|
||||||
|
# TODO: Some permissions are deleted.
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Protects builtin groups 'Default' (pk=1) from being deleted.
|
Protects builtin groups 'Default' (pk=1) from being deleted.
|
||||||
|
@ -146,13 +146,15 @@ def send_data(message):
|
|||||||
Informs all site users and projector clients about changed data.
|
Informs all site users and projector clients about changed data.
|
||||||
"""
|
"""
|
||||||
collection_elements = CollectionElementList.from_channels_message(message)
|
collection_elements = CollectionElementList.from_channels_message(message)
|
||||||
|
|
||||||
|
# Send data to site users.
|
||||||
for user_id, channel_names in websocket_user_cache.get_all().items():
|
for user_id, channel_names in websocket_user_cache.get_all().items():
|
||||||
if not user_id:
|
if not user_id:
|
||||||
# Anonymous user
|
# Anonymous user
|
||||||
user = None
|
user = None
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
user = CollectionElement.from_values('users/user', user_id)
|
user = user_to_collection_user(user_id)
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
# The user does not exist. Skip him/her.
|
# The user does not exist. Skip him/her.
|
||||||
continue
|
continue
|
||||||
@ -253,6 +255,18 @@ def inform_deleted_data(*args, information=None):
|
|||||||
transaction.on_commit(lambda: send_autoupdate(collection_elements))
|
transaction.on_commit(lambda: send_autoupdate(collection_elements))
|
||||||
|
|
||||||
|
|
||||||
|
def inform_data_collection_element_list(collection_elements, information=None):
|
||||||
|
"""
|
||||||
|
Informs the autoupdate system about some collection elements. This is
|
||||||
|
used just to send some data to all users.
|
||||||
|
"""
|
||||||
|
# If currently there is an open database transaction, then the
|
||||||
|
# send_autoupdate function is only called, when the transaction is
|
||||||
|
# commited. If there is currently no transaction, then the function
|
||||||
|
# is called immediately.
|
||||||
|
transaction.on_commit(lambda: send_autoupdate(collection_elements))
|
||||||
|
|
||||||
|
|
||||||
def send_autoupdate(collection_elements):
|
def send_autoupdate(collection_elements):
|
||||||
"""
|
"""
|
||||||
Helper function, that sends collection_elements through a channel to the
|
Helper function, that sends collection_elements through a channel to the
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
[coverage:run]
|
[coverage:run]
|
||||||
source = openslides
|
source = openslides
|
||||||
omit = openslides/core/management/commands/getgeiss.py
|
omit =
|
||||||
|
openslides/core/management/commands/*.py
|
||||||
|
openslides/users/management/commands/*.py
|
||||||
|
|
||||||
[coverage:html]
|
[coverage:html]
|
||||||
directory = personal_data/tmp/htmlcov
|
directory = personal_data/tmp/htmlcov
|
||||||
|
@ -430,6 +430,53 @@ class GroupUpdate(TestCase):
|
|||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertEqual(response.data, {'name': ['This field is required.']})
|
self.assertEqual(response.data, {'name': ['This field is required.']})
|
||||||
|
|
||||||
|
def test_update_via_put_with_new_permissions(self):
|
||||||
|
admin_client = APIClient()
|
||||||
|
admin_client.login(username='admin', password='admin')
|
||||||
|
group = Group.objects.create(name='group_name_inooThe3dii4mahWeeSe')
|
||||||
|
# This contains all permissions.
|
||||||
|
permissions = [
|
||||||
|
'agenda.can_be_speaker',
|
||||||
|
'agenda.can_manage',
|
||||||
|
'agenda.can_see',
|
||||||
|
'agenda.can_see_hidden_items',
|
||||||
|
'assignments.can_manage',
|
||||||
|
'assignments.can_nominate_other',
|
||||||
|
'assignments.can_nominate_self',
|
||||||
|
'assignments.can_see',
|
||||||
|
'core.can_manage_config',
|
||||||
|
'core.can_manage_projector',
|
||||||
|
'core.can_manage_tags',
|
||||||
|
'core.can_manage_chat',
|
||||||
|
'core.can_see_frontpage',
|
||||||
|
'core.can_see_projector',
|
||||||
|
'core.can_use_chat',
|
||||||
|
'mediafiles.can_manage',
|
||||||
|
'mediafiles.can_see',
|
||||||
|
'mediafiles.can_see_hidden',
|
||||||
|
'mediafiles.can_upload',
|
||||||
|
'motions.can_create',
|
||||||
|
'motions.can_manage',
|
||||||
|
'motions.can_see',
|
||||||
|
'motions.can_see_and_manage_comments',
|
||||||
|
'motions.can_support',
|
||||||
|
'users.can_manage',
|
||||||
|
'users.can_see_extra_data',
|
||||||
|
'users.can_see_name',
|
||||||
|
]
|
||||||
|
|
||||||
|
response = admin_client.put(
|
||||||
|
reverse('group-detail', args=[group.pk]),
|
||||||
|
{'name': 'new_group_name_Chie6duwaepoo8aech7r',
|
||||||
|
'permissions': permissions},
|
||||||
|
format='json')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
group = Group.objects.get(pk=group.pk)
|
||||||
|
for permission in permissions:
|
||||||
|
app_label, codename = permission.split('.')
|
||||||
|
self.assertTrue(group.permissions.get(content_type__app_label=app_label, codename=codename))
|
||||||
|
|
||||||
|
|
||||||
class GroupDelete(TestCase):
|
class GroupDelete(TestCase):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user