1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244 | """
Management utility to create superusers.
"""
import getpass
import os
import sys
from django.contrib.auth import get_user_model
from django.contrib.auth.management import get_default_username
from django.contrib.auth.password_validation import validate_password
from django.core import exceptions
from django.core.management.base import BaseCommand, CommandError
from django.db import DEFAULT_DB_ALIAS
from django.utils.text import capfirst
class NotRunningInTTYException(Exception):
pass
PASSWORD_FIELD = 'password'
class Command(BaseCommand):
help = 'Used to create a superuser.'
requires_migrations_checks = True
stealth_options = ('stdin',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.UserModel = get_user_model()
self.username_field = self.UserModel._meta.get_field(self.UserModel.USERNAME_FIELD)
def add_arguments(self, parser):
parser.add_argument(
'--%s' % self.UserModel.USERNAME_FIELD,
help='Specifies the login for the superuser.',
)
parser.add_argument(
'--noinput', '--no-input', action='store_false', dest='interactive',
help=(
'Tells Django to NOT prompt the user for input of any kind. '
'You must use --%s with --noinput, along with an option for '
'any other required field. Superusers created with --noinput will '
'not be able to log in until they\'re given a valid password.' %
self.UserModel.USERNAME_FIELD
),
)
parser.add_argument(
'--database',
default=DEFAULT_DB_ALIAS,
help='Specifies the database to use. Default is "default".',
)
for field_name in self.UserModel.REQUIRED_FIELDS:
field = self.UserModel._meta.get_field(field_name)
if field.many_to_many:
if field.remote_field.through and not field.remote_field.through._meta.auto_created:
raise CommandError(
"Required field '%s' specifies a many-to-many "
"relation through model, which is not supported."
% field_name
)
else:
parser.add_argument(
'--%s' % field_name, action='append',
help=(
'Specifies the %s for the superuser. Can be used '
'multiple times.' % field_name,
),
)
else:
parser.add_argument(
'--%s' % field_name,
help='Specifies the %s for the superuser.' % field_name,
)
def execute(self, *args, **options):
self.stdin = options.get('stdin', sys.stdin) # Used for testing
return super().execute(*args, **options)
def handle(self, *args, **options):
username = options[self.UserModel.USERNAME_FIELD]
database = options['database']
user_data = {}
verbose_field_name = self.username_field.verbose_name
try:
self.UserModel._meta.get_field(PASSWORD_FIELD)
except exceptions.FieldDoesNotExist:
pass
else:
# If not provided, create the user with an unusable password.
user_data[PASSWORD_FIELD] = None
try:
if options['interactive']:
# Same as user_data but without many to many fields and with
# foreign keys as fake model instances instead of raw IDs.
fake_user_data = {}
if hasattr(self.stdin, 'isatty') and not self.stdin.isatty():
raise NotRunningInTTYException
default_username = get_default_username(database=database)
if username:
error_msg = self._validate_username(username, verbose_field_name, database)
if error_msg:
self.stderr.write(error_msg)
username = None
elif username == '':
raise CommandError('%s cannot be blank.' % capfirst(verbose_field_name))
# Prompt for username.
while username is None:
message = self._get_input_message(self.username_field, default_username)
username = self.get_input_data(self.username_field, message, default_username)
if username:
error_msg = self._validate_username(username, verbose_field_name, database)
if error_msg:
self.stderr.write(error_msg)
username = None
continue
user_data[self.UserModel.USERNAME_FIELD] = username
fake_user_data[self.UserModel.USERNAME_FIELD] = (
self.username_field.remote_field.model(username)
if self.username_field.remote_field else username
)
# Prompt for required fields.
for field_name in self.UserModel.REQUIRED_FIELDS:
field = self.UserModel._meta.get_field(field_name)
user_data[field_name] = options[field_name]
while user_data[field_name] is None:
message = self._get_input_message(field)
input_value = self.get_input_data(field, message)
user_data[field_name] = input_value
if field.many_to_many and input_value:
if not input_value.strip():
user_data[field_name] = None
self.stderr.write('Error: This field cannot be blank.')
continue
user_data[field_name] = [pk.strip() for pk in input_value.split(',')]
if not field.many_to_many:
fake_user_data[field_name] = input_value
# Wrap any foreign keys in fake model instances
if field.many_to_one:
fake_user_data[field_name] = field.remote_field.model(input_value)
# Prompt for a password if the model has one.
while PASSWORD_FIELD in user_data and user_data[PASSWORD_FIELD] is None:
password = getpass.getpass()
password2 = getpass.getpass('Password (again): ')
if password != password2:
self.stderr.write("Error: Your passwords didn't match.")
# Don't validate passwords that don't match.
continue
if password.strip() == '':
self.stderr.write("Error: Blank passwords aren't allowed.")
# Don't validate blank passwords.
continue
try:
validate_password(password2, self.UserModel(**fake_user_data))
except exceptions.ValidationError as err:
self.stderr.write('\n'.join(err.messages))
response = input('Bypass password validation and create user anyway? [y/N]: ')
if response.lower() != 'y':
continue
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:
error_msg = self._validate_username(username, verbose_field_name, database)
if error_msg:
raise CommandError(error_msg)
user_data[self.UserModel.USERNAME_FIELD] = username
for field_name in self.UserModel.REQUIRED_FIELDS:
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:
self.stdout.write("Superuser created successfully.")
except KeyboardInterrupt:
self.stderr.write('\nOperation cancelled.')
sys.exit(1)
except exceptions.ValidationError as e:
raise CommandError('; '.join(e.messages))
except NotRunningInTTYException:
self.stdout.write(
'Superuser creation skipped due to not running in a TTY. '
'You can run `manage.py createsuperuser` in your project '
'to create one manually.'
)
def get_input_data(self, field, message, default=None):
"""
Override this method if you want to customize data inputs or
validation exceptions.
"""
raw_value = input(message)
if default and raw_value == '':
raw_value = default
try:
val = field.clean(raw_value, None)
except exceptions.ValidationError as e:
self.stderr.write("Error: %s" % '; '.join(e.messages))
val = None
return val
def _get_input_message(self, field, default=None):
return '%s%s%s: ' % (
capfirst(field.verbose_name),
" (leave blank to use '%s')" % default if default else '',
' (%s.%s)' % (
field.remote_field.model._meta.object_name,
field.m2m_target_field_name() if field.many_to_many else field.remote_field.field_name,
) if field.remote_field else '',
)
def _validate_username(self, username, verbose_field_name, database):
"""Validate username. If invalid, return a string error message."""
if self.username_field.unique:
try:
self.UserModel._default_manager.db_manager(database).get_by_natural_key(username)
except self.UserModel.DoesNotExist:
pass
else:
return 'Error: That %s is already taken.' % verbose_field_name
if not username:
return '%s cannot be blank.' % capfirst(verbose_field_name)
try:
self.username_field.clean(username, None)
except exceptions.ValidationError as e:
return '; '.join(e.messages)
|