Source code for improved_user.forms
"""Forms for Creating and Updating Improved Users"""
from django import forms
from django.contrib.auth import get_user_model, password_validation
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _
User = get_user_model() # pylint: disable=invalid-name
[docs]class AbstractUserCreationForm(forms.ModelForm):
"""Abstract Form to create an unprivileged user
Create a User with no permissions based on username and password.
"""
error_messages = {
"password_mismatch": _("The two password fields didn't match."),
}
password1 = forms.CharField(
label=_("Password"),
widget=forms.PasswordInput,
help_text=password_validation.password_validators_help_text_html(),
strip=False,
)
password2 = forms.CharField(
label=_("Password confirmation"),
widget=forms.PasswordInput,
help_text=_("Enter the same password as above, for verification."),
strip=False,
)
[docs] def clean_password2(self):
"""Check wether password 1 and password 2 are equivalent
While ideally this would be done in clean, there is a chance a
superclass could declare clean and forget to call super. We
therefore opt to run this password mismatch check in password2
clean, but to show the error above password1 (as we are unsure
whether password 1 or password 2 contains the typo, and putting
it above password 2 may lead some users to believe the typo is
in just one).
"""
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
self.add_error(
"password1",
forms.ValidationError(
self.error_messages["password_mismatch"],
code="password_mismatch",
),
)
return password2
[docs] def _post_clean(self):
"""Run password validaton after clean methods
When clean methods are run, the user instance does not yet
exist. To properly compare model values agains the password (in
the UserAttributeSimilarityValidator), we wait until we have an
instance to compare against.
https://code.djangoproject.com/ticket/28127
https://github.com/django/django/pull/8408
Has no effect in Django prior to 1.9
May become unnecessary in Django 2.0 (if this superclass changes)
"""
super()._post_clean() # updates self.instance with form data
password = self.cleaned_data.get("password1")
if password:
try:
password_validation.validate_password(password, self.instance)
except ValidationError as error:
self.add_error("password1", error)
[docs] def save(self, commit=True):
"""Save the user; use password hasher to set password"""
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
[docs]class UserCreationForm(AbstractUserCreationForm):
"""Form to create an unprivileged user
A concrete implementation of AbstractUserCreationForm that uses an
e-mail address as a user's identifier.
"""
error_messages = {
**AbstractUserCreationForm.error_messages,
"duplicate_email": _("A user with that email already exists."),
}
class Meta:
model = User
fields = ("email", "full_name", "short_name")
[docs] def clean_email(self):
"""Clean email; set nice error message
Since User.email is unique, this check is redundant,
but it sets a nicer error message than the ORM. See #13147.
https://code.djangoproject.com/ticket/13147
"""
email = self.cleaned_data["email"]
try:
# https://docs.djangoproject.com/en/stable/topics/db/managers/#default-managers
# pylint: disable=protected-access
User._default_manager.get(email=email)
# pylint: enable=protected-access
except User.DoesNotExist:
return email
raise forms.ValidationError(
self.error_messages["duplicate_email"],
code="duplicate_email",
)
[docs]class AbstractUserChangeForm(forms.ModelForm):
"""Base form update User, but not their password"""
password = ReadOnlyPasswordHashField(
label=_("Password"),
help_text=_(
"Raw passwords are not stored, so there is no way to see this "
"user's password, but you can change the password using "
'<a href="{}">this form</a>.'
),
)
rel_password_url = None
def __init__(self, *args, **kwargs):
"""Initialize form; optimize user permission queryset"""
super().__init__(*args, **kwargs)
self.fields["password"].help_text = self.fields[
"password"
].help_text.format(self.get_local_password_path())
permission_field = self.fields.get("user_permissions", None)
if permission_field is not None:
# pre-load content types associated with permissions
permission_field.queryset = (
permission_field.queryset.select_related("content_type")
)
[docs] def get_local_password_path(self):
"""Return relative path to password form
Will return rel_password_url attribute on form
or else '../password/'. If subclasses cannot simply replace
rel_password_url, then they can override this method instead of
__init__.
"""
if (
hasattr(self, "rel_password_url")
and self.rel_password_url is not None
):
return self.rel_password_url
return "../password/"
[docs] def clean_password(self):
"""Change user info; not the password
We seek to change the user, but not the password.
Regardless of what the user provides, return the initial value.
This is done here, rather than on the field, because the
field does not have access to the initial value
"""
return self.initial["password"]
[docs]class UserChangeForm(AbstractUserChangeForm):
"""Form to update user, but not their password"""
class Meta:
model = User
fields = "__all__"