Projet

Général

Profil

0001-manager-improve-default-role-form-fields-58699.patch

Valentin Deniaud, 04 octobre 2022 12:20

Télécharger (9,52 ko)

Voir les différences:

Subject: [PATCH] manager: improve default role form fields (#58699)

 src/authentic2/forms/fields.py         | 52 +++++++++++++++++++++
 src/authentic2/manager/app_settings.py |  1 -
 src/authentic2/manager/forms.py        | 62 ++++++++++++++++++++------
 src/authentic2/manager/role_views.py   |  4 +-
 tests/test_manager.py                  | 24 ++++++++++
 5 files changed, 126 insertions(+), 17 deletions(-)
src/authentic2/forms/fields.py
18 18
import warnings
19 19

  
20 20
import PIL.Image
21
from django import forms
22
from django.core import validators
21 23
from django.core.files import File
22 24
from django.forms import CharField, EmailField, FileField, ModelChoiceField, ValidationError
23 25
from django.forms.fields import FILE_INPUT_CONTRADICTION
......
126 128

  
127 129
    def label_from_instance(self, obj):
128 130
        return label_from_role(obj)
131

  
132

  
133
class ListValidator:
134
    def __init__(self, item_validator):
135
        self.item_validator = item_validator
136

  
137
    def __call__(self, value):
138
        for i, item in enumerate(value):
139
            try:
140
                self.item_validator(item)
141
            except ValidationError as e:
142
                raise ValidationError(_('Item {0} is invalid: {1}').format(i, e.args[0]))
143

  
144

  
145
class CommaSeparatedInput(forms.TextInput):
146
    def format_value(self, value):
147
        if not value:
148
            return ''
149
        if not isinstance(value, str):
150
            return ', '.join(value)
151
        return value
152

  
153

  
154
class CommaSeparatedCharField(forms.Field):
155
    widget = CommaSeparatedInput
156

  
157
    def __init__(self, dedup=True, max_length=None, min_length=None, *args, **kwargs):
158
        self.dedup = dedup
159
        self.max_length = max_length
160
        self.min_length = min_length
161
        item_validators = kwargs.pop('item_validators', [])
162
        super().__init__(*args, **kwargs)
163
        for item_validator in item_validators:
164
            self.validators.append(ListValidator(item_validator))
165

  
166
    def to_python(self, value):
167
        if value in validators.EMPTY_VALUES:
168
            return []
169

  
170
        value = [item.strip() for item in value.split(',') if item.strip()]
171
        if self.dedup:
172
            value = list(set(value))
173

  
174
        return value
175

  
176
    def clean(self, value):
177
        value = self.to_python(value)
178
        self.validate(value)
179
        self.run_validators(value)
180
        return value
src/authentic2/manager/app_settings.py
21 21
    __PREFIX = 'A2_MANAGER_'
22 22
    __DEFAULTS = {
23 23
        'HOMEPAGE_URL': None,
24
        'ROLE_FORM_CLASS': None,
25 24
        'SHOW_ALL_OU': True,
26 25
        'ROLE_MEMBERS_FROM_OU': False,
27 26
        'SHOW_INTERNAL_ROLES': False,
src/authentic2/manager/forms.py
30 30
from django.utils.translation import ugettext_lazy as _
31 31
from django_select2.forms import HeavySelect2Widget
32 32

  
33
from authentic2.a2_rbac.models import OrganizationalUnit, Permission, Role
33
from authentic2.a2_rbac.models import OrganizationalUnit, Permission, Role, RoleAttribute
34 34
from authentic2.a2_rbac.utils import generate_slug, get_default_ou
35
from authentic2.forms.fields import CheckPasswordField, NewPasswordField, ValidatedEmailField
35
from authentic2.forms.fields import (
36
    CheckPasswordField,
37
    CommaSeparatedCharField,
38
    NewPasswordField,
39
    ValidatedEmailField,
40
)
36 41
from authentic2.forms.mixins import SlugMixin
37 42
from authentic2.forms.profile import BaseUserForm
38 43
from authentic2.models import APIClient, PasswordReset, Service
39 44
from authentic2.passwords import generate_password
40
from authentic2.utils.misc import (
41
    import_module_or_class,
42
    send_email_change_email,
43
    send_password_reset_mail,
44
    send_templated_mail,
45
)
45
from authentic2.utils.misc import send_email_change_email, send_password_reset_mail, send_templated_mail
46
from authentic2.validators import EmailValidator
46 47
from django_rbac.backends import DjangoRBACBackend
47 48
from django_rbac.models import Operation
48 49

  
......
601 602
    ou = forms.ModelChoiceField(
602 603
        queryset=OrganizationalUnit.objects, required=True, label=_('Organizational unit')
603 604
    )
605
    details = forms.CharField(
606
        label=_('Role details (frontoffice)'), widget=forms.Textarea, initial='', required=False
607
    )
608
    emails = CommaSeparatedCharField(
609
        label=_('Emails'),
610
        item_validators=[EmailValidator()],
611
        required=False,
612
        help_text=_('Emails must be separated by commas.'),
613
    )
614
    emails_to_members = forms.BooleanField(required=False, initial=True, label=_('Emails to members'))
604 615

  
605 616
    class Meta:
606 617
        model = Role
......
609 620
            'name': forms.TextInput(),
610 621
        }
611 622

  
623
    def __init__(self, *args, **kwargs):
624
        instance = kwargs.get('instance')
625
        if instance:
626
            fields = [x.name for x in Role._meta.get_fields()]
627
            initial = kwargs.setdefault('initial', {})
628
            role_attributes = RoleAttribute.objects.filter(role=instance, kind='json')
629
            for role_attribute in role_attributes:
630
                if role_attribute.name in fields:
631
                    continue
632
                initial[role_attribute.name] = json.loads(role_attribute.value)
633
        super().__init__(*args, **kwargs)
634

  
635
    def save(self, commit=True):
636
        fields = [x.name for x in Role._meta.get_fields()]
637
        assert commit
638
        instance = super().save(commit=commit)
639
        for field in self.cleaned_data:
640
            if field in fields:
641
                continue
642
            value = json.dumps(self.cleaned_data[field])
643
            ra, created = RoleAttribute.objects.get_or_create(
644
                role=instance, name=field, kind='json', defaults={'value': value}
645
            )
646
            if not created and ra.value != value:
647
                ra.value = value
648
                ra.save()
649
        instance.save()
650
        return instance
651

  
612 652

  
613 653
class OUEditForm(SlugMixin, CssClass, forms.ModelForm):
614 654
    def __init__(self, *args, **kwargs):
......
637 677
        )
