diff --git a/plinth/modules/users/forms.py b/plinth/modules/users/forms.py
index 41df46aa1..dea81f95e 100644
--- a/plinth/modules/users/forms.py
+++ b/plinth/modules/users/forms.py
@@ -50,6 +50,49 @@ class ValidNewUsernameCheckMixin:
return True
+class GroupsFieldMixin:
+ """Mixin to set common properties for the group field."""
+
+ def __init__(self, *args, **kwargs):
+ """Set basic properties for the groups field.
+
+ Also ensure that all the groups are created in django.
+ """
+ group_choices = dict(UsersAndGroups.get_group_choices())
+ for group in group_choices:
+ Group.objects.get_or_create(name=group)
+
+ super().__init__(*args, **kwargs)
+
+ choices = []
+ django_groups = sorted(self.fields['groups'].choices,
+ key=lambda choice: choice[1])
+ for group_id, group_name in django_groups:
+ try:
+ group_id = group_id.value
+ except AttributeError:
+ pass
+
+ # Show choices only from groups declared by apps.
+ if group_name in group_choices:
+ label = group_choices[group_name]
+ if group_name == 'admin' and self.is_last_admin_user:
+ label = {'label': label, 'disabled': True}
+
+ choices.append((group_id, label))
+
+ self.fields['groups'].label = _('Permissions')
+ self.fields['groups'].choices = choices
+ self.fields['groups'].help_text = _(
+ 'Select which services should be available to the new '
+ 'user. The user will be able to log in to services that '
+ 'support single sign-on through LDAP, if they are in the '
+ 'appropriate group.
Users in the admin group '
+ 'will be able to log in to all services. They can also '
+ 'log in to the system through SSH and have '
+ 'administrative privileges (sudo).')
+
+
@deconstructible
class UsernameValidator(validators.RegexValidator):
"""Username validator.
@@ -96,7 +139,7 @@ class PasswordConfirmForm(forms.Form):
return confirm_password
-class CreateUserForm(ValidNewUsernameCheckMixin,
+class CreateUserForm(ValidNewUsernameCheckMixin, GroupsFieldMixin,
plinth.forms.LanguageSelectionFormMixin,
PasswordConfirmForm, UserCreationForm):
"""Custom user create form.
@@ -105,17 +148,6 @@ class CreateUserForm(ValidNewUsernameCheckMixin,
"""
username = USERNAME_FIELD
- groups = forms.MultipleChoiceField(
- choices=UsersAndGroups.get_group_choices,
- label=gettext_lazy('Permissions'), required=False,
- widget=plinth.forms.CheckboxSelectMultiple, help_text=gettext_lazy(
- 'Select which services should be available to the new '
- 'user. The user will be able to log in to services that '
- 'support single sign-on through LDAP, if they are in the '
- 'appropriate group.
Users in the admin group '
- 'will be able to log in to all services. They can also '
- 'log in to the system through SSH and have '
- 'administrative privileges (sudo).'))
language = plinth.forms.LanguageSelectionFormMixin.language
@@ -124,10 +156,14 @@ class CreateUserForm(ValidNewUsernameCheckMixin,
fields = ('username', 'password1', 'password2', 'groups', 'language',
'confirm_password')
+ widgets = {
+ 'groups': plinth.forms.CheckboxSelectMultiple(),
+ }
def __init__(self, request, *args, **kwargs):
"""Initialize the form with extra request argument."""
self.request = request
+ self.is_last_admin_user = False
super().__init__(*args, **kwargs)
self.fields['username'].widget.attrs.update({
'autofocus': 'autofocus',
@@ -140,6 +176,8 @@ class CreateUserForm(ValidNewUsernameCheckMixin,
user = super().save(commit)
if commit:
+ self.save_m2m() # Django 3.x does not call save_m2m()
+
user.userprofile.language = self.cleaned_data['language']
user.userprofile.save()
auth_username = self.request.user.username
@@ -155,7 +193,8 @@ class CreateUserForm(ValidNewUsernameCheckMixin,
_('Creating LDAP user failed: {error}'.format(
error=error)))
- for group in self.cleaned_data['groups']:
+ groups = user.groups.values_list('name', flat=True)
+ for group in groups:
try:
privileged.add_user_to_group(user.get_username(), group,
auth_username,
@@ -173,7 +212,8 @@ class CreateUserForm(ValidNewUsernameCheckMixin,
class UserUpdateForm(ValidNewUsernameCheckMixin, PasswordConfirmForm,
- plinth.forms.LanguageSelectionFormMixin, forms.ModelForm):
+ GroupsFieldMixin, plinth.forms.LanguageSelectionFormMixin,
+ forms.ModelForm):
"""When user info is changed, also updates LDAP user."""
username = USERNAME_FIELD
@@ -200,39 +240,15 @@ class UserUpdateForm(ValidNewUsernameCheckMixin, PasswordConfirmForm,
def __init__(self, request, username, *args, **kwargs):
"""Initialize the form with extra request argument."""
- group_choices = dict(UsersAndGroups.get_group_choices())
- for group in group_choices:
- Group.objects.get_or_create(name=group)
-
self.request = request
self.username = username
- super().__init__(*args, **kwargs)
self.is_last_admin_user = get_last_admin_user() == self.username
+ super().__init__(*args, **kwargs)
self.fields['username'].widget.attrs.update({
'autocapitalize': 'none',
'autocomplete': 'username'
})
- choices = []
- django_groups = sorted(self.fields['groups'].choices,
- key=lambda choice: choice[1])
- for group_id, group_name in django_groups:
- try:
- group_id = group_id.value
- except AttributeError:
- pass
-
- # Show choices only from groups declared by apps.
- if group_name in group_choices:
- label = group_choices[group_name]
- if group_name == 'admin' and self.is_last_admin_user:
- label = {'label': label, 'disabled': True}
-
- choices.append((group_id, label))
-
- self.fields['groups'].label = _('Permissions')
- self.fields['groups'].choices = choices
-
if not is_user_admin(request):
self.fields['is_active'].widget = forms.HiddenInput()
self.fields['groups'].disabled = True
diff --git a/plinth/tests/functional/__init__.py b/plinth/tests/functional/__init__.py
index d2a6dcc51..2f86dae3a 100644
--- a/plinth/tests/functional/__init__.py
+++ b/plinth/tests/functional/__init__.py
@@ -614,7 +614,8 @@ def create_user(browser, name, password=None, groups=[]):
browser.find_by_id('id_password2').fill(password)
for group in groups:
- browser.find_by_id(f'id_groups_{group}').check()
+ browser.find_by_xpath(
+ f'//label[contains(text(), "({group})")]/input').check()
browser.find_by_id('id_confirm_password').fill(
config['DEFAULT']['password'])