Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #19822 -- Added validation for uniqueness on USERNAME_FIELD on …

…custom User models.

Thanks to Claude Peroz for the draft patch.
  • Loading branch information...
commit f5e4a699ca0f58818acbdf9081164060cee910fa 1 parent f179a51
Russell Keith-Magee authored February 15, 2013
22  django/contrib/auth/tests/custom_user.py
@@ -144,3 +144,25 @@ class Meta:
144 144
         app_label = 'auth'
145 145
 
146 146
     # the is_active attr is provided by AbstractBaseUser
  147
+
  148
+
  149
+class CustomUserNonUniqueUsername(AbstractBaseUser):
  150
+    "A user with a non-unique username"
  151
+    username = models.CharField(max_length=30)
  152
+
  153
+    USERNAME_FIELD = 'username'
  154
+
  155
+    class Meta:
  156
+        app_label = 'auth'
  157
+
  158
+
  159
+class CustomUserBadRequiredFields(AbstractBaseUser):
  160
+    "A user with a non-unique username"
  161
+    username = models.CharField(max_length=30, unique=True)
  162
+    date_of_birth = models.DateField()
  163
+
  164
+    USERNAME_FIELD = 'username'
  165
+    REQUIRED_FIELDS = ['username', 'date_of_birth']
  166
+
  167
+    class Meta:
  168
+        app_label = 'auth'
18  django/contrib/auth/tests/management.py
@@ -9,6 +9,8 @@
9 9
 from django.contrib.auth.tests.utils import skipIfCustomUser
10 10
 from django.core.management import call_command
11 11
 from django.core.management.base import CommandError
  12
+from django.core.management.validation import get_validation_errors
  13
+from django.db.models.loading import get_app
12 14
 from django.test import TestCase
13 15
 from django.test.utils import override_settings
14 16
 from django.utils import six
@@ -170,6 +172,22 @@ def test_swappable_user_missing_required_field(self):
170 172
         self.assertEqual(CustomUser._default_manager.count(), 0)
171 173
 
172 174
 
  175
+class CustomUserModelValidationTestCase(TestCase):
  176
+    @override_settings(AUTH_USER_MODEL='auth.CustomUserBadRequiredFields')
  177
+    def test_username_not_in_required_fields(self):
  178
+        "USERNAME_FIELD should not appear in REQUIRED_FIELDS."
  179
+        new_io = StringIO()
  180
+        get_validation_errors(new_io, get_app('auth'))
  181
+        self.assertIn("The field named as the USERNAME_FIELD should not be included in REQUIRED_FIELDS on a swappable User model.", new_io.getvalue())
  182
+
  183
+    @override_settings(AUTH_USER_MODEL='auth.CustomUserNonUniqueUsername')
  184
+    def test_username_non_unique(self):
  185
+        "A non-unique USERNAME_FIELD should raise a model validation error."
  186
+        new_io = StringIO()
  187
+        get_validation_errors(new_io, get_app('auth'))
  188
+        self.assertIn("The USERNAME_FIELD must be unique. Add unique=True to the field parameters.", new_io.getvalue())
  189
+
  190
+
173 191
 class PermissionDuplicationTestCase(TestCase):
174 192
 
175 193
     def setUp(self):
6  django/core/management/validation.py
@@ -49,12 +49,16 @@ def get_validation_errors(outfile, app=None):
49 49
             # No need to perform any other validation checks on a swapped model.
50 50
             continue
51 51
 
52  
-        # This is the current User model. Check known validation problems with User models
  52
+        # If this is the current User model, check known validation problems with User models
53 53
         if settings.AUTH_USER_MODEL == '%s.%s' % (opts.app_label, opts.object_name):
54 54
             # Check that the USERNAME FIELD isn't included in REQUIRED_FIELDS.
55 55
             if cls.USERNAME_FIELD in cls.REQUIRED_FIELDS:
56 56
                 e.add(opts, 'The field named as the USERNAME_FIELD should not be included in REQUIRED_FIELDS on a swappable User model.')
57 57
 
  58
+            # Check that the username field is unique
  59
+            if not opts.get_field(cls.USERNAME_FIELD).unique:
  60
+                e.add(opts, 'The USERNAME_FIELD must be unique. Add unique=True to the field parameters.')
  61
+
58 62
         # Model isn't swapped; do field-specific validation.
59 63
         for f in opts.local_fields:
60 64
             if f.name == 'id' and not f.primary_key and opts.pk.name == 'id':
5  docs/topics/auth/customizing.txt
@@ -492,7 +492,10 @@ password resets. You must then provide some key implementation details:
492 492
         A string describing the name of the field on the User model that is
493 493
         used as the unique identifier. This will usually be a username of
494 494
         some kind, but it can also be an email address, or any other unique
495  
-        identifier. In the following example, the field `identifier` is used
  495
+        identifier. The field *must* be unique (i.e., have ``unique=True``
  496
+        set in it's definition).
  497
+
  498
+        In the following example, the field `identifier` is used
496 499
         as the identifying field::
497 500
 
498 501
             class MyUser(AbstractBaseUser):

0 notes on commit f5e4a69

Please sign in to comment.
Something went wrong with that request. Please try again.