Fixed #27801 -- Made createsuperuser fall back to environment variables for password and required fields.

This commit is contained in:
Hasan Ramezani 2019-06-21 23:38:27 +02:00 committed by Mariusz Felisiak
parent 4b32d039db
commit a5308514fb
4 changed files with 90 additions and 7 deletions

View File

@ -2,6 +2,7 @@
Management utility to create superusers.
"""
import getpass
import os
import sys
from django.contrib.auth import get_user_model
@ -138,6 +139,13 @@ class Command(BaseCommand):
user_data[PASSWORD_FIELD] = password
else:
# Non-interactive mode.
# Use password from environment variable, if provided.
if PASSWORD_FIELD in user_data and 'DJANGO_SUPERUSER_PASSWORD' in os.environ:
user_data[PASSWORD_FIELD] = os.environ['DJANGO_SUPERUSER_PASSWORD']
# Use username from environment variable, if not provided in
# options.
if username is None:
username = os.environ.get('DJANGO_SUPERUSER_' + self.UserModel.USERNAME_FIELD.upper())
if username is None:
raise CommandError('You must use --%s with --noinput.' % self.UserModel.USERNAME_FIELD)
else:
@ -147,11 +155,12 @@ class Command(BaseCommand):
user_data[self.UserModel.USERNAME_FIELD] = username
for field_name in self.UserModel.REQUIRED_FIELDS:
if options[field_name]:
field = self.UserModel._meta.get_field(field_name)
user_data[field_name] = field.clean(options[field_name], None)
else:
env_var = 'DJANGO_SUPERUSER_' + field_name.upper()
value = options[field_name] or os.environ.get(env_var)
if not value:
raise CommandError('You must use --%s with --noinput.' % field_name)
field = self.UserModel._meta.get_field(field_name)
user_data[field_name] = field.clean(value, None)
self.UserModel._default_manager.db_manager(database).create_superuser(**user_data)
if options['verbosity'] >= 1:

View File

@ -1560,9 +1560,23 @@ useful if you need to create an initial superuser account or if you need to
programmatically generate superuser accounts for your site(s).
When run interactively, this command will prompt for a password for
the new superuser account. When run non-interactively, no password
will be set, and the superuser account will not be able to log in until
a password has been manually set for it.
the new superuser account. When run non-interactively, you can provide
a password by setting the ``DJANGO_SUPERUSER_PASSWORD`` environment variable.
Otherwise, no password will be set, and the superuser account will not be able
to log in until a password has been manually set for it.
In non-interactive mode, the
:attr:`~django.contrib.auth.models.CustomUser.USERNAME_FIELD` and required
fields (listed in
:attr:`~django.contrib.auth.models.CustomUser.REQUIRED_FIELDS`) fall back to
``DJANGO_SUPERUSER_<uppercase_field_name>`` environment variables, unless they
are overridden by a command line argument. For example, to provide an ``email``
field, you can use ``DJANGO_SUPERUSER_EMAIL`` environment variable.
.. versionchanged:: 3.0
Support for using ``DJANGO_SUPERUSER_PASSWORD`` and
``DJANGO_SUPERUSER_<uppercase_field_name>`` environment variables was added.
.. django-admin-option:: --username USERNAME
.. django-admin-option:: --email EMAIL

View File

@ -102,6 +102,10 @@ Minor features
password fields in :mod:`django.contrib.auth.forms` for better interaction
with browser password managers.
* :djadmin:`createsuperuser` now falls back to environment variables for
password and required fields, when a corresponding command line argument
isn't provided in non-interactive mode.
:mod:`django.contrib.contenttypes`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1,5 +1,6 @@
import builtins
import getpass
import os
import sys
from datetime import date
from io import StringIO
@ -905,6 +906,61 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
test(self)
@mock.patch.dict(os.environ, {
'DJANGO_SUPERUSER_PASSWORD': 'test_password',
'DJANGO_SUPERUSER_USERNAME': 'test_superuser',
'DJANGO_SUPERUSER_EMAIL': 'joe@somewhere.org',
'DJANGO_SUPERUSER_FIRST_NAME': 'ignored_first_name',
})
def test_environment_variable_non_interactive(self):
call_command('createsuperuser', interactive=False, stdout=StringIO())
user = User.objects.get(username='test_superuser')
self.assertEqual(user.email, 'joe@somewhere.org')
self.assertTrue(user.check_password('test_password'))
# Environment variables are ignored for non-required fields.
self.assertEqual(user.first_name, '')
@mock.patch.dict(os.environ, {
'DJANGO_SUPERUSER_USERNAME': 'test_superuser',
'DJANGO_SUPERUSER_EMAIL': 'joe@somewhere.org',
})
def test_ignore_environment_variable_non_interactive(self):
# Environment variables are ignored in non-interactive mode, if
# provided by a command line arguments.
call_command(
'createsuperuser',
interactive=False,
username='cmd_superuser',
email='cmd@somewhere.org',
stdout=StringIO(),
)
user = User.objects.get(username='cmd_superuser')
self.assertEqual(user.email, 'cmd@somewhere.org')
self.assertFalse(user.has_usable_password())
@mock.patch.dict(os.environ, {
'DJANGO_SUPERUSER_PASSWORD': 'test_password',
'DJANGO_SUPERUSER_USERNAME': 'test_superuser',
'DJANGO_SUPERUSER_EMAIL': 'joe@somewhere.org',
})
def test_ignore_environment_variable_interactive(self):
# Environment variables are ignored in interactive mode.
@mock_inputs({'password': 'cmd_password'})
def test(self):
call_command(
'createsuperuser',
interactive=True,
username='cmd_superuser',
email='cmd@somewhere.org',
stdin=MockTTY(),
stdout=StringIO(),
)
user = User.objects.get(username='cmd_superuser')
self.assertEqual(user.email, 'cmd@somewhere.org')
self.assertTrue(user.check_password('cmd_password'))
test(self)
class MultiDBCreatesuperuserTestCase(TestCase):
databases = {'default', 'other'}