From bf3a1ce714bedf25b10c72f982dd3d76ed9deea4 Mon Sep 17 00:00:00 2001 From: Finn Stutzenstein Date: Mon, 8 Aug 2016 09:37:46 +0200 Subject: [PATCH] New matrix interface for group editing (closes #435) --- CHANGELOG | 1 + openslides/agenda/static/js/agenda/base.js | 4 +- .../assignments/static/js/assignments/base.js | 1 + openslides/core/static/css/app.css | 53 +++- .../mediafiles/static/js/mediafiles/base.js | 5 +- openslides/motions/static/js/motions/base.js | 1 + openslides/users/auth.py | 18 +- openslides/users/models.py | 6 +- openslides/users/serializers.py | 2 +- openslides/users/signals.py | 60 ++-- openslides/users/static/js/users/base.js | 18 +- openslides/users/static/js/users/site.js | 288 +++++++++++++----- .../static/templates/users/group-detail.html | 19 -- .../static/templates/users/group-edit.html | 27 ++ .../static/templates/users/group-form.html | 34 --- .../static/templates/users/group-list.html | 93 +++--- .../static/templates/users/user-import.html | 6 +- tests/integration/motions/test_viewset.py | 4 +- tests/integration/users/test_viewset.py | 31 +- tests/unit/users/test_models.py | 8 +- 20 files changed, 440 insertions(+), 239 deletions(-) delete mode 100644 openslides/users/static/templates/users/group-detail.html create mode 100644 openslides/users/static/templates/users/group-edit.html delete mode 100644 openslides/users/static/templates/users/group-form.html diff --git a/CHANGELOG b/CHANGELOG index 118e79ecf..f03480f19 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ Motions: Users: - Added field is_committee and new default group Committees. - Added field number. +- Added new matrix-interface for managing groups and their permissions. Other: - Removed config cache to support multiple threads or processes. diff --git a/openslides/agenda/static/js/agenda/base.js b/openslides/agenda/static/js/agenda/base.js index e597b41d5..3d4739338 100644 --- a/openslides/agenda/static/js/agenda/base.js +++ b/openslides/agenda/static/js/agenda/base.js @@ -28,11 +28,13 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users']) 'jsDataModel', 'Projector', 'gettextCatalog', - function($http, DS, Speaker, jsDataModel, Projector, gettextCatalog) { + 'gettext', + function($http, DS, Speaker, jsDataModel, Projector, gettextCatalog, gettext) { var name = 'agenda/item'; return DS.defineResource({ name: name, useClass: jsDataModel, + verboseName: gettext('Agenda'), methods: { getResourceName: function () { return name; diff --git a/openslides/assignments/static/js/assignments/base.js b/openslides/assignments/static/js/assignments/base.js index 5c8b95b6a..cd1a35c5c 100644 --- a/openslides/assignments/static/js/assignments/base.js +++ b/openslides/assignments/static/js/assignments/base.js @@ -167,6 +167,7 @@ angular.module('OpenSlidesApp.assignments', []) name: name, useClass: jsDataModel, verboseName: gettext('Election'), + verboseNamePlural: gettext('Elections'), phases: phases, getPhases: function () { if (!this.phases) { diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css index f6d2974d4..37ae61dbc 100644 --- a/openslides/core/static/css/app.css +++ b/openslides/core/static/css/app.css @@ -671,6 +671,42 @@ img { content: ":"; } +/* group list */ +#groups-table { + table-layout: fixed; + text-align: center; +} + +#groups-table > thead > tr > th { + vertical-align: top; + text-align: center; + min-width: 40px; +} + +#groups-table .perm-head { + width: 300px; +} + +#groups-table > thead > tr > th > div { + text-align: center; +} + +#groups-table > tbody > tr:hover { + background-color: #f5f5f5 !important; +} + +#groups-table > tbody > tr:first-child { + background-color: #f9f9f9; +} + +#groups-table > tbody > tr > td:first-child { + text-align: left; +} + +#groups-table .optional-show { /* hide optional-show column */ + display: none; +} + /* search results */ .searchresults li { margin-bottom: 12px; @@ -849,7 +885,6 @@ tr.selected td { color: #9a9898; } - /*//////////////////////////////////////// =MEDIA QUERIES (RESPONSIVE DESIGN) ////////////////////////////////////////*/ @@ -872,6 +907,16 @@ tr.selected td { @media only screen and (max-width: 900px) { #nav .navbar li a { padding: 24px 5px; } + + #groups-table .perm-head { + width: 200px; + } + + /* hide text in groups-table earlier */ + #groups-table .optional { display: none; } + + /* show replacement elements, if any */ + #groups-table .optional-show { display: block !important; } } @@ -894,6 +939,9 @@ tr.selected td { /* hide marked element / column */ .optional, .hide-sm { display: none; } + + /* show replacement elements, if any */ + .optional-show { display: block !important; } } /* display for resolutions smaller that 560px */ @@ -908,4 +956,7 @@ tr.selected td { .col2 .projector_full { margin-left: 0px; } + #groups-table .perm-head { + width: 150px; + } } diff --git a/openslides/mediafiles/static/js/mediafiles/base.js b/openslides/mediafiles/static/js/mediafiles/base.js index af1e7749c..6611b8a77 100644 --- a/openslides/mediafiles/static/js/mediafiles/base.js +++ b/openslides/mediafiles/static/js/mediafiles/base.js @@ -7,11 +7,14 @@ angular.module('OpenSlidesApp.mediafiles', []) .factory('Mediafile', [ 'DS', 'jsDataModel', - function(DS, jsDataModel) { + 'gettext', + function(DS, jsDataModel, gettext) { var name = 'mediafiles/mediafile'; return DS.defineResource({ name: name, useClass: jsDataModel, + verboseName: gettext('Files'), + verboseNamePlural: gettext('Files'), getAllImages: function () { var images = []; angular.forEach(this.getAll(), function(file) { diff --git a/openslides/motions/static/js/motions/base.js b/openslides/motions/static/js/motions/base.js index 69d7b8c1d..3fe3d7d98 100644 --- a/openslides/motions/static/js/motions/base.js +++ b/openslides/motions/static/js/motions/base.js @@ -118,6 +118,7 @@ angular.module('OpenSlidesApp.motions', ['OpenSlidesApp.users']) name: name, useClass: jsDataModel, verboseName: gettext('Motion'), + verboseNamePlural: gettext('Motions'), methods: { getResourceName: function () { return name; diff --git a/openslides/users/auth.py b/openslides/users/auth.py index 27bd0bfea..d826a6707 100644 --- a/openslides/users/auth.py +++ b/openslides/users/auth.py @@ -3,7 +3,6 @@ from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend from django.contrib.auth.models import AnonymousUser as DjangoAnonymousUser from django.contrib.auth.models import Permission -from django.db.models import Q from django.utils.functional import SimpleLazyObject from rest_framework.authentication import BaseAuthentication @@ -14,9 +13,9 @@ from ..core.config import config class CustomizedModelBackend(ModelBackend): """ - Customized backend for authentication. Ensures that registered users - have all permissions of the group 'Registered' (pk=2). See - AUTHENTICATION_BACKENDS settings. + Customized backend for authentication. Ensures that all users + without a group have the permissions of the group 'Default' (pk=1). + See AUTHENTICATION_BACKENDS settings. """ def get_group_permissions(self, user_obj, obj=None): """ @@ -31,11 +30,12 @@ class CustomizedModelBackend(ModelBackend): if user_obj.is_superuser: perms = Permission.objects.all() else: - user_groups_field = get_user_model()._meta.get_field('groups') - user_groups_query = 'group__%s' % user_groups_field.related_query_name() - # The next two lines are the customization. - query = Q(**{user_groups_query: user_obj}) | Q(group__pk=2) - perms = Permission.objects.filter(query) + if user_obj.groups.all().count() == 0: # user is in no group + perms = Permission.objects.filter(group__pk=1) # group 'default' (pk=1) + else: + user_groups_field = get_user_model()._meta.get_field('groups') + user_groups_query = 'group__%s' % user_groups_field.related_query_name() + perms = Permission.objects.filter(**{user_groups_query: user_obj}) perms = perms.values_list('content_type__app_label', 'codename').order_by() user_obj._group_perm_cache = set("%s.%s" % (ct, name) for ct, name in perms) return user_obj._group_perm_cache diff --git a/openslides/users/models.py b/openslides/users/models.py index dbabd6747..f645dd7b4 100644 --- a/openslides/users/models.py +++ b/openslides/users/models.py @@ -35,13 +35,13 @@ class UserManager(BaseUserManager): """ Creates an user with the username 'admin'. If such a user already exists, resets it. The password is (re)set to 'admin'. The user - becomes member of the group 'Staff' (pk=4). + becomes member of the group 'Staff' (pk=3). """ try: - staff = Group.objects.get(pk=4) + staff = Group.objects.get(pk=3) except Group.DoesNotExist: raise UsersError("Admin user can not be created or reset because " - "the group 'Staff' (pk=4) is not available.") + "the group 'Staff' (pk=3) is not available.") admin, created = self.get_or_create( username='admin', defaults={'last_name': 'Administrator'}) diff --git a/openslides/users/serializers.py b/openslides/users/serializers.py index f1b40737d..7ab7ad71c 100644 --- a/openslides/users/serializers.py +++ b/openslides/users/serializers.py @@ -44,7 +44,7 @@ class UserFullSerializer(ModelSerializer): """ groups = IdPrimaryKeyRelatedField( many=True, - queryset=Group.objects.exclude(pk__in=(1, 2)), + queryset=Group.objects.exclude(pk=1), help_text=ugettext_lazy('The groups this user belongs to. A user will ' 'get all permissions granted to each of ' 'his/her groups.')) diff --git a/openslides/users/signals.py b/openslides/users/signals.py index 52486fbe3..95a008701 100644 --- a/openslides/users/signals.py +++ b/openslides/users/signals.py @@ -6,13 +6,12 @@ from .models import Group, User def create_builtin_groups_and_admin(**kwargs): """ - Creates the builtin groups: Anonymous, Registered, Delegates, Staff and - Committees. + Creates the builtin groups: Default, Delegates, Staff and Committees. Creates the builtin user: admin. """ - # Check whether the group pk's 1 to 5 are free. - if Group.objects.filter(pk__in=range(1, 6)).exists(): + # Check whether the group pk's 1 to 4 are free. + if Group.objects.filter(pk__in=range(1, 5)).exists(): # Do completely nothing if there are already some of our groups in the database. return @@ -53,7 +52,7 @@ def create_builtin_groups_and_admin(**kwargs): permission_string = '.'.join((permission.content_type.app_label, permission.codename)) permission_dict[permission_string] = permission - # Anonymous (pk 1) and Registered (pk 2) + # Default (pk 1) base_permissions = ( permission_dict['agenda.can_see'], permission_dict['agenda.can_see_hidden_items'], @@ -63,40 +62,54 @@ def create_builtin_groups_and_admin(**kwargs): permission_dict['mediafiles.can_see'], permission_dict['motions.can_see'], permission_dict['users.can_see_name'], ) - group_anonymous = Group.objects.create(name='Guests', pk=1) - group_anonymous.permissions.add(*base_permissions) - group_registered = Group.objects.create(name='Registered users', pk=2) - group_registered.permissions.add( - permission_dict['agenda.can_be_speaker'], - *base_permissions) + group_default = Group.objects.create(name='Default', pk=1) + group_default.permissions.add(*base_permissions) - # Delegates (pk 3) + # Delegates (pk 2) delegates_permissions = ( + permission_dict['agenda.can_see'], + permission_dict['agenda.can_see_hidden_items'], + permission_dict['agenda.can_be_speaker'], + permission_dict['assignments.can_see'], permission_dict['assignments.can_nominate_other'], permission_dict['assignments.can_nominate_self'], + permission_dict['core.can_see_frontpage'], + permission_dict['core.can_see_projector'], + permission_dict['mediafiles.can_see'], permission_dict['mediafiles.can_upload'], + permission_dict['motions.can_see'], permission_dict['motions.can_create'], - permission_dict['motions.can_support'], ) - group_delegates = Group.objects.create(name='Delegates', pk=3) + permission_dict['motions.can_support'], + permission_dict['users.can_see_name'], ) + group_delegates = Group.objects.create(name='Delegates', pk=2) group_delegates.permissions.add(*delegates_permissions) - # Staff (pk 4) + # Staff (pk 3) staff_permissions = ( + permission_dict['agenda.can_see'], + permission_dict['agenda.can_see_hidden_items'], + permission_dict['agenda.can_be_speaker'], permission_dict['agenda.can_manage'], + permission_dict['assignments.can_see'], permission_dict['assignments.can_manage'], permission_dict['assignments.can_nominate_other'], permission_dict['assignments.can_nominate_self'], + permission_dict['core.can_see_frontpage'], + permission_dict['core.can_see_projector'], permission_dict['core.can_manage_config'], permission_dict['core.can_manage_projector'], permission_dict['core.can_manage_tags'], permission_dict['core.can_use_chat'], + permission_dict['mediafiles.can_see'], permission_dict['mediafiles.can_manage'], permission_dict['mediafiles.can_upload'], + permission_dict['motions.can_see'], permission_dict['motions.can_create'], permission_dict['motions.can_manage'], + permission_dict['users.can_see_name'], permission_dict['users.can_manage'], permission_dict['users.can_see_extra_data'],) - group_staff = Group.objects.create(name='Staff', pk=4) + group_staff = Group.objects.create(name='Staff', pk=3) group_staff.permissions.add(*staff_permissions) # Add users.can_see_name permission to staff @@ -105,12 +118,21 @@ def create_builtin_groups_and_admin(**kwargs): group_staff.permissions.add( permission_dict['users.can_see_name']) - # Committees (pk 5) + # Committees (pk 4) committees_permissions = ( + permission_dict['agenda.can_see'], + permission_dict['agenda.can_see_hidden_items'], + permission_dict['agenda.can_be_speaker'], + permission_dict['assignments.can_see'], + permission_dict['core.can_see_frontpage'], + permission_dict['core.can_see_projector'], + permission_dict['mediafiles.can_see'], permission_dict['mediafiles.can_upload'], + permission_dict['motions.can_see'], permission_dict['motions.can_create'], - permission_dict['motions.can_support'], ) - group_committee = Group.objects.create(name='Committees', pk=5) + permission_dict['motions.can_support'], + permission_dict['users.can_see_name'], ) + group_committee = Group.objects.create(name='Committees', pk=4) group_committee.permissions.add(*committees_permissions) # Create or reset admin user diff --git a/openslides/users/static/js/users/base.js b/openslides/users/static/js/users/base.js index 6e9c8c0f2..d4aa56b50 100644 --- a/openslides/users/static/js/users/base.js +++ b/openslides/users/static/js/users/base.js @@ -55,6 +55,10 @@ angular.module('OpenSlidesApp.users', []) } return _.intersection(perms, operator.perms).length > 0; }, + // Returns true if the operator is a member of group. + isInGroup: function(group) { + return _.indexOf(operator.user.groups_id, group.id) > -1; + }, }; return operator; } @@ -64,11 +68,14 @@ angular.module('OpenSlidesApp.users', []) 'DS', 'Group', 'jsDataModel', - function(DS, Group, jsDataModel) { + 'gettext', + function(DS, Group, jsDataModel, gettext) { var name = 'users/user'; return DS.defineResource({ name: name, useClass: jsDataModel, + verboseName: gettext('Participants'), + verboseNamePlural: gettext('Participants'), computed: { full_name: function () { return this.get_full_name(); @@ -125,8 +132,9 @@ angular.module('OpenSlidesApp.users', []) if (this.groups_id) { allGroups = this.groups_id.slice(0); } - // Add registered group - allGroups.push(2); + if (allGroups.length === 0) { + allGroups.push(1); // add default group + } _.forEach(allGroups, function(groupId) { var group = Group.get(groupId); if (group) { @@ -191,10 +199,10 @@ angular.module('OpenSlidesApp.users', []) 'gettext', function (gettext) { // default group names (from users/signals.py) - gettext('Guests'); - gettext('Registered users'); + gettext('Default'); gettext('Delegates'); gettext('Staff'); + gettext('Committees'); } ]); diff --git a/openslides/users/static/js/users/site.js b/openslides/users/static/js/users/site.js index a386302ce..980b406d7 100644 --- a/openslides/users/static/js/users/site.js +++ b/openslides/users/static/js/users/site.js @@ -111,36 +111,12 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) resolve: { groups: function(Group) { return Group.findAll(); - } - } - }) - .state('users.group.create', { - resolve: { - permissions: function(Group) { - return Group.getPermissions(); - } - } - }) - .state('users.group.detail', { - resolve: { - group: function(Group, $stateParams) { - return Group.find($stateParams.id); }, permissions: function(Group) { return Group.getPermissions(); } } }) - .state('users.group.detail.update', { - views: { - '@users.group': {} - }, - resolve: { - permissions: function(Group) { - return Group.getPermissions(); - } - } - }) .state('login', { template: null, url: '/login', @@ -331,7 +307,7 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) label: gettextCatalog.getString('Groups'), options: Group.getAll(), ngOptions: 'option.id as option.name | translate for option in to.options | ' + - 'filter: {id: "!1"} | filter: {id: "!2"}', + 'filter: {id: "!1"}', placeholder: gettextCatalog.getString('Select or search a group ...') } }, @@ -850,79 +826,233 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) .controller('GroupListCtrl', [ '$scope', + '$http', + 'operator', 'Group', - function($scope, Group) { - Group.bindAll({}, $scope, 'groups'); + 'permissions', + 'gettext', + 'Agenda', + 'Assignment', + 'Mediafile', + 'Motion', + 'User', + 'ngDialog', + function($scope, $http, operator, Group, permissions, gettext, Agenda, Assignment, Mediafile, Motion, User, ngDialog) { + //Group.bindAll({}, $scope, 'groups'); + $scope.permissions = permissions; + + $scope.$watch(function() { + return Group.lastModified(); + }, function() { + $scope.groups = Group.getAll(); + + // find all groups with the 2 dangerous permissions + var groups_danger = []; + $scope.groups.forEach(function (group) { + if ((_.indexOf(group.permissions, 'users.can_see_name') > -1) && + (_.indexOf(group.permissions, 'users.can_manage') > -1)){ + if (operator.isInGroup(group)){ + groups_danger.push(group); + } + } + }); + // if there is only one dangerous group, block it. + $scope.group_danger = groups_danger.length == 1 ? groups_danger[0] : null; + }); + + $scope.apps = []; + // Create the main clustering with appname->permissions + angular.forEach(permissions, function(perm) { + var permissionApp = perm.value.split('.')[0]; // get appname + + // To insert perm in the right spot in $scope.apps + var insert = function (id, perm, verboseName) { + if (!$scope.apps[id]) { + $scope.apps[id] = { + app_name: verboseName, + app_visible: true, + permissions: [] + }; + } + $scope.apps[id].permissions.push(perm); + }; + + switch(permissionApp) { + case 'core': // id 0 (projector) and id 6 (general) + if (perm.value.indexOf('projector') > -1) { + insert(0, perm, gettext('Projector')); + } else { + insert(6, perm, gettext('General')); + } + break; + case 'agenda': // id 1 + insert(1, perm, Agenda.verboseName); + break; + case 'motions': // id 2 + insert(2, perm, Motion.verboseNamePlural); + break; + case 'assignments': // id 3 + insert(3, perm, Assignment.verboseNamePlural); + break; + case 'mediafiles': // id 4 + insert(4, perm, Mediafile.verboseNamePlural); + break; + case 'users': // id 5 + insert(5, perm, User.verboseNamePlural); + break; + default: // plugins: id>5 + var display_name = permissionApp.charAt(0).toUpperCase() + permissionApp.slice(1); + // does the app exists? + var result = -1; + angular.forEach($scope.apps, function (app, index) { + if (app.app_name === display_name) + result = index; + }); + var id = result == -1 ? $scope.apps.length : result; + insert(id, perm, display_name); + break; + } + }); + + // sort each app: first all permission with 'see', then 'manage', then the rest + // save the permissions in different lists an concat them in the right order together + // Special Users: the two "see"-permissions are normally swapped. To create the right + // order, we could simply reverse the whole permissions. + angular.forEach($scope.apps, function (app, index) { + if(index == 5) { // users + app.permissions.reverse(); + } else { // rest + var see = []; + var manage = []; + var others = []; + angular.forEach(app.permissions, function (perm) { + if (perm.value.indexOf('see') > -1) { + see.push(perm); + } else if (perm.value.indexOf('manage') > -1) { + manage.push(perm); + } else { + others.push(perm); + } + }); + app.permissions = see.concat(manage.concat(others)); + } + }); + + // check if the given group has the given permission + $scope.hasPerm = function (group, permission) { + return _.indexOf(group.permissions, permission.value) > -1; + }; + + // The current user is not allowed to lock himself out of the configuration: + // - if the permission is 'users.can_manage' or 'users.can_see' + // - if the user is in only one group with these permissions (group_danger is set) + $scope.danger = function (group, permission){ + if ($scope.group_danger){ + if (permission.value == 'users.can_see_name' || + permission.value == 'users.can_manage'){ + return $scope.group_danger == group; + } + } + return false; + }; // delete selected group $scope.delete = function (group) { Group.destroy(group.id); }; + + // save changed permission + $scope.changePermission = function (group, perm) { + if (!$scope.danger(group, perm)) { + if (!$scope.hasPerm(group, perm)) { // activate perm + group.permissions.push(perm.value); + } else { + // delete perm in group.permissions + group.permissions = _.filter(group.permissions, function(value) { + return value != perm.value; // remove perm + }); + } + Group.save(group); + } + }; + + $scope.openDialog = function (group) { + var resolve; + if (group) { + resolve = { + group: function() {return Group.find(group.id);} + }; + } + ngDialog.open({ + template: 'static/templates/users/group-edit.html', + controller: group ? 'GroupRenameCtrl' : 'GroupCreateCtrl', + className: 'ngdialog-theme-default wide-form', + closeByEscape: false, + closeByDocument: false, + resolve: (resolve) ? resolve : null + }); + }; + } +]) + +.controller('GroupRenameCtrl', [ + '$scope', + 'Group', + 'group', + function($scope, Group, group) { + $scope.group = group; + $scope.new_name = group.name; + + $scope.alert = {}; + $scope.save = function() { + var old_name = $scope.group.name; + $scope.group.name = $scope.new_name; + Group.save($scope.group).then( + function (success) { + $scope.closeThisDialog(); + }, + function (error) { + var message = ''; + for (var e in error.data) { + message += e + ': ' + error.data[e] + ' '; + } + $scope.alert = { msg: message, show: true }; + $scope.group.name = old_name; + } + ); + }; } ]) .controller('GroupCreateCtrl', [ '$scope', - '$state', 'Group', - 'permissions', - function($scope, $state, Group, permissions) { - // get all permissions - $scope.permissions = permissions; - $scope.group = {}; - $scope.save = function (group) { - if (!group.permissions) { - group.permissions = []; - } + function($scope, Group) { + $scope.new_name = ''; + $scope.alert = {}; + + $scope.save = function() { + var group = { + name: $scope.new_name, + permissions: [] + }; + Group.create(group).then( - function(success) { - $state.go('users.group.list'); + function (success) { + $scope.closeThisDialog(); + }, + function (error) { + var message = ''; + for (var e in error.data) { + message += e + ': ' + error.data[e] + ' '; + } + $scope.alert = { msg: message, show: true }; } ); }; } ]) -.controller('GroupUpdateCtrl', [ - '$scope', - '$state', - 'Group', - 'permissions', - 'group', - function($scope, $state, Group, permissions, group) { - // get all permissions - $scope.permissions = permissions; - $scope.group = group; // autoupdate is not activated - $scope.save = function (group) { - Group.save(group).then( - function(success) { - $state.go('users.group.list'); - } - ); - }; - } -]) - -.controller('GroupDetailCtrl', [ - '$scope', - 'Group', - 'group', - 'permissions', - function($scope, Group, group, permissions) { - Group.bindOne(group.id, $scope, 'group'); - $scope.groupPermissionNames = []; - // get display names of group permissions - // from an object array with all available permissions [{display_name, value}] - angular.forEach(group.permissions, function(permValue) { - angular.forEach(permissions, function(p) { - if (p.value == permValue) { - $scope.groupPermissionNames.push(p.display_name); - } - }); - }); - } -]) - .controller('userMenu', [ '$scope', '$http', diff --git a/openslides/users/static/templates/users/group-detail.html b/openslides/users/static/templates/users/group-detail.html deleted file mode 100644 index 578170220..000000000 --- a/openslides/users/static/templates/users/group-detail.html +++ /dev/null @@ -1,19 +0,0 @@ -
-
- -

{{ group.name | translate }}

-

Group

-
-
- -
-

Permissions:

- -
diff --git a/openslides/users/static/templates/users/group-edit.html b/openslides/users/static/templates/users/group-edit.html new file mode 100644 index 000000000..22aec3691 --- /dev/null +++ b/openslides/users/static/templates/users/group-edit.html @@ -0,0 +1,27 @@ +

Edit name

+

Create new group

+ + {{ alert.msg }} + + +
+
+ +
+
+ + +
+
diff --git a/openslides/users/static/templates/users/group-form.html b/openslides/users/static/templates/users/group-form.html deleted file mode 100644 index 260108fe3..000000000 --- a/openslides/users/static/templates/users/group-form.html +++ /dev/null @@ -1,34 +0,0 @@ -
-
- -

Edit group

-

New group

-
-
- -
-
-
- - -
-
- - -
- - - -
-
diff --git a/openslides/users/static/templates/users/group-list.html b/openslides/users/static/templates/users/group-list.html index a36ca945e..564cbbf32 100644 --- a/openslides/users/static/templates/users/group-list.html +++ b/openslides/users/static/templates/users/group-list.html @@ -1,7 +1,8 @@
-
-
-
-
-
- -
-
-
-
- - +

+ All your changes are saved immediately. Changes you make are only effective once you (or the users concerned) reload the page. +

+
- - - - + + +
- ID - - - Name - - Actions
{{ group.id }} - {{ group.name | translate }} - - - - - - - +

Permissions

+
+ + {{ group.name | translate }} + + + {{ group.name | translate | limitTo: 1 }}... + + +
+ + + +   + + - - + {{ group.name | translate }}" + ng-bootbox-confirm-action="delete(group)" translate> + + +
+
+ {{ app.app_name | translate}} + + + +
+ {{ permission.display_name | translate }} + + + + + + +
diff --git a/openslides/users/static/templates/users/user-import.html b/openslides/users/static/templates/users/user-import.html index ffb54e7cb..b20f36111 100644 --- a/openslides/users/static/templates/users/user-import.html +++ b/openslides/users/static/templates/users/user-import.html @@ -70,9 +70,9 @@
  • Required comma or semicolon separated values with these column header names in the first row:
    title, first_name, last_name, structure_level, number, groups, comment, is_active, is_committee
  • Default groups: - Delegates 3, - Staff 4 - Committees 5 + Delegates 2, + Staff 3 + Committees 4
  • At least first name or last name have to be filled in. All other fields are optional and may be empty.
  • Only double quotes are accepted as text delimiter (no single quotes). diff --git a/tests/integration/motions/test_viewset.py b/tests/integration/motions/test_viewset.py index f18c5aaf2..eeafc4b03 100644 --- a/tests/integration/motions/test_viewset.py +++ b/tests/integration/motions/test_viewset.py @@ -150,7 +150,7 @@ class RetrieveMotion(TestCase): self.motion.create_poll() def test_number_of_queries(self): - with self.assertNumQueries(16): + with self.assertNumQueries(17): self.client.get(reverse('motion-detail', args=[self.motion.pk])) @@ -312,7 +312,7 @@ class SupportMotion(TestCase): """ def setUp(self): self.admin = get_user_model().objects.get(username='admin') - self.admin.groups.add(3) + self.admin.groups.add(2) self.client.login(username='admin', password='admin') self.motion = Motion( title='test_title_chee7ahCha6bingaew4e', diff --git a/tests/integration/users/test_viewset.py b/tests/integration/users/test_viewset.py index 759f2e477..bf74eb58a 100644 --- a/tests/integration/users/test_viewset.py +++ b/tests/integration/users/test_viewset.py @@ -44,7 +44,7 @@ class UserCreate(TestCase): def test_creation_with_group(self): self.client.login(username='admin', password='admin') # These are the builtin groups 'Delegates' and 'Staff'. The pks are valid. - group_pks = (3, 4,) + group_pks = (2, 3,) self.client.post( reverse('user-list'), @@ -55,19 +55,19 @@ class UserCreate(TestCase): self.assertTrue(user.groups.filter(pk=group_pks[0]).exists()) self.assertTrue(user.groups.filter(pk=group_pks[1]).exists()) - def test_creation_with_anonymous_or_registered_group(self): + def test_creation_with_default_group(self): self.client.login(username='admin', password='admin') - # These are the builtin groups 'Anonymous' and 'Registered'. - # The pks are valid. But these groups can not be added to users. - group_pks = (1, 2,) + # This is the builtin groups 'default'. + # The pk is valid. But this group can not be added to users. + group_pk = (1,) response = self.client.post( reverse('user-list'), {'last_name': 'Test name aedah1iequoof0Ashed4', - 'groups_id': group_pks}) + 'groups_id': group_pk}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data, {'groups_id': ["Invalid pk \"%d\" - object does not exist." % group_pks[0]]}) + self.assertEqual(response.data, {'groups_id': ["Invalid pk \"%d\" - object does not exist." % group_pk]}) class UserUpdate(TestCase): @@ -211,8 +211,8 @@ class GroupReceive(TestCase): user = User(username='test') user.set_password('test') user.save() - registered_group = Group.objects.get(pk=2) - registered_group.permissions.all().delete() + default_group = Group.objects.get(pk=1) + default_group.permissions.all().delete() self.client.login(username='test', password='test') response = self.client.get('/rest/users/group/') @@ -277,7 +277,7 @@ class GroupUpdate(TestCase): admin_client = APIClient() admin_client.login(username='admin', password='admin') # This is the builtin group 'Delegates'. The pk is valid. - group_pk = 3 + group_pk = 2 # This contains one valid permission of the users app. permissions = ('users.can_see_name',) @@ -295,7 +295,7 @@ class GroupUpdate(TestCase): admin_client = APIClient() admin_client.login(username='admin', password='admin') # This is the builtin group 'Delegates'. The pk is valid. - group_pk = 3 + group_pk = 2 # This contains one valid permission of the users app. permissions = ('users.can_see_name',) @@ -324,9 +324,8 @@ class GroupDelete(TestCase): def test_delete_builtin_groups(self): admin_client = APIClient() admin_client.login(username='admin', password='admin') - # The pks of builtin groups 'Anonymous' and 'Registered' - group_pks = (1, 2,) + # The pk of builtin group 'Default' + group_pk = 1 - for group_pk in group_pks: - response = admin_client.delete(reverse('group-detail', args=[group_pk])) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + response = admin_client.delete(reverse('group-detail', args=[group_pk])) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/tests/unit/users/test_models.py b/tests/unit/users/test_models.py index 9dabef794..0d005da24 100644 --- a/tests/unit/users/test_models.py +++ b/tests/unit/users/test_models.py @@ -290,12 +290,10 @@ class UserManagerGeneratePassword(TestCase): class UserManagerCreateOrResetAdminUser(TestCase): def test_get_admin_group(self, mock_group): """ - Tests that the Group with pk=4 is added to the admin. + Tests that the Group with pk=3 is added to the admin. """ def mock_side_effect(pk): - if pk == 2: - result = 'mock_registered' - elif pk == 4: + if pk == 3: result = 'mock_staff' else: result = '' @@ -308,7 +306,7 @@ class UserManagerCreateOrResetAdminUser(TestCase): manager.create_or_reset_admin_user() - mock_group.objects.get.assert_called_once_with(pk=4) + mock_group.objects.get.assert_called_once_with(pk=3) admin_user.groups.add.assert_called_once_with('mock_staff') def test_password_set_to_admin(self, mock_group):