Browse Source

Fixed #29449 -- Reverted "Fixed #28757 -- Allowed using contrib.auth forms without installing contrib.auth."

This reverts commit 3333d935d2914cd80cf31f4803821ad5c0e2a51d due to
a crash if USERNAME_FIELD isn't a CharField.
Tim Graham 6 years ago
parent
commit
f3fa86a89b

+ 6 - 5
django/contrib/auth/forms.py

@@ -7,6 +7,7 @@ from django.contrib.auth import (
 from django.contrib.auth.hashers import (
     UNUSABLE_PASSWORD_PREFIX, identify_hasher,
 )
+from django.contrib.auth.models import User
 from django.contrib.auth.tokens import default_token_generator
 from django.contrib.sites.shortcuts import get_current_site
 from django.core.mail import EmailMultiAlternatives
@@ -82,9 +83,9 @@ class UserCreationForm(forms.ModelForm):
     )
 
     class Meta:
-        model = UserModel
-        fields = (UserModel.USERNAME_FIELD,)
-        field_classes = {UserModel.USERNAME_FIELD: UsernameField}
+        model = User
+        fields = ("username",)
+        field_classes = {'username': UsernameField}
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -131,9 +132,9 @@ class UserChangeForm(forms.ModelForm):
     )
 
     class Meta:
-        model = UserModel
+        model = User
         fields = '__all__'
-        field_classes = {UserModel.USERNAME_FIELD: UsernameField}
+        field_classes = {'username': UsernameField}
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)

+ 21 - 11
docs/topics/auth/customizing.txt

@@ -814,20 +814,11 @@ are working with.
 The following forms are compatible with any subclass of
 :class:`~django.contrib.auth.models.AbstractBaseUser`:
 
-* :class:`~django.contrib.auth.forms.AuthenticationForm`
+* :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`
-* :class:`~django.contrib.auth.forms.UserCreationForm`
-* :class:`~django.contrib.auth.forms.UserChangeForm`
-
-The forms that handle a username use the username field specified by
-:attr:`~models.CustomUser.USERNAME_FIELD`.
-
-.. versionchanged:: 2.1
-
-    In older versions, ``UserCreationForm`` and ``UserChangeForm`` need to be
-    rewritten to work with custom user models.
 
 The following forms make assumptions about the user model and can be used as-is
 if those assumptions are met:
@@ -838,6 +829,25 @@ if those assumptions are met:
   default) that can be used to identify the user and a boolean field named
   ``is_active`` to prevent password resets for inactive users.
 
+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.UserCreationForm`
+* :class:`~django.contrib.auth.forms.UserChangeForm`
+
+If your custom user model is a simple subclass of ``AbstractUser``, then you
+can extend these forms in this manner::
+
+    from django.contrib.auth.forms import UserCreationForm
+    from myapp.models import CustomUser
+
+    class CustomUserCreationForm(UserCreationForm):
+
+        class Meta(UserCreationForm.Meta):
+            model = CustomUser
+            fields = UserCreationForm.Meta.fields + ('custom_field',)
+
 Custom users and :mod:`django.contrib.admin`
 --------------------------------------------
 

+ 3 - 6
docs/topics/auth/default.txt

