Permalink
Browse files

Modifications to the handling and docs for auth forms.

  • Loading branch information...
1 parent 98aba85 commit e2b6e22f298eb986d74d28b8d9906f37f5ff8eb8 @freakboy3742 freakboy3742 committed Sep 23, 2012
@@ -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.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 @@ def __init__(self, request=None, *args, **kwargs):
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 @@ def clean_email(self):
"""
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 @@ def create_superuser(self, username, password, date_of_birth):
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 @@ def has_module_perms(self, app_label):
@property
def is_staff(self):
return self.is_admin
-
- @property
- def is_active(self):
- return True
@@ -175,6 +175,29 @@ def test_confirm_different_passwords(self):
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.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
@@ -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:

0 comments on commit e2b6e22

Please sign in to comment.