mirror of
https://github.com/django/django.git
synced 2025-10-23 21:59:11 +00:00
Fixed #373 -- Added CompositePrimaryKey.
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>
This commit is contained in:
committed by
Sarah Boyce
parent
86661f2449
commit
978aae4334
@@ -181,6 +181,7 @@ Model fields
|
||||
* **fields.E011**: ``<database>`` does not support default database values with
|
||||
expressions (``db_default``).
|
||||
* **fields.E012**: ``<expression>`` cannot be used in ``db_default``.
|
||||
* **fields.E013**: ``CompositePrimaryKey`` must be named ``pk``.
|
||||
* **fields.E100**: ``AutoField``\s must set primary_key=True.
|
||||
* **fields.E110**: ``BooleanField``\s do not accept null values. *This check
|
||||
appeared before support for null values was added in Django 2.1.*
|
||||
@@ -417,6 +418,8 @@ Models
|
||||
* **models.W040**: ``<database>`` does not support indexes with non-key
|
||||
columns.
|
||||
* **models.E041**: ``constraints`` refers to the joined field ``<field name>``.
|
||||
* **models.E042**: ``<field name>`` cannot be included in the composite
|
||||
primary key.
|
||||
* **models.W042**: Auto-created primary key used when not defining a primary
|
||||
key type, by default ``django.db.models.AutoField``.
|
||||
* **models.W043**: ``<database>`` does not support indexes on expressions.
|
||||
|
||||
@@ -707,6 +707,23 @@ or :class:`~django.forms.NullBooleanSelect` if :attr:`null=True <Field.null>`.
|
||||
The default value of ``BooleanField`` is ``None`` when :attr:`Field.default`
|
||||
isn't defined.
|
||||
|
||||
``CompositePrimaryKey``
|
||||
-----------------------
|
||||
|
||||
.. versionadded:: 5.2
|
||||
|
||||
.. class:: CompositePrimaryKey(*field_names, **options)
|
||||
|
||||
A virtual field used for defining a composite primary key.
|
||||
|
||||
This field must be defined as the model's ``pk`` field. If present, Django will
|
||||
create the underlying model table with a composite primary key.
|
||||
|
||||
The ``*field_names`` argument is a list of positional field names that compose
|
||||
the primary key.
|
||||
|
||||
See :doc:`/topics/composite-primary-key` for more details.
|
||||
|
||||
``CharField``
|
||||
-------------
|
||||
|
||||
@@ -1615,6 +1632,8 @@ not an instance of ``UUID``.
|
||||
hyphens, because PostgreSQL and MariaDB 10.7+ store them in a hyphenated
|
||||
uuid datatype type.
|
||||
|
||||
.. _relationship-fields:
|
||||
|
||||
Relationship fields
|
||||
===================
|
||||
|
||||
|
||||
@@ -31,6 +31,25 @@ and only officially support the latest release of each series.
|
||||
What's new in Django 5.2
|
||||
========================
|
||||
|
||||
Composite Primary Keys
|
||||
----------------------
|
||||
|
||||
The new :class:`django.db.models.CompositePrimaryKey` allows tables to be
|
||||
created with a primary key consisting of multiple fields.
|
||||
|
||||
To use a composite primary key, when creating a model set the ``pk`` field to
|
||||
be a ``CompositePrimaryKey``::
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Release(models.Model):
|
||||
pk = models.CompositePrimaryKey("version", "name")
|
||||
version = models.IntegerField()
|
||||
name = models.CharField(max_length=20)
|
||||
|
||||
See :doc:`/topics/composite-primary-key` for more details.
|
||||
|
||||
Minor features
|
||||
--------------
|
||||
|
||||
|
||||
183
docs/topics/composite-primary-key.txt
Normal file
183
docs/topics/composite-primary-key.txt
Normal file
@@ -0,0 +1,183 @@
|
||||
======================
|
||||
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.
|
||||
@@ -19,6 +19,7 @@ Introductions to all the key parts of Django you'll need to know:
|
||||
auth/index
|
||||
cache
|
||||
conditional-view-processing
|
||||
composite-primary-key
|
||||
signing
|
||||
email
|
||||
i18n/index
|
||||
|
||||
Reference in New Issue
Block a user