123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- ======================
- 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.
|