summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnubhav Joshi <anubhav9042@gmail.com>2014-06-30 18:36:28 +0530
committerTim Graham <timograham@gmail.com>2014-07-03 07:42:52 -0400
commit9bc2d766a0c31701a4c0e20a8b04164bb22cf6d5 (patch)
tree478a6119acaed199052bbf37ec6ab7ce01d26080
parent23d68c0f0d705fede71e1a99625d9988a63cdad3 (diff)
Fixed #21755 -- Added ForeignKey support to REQUIRED_FIELDS.
This allows specifying ForeignKeys in REQUIRED_FIELDS when using a custom User model. Thanks cjerdonek and bmispelon for suggestion and timgraham for review.
-rw-r--r--django/contrib/auth/management/commands/createsuperuser.py2
-rw-r--r--django/contrib/auth/tests/custom_user.py26
-rw-r--r--django/contrib/auth/tests/test_management.py60
-rw-r--r--django/db/utils.py8
-rw-r--r--docs/releases/1.8.txt2
-rw-r--r--docs/topics/auth/customizing.txt12
6 files changed, 101 insertions, 9 deletions
diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py
index 5b660031ba..de0b46770a 100644
--- a/django/contrib/auth/management/commands/createsuperuser.py
+++ b/django/contrib/auth/management/commands/createsuperuser.py
@@ -115,7 +115,7 @@ class Command(BaseCommand):
field = self.UserModel._meta.get_field(field_name)
user_data[field_name] = options.get(field_name)
while user_data[field_name] is None:
- raw_value = input(force_str('%s: ' % capfirst(field.verbose_name)))
+ raw_value = input(force_str('%s%s: ' % (capfirst(field.verbose_name), ' (%s.%s)' % (field.rel.to._meta.object_name, field.rel.field_name) if field.rel else '')))
try:
user_data[field_name] = field.clean(raw_value, None)
except exceptions.ValidationError as e:
diff --git a/django/contrib/auth/tests/custom_user.py b/django/contrib/auth/tests/custom_user.py
index 394baa3204..cfa998ad2f 100644
--- a/django/contrib/auth/tests/custom_user.py
+++ b/django/contrib/auth/tests/custom_user.py
@@ -38,6 +38,18 @@ class CustomUserManager(BaseUserManager):
return u
+class CustomUserWithFKManager(BaseUserManager):
+ def create_superuser(self, username, email, group, password):
+ user = self.model(username=username, email_id=email, group_id=group)
+ user.set_password(password)
+ user.save(using=self._db)
+ return user
+
+
+class Email(models.Model):
+ email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
+
+
class CustomUser(AbstractBaseUser):
email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
is_active = models.BooleanField(default=True)
@@ -83,6 +95,20 @@ class CustomUser(AbstractBaseUser):
return self.is_admin
+class CustomUserWithFK(AbstractBaseUser):
+ username = models.CharField(max_length=30, unique=True)
+ email = models.ForeignKey(Email, to_field='email')
+ group = models.ForeignKey(Group)
+
+ custom_objects = CustomUserWithFKManager()
+
+ USERNAME_FIELD = 'username'
+ REQUIRED_FIELDS = ['email', 'group']
+
+ class Meta:
+ app_label = 'auth'
+
+
# At this point, temporarily remove the groups and user_permissions M2M
# fields from the AbstractUser class, so they don't clash with the related_name
# that sets.
diff --git a/django/contrib/auth/tests/test_management.py b/django/contrib/auth/tests/test_management.py
index fc016135b2..e774d3a071 100644
--- a/django/contrib/auth/tests/test_management.py
+++ b/django/contrib/auth/tests/test_management.py
@@ -9,8 +9,8 @@ from django.contrib.auth import models, management
from django.contrib.auth.checks import check_user_model
from django.contrib.auth.management import create_permissions
from django.contrib.auth.management.commands import changepassword, createsuperuser
-from django.contrib.auth.models import User
-from django.contrib.auth.tests.custom_user import CustomUser
+from django.contrib.auth.models import User, Group
+from django.contrib.auth.tests.custom_user import CustomUser, CustomUserWithFK, Email
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.contenttypes.models import ContentType
from django.core import checks
@@ -349,6 +349,62 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
)
self.assertIs(command.stdin, sys.stdin)
+ @override_settings(AUTH_USER_MODEL='auth.CustomUserWithFK')
+ def test_required_field_with_fk(self):
+ new_io = six.StringIO()
+ group = Group.objects.create(name='mygroup')
+ email = Email.objects.create(email='mymail@gmail.com')
+ call_command(
+ 'createsuperuser',
+ interactive=False,
+ username='user',
+ email='mymail@gmail.com',
+ group=group.pk,
+ stdout=new_io,
+ skip_checks=True,
+ )
+ command_output = new_io.getvalue().strip()
+ self.assertEqual(command_output, 'Superuser created successfully.')
+ u = CustomUserWithFK._default_manager.get(email=email)
+ self.assertEqual(u.username, "user")
+ self.assertEqual(u.group, group)
+
+ non_existent_email = 'mymail2@gmail.com'
+ with self.assertRaisesMessage(CommandError,
+ 'email instance with email %r does not exist.' % non_existent_email):
+ call_command(
+ 'createsuperuser',
+ interactive=False,
+ username='user',
+ email=non_existent_email,
+ stdout=new_io,
+ skip_checks=True,
+ )
+
+ @override_settings(AUTH_USER_MODEL='auth.CustomUserWithFK')
+ def test_required_fields_with_fk_interactive(self):
+ new_io = six.StringIO()
+ group = Group.objects.create(name='mygroup')
+ email = Email.objects.create(email='mymail@gmail.com')
+
+ @mock_inputs({'password': "nopasswd", 'username': "user", 'email': "mymail@gmail.com", 'group': group.pk})
+ def test(self):
+ call_command(
+ 'createsuperuser',
+ interactive=True,
+ stdout=new_io,
+ stdin=MockTTY(),
+ skip_checks=True,
+ )
+
+ command_output = new_io.getvalue().strip()
+ self.assertEqual(command_output, 'Superuser created successfully.')
+ u = CustomUserWithFK._default_manager.get(email=email)
+ self.assertEqual(u.username, 'user')
+ self.assertEqual(u.group, group)
+
+ test(self)
+
class CustomUserModelValidationTestCase(TestCase):
@override_settings(AUTH_USER_MODEL='auth.CustomUserNonListRequiredFields')
diff --git a/django/db/utils.py b/django/db/utils.py
index 4fb75e3150..f1f43ee5ed 100644
--- a/django/db/utils.py
+++ b/django/db/utils.py
@@ -286,10 +286,10 @@ class ConnectionRouter(object):
chosen_db = method(model, **hints)
if chosen_db:
return chosen_db
- try:
- return hints['instance']._state.db or DEFAULT_DB_ALIAS
- except KeyError:
- return DEFAULT_DB_ALIAS
+ instance = hints.get('instance')
+ if instance is not None and instance._state.db:
+ return instance._state.db
+ return DEFAULT_DB_ALIAS
return _route_db
db_for_read = _router_func('db_for_read')
diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt
index 3e2790c602..70858869c9 100644
--- a/docs/releases/1.8.txt
+++ b/docs/releases/1.8.txt
@@ -49,6 +49,8 @@ Minor features
* The ``max_length`` of :attr:`Permission.name
<django.contrib.auth.models.Permission.name>` has been increased from 50 to
255 characters. Please run the database migration.
+* :attr:`~django.contrib.auth.models.CustomUser.REQUIRED_FIELDS` now supports
+ :class:`~django.db.models.ForeignKey`\s.
:mod:`django.contrib.formtools`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt
index a70753132c..0b0582775a 100644
--- a/docs/topics/auth/customizing.txt
+++ b/docs/topics/auth/customizing.txt
@@ -515,8 +515,7 @@ password resets. You must then provide some key implementation details:
will be prompted to supply a value for each of these fields. It must
include any field for which :attr:`~django.db.models.Field.blank` is
``False`` or undefined and may include additional fields you want
- prompted for when a user is created interactively. However, it will not
- work for :class:`~django.db.models.ForeignKey` fields.
+ prompted for when a user is created interactively.
``REQUIRED_FIELDS`` has no effect in other parts of Django, like
creating a user in the admin.
@@ -536,6 +535,15 @@ password resets. You must then provide some key implementation details:
``User`` model, but should *not* contain the ``USERNAME_FIELD`` or
``password`` as these fields will always be prompted for.
+ .. versionadded:: 1.8
+
+ :attr:`REQUIRED_FIELDS` now supports
+ :class:`~django.db.models.ForeignKey`\s. Since there is no way to pass
+ model instances during the :djadmin:`createsuperuser` prompt, expect the
+ user to enter the value of :attr:`~django.db.models.ForeignKey.to_field`
+ value (the :attr:`~django.db.models.Field.primary_key` by default) of an
+ existing instance.
+
.. attribute:: is_active
A boolean attribute that indicates whether the user is considered