0001-manager-improve-default-role-form-fields-58699.patch
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 |
- |