Chapter 8 · Models and fields

8.3 A field: the base class

A field of a model is an object of a class built on django.db.models.fields.Field. It describes one column, or for a few field classes none, and holds no values. The base class gives every field its place on the model class (Field.contribute_to_class), its column type (Field.db_type), the steps a value takes into a row (Field.pre_save, Field.get_db_prep_save, Field.get_prep_value) and out of one (Field.get_db_converters), and Field.to_python, defaults and choices.

A field is asked for many things, by parties that know nothing of each other. The schema editor wants a column type, and a migration the arguments the field was made with. A save wants the value to write, in a form the database’s driver takes, and a query wants the same for the value in a condition. Validation wants a value cleaned, a form a form field, a serializer the value as text. Field answers each with a method or two, and a field class changes the answers it has to (The field classes).

Two facts shape the class. A field is made before its model exists: the body of a class statement runs first, and tonnage = models.PositiveIntegerField() is an ordinary call, which returns an object that knows neither its name nor its class. So a field comes into being in two steps, its __init__ and then its contribute_to_class. And one field object serves every instance of its model, so it keeps no value: a method that deals with a value is handed the value, or the instance to read it from.

What a field’s __init__ keeps

Field.__init__ stores its arguments and takes a number. The base class’s version checks nothing: an argument that cannot be right is found afterwards by the system checks, which look at the field once it has a model (The model checks).

Most arguments become attributes of the same name: Field.primary_key, Field.null, Field.blank, Field.default, Field.db_column, Field.editable and the rest. Some are stored as they were passed, whatever is made of them later, and Field.deconstruct gives those back; the comments beside three of them, verbose_name, validators and error_messages, say that is what they are stored for. verbose_name is stored twice: as Field.verbose_name, which is filled in from the field’s name when none was given, and as Field._verbose_name, which is left alone. unique, validators, error_messages and db_tablespace are stored once each, as Field._unique, Field._validators, Field._error_messages and Field._db_tablespace, and the public attributes of those names are worked out from them when read.

rel=, which a relation field’s own __init__ passes, is stored as Field.remote_field, and Field.is_relation records whether there is one (Relations: a field, its rel and the class at the other end). And choices is assigned through a property, which puts whatever it is given into one form.

Two counters, and the order of a model’s fields

Field.creation_counter and Field.auto_creation_counter are two numbers kept on the class Field, and every field made in a process takes its own from one of them. A field made in the ordinary way takes the class’s creation_counter as it stands, and steps the class’s up by one. A field made with auto_created=True, which is how Django makes the key and the link to a parent that it adds to a model itself, takes auto_creation_counter, which starts at −1, and steps it down. The numbers therefore run across all the models of a process, in the order their fields were made, and such a field has a negative one.

The number is what fields are ordered by. Field.__lt__ compares counters, and functools.total_ordering builds the other comparisons from it and Field.__eq__. The comment on __lt__ says what the method is for: bisect does not take a comparison function, and Options.add_field uses bisect.insort to keep Options.local_fields in order as the fields arrive. So a model’s own fields stand in the order they were made, those with a negative number first, whatever order they were added to the class in. BaseDatabaseSchemaEditor.table_sql goes through that list to write the table, which makes it the order of the columns as well.

[(f.name, f.creation_counter) for f in Ship._meta.local_fields]  ->  [('id', -2), ('name', 28), ('tonnage', 30), ('home', 31), ('captain', 32)]
Named._meta.get_field('name').creation_counter == Port._meta.get_field('name').creation_counter == Ship._meta.get_field('name').creation_counter  ->  True
Port._meta.get_field('name') is Ship._meta.get_field('name'), Port._meta.get_field('name') == Ship._meta.get_field('name')  ->  (False, False)
...
hash(Port._meta.get_field('name')) == hash(Ship._meta.get_field('name'))  ->  True

The field "name" stands ahead of the three that the body of Ship wrote, though it was added to Ship after them: it was made earlier, in the body of Named, and Ship’s copy of it kept the number. The key that Django added stands first.

The copies are why equality needs more than the counter. Field.__eq__ holds two fields equal when the counter and the model are both the same, and __lt__, where the counters are equal, goes by the model’s app label and name, which gives the copies a fixed order among themselves. Field.__hash__ is the hash of the counter alone, so the copies hash alike. A field’s model is not assigned until the field joins a class, and its counter never changes.

