mirror of
https://github.com/django/django.git
synced 2024-12-22 09:05:43 +00:00
978aae4334
Thanks Lily Foote and Simon Charette for reviews and mentoring this Google Summer of Code 2024 project. Co-authored-by: Simon Charette <charette.s@gmail.com> Co-authored-by: Lily Foote <code@lilyf.org>
184 lines
5.8 KiB
Plaintext
184 lines
5.8 KiB
Plaintext
======================
|
|
Composite primary keys
|
|
======================
|
|
|
|
.. versionadded:: 5.2
|
|
|
|
In Django, each model has a primary key. By default, this primary key consists
|
|
of a single field.
|
|
|
|
In most cases, a single primary key should suffice. In database design,
|
|
however, defining a primary key consisting of multiple fields is sometimes
|
|
necessary.
|
|
|
|
To use a composite primary key, when creating a model set the ``pk`` field to
|
|
be a :class:`.CompositePrimaryKey`::
|
|
|
|
class Product(models.Model):
|
|
name = models.CharField(max_length=100)
|
|
|
|
|
|
class Order(models.Model):
|
|
reference = models.CharField(max_length=20, primary_key=True)
|
|
|
|
|
|
class OrderLineItem(models.Model):
|
|
pk = models.CompositePrimaryKey("product_id", "order_id")
|
|
product = models.ForeignKey(Product, on_delete=models.CASCADE)
|
|
order = models.ForeignKey(Order, on_delete=models.CASCADE)
|
|
quantity = models.IntegerField()
|
|
|
|
This will instruct Django to create a composite primary key
|
|
(``PRIMARY KEY (product_id, order_id)``) when creating the table.
|
|
|
|
A composite primary key is represented by a ``tuple``:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> product = Product.objects.create(name="apple")
|
|
>>> order = Order.objects.create(reference="A755H")
|
|
>>> item = OrderLineItem.objects.create(product=product, order=order, quantity=1)
|
|
>>> item.pk
|
|
(1, "A755H")
|
|
|
|
You can assign a ``tuple`` to a composite primary key. This sets the associated
|
|
field values.
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> item = OrderLineItem(pk=(2, "B142C"))
|
|
>>> item.pk
|
|
(2, "B142C")
|
|
>>> item.product_id
|
|
2
|
|
>>> item.order_id
|
|
"B142C"
|
|
|
|
A composite primary key can also be filtered by a ``tuple``:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> OrderLineItem.objects.filter(pk=(1, "A755H")).count()
|
|
1
|
|
|
|
We're still working on composite primary key support for
|
|
:ref:`relational fields <cpk-and-relations>`, including
|
|
:class:`.GenericForeignKey` fields, and the Django admin. Models with composite
|
|
primary keys cannot be registered in the Django admin at this time. You can
|
|
expect to see this in future releases.
|
|
|
|
Migrating to a composite primary key
|
|
====================================
|
|
|
|
Django doesn't support migrating to, or from, a composite primary key after the
|
|
table is created. It also doesn't support adding or removing fields from the
|
|
composite primary key.
|
|
|
|
If you would like to migrate an existing table from a single primary key to a
|
|
composite primary key, follow your database backend's instructions to do so.
|
|
|
|
Once the composite primary key is in place, add the ``CompositePrimaryKey``
|
|
field to your model. This allows Django to recognize and handle the composite
|
|
primary key appropriately.
|
|
|
|
While migration operations (e.g. ``AddField``, ``AlterField``) on primary key
|
|
fields are not supported, ``makemigrations`` will still detect changes.
|
|
|
|
In order to avoid errors, it's recommended to apply such migrations with
|
|
``--fake``.
|
|
|
|
Alternatively, :class:`.SeparateDatabaseAndState` may be used to execute the
|
|
backend-specific migrations and Django-generated migrations in a single
|
|
operation.
|
|
|
|
.. _cpk-and-relations:
|
|
|
|
Composite primary keys and relations
|
|
====================================
|
|
|
|
:ref:`Relationship fields <relationship-fields>`, including
|
|
:ref:`generic relations <generic-relations>` do not support composite primary
|
|
keys.
|
|
|
|
For example, given the ``OrderLineItem`` model, the following is not
|
|
supported::
|
|
|
|
class Foo(models.Model):
|
|
item = models.ForeignKey(OrderLineItem, on_delete=models.CASCADE)
|
|
|
|
Because ``ForeignKey`` currently cannot reference models with composite primary
|
|
keys.
|
|
|
|
To work around this limitation, ``ForeignObject`` can be used as an
|
|
alternative::
|
|
|
|
class Foo(models.Model):
|
|
item_order_id = models.IntegerField()
|
|
item_product_id = models.CharField(max_length=20)
|
|
item = models.ForeignObject(
|
|
OrderLineItem,
|
|
on_delete=models.CASCADE,
|
|
from_fields=("item_order_id", "item_product_id"),
|
|
to_fields=("order_id", "product_id"),
|
|
)
|
|
|
|
``ForeignObject`` is much like ``ForeignKey``, except that it doesn't create
|
|
any columns (e.g. ``item_id``), foreign key constraints or indexes in the
|
|
database.
|
|
|
|
.. warning::
|
|
|
|
``ForeignObject`` is an internal API. This means it is not covered by our
|
|
:ref:`deprecation policy <internal-release-deprecation-policy>`.
|
|
|
|
Composite primary keys and database functions
|
|
=============================================
|
|
|
|
Many database functions only accept a single expression.
|
|
|
|
.. code-block:: sql
|
|
|
|
MAX("order_id") -- OK
|
|
MAX("product_id", "order_id") -- ERROR
|
|
|
|
As a consequence, they cannot be used with composite primary key references as
|
|
they are composed of multiple column expressions.
|
|
|
|
.. code-block:: python
|
|
|
|
Max("order_id") # OK
|
|
Max("pk") # ERROR
|
|
|
|
Composite primary keys in forms
|
|
===============================
|
|
|
|
As a composite primary key is a virtual field, a field which doesn't represent
|
|
a single database column, this field is excluded from ModelForms.
|
|
|
|
For example, take the following form::
|
|
|
|
class OrderLineItemForm(forms.ModelForm):
|
|
class Meta:
|
|
model = OrderLineItem
|
|
fields = "__all__"
|
|
|
|
This form does not have a form field ``pk`` for the composite primary key:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> OrderLineItemForm()
|
|
<OrderLineItemForm bound=False, valid=Unknown, fields=(product;order;quantity)>
|
|
|
|
Setting the primary composite field ``pk`` as a form field raises an unknown
|
|
field :exc:`.FieldError`.
|
|
|
|
.. admonition:: Primary key fields are read only
|
|
|
|
If you change the value of a primary key on an existing object and then
|
|
save it, a new object will be created alongside the old one (see
|
|
:attr:`.Field.primary_key`).
|
|
|
|
This is also true of composite primary keys. Hence, you may want to set
|
|
:attr:`.Field.editable` to ``False`` on all primary key fields to exclude
|
|
them from ModelForms.
|