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. Management utility to create superusers.
""" """
import getpass import getpass
import os
import sys import sys
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -138,6 +139,13 @@ class Command(BaseCommand):
user_data[PASSWORD_FIELD] = password user_data[PASSWORD_FIELD] = password
else: else:
# Non-interactive mode. # 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: if username is None:
raise CommandError('You must use --%s with --noinput.' % self.UserModel.USERNAME_FIELD) raise CommandError('You must use --%s with --noinput.' % self.UserModel.USERNAME_FIELD)
else: else:
@ -147,11 +155,12 @@ class Command(BaseCommand):
user_data[self.UserModel.USERNAME_FIELD] = username user_data[self.UserModel.USERNAME_FIELD] = username
for field_name in self.UserModel.REQUIRED_FIELDS: for field_name in self.UserModel.REQUIRED_FIELDS:
if options[field_name]: env_var = 'DJANGO_SUPERUSER_' + field_name.upper()
field = self.UserModel._meta.get_field(field_name) value = options[field_name] or os.environ.get(env_var)
user_data[field_name] = field.clean(options[field_name], None) if not value:
else:
raise CommandError('You must use --%s with --noinput.' % field_name) 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) self.UserModel._default_manager.db_manager(database).create_superuser(**user_data)
if options['verbosity'] >= 1: 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). programmatically generate superuser accounts for your site(s).
When run interactively, this command will prompt for a password for When run interactively, this command will prompt for a password for
the new superuser account. When run non-interactively, no password the new superuser account. When run non-interactively, you can provide
will be set, and the superuser account will not be able to log in until a password by setting the ``DJANGO_SUPERUSER_PASSWORD`` environment variable.
a password has been manually set for it. 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:: --username USERNAME
.. django-admin-option:: --email EMAIL .. django-admin-option:: --email EMAIL

View File

@ -102,6 +102,10 @@ Minor features
password fields in :mod:`django.contrib.auth.forms` for better interaction password fields in :mod:`django.contrib.auth.forms` for better interaction
with browser password managers. 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` :mod:`django.contrib.contenttypes`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1,5 +1,6 @@
import builtins import builtins
import getpass import getpass
import os
import sys import sys
from datetime import date from datetime import date
from io import StringIO from io import StringIO
@ -905,6 +906,61 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
test(self) 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): class MultiDBCreatesuperuserTestCase(TestCase):
databases = {'default', 'other'} databases = {'default', 'other'}