Joining a class: contribute_to_class

Field.contribute_to_class is the method the metaclass calls, with the new class and the attribute’s name, for each field it finds among the body’s attributes (ModelBase: a class statement becomes a model). This is the first field of Ship’s body joining the class:

ModelBase.add_to_class(Ship, 'tonnage', a PositiveIntegerField)
  Field.contribute_to_class(Ship, 'tonnage')  (a PositiveIntegerField)
    Options.add_field(Ship.tonnage)
    DeferredAttribute.__init__(Ship.tonnage)

The method begins by letting the field learn its names, in Field.set_attributes_from_name. There are three. Field.name is the attribute the body assigned the field to, unless the field was made with a name of its own, which is kept. Field.attname is the name its value is kept under on an instance: Field.get_attname returns the name, and a foreign key overrides it to add "_id". Field.column is the column, which Field.get_attname_column gives as the db_column the field was made with or else the attname. set_attributes_from_name also sets Field.concrete, true when the column is not None: a concrete field is one that has a column. A field class without one says so by overriding get_attname_column, as ManyToManyField and CompositePrimaryKey do.

[(f.name, f.attname, f.column) for f in Ship._meta.fields]  ->  [('id', 'id', 'id'), ('name', 'name', 'name'), ('tonnage', 'tonnage', 'tonnage'), ('home', 'home_id', 'home_id'), ('captain', 'captain_id', 'captain_id')]
[(f.name, f.attname, f.column) for f in Voyage._meta.get_fields() if not f.auto_created]  ->  [('ship', 'ship_id', 'ship_id'), ('sailed', 'sailed', 'sailed'), ('calls', 'calls', None)]

With its names settled, the field records the class as Field.model and enters itself in the class’s options with Options.add_field, which is where it takes its place in the model’s lists of fields and where a field that says it is the primary key becomes one (The options: what a model keeps as _meta).

Then, if it has a column, it sets a descriptor on the class under its attname: an object of the class the field names as Field.descriptor_class, made with the field as its one argument. On the base class that is DeferredAttribute, which keeps the field and has a __get__ and no __set__. By Python’s rules an entry in an instance’s own __dict__ is found before such a descriptor, so it is not asked for a value the instance holds. It is asked when the instance has no entry under the attname, which is what it means for the field to be deferred, and it then fetches the value (An instance and its state). A field class may name a descriptor of its own: ForeignKey and CompositePrimaryKey do, and the two file fields (File fields). A field with no column gets none from the base class, and the classes of such fields set whatever they need in their own contribute_to_class.

A field with choices gets one more attribute on the class: a method whose name is "get_%s_display" with the field’s name filled in, a functools.partialmethod of Model._get_FIELD_display that carries the field. It is not set when the class’s own body defines an attribute of that name. One that the class merely inherits is replaced, and the source’s comment says why: to allow overriding inherited choices.

One field, one model

A field object belongs to one model class, the one in Field.model, and where two classes need the same field Django makes a copy. The common case is an abstract base: for each class that inherits directly from one, the metaclass passes each field of the base that the class has not replaced to copy.deepcopy, and adds the copy to the new class like a field of the body. Ship inherits "name" from Named so:

Field.__deepcopy__  (a copy of Named.name)
ModelBase.add_to_class(Ship, 'name', a CharField)

Despite its name, __deepcopy__ copies very little, and its comment says so: most things are not intended to be altered after initial creation. It makes a shallow copy of the field, and for a relation a shallow copy of the rel, pointed at the new field. Everything else the original and its copies share, down to the list of validators. The shallow copy is Field.__copy__, which builds an empty object, gives it the field’s class and a copy of the field’s __dict__, and so keeps the counter. It is written by hand to keep clear of Field.__reduce__, which Python’s own copying would go through.

A field that was entered as private, as a generic foreign key is, is copied in the same way for a subclass that has not replaced it, whether or not the base is abstract. A parent that has a table of its own is the other case, and there none of the parent’s fields with columns, nor its many-to-many fields, is copied. The child’s options list the parent’s own field objects, each still saying that its model is the parent (Inheritance: abstract bases, parents with tables and proxies):

Officer._meta.get_field('name') is Sailor._meta.get_field('name'), Officer._meta.get_field('name').model  ->  (True, <class 'harbour.crew.models.Sailor'>)

