Merge pull request #4861 from FinnStutzenstein/userBulkViews

Bulk views for users: state, password and delete
This commit is contained in:
Finn Stutzenstein 2019-07-23 10:25:15 +02:00 committed by GitHub
commit 20dc306106
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 428 additions and 161 deletions

View File

@ -118,6 +118,21 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTi
return this.translate.instant(plural ? 'Participants' : 'Participant');
};
/**
* Generates a random password
*
* @param length The length of the password to generate
* @returns a random password
*/
public getRandomPassword(length: number = 10): string {
let pw = '';
const characters = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
for (let i = 0; i < length; i++) {
pw += characters.charAt(Math.floor(Math.random() * characters.length));
}
return pw;
}
public createViewModel(user: User): ViewUser {
const groups = this.viewModelStoreService.getMany(ViewGroup, user.groups_id);
const viewUser = new ViewUser(user, groups);
@ -154,37 +169,6 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTi
return await this.dataSend.updateModel(updateUser);
}
/**
* Creates and saves a list of users in a bulk operation.
*
* @param newEntries
*/
public async bulkCreate(newEntries: NewEntry<ViewUser>[]): Promise<number[]> {
const data = newEntries.map(entry => {
return { ...entry.newEntry.user, importTrackId: entry.importTrackId };
});
const response = (await this.httpService.post(`/rest/users/user/mass_import/`, { users: data })) as {
detail: string;
importedTrackIds: number[];
};
return response.importedTrackIds;
}
/**
* Generates a random password
*
* @param length THe length of the password to generate
* @returns a random password
*/
public getRandomPassword(length: number = 8): string {
let pw = '';
const characters = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
for (let i = 0; i < length; i++) {
pw += characters.charAt(Math.floor(Math.random() * characters.length));
}
return pw;
}
/**
* Updates the password and sets the password without checking for the old one.
* Also resets the 'default password' to the newly created one.
@ -193,13 +177,9 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTi
* @param password The password to set
* @param updateDefaultPassword Control, if the default password should be updated.
*/
public async resetPassword(
user: ViewUser,
password: string,
updateDefaultPassword: boolean = false
): Promise<void> {
public async resetPassword(user: ViewUser, password: string): Promise<void> {
const path = `/rest/users/user/${user.id}/reset_password/`;
await this.httpService.post(path, { password: password, update_default_password: updateDefaultPassword });
await this.httpService.post(path, { password: password });
}
/**
@ -215,6 +195,74 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTi
});
}
/**
* Resets the passwords of all given users to their default ones. The operator will
* not be changed (if provided in `users`).
*
* @param users The users to reset the passwords from
*/
public async bulkResetPasswordsToDefault(users: ViewUser[]): Promise<void> {
await this.httpService.post('/rest/users/user/bulk_reset_passwords_to_default/', {
user_ids: users.map(user => user.id)
});
}
/**
* Generates new random passwords for many users. The default password will be set to these. The
* operator will not be changed (if provided in `users`).
*
* @param users The users to generate new passwords for
*/
public async bulkGenerateNewPasswords(users: ViewUser[]): Promise<void> {
await this.httpService.post('/rest/users/user/bulk_generate_passwords/', {
user_ids: users.map(user => user.id)
});
}
/**
* Creates and saves a list of users in a bulk operation.
*
* @param newEntries
*/
public async bulkCreate(newEntries: NewEntry<ViewUser>[]): Promise<number[]> {
const data = newEntries.map(entry => {
return { ...entry.newEntry.user, importTrackId: entry.importTrackId };
});
const response = (await this.httpService.post(`/rest/users/user/mass_import/`, { users: data })) as {
detail: string;
importedTrackIds: number[];
};
return response.importedTrackIds;
}
/**
* Deletes many users. The operator will not be deleted (if included in `uisers`)
*
* @param users The users to delete
*/
public async bulkDelete(users: ViewUser[]): Promise<void> {
await this.httpService.post('/rest/users/user/bulk_delete/', { user_ids: users.map(user => user.id) });
}
/**
* Sets the state of many users. The "state" means any boolean attribute of a user like active or committee.
*
* @param users The users to set the state
* @param field The boolean field to set
* @param value The value to set this field to.
*/
public async bulkSetState(
users: ViewUser[],
field: 'is_active' | 'is_present' | 'is_committee',
value: boolean
): Promise<void> {
await this.httpService.post('/rest/users/user/bulk_set_state/', {
user_ids: users.map(user => user.id),
field: field,
value: value
});
}
/**
* 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
@ -222,7 +270,7 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTi
*
* @param users All affected users
*/
public async sendInvitationEmail(users: ViewUser[]): Promise<string> {
public async bulkSendInvitationEmail(users: ViewUser[]): Promise<string> {
const user_ids = users.map(user => user.id);
const subject = this.translate.instant(this.configService.instant('users_email_subject'));
const message = this.translate.instant(this.configService.instant('users_email_body'));

View File

@ -441,7 +441,7 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
const title = this.translate.instant('Sending an invitation email');
const content = this.translate.instant('Are you sure you want to send an invitation email to the user?');
if (await this.promptService.open(title, content)) {
this.repo.sendInvitationEmail([this.user]).then(this.raiseError, this.raiseError);
this.repo.bulkSendInvitationEmail([this.user]).then(this.raiseError, this.raiseError);
}
}

View File

@ -181,17 +181,17 @@
<div *osPerms="'users.can_manage'">
<mat-divider></mat-divider>
<button mat-menu-item [disabled]="!selectedRows.length" (click)="setActiveSelected()">
<button mat-menu-item [disabled]="!selectedRows.length" (click)="setStateSelected('is_active')">
<mat-icon>block</mat-icon>
<span translate>Enable/disable account ...</span>
</button>
<button mat-menu-item [disabled]="!selectedRows.length" (click)="setPresentSelected()">
<button mat-menu-item [disabled]="!selectedRows.length" (click)="setStateSelected('is_present')">
<mat-icon>check_box</mat-icon>
<span translate>Set presence ...</span>
</button>
<button mat-menu-item [disabled]="!selectedRows.length" (click)="setCommitteeSelected()">
<button mat-menu-item [disabled]="!selectedRows.length" (click)="setStateSelected('is_committee')">
<mat-icon>account_balance</mat-icon>
<span translate>Set committee ...</span>
</button>
@ -203,7 +203,11 @@
<span translate>Send invitation email</span>
</button>
<button mat-menu-item [disabled]="!selectedRows.length" (click)="resetPasswordsSelected()">
<button mat-menu-item [disabled]="!selectedRows.length" (click)="resetPasswordsToDefaultSelected()">
<mat-icon>vpn_key</mat-icon>
<span translate>Reset passwords to the default ones</span>
</button>
<button mat-menu-item [disabled]="!selectedRows.length" (click)="generateNewPasswordsPasswordsSelected()">
<mat-icon>vpn_key</mat-icon>
<span translate>Generate new passwords</span>
</button>

View File

@ -287,9 +287,7 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
public async deleteSelected(): Promise<void> {
const title = this.translate.instant('Are you sure you want to delete all selected participants?');
if (await this.promptService.open(title)) {
for (const user of this.selectedRows) {
await this.repo.delete(user);
}
await this.repo.bulkDelete(this.selectedRows);
}
}
@ -323,47 +321,29 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
* Handler for bulk setting/unsetting the 'active' attribute.
* Uses selectedRows defined via multiSelect mode.
*/
public async setActiveSelected(): Promise<void> {
const content = this.translate.instant('Set active status for selected participants:');
const options = [_('active'), _('inactive')];
const selectedChoice = await this.choiceService.open(content, null, false, options);
if (selectedChoice) {
const active = selectedChoice.action === options[0];
for (const user of this.selectedRows) {
await this.repo.update({ is_active: active }, user);
}
public async setStateSelected(field: 'is_active' | 'is_present' | 'is_committee'): Promise<void> {
let options: [string, string];
let verboseStateName: string;
switch (field) {
case 'is_active':
options = [_('active'), _('inactive')];
verboseStateName = 'active';
break;
case 'is_present':
options = [_('present'), _('absent')];
verboseStateName = 'present';
break;
case 'is_committee':
options = [_('committee'), _('no committee')];
verboseStateName = 'committee';
break;
}
}
const content = this.translate.instant(`Set ${verboseStateName} status for selected participants:`);
/**
* Handler for bulk setting/unsetting the 'is present' attribute.
* Uses selectedRows defined via multiSelect mode.
*/
public async setPresentSelected(): Promise<void> {
const content = this.translate.instant('Set presence status for selected participants:');
const options = [_('present'), _('absent')];
const selectedChoice = await this.choiceService.open(content, null, false, options);
if (selectedChoice) {
const present = selectedChoice.action === options[0];
for (const user of this.selectedRows) {
await this.repo.update({ is_present: present }, user);
}
}
}
/**
* Handler for bulk setting/unsetting the 'is committee' attribute.
* Uses selectedRows defined via multiSelect mode.
*/
public async setCommitteeSelected(): Promise<void> {
const content = this.translate.instant('Set committee status for selected participants:');
const options = [_('committee'), _('no committee')];
const selectedChoice = await this.choiceService.open(content, null, false, options);
if (selectedChoice) {
const committee = selectedChoice.action === options[0];
for (const user of this.selectedRows) {
await this.repo.update({ is_committee: committee }, user);
}
const value = selectedChoice.action === options[0];
await this.repo.bulkSetState(this.selectedRows, field, value);
}
}
@ -375,7 +355,7 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
const title = this.translate.instant('Are you sure you want to send emails to all selected participants?');
const content = this.selectedRows.length + ' ' + this.translate.instant('emails');
if (await this.promptService.open(title, content)) {
this.repo.sendInvitationEmail(this.selectedRows).then(this.raiseError, this.raiseError);
await this.repo.bulkSendInvitationEmail(this.selectedRows);
}
}
@ -393,9 +373,14 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
}
/**
* Handler for bulk setting new passwords. Needs multiSelect mode.
* Handler for bulk resetting passwords to the default ones. Needs multiSelect mode.
*/
public async resetPasswordsSelected(): Promise<void> {
public async resetPasswordsToDefaultSelected(): Promise<void> {
const title = this.translate.instant('Are you sure you want to reset all passwords to the default ones?');
if (!(await this.promptService.open(title))) {
return;
}
if (this.selectedRows.find(row => row.user.id === this.operator.user.id)) {
this.raiseError(
this.translate.instant(
@ -403,10 +388,31 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
)
);
}
for (const user of this.selectedRows.filter(u => u.user.id !== this.operator.user.id)) {
const password = this.repo.getRandomPassword();
this.repo.resetPassword(user, password, true);
this.repo.bulkResetPasswordsToDefault(this.selectedRows).then(null, this.raiseError);
}
/**
* Handler for bulk generating new passwords. Needs multiSelect mode.
*/
public async generateNewPasswordsPasswordsSelected(): Promise<void> {
const title = this.translate.instant(
'Are you sure you want to generate new passwords for all selected participants?'
);
const content = this.translate.instant(
'Note, that the default password will be changed to the new generated one.'
);
if (!(await this.promptService.open(title, content))) {
return;
}
if (this.selectedRows.find(row => row.user.id === this.operator.user.id)) {
this.raiseError(
this.translate.instant(
'Note: Your own password was not changed. Please use the password change dialog instead.'
)
);
}
this.repo.bulkGenerateNewPasswords(this.selectedRows).then(null, this.raiseError);
}
/**

View File

@ -1,5 +1,4 @@
import smtplib
from random import choice
from django.conf import settings
from django.contrib.auth.hashers import make_password
@ -108,14 +107,6 @@ class UserManager(BaseUserManager):
return generated_username
def generate_password(self):
"""
Generates a random passwort. Do not use l, o, I, O, 1 or 0.
"""
chars = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
size = 8
return "".join([choice(chars) for i in range(size)])
class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
"""

View File

@ -97,7 +97,7 @@ class UserFullSerializer(ModelSerializer):
"""
# Prepare setup password.
if not validated_data.get("default_password"):
validated_data["default_password"] = User.objects.generate_password()
validated_data["default_password"] = User.objects.make_random_password()
validated_data["password"] = make_password(validated_data["default_password"])
return validated_data

View File

@ -79,6 +79,10 @@ class UserViewSet(ModelViewSet):
"create",
"destroy",
"reset_password",
"bulk_generate_passwords",
"bulk_reset_passwords_to_default",
"bulk_set_state",
"bulk_delete",
"mass_import",
"mass_invite_email",
):
@ -144,31 +148,120 @@ class UserViewSet(ModelViewSet):
@detail_route(methods=["post"])
def reset_password(self, request, pk=None):
"""
View to reset the password using the requested password.
If update_defualt_password=True is given, the new password will also be set
as the default_password.
View to reset the password of the given user (by url) using a provided password.
Expected data: { pasword: <the new password> }
"""
user = self.get_object()
password = request.data.get("password")
if not isinstance(password, str):
raise ValidationError({"detail": "Password has to be a string."})
update_default_password = request.data.get("update_default_password", False)
if not isinstance(update_default_password, bool):
raise ValidationError(
{"detail": "update_default_password has to be a boolean."}
)
try:
validate_password(password, user=request.user)
except DjangoValidationError as errors:
raise ValidationError({"detail": " ".join(errors)})
user.set_password(password)
if update_default_password:
user.default_password = password
user.save()
return Response({"detail": "Password successfully reset."})
@list_route(methods=["post"])
def bulk_generate_passwords(self, request):
"""
Generates new random passwords for many users. The request user is excluded
and the default password will be set to the new generated passwords.
Expected data: { user_ids: <list of ids> }
"""
ids = request.data.get("user_ids")
self.assert_list_of_ints(ids)
# Exclude the request user
users = User.objects.exclude(pk=request.user.id).filter(pk__in=ids)
for user in users:
password = User.objects.make_random_password()
user.set_password(password)
user.default_password = password
user.save()
return Response()
@list_route(methods=["post"])
def bulk_reset_passwords_to_default(self, request):
"""
resets the password of all given users to their default ones. The
request user is excluded.
Expected data: { user_ids: <list of ids> }
"""
ids = request.data.get("user_ids")
self.assert_list_of_ints(ids)
# Exclude the request user
users = User.objects.exclude(pk=request.user.id).filter(pk__in=ids)
# Validate all default passwords
for user in users:
try:
validate_password(user.default_password, user=user)
except DjangoValidationError as errors:
errors = " ".join(errors)
raise ValidationError(
{
"detail": f'The default password of user "{user.username}" is not valid: {errors}'
}
)
# Reset passwords
for user in users:
user.set_password(user.default_password)
user.save()
return Response()
@list_route(methods=["post"])
def bulk_set_state(self, request):
"""
Sets the "state" of may users. The "state" means boolean attributes like active
or committee of a user. If 'is_active' is choosen, the request user will be
removed from the list of user ids. Expected data:
{
user_ids: <list of ids>
field: 'is_active' | 'is_present' | 'is_committee'
value: True|False
}
"""
ids = request.data.get("user_ids")
self.assert_list_of_ints(ids)
field = request.data.get("field")
if field not in ("is_active", "is_present", "is_committee"):
raise ValidationError({"detail": "Unsupported field"})
value = request.data.get("value")
if not isinstance(value, bool):
raise ValidationError({"detail": "value must be true or false"})
users = User.objects.filter(pk__in=ids)
if field == "is_active":
users = users.exclude(pk=request.user.id)
for user in users:
setattr(user, field, value)
user.save()
return Response()
@list_route(methods=["post"])
def bulk_delete(self, request):
"""
Deletes many users. The request user will be excluded. Expected data:
{ user_ids: <list of ids> }
"""
ids = request.data.get("user_ids")
self.assert_list_of_ints(ids)
# Exclude the request user
users = User.objects.exclude(pk=request.user.id).filter(pk__in=ids)
for user in list(users):
user.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@list_route(methods=["post"])
@transaction.atomic
def mass_import(self, request):
@ -219,11 +312,7 @@ class UserViewSet(ModelViewSet):
number of emails send.
"""
user_ids = request.data.get("user_ids")
if not isinstance(user_ids, list):
raise ValidationError({"detail": "User_ids has to be a list."})
for user_id in user_ids:
if not isinstance(user_id, int):
raise ValidationError({"detail": "User_id has to be an int."})
self.assert_list_of_ints(user_ids)
# Get subject and body from the response. Do not use the config values
# because they might not be translated.
subject = request.data.get("subject")
@ -268,6 +357,14 @@ class UserViewSet(ModelViewSet):
{"count": len(success_users), "no_email_ids": user_pks_without_email}
)
def assert_list_of_ints(self, ids):
""" Asserts, that ids is a list of ints. Raises a ValidationError, if not. """
if not isinstance(ids, list):
raise ValidationError({"detail": "user_ids must be a list"})
for id in ids:
if not isinstance(id, int):
raise ValidationError({"detail": "every id must be a int"})
class GroupViewSetMetadata(SimpleMetadata):
"""

View File

@ -6,7 +6,6 @@ from rest_framework.test import APIClient
from openslides.core.config import config
from openslides.users.models import Group, PersonalNote, User
from openslides.users.serializers import UserFullSerializer
from openslides.utils.autoupdate import inform_changed_data
from openslides.utils.test import TestCase
@ -218,12 +217,14 @@ class UserDelete(TestCase):
Tests delete of users via REST API.
"""
def setUp(self):
self.admin_client = APIClient()
self.admin_client.login(username="admin", password="admin")
def test_delete(self):
admin_client = APIClient()
admin_client.login(username="admin", password="admin")
User.objects.create(username="Test name bo3zieT3iefahng0ahqu")
response = admin_client.delete(reverse("user-detail", args=["2"]))
response = self.admin_client.delete(reverse("user-detail", args=["2"]))
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertFalse(
@ -231,28 +232,50 @@ class UserDelete(TestCase):
)
def test_delete_yourself(self):
admin_client = APIClient()
admin_client.login(username="admin", password="admin")
# This is the builtin user 'Administrator'. The pk is valid.
admin_user_pk = 1
response = admin_client.delete(reverse("user-detail", args=[admin_user_pk]))
response = self.admin_client.delete(
reverse("user-detail", args=[admin_user_pk])
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_bulk_delete(self):
# create 10 users:
ids = []
for i in range(10):
user = User(username=f"user_{i}")
user.save()
ids.append(user.id)
class UserResetPassword(TestCase):
response = self.admin_client.post(
reverse("user-bulk-delete"), {"user_ids": ids}, format="json"
)
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertFalse(User.objects.filter(pk__in=ids).exists())
def test_bulk_delete_self(self):
""" The own id should be excluded, so nothing should happen. """
response = self.admin_client.post(
reverse("user-bulk-delete"), {"user_ids": [1]}, format="json"
)
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertTrue(User.objects.filter(pk=1).exists())
class UserPassword(TestCase):
"""
Tests resetting users password via REST API by a manager.
"""
def setUp(self):
self.admin_client = APIClient()
self.admin_client.login(username="admin", password="admin")
def test_reset(self):
admin_client = APIClient()
admin_client.login(username="admin", password="admin")
user = User.objects.create(username="Test name ooMoa4ou4mohn2eo1ree")
user.default_password = "new_password_Yuuh8OoQueePahngohy3"
user.save()
response = admin_client.post(
response = self.admin_client.post(
reverse("user-reset-password", args=[user.pk]),
{"password": "new_password_Yuuh8OoQueePahngohy3_new"},
)
@ -263,23 +286,139 @@ class UserResetPassword(TestCase):
)
)
"""
Tests whether a random password is set as default and actual password
if no default password is provided.
"""
def test_set_random_initial_password(self):
admin_client = APIClient()
admin_client.login(username="admin", password="admin")
"""
Tests whether a random password is set if no default password is given. The password
must be set as the default and real password.
"""
response = self.admin_client.post(
reverse("user-list"), {"username": "Test name 9gt043qwvnj2d0cr"}
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
serializer = UserFullSerializer()
user = serializer.create({"username": "Test name 9gt043qwvnj2d0cr"})
user.save()
user = User.objects.get(username="Test name 9gt043qwvnj2d0cr")
self.assertTrue(isinstance(user.default_password, str))
self.assertTrue(len(user.default_password) >= 8)
self.assertTrue(user.check_password(user.default_password))
default_password = User.objects.get(pk=user.pk).default_password
self.assertIsNotNone(default_password)
self.assertEqual(len(default_password), 8)
self.assertTrue(User.objects.get(pk=user.pk).check_password(default_password))
def test_bulk_generate_new_passwords(self):
default_password1 = "Default password e3fj3oh39hwwcbjb2qqy"
default_password2 = "Default password 32pifjmaewrelkqwelng"
user1 = User.objects.create(
username="Test name r9uJoqq1k0fk09i39elq",
default_password=default_password1,
)
user2 = User.objects.create(
username="Test name poqwhfjpofmouivg73NU",
default_password=default_password2,
)
user1.set_password(default_password1)
user2.set_password(default_password2)
self.assertTrue(user1.check_password(default_password1))
self.assertTrue(user2.check_password(default_password2))
response = self.admin_client.post(
reverse("user-bulk-generate-passwords"),
{"user_ids": [user1.id, user2.id]},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
user1 = User.objects.get(username="Test name r9uJoqq1k0fk09i39elq")
user2 = User.objects.get(username="Test name poqwhfjpofmouivg73NU")
self.assertTrue(default_password1 != user1.default_password)
self.assertTrue(default_password2 != user2.default_password)
self.assertTrue(len(user1.default_password) >= 8)
self.assertTrue(len(user2.default_password) >= 8)
self.assertTrue(user1.check_password(user1.default_password))
self.assertTrue(user2.check_password(user2.default_password))
def test_bulk_reset_passwords_to_default_ones(self):
default_password1 = "Default password e3fj3oh39hwwcbjb2qqy"
default_password2 = "Default password 32pifjmaewrelkqwelng"
user1 = User.objects.create(
username="Test name pefkjOf9m8efNspuhPFq",
default_password=default_password1,
)
user2 = User.objects.create(
username="Test name qpymcmbmntiwoE97ev7C",
default_password=default_password2,
)
user1.set_password("")
user2.set_password("")
self.assertFalse(user1.check_password(default_password1))
self.assertFalse(user2.check_password(default_password2))
response = self.admin_client.post(
reverse("user-bulk-reset-passwords-to-default"),
{"user_ids": [user1.id, user2.id]},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
user1 = User.objects.get(username="Test name pefkjOf9m8efNspuhPFq")
user2 = User.objects.get(username="Test name qpymcmbmntiwoE97ev7C")
self.assertTrue(user1.check_password(default_password1))
self.assertTrue(user2.check_password(default_password2))
class UserBulkSetState(TestCase):
"""
Tests setting states of users.
"""
def setUp(self):
self.client = APIClient()
self.client.login(username="admin", password="admin")
admin = User.objects.get()
admin.is_active = True
admin.is_present = True
admin.is_committee = True
admin.save()
def test_set_is_present(self):
response = self.client.post(
reverse("user-bulk-set-state"),
{"user_ids": [1], "field": "is_present", "value": False},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(User.objects.get().is_active)
self.assertFalse(User.objects.get().is_present)
self.assertTrue(User.objects.get().is_committee)
def test_invalid_field(self):
response = self.client.post(
reverse("user-bulk-set-state"),
{"user_ids": [1], "field": "invalid", "value": False},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertTrue(User.objects.get().is_active)
self.assertTrue(User.objects.get().is_present)
self.assertTrue(User.objects.get().is_committee)
def test_invalid_value(self):
response = self.client.post(
reverse("user-bulk-set-state"),
{"user_ids": [1], "field": "is_active", "value": "invalid"},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertTrue(User.objects.get().is_active)
self.assertTrue(User.objects.get().is_present)
self.assertTrue(User.objects.get().is_committee)
def test_set_active_not_self(self):
response = self.client.post(
reverse("user-bulk-set-state"),
{"user_ids": [1], "field": "is_active", "value": False},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(User.objects.get().is_active)
self.assertTrue(User.objects.get().is_present)
self.assertTrue(User.objects.get().is_committee)
class UserMassImport(TestCase):

View File

@ -1,5 +1,5 @@
from unittest import TestCase
from unittest.mock import MagicMock, call, patch
from unittest.mock import MagicMock, patch
from django.core.exceptions import ObjectDoesNotExist
@ -115,24 +115,6 @@ class UserManagerGenerateUsername(TestCase):
)
@patch("openslides.users.models.choice")
class UserManagerGeneratePassword(TestCase):
def test_normal(self, mock_choice):
"""
Test normal run of the method.
"""
mock_choice.side_effect = tuple("test_password")
self.assertEqual(UserManager().generate_password(), "test_pas")
# choice has to be called 8 times
mock_choice.assert_has_calls(
[
call("abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789")
for _ in range(8)
]
)
@patch("openslides.users.models.Permission")
@patch("openslides.users.models.Group")
class UserManagerCreateOrResetAdminUser(TestCase):