Group add/remove bulk view
This commit is contained in:
parent
20dc306106
commit
bbb8a84f5c
@ -263,6 +263,21 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTi
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alters groups of all given users. Either adds or removes the given groups.
|
||||||
|
*
|
||||||
|
* @param users Affected users
|
||||||
|
* @param action add or remove the groups
|
||||||
|
* @param groupIds All group ids to add or remove
|
||||||
|
*/
|
||||||
|
public async bulkAlterGroups(users: ViewUser[], action: 'add' | 'remove', groupIds: number[]): Promise<void> {
|
||||||
|
await this.httpService.post('/rest/users/user/bulk_alter_groups/', {
|
||||||
|
user_ids: users.map(user => user.id),
|
||||||
|
action: action,
|
||||||
|
group_ids: groupIds
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends invitation emails to all given users. Returns a prepared string to show the user.
|
* Sends invitation emails to all given users. Returns a prepared string to show the user.
|
||||||
* This string should always be shown, becuase even in success cases, some users may not get
|
* This string should always be shown, becuase even in success cases, some users may not get
|
||||||
|
@ -290,8 +290,7 @@
|
|||||||
<div *ngIf="user.groups && user.groups.length > 0">
|
<div *ngIf="user.groups && user.groups.length > 0">
|
||||||
<h4 translate>Groups</h4>
|
<h4 translate>Groups</h4>
|
||||||
<span *ngFor="let group of user.groups; let last = last">
|
<span *ngFor="let group of user.groups; let last = last">
|
||||||
{{ group.getTitle() | translate }}
|
{{ group.getTitle() | translate }}<span *ngIf="!last">, </span>
|
||||||
<span *ngIf="!last">, </span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -302,18 +302,8 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
|
|||||||
const choices = [_('add group(s)'), _('remove group(s)')];
|
const choices = [_('add group(s)'), _('remove group(s)')];
|
||||||
const selectedChoice = await this.choiceService.open(content, this.groupRepo.getViewModelList(), true, choices);
|
const selectedChoice = await this.choiceService.open(content, this.groupRepo.getViewModelList(), true, choices);
|
||||||
if (selectedChoice) {
|
if (selectedChoice) {
|
||||||
for (const user of this.selectedRows) {
|
const action = selectedChoice.action === choices[0] ? 'add' : 'remove';
|
||||||
const newGroups = [...user.groups_id];
|
await this.repo.bulkAlterGroups(this.selectedRows, action, selectedChoice.items as number[]);
|
||||||
(selectedChoice.items as number[]).forEach(newChoice => {
|
|
||||||
const idx = newGroups.indexOf(newChoice);
|
|
||||||
if (idx < 0 && selectedChoice.action === choices[0]) {
|
|
||||||
newGroups.push(newChoice);
|
|
||||||
} else if (idx >= 0 && selectedChoice.action === choices[1]) {
|
|
||||||
newGroups.splice(idx, 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await this.repo.update({ groups_id: newGroups }, user);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +82,7 @@ class UserViewSet(ModelViewSet):
|
|||||||
"bulk_generate_passwords",
|
"bulk_generate_passwords",
|
||||||
"bulk_reset_passwords_to_default",
|
"bulk_reset_passwords_to_default",
|
||||||
"bulk_set_state",
|
"bulk_set_state",
|
||||||
|
"bulk_alter_groups",
|
||||||
"bulk_delete",
|
"bulk_delete",
|
||||||
"mass_import",
|
"mass_import",
|
||||||
"mass_invite_email",
|
"mass_invite_email",
|
||||||
@ -246,6 +247,40 @@ class UserViewSet(ModelViewSet):
|
|||||||
|
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
|
@list_route(methods=["post"])
|
||||||
|
def bulk_alter_groups(self, request):
|
||||||
|
"""
|
||||||
|
Adds or removes groups from given users. The request user is excluded.
|
||||||
|
Expected data:
|
||||||
|
{
|
||||||
|
user_ids: <list of ids>,
|
||||||
|
action: "add" | "remove",
|
||||||
|
group_ids: <list of ids>
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
user_ids = request.data.get("user_ids")
|
||||||
|
self.assert_list_of_ints(user_ids)
|
||||||
|
group_ids = request.data.get("group_ids")
|
||||||
|
self.assert_list_of_ints(group_ids, ids_name="groups_id")
|
||||||
|
|
||||||
|
action = request.data.get("action")
|
||||||
|
if action not in ("add", "remove"):
|
||||||
|
raise ValidationError({"detail": "The action must be add or remove"})
|
||||||
|
|
||||||
|
users = User.objects.exclude(pk=request.user.id).filter(pk__in=user_ids)
|
||||||
|
groups = list(Group.objects.filter(pk__in=group_ids))
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
if action == "add":
|
||||||
|
user.groups.add(*groups)
|
||||||
|
else:
|
||||||
|
user.groups.remove(*groups)
|
||||||
|
# Maybe some group assignments have changed. Better delete the restricted user cache
|
||||||
|
async_to_sync(element_cache.del_user)(user.pk)
|
||||||
|
|
||||||
|
inform_changed_data(users)
|
||||||
|
return Response()
|
||||||
|
|
||||||
@list_route(methods=["post"])
|
@list_route(methods=["post"])
|
||||||
def bulk_delete(self, request):
|
def bulk_delete(self, request):
|
||||||
"""
|
"""
|
||||||
@ -357,13 +392,13 @@ class UserViewSet(ModelViewSet):
|
|||||||
{"count": len(success_users), "no_email_ids": user_pks_without_email}
|
{"count": len(success_users), "no_email_ids": user_pks_without_email}
|
||||||
)
|
)
|
||||||
|
|
||||||
def assert_list_of_ints(self, ids):
|
def assert_list_of_ints(self, ids, ids_name="user_ids"):
|
||||||
""" Asserts, that ids is a list of ints. Raises a ValidationError, if not. """
|
""" Asserts, that ids is a list of ints. Raises a ValidationError, if not. """
|
||||||
if not isinstance(ids, list):
|
if not isinstance(ids, list):
|
||||||
raise ValidationError({"detail": "user_ids must be a list"})
|
raise ValidationError({"detail": f"{ids_name} must be a list"})
|
||||||
for id in ids:
|
for id in ids:
|
||||||
if not isinstance(id, int):
|
if not isinstance(id, int):
|
||||||
raise ValidationError({"detail": "every id must be a int"})
|
raise ValidationError({"detail": "Every id must be a int"})
|
||||||
|
|
||||||
|
|
||||||
class GroupViewSetMetadata(SimpleMetadata):
|
class GroupViewSetMetadata(SimpleMetadata):
|
||||||
|
@ -9,7 +9,12 @@ from openslides.users.models import Group, PersonalNote, User
|
|||||||
from openslides.utils.autoupdate import inform_changed_data
|
from openslides.utils.autoupdate import inform_changed_data
|
||||||
from openslides.utils.test import TestCase
|
from openslides.utils.test import TestCase
|
||||||
|
|
||||||
from ...common_groups import GROUP_DEFAULT_PK, GROUP_DELEGATE_PK, GROUP_STAFF_PK
|
from ...common_groups import (
|
||||||
|
GROUP_ADMIN_PK,
|
||||||
|
GROUP_DEFAULT_PK,
|
||||||
|
GROUP_DELEGATE_PK,
|
||||||
|
GROUP_STAFF_PK,
|
||||||
|
)
|
||||||
from ..helpers import count_queries
|
from ..helpers import count_queries
|
||||||
|
|
||||||
|
|
||||||
@ -421,6 +426,80 @@ class UserBulkSetState(TestCase):
|
|||||||
self.assertTrue(User.objects.get().is_committee)
|
self.assertTrue(User.objects.get().is_committee)
|
||||||
|
|
||||||
|
|
||||||
|
class UserBulkAlterGroups(TestCase):
|
||||||
|
"""
|
||||||
|
Tests altering groups of users.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client = APIClient()
|
||||||
|
self.client.login(username="admin", password="admin")
|
||||||
|
self.admin = User.objects.get()
|
||||||
|
self.user = User.objects.create(username="Test name apfj31fa0ovmc8cqc8e8")
|
||||||
|
|
||||||
|
def test_add(self):
|
||||||
|
self.assertEqual(self.user.groups.count(), 0)
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("user-bulk-alter-groups"),
|
||||||
|
{
|
||||||
|
"user_ids": [self.user.pk],
|
||||||
|
"action": "add",
|
||||||
|
"group_ids": [GROUP_DELEGATE_PK, GROUP_STAFF_PK],
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(self.user.groups.count(), 2)
|
||||||
|
self.assertTrue(self.user.groups.filter(pk=GROUP_DELEGATE_PK).exists())
|
||||||
|
self.assertTrue(self.user.groups.filter(pk=GROUP_STAFF_PK).exists())
|
||||||
|
|
||||||
|
def test_remove(self):
|
||||||
|
groups = Group.objects.filter(
|
||||||
|
pk__in=[GROUP_DEFAULT_PK, GROUP_DELEGATE_PK, GROUP_STAFF_PK]
|
||||||
|
)
|
||||||
|
self.user.groups.set(groups)
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("user-bulk-alter-groups"),
|
||||||
|
{
|
||||||
|
"user_ids": [self.user.pk],
|
||||||
|
"action": "remove",
|
||||||
|
"group_ids": [GROUP_DEFAULT_PK, GROUP_STAFF_PK],
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(self.user.groups.count(), 1)
|
||||||
|
self.assertTrue(self.user.groups.filter(pk=GROUP_DELEGATE_PK).exists())
|
||||||
|
|
||||||
|
def test_no_request_user(self):
|
||||||
|
self.assertEqual(self.admin.groups.count(), 1)
|
||||||
|
self.assertEqual(self.admin.groups.get().pk, GROUP_ADMIN_PK)
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("user-bulk-alter-groups"),
|
||||||
|
{
|
||||||
|
"user_ids": [self.admin.pk],
|
||||||
|
"action": "add",
|
||||||
|
"group_ids": [GROUP_DELEGATE_PK],
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(self.admin.groups.count(), 1)
|
||||||
|
self.assertEqual(self.admin.groups.get().pk, GROUP_ADMIN_PK)
|
||||||
|
|
||||||
|
def test_invalid_action(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("user-bulk-alter-groups"),
|
||||||
|
{
|
||||||
|
"user_ids": [self.admin.pk],
|
||||||
|
"action": "invalid",
|
||||||
|
"group_ids": [GROUP_DELEGATE_PK],
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
class UserMassImport(TestCase):
|
class UserMassImport(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests mass import of users.
|
Tests mass import of users.
|
||||||
|
Loading…
Reference in New Issue
Block a user