Field.deconstruct describes a field as the call that would make it again: its name, the dotted path of its class, a list of positional arguments, which the base class leaves empty, and a dictionary of keyword arguments holding only those that differ from the defaults. Migrations are written from it. The autodetector compares two versions of a model field by field through their deconstructions, and the writer puts a field into a migration file as that call. A field class that takes arguments of its own overrides the method to add them, and one that forces an argument takes it out again (Serializing values: serializer_factory and deconstruct).

The path is shortened on purpose. A class whose module is under django.db.models.fields is written as if it lived in django.db.models: the classes of the package’s own module by dropping the package’s last name, and those of its modules "related", "files", "generated", "json", "proxy" and "composite" by dropping the module’s name too. The docstring asks for the most portable version of the path. A migration therefore names django.db.models.ForeignKey, and goes on loading whichever of those modules the class is in.

Ship._meta.get_field('tonnage').deconstruct()  ->  ('tonnage', 'django.db.models.PositiveIntegerField', [], {})
Ship._meta.get_field('name').deconstruct()  ->  ('name', 'django.db.models.CharField', [], {'max_length': 60})

Field.clone calls the field’s class with what deconstruct returned. The result is a new field of the same definition and nothing else: a new counter, no name, no model. It is what ModelState.from_model uses to take a model’s fields without taking the model.

Pickling goes the other way and avoids making a second object at all. Field.__reduce__, for a field that has a model, reduces it to three strings, the app label, the model’s name and the field’s, and unpickling asks the app registry for that model and its options for that field. The docstring gives the intent: pickling should return the field the model’s options hold, not a new copy of it. A field with no model, such as one made to say what type an expression has, is pickled as its __dict__.

pickle.loads(pickle.dumps(Ship._meta.get_field('tonnage'))) is Ship._meta.get_field('tonnage')  ->  True
Ship._meta.get_field('tonnage').__reduce__()[0].__name__, Ship._meta.get_field('tonnage').__reduce__()[1]  ->  ('_load_field', ('fleet', 'Ship', 'tonnage'))

The column: db_type

Field.db_type returns the column type a field wants on one database, and the field does not know it: the types are the backend’s. The method asks the field for its internal type, a string, and looks that up in the connection’s BaseDatabaseWrapper.data_types, a dictionary each backend fills in. The entry is a format string, filled from the field’s own attributes so that "varchar(%(max_length)s)" gets its length, or a function called with them. An internal type the dictionary lacks gives None, and to the schema editor a field whose type is None is not a column.

Field.get_internal_type, in the base class, returns the name of the field’s class. CharField, like other field classes, declares the method again, to return the string "CharField" whatever the class of the object, and the difference matters to a class built on it: a project’s subclass of CharField answers "CharField" without doing anything, and gets the column its parent gets. The comment in db_type sets out the two ways a field class has of choosing its column: return, from get_internal_type, the name of the built-in class it most resembles, or override db_type and write the type out.

A check for the column is found in the same way, by the internal type in a dictionary of the backend’s (Field.db_check, from BaseDatabaseWrapper.data_type_check_constraints), and the schema editor asks for the two together, through Field.db_parameters. So are words that follow the type (Field.db_type_suffix, from BaseDatabaseWrapper.data_types_suffix) and the type to name in a Cast where that differs (Field.cast_db_type, from BaseDatabaseOperations.cast_data_types). The format strings are filled from Field.db_type_parameters, the field’s __dict__ (Column types: from a field to a column).

Field.rel_db_type answers a different question, the type for a column of another table that points at this field. In the base class that is the field’s own type, and an automatic key answers with the type of the plain integer class of its size.

Ship._meta.get_field('tonnage').get_internal_type(), Ship._meta.get_field('tonnage').db_type(connection)  ->  ('PositiveIntegerField', 'integer unsigned')
Ship._meta.get_field('tonnage').db_parameters(connection)  ->  {'type': 'integer unsigned', 'check': '"tonnage" >= 0'}
Ship._meta.pk.db_type(connection), Ship._meta.pk.rel_db_type(connection), Ship._meta.get_field('home').db_type(connection)  ->  ('integer', 'bigint', 'bigint')

