mirror of
https://github.com/django-polymorphic/django-polymorphic.git
synced 2026-01-15 04:18:41 +03:00
Merge pull request #661 from bckohan/docs
refresh docs, add missing intersphinx references, update CONTRIBUTING
This commit is contained in:
commit
7c1a73c5cd
|
|
@ -111,40 +111,40 @@ just release x.x.x
|
|||
## Just Recipes
|
||||
|
||||
```bash
|
||||
build # build docs and package
|
||||
build-docs # build the docs
|
||||
build-docs-html # build html documentation
|
||||
build-docs-pdf # build pdf documentation
|
||||
check # run all static checks
|
||||
check-docs # lint the documentation
|
||||
check-docs-links # check the documentation links for broken links
|
||||
check-format # check if the code needs formatting
|
||||
check-lint # lint the code
|
||||
check-package # run package checks
|
||||
check-readme # check that the readme renders
|
||||
check-types # run static type checking
|
||||
clean # remove all non repository artifacts
|
||||
clean-docs # remove doc build artifacts
|
||||
clean-env # remove the virtual environment
|
||||
clean-git-ignored # remove all git ignored files
|
||||
coverage # generate the test coverage report
|
||||
docs # build and open the documentation
|
||||
docs-live # serve the documentation, with auto-reload
|
||||
fetch-refs LIB
|
||||
fix # fix formatting, linting issues and import sorting
|
||||
format # format the code and sort imports
|
||||
install *OPTS # update and install development dependencies
|
||||
install-docs # install documentation dependencies
|
||||
install-precommit # install git pre-commit hooks
|
||||
install_uv # install the uv package manager
|
||||
lint # sort the imports and fix linting issues
|
||||
manage *COMMAND # run the django admin
|
||||
open-docs # open the html documentation
|
||||
precommit # run the pre-commit checks
|
||||
release VERSION # issue a release for the given semver string (e.g. 2.1.0)
|
||||
run +ARGS # run the command in the virtual environment
|
||||
setup python="python" # setup the venv and pre-commit hooks
|
||||
sort-imports # sort the python imports
|
||||
test *TESTS # run tests
|
||||
validate_version VERSION # validate the given version string against the lib version
|
||||
build # build docs and package
|
||||
build-docs # build the docs
|
||||
build-docs-html # build html documentation
|
||||
build-docs-pdf # build pdf documentation
|
||||
check # run all static checks
|
||||
check-docs # lint the documentation
|
||||
check-docs-links # check the documentation links for broken links
|
||||
check-format # check if the code needs formatting
|
||||
check-lint # lint the code
|
||||
check-package # run package checks
|
||||
check-readme # check that the readme renders
|
||||
check-types # run static type checking
|
||||
clean # remove all non repository artifacts
|
||||
clean-docs # remove doc build artifacts
|
||||
clean-env # remove the virtual environment
|
||||
clean-git-ignored # remove all git ignored files
|
||||
coverage # generate the test coverage report
|
||||
docs # build and open the documentation
|
||||
docs-live # serve the documentation, with auto-reload
|
||||
fetch-refs LIB
|
||||
fix # fix formatting, linting issues and import sorting
|
||||
format # format the code and sort imports
|
||||
install *OPTS # update and install development dependencies
|
||||
install-docs # install documentation dependencies
|
||||
install-precommit # install git pre-commit hooks
|
||||
install_uv # install the uv package manager
|
||||
lint # sort the imports and fix linting issues
|
||||
manage *COMMAND # run the django admin
|
||||
open-docs # open the html documentation
|
||||
precommit # run the pre-commit checks
|
||||
release VERSION # issue a release for the given semver string (e.g. 2.1.0)
|
||||
run +ARGS # run the command in the virtual environment
|
||||
setup python="python" # setup the venv and pre-commit hooks
|
||||
sort-imports # sort the python imports
|
||||
test *TESTS # run tests
|
||||
validate_version VERSION # validate the given version string against the lib version
|
||||
```
|
||||
|
|
|
|||
32
docs/_static/style.css
vendored
Normal file
32
docs/_static/style.css
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
section#api-documentation div.highlight pre {
|
||||
color: #b30000;
|
||||
display: block; /* ensures it's treated as a block */
|
||||
margin-left: auto; /* auto margins center block elements */
|
||||
margin-right: auto;
|
||||
width: fit-content;
|
||||
}
|
||||
body[data-theme="light"] section#api-documentation div.highlight,
|
||||
body[data-theme="light"] section#api-documentation div.highlight pre {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
body[data-theme="dark"] section#api-documentation div.highlight,
|
||||
body[data-theme="dark"] section#api-documentation div.highlight pre {
|
||||
background-color: #202020;
|
||||
}
|
||||
|
||||
/* AUTO → system prefers DARK (acts like dark unless user forced light) */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body:not([data-theme="light"]) #api-documentation .highlight,
|
||||
body:not([data-theme="light"]) #api-documentation .highlight pre {
|
||||
background-color: #202020;
|
||||
}
|
||||
}
|
||||
|
||||
/* AUTO → system prefers LIGHT (acts like light unless user forced dark) */
|
||||
@media (prefers-color-scheme: light) {
|
||||
body:not([data-theme="dark"]) #api-documentation .highlight,
|
||||
body:not([data-theme="dark"]) #api-documentation .highlight pre {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
}
|
||||
106
docs/admin.rst
106
docs/admin.rst
|
|
@ -1,26 +1,27 @@
|
|||
Django admin integration
|
||||
========================
|
||||
Admin Integration
|
||||
=================
|
||||
|
||||
Of course, it's possible to register individual polymorphic models in the Django admin interface.
|
||||
However, to use these models in a single cohesive interface, some extra base classes are available.
|
||||
Of course, it's possible to register individual polymorphic models in the
|
||||
:doc:`Django admin interface <django:ref/contrib/admin/index>`. However, to use these models in a single
|
||||
cohesive interface, some extra base classes are available.
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
Both the parent model and child model need to have a ``ModelAdmin`` class.
|
||||
Both the parent model and child model need to have a :class:`~django.contrib.admin.ModelAdmin` class.
|
||||
|
||||
The shared base model should use the :class:`~polymorphic.admin.PolymorphicParentModelAdmin` as base
|
||||
class.
|
||||
|
||||
* :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.base_model` should be set
|
||||
* :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` or
|
||||
:meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` should return an iterable
|
||||
of Model classes.
|
||||
* :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.base_model` should be set
|
||||
* :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` or
|
||||
:meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` should return an iterable
|
||||
of Model classes.
|
||||
|
||||
The admin class for every child model should inherit from
|
||||
:class:`~polymorphic.admin.PolymorphicChildModelAdmin`
|
||||
|
||||
* :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_model` should be set.
|
||||
* :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_model` should be set.
|
||||
|
||||
Although the child models are registered too, they won't be shown in the admin index page.
|
||||
This only happens when :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` is set to
|
||||
|
|
@ -29,27 +30,31 @@ This only happens when :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show
|
|||
Fieldset configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The parent admin is only used for the list display of models,
|
||||
and for the edit/delete view of non-subclassed models.
|
||||
The parent admin is only used for the list display of models, and for the edit/delete view of
|
||||
non-subclassed models.
|
||||
|
||||
All other model types are redirected to the edit/delete/history view of the child model admin.
|
||||
Hence, the fieldset configuration should be placed on the child admin.
|
||||
|
||||
.. tip::
|
||||
|
||||
When the child admin is used as base class for various derived classes, avoid using
|
||||
the standard ``ModelAdmin`` attributes ``form`` and ``fieldsets``.
|
||||
Instead, use the ``base_form`` and ``base_fieldsets`` attributes.
|
||||
This allows the :class:`~polymorphic.admin.PolymorphicChildModelAdmin` class
|
||||
to detect any additional fields in case the child model is overwritten.
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
.. versionchanged:: 1.0
|
||||
|
||||
It's now needed to register the child model classes too.
|
||||
|
||||
In *django-polymorphic* 0.9 and below, the ``child_models`` was a tuple of a
|
||||
``(Model, ChildModelAdmin)``. The admin classes were registered in an internal class, and kept
|
||||
away from the main admin site. This caused various subtle problems with the ``ManyToManyField``
|
||||
and related field wrappers, which are fixed by registering the child admin classes too. Note
|
||||
that they are hidden from the main view, unless
|
||||
In :pypi:`django-polymorphic` 0.9 and below, the
|
||||
:meth:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` was a tuple of a
|
||||
(:class:`~django.db.models.Model`, :class:`~polymorphic.admin.PolymorphicChildModelAdmin`). The
|
||||
admin classes were registered in an internal class, and kept away from the main admin site. This
|
||||
caused various subtle problems with the :class:`~django.db.models.ManyToManyField` and related
|
||||
field wrappers, which are fixed by registering the child admin classes too. Note that they are
|
||||
hidden from the main view, unless
|
||||
:attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` is set.
|
||||
|
||||
.. _admin-example:
|
||||
|
|
@ -104,8 +109,8 @@ Filtering child types
|
|||
---------------------
|
||||
|
||||
Child model types can be filtered by adding a
|
||||
:class:`~polymorphic.admin.PolymorphicChildModelFilter` to the ``list_filter`` attribute. See the
|
||||
example above.
|
||||
:class:`~polymorphic.admin.PolymorphicChildModelFilter` to the
|
||||
:attr:`~django.contrib.admin.ModelAdmin.list_filter` attribute. See the example above.
|
||||
|
||||
|
||||
Inline models
|
||||
|
|
@ -118,15 +123,13 @@ Inline models are handled via a special :class:`~polymorphic.admin.StackedPolymo
|
|||
For models with a generic foreign key, there is a
|
||||
:class:`~polymorphic.admin.GenericStackedPolymorphicInline` class available.
|
||||
|
||||
When the inline is included to a normal :class:`~django.contrib.admin.ModelAdmin`,
|
||||
make sure the :class:`~polymorphic.admin.PolymorphicInlineSupportMixin` is included.
|
||||
This is not needed when the admin inherits from the
|
||||
:class:`~polymorphic.admin.PolymorphicParentModelAdmin` /
|
||||
When the inline is included to a normal :class:`~django.contrib.admin.ModelAdmin`, make sure the
|
||||
:class:`~polymorphic.admin.PolymorphicInlineSupportMixin` is included. This is not needed when the
|
||||
admin inherits from the :class:`~polymorphic.admin.PolymorphicParentModelAdmin` or
|
||||
:class:`~polymorphic.admin.PolymorphicChildModelAdmin` classes.
|
||||
|
||||
In the following example, the ``PaymentInline`` supports several types.
|
||||
These are defined as separate inline classes.
|
||||
The child classes can be nested for clarity, but this is not a requirement.
|
||||
In the following example, the ``PaymentInline`` supports several types. These are defined as
|
||||
separate inline classes. The child classes can be nested for clarity, but this is not a requirement.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -170,15 +173,13 @@ The child classes can be nested for clarity, but this is not a requirement.
|
|||
inlines = (PaymentInline,)
|
||||
|
||||
|
||||
|
||||
|
||||
Using polymorphic models in standard inlines
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To add a polymorphic child model as an Inline for another model, add a field to the inline's
|
||||
``readonly_fields`` list formed by the lowercased name of the polymorphic parent model with the
|
||||
string ``_ptr`` appended to it. Otherwise, trying to save that model in the admin will raise an
|
||||
AttributeError with the message "can't set attribute".
|
||||
:attr:`~django.contrib.admin.ModelAdmin.readonly_fields` list formed by the lowercased name of the
|
||||
polymorphic parent model with the string ``_ptr`` appended to it. Otherwise, trying to save that
|
||||
model in the admin will raise an :exc:`AttributeError` with the message "can't set attribute".
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -207,9 +208,9 @@ The polymorphic admin interface works in a simple way:
|
|||
* The edit screen displays the admin interface of the child model.
|
||||
* The list screen still displays all objects of the base class.
|
||||
|
||||
The polymorphic admin is implemented via a parent admin that redirects the *edit* and *delete* views
|
||||
to the ``ModelAdmin`` of the derived child model. The *list* page is still implemented by the parent
|
||||
model admin.
|
||||
The polymorphic admin is implemented via a parent admin that redirects the ``edit`` and ``delete``
|
||||
views to the :class:`~django.contrib.admin.ModelAdmin` of the derived child model. The ``list`` page
|
||||
is still implemented by the parent model admin.
|
||||
|
||||
The parent model
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
|
@ -217,27 +218,32 @@ The parent model
|
|||
The parent model needs to inherit :class:`~polymorphic.admin.PolymorphicParentModelAdmin`, and
|
||||
implement the following:
|
||||
|
||||
* :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.base_model` should be set
|
||||
* :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` or
|
||||
:meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` should return an iterable
|
||||
of Model classes.
|
||||
* :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.base_model` should be set
|
||||
* :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` or
|
||||
:meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` should return an iterable
|
||||
of Model classes.
|
||||
|
||||
The exact implementation can depend on the way your module is structured.
|
||||
For simple inheritance situations, ``child_models`` is the best solution.
|
||||
For large applications, ``get_child_models()`` can be used to query a plugin registration system.
|
||||
The exact implementation can depend on the way your module is structured. For simple inheritance
|
||||
situations, :meth:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` is the best
|
||||
solution. For large applications,
|
||||
:meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` can be used to query a
|
||||
plugin registration system.
|
||||
|
||||
By default, the non_polymorphic() method will be called on the queryset, so
|
||||
only the Parent model will be provided to the list template. This is to avoid
|
||||
the performance hit of retrieving child models.
|
||||
By default, the :meth:`~polymorphic.managers.PolymorphicQuerySet.non_polymorphic` method will be
|
||||
called on the queryset, so only the Parent model will be provided to the list template. This is to
|
||||
avoid the performance hit of retrieving child models.
|
||||
|
||||
This can be controlled by setting the ``polymorphic_list`` property on the
|
||||
parent admin. Setting it to True will provide child models to the list template.
|
||||
This can be controlled by setting the
|
||||
:attr:`~polymorphic.admin.PolymorphicParentModelAdmin.polymorphic_list` property on the parent
|
||||
admin. Setting it to True will provide child models to the list template.
|
||||
|
||||
If you use other applications such as django-reversion_ or django-mptt_, please check
|
||||
+:ref:`third-party`.
|
||||
:ref:`integrations`.
|
||||
|
||||
Note: If you are using non-integer primary keys in your model, you have to edit ``pk_regex``,
|
||||
for example ``pk_regex = '([\w-]+)'`` if you use UUIDs. Otherwise you cannot change model entries.
|
||||
Note: If you are using non-integer primary keys in your model, you have to edit
|
||||
:attr:`~polymorphic.admin.PolymorphicParentModelAdmin.pk_regex`, for example
|
||||
``pk_regex = '([\w-]+)'`` if you use :class:`~uuid.UUID` primary keys. Otherwise you cannot change
|
||||
model entries.
|
||||
|
||||
The child models
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
.. _advanced-features:
|
||||
|
||||
Advanced features
|
||||
Advanced Features
|
||||
=================
|
||||
|
||||
In the examples below, these models are being used::
|
||||
In the examples below, these models are being used:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.db import models
|
||||
from polymorphic.models import PolymorphicModel
|
||||
|
|
@ -17,35 +19,41 @@ In the examples below, these models are being used::
|
|||
class ModelC(ModelB):
|
||||
field3 = models.CharField(max_length=10)
|
||||
|
||||
Filtering for classes (equivalent to python's isinstance() ):
|
||||
-------------------------------------------------------------
|
||||
|
||||
>>> ModelA.objects.instance_of(ModelB)
|
||||
.
|
||||
[ <ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
Filtering for classes (equivalent to python's :func:`isinstance`):
|
||||
------------------------------------------------------------------
|
||||
|
||||
In general, including or excluding parts of the inheritance tree::
|
||||
.. code-block:: python
|
||||
|
||||
>>> ModelA.objects.instance_of(ModelB)
|
||||
[ <ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
In general, including or excluding parts of the inheritance tree:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ModelA.objects.instance_of(ModelB [, ModelC ...])
|
||||
ModelA.objects.not_instance_of(ModelB [, ModelC ...])
|
||||
|
||||
You can also use this feature in Q-objects (with the same result as above):
|
||||
|
||||
>>> ModelA.objects.filter( Q(instance_of=ModelB) )
|
||||
.. code-block:: python
|
||||
|
||||
>>> ModelA.objects.filter( Q(instance_of=ModelB) )
|
||||
|
||||
|
||||
Polymorphic filtering (for fields in inherited classes)
|
||||
-------------------------------------------------------
|
||||
|
||||
For example, cherrypicking objects from multiple derived classes
|
||||
anywhere in the inheritance tree, using Q objects (with the
|
||||
syntax: ``exact model name + three _ + field name``):
|
||||
For example, cherry-picking objects from multiple derived classes anywhere in the inheritance tree,
|
||||
using Q objects (with the syntax: ``exact model name + three _ + field name``):
|
||||
|
||||
>>> ModelA.objects.filter( Q(ModelB___field2 = 'B2') | Q(ModelC___field3 = 'C3') )
|
||||
.
|
||||
[ <ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
.. code-block:: python
|
||||
|
||||
>>> ModelA.objects.filter( Q(ModelB___field2 = 'B2') | Q(ModelC___field3 = 'C3') )
|
||||
[ <ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
|
||||
Combining Querysets
|
||||
|
|
@ -56,10 +64,12 @@ aggregation of different object types, very similar to python
|
|||
lists - as long as the objects are accessed through the manager of
|
||||
a common base class:
|
||||
|
||||
>>> Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY)
|
||||
.
|
||||
[ <ModelX: id 1, field_x (CharField)>,
|
||||
<ModelY: id 2, field_y (CharField)> ]
|
||||
.. code-block:: python
|
||||
|
||||
>>> Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY)
|
||||
|
||||
[ <ModelX: id 1, field_x (CharField)>,
|
||||
<ModelY: id 2, field_y (CharField)> ]
|
||||
|
||||
|
||||
ManyToManyField, ForeignKey, OneToOneField
|
||||
|
|
@ -70,17 +80,23 @@ expected: like polymorphic querysets they now always return the
|
|||
referred objects with the same type/class these were created and
|
||||
saved as.
|
||||
|
||||
E.g., if in your model you define::
|
||||
E.g., if in your model you define:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
field1 = OneToOneField(ModelA)
|
||||
|
||||
then field1 may now also refer to objects of type ``ModelB`` or ``ModelC``.
|
||||
|
||||
A ManyToManyField example::
|
||||
A :class:`~django.db.models.ManyToManyField` example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# The model holding the relation may be any kind of model, polymorphic or not
|
||||
class RelatingModel(models.Model):
|
||||
many2many = models.ManyToManyField('ModelA') # ManyToMany relation to a polymorphic model
|
||||
|
||||
# ManyToMany relation to a polymorphic model
|
||||
many2many = models.ManyToManyField('ModelA')
|
||||
|
||||
>>> o=RelatingModel.objects.create()
|
||||
>>> o.many2many.add(ModelA.objects.get(id=1))
|
||||
|
|
@ -96,9 +112,11 @@ Copying Polymorphic objects
|
|||
---------------------------
|
||||
|
||||
When creating a copy of a polymorphic object, both the
|
||||
``.id`` and the ``.pk`` of the object need to be set
|
||||
to ``None`` before saving so that both the base table
|
||||
and the derived table will be updated to the new object::
|
||||
:attr:`~django.db.models.Model.id` and the :attr:`~django.db.models.Model.pk` of the object need to
|
||||
be set to ``None`` before saving so that both the base table and the derived table will be updated
|
||||
to the new object:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> o = ModelB.objects.first()
|
||||
>>> o.field1 = 'new val' # leave field2 unchanged
|
||||
|
|
@ -112,7 +130,9 @@ Using Third Party Models (without modifying them)
|
|||
|
||||
Third party models can be used as polymorphic models without
|
||||
restrictions by subclassing them. E.g. using a third party
|
||||
model as the root of a polymorphic inheritance tree::
|
||||
model as the root of a polymorphic inheritance tree:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from thirdparty import ThirdPartyModel
|
||||
|
||||
|
|
@ -120,7 +140,9 @@ model as the root of a polymorphic inheritance tree::
|
|||
pass # or add fields
|
||||
|
||||
Or instead integrating the third party model anywhere into an
|
||||
existing polymorphic inheritance tree::
|
||||
existing polymorphic inheritance tree:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyBaseModel(SomePolymorphicModel):
|
||||
my_field = models.CharField(max_length=10)
|
||||
|
|
@ -132,90 +154,96 @@ existing polymorphic inheritance tree::
|
|||
Non-Polymorphic Queries
|
||||
-----------------------
|
||||
|
||||
If you insert ``.non_polymorphic()`` anywhere into the query chain, then
|
||||
django_polymorphic will simply leave out the final step of retrieving the
|
||||
real objects, and the manager/queryset will return objects of the type of
|
||||
the base class you used for the query, like vanilla Django would
|
||||
(``ModelA`` in this example).
|
||||
If you insert :meth:`~polymorphic.managers.PolymorphicQuerySet.non_polymorphic` anywhere into the
|
||||
query chain, then :pypi:`django-polymorphic` will simply leave out the final step of retrieving the
|
||||
real objects, and the manager/queryset will return objects of the type of the base class you used
|
||||
for the query, like vanilla Django would (``ModelA`` in this example).
|
||||
|
||||
>>> qs=ModelA.objects.non_polymorphic().all()
|
||||
>>> qs
|
||||
[ <ModelA: id 1, field1 (CharField)>,
|
||||
<ModelA: id 2, field1 (CharField)>,
|
||||
<ModelA: id 3, field1 (CharField)> ]
|
||||
.. code-block:: python
|
||||
|
||||
>>> qs=ModelA.objects.non_polymorphic().all()
|
||||
>>> qs
|
||||
[ <ModelA: id 1, field1 (CharField)>,
|
||||
<ModelA: id 2, field1 (CharField)>,
|
||||
<ModelA: id 3, field1 (CharField)> ]
|
||||
|
||||
There are no other changes in the behaviour of the queryset. For example,
|
||||
enhancements for ``filter()`` or ``instance_of()`` etc. still work as expected.
|
||||
If you do the final step yourself, you get the usual polymorphic result:
|
||||
|
||||
>>> ModelA.objects.get_real_instances(qs)
|
||||
[ <ModelA: id 1, field1 (CharField)>,
|
||||
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
.. code-block:: python
|
||||
|
||||
>>> ModelA.objects.get_real_instances(qs)
|
||||
[ <ModelA: id 1, field1 (CharField)>,
|
||||
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
|
||||
About Queryset Methods
|
||||
----------------------
|
||||
|
||||
* ``annotate()`` and ``aggregate()`` work just as usual, with the
|
||||
addition that the ``ModelX___field`` syntax can be used for the
|
||||
keyword arguments (but not for the non-keyword arguments).
|
||||
* :meth:`~django.db.models.query.QuerySet.annotate` and
|
||||
:meth:`~django.db.models.query.QuerySet.aggregate` work just as usual, with the addition that
|
||||
the ``ModelX___field`` syntax can be used for the keyword arguments (but not for the non-keyword
|
||||
arguments).
|
||||
|
||||
* ``order_by()`` similarly supports the ``ModelX___field`` syntax
|
||||
for specifying ordering through a field in a submodel.
|
||||
* :meth:`~django.db.models.query.QuerySet.order_by` similarly supports the ``ModelX___field``
|
||||
syntax for specifying ordering through a field in a submodel.
|
||||
|
||||
* ``distinct()`` works as expected. It only regards the fields of
|
||||
the base class, but this should never make a difference.
|
||||
* :meth:`~django.db.models.query.QuerySet.distinct` works as expected. It only regards the fields
|
||||
of the base class, but this should never make a difference.
|
||||
|
||||
* ``select_related()`` works just as usual, but it can not (yet) be used
|
||||
to select relations in inherited models
|
||||
(like ``ModelA.objects.select_related('ModelC___fieldxy')`` )
|
||||
* :meth:`~django.db.models.query.QuerySet.select_related` works just as usual, but it can not
|
||||
(yet) be used to select relations in inherited models (like
|
||||
``ModelA.objects.select_related('ModelC___fieldxy')`` )
|
||||
|
||||
* ``extra()`` works as expected (it returns polymorphic results) but
|
||||
currently has one restriction: The resulting objects are required to have
|
||||
a unique primary key within the result set - otherwise an error is thrown
|
||||
(this case could be made to work, however it may be mostly unneeded)..
|
||||
The keyword-argument "polymorphic" is no longer supported.
|
||||
You can get back the old non-polymorphic behaviour
|
||||
by using ``ModelA.objects.non_polymorphic().extra(...)``.
|
||||
* :meth:`~django.db.models.query.QuerySet.extra` works as expected (it returns polymorphic
|
||||
results) but currently has one restriction: The resulting objects are required to have a unique
|
||||
primary key within the result set - otherwise an error is thrown (this case could be made to
|
||||
work, however it may be mostly unneeded).. The keyword-argument "polymorphic" is no longer
|
||||
supported. You can get back the old non-polymorphic behaviour by using
|
||||
``ModelA.objects.non_polymorphic().extra(...)``.
|
||||
|
||||
* ``get_real_instances()`` allows you to turn a
|
||||
* :meth:`~polymorphic.managers.PolymorphicQuerySet.get_real_instances` allows you to turn a
|
||||
queryset or list of base model objects efficiently into the real objects.
|
||||
For example, you could do ``base_objects_queryset=ModelA.extra(...).non_polymorphic()``
|
||||
and then call ``real_objects=base_objects_queryset.get_real_instances()``. Or alternatively
|
||||
``real_objects=ModelA.objects.get_real_instances(base_objects_queryset_or_object_list)``
|
||||
|
||||
* ``values()`` & ``values_list()`` currently do not return polymorphic
|
||||
results. This may change in the future however. If you want to use these
|
||||
methods now, it's best if you use ``Model.base_objects.values...`` as
|
||||
this is guaranteed to not change.
|
||||
* :meth:`~django.db.models.query.QuerySet.values` &
|
||||
:meth:`~django.db.models.query.QuerySet.values_list` currently do not return polymorphic
|
||||
results. This may change in the future however. If you want to use these methods now, it's best
|
||||
if you use ``Model.base_objects.values...`` as this is guaranteed to not change.
|
||||
|
||||
* ``defer()`` and ``only()`` work as expected. On Django 1.5+ they support
|
||||
the ``ModelX___field`` syntax, but on Django 1.4 it is only possible to
|
||||
pass fields on the base model into these methods.
|
||||
* :meth:`~django.db.models.query.QuerySet.defer` and :meth:`~django.db.models.query.QuerySet.only`
|
||||
work as expected. On Django 1.5+ they support the ``ModelX___field`` syntax, but on Django 1.4
|
||||
it is only possible to pass fields on the base model into these methods.
|
||||
|
||||
|
||||
Using enhanced Q-objects in any Places
|
||||
--------------------------------------
|
||||
|
||||
The queryset enhancements (e.g. ``instance_of``) only work as arguments
|
||||
to the member functions of a polymorphic queryset. Occasionally it may
|
||||
be useful to be able to use Q objects with these enhancements in other places.
|
||||
As Django doesn't understand these enhanced Q objects, you need to
|
||||
transform them manually into normal Q objects before you can feed them
|
||||
to a Django queryset or function::
|
||||
The queryset enhancements (e.g. :meth:`~polymorphic.managers.PolymorphicQuerySet.instance_of`)
|
||||
only work as arguments to the member functions of a polymorphic queryset. Occasionally it may
|
||||
be useful to be able to use Q objects with these enhancements in other places. As Django doesn't
|
||||
understand these enhanced Q objects, you need to transform them manually into normal Q objects
|
||||
before you can feed them to a Django queryset or function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
normal_q_object = ModelA.translate_polymorphic_Q_object( Q(instance_of=Model2B) )
|
||||
|
||||
This function cannot be used at model creation time however (in models.py),
|
||||
as it may need to access the ContentTypes database table.
|
||||
This function cannot be used at model creation time however (in models.py), as it may need to access
|
||||
the ContentTypes database table.
|
||||
|
||||
|
||||
Nicely Displaying Polymorphic Querysets
|
||||
---------------------------------------
|
||||
|
||||
In order to get the output as seen in all examples here, you need to use the
|
||||
:class:`~polymorphic.showfields.ShowFieldType` class mixin::
|
||||
:class:`~polymorphic.showfields.ShowFieldType` class mixin:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from polymorphic.models import PolymorphicModel
|
||||
from polymorphic.showfields import ShowFieldType
|
||||
|
|
@ -223,12 +251,14 @@ In order to get the output as seen in all examples here, you need to use the
|
|||
class ModelA(ShowFieldType, PolymorphicModel):
|
||||
field1 = models.CharField(max_length=10)
|
||||
|
||||
You may also use :class:`~polymorphic.showfields.ShowFieldContent`
|
||||
or :class:`~polymorphic.showfields.ShowFieldTypeAndContent` to display
|
||||
additional information when printing querysets (or converting them to text).
|
||||
You may also use :class:`~polymorphic.showfields.ShowFieldContent` or
|
||||
:class:`~polymorphic.showfields.ShowFieldTypeAndContent` to display additional information when
|
||||
printing querysets (or converting them to text).
|
||||
|
||||
When showing field contents, they will be truncated to 20 characters. You can
|
||||
modify this behaviour by setting a class variable in your model like this::
|
||||
When showing field contents, they will be truncated to 20 characters. You can modify this behavior
|
||||
by setting a class variable in your model like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ModelA(ShowFieldType, PolymorphicModel):
|
||||
polymorphic_showfield_max_field_width = 20
|
||||
|
|
@ -238,36 +268,33 @@ Similarly, pre-V1.0 output formatting can be re-estated by using
|
|||
``polymorphic_showfield_old_format = True``.
|
||||
|
||||
|
||||
|
||||
.. _restrictions:
|
||||
|
||||
Restrictions & Caveats
|
||||
----------------------
|
||||
|
||||
* Database Performance regarding concrete Model inheritance in general.
|
||||
Please see the :ref:`performance`.
|
||||
* Database Performance regarding concrete Model inheritance in general. Please see
|
||||
:ref:`performance`.
|
||||
|
||||
* Queryset methods ``values()``, ``values_list()``, and ``select_related()``
|
||||
are not yet fully supported (see above). ``extra()`` has one restriction:
|
||||
the resulting objects are required to have a unique primary key within
|
||||
the result set.
|
||||
* Queryset methods :meth:`~django.db.models.query.QuerySet.values`,
|
||||
:meth:`~django.db.models.query.QuerySet.values_list`, and
|
||||
:meth:`~django.db.models.query.QuerySet.select_related` are not yet fully supported (see above).
|
||||
:meth:`~django.db.models.query.QuerySet.extra` has one restriction: the resulting objects are
|
||||
required to have a unique primary key within the result set.
|
||||
|
||||
* Diamond shaped inheritance: There seems to be a general problem
|
||||
with diamond shaped multiple model inheritance with Django models
|
||||
(tested with V1.1 - V1.3).
|
||||
An example is here: http://code.djangoproject.com/ticket/10808.
|
||||
This problem is aggravated when trying to enhance models.Model
|
||||
by subclassing it instead of modifying Django core (as we do here
|
||||
with PolymorphicModel).
|
||||
* Diamond shaped inheritance: There seems to be a general problem with diamond shaped multiple
|
||||
model inheritance with Django models (tested with V1.1 - V1.3). An example
|
||||
`is here <http://code.djangoproject.com/ticket/10808>`_. This problem is aggravated when trying
|
||||
to enhance :class:`~django.db.models.Model` by subclassing it instead of modifying Django core
|
||||
(as we do here with :class:`~polymorphic.models.PolymorphicModel`).
|
||||
|
||||
* The enhanced filter-definitions/Q-objects only work as arguments
|
||||
for the methods of the polymorphic querysets. Please see above
|
||||
for ``translate_polymorphic_Q_object``.
|
||||
* The enhanced filter-definitions/Q-objects only work as arguments for the methods of the
|
||||
polymorphic querysets. Please see above for ``translate_polymorphic_Q_object``.
|
||||
|
||||
* When using the ``dumpdata`` management command on polymorphic tables
|
||||
(or any table that has a reference to
|
||||
:class:`~django.contrib.contenttypes.models.ContentType`),
|
||||
include the ``--natural-foreign`` and ``--natural-primary`` flags in the arguments.
|
||||
* When using the :django-admin:`dumpdata` management command on polymorphic tables
|
||||
(or any table that has a reference to :class:`~django.contrib.contenttypes.models.ContentType`),
|
||||
include the :option:`--natural-primary <dumpdata.--natural-primary>` and
|
||||
:option:`--natural-foreign <dumpdata.--natural-foreign>` flags in the arguments.
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
API Documentation
|
||||
=================
|
||||
|
||||
.. _base:
|
||||
|
||||
.. automodule:: polymorphic
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:inherited-members:
|
||||
|
||||
.. toctree::
|
||||
|
||||
polymorphic.admin
|
||||
|
|
@ -9,5 +16,6 @@ API Documentation
|
|||
polymorphic.formsets
|
||||
polymorphic.managers
|
||||
polymorphic.models
|
||||
polymorphic.showfields
|
||||
polymorphic.templatetags
|
||||
polymorphic.utils
|
||||
|
|
|
|||
7
docs/api/polymorphic.showfields.rst
Normal file
7
docs/api/polymorphic.showfields.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
polymorphic.showfields
|
||||
======================
|
||||
|
||||
.. automodule:: polymorphic.showfields
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:inherited-members:
|
||||
|
|
@ -17,10 +17,10 @@ v4.0.0 (2025-05-20)
|
|||
|
||||
There were many updates modernizing the package and incorporating Jazzband standards:
|
||||
|
||||
* Updates to documentation
|
||||
* Formatting and linting with ruff
|
||||
* Moving to GHA from Travis CI
|
||||
* Switch to pytest
|
||||
* Updates to documentation
|
||||
* Formatting and linting with ruff
|
||||
* Moving to GHA from Travis CI
|
||||
* Switch to pytest
|
||||
|
||||
Changes that touched the core package code were:
|
||||
|
||||
|
|
|
|||
71
docs/conf.py
71
docs/conf.py
|
|
@ -11,7 +11,9 @@
|
|||
# serve to show the default.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import django
|
||||
|
||||
|
|
@ -20,10 +22,12 @@ import django
|
|||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath("_ext"))
|
||||
sys.path.insert(0, os.path.abspath(".."))
|
||||
os.environ["DJANGO_SETTINGS_MODULE"] = "djangodummy.settings"
|
||||
os.environ["DJANGO_SETTINGS_MODULE"] = "polymorphic.tests.settings"
|
||||
|
||||
django.setup()
|
||||
|
||||
import polymorphic # noqa: E402
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
|
|
@ -35,6 +39,7 @@ extensions = [
|
|||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.graphviz",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.todo",
|
||||
"sphinxcontrib_django",
|
||||
]
|
||||
|
||||
|
|
@ -50,18 +55,13 @@ source_suffix = ".rst"
|
|||
# The master toctree document.
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = "django-polymorphic"
|
||||
copyright = "2013, Bert Constantin, Chris Glass, Diederik van der Boor, Brian Kohan"
|
||||
# -- Project information -----------------------------------------------------
|
||||
project = polymorphic.__title__
|
||||
copyright = polymorphic.__copyright__
|
||||
author = polymorphic.__author__
|
||||
release = polymorphic.__version__
|
||||
version = ".".join(polymorphic.__version__.split(".")[:2])
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = "3.1"
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = "3.1"
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
|
@ -92,7 +92,7 @@ exclude_patterns = ["_build"]
|
|||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = "sphinx"
|
||||
# pygments_style = "sphinx"
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
# modindex_common_prefix = []
|
||||
|
|
@ -102,7 +102,12 @@ pygments_style = "sphinx"
|
|||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
html_theme = "furo"
|
||||
html_theme_options = {
|
||||
"source_repository": "https://github.com/jazzband/django-polymorphic/",
|
||||
"source_branch": "master",
|
||||
"source_directory": "docs",
|
||||
}
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
|
|
@ -132,6 +137,9 @@ html_theme = "sphinx_rtd_theme"
|
|||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ["_static"]
|
||||
html_css_files = [
|
||||
"style.css",
|
||||
]
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
|
|
@ -180,6 +188,7 @@ htmlhelp_basename = "django-polymorphicdoc"
|
|||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_engine = "xelatex"
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
|
@ -273,7 +282,39 @@ intersphinx_mapping = {
|
|||
"https://docs.djangoproject.com/en/stable/_objects/",
|
||||
),
|
||||
"python": ("https://docs.python.org/3", None),
|
||||
"django-guardian": ("https://django-guardian.readthedocs.io/en/stable", None),
|
||||
"django-reversion": ("https://django-reversion.readthedocs.io/en/stable", None),
|
||||
"django-extra-views": ("https://django-extra-views.readthedocs.io/en/stable", None),
|
||||
}
|
||||
|
||||
# autodoc settings
|
||||
autodoc_member_order = "groupwise"
|
||||
autodoc_default_options = {
|
||||
"show-inheritance": True,
|
||||
# Add other autodoc options here if desired, e.g.:
|
||||
# 'members': True,
|
||||
# 'inherited-members': True,
|
||||
}
|
||||
# In your Sphinx conf.py
|
||||
autodoc_typehints = "description"
|
||||
autodoc_typehints_format = "short"
|
||||
autodoc_class_signature = "separated"
|
||||
autodoc_member_order = "groupwise" # "bysource"
|
||||
|
||||
|
||||
def pypi_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
from docutils import nodes
|
||||
|
||||
url = f"https://pypi.org/project/{text}/"
|
||||
node = nodes.reference(rawtext, text, refuri=url, **options)
|
||||
return [node], []
|
||||
|
||||
|
||||
def setup(app):
|
||||
from docutils.parsers.rst import roles
|
||||
|
||||
# https://sphinxcontrib-typer.readthedocs.io/en/latest/howto.html#build-to-multiple-formats
|
||||
if Path(app.doctreedir).exists():
|
||||
shutil.rmtree(app.doctreedir)
|
||||
app.add_crossref_type(directivename="django-admin", rolename="django-admin")
|
||||
roles.register_local_role("pypi", pypi_role)
|
||||
return app
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
Contributing
|
||||
============
|
||||
|
||||
You can contribute to *django-polymorphic* to forking the code on GitHub:
|
||||
|
||||
https://github.com/jazzband/django-polymorphic
|
||||
|
||||
|
||||
Running tests
|
||||
-------------
|
||||
|
||||
We require features to be backed by a unit test.
|
||||
This way, we can test *django-polymorphic* against new Django versions.
|
||||
To run the included test suite, execute::
|
||||
|
||||
pytest
|
||||
|
||||
To test support for multiple Python and Django versions, run tox from the repository root::
|
||||
|
||||
pip install tox
|
||||
tox
|
||||
|
||||
The Python versions need to be installed at your system.
|
||||
On Linux, download the versions at http://www.python.org/download/releases/.
|
||||
On MacOS X, use Homebrew_ to install other Python versions.
|
||||
|
||||
|
||||
Example project
|
||||
----------------
|
||||
|
||||
The repository contains a complete Django project that may be used for tests or experiments,
|
||||
without any installation needed.
|
||||
|
||||
The management command ``pcmd.py`` in the app ``pexp`` can be used for quick tests
|
||||
or experiments - modify this file (pexp/management/commands/pcmd.py) to your liking.
|
||||
|
||||
|
||||
Supported Django versions
|
||||
-------------------------
|
||||
|
||||
The current release should be usable with the supported releases of Django;
|
||||
the current stable release and the previous release. Supporting older Django
|
||||
versions is a nice-to-have feature, but not mandatory.
|
||||
|
||||
In case you need to use *django-polymorphic* with older Django versions,
|
||||
consider installing a previous version.
|
||||
|
||||
.. _Homebrew: http://mxcl.github.io/homebrew/
|
||||
|
|
@ -5,7 +5,7 @@ Formsets
|
|||
|
||||
Polymorphic models can be used in formsets.
|
||||
|
||||
The implementation is almost identical to the regular Django formsets.
|
||||
The implementation is almost identical to the regular Django :doc:`django:topics/forms/formsets`.
|
||||
As extra parameter, the factory needs to know how to display the child models.
|
||||
Provide a list of :class:`~polymorphic.formsets.PolymorphicFormSetChild` objects for this.
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ The formset can be used just like all other formsets:
|
|||
else:
|
||||
formset = ModelAFormSet(queryset=ModelA.objects.all())
|
||||
|
||||
Like standard Django formsets, there are 3 factory methods available:
|
||||
Like standard Django :doc:`django:topics/forms/formsets`, there are 3 factory methods available:
|
||||
|
||||
* :func:`~polymorphic.formsets.polymorphic_modelformset_factory` - create a regular model formset.
|
||||
* :func:`~polymorphic.formsets.polymorphic_inlineformset_factory` - create a inline model formset.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,56 @@
|
|||
Welcome to django-polymorphic's documentation!
|
||||
==============================================
|
||||
django-polymorphic
|
||||
==================
|
||||
|
||||
Django-polymorphic builds on top of the standard Django model inheritance.
|
||||
.. image:: https://img.shields.io/badge/License-BSD-blue.svg
|
||||
:target: https://opensource.org/license/bsd-3-clause
|
||||
:alt: License: BSD
|
||||
|
||||
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
|
||||
:target: https://github.com/astral-sh/ruff
|
||||
:alt: Ruff
|
||||
|
||||
.. image:: https://badge.fury.io/py/django-polymorphic.svg
|
||||
:target: https://pypi.python.org/pypi/django-polymorphic/
|
||||
:alt: PyPI version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/django-polymorphic.svg
|
||||
:target: https://pypi.python.org/pypi/django-polymorphic/
|
||||
:alt: PyPI pyversions
|
||||
|
||||
.. image:: https://img.shields.io/pypi/djversions/django-polymorphic.svg
|
||||
:target: https://pypi.org/project/django-polymorphic/
|
||||
:alt: PyPI Django versions
|
||||
|
||||
.. image:: https://img.shields.io/pypi/status/django-polymorphic.svg
|
||||
:target: https://pypi.python.org/pypi/django-polymorphic
|
||||
:alt: PyPI status
|
||||
|
||||
.. image:: https://readthedocs.org/projects/django-polymorphic/badge/?version=latest
|
||||
:target: http://django-polymorphic.readthedocs.io/?badge=latest/
|
||||
:alt: Documentation Status
|
||||
|
||||
.. image:: https://img.shields.io/codecov/c/github/jazzband/django-polymorphic/master.svg
|
||||
:target: https://codecov.io/github/jazzband/django-polymorphic?branch=master
|
||||
:alt: Code Coverage
|
||||
|
||||
.. image:: https://github.com/jazzband/django-polymorphic/actions/workflows/test.yml/badge.svg?branch=master
|
||||
:target: https://github.com/jazzband/django-polymorphic/actions/workflows/test.yml?query=branch:master
|
||||
:alt: Test Status
|
||||
|
||||
.. image:: https://github.com/jazzband/django-polymorphic/actions/workflows/lint.yml/badge.svg?branch=master
|
||||
:target: https://github.com/jazzband/django-polymorphic/actions/workflows/lint.yml?query=branch:master
|
||||
:alt: Lint Status
|
||||
|
||||
.. image:: https://img.shields.io/badge/Published%20on-Django%20Packages-0c3c26
|
||||
:target: https://djangopackages.org/packages/p/django-polymorphic/
|
||||
:alt: Published on Django Packages
|
||||
|
||||
.. image:: https://jazzband.co/static/img/badge.svg
|
||||
:target: https://jazzband.co/
|
||||
:alt: Jazzband
|
||||
|
||||
|
||||
:pypi:`django-polymorphic` builds on top of the standard Django model inheritance.
|
||||
It makes using inherited models easier. When a query is made at the base model,
|
||||
the inherited model classes are returned.
|
||||
|
||||
|
|
@ -37,12 +86,13 @@ Features
|
|||
* Full admin integration.
|
||||
* ORM integration:
|
||||
|
||||
* Support for ForeignKey, ManyToManyField, OneToOneField descriptors.
|
||||
* Support for proxy models.
|
||||
* Filtering/ordering of inherited models (``ArtProject___artist``).
|
||||
* Filtering model types: ``instance_of(...)`` and ``not_instance_of(...)``
|
||||
* Combining querysets of different models (``qs3 = qs1 | qs2``)
|
||||
* Support for custom user-defined managers.
|
||||
- Support for ForeignKey, ManyToManyField, OneToOneField descriptors.
|
||||
- Support for proxy models.
|
||||
- Filtering/ordering of inherited models (``ArtProject___artist``).
|
||||
- Filtering model types: :meth:`~polymorphic.managers.PolymorphicQuerySet.instance_of` and
|
||||
:meth:`~polymorphic.managers.PolymorphicQuerySet.not_instance_of`
|
||||
- Combining querysets of different models (``qs3 = qs1 | qs2``)
|
||||
- Support for custom user-defined managers.
|
||||
|
||||
* Formset support.
|
||||
* Uses the minimum amount of queries needed to fetch the inherited models.
|
||||
|
|
@ -58,7 +108,7 @@ Getting started
|
|||
quickstart
|
||||
admin
|
||||
performance
|
||||
third-party
|
||||
integrations
|
||||
|
||||
Advanced topics
|
||||
---------------
|
||||
|
|
@ -71,7 +121,6 @@ Advanced topics
|
|||
managers
|
||||
advanced
|
||||
changelog
|
||||
contributing
|
||||
api/index
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,35 @@
|
|||
Third-party applications support
|
||||
================================
|
||||
.. _integrations:
|
||||
|
||||
Integrations
|
||||
============
|
||||
|
||||
django-guardian support
|
||||
-----------------------
|
||||
.. _django-django-guardian-support:
|
||||
|
||||
django-guardian
|
||||
---------------
|
||||
|
||||
.. versionadded:: 1.0.2
|
||||
|
||||
You can configure django-guardian_ to use the base model for object level permissions.
|
||||
You can configure :pypi:`django-guardian` to use the base model for object level permissions.
|
||||
Add this option to your settings:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
GUARDIAN_GET_CONTENT_TYPE = 'polymorphic.contrib.guardian.get_polymorphic_base_content_type'
|
||||
GUARDIAN_GET_CONTENT_TYPE = \
|
||||
'polymorphic.contrib.guardian.get_polymorphic_base_content_type'
|
||||
|
||||
This option requires django-guardian_ >= 1.4.6. Details about how this option works are available in
|
||||
the `django-guardian documentation
|
||||
<https://django-guardian.readthedocs.io/en/latest/configuration>`_.
|
||||
This option requires :pypi:`django-guardian` >= 1.4.6. Details about how this option works are
|
||||
available in the `django-guardian documentation
|
||||
<https://django-guardian.readthedocs.io/en/stable/configuration>`_.
|
||||
|
||||
|
||||
django-rest-framework support
|
||||
-----------------------------
|
||||
.. _django-rest-framework-support:
|
||||
|
||||
The django-rest-polymorphic_ package provides polymorphic serializers that help you integrate your
|
||||
polymorphic models with `django-rest-framework`.
|
||||
djangorestframework
|
||||
-------------------
|
||||
|
||||
The :pypi:`django-rest-polymorphic` package provides polymorphic serializers that help you integrate
|
||||
your polymorphic models with :pypi:`djangorestframework`.
|
||||
|
||||
|
||||
Example
|
||||
|
|
@ -77,42 +83,46 @@ Create viewset with serializer_class equals to your polymorphic serializer:
|
|||
serializer_class = ProjectPolymorphicSerializer
|
||||
|
||||
|
||||
.. _django-extra-views-support:
|
||||
|
||||
django-extra-views
|
||||
------------------
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
The :mod:`polymorphic.contrib.extra_views` package provides classes to display polymorphic formsets
|
||||
using the classes from django-extra-views_. See the documentation of:
|
||||
using the classes from :pypi:`django-extra-views`. See the documentation of:
|
||||
|
||||
* :class:`~polymorphic.contrib.extra_views.PolymorphicFormSetView`
|
||||
* :class:`~polymorphic.contrib.extra_views.PolymorphicInlineFormSetView`
|
||||
* :class:`~polymorphic.contrib.extra_views.PolymorphicInlineFormSet`
|
||||
|
||||
|
||||
django-mptt support
|
||||
-------------------
|
||||
.. _django-mptt-support:
|
||||
|
||||
Combining polymorphic with django-mptt_ is certainly possible, but not straightforward.
|
||||
django-mptt
|
||||
-----------
|
||||
|
||||
Combining polymorphic with :pypi:`django-mptt` is certainly possible, but not straightforward.
|
||||
It involves combining both managers, querysets, models, meta-classes and admin classes
|
||||
using multiple inheritance.
|
||||
|
||||
The django-polymorphic-tree_ package provides this out of the box.
|
||||
The :pypi:`django-polymorphic-tree` package provides this out of the box.
|
||||
|
||||
|
||||
django-reversion support
|
||||
------------------------
|
||||
.. _django-reversion-support:
|
||||
|
||||
Support for django-reversion_ works as expected with polymorphic models.
|
||||
django-reversion
|
||||
----------------
|
||||
|
||||
Support for :pypi:`django-reversion` works as expected with polymorphic models.
|
||||
However, they require more setup than standard models. That's become:
|
||||
|
||||
* Manually register the child models with django-reversion_, so their ``follow`` parameter can be
|
||||
set.
|
||||
* Polymorphic models use `multi-table inheritance
|
||||
<https://docs.djangoproject.com/en/dev/topics/db/models/#multi-table-inheritance>`_.
|
||||
See the `reversion documentation
|
||||
<https://django-reversion.readthedocs.io/en/latest/api.html#registration-api>`_
|
||||
how to deal with this by adding a ``follow`` field for the primary key.
|
||||
* Manually register the child models with :pypi:`django-reversion`, so their ``follow`` parameter
|
||||
can be set.
|
||||
* Polymorphic models use :ref:`django:multi-table-inheritance`.
|
||||
See the :doc:`django-reversion:api` for how to deal with this by adding a ``follow`` field for the
|
||||
primary key.
|
||||
* Both admin classes redefine ``object_history_template``.
|
||||
|
||||
|
||||
|
|
@ -171,10 +181,10 @@ polymorphic model.
|
|||
|
||||
.. _django-reversion-compare-support:
|
||||
|
||||
django-reversion-compare support
|
||||
--------------------------------
|
||||
django-reversion-compare
|
||||
------------------------
|
||||
|
||||
The django-reversion-compare_ views work as expected, the admin requires a little tweak.
|
||||
The :pypi:`django-reversion-compare` views work as expected, the admin requires a little tweak.
|
||||
In your parent admin, include the following method:
|
||||
|
||||
.. code-block:: python
|
||||
|
|
@ -188,12 +198,3 @@ As the compare view resolves the the parent admin, it uses it's base model to fi
|
|||
This doesn't work, since it needs to look for revisions of the child model. Using this tweak,
|
||||
the view of the actual child model is used, similar to the way the regular change and delete views
|
||||
are redirected.
|
||||
|
||||
|
||||
.. _django-extra-views: https://github.com/AndrewIngram/django-extra-views
|
||||
.. _django-guardian: https://github.com/django-guardian/django-guardian
|
||||
.. _django-mptt: https://github.com/django-mptt/django-mptt
|
||||
.. _django-polymorphic-tree: https://github.com/django-polymorphic/django-polymorphic-tree
|
||||
.. _django-rest-polymorphic: https://github.com/apirobot/django-rest-polymorphic
|
||||
.. _django-reversion-compare: https://github.com/jedie/django-reversion-compare
|
||||
.. _django-reversion: https://github.com/etianen/django-reversion
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
Custom Managers, Querysets & Manager Inheritance
|
||||
================================================
|
||||
Managers & Querysets
|
||||
====================
|
||||
|
||||
Using a Custom Manager
|
||||
----------------------
|
||||
|
||||
A nice feature of Django is the possibility to define one's own custom object managers.
|
||||
This is fully supported with django_polymorphic: For creating a custom polymorphic
|
||||
manager class, just derive your manager from ``PolymorphicManager`` instead of
|
||||
``models.Manager``. As with vanilla Django, in your model class, you should
|
||||
explicitly add the default manager first, and then your custom manager::
|
||||
This is fully supported with :pypi:`django-polymorphic`. For creating a custom polymorphic
|
||||
manager class, just derive your manager from :class:`~polymorphic.managers.PolymorphicManager`
|
||||
instead of :class:`~django.db.models.Manager`. As with vanilla Django, in your model class, you
|
||||
should explicitly add the default manager first, and then your custom manager:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from polymorphic.models import PolymorphicModel
|
||||
from polymorphic.managers import PolymorphicManager
|
||||
|
|
@ -16,29 +18,29 @@ explicitly add the default manager first, and then your custom manager::
|
|||
class TimeOrderedManager(PolymorphicManager):
|
||||
def get_queryset(self):
|
||||
qs = super(TimeOrderedManager,self).get_queryset()
|
||||
return qs.order_by('-start_date') # order the queryset
|
||||
return qs.order_by('-start_date')
|
||||
|
||||
def most_recent(self):
|
||||
qs = self.get_queryset() # get my ordered queryset
|
||||
return qs[:10] # limit => get ten most recent entries
|
||||
qs = self.get_queryset() # get my ordered queryset
|
||||
return qs[:10] # limit => get ten most recent entries
|
||||
|
||||
class Project(PolymorphicModel):
|
||||
objects = PolymorphicManager() # add the default polymorphic manager first
|
||||
objects_ordered = TimeOrderedManager() # then add your own manager
|
||||
start_date = DateTimeField() # project start is this date/time
|
||||
objects = PolymorphicManager() # add the default polymorphic manager first
|
||||
objects_ordered = TimeOrderedManager() # then add your own manager
|
||||
start_date = DateTimeField() # project start is this date/time
|
||||
|
||||
The first manager defined ('objects' in the example) is used by
|
||||
Django as automatic manager for several purposes, including accessing
|
||||
related objects. It must not filter objects and it's safest to use
|
||||
the plain ``PolymorphicManager`` here.
|
||||
The first manager defined (:attr:`~django.db.models.Model.objects` in the example) is used by Django
|
||||
as automatic manager for several purposes, including accessing related objects. It must not filter
|
||||
objects and it's safest to use the plain :class:`~polymorphic.managers.PolymorphicManager` here.
|
||||
|
||||
Manager Inheritance
|
||||
-------------------
|
||||
|
||||
Polymorphic models inherit/propagate all managers from their
|
||||
base models, as long as these are polymorphic. This means that all
|
||||
managers defined in polymorphic base models continue to work as
|
||||
expected in models inheriting from this base model::
|
||||
Polymorphic models inherit/propagate all managers from their base models, as long as these are
|
||||
polymorphic. This means that all managers defined in polymorphic base models continue to work as
|
||||
expected in models inheriting from this base model:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from polymorphic.models import PolymorphicModel
|
||||
from polymorphic.managers import PolymorphicManager
|
||||
|
|
@ -46,33 +48,34 @@ expected in models inheriting from this base model::
|
|||
class TimeOrderedManager(PolymorphicManager):
|
||||
def get_queryset(self):
|
||||
qs = super(TimeOrderedManager,self).get_queryset()
|
||||
return qs.order_by('-start_date') # order the queryset
|
||||
return qs.order_by('-start_date')
|
||||
|
||||
def most_recent(self):
|
||||
qs = self.get_queryset() # get my ordered queryset
|
||||
return qs[:10] # limit => get ten most recent entries
|
||||
qs = self.get_queryset() # get my ordered queryset
|
||||
return qs[:10] # limit => get ten most recent entries
|
||||
|
||||
class Project(PolymorphicModel):
|
||||
objects = PolymorphicManager() # add the default polymorphic manager first
|
||||
objects_ordered = TimeOrderedManager() # then add your own manager
|
||||
start_date = DateTimeField() # project start is this date/time
|
||||
objects = PolymorphicManager() # add the default polymorphic manager first
|
||||
objects_ordered = TimeOrderedManager() # then add your own manager
|
||||
start_date = DateTimeField() # project start is this date/time
|
||||
|
||||
class ArtProject(Project): # inherit from Project, inheriting its fields and managers
|
||||
class ArtProject(Project): # inherit from Project, inheriting its fields and managers
|
||||
artist = models.CharField(max_length=30)
|
||||
|
||||
ArtProject inherited the managers ``objects`` and ``objects_ordered`` from Project.
|
||||
|
||||
``ArtProject.objects_ordered.all()`` will return all art projects ordered
|
||||
regarding their start time and ``ArtProject.objects_ordered.most_recent()``
|
||||
will return the ten most recent art projects.
|
||||
``ArtProject.objects_ordered.all()`` will return all art projects ordered regarding their start time
|
||||
and ``ArtProject.objects_ordered.most_recent()`` will return the ten most recent art projects.
|
||||
|
||||
Using a Custom Queryset Class
|
||||
-----------------------------
|
||||
|
||||
The ``PolymorphicManager`` class accepts one initialization argument,
|
||||
which is the queryset class the manager should use. Just as with vanilla Django,
|
||||
you may define your own custom queryset classes. Just use PolymorphicQuerySet
|
||||
instead of Django's QuerySet as the base class::
|
||||
The :class:`~polymorphic.managers.PolymorphicManager` class accepts one initialization argument,
|
||||
which is the queryset class the manager should use. Just as with vanilla Django, you may define your
|
||||
own custom queryset classes. Just use :class:`~polymorphic.managers.PolymorphicQuerySet` instead of
|
||||
Django's :class:`~django.db.models.query.QuerySet` as the base class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from polymorphic.models import PolymorphicModel
|
||||
from polymorphic.managers import PolymorphicManager
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
Migrating existing models to polymorphic
|
||||
========================================
|
||||
Migrating Existing Models
|
||||
=========================
|
||||
|
||||
Existing models can be migrated to become polymorphic models.
|
||||
During the migrating, the ``polymorphic_ctype`` field needs to be filled in.
|
||||
Existing models can be migrated to become polymorphic models. During migration, the
|
||||
:attr:`~polymorphic.models.PolymorphicModel.polymorphic_ctype` field needs to be populated.
|
||||
|
||||
This can be done in the following steps:
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ This can be done in the following steps:
|
|||
Filling the content type value
|
||||
------------------------------
|
||||
|
||||
The following Python code can be used to fill the value of a model:
|
||||
The following code can be used to fill the value of a model:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -23,8 +23,8 @@ The following Python code can be used to fill the value of a model:
|
|||
new_ct = ContentType.objects.get_for_model(MyModel)
|
||||
MyModel.objects.filter(polymorphic_ctype__isnull=True).update(polymorphic_ctype=new_ct)
|
||||
|
||||
The creation and update of the ``polymorphic_ctype_id`` column
|
||||
can be included in a single Django migration. For example:
|
||||
The creation and update of the ``polymorphic_ctype_id`` column can be included in a single Django
|
||||
migration. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -37,7 +37,9 @@ can be included in a single Django migration. For example:
|
|||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
|
||||
new_ct = ContentType.objects.get_for_model(MyModel)
|
||||
MyModel.objects.filter(polymorphic_ctype__isnull=True).update(polymorphic_ctype=new_ct)
|
||||
MyModel.objects.filter(polymorphic_ctype__isnull=True).update(
|
||||
polymorphic_ctype=new_ct
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
@ -51,18 +53,23 @@ can be included in a single Django migration. For example:
|
|||
migrations.AddField(
|
||||
model_name='mymodel',
|
||||
name='polymorphic_ctype',
|
||||
field=models.ForeignKey(related_name='polymorphic_myapp.mymodel_set+', editable=False, to='contenttypes.ContentType', null=True),
|
||||
field=models.ForeignKey(
|
||||
related_name='polymorphic_myapp.mymodel_set+',
|
||||
editable=False,
|
||||
to='contenttypes.ContentType',
|
||||
null=True
|
||||
),
|
||||
),
|
||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
||||
]
|
||||
|
||||
It's recommended to let ``makemigrations`` create the migration file,
|
||||
and include the ``RunPython`` manually before running the migration.
|
||||
It's recommended to let :django-admin:`makemigrations` create the migration file, and include the
|
||||
:class:`~django.db.migrations.operations.RunPython` manually before running the migration.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
When the model is created elsewhere, you can also use
|
||||
the :func:`polymorphic.utils.reset_polymorphic_ctype` function:
|
||||
When the model is created elsewhere, you can also use the
|
||||
:func:`~polymorphic.utils.reset_polymorphic_ctype` function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
|
|||
|
|
@ -3,37 +3,37 @@
|
|||
Performance Considerations
|
||||
==========================
|
||||
|
||||
Usually, when Django users create their own polymorphic ad-hoc solution
|
||||
without a tool like *django-polymorphic*, this usually results in a variation of ::
|
||||
Usually, when Django users create their own polymorphic ad-hoc solution without a tool like
|
||||
:pypi:`django-polymorphic`, this usually results in a variation of:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ]
|
||||
|
||||
which has very bad performance, as it introduces one additional
|
||||
SQL query for every object in the result which is not of class ``BaseModel``.
|
||||
Compared to these solutions, *django-polymorphic* has the advantage
|
||||
that it only needs 1 SQL query *per object type*, and not *per object*.
|
||||
which has very bad performance, as it introduces one additional SQL query for every object in the
|
||||
result which is not of class ``BaseModel``. Compared to these solutions, :pypi:`django-polymorphic`
|
||||
has the advantage that it only needs 1 SQL query *per object type*, and not *per object*.
|
||||
|
||||
The current implementation does not use any custom SQL or Django DB layer
|
||||
internals - it is purely based on the standard Django ORM. Specifically, the query::
|
||||
The current implementation does not use any custom SQL or Django DB layer internals - it is purely
|
||||
based on the standard Django ORM. Specifically, the query:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
result_objects = list( ModelA.objects.filter(...) )
|
||||
|
||||
performs one SQL query to retrieve ``ModelA`` objects and one additional
|
||||
query for each unique derived class occurring in result_objects.
|
||||
The best case for retrieving 100 objects is 1 SQL query if all are
|
||||
class ``ModelA``. If 50 objects are ``ModelA`` and 50 are ``ModelB``, then
|
||||
two queries are executed. The pathological worst case is 101 db queries if
|
||||
result_objects contains 100 different object types (with all of them
|
||||
subclasses of ``ModelA``).
|
||||
performs one SQL query to retrieve ``ModelA`` objects and one additional query for each unique
|
||||
derived class occurring in result_objects. The best case for retrieving 100 objects is 1 SQL query
|
||||
if all are class ``ModelA``. If 50 objects are ``ModelA`` and 50 are ``ModelB``, then two queries
|
||||
are executed. The pathological worst case is 101 db queries if result_objects contains 100 different
|
||||
object types (with all of them subclasses of ``ModelA``).
|
||||
|
||||
ContentType retrieval
|
||||
---------------------
|
||||
:class:`~django.contrib.contenttypes.models.ContentType` retrieval
|
||||
------------------------------------------------------------------
|
||||
|
||||
When fetching the :class:`~django.contrib.contenttypes.models.ContentType` class,
|
||||
it's tempting to read the ``object.polymorphic_ctype`` field directly.
|
||||
However, this performs an additional query via the :class:`~django.db.models.ForeignKey` object
|
||||
to fetch the :class:`~django.contrib.contenttypes.models.ContentType`.
|
||||
Instead, use:
|
||||
When fetching the :class:`~django.contrib.contenttypes.models.ContentType` class, it's tempting to
|
||||
read the :attr:`~polymorphic.models.PolymorphicModel.polymorphic_ctype` field directly. However,
|
||||
this performs an additional query via the :class:`~django.db.models.ForeignKey` object to fetch the
|
||||
:class:`~django.contrib.contenttypes.models.ContentType`. Instead, use:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -43,16 +43,3 @@ Instead, use:
|
|||
|
||||
This uses the :meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_id` function
|
||||
which caches the results internally.
|
||||
|
||||
Database notes
|
||||
--------------
|
||||
|
||||
Current relational DBM systems seem to have general problems with
|
||||
the SQL queries produced by object relational mappers like the Django
|
||||
ORM, if these use multi-table inheritance like Django's ORM does.
|
||||
The "inner joins" in these queries can perform very badly.
|
||||
This is independent of django_polymorphic and affects all uses of
|
||||
multi table Model inheritance.
|
||||
|
||||
Please also see this `post (and comments) from Jacob Kaplan-Moss
|
||||
<http://jacobian.org/writing/concrete-inheritance/>`_.
|
||||
|
|
|
|||
|
|
@ -5,19 +5,37 @@ Install the project using::
|
|||
|
||||
pip install django-polymorphic
|
||||
|
||||
Update the settings file::
|
||||
Update the settings file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
INSTALLED_APPS += (
|
||||
'polymorphic',
|
||||
'django.contrib.contenttypes',
|
||||
)
|
||||
|
||||
The current release of *django-polymorphic* supports Django 2.2 - 4.0 and Python 3.6+.
|
||||
The current release of :pypi:`django-polymorphic` supports:
|
||||
|
||||
.. image:: https://badge.fury.io/py/django-polymorphic.svg
|
||||
:target: https://pypi.python.org/pypi/django-polymorphic/
|
||||
:alt: PyPI version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/django-polymorphic.svg
|
||||
:target: https://pypi.python.org/pypi/django-polymorphic/
|
||||
:alt: Supported Pythons
|
||||
|
||||
.. image:: https://img.shields.io/pypi/djversions/django-polymorphic.svg
|
||||
:target: https://pypi.org/project/django-polymorphic/
|
||||
:alt: Supported Django
|
||||
|
||||
|
||||
Making Your Models Polymorphic
|
||||
------------------------------
|
||||
|
||||
Use ``PolymorphicModel`` instead of Django's ``models.Model``, like so::
|
||||
Use :class:`~polymorphic.models.PolymorphicModel` instead of Django's
|
||||
:class:`~django.db.models.Model`, like so:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from polymorphic.models import PolymorphicModel
|
||||
|
||||
|
|
@ -37,25 +55,35 @@ Using Polymorphic Models
|
|||
|
||||
Create some objects:
|
||||
|
||||
>>> Project.objects.create(topic="Department Party")
|
||||
>>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner")
|
||||
>>> ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
|
||||
.. code-block:: python
|
||||
|
||||
>>> Project.objects.create(topic="Department Party")
|
||||
>>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner")
|
||||
>>> ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
|
||||
|
||||
Get polymorphic query results:
|
||||
|
||||
>>> Project.objects.all()
|
||||
[ <Project: id 1, topic "Department Party">,
|
||||
<ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
|
||||
<ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
|
||||
.. code-block:: python
|
||||
|
||||
Use ``instance_of`` or ``not_instance_of`` for narrowing the result to specific subtypes:
|
||||
>>> Project.objects.all()
|
||||
[ <Project: id 1, topic "Department Party">,
|
||||
<ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
|
||||
<ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
|
||||
|
||||
>>> Project.objects.instance_of(ArtProject)
|
||||
[ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner"> ]
|
||||
Use :meth:`~polymorphic.managers.PolymorphicQuerySet.instance_of` and
|
||||
:meth:`~polymorphic.managers.PolymorphicQuerySet.not_instance_of` for narrowing the result to
|
||||
specific subtypes:
|
||||
|
||||
>>> Project.objects.instance_of(ArtProject) | Project.objects.instance_of(ResearchProject)
|
||||
[ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
|
||||
<ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
|
||||
.. code-block:: python
|
||||
|
||||
>>> Project.objects.instance_of(ArtProject)
|
||||
[ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner"> ]
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> Project.objects.instance_of(ArtProject) | Project.objects.instance_of(ResearchProject)
|
||||
[ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
|
||||
<ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
|
||||
|
||||
Polymorphic filtering: Get all projects where Mr. Turner is involved as an artist
|
||||
or supervisor (note the three underscores):
|
||||
|
|
@ -64,20 +92,23 @@ or supervisor (note the three underscores):
|
|||
|
||||
>>> Project.objects.filter(Q(ArtProject___artist='T. Turner') | Q(ResearchProject___supervisor='T. Turner'))
|
||||
[ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
|
||||
<ResearchProject: id 4, topic "Color Use in Late Cubism", supervisor "T. Turner"> ]
|
||||
<ResearchProject: id 4, topic "Color Use in Late Cubism", supervisor "T. Turner"> ]
|
||||
|
||||
This is basically all you need to know, as *django-polymorphic* mostly
|
||||
works fully automatic and just delivers the expected results.
|
||||
|
||||
Note: When using the ``dumpdata`` management command on polymorphic tables
|
||||
(or any table that has a reference to :class:`~django.contrib.contenttypes.models.ContentType`),
|
||||
include the ``--natural`` flag in the arguments. This makes sure the
|
||||
:class:`~django.contrib.contenttypes.models.ContentType` models will be referenced by name
|
||||
instead of their primary key as that changes between Django instances.
|
||||
.. note::
|
||||
|
||||
When using the :django-admin:`dumpdata` management command on polymorphic tables
|
||||
(or any table that has a reference to :class:`~django.contrib.contenttypes.models.ContentType`),
|
||||
include the :option:`--natural-primary <dumpdata.--natural-primary>` and
|
||||
:option:`--natural-foreign <dumpdata.--natural-foreign>` flag in the arguments. This makes sure
|
||||
the :class:`~django.contrib.contenttypes.models.ContentType` models will be referenced by name
|
||||
instead of their primary key as that changes between Django instances.
|
||||
|
||||
|
||||
.. note::
|
||||
While *django-polymorphic* makes subclassed models easy to use in Django,
|
||||
While :pypi:`django-polymorphic` makes subclassed models easy to use in Django,
|
||||
we still encourage to use them with caution. Each subclassed model will require
|
||||
Django to perform an ``INNER JOIN`` to fetch the model fields from the database.
|
||||
While taking this in mind, there are valid reasons for using subclassed models.
|
||||
|
|
|
|||
|
|
@ -162,10 +162,10 @@ dev = [
|
|||
docs = [
|
||||
"django-extra-views>=0.15.0",
|
||||
"doc8>=1.1.2",
|
||||
"furo>=2025.7.19",
|
||||
"readme-renderer[md]>=43.0",
|
||||
"sphinx>=7.1.2",
|
||||
"sphinx-autobuild>=2021.3.14",
|
||||
"sphinx-rtd-theme>=3.0.2",
|
||||
"sphinx-autobuild>=2024.10.3",
|
||||
"sphinxcontrib-django>=2.5",
|
||||
]
|
||||
psycopg2 = [
|
||||
|
|
|
|||
|
|
@ -1,12 +1,30 @@
|
|||
"""
|
||||
Seamless Polymorphic Inheritance for Django Models
|
||||
r"""
|
||||
::
|
||||
|
||||
Copyright:
|
||||
This code and affiliated files are (C) by Bert Constantin and individual contributors.
|
||||
Please see LICENSE and AUTHORS for more information.
|
||||
██████╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ██████╗
|
||||
██╔══██╗ ██║██╔══██╗████╗ ██║██╔════╝ ██╔═══██╗
|
||||
██║ ██║ ██║███████║██╔██╗ ██║██║ ███╗██║ ██║
|
||||
██║ ██║██ ██║██╔══██║██║╚██╗██║██║ ██║██║ ██║
|
||||
██████╔╝╚█████╔╝██║ ██║██║ ╚████║╚██████╔╝╚██████╔╝
|
||||
╚═════╝ ╚════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝
|
||||
|
||||
██████╗ ██████╗ ██╗ ██╗ ██╗███╗ ███╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗██╗ ██████╗
|
||||
██╔══██╗██╔═══██╗██║ ╚██╗ ██╔╝████╗ ████║██╔═══██╗██╔══██╗██╔══██╗██║ ██║██║██╔════╝
|
||||
██████╔╝██║ ██║██║ ╚████╔╝ ██╔████╔██║██║ ██║██████╔╝██████╔╝███████║██║██║
|
||||
██╔═══╝ ██║ ██║██║ ╚██╔╝ ██║╚██╔╝██║██║ ██║██╔══██╗██╔═══╝ ██╔══██║██║██║
|
||||
██║ ╚██████╔╝███████╗██║ ██║ ╚═╝ ██║╚██████╔╝██║ ██║██║ ██║ ██║██║╚██████╗
|
||||
╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═════╝
|
||||
|
||||
|
||||
Seamless Polymorphic Inheritance for Django Models
|
||||
"""
|
||||
|
||||
VERSION = "4.2.0"
|
||||
|
||||
# version synonym for backwards compatibility
|
||||
__version__ = VERSION
|
||||
__title__ = "Django Polymorphic"
|
||||
__version__ = VERSION # version synonym for backwards compatibility
|
||||
__author__ = "Brian Kohan"
|
||||
__license__ = "BSD-3-Clause"
|
||||
__copyright__ = (
|
||||
"Copyright 2010-2025, Bert Constantin, Chris Glass, Diederik van der Boor, Brian Kohan"
|
||||
)
|
||||
|
|
|
|||
84
uv.lock
84
uv.lock
|
|
@ -1,5 +1,5 @@
|
|||
version = 1
|
||||
revision = 3
|
||||
revision = 2
|
||||
requires-python = ">=3.9, <4.0"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.11'",
|
||||
|
|
@ -7,6 +7,18 @@ resolution-markers = [
|
|||
"python_full_version < '3.10'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accessible-pygments"
|
||||
version = "0.0.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alabaster"
|
||||
version = "0.7.16"
|
||||
|
|
@ -77,6 +89,19 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.13.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "soupsieve" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/85/2e/3e5079847e653b1f6dc647aa24549d68c6addb4c595cc0d902d1b19308ad/beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695", size = 622954, upload-time = "2025-08-24T14:06:13.168Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/eb/f4151e0c7377a6e08a38108609ba5cede57986802757848688aeedd1b9e8/beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a", size = 105113, upload-time = "2025-08-24T14:06:14.884Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "6.1.0"
|
||||
|
|
@ -571,12 +596,12 @@ docs = [
|
|||
{ name = "django-extra-views" },
|
||||
{ name = "doc8", version = "1.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "doc8", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "furo" },
|
||||
{ name = "readme-renderer", extra = ["md"] },
|
||||
{ name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
|
||||
{ name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "sphinx-autobuild" },
|
||||
{ name = "sphinx-rtd-theme" },
|
||||
{ name = "sphinxcontrib-django" },
|
||||
]
|
||||
mysql = [
|
||||
|
|
@ -614,10 +639,10 @@ dev = [
|
|||
docs = [
|
||||
{ name = "django-extra-views", specifier = ">=0.15.0" },
|
||||
{ name = "doc8", specifier = ">=1.1.2" },
|
||||
{ name = "furo", specifier = ">=2025.7.19" },
|
||||
{ name = "readme-renderer", extras = ["md"], specifier = ">=43.0" },
|
||||
{ name = "sphinx", specifier = ">=7.1.2" },
|
||||
{ name = "sphinx-autobuild", specifier = ">=2021.3.14" },
|
||||
{ name = "sphinx-rtd-theme", specifier = ">=3.0.2" },
|
||||
{ name = "sphinx-autobuild", specifier = ">=2024.10.3" },
|
||||
{ name = "sphinxcontrib-django", specifier = ">=2.5" },
|
||||
]
|
||||
mysql = [{ name = "mysqlclient", specifier = ">=1.4.0" }]
|
||||
|
|
@ -703,6 +728,24 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "furo"
|
||||
version = "2025.7.19"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "accessible-pygments" },
|
||||
{ name = "beautifulsoup4" },
|
||||
{ name = "pygments" },
|
||||
{ name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
|
||||
{ name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "sphinx-basic-ng" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/69/312cd100fa45ddaea5a588334d2defa331ff427bcb61f5fe2ae61bdc3762/furo-2025.7.19.tar.gz", hash = "sha256:4164b2cafcf4023a59bb3c594e935e2516f6b9d35e9a5ea83d8f6b43808fe91f", size = 1662054, upload-time = "2025-07-19T10:52:09.754Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/34/2b07b72bee02a63241d654f5d8af87a2de977c59638eec41ca356ab915cd/furo-2025.7.19-py3-none-any.whl", hash = "sha256:bdea869822dfd2b494ea84c0973937e35d1575af088b6721a29c7f7878adc9e3", size = 342175, upload-time = "2025-07-19T10:52:02.399Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
|
|
@ -1471,6 +1514,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soupsieve"
|
||||
version = "2.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sphinx"
|
||||
version = "7.4.7"
|
||||
|
|
@ -1585,19 +1637,17 @@ wheels = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-rtd-theme"
|
||||
version = "3.0.2"
|
||||
name = "sphinx-basic-ng"
|
||||
version = "1.0.0b2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "docutils" },
|
||||
{ name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
|
||||
{ name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "sphinxcontrib-jquery" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/91/44/c97faec644d29a5ceddd3020ae2edffa69e7d00054a8c7a6021e82f20335/sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85", size = 7620463, upload-time = "2024-11-13T11:06:04.545Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736, upload-time = "2023-07-08T18:40:54.166Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/85/77/46e3bac77b82b4df5bb5b61f2de98637724f246b4966cfc34bc5895d852a/sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13", size = 7655561, upload-time = "2024-11-13T11:06:02.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496, upload-time = "2023-07-08T18:40:52.659Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1644,20 +1694,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sphinxcontrib-jquery"
|
||||
version = "4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
|
||||
{ name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sphinxcontrib-jsmath"
|
||||
version = "1.0.1"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user