Commit f78892f2 authored by Berker Peksag's avatar Berker Peksag Committed by Tim Graham
Browse files

[1.9.x] Refs #19353 -- Added tests for using custom user models with built-in auth forms.

Also updated topics/auth/customizing.txt to reflect that subclasses of
UserCreationForm and UserChangeForm can be used with custom user models.

Thanks Baptiste Mispelon for the initial documentation.

Backport of f0425c72 from master
parent 61f1b6ff
Loading
Loading
Loading
Loading
+29 −31
Original line number Diff line number Diff line
@@ -745,47 +745,45 @@ the "Model design considerations" note of :ref:`specifying-custom-user-model`.
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 <built-in-auth-views>` make certain assumptions about the user
model that they are working with.
Django's built-in :ref:`forms <built-in-auth-forms>` and :ref:`views
<built-in-auth-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``.
The following forms are compatible with any subclass of
:class:`~django.contrib.auth.models.AbstractBaseUser`:

* :class:`~django.contrib.auth.forms.PasswordResetForm`
* :class:`~django.contrib.auth.forms.AuthenticationForm`: Uses the username
  field specified by :attr:`~models.CustomUser.USERNAME_FIELD`.
* :class:`~django.contrib.auth.forms.SetPasswordForm`
* :class:`~django.contrib.auth.forms.PasswordChangeForm`
* :class:`~django.contrib.auth.forms.AdminPasswordChangeForm`

  Assumes that the user model 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.
The following forms make assumptions about the user model and can be used as-is
if those assumptions are met:

* :class:`~django.contrib.auth.forms.SetPasswordForm`
* :class:`~django.contrib.auth.forms.PasswordResetForm`: Assumes that the user
  model 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.

  Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`
Finally, the following forms are tied to
:class:`~django.contrib.auth.models.User` and need to be rewritten or extended
to work with a custom user model:

* :class:`~django.contrib.auth.forms.PasswordChangeForm`
* :class:`~django.contrib.auth.forms.UserCreationForm`
* :class:`~django.contrib.auth.forms.UserChangeForm`

  Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`
If your custom user model is a simple subclass of ``AbstractUser``, then you
can extend these forms in this manner::

* :class:`~django.contrib.auth.forms.AdminPasswordChangeForm`
    from django.contrib.auth.forms import UserCreationForm
    from myapp.models import CustomUser

  Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`
    class CustomUserCreationForm(UserCreationForm):

        class Meta(UserCreationForm.Meta):
            model = CustomUser
            fields = UserCreationForm.Meta.fields + ('custom_field',)

Custom users and :mod:`django.contrib.admin`
--------------------------------------------
+34 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ from django.contrib.auth.forms import (
    SetPasswordForm, UserChangeForm, UserCreationForm,
)
from django.contrib.auth.models import User
from django.contrib.auth.tests.custom_user import ExtensionUser
from django.contrib.sites.models import Site
from django.core import mail
from django.core.mail import EmailMultiAlternatives
@@ -153,6 +154,21 @@ class UserCreationFormTest(TestDataMixin, TestCase):
            form['password2'].errors
        )

    def test_custom_form(self):
        class CustomUserCreationForm(UserCreationForm):
            class Meta(UserCreationForm.Meta):
                model = ExtensionUser
                fields = UserCreationForm.Meta.fields + ('date_of_birth',)

        data = {
            'username': 'testclient',
            'password1': 'testclient',
            'password2': 'testclient',
            'date_of_birth': '1988-02-24',
        }
        form = CustomUserCreationForm(data)
        self.assertTrue(form.is_valid())


@override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'])
class AuthenticationFormTest(TestDataMixin, TestCase):
@@ -441,6 +457,24 @@ class UserChangeFormTest(TestDataMixin, TestCase):
        # value to render correctly
        self.assertEqual(form.initial['password'], form['password'].value())

    def test_custom_form(self):
        class CustomUserChangeForm(UserChangeForm):
            class Meta(UserChangeForm.Meta):
                model = ExtensionUser
                fields = ('username', 'password', 'date_of_birth',)

        user = User.objects.get(username='testclient')
        data = {
            'username': 'testclient',
            'password': 'testclient',
            'date_of_birth': '1998-02-24',
        }
        form = CustomUserChangeForm(data, instance=user)
        self.assertTrue(form.is_valid())
        form.save()
        self.assertEqual(form.cleaned_data['username'], 'testclient')
        self.assertEqual(form.cleaned_data['date_of_birth'], datetime.date(1998, 2, 24))


@override_settings(
    PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'],