createsuperuser.py 13 KB

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