Defining Models and Fields
In this codebase, database schemas are defined as Python classes that inherit from the core Model class. This implementation uses a metaclass-driven approach to transform class attributes into database fields and manage model metadata.
The Model Class
All models in this project must subclass django.db.models.base.Model. The Model class provides the interface for database interactions, such as save(), delete(), and refresh_from_db().
When you define a model, the ModelBase metaclass (found in django/db/models/base.py) orchestrates the setup process. It performs several critical tasks:
- Metadata Extraction: It looks for an inner
Metaclass and initializes anOptionsobject (accessible via_meta) to store model configuration. - Field Registration: It iterates through the class attributes and identifies instances of
Field. - Exception Creation: It automatically attaches
DoesNotExistandMultipleObjectsReturnedexception classes to the model.
Example: Permission Model
The Permission model in django/contrib/auth/models.py demonstrates a standard implementation:
from django.db import models
from django.utils.translation import gettext_lazy as _
class Permission(models.Model):
name = models.CharField(_("name"), max_length=255)
content_type = models.ForeignKey(
ContentType,
models.CASCADE,
verbose_name=_("content type"),
)
codename = models.CharField(_("codename"), max_length=100)
class Meta:
verbose_name = _("permission")
verbose_name_plural = _("permissions")
unique_together = [["content_type", "codename"]]
ordering = ["content_type__app_label", "content_type__model", "codename"]
Defining Fields
Fields are defined as class attributes using subclasses of django.db.models.fields.Field. Each field instance manages its own database column type, validation logic, and default values.
Core Field Arguments
The Field base class constructor in django/db/models/fields/__init__.py defines several universal parameters:
null: IfTrue, the database will store empty values asNULL.blank: IfTrue, the field is allowed to be blank in forms and during model validation.default: Provides a default value or a callable that returns a value.choices: An iterable of 2-tuples or a mapping to be used as choices for the field.primary_key: IfTrue, this field becomes the primary key for the model.
Field Contribution
Fields are attached to the model via the contribute_to_class() method. This method sets the field's name, associates it with the model class, and adds it to the model's _meta object.
# Internal logic in Field.contribute_to_class
def contribute_to_class(self, cls, name, private_only=False):
self.set_attributes_from_name(name)
self.model = cls
cls._meta.add_field(self, private=private_only)
if self.column:
setattr(cls, self.attname, self.descriptor_class(self))
Model Metadata (The Meta Class)
The inner Meta class allows you to define non-field options for the model. These are processed by ModelBase and stored in the _meta attribute.
Common options include:
abstract: IfTrue, the model is an Abstract Base Class and will not result in a database table.ordering: A list of field names defining the default ordering for querysets.unique_together: A list of field sets that must be unique when combined.db_table: Explicitly sets the name of the database table.
Enumerated Choices
This codebase supports several ways to define choices for fields, including the use of IntegerChoices and TextChoices for type safety.
Using Choices Classes
You can define choices as inner classes to keep them encapsulated within the model. The Choiceful model in tests/model_fields/models.py illustrates this:
class Choiceful(models.Model):
class Suit(models.IntegerChoices):
DIAMOND = 1, "Diamond"
SPADE = 2, "Spade"
HEART = 3, "Heart"
CLUB = 4, "Club"
choices_from_enum = models.IntegerField(choices=Suit)
with_choices_dict = models.IntegerField(choices={1: "A"}, null=True)
When choices are defined, the model automatically gains a get_FOO_display() method (where FOO is the field name) to retrieve the human-readable label for the current value.
Model Validation
Validation is performed through the full_clean() method, which orchestrates several steps:
clean_fields(): Callsfield.clean()for every field on the model.clean(): A hook for custom, model-wide validation logic.validate_unique(): Checks for uniqueness constraints defined viaunique=True,unique_together, orUniqueConstraint.
The Model.save() method does not automatically call full_clean(). Developers must call it explicitly if they require full validation before persistence.
Constraints and Checks
The codebase includes a robust system for checking model definitions at startup. Model.check() runs various system checks to ensure field names are valid (e.g., they cannot end with an underscore or contain __) and that metadata options like ordering refer to existing fields.