Chapter 18 · Authentication, sessions and messages

18.15 Creating a superuser: createsuperuser and changepassword

The createsuperuser command makes a user with is_staff and is_superuser set: it asks at a terminal for each field the user model requires, or with --noinput reads them from options and environment variables, and hands them to the manager’s create_superuser. The changepassword command asks twice for a new password for an existing user, validates it with validate_password, and saves it.

A new project has no user, and the admin, where users are added, admits only an active staff user (The site’s own pages: the index, the login and AdminSite.each_context). createsuperuser makes the first from the command line, for whatever model AUTH_USER_MODEL names, so it knows of the fields only what the model declares: the field named by USERNAME_FIELD, those of AbstractBaseUser.REQUIRED_FIELDS, and whether there is a field called "password". Each model field cleans its own value, and the model’s manager makes the user. It has two kinds of caller: a person at a terminal, who can be asked again after a mistake, and a script, which cannot, and must give every answer in advance.

The options come from the user model

createsuperuser looks up the user model in its __init__, before the parser is built, so that add_arguments can name the options after the model: one for USERNAME_FIELD, --username for User, and one for each field of REQUIRED_FIELDS, so User has --email, AbstractUser.REQUIRED_FIELDS being ["email"]. A many-to-many field’s option may be repeated; one with a through model of its own is refused with a CommandError. Beside these stand --noinput and --database. Its execute keeps a stream, standard input unless a caller in code passes the stealth option "stdin"; the command never reads from it, and only asks it whether it is a terminal (call_command: a command called from code). If the model has a field called "password", the data starts with a password of None, meaning none given yet; a model without one is never asked for a password.

At a terminal

The interactive mode first checks for a terminal. When the stream is not a TTY it raises NotRunningInTTYException, which handle catches at its end: it writes that creation was skipped and ends without an error, so a script that forgets --noinput and runs without a terminal makes no user and does not fail.

The default username is the name of the account the process runs under, from get_system_username, reduced to ASCII without spaces and in lower case, and offered only if User’s username field accepts it and no user has it. When AUTH_USER_MODEL names another model there is none, since, a comment says, no assumption can be made about the username then.

The username is asked for first, unless an option gave a valid one, then each required field no option gave. Command.get_input_data reads an answer with input, puts the default in place of an empty one, and has the model field clean it; an error is written and the question asked again. For a unique username, Command._validate_username also asks the manager’s get_by_natural_key whether it is taken. A foreign key is answered with the key of a row that must exist.

The password comes last, typed twice with getpass.getpass. Two that differ, or one that is blank, are refused before any validator sees them. Then validate_password runs the validators of AUTH_PASSWORD_VALIDATORS against a user that is never saved, made from the answers, so that a validator comparing the password with the username or the email has them. When a validator refuses, the command asks whether to create the user anyway, and “y” keeps the password (Password validation: validate_password and the validators).

The office’s new harbour master, under a system account named Maren Visser, met each refusal in turn; the passwords typed are not echoed:

Username (leave blank to use 'marenvisser'): piet
Error: That username is already taken.
Username (leave blank to use 'marenvisser'):
Email address: maren-at-harbour
Error: Enter a valid email address.
Email address: maren@harbour.example
Password:
Password (again):
Error: Your passwords didn't match.
Password:
Password (again):
Error: Blank passwords aren't allowed.
Password:
Password (again):
The password is too similar to the username.
Bypass password validation and create user anyway? [y/N]: N
Password:
Password (again):
Superuser created successfully.

With --noinput

With --noinput each value comes from its option or else from an environment variable, "DJANGO_SUPERUSER_" and the field’s name in capitals. A missing value is a CommandError naming the option to use; a username taken or invalid, or a value its field will not clean, is a CommandError with the error’s message. A field that may be blank may be given empty, and is then left out.

The password comes only from "DJANGO_SUPERUSER_PASSWORD", and in this mode nothing calls validate_password. Without the variable the password stays None, which make_password turns into an unusable one, and the superuser cannot log in until given a password (Password hashing: make_password and the hashers). With the example’s validators, which refuse “12345678”:

>>> os.environ["DJANGO_SUPERUSER_EMAIL"] = "joris@harbour.example"
>>> os.environ["DJANGO_SUPERUSER_PASSWORD"] = "12345678"
>>> call_command("createsuperuser", interactive=False, username="joris")
Superuser created successfully.
>>> User.objects.get(username="joris").check_password("12345678")
True
>>> del os.environ["DJANGO_SUPERUSER_PASSWORD"]
>>> call_command("createsuperuser", interactive=False, username="anna")
Superuser created successfully.
>>> User.objects.get(username="anna").has_usable_password()
False

The manager makes the user

Both modes end in one call: the default manager, bound to the --database alias, is asked to create_superuser with the values as keyword arguments. UserManager.create_superuser sets is_staff=True and is_superuser=True, raises ValueError if told otherwise, and makes the user as UserManager.create_user does. A custom model’s manager must accept these keywords: the username field, each required field and the password (The user model: AbstractBaseUser, User and AUTH_USER_MODEL). UserManager.create_superuser validates no password; the documentation gives two reasons: developers, not users, work at that level, and a model’s validation does not run as it is created.

changepassword

changepassword looks the user up by USERNAME_FIELD, under the name given or else the system account’s from getpass.getuser, taken as it is; a name no user has is a CommandError. It asks for the password twice, for up to three tries. An empty answer ends the command at once. Two answers that differ cost a try; two that match go to validate_password with the real user, and a refusal costs a try, with no way past it. After three failures the command gives up with a CommandError; otherwise it calls AbstractBaseUser.set_password and saves, and AbstractBaseUser.save tells the validators through password_changed.

Run for nora, it also ends nora’s sessions on the site: each holds a hash made from the stored password, and django.contrib.auth.get_user empties a session whose hash no longer matches the next time a request reads its user from it (The user of a request: login, logout and AuthenticationMiddleware).