12.16 The questioner: MigrationQuestioner
The questioner is the object the autodetector asks what the two project states it compares do not tell it; makemigrations asks one itself before a merge. MigrationQuestioner asks no one, InteractiveMigrationQuestioner asks the person at the terminal, and NonInteractiveMigrationQuestioner answers what it can and ends the command with status 3 where it cannot.
Two edits to Repair, in the example’s repair yard, show why the autodetector has questions.
# harbour/yard/models.py, in Repair
begun = models.DateField() # first edit: it was called started
foreman = models.CharField(max_length=60) # second edit: a new field
After the first, a date field is gone and a date field has come: one column renamed, or one dropped and another made (The autodetector: fields, indexes and constraints). After the second, a column that may not be null needs a value for the rows already there, and the model gives none. The autodetector does not guess: it calls a method of the questioner it was made with.
MigrationQuestioner: answers without asking
MigrationQuestioner, the base class, keeps what it is made with: MigrationQuestioner, a dictionary from a method’s name to its answer; MigrationQuestioner, a set of labels; and MigrationQuestioner, which only the interactive subclass reads. MigrationQuestioner, and likewise the questions of a model renamed and of a merge, return what the dictionary has under the method’s name, and false where it has nothing. The questions that want a value for existing rows return None whatever the dictionary holds, as does MigrationQuestioner.
An autodetector made with no questioner makes a MigrationQuestioner() for itself, and so never blocks: migrate’s check for changes to the models that are in no migration is made with one (The migrate command, showmigrations and sqlmigrate). makemigrations --merge with --noinput asks MigrationQuestioner(defaults={"ask_merge": True}).
ask_initial: whether an application gets its first migration
MigrationAutodetector asks MigrationQuestioner of each application that has migrations to be written and no leaf node in the graph, and on a no drops them (From operations to migrations: dependencies, order and names). What becomes of an application with no migrations yet is told whole in The makemigrations command.
A label in MigrationQuestioner gets a yes; makemigrations puts there the labels on its command line. For any other, the graph having nothing for the application, ask_initial looks for a migrations package with no migration in it, as the application template makes one. It imports the migrations module, by the dotted path MigrationLoader gives, lists its directory, and says yes where no file there but __init__.py ends in .py. A package spread over several directories has no one directory to list and gets a no. MigrationQuestioner is read, with no for want of an entry, only where there is no module to list: the label is no installed application’s, MIGRATION_MODULES gives None for it, or the import fails.
InteractiveMigrationQuestioner: asking at the terminal
InteractiveMigrationQuestioner writes its questions to InteractiveMigrationQuestioner, an OutputWrapper around sys.stdout unless it is given another, and reads each answer with input(), given no prompt: the answers come from standard input wherever the questions go.
InteractiveMigrationQuestioner asks whether something was renamed or is to be merged, and takes an empty first answer for no, the default every caller gives it. InteractiveMigrationQuestioner puts the other questions the class overrides as choices numbered from 1, the last of which is to quit and edit the model: sys.exit(3).
InteractiveMigrationQuestioner is where the first choice leads when a value is wanted. It reads a line and evaluates it as a Python expression, with the built-in eval, in a scope that holds, beside the built-ins, datetime, the module, and timezone, which is django, and returns what it gives. A line that raises an exception gets a message and the prompt again, as does an empty one, except from InteractiveMigrationQuestioner, where it stands for "timezone.now". The word exit ends the process with status 1, not the 3 of a choice to quit. In the recording, asking is an interactive questioner, and keyboard, in place of standard input, holds the answers typed ahead; stdout | begins a line the questioner wrote and stdin | a line input() read.
keyboard.lines = ['1', 'timezone.now']
answer = asking.ask_not_null_addition('foreman', 'repair')
stdout | It is impossible to add a non-nullable field 'foreman' to repair without specifying a default. This is because the database needs something to populate existing rows.
stdout | Please select a fix:
stdout | 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
stdout | 2) Quit and manually define a default value in models.py.
stdout | Select an option:
stdin | 1
stdout | Please enter the default value as valid Python.
stdout | The datetime and django.utils.timezone modules are available, so it is possible to provide e.g. timezone.now as a value.
stdout | Type 'exit' to exit this prompt
stdout | >>>
stdin | timezone.now
answer -> the function now
The autodetector makes the value typed the default of the operation’s field and gives the operation preserve_default=False: a one-off default (The field operations, and those on indexes and constraints).
InteractiveMigrationQuestioner is a warning with two choices: a callable default will not give the rows there are unique values, since the value that fills them is made once, and one may go on with the migration, as the first step towards one written by hand, or quit. MigrationAutodetector puts the question of a managed model’s new field that is unique and whose default is callable, a function typed as a one-off default included, and does not read the answer.
In a dry run of makemigrations (--dry-run or --check) the methods built on _choice_input return None unasked, and where a value was wanted that None is the field’s default; the yes-or-no questions are still put.
NonInteractiveMigrationQuestioner: logging, and status 3
NonInteractiveMigrationQuestioner is given, besides what its base class MigrationQuestioner takes, a verbosity, 1 unless told otherwise, and a function to call with a message, kept as NonInteractiveMigrationQuestioner. It overrides the questions that want a value and has the base class’s answers to the rest.
NonInteractiveMigrationQuestioner and NonInteractiveMigrationQuestioner log why the field was not migrated (NonInteractiveMigrationQuestioner, silent at verbosity 0) and call sys.exit(3), as the interactive questioner’s choice to quit does. NonInteractiveMigrationQuestioner lets the command go on: it logs, at any verbosity, and returns NOT_PROVIDED, on which the autodetector makes the AlterField with the field as the model has it.
The class never asks whether the run is dry. Where it is not merging, makemigrations makes one under --noinput, with no defaults (The makemigrations command), so to a rename it says no, and a renamed field is taken for one removed and one added. After the first edit to Repair that ends the command, dry run or not:
python manage.py makemigrations yard --dry-run --noinput
stdout | Field 'begun' on model 'repair' not migrated: it is impossible to add a non-nullable field without specifying a default.
exit status 3
The questions
In the table a question is a method of MigrationQuestioner, the method that asks it one of MigrationAutodetector unless the cell says otherwise, defaults and specified_apps are the questioner’s attributes, and NOT_PROVIDED is django, the value a field has for no default.
| question | asked by |
|---|---|
ask_initial |
arrange_, of an application with no leaf node |
ask_rename_model |
generate_, of a model that may be another renamed |
ask_rename |
create_, of a field that may be another renamed |
ask_ |
_generate_, of a managed model’s new field with no value for existing rows |
ask_ |
_generate_, in place of ask_, for a date or time field with auto_now_add |
ask_ |
_generate_, of a managed model’s new unique field whose default is callable; the answer is not read |
ask_ |
generate_, of a managed model’s field made not null with no default |
ask_merge |
makemigrations (Command django/ |
| question | base class | interactive | non-interactive |
|---|---|---|---|
ask_initial |
base class yes for a label in specified_apps or a package with no migration in it; defaults, or no, for want of a module; else no |
interactive the base’s | non-interactive the base’s |
ask_rename_model |
base class defaults, or no |
interactive yes or no; an empty first answer is no | non-interactive the base’s |
ask_rename |
base class defaults, or no |
interactive yes or no; an empty first answer is no | non-interactive the base’s |
ask_ |
base class None |
interactive 1: a value typed. 2: sys.exit(3) |
non-interactive logs unless verbosity is 0, then sys.exit(3) |
ask_ |
base class None |
interactive 1: a value typed, "timezone.now" offered. 2: sys.exit(3) |
non-interactive logs unless verbosity is 0, then sys.exit(3) |
ask_ |
base class None |
interactive 1: None. 2: sys.exit(3) |
non-interactive the base’s |
ask_ |
base class None |
interactive 1: a value typed. 2: NOT_PROVIDED. 3: sys.exit(3) |
non-interactive logs; NOT_PROVIDED |
ask_merge |
base class defaults, or no |
interactive yes or no; an empty first answer is no | non-interactive the base’s |