638 678

  
639 679

  
640
def get_role_form_class():
641
    if app_settings.ROLE_FORM_CLASS:
642
        return import_module_or_class(app_settings.ROLE_FORM_CLASS)
643
    return RoleEditForm
644

  
645

  
646 680
# we need a model form so that we can use a BaseEditView, a simple Form
647 681
# would not work
648 682
class UserChangeEmailForm(CssClass, FormWithRequest, forms.ModelForm):
src/authentic2/manager/role_views.py
109 109
        return initial
110 110

  
111 111
    def get_form_class(self):
112
        form = forms.get_role_form_class()
112
        form = forms.RoleEditForm
113 113
        fields = [x for x in form.base_fields.keys() if x not in self.exclude_fields]
114 114
        return modelform_factory(self.model, form=form, fields=fields)
115 115

  
......
158 158
    title = _('Edit role description')
159 159

  
160 160
    def get_form_class(self):
161
        return forms.get_role_form_class()
161
        return forms.RoleEditForm
162 162

  
163 163
    def form_valid(self, form):
164 164
        response = super().form_valid(form)
tests/test_manager.py
145 145
    assert 'New OU' in options
146 146

  
147 147

  
148
def test_manager_edit_role(superuser_or_admin, app, simple_role):
149
    resp = login(app, superuser_or_admin, '/manage/roles/%s/edit/' % simple_role.pk)
150
    resp.form['details'] = 'xxx'
151
    resp.form['emails'] = 'test@example.com'
152
    resp.form['emails_to_members'] = False
153
    resp = resp.form.submit().follow()
154
    assert set(simple_role.attributes.values_list('name', 'value')) == {
155
        ('emails_to_members', 'false'),
156
        ('emails', '["test@example.com"]'),
157
        ('details', '"xxx"'),
158
    }
159

  
160
    resp = app.get('/manage/roles/%s/edit/' % simple_role.pk)
161
    resp.form['emails'] = 'test@example.com, hop@example.com'
162
    resp = resp.form.submit().follow()
163
    emails = simple_role.attributes.get(name='emails')
164
    assert set(json.loads(emails.value)) == {'test@example.com', 'hop@example.com'}
165

  
166
    resp = app.get('/manage/roles/%s/edit/' % simple_role.pk)
167
    resp.form['emails'] = 'xxx'
168
    resp = resp.form.submit()
169
    assert 'Item 0 is invalid: Enter a valid email address.' in resp.text
170

  
171

  
148 172
def test_manager_edit_role_slug(superuser_or_admin, app, simple_role):
149 173
    assert Role.objects.get(name='simple role').slug == 'simple-role'
150 174
    resp = login(app, superuser_or_admin, reverse('a2-manager-role-edit', kwargs={'pk': simple_role.pk}))
151
-