Filter concepts by levelShowing all levels.

Django · Relationships

Relationships

ForeignKey, OneToOneField, ManyToManyField, reverse access, through models, and self-referencing links

  • FK / OneToOne / M2Mthe same idea, three different uniqueness rules
  • Reverse managers are automatic<model>_set by default, renamed via related_name
  • through model = a real join tableneeded only when the LINK itself has extra fields
  • "self" for self-referencing fieldsthe class isn't defined yet at declaration time

1.ForeignKey, OneToOneField, and ManyToManyField

ForeignKey is many-to-one (many Orders, one Customer). OneToOneField enforces uniqueness on top of the same idea — exactly one match each way (one User, one Profile). ManyToManyField lets both sides have many matches (many Students, many Courses). related_name renames the reverse accessor (customer.order_set becomes customer.orders); related_query_name renames the reverse lookup keyword used in filter().

Key features

  • ForeignKey(Model, on_delete=...) is required to declare on_delete explicitly — it has no default value
  • OneToOneField is functionally a ForeignKey plus unique=True — the same on_delete rules apply to it
  • ManyToManyField needs on_delete=... on NEITHER side — deleting a row just removes its rows from the join table
  • related_name="orders" changes customer.order_set (the default) to customer.orders
  • related_query_name changes the keyword used in filter() lookups (Customer.objects.filter(order__status=...) vs a renamed keyword) independently of related_name

The three relationship fields

The three relationship fields
FieldCardinalityUnderlying structure
ForeignKeymany-to-onea column on the "many" side, storing the related row's id
OneToOneFieldone-to-onethe same column, plus a UNIQUE constraint
ManyToManyFieldmany-to-manya separate join table with two foreign keys
Cardinality vs. underlying structure
ForeignKey
many-to-one, a column on the "many" side
OneToOneField
ForeignKey + unique=True
ManyToManyField
a real join table, two foreign keys
  • ForeignKey: plain column, unconstrained — many-to-one, a column on the "many" side
  • OneToOneField: plain column, unique — ForeignKey + unique=True
  • ManyToManyField: separate join table, between unconstrained and unique — a real join table, two foreign keys

Command

python
customer = models.ForeignKey(
    "Customer", on_delete=models.PROTECT, related_name="orders", related_query_name="order",
)

ForeignKey (many-to-one), OneToOneField (ForeignKey + unique=True), ManyToManyField (a separate join table, no on_delete needed) — related_name renames the reverse accessor, related_query_name renames the filter() keyword, independently.

2.Reverse relations and through models

Every ForeignKey/ManyToManyField automatically creates a reverse manager on the OTHER model — Model.objects.filter(...) plus the default <model>_set (or related_name, if set) to go the other way. A through model replaces ManyToManyField's auto-generated join table with a real model of your own, needed the moment the relationship itself needs extra fields (grade, joined_at) beyond just "these two are linked."

Key features

  • The reverse manager's default name is <lowercased-model-name>_set — e.g. Reporter.objects... reversed from Article.reporter becomes reporter.article_set
  • related_name overrides that default; with related_name="+", Django creates no reverse relation at all
  • A through model must define its OWN ForeignKeys to both sides explicitly — Django does not add them automatically once through= is set
  • Once a through model is set, .add()/.remove()/.clear() on the ManyToManyField are restricted (or removed, depending on whether the through model has extra required fields) — the through model's own manager is used instead
  • The through model is a completely ordinary model — it can be queried, filtered, and admin-registered like any other

Plain ManyToManyField vs a through model

Plain ManyToManyField vs a through model
AspectPlain ManyToManyFieldthrough model
Join tableauto-created, invisiblea real model you define
Extra fields on the relationshipnot possibleyes — any fields the through model declares
Adding a link.add(obj)Enrollment.objects.create(student=..., course=..., grade=...)
Querying the relationship itselfnot directly possibleEnrollment.objects.filter(grade="A")
A through model makes the join table a real, queryable model

Student

id

Enrollment (through)

student, course

grade, enrolled_at

Course

id

  • Student
    • id
  • Enrollment (through)
    • student, course
    • grade, enrolled_at
  • Course
    • id

Command

python
Enrollment.objects.create(student=alice, course=math_101, grade="A")   # through model
customer.orders.all()                                                    # reverse relation

Every relationship field gets a reverse manager automatically (<model>_set by default, renamed via related_name) — a through model is needed only when the LINK itself needs extra fields, and once set, its own manager (not .add()/.set()) creates the relationship.

3.Self-referencing relationships

A model can have a ForeignKey or ManyToManyField pointing at ITSELF — pass the string "self" instead of the model's name, since the class isn't fully defined yet at the point the field is declared. Common shapes: a tree (Category.parent points at another Category) and a symmetrical or non-symmetrical friendship/follow graph (User.objects.filter... via a self-referencing ManyToManyField).

Key features

  • "self" (the literal string) is used in place of a model name to reference the model currently being defined
  • A self-referencing ForeignKey with null=True models an optional parent — the root of a tree has parent=None
  • ManyToManyField("self", symmetrical=False) is required for a non-mutual relationship (follows); the default symmetrical=True assumes a mutual one (friends)
  • symmetrical only applies to self-referencing ManyToManyFields — it has no effect on a ManyToManyField between two different models
  • A self-referencing ForeignKey still needs on_delete, same as any other — CASCADE on a tree can delete an entire subtree at once

Self-referencing ForeignKey vs ManyToManyField

Self-referencing ForeignKey vs ManyToManyField
ShapeFieldSymmetrical?
Tree (category → parent category)ForeignKey("self", null=True, on_delete=...)n/a — not a M2M
Mutual link (friends)ManyToManyField("self")True (default) — adding A→B also implies B→A
One-directional link (follows)ManyToManyField("self", symmetrical=False)False — A→B does not imply B→A

Command

python
parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.CASCADE, related_name="children")

"self" (the string) references the model currently being defined; ManyToManyField("self") defaults to symmetrical=True (mutual) — a one-directional relationship (follows, not friends) needs symmetrical=False set explicitly.

Remember

  • ForeignKey (many-to-one), OneToOneField (+ unique=True), ManyToManyField (a separate join table) — on_delete has no default, it must be set explicitly.
  • Every relationship field gets a reverse manager automatically; related_name renames it, related_name="+" removes it.
  • A through model is required only once the relationship itself needs extra fields — its own manager creates the link, not .add().
  • "self" (the string) is required inside the class body, since the class name is not yet bound to a name at that point.

Pro tip

  • A self-referencing ManyToManyField defaults to symmetrical=True (mutual) — set symmetrical=False explicitly for a one-directional relationship like "follows."
  • related_query_name is independent of related_name — one controls filter() keywords, the other the reverse accessor.

Next up

  • on_delete Behavior
  • Model Methods and Properties
  • Django ORM Fundamentals

Sources

  • Many-to-one relationships — docs.djangoproject.com/en/5.2/topics/db/examples/many_to_one/
  • ManyToManyField.through — docs.djangoproject.com/en/5.2/ref/models/fields/
  • Every claim checked against Django 5.2 LTS docs · checked 2026-08-24

Spotted a mistake on this page? Tell us.