diff --git a/plinth/forms.py b/plinth/forms.py index 045c9231c..5bf5e739f 100644 --- a/plinth/forms.py +++ b/plinth/forms.py @@ -4,13 +4,10 @@ Common forms for use by modules. """ import os -from itertools import chain from django import forms from django.conf import settings -from django.forms import CheckboxInput from django.utils import translation -from django.utils.safestring import mark_safe from django.utils.translation import get_language_info from django.utils.translation import gettext_lazy as _ @@ -81,47 +78,25 @@ class LanguageSelectionForm(LanguageSelectionFormMixin, forms.Form): language = LanguageSelectionFormMixin.language -def _get_value_in_parens(string): - return string[string.find("(") + 1:string.find(")")] - - class CheckboxSelectMultipleWithReadOnly(forms.widgets.CheckboxSelectMultiple): - """ - Subclass of Django's CheckboxSelectMultiple widget that allows setting - individual fields as readonly + """Multiple checkbox widget that allows setting individual fields readonly. + To mark a feature as readonly an option, pass a dict instead of a string - for its label, of the form: {'label': 'option label', 'disabled': True} + for its label, of the form: {'label': 'option label', 'disabled': True}. - Derived from https://djangosnippets.org/snippets/2786/ """ - def render(self, name, value, attrs=None, choices=(), renderer=None): - if value is None: - value = [] - final_attrs = self.build_attrs(attrs) - output = [u'') - return mark_safe(u'\n'.join(output)) + def create_option(self, name, value, label, selected, index, subindex=None, + attrs=None): + option = super().create_option(name, value, label, selected, index, + subindex, attrs) + if isinstance(option['label'], dict): + if option['label'].get('disabled'): + option['attrs']['disabled'] = 'disabled' + + option['label'] = option['label']['label'] + + return option class CheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple): diff --git a/plinth/modules/users/forms.py b/plinth/modules/users/forms.py index 3c2333685..6da76ec07 100644 --- a/plinth/modules/users/forms.py +++ b/plinth/modules/users/forms.py @@ -210,19 +210,21 @@ class UserUpdateForm(ValidNewUsernameCheckMixin, PasswordConfirmForm, }) 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 - for c in sorted(self.fields['groups'].choices, key=lambda x: x[1]): - # Handle case where groups exist in database for - # applications not installed yet. - if c[1] in group_choices: - # Replace group names with descriptions - if c[1] == 'admin' and self.is_last_admin_user: - choices.append((c[0], { - 'label': group_choices[c[1]], - 'readonly': True - })) - else: - choices.append((c[0], group_choices[c[1]])) + # 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 @@ -323,18 +325,20 @@ class UserUpdateForm(ValidNewUsernameCheckMixin, PasswordConfirmForm, return user - def validate_last_admin_user(self, groups): - group_names = [group.name for group in groups] - if 'admin' not in group_names: - raise ValidationError( - _('Cannot delete the only administrator in the system.')) + def clean_groups(self): + """Validate groups to ensure admin group for last admin. - def clean(self): - """Override clean to add form validation logic.""" - cleaned_data = super().clean() + For the last admin user, we disable the checkbox for 'admin' group so + that it can't be unchecked. However, this means that browser will no + longer submit that value. Forcefully add 'admin' group in this case. + + """ + groups = self.cleaned_data['groups'] if self.is_last_admin_user: - self.validate_last_admin_user(cleaned_data.get("groups")) - return cleaned_data + groups = groups | self.fields['groups'].queryset.filter( + **{'name': 'admin'}) + + return groups class UserChangePasswordForm(PasswordConfirmForm, SetPasswordForm):