@@ -1508,12 +1508,9 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`:
 
     A :class:`~django.forms.ModelForm` for creating a new user.
 
-    It has three fields: one named after the
-    :attr:`~django.contrib.auth.models.CustomUser.USERNAME_FIELD` from the
-    user model, and ``password1`` and ``password2``.
-
-    It verifies that ``password1`` and ``password2`` match, validates the
-    password using
+    It has three fields: ``username`` (from the user model), ``password1``,
+    and ``password2``. It verifies that ``password1`` and ``password2`` match,
+    validates the password using
     :func:`~django.contrib.auth.password_validation.validate_password`, and
     sets the user's password using
     :meth:`~django.contrib.auth.models.User.set_password()`.

+ 30 - 110
tests/auth_tests/test_forms.py

@@ -1,9 +1,7 @@
 import datetime
 import re
-from importlib import reload
 from unittest import mock
 
-import django
 from django import forms
 from django.contrib.auth.forms import (
     AdminPasswordChangeForm, AuthenticationForm, PasswordChangeForm,
@@ -13,7 +11,7 @@ from django.contrib.auth.forms import (
 from django.contrib.auth.models import User
 from django.contrib.auth.signals import user_login_failed
 from django.contrib.sites.models import Site
-from django.core import mail, signals
+from django.core import mail
 from django.core.mail import EmailMultiAlternatives
 from django.forms.fields import CharField, Field, IntegerField
 from django.test import SimpleTestCase, TestCase, override_settings
@@ -29,24 +27,6 @@ from .models.with_integer_username import IntegerUsernameUser
 from .settings import AUTH_TEMPLATES
 
 
-def reload_auth_forms(sender, setting, value, enter, **kwargs):
-    if setting == 'AUTH_USER_MODEL':
-        reload(django.contrib.auth.forms)
-
-
-class ReloadFormsMixin:
-
-    @classmethod
-    def setUpClass(cls):
-        super().setUpClass()
-        signals.setting_changed.connect(reload_auth_forms)
-
-    @classmethod
-    def tearDownClass(cls):
-        signals.setting_changed.disconnect(reload_auth_forms)
-        super().tearDownClass()
-
-
 class TestDataMixin:
 
     @classmethod
@@ -57,10 +37,9 @@ class TestDataMixin:
         cls.u4 = User.objects.create(username='empty_password', password='')
         cls.u5 = User.objects.create(username='unmanageable_password', password='$')
         cls.u6 = User.objects.create(username='unknown_password', password='foo$bar')
-        cls.u7 = ExtensionUser.objects.create(username='extension_client', date_of_birth='1998-02-24')
 
 
-class UserCreationFormTest(ReloadFormsMixin, TestDataMixin, TestCase):
+class UserCreationFormTest(TestDataMixin, TestCase):
 
     def test_user_already_exists(self):
         data = {
@@ -196,25 +175,19 @@ class UserCreationFormTest(ReloadFormsMixin, TestDataMixin, TestCase):
         )
 
     def test_custom_form(self):
-        with override_settings(AUTH_USER_MODEL='auth_tests.ExtensionUser'):
-            from django.contrib.auth.forms import UserCreationForm
-            self.assertEqual(UserCreationForm.Meta.model, ExtensionUser)
-
-            class CustomUserCreationForm(UserCreationForm):
-                class Meta(UserCreationForm.Meta):
-                    fields = UserCreationForm.Meta.fields + ('date_of_birth',)
+        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())
-        # reload_auth_forms() reloads the form.
-        from django.contrib.auth.forms import UserCreationForm
-        self.assertEqual(UserCreationForm.Meta.model, User)
+        data = {
+            'username': 'testclient',
+            'password1': 'testclient',
+            'password2': 'testclient',
+            'date_of_birth': '1988-02-24',
+        }
+        form = CustomUserCreationForm(data)
+        self.assertTrue(form.is_valid())
 
     def test_custom_form_with_different_username_field(self):
         class CustomUserCreationForm(UserCreationForm):
@@ -288,30 +261,6 @@ class UserCreationFormTest(ReloadFormsMixin, TestDataMixin, TestCase):
             ['The password is too similar to the first name.'],
         )
 
-    def test_with_custom_user_model(self):
-        with override_settings(AUTH_USER_MODEL='auth_tests.ExtensionUser'):
-            data = {
-                'username': 'test_username',
-                'password1': 'test_password',
-                'password2': 'test_password',
-            }
-            from django.contrib.auth.forms import UserCreationForm
-            self.assertEqual(UserCreationForm.Meta.model, ExtensionUser)
-            form = UserCreationForm(data)
-            self.assertTrue(form.is_valid())
-
-    def test_customer_user_model_with_different_username_field(self):
-        with override_settings(AUTH_USER_MODEL='auth_tests.CustomUser'):
-            from django.contrib.auth.forms import UserCreationForm
-            self.assertEqual(UserCreationForm.Meta.model, CustomUser)
-            data = {
-                'email': 'testchange@test.com',
-                'password1': 'test_password',
-                'password2': 'test_password',
-            }
-            form = UserCreationForm(data)
-            self.assertTrue(form.is_valid())
-
 
 # To verify that the login form rejects inactive users, use an authentication
 # backend that allows them.
@@ -677,7 +626,7 @@ class PasswordChangeFormTest(TestDataMixin, TestCase):
         self.assertEqual(form.cleaned_data['new_password2'], data['new_password2'])
 
 
-class UserChangeFormTest(ReloadFormsMixin, TestDataMixin, TestCase):
+class UserChangeFormTest(TestDataMixin, TestCase):
 
     def test_username_validity(self):
         user = User.objects.get(username='testclient')
@@ -751,51 +700,22 @@ class UserChangeFormTest(ReloadFormsMixin, TestDataMixin, TestCase):
         self.assertEqual(form.initial['password'], form['password'].value())
 
     def test_custom_form(self):
-        with override_settings(AUTH_USER_MODEL='auth_tests.ExtensionUser'):
-            from django.contrib.auth.forms import UserChangeForm
-            self.assertEqual(UserChangeForm.Meta.model, ExtensionUser)
-
-            class CustomUserChangeForm(UserChangeForm):
-                class Meta(UserChangeForm.Meta):
-                    fields = ('username', 'password', 'date_of_birth')
+        class CustomUserChangeForm(UserChangeForm):
+            class Meta(UserChangeForm.Meta):
+                model = ExtensionUser
+                fields = ('username', 'password', 'date_of_birth',)
 
-            data = {
-                'username': 'testclient',
-                'password': 'testclient',
-                'date_of_birth': '1998-02-24',
-            }
-            form = CustomUserChangeForm(data, instance=self.u7)
-            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))
-        # reload_auth_forms() reloads the form.
-        from django.contrib.auth.forms import UserChangeForm
-        self.assertEqual(UserChangeForm.Meta.model, User)
-
-    def test_with_custom_user_model(self):
-        with override_settings(AUTH_USER_MODEL='auth_tests.ExtensionUser'):
-            from django.contrib.auth.forms import UserChangeForm
-            self.assertEqual(UserChangeForm.Meta.model, ExtensionUser)
-            data = {
-                'username': 'testclient',
-                'date_joined': '1998-02-24',
-                'date_of_birth': '1998-02-24',
-            }
-            form = UserChangeForm(data, instance=self.u7)
-            self.assertTrue(form.is_valid())
-
-    def test_customer_user_model_with_different_username_field(self):
-        with override_settings(AUTH_USER_MODEL='auth_tests.CustomUser'):
-            from django.contrib.auth.forms import UserChangeForm
-            self.assertEqual(UserChangeForm.Meta.model, CustomUser)
-            user = CustomUser.custom_objects.create(email='test@test.com', date_of_birth='1998-02-24')
-            data = {
-                'email': 'testchange@test.com',
-                'date_of_birth': '1998-02-24',
-            }
-            form = UserChangeForm(data, instance=user)
-            self.assertTrue(form.is_valid())
+        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))
 
     def test_password_excluded(self):
         class UserChangeFormWithoutPassword(UserChangeForm):