Merge pull request #2995 from FinnStutzenstein/Issue2957

Autoupdate on permission change
This commit is contained in:
Norman Jäckel 2017-03-07 15:32:56 +01:00 committed by GitHub
commit f105ecd68c
20 changed files with 306 additions and 21 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View 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()

View File

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

View File

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

View File

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

View 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()

View File

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

View File

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

View File

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

View 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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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):
""" """