Skip to main content

Models and Databases

Models in this codebase are the single, definitive source of truth about your data. They contain the essential fields and behaviors of the data you’re storing. Generally, each model maps to a single database table.

Model Definition and Metadata

All models in this project subclass django.db.models.Model. The Model class uses a metaclass called ModelBase (found in django/db/models/base.py) which handles the registration of fields, managers, and the setup of the _meta options.

The Model Class

A model is defined by class attributes that represent database fields. For example, the Group model in django/contrib/auth/models.py defines a name field and a many-to-many relationship with Permission:

class Group(models.Model):
name = models.CharField(_("name"), max_length=150, unique=True)
permissions = models.ManyToManyField(
Permission,
verbose_name=_("permissions"),
blank=True,
)

objects = GroupManager()

class Meta:
verbose_name = _("group")
verbose_name_plural = _("groups")

Model Metadata (Meta Options)

The inner class Meta is used to provide metadata about the model. Common options include:

  • verbose_name and verbose_name_plural: Human-readable names for the model.
  • abstract: If True, the model will be an abstract base class (e.g., PermissionsMixin in django/contrib/auth/models.py).
  • unique_together: A list of field names that must be unique when considered together.
  • ordering: The default ordering for the object list.

These options are processed into an Options object (accessible via Model._meta) during class creation.

Managers and QuerySets

The Manager is the interface through which database query operations are provided to models. By default, every model has at least one manager, typically named objects.

Custom Managers

You can define custom managers to add extra methods or modify the initial QuerySet the manager returns. In django/contrib/auth/models.py, the UserManager provides helper methods for creating users:

class UserManager(BaseUserManager):
def create_user(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_superuser", False)
return self._create_user(username, email, password, **extra_fields)

def create_superuser(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
return self._create_user(username, email, password, **extra_fields)

QuerySet API

A QuerySet represents a collection of objects from your database. It can have zero, one, or many filters. QuerySets are lazy—the act of creating a QuerySet doesn’t involve any database activity.

Common operations include:

  • filter(**kwargs): Returns a new QuerySet containing objects that match the given lookup parameters.
  • exclude(**kwargs): Returns a new QuerySet containing objects that do not match the given lookup parameters.
  • get(**kwargs): Returns a single object matching the lookup parameters.

Example of complex filtering from tests/queries/tests.py:

def test_subquery_condition(self):
qs1 = Tag.objects.filter(pk__lte=0)
qs2 = Tag.objects.filter(parent__in=qs1)
qs3 = Tag.objects.filter(parent__in=qs2)
# QuerySets are evaluated only when needed
self.assertEqual(qs3.query.subq_aliases, {"T", "U", "V"})

Query Optimization

To avoid the "N+1" query problem, where the database is hit multiple times for related objects, this codebase provides select_related and prefetch_related.

  • select_related: Works by creating a SQL join and including the fields of the related object in the SELECT statement. Use this for ForeignKey and OneToOneField.
  • prefetch_related: Does a separate lookup for each relationship and does the "joining" in Python. Use this for ManyToManyField and reverse ForeignKey relationships.

Database Migrations

Migrations are how this project propagates changes you make to your models (adding a field, deleting a model, etc.) into your database schema.

The Migration Class

Each migration is a Python file containing a subclass of django.db.migrations.Migration. It defines:

  1. dependencies: A list of migrations this migration depends on.
  2. operations: A list of Operation classes that define the changes (e.g., CreateModel, AddField, AlterUniqueTogether).

Example structure from django/db/migrations/migration.py:

class Migration:
"""
The base class for all migrations.
"""
operations = []
dependencies = []

def apply(self, project_state, schema_editor, collect_sql=False):
"""
Takes a project_state representing all models at the start of this
migration and applies it to the database using the provided schema_editor.
"""
return project_state

Model Instance State

Every model instance has a _state attribute, which is an instance of ModelState (defined in django/db/models/base.py). This object tracks:

  • db: The database alias the instance is associated with.
  • adding: A boolean indicating if the instance is being added to the database (True) or updated (False).

This state is crucial for the save() method to determine whether to perform an INSERT or an UPDATE SQL statement. Attempting to instantiate an abstract model directly will result in a TypeError, as enforced in Model.__init__.