Model Relationships
In this codebase, model relationships are implemented through a hierarchy of specialized field classes in django/db/models/fields/related.py. These fields manage how models connect to one another at both the database level (via foreign keys and intermediary tables) and the Python level (via descriptors and managers).
Relational Field Architecture
All relationship fields inherit from the base RelatedField class. This class provides the core logic for resolving model references, handling reverse relationship names (related_name), and managing name clashes during model validation.
The primary relationship types are:
- Many-to-One: Implemented by
ForeignKey. - One-to-One: Implemented by
OneToOneField. - Many-to-Many: Implemented by
ManyToManyField. - Custom Joins: Implemented by
ForeignObject, which serves as the base forForeignKeybut supports multi-column and conditional joins.
Many-to-One Relationships
The ForeignKey class defines a many-to-one relationship. In the database, this is represented by a single column on the local model that stores the primary key of the related model. By default, ForeignKey generates a column name by appending _id to the field name, as seen in ForeignKey.get_attname():
def get_attname(self):
return "%s_id" % self.name
Key Parameters and Behavior
- on_delete: This is a mandatory callable that defines what happens when the referenced object is deleted. The codebase supports both Python-level variants (like
CASCADE,SET_NULL) and database-level variants (likeDB_CASCADE). - to_field: By default, a
ForeignKeytargets the primary key of the remote model. You can target a different unique field usingto_field. - Validation: The
ForeignKey.validate()method ensures that the related object exists in the database before saving, using a queryset filter:using = router.db_for_read(self.remote_field.model, instance=model_instance)
qs = self.remote_field.model._base_manager.using(using).filter(
**{self.remote_field.field_name: value}
)
One-to-One Relationships
A OneToOneField is conceptually a ForeignKey with a unique constraint. In django/db/models/fields/related.py, it explicitly sets unique=True in its __init__ method:
class OneToOneField(ForeignKey):
def __init__(self, to, on_delete, to_field=None, **kwargs):
kwargs["unique"] = True
super().__init__(to, on_delete, to_field=to_field, **kwargs)
The primary difference between a OneToOneField and a ForeignKey(unique=True) is the reverse accessor. While a ForeignKey reverse accessor returns a manager (e.g., model_set), a OneToOneField reverse accessor returns the single related object directly.
Many-to-Many Relationships
The ManyToManyField class manages relationships where both sides can have multiple associations. Unlike ForeignKey, it does not add a column to the model's table. Instead, it uses an intermediary "through" table.
Intermediary Models
If no through model is specified, ManyToManyField automatically generates one using create_many_to_many_intermediary_model. However, you can provide a custom model to store extra data on the relationship:
# Example of a custom through model
class Group(models.Model):
members = models.ManyToManyField(Person, through="Membership")
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
Symmetrical Relationships
For self-referential many-to-many relationships (e.g., to="self"), the relationship is symmetrical by default. This means if Person A is a friend of Person B, Person B is automatically a friend of Person A. This is controlled by the symmetrical argument in ManyToManyField.__init__.
Custom Multi-Column Joins
The ForeignObject class is a low-level abstraction used for complex joins that ForeignKey cannot handle, such as joins on multiple columns or joins with extra SQL restrictions.
Implementing Custom Restrictions
You can override get_extra_restriction to add custom SQL to the JOIN clause. This is useful for multi-tenant or multi-language systems where a join must always filter by a specific criteria.
Example from tests/foreign_object/models/article.py:
class ActiveTranslationField(models.ForeignObject):
def get_extra_restriction(self, alias, related_alias):
# Adds a language constraint to the JOIN condition
return ColConstraint(alias, "lang", get_language())
Reverse Relationships and Accessors
The RelatedField base class manages how models are accessed from the "other side" of the relationship.
- related_name: Defines the attribute name on the target model. If not provided, it defaults to
modelname_set(forForeignKey) ormodelname(forOneToOneField). - related_query_name: Defines the name used for filtering in querysets (e.g.,
Target.objects.filter(query_name__field=value)). - Clash Detection: The
_check_clashesmethod inRelatedFieldensures that reverse accessors do not conflict with existing fields or other relationships on the target model, raising errors likefields.E302orfields.E304.
def _check_clashes(self):
# ... logic to check if rel_name or rel_query_name
# clashes with fields on the target model
Implementation Gotchas
- ManyToManyField and null: Setting
null=Trueon aManyToManyFieldhas no effect and triggers afields.W340warning, as the relationship is managed by an intermediary table, not a nullable column. - ForeignKey(unique=True): Using
unique=Trueon aForeignKeytriggers afields.W342warning, suggesting the use ofOneToOneFieldinstead for better descriptor behavior. - Recursive Relationships: When defining a relationship to
self, theRECURSIVE_RELATIONSHIP_CONSTANT(the string"self") is used to handle lazy resolution of the model class.