authentic/src/authentic2/forms/profile.py

175 lines
6.1 KiB
Python

# authentic2 - versatile identity manager
# Copyright (C) 2010-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django import forms
from django.forms.models import modelform_factory as dj_modelform_factory
from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _
from authentic2 import app_settings, models
from authentic2.custom_user.models import User
from .fields import ValidatedEmailField
from .mixins import LockedFieldFormMixin
from .utils import NextUrlFormMixin
class DeleteAccountForm(forms.Form):
password = forms.CharField(widget=forms.PasswordInput, label=_("Password"))
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(DeleteAccountForm, self).__init__(*args, **kwargs)
def clean_password(self):
password = self.cleaned_data.get('password')
if password and not self.user.check_password(password):
raise forms.ValidationError(ugettext('Password is invalid'))
return password
class EmailChangeFormNoPassword(forms.Form):
email = ValidatedEmailField(label=_('New email'))
def __init__(self, user, *args, **kwargs):
self.user = user
super(EmailChangeFormNoPassword, self).__init__(*args, **kwargs)
class EmailChangeForm(EmailChangeFormNoPassword):
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
def clean_email(self):
email = self.cleaned_data['email']
if email == self.user.email:
raise forms.ValidationError(_('This is already your email address.'))
return email
def clean_password(self):
password = self.cleaned_data["password"]
if not self.user.check_password(password):
raise forms.ValidationError(
_('Incorrect password.'),
code='password_incorrect',
)
return password
class BaseUserForm(LockedFieldFormMixin, forms.ModelForm):
error_messages = {
'duplicate_username': _("A user with that username already exists."),
}
def __init__(self, *args, **kwargs):
from authentic2 import models
self.attributes = models.Attribute.objects.all()
initial = kwargs.setdefault('initial', {})
instance = kwargs.get('instance')
# extended attributes are not model fields, their initial value must be
# explicitely defined
self.atvs = []
self.locked_fields = set()
if instance:
self.atvs = models.AttributeValue.objects.select_related('attribute').with_owner(instance)
for atv in self.atvs:
name = atv.attribute.name
if name in self.declared_fields:
initial[name] = atv.to_python()
# helper data for LockedFieldFormMixin
if atv.verified:
self.locked_fields.add(name)
super(BaseUserForm, self).__init__(*args, **kwargs)
def is_field_locked(self, name):
# helper method for LockedFieldFormMixin
return name in self.locked_fields
def save_attributes(self):
# only save non verified attributes here
verified_attributes = set(
self.instance.attribute_values.filter(verified=True).values_list('attribute__name', flat=True)
)
for attribute in self.attributes:
name = attribute.name
if name in self.fields and name not in verified_attributes:
value = self.cleaned_data[name]
setattr(self.instance.attributes, name, value)
def save(self, commit=True):
result = super(BaseUserForm, self).save(commit=commit)
if commit:
self.save_attributes()
else:
old = self.save_m2m
def save_m2m(*args, **kwargs):
old(*args, **kwargs)
self.save_attributes()
self.save_m2m = save_m2m
return result
class EditProfileForm(NextUrlFormMixin, BaseUserForm):
pass
def modelform_factory(model, **kwargs):
"""Build a modelform for the given model,
For the user model also add attribute based fields.
"""
form = kwargs.pop('form', None)
fields = kwargs.get('fields') or []
required = list(kwargs.pop('required', []) or [])
d = {}
# KV attributes are only supported for the user model currently
modelform = None
if issubclass(model, User):
if not form:
form = BaseUserForm
attributes = models.Attribute.objects.all()
for attribute in attributes:
if attribute.name not in fields:
continue
d[attribute.name] = attribute.get_form_field()
for field in app_settings.A2_REQUIRED_FIELDS:
if field not in required:
required.append(field)
if not form or not hasattr(form, 'Meta'):
meta_d = {'model': model, 'fields': '__all__'}
meta = type('Meta', (), meta_d)
d['Meta'] = meta
if not form: # fallback
form = forms.ModelForm
modelform = None
if required:
def __init__(self, *args, **kwargs):
super(modelform, self).__init__(*args, **kwargs)
for field in required:
if field in self.fields:
self.fields[field].required = True
d['__init__'] = __init__
modelform = type(model.__name__ + 'ModelForm', (form,), d)
kwargs['form'] = modelform
modelform.required_css_class = 'form-field-required'
return dj_modelform_factory(model, **kwargs)