Skip to main content

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:

  1. Metadata Extraction: It looks for an inner Meta class and initializes an Options object (accessible via _meta) to store model configuration.
  2. Field Registration: It iterates through the class attributes and identifies instances of Field.
  3. Exception Creation: It automatically attaches DoesNotExist and MultipleObjectsReturned exception 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: If True, the database will store empty values as NULL.
  • blank: If True, 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: If True, 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: If True, 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:

  1. clean_fields(): Calls field.clean() for every field on the model.
  2. clean(): A hook for custom, model-wide validation logic.
  3. validate_unique(): Checks for uniqueness constraints defined via unique=True, unique_together, or UniqueConstraint.

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.