#35956: Add composite foreign keys
-------------------------------------+-------------------------------------
Reporter: Csirmaz Bendegúz | Owner: David
| Sanders
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Simon Charette):
* cc: Simon Charette (added)
Comment:
For anyone interested in enforcing composite foreign key constraints until
this ticket gets fixed you might be interested in
[
https://github.com/charettes/django-fk-constraint this package] that adds
the missing part of top of `ForeignObject` to enforce validation and
constraint creation and enforcement.
It can be used like the following
{{{#!python
class Tenant(models.Model):
name = models.CharField()
class TenantModel(models.Model):
tenant = models.ForeignKey(
Tenant,
models.CASCADE,
)
uuid = models.UUIDField(default=uuid.uuid4)
pk = models.CompositePrimaryKey("tenant", "uuid")
class Meta:
abstract = True
class Product(TenantModel):
name = models.CharField()
class ProductPrice(TenantModel):
product_uuid = models.UUIDField()
product = models.ForeignObject(
Product,
models.CASCADE,
from_fields=["tenant", "product_uuid"],
to_fields=["tenant", "uuid"],
)
price = models.DecimalField(max_digits=10, decimal_places=2)
class Meta:
constraints = [
ForeignKeyConstraint(
Product,
from_fields=["tenant", "product_uuid"],
to_fields=["tenant", "uuid"],
name="product_price_product_fk",
)
]
}}}
TL;DR use `ForeignObject` and define a `ForeignKeyConstraint` with a
similar signature.
This works as `ForeignKey` in its current form is basically ''sugar'' to
1. Define a concrete field
2. Define a `ForeignObject`
3. Define the equivalent of `ForeignKeyConstraint`
In other words, these two definitions are equivalent
{{{#!python
class Book(models.Model):
author_id = models.IntegerField()
author = models.ForeignObject(
Author, models.CASCADE, from_fields=["author_id"],
to_fields=["id"]
)
class Meta:
constraints = [
ForeignKeyConstraint(
Author,
from_fields=["author_id"],
to_fields=["id"],
name="book_author_fk",
)
]
}}}
and
{{{#!python
class Book(models.Model):
author = models.ForeignKey(
Author, models.CASCADE
)
}}}
Now, whether something similar to `ForeignKeyConstraint` should exist as
standalone documented form is debatable but getting `ForeignKey` ''sugar''
to support multiple fields without it is going to be challenging as it
will require figuring out what should be done with `attname` (AKA the
implicit `_id` field).
Until we have non-pk composite fields support I suspect we'll have to make
the field fully ''virtual'' and require that concrete local `from_fields`
are specified. That is making `.attname` be `None` and limit the ''sugar''
to injecting a `constraints` entry as when a field is virtual (it's
`db_column is None`) the schema editor completely ignores it.
--
Ticket URL: <
https://code.djangoproject.com/ticket/35956#comment:13>