Browse Source

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

Hasan Ramezani 5 years ago
parent
commit
a5308514fb

+ 13 - 4
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:

+ 17 - 3
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_<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

+ 4 - 0
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`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

+ 56 - 0
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'}