From 1d6361063908555eea0c3dec73564b19409a891b Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Thu, 9 May 2019 18:51:56 +0200 Subject: [PATCH] forms: implement locked fields by renaming and widget change (#32954) It simplifies the code (no need to implement a special clean() method) and it covers the case of field with widget not supporting the readonly HTML attribute like those based on tags. --- src/authentic2/forms/profile.py | 57 ++++++++++++++++++++++++--------- tests/test_profile.py | 20 ++++++++++++ 2 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/authentic2/forms/profile.py b/src/authentic2/forms/profile.py index 5dd81a0d..210b25d8 100644 --- a/src/authentic2/forms/profile.py +++ b/src/authentic2/forms/profile.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from collections import OrderedDict from django.forms.models import modelform_factory as dj_modelform_factory from django import forms @@ -76,23 +77,47 @@ class BaseUserForm(forms.ModelForm): self.attributes = models.Attribute.objects.all() initial = kwargs.setdefault('initial', {}) - if kwargs.get('instance'): - instance = kwargs['instance'] - for av in models.AttributeValue.objects.with_owner(instance): - if av.attribute.name in self.declared_fields: - if av.verified: - self.declared_fields[av.attribute.name].widget.attrs['readonly'] = 'readonly' - initial[av.attribute.name] = av.to_python() + instance = kwargs.get('instance') + # extended attributes are not model fields, their initial value must be + # explicitely defined + for av in models.AttributeValue.objects.select_related('attribute').with_owner(instance): + name = av.attribute.name + if name in self.declared_fields: + initial[name] = av.to_python() super(BaseUserForm, self).__init__(*args, **kwargs) - - def clean(self): - from authentic2 import models - - # make sure verified fields are not modified - for av in models.AttributeValue.objects.with_owner( - self.instance).filter(verified=True): - self.cleaned_data[av.attribute.name] = av.to_python() - super(BaseUserForm, self).clean() + if instance: + # Locked attributes are modified to use a read-only TextInput + # widget remapped to a name which will be ignored by Form + # implementation + modified_fields = {} + for av in models.AttributeValue.objects.select_related('attribute').with_owner(instance): + name = av.attribute.name + if name in self.fields and av.verified: + old_field = self.fields[name] + initial = self.initial[name] + try: + choices = old_field.choices + except AttributeError: + initial = old_field.widget.format_value(initial) + else: + for key, label in choices: + if initial == key: + initial = label + break + + modified_fields[av.attribute.name] = forms.CharField( + label=old_field.label, + help_text=old_field.help_text, + initial=initial, + widget=forms.TextInput(attrs={'readonly': ''})) + if modified_fields: + new_fields = OrderedDict() + for name in self.fields: + if name not in modified_fields: + new_fields[name] = self.fields[name] + continue + new_fields[name + '@disabled'] = modified_fields[name] + self.fields = new_fields def save_attributes(self): # only save non verified attributes here diff --git a/tests/test_profile.py b/tests/test_profile.py index 8482fa0a..1df28088 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -135,3 +135,23 @@ def test_account_edit_scopes(app, simple_user): resp = app.get(reverse('profile_edit_with_scope', kwargs={'scope': 'address'}), status=200) assert get_fields(resp) == set(['city', 'zipcode', 'next_url']) + + +def test_account_edit_locked_title(app, simple_user): + Attribute.objects.create( + name='title', label='title', + kind='title', user_visible=True, user_editable=True) + simple_user.attributes.title = 'Monsieur' + + utils.login(app, simple_user) + url = reverse('profile_edit') + response = app.get(url, status=200) + assert len(response.pyquery('input[type="radio"][name="edit-profile-title"]')) == 2 + assert len(response.pyquery('input[type="radio"][name="edit-profile-title"][readonly="true"]')) == 0 + assert len(response.pyquery('select[name="edit-profile-title"]')) == 0 + + simple_user.verified_attributes.title = 'Monsieur' + + response = app.get(url, status=200) + assert len(response.pyquery('input[type="radio"][name="edit-profile-title"][readonly=true]')) == 0 + assert len(response.pyquery('select[name="edit-profile-title"]')) == 1 -- 2.20.1