On SQLite the key of Ship, a BigAutoField, has the type "integer" and answers "bigint" for a column that points at it. Ship’s foreign key "home" points at the key of Port, another BigAutoField, and has "bigint": it got its type by asking that key.

The path of a value

A value takes one path into the database and another out, and the two are not mirror images. On the way in the field does the work, in four methods of Field that Model._save_table and the compilers call. On the way out the work is done by converters, functions gathered once for a query from the backend and from the field, and Field.to_python is not among them.

Figure 8.3 The path of one value, in and out. The field’s methods carry it in. Converters carry it out, the backend’s and then the field’s, and to_python is not on the way out of a row.

Into a row

Field.pre_save, Field.get_db_prep_save, Field.get_db_prep_value and Field.get_prep_value differ in what each is allowed to see.

Table 8.2 The four methods of Field on the way into a row, outermost first. Only the first sees the instance, and only the middle two see the connection.
method it is handed it answers with in the base class
Field.pre_save it is handed the instance, and whether the row is being inserted it answers with the value to write in the base class the instance’s attribute, as it stands
Field.get_db_prep_save it is handed a value and a connection it answers with the value as a parameter of an insert or an update in the base class an expression is returned as it is, and any other value goes to get_db_prep_value
Field.get_db_prep_value it is handed a value, a connection, and whether the value is already prepared it answers with the value in the form this backend’s driver takes in the base class calls get_prep_value, unless told the value is prepared
Field.get_prep_value it is handed a value it answers with the value as the Python type the field stands for in the base class a lazy object (a Promise) is evaluated, and anything else is returned as it is

Field.pre_save is the one step that is given the instance, and so the one step that can change what the instance holds. A field class overrides it to supply a value at the moment of saving, as a date made with auto_now=True does, and a file field, which writes its file (The field classes, File fields).

Its callers are Model._save_table and the compiler of an insert, SQLInsertCompiler.pre_save_val. Saving an instance says why an insert has both.

Table 8.3 How many times a field’s pre_save is called, and with what argument.
what is done calls of pre_save for a field
a save whose update finds its row one, with add=False, for each field it sets
a save that goes straight to an insert two, with add=True, for each field it writes: one from each caller
a save whose update finds no row, and which then inserts one with add=False and then two with add=True, for each field the update would have set
a raw save, Model.save_base called with raw=True none: the attribute is read
QuerySet.bulk_create one, with add=True, for each field inserted, from the compiler
QuerySet.update none

In the base class the other three methods are nested, each calling the next. Here a Ship has been made with a string, "300", for its tonnage, which is an integer field, and is saved. These are the calls for that one value, down to the one that converts it, and the statement:

SQLInsertCompiler.prepare_value(Ship.tonnage, '300')
  Field.get_db_prep_save('300')  (Ship.tonnage)
    IntegerField.get_db_prep_value('300')  (Ship.tonnage)
      Field.get_db_prep_value('300')  (Ship.tonnage)
        IntegerField.get_prep_value('300')  (Ship.tonnage)
          Field.get_prep_value('300')  (Ship.tonnage)  ->  '300'
          -> 300
...
SQL INSERT INTO "fleet_ship" ("name", "tonnage", "home_id", "captain_id") VALUES (%s, %s, %s, %s) RETURNING "fleet_ship"."id" with params ('Wren', 300, 2, None)

SQLInsertCompiler.prepare_value calls the field’s get_db_prep_save, here the base class’s. IntegerField overrides the two methods under it, and each override begins by calling the method it overrides. Field.get_prep_value has nothing to do with a string, and it is IntegerField’s that makes an integer of it, which is what reaches the statement.

get_db_prep_value is also where a field class brings the backend in, by handing the prepared value to the connection’s operations object. For the "signed_on" of a Sailor, a DateTimeField, that last step turns an aware datetime into the string SQLite stores. The calls of get_prep_value between these two lines are left out:

DateTimeField.get_db_prep_value(datetime(2026, 4, 10, 9, 0, tzinfo=UTC))  (Sailor.signed_on)
  DatabaseOperations.adapt_datetimefield_value(datetime(2026, 4, 10, 9, 0, tzinfo=UTC))  ->  '2026-04-10 09:00:00'

An update takes the same road from the second step on: SQLUpdateCompiler.as_sql calls get_db_prep_save for each field it sets. This is the same Sailor, fetched and saved again, with the lines for its other fields and everything nested left out:

