Modifications to the handling and docs for auth forms.

This commit is contained in:
Russell Keith-Magee 2012-09-23 17:53:05 +08:00
parent 98aba856b5
commit e2b6e22f29
7 changed files with 108 additions and 20 deletions

View File

@ -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 %}

View File

@ -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"
}
}
]

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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: