From a5308514fb4bc5086c9a16a8a24a945eeebb073c Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Fri, 21 Jun 2019 23:38:27 +0200 Subject: [PATCH] Fixed #27801 -- Made createsuperuser fall back to environment variables for password and required fields. --- .../management/commands/createsuperuser.py | 17 ++++-- docs/ref/django-admin.txt | 20 ++++++- docs/releases/3.0.txt | 4 ++ tests/auth_tests/test_management.py | 56 +++++++++++++++++++ 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py index d700ce4c0ee..ece66dfad1e 100644 --- a/django/contrib/auth/management/commands/createsuperuser.py +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -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: diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 69e00b04281..e139c8128f7 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -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_`` 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_`` environment variables was added. .. django-admin-option:: --username USERNAME .. django-admin-option:: --email EMAIL diff --git a/docs/releases/3.0.txt b/docs/releases/3.0.txt index 4c33e7c309e..c7f1a4e78b9 100644 --- a/docs/releases/3.0.txt +++ b/docs/releases/3.0.txt @@ -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` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py index d0a91f32615..bf9b0631028 100644 --- a/tests/auth_tests/test_management.py +++ b/tests/auth_tests/test_management.py @@ -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'}