Model.save()  (a Sailor)
  Field.pre_save(add=False)  (Sailor.name)  ->  'Edda'
...
  SQLUpdateCompiler.as_sql
    Field.get_db_prep_save('Edda')  (Sailor.name)
...
  SQL UPDATE "crew_sailor" SET "name" = %s, "ship_id" = %s, "signed_on" = %s WHERE "crew_sailor"."id" = %s with params ('Edda', 1, '2026-04-10 09:00:00', 5)

What is written need not be a value. Both compilers resolve an expression before anything else, and get_db_prep_save returns as it is anything that has an as_sql method, to be compiled into the statement where a parameter would have stood.

Into a condition

A value given to a filter is prepared by the two inner methods of Field, get_prep_value and get_db_prep_value, at two different moments. This is the start of a query for a Sailor by name:

CharField.get_prep_value('Edda')  (Sailor.name)
  Field.get_prep_value('Edda')  (Sailor.name)  ->  'Edda'
  CharField.to_python('Edda')  (Sailor.name)  ->  'Edda'
  -> 'Edda'
Field.get_db_prep_value('Edda', prepared=True)  (Sailor.name)  ->  'Edda'

The first call, with the two under it, was made while the queryset was being built. Lookup.__init__ calls Lookup.get_prep_lookup, which hands the value to the get_prep_value of the field on the lookup’s left. get_prep_value takes no connection, and at that moment there need not be one: nothing has yet said which database the queryset will be run against. It follows that a value the field cannot take is refused when filter is called, and no statement is sent:

Ship.objects.filter(tonnage='many')  ->  raises ValueError: Field 'tonnage' expected a number but got 'many'.

The last call was made as the statement was compiled for a connection. FieldGetDbPrepValueMixin.get_db_prep_lookup calls the field’s get_db_prep_value with prepared=True, which is what that argument is for: the first step has been taken and is not to be taken again. The mixin is on the lookups that compare a column with a value of its own type, Exact and the four orderings among them. A lookup may decline both steps. The pattern lookups, Contains and its relatives, set Lookup.prepare_rhs false and deal with their value themselves (From QuerySet to SQL).

Out of a row

A row that comes back from a query is not handed to the fields. Before any row is converted, SQLCompiler.get_converters goes through the expressions the query selected and asks two parties about each. It asks the backend’s operations object, with get_db_converters, and it asks the expression, which for a column of a model answers with Field.get_db_converters of the column’s field. Each answer is a list of functions, and a column for which both lists are empty is left out altogether. SQLCompiler.apply_converters then calls the functions kept on each row in turn, the backend’s before the field’s, each with the value, the expression and the connection. This is what followed the statement of the query for the Sailor, which selected four columns:

DatabaseOperations.get_db_converters(the column of Sailor.id)  ->  []
DatabaseOperations.get_db_converters(the column of Sailor.name)  ->  []
DatabaseOperations.get_db_converters(the column of Sailor.ship, whose values are those of Ship.id)  ->  []
DatabaseOperations.get_db_converters(the column of Sailor.signed_on)  ->  [convert_datetimefield_value]
DatabaseOperations.convert_datetimefield_value(datetime(2026, 4, 10, 9, 0))  ->  datetime(2026, 4, 10, 9, 0, tzinfo=UTC)

SQLite’s operations object had a converter for one of the four. The expression for a foreign key’s column carries the field the key points at, which is why the third line names the key of Ship. The value of "signed_on" reached it as a naive datetime, and DatabaseOperations.convert_datetimefield_value gave it its time zone back. The other three values went as the driver produced them to Model.from_db, which makes the instance from the row’s values. The columns an insert or an update hands back are converted in the same way.

Field.get_db_converters, in the base class, returns a list holding the field’s from_db_value when the field has a method of that name, and an empty list when it has not. The base class declares no such method: the test is hasattr. Of the field classes in django/db/models/fields/, JSONField is the one that does:

Sailor._meta.get_field('signed_on').get_db_converters(connection), hasattr(models.DateTimeField, 'from_db_value')  ->  ([], False)
models.JSONField().get_db_converters(connection)[0].__qualname__, hasattr(models.JSONField, 'from_db_value')  ->  ('JSONField.from_db_value', True)

