CodeOath
← All posts
Python70 min total · 18 parts

Django Fundamentals: MVT Architecture, the ORM, and Middleware

Contents — Part 8 of 18: Relationships: ForeignKey, ManyToMany, OneToOne
Part 8 of 18 · ~1 min

Relationships: ForeignKey, ManyToMany, OneToOne

class Customer(models.Model):
    name = models.CharField(max_length=100)

class Order(models.Model):
    customer = models.ForeignKey(
        Customer,
        on_delete=models.CASCADE,        # what happens to Orders when their Customer is deleted
        related_name="orders",            # customer.orders.all() instead of customer.order_set.all()
    )

class Tag(models.Model):
    name = models.CharField(max_length=50)

class Product(models.Model):
    tags = models.ManyToManyField(Tag, related_name="products")

class Profile(models.Model):
    user = models.OneToOneField("auth.User", on_delete=models.CASCADE)

on_delete options, and why the choice actually matters

models.CASCADE      # delete this row too when the related row is deleted
models.PROTECT       # raise ProtectedError — refuse to delete the related row at all
models.SET_NULL      # set this FK to NULL (requires null=True on the field)
models.SET_DEFAULT    # set this FK to its default value

Picking CASCADE for every foreign key by default is a common early mistake — it's exactly right for genuinely dependent data (deleting an Order should delete its OrderLine items), but deleting a Customer and silently cascading away every historical Order they ever placed is rarely the intended behavior for financial/audit data; PROTECT or SET_NULL is often the safer default there.

related_name and reverse relationships

A ForeignKey automatically creates a reverse accessor on the other side of the relationship — by default named <lowercase model>_set, or the explicit related_name if given:

customer = Customer.objects.get(pk=1)
customer.orders.all()      # every Order pointing at this customer, via related_name="orders"
# without related_name, this would be customer.order_set.all()

Two ForeignKeys from the same model to the same target must have distinct related_names (or Django raises an error at startup) — this is the most common reason related_name shows up explicitly in real code rather than being left to its default.