Modifications to the handling and docs for auth forms.
This commit is contained in:
parent
98aba856b5
commit
e2b6e22f29
|
@ -30,7 +30,7 @@
|
|||
<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
|
||||
<div class="form-row">
|
||||
{% if not form.this_is_the_login_form.errors %}{{ form.username.errors }}{% endif %}
|
||||
<label for="id_username" class="required">{% trans 'Username:' %}</label> {{ form.username }}
|
||||
<label for="id_username" class="required">{{ form.username.label }}:</label> {{ form.username }}
|
||||
</div>
|
||||
<div class="form-row">
|
||||
{% if not form.this_is_the_login_form.errors %}{{ form.password.errors }}{% endif %}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
{
|
||||
"pk": "1",
|
||||
"model": "auth.customuser",
|
||||
"fields": {
|
||||
"password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
|
||||
"last_login": "2006-12-17 07:03:31",
|
||||
"email": "staffmember@example.com",
|
||||
"is_active": true,
|
||||
"is_admin": false,
|
||||
"date_of_birth": "1976-11-08"
|
||||
}
|
||||
}
|
||||
]
|
|
@ -7,9 +7,10 @@ from django.utils.datastructures import SortedDict
|
|||
from django.utils.html import format_html, format_html_join
|
||||
from django.utils.http import int_to_base36
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib.auth import authenticate, get_user_model
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.hashers import UNUSABLE_PASSWORD, identify_hasher
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
|
@ -135,7 +136,7 @@ class AuthenticationForm(forms.Form):
|
|||
Base class for authenticating users. Extend this to get a form that accepts
|
||||
username/password logins.
|
||||
"""
|
||||
username = forms.CharField(label=_("Username"), max_length=30)
|
||||
username = forms.CharField(max_length=30)
|
||||
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
|
||||
|
||||
error_messages = {
|
||||
|
@ -157,6 +158,11 @@ class AuthenticationForm(forms.Form):
|
|||
self.user_cache = None
|
||||
super(AuthenticationForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# Set the label for the "username" field.
|
||||
UserModel = get_user_model()
|
||||
username_field = UserModel._meta.get_field(getattr(UserModel, 'USERNAME_FIELD', 'username'))
|
||||
self.fields['username'].label = capfirst(username_field.verbose_name)
|
||||
|
||||
def clean(self):
|
||||
username = self.cleaned_data.get('username')
|
||||
password = self.cleaned_data.get('password')
|
||||
|
@ -198,9 +204,10 @@ class PasswordResetForm(forms.Form):
|
|||
"""
|
||||
Validates that an active user exists with the given email address.
|
||||
"""
|
||||
UserModel = get_user_model()
|
||||
email = self.cleaned_data["email"]
|
||||
self.users_cache = User.objects.filter(email__iexact=email,
|
||||
is_active=True)
|
||||
self.users_cache = UserModel.objects.filter(email__iexact=email,
|
||||
is_active=True)
|
||||
if not len(self.users_cache):
|
||||
raise forms.ValidationError(self.error_messages['unknown'])
|
||||
if any((user.password == UNUSABLE_PASSWORD)
|
||||
|
|
|
@ -32,6 +32,7 @@ class CustomUserManager(BaseUserManager):
|
|||
|
||||
class CustomUser(AbstractBaseUser):
|
||||
email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
|
||||
is_active = models.BooleanField(default=True)
|
||||
is_admin = models.BooleanField(default=False)
|
||||
date_of_birth = models.DateField()
|
||||
|
||||
|
@ -72,7 +73,3 @@ class CustomUser(AbstractBaseUser):
|
|||
@property
|
||||
def is_staff(self):
|
||||
return self.is_admin
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return True
|
||||
|
|
|
@ -175,6 +175,29 @@ class PasswordResetTest(AuthViewsTestCase):
|
|||
self.assertContainsEscaped(response, SetPasswordForm.error_messages['password_mismatch'])
|
||||
|
||||
|
||||
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
|
||||
class CustomUserPasswordResetTest(AuthViewsTestCase):
|
||||
fixtures = ['custom_user.json']
|
||||
|
||||
def _test_confirm_start(self):
|
||||
# Start by creating the email
|
||||
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
return self._read_signup_email(mail.outbox[0])
|
||||
|
||||
def _read_signup_email(self, email):
|
||||
urlmatch = re.search(r"https?://[^/]*(/.*reset/\S*)", email.body)
|
||||
self.assertTrue(urlmatch is not None, "No URL found in sent email")
|
||||
return urlmatch.group(), urlmatch.groups()[0]
|
||||
|
||||
def test_confirm_valid_custom_user(self):
|
||||
url, path = self._test_confirm_start()
|
||||
response = self.client.get(path)
|
||||
# redirect to a 'complete' page:
|
||||
self.assertContains(response, "Please enter your new password")
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class ChangePasswordTest(AuthViewsTestCase):
|
||||
|
||||
|
|
|
@ -15,10 +15,9 @@ from django.views.decorators.cache import never_cache
|
|||
from django.views.decorators.csrf import csrf_protect
|
||||
|
||||
# Avoid shadowing the login() and logout() views below.
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login, logout as auth_logout
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login, logout as auth_logout, get_user_model
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm, PasswordChangeForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.contrib.sites.models import get_current_site
|
||||
|
||||
|
@ -201,13 +200,14 @@ def password_reset_confirm(request, uidb36=None, token=None,
|
|||
View that checks the hash in a password reset link and presents a
|
||||
form for entering a new password.
|
||||
"""
|
||||
UserModel = get_user_model()
|
||||
assert uidb36 is not None and token is not None # checked by URLconf
|
||||
if post_reset_redirect is None:
|
||||
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete')
|
||||
try:
|
||||
uid_int = base36_to_int(uidb36)
|
||||
user = User.objects.get(id=uid_int)
|
||||
except (ValueError, OverflowError, User.DoesNotExist):
|
||||
user = UserModel.objects.get(id=uid_int)
|
||||
except (ValueError, OverflowError, UserModel.DoesNotExist):
|
||||
user = None
|
||||
|
||||
if user is not None and token_generator.check_token(user, token):
|
||||
|
|
|
@ -1357,6 +1357,9 @@ Helper functions
|
|||
URL to redirect to after log out. Overrides ``next`` if the given
|
||||
``GET`` parameter is passed.
|
||||
|
||||
|
||||
.. _built-in-auth-forms:
|
||||
|
||||
Built-in forms
|
||||
--------------
|
||||
|
||||
|
@ -1915,6 +1918,51 @@ model and you just want to add some additional profile information, you can
|
|||
simply subclass :class:`~django.contrib.auth.models.AbstractUser` and add your
|
||||
custom profile fields.
|
||||
|
||||
Custom users and the built-in auth forms
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As you may expect, built-in Django's :ref:`forms <_built-in-auth-forms>`
|
||||
and :ref:`views <other-built-in-views>` make certain assumptions about
|
||||
the user model that they are working with.
|
||||
|
||||
If your user model doesn't follow the same assumptions, it may be necessary to define
|
||||
a replacement form, and pass that form in as part of the configuration of the
|
||||
auth views.
|
||||
|
||||
* :class:`~django.contrib.auth.forms.UserCreationForm`
|
||||
|
||||
Depends on the :class:`~django.contrib.auth.models.User` model.
|
||||
Must be re-written for any custom user model.
|
||||
|
||||
* :class:`~django.contrib.auth.forms.UserChangeForm`
|
||||
|
||||
Depends on the :class:`~django.contrib.auth.models.User` model.
|
||||
Must be re-written for any custom user model.
|
||||
|
||||
* :class:`~django.contrib.auth.forms.AuthenticationForm`
|
||||
|
||||
Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`,
|
||||
and will adapt to use the field defined in `USERNAME_FIELD`.
|
||||
|
||||
* :class:`~django.contrib.auth.forms.PasswordResetForm`
|
||||
|
||||
Assumes that the user model has an integer primary key, has a field named
|
||||
`email` that can be used to identify the user, and a boolean field
|
||||
named `is_active` to prevent password resets for inactive users.
|
||||
|
||||
* :class:`~django.contrib.auth.forms.SetPasswordForm`
|
||||
|
||||
Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`
|
||||
|
||||
* :class:`~django.contrib.auth.forms.PasswordChangeForm`
|
||||
|
||||
Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`
|
||||
|
||||
* :class:`~django.contrib.auth.forms.AdminPasswordChangeForm`
|
||||
|
||||
Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`
|
||||
|
||||
|
||||
Custom users and django.contrib.admin
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -1960,7 +2008,11 @@ A full example
|
|||
Here is an example of a full models.py for an admin-compliant custom
|
||||
user app. This user model uses an email address as the username, and has a
|
||||
required date of birth; it provides no permission checking, beyond a simple
|
||||
`admin` flag on the user account::
|
||||
`admin` flag on the user account. This model would be compatible with all
|
||||
the built-in auth forms and views, except for the User creation forms.
|
||||
|
||||
This code would all live in a ``models.py`` file for a custom
|
||||
authentication app::
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import (
|
||||
|
@ -2006,6 +2058,7 @@ required date of birth; it provides no permission checking, beyond a simple
|
|||
max_length=255
|
||||
)
|
||||
date_of_birth = models.DateField()
|
||||
is_active = models.BooleanField(default=True)
|
||||
is_admin = models.BooleanField(default=False)
|
||||
|
||||
objects = MyUserManager()
|
||||
|
@ -2040,12 +2093,6 @@ required date of birth; it provides no permission checking, beyond a simple
|
|||
# Simplest possible answer: All admins are staff
|
||||
return self.is_admin
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
"Is the user account currently active?"
|
||||
# Simplest possible answer: User is always active
|
||||
return True
|
||||
|
||||
|
||||
.. _authentication-backends:
|
||||
|
||||
|
|
Loading…
Reference in New Issue