A field class can also take part by overriding get_db_converters, as DurationField and ForeignKey do, each to add a function for backends that need it. The rest of the conversion on the way out is the backend’s own, chosen by the field’s internal type (Values in and out: adapters, converters and time zones).

Field.to_python

Field.to_python converts a value to the Python type the field stands for, and raises ValidationError for one it cannot convert. In the base class it returns the value as it is. Nothing on the way out of a row calls it. It is called where a value arrives from somewhere other than the database. Field.clean calls it when an instance is validated. The get_prep_value of many field classes calls it, on the way in. The deserializers call it for a value read from text, and a form’s choice field is given it as the function to coerce a submitted choice with. django.contrib has callers of the same kind, such as the code that reads a user’s key back out of a session.

Two things follow. An instance holds what it was given: under the base class’s descriptor, assigning to an attribute converts nothing, since a DeferredAttribute has no __set__. The Ship that was made with a string for its tonnage still holds the string after its save, and the row holds the number:

wren.tonnage, Ship.objects.get(name='Wren').tonnage  ->  ('300', 300)

The attribute becomes an integer when the instance is validated, since Model.clean_fields stores what clean returns, or when the row is loaded again. And a field class that needs to turn what a column holds into an object of its own supplies a converter, as from_db_value or through get_db_converters, or, as the file fields do, a descriptor that wraps the value when the attribute is read. Overriding to_python alone changes nothing about a row that is read.

A value as text

Field.value_from_object returns the instance’s attribute under the attname, and Field.value_to_string returns that value as a string, which in the base class is str of it. The serializers are what call the second: a value that is not of a plain type is written out as the string its field makes of it, and read back in through the field’s to_python, so the two methods are a pair: what the one writes, the other has to be able to read (Serialization: serialize, deserialize and DeserializedObject). value_from_object is also what model_to_dict reads an instance with to make a form’s initial data.

Sailor._meta.get_field('signed_on').to_python('2026-04-10 09:00:00+00:00')  ->  datetime.datetime(2026, 4, 10, 9, 0, tzinfo=datetime.timezone.utc)
Sailor._meta.get_field('signed_on').value_to_string(edda), Sailor._meta.get_field('ship').value_to_string(edda)  ->  ('2026-04-10T09:00:00+00:00', '1')

Defaults

Field.get_default returns the value a new instance gets for a field it was given nothing for, and Model.__init__ is what asks (An instance and its state). It calls a function that the field chooses once and keeps, as Field._get_default. Where the field was made with a default that is callable, the function is that callable, so it is called afresh for each instance. Where the default is a plain value, the function returns that same object each time. Field.has_default is true in both cases: it tests the default against the marker NOT_PROVIDED, so a default of None is a default.

With no default of any kind, the field falls back on one of two values. A field class whose Field.empty_strings_allowed is false, as it is for the classes of numbers, dates and booleans among others, gets None, and so does a nullable field on a backend that tells an empty string from a null. Any other gets the empty string:

Ship._meta.get_field('tonnage').get_default(), Ship._meta.get_field('name').get_default(), Ship._meta.get_field('captain').get_default()  ->  (None, '', None)

A default may also be the database’s. A field made with db_default=, and with no default of the ordinary kind, gets from _get_default a function that returns one DatabaseDefault, made once, which a new instance holds under the field’s attname in the value’s place. The field classes follows such a field through its table and a save.

Field.get_pk_value_on_save is called by _save_table for an instance whose primary key is not set, to give the key’s field a chance to make one before the choice between an update and an insert. It returns what get_default gives. The exception is a field whose default is itself a false value, such as 0: the method tests the default for truth before it calls anything, and returns None. An automatic key has no default and answers None, which leaves the key for the database to assign.

Cleaning a value: clean

Field.clean is the field’s part in validating an instance, and a save does not call it: Model.clean_fields does, for the fields of an instance, and stores what each call returns (Validating an instance). It is three calls in a row. to_python converts the value. Field.validate holds it to the field’s own arguments: for a field that is editable, it refuses a value that is not empty and is not among the choices, where there are choices, None where the field is not nullable, and an empty value where the field is not allowed to be blank. Field.run_validators passes over an empty value and otherwise calls each of Field.validators, gathering the errors, and puts the field’s own message in place of a validator’s where Field.error_messages has one for the error’s code.

Choices

The choices of a field are a list of permitted values, each with a label for people, and the field stores them in one form whatever form they were given in. The attribute is a property, and assigning to it passes the value through normalize_choices. A mapping becomes a list of pairs. An enumeration class built on Django’s Choices becomes the list it gives as its own choices. A callable is not called: it is wrapped in a CallableChoiceIterator, which calls it each time the choices are gone through. A group, a label with choices of its own under it, is put into the same form one level down.

The crew’s officers have a rank, declared with an enumeration:

# harbour/crew/models.py
class Rank(models.TextChoices):
    MASTER = "master"
    MATE = "mate", "First mate"
    ENGINEER = "engineer"


# SailorQuerySet and Sailor are declared here


class Officer(Sailor):
    rank = models.CharField(max_length=10, choices=Rank)
Officer._meta.get_field('rank').choices  ->  [('master', 'Master'), ('mate', 'First mate'), ('engineer', 'Engineer')]
...
Officer(rank='mate').get_rank_display(), Officer(rank='bosun').get_rank_display()  ->  ('First mate', 'bosun')
...
list(Officer._meta.get_field('rank').get_choices())  ->  [('', '- Select an option -'), ('master', 'Master'), ('mate', 'First mate'), ('engineer', 'Engineer')]

What the field kept is a plain list of pairs, with no trace of the class Rank. deconstruct gives a migration that list, so a migration does not import the enumeration.

Choices are not known to the database. They are listed in Field.non_db_attrs, the attributes that do not affect a column’s definition, and a save writes whatever value the instance holds. Field.validate refuses a value outside them. Field.formfield makes a choice field of them. And the display method that contribute_to_class added looks the value up in Field.flatchoices, the same choices with the groups flattened away, and returns the label, or the value itself when it is not among them, as "bosun" is.

Field.get_choices, in the last line, returns the choices with a blank one in front, for a form’s list to start with, and leaves the blank one out when asked to, or when the choices already hold a value that is empty or None. Called on a field with no choices it serves a relation: it runs a query on the related model’s default manager and returns, for each object, the value a foreign key to it would hold and the object’s str.

The enumeration types

Choices is an enum.Enum with a metaclass of its own, ChoicesType, and its two subclasses add a concrete type: IntegerChoices is also an enum.IntEnum, and TextChoices an enum.StrEnum. A member of either is therefore a real integer or string, equal to its value, and can be assigned to a field and written to a row as it is.

type(Rank), [c.__name__ for c in Rank.__mro__]  ->  (<class 'django.db.models.enums.ChoicesType'>, ['Rank', 'TextChoices', 'Choices', 'StrEnum', 'str', 'ReprEnum', 'Enum', 'object'])
...
Rank.MATE, Rank.MATE.value, Rank.MATE.label, Rank.ENGINEER.label  ->  (Rank.MATE, 'mate', 'First mate', 'Engineer')
Rank.MATE == 'mate', str(Rank.MATE), 'mate' in Rank, Rank('mate') is Rank.MATE  ->  (True, 'mate', True, True)

The metaclass adds the labels. As the class is made, ChoicesType.__new__ goes through the members the body declared. Where a member’s value is a list or a tuple of more than one element and the last is a string, that last element is taken off as the label and the rest is the value, as for the member MATE. Otherwise the label is made from the member’s name, with underscores turned into spaces and each word given a capital, so that ENGINEER becomes “Engineer”. The label is kept on the member and read as Choices.label. The finished class is passed through enum.unique, so two members with one value are an error.

What a field takes from the class is ChoicesType.choices, a property of the metaclass that pairs each member’s value with its label; ChoicesType.values, ChoicesType.labels and ChoicesType.names list the values, the labels and the members’ names alone. A class that defines "__empty__" gets a first entry in each: in choices a pair whose value is None and whose label is that attribute, and in names the string "__empty__".

A form field, and lookups

Field.formfield returns the form field for a model field, and a ModelForm gets its fields by calling it, unless it was given a callback to call in its place. The field classes override it to name their own form class (Model forms: ModelForm and fields_for_model).

Field inherits from RegisterLookupMixin, which makes a field class the place where lookups and transforms are registered. A lookup registered on a class with RegisterLookupMixin.register_lookup is found on that class and on every class built on it, which is how the lookups registered on Field itself, "exact" among them, come to work on every field (From QuerySet to SQL).