createsuperuser.py 11 KB

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