From 015e72d73f66c4af284fbd26425f4f066ae64554 Mon Sep 17 00:00:00 2001 From: Brian Kohan Date: Tue, 26 Aug 2025 14:24:17 -0700 Subject: [PATCH] refresh docs, add missing intersphinx references, update CONTRIBUTING --- CONTRIBUTING.md | 72 +++---- docs/_static/style.css | 32 +++ docs/admin.rst | 106 +++++----- docs/advanced.rst | 233 ++++++++++++--------- docs/api/index.rst | 8 + docs/api/polymorphic.showfields.rst | 7 + docs/changelog.rst | 8 +- docs/conf.py | 71 +++++-- docs/contributing.rst | 48 ----- docs/formsets.rst | 4 +- docs/index.rst | 71 ++++++- docs/{third-party.rst => integrations.rst} | 81 +++---- docs/managers.rst | 71 ++++--- docs/migrating.rst | 33 +-- docs/performance.rst | 57 ++--- docs/quickstart.rst | 77 +++++-- pyproject.toml | 4 +- src/polymorphic/__init__.py | 32 ++- uv.lock | 84 +++++--- 19 files changed, 652 insertions(+), 447 deletions(-) create mode 100644 docs/_static/style.css create mode 100644 docs/api/polymorphic.showfields.rst delete mode 100644 docs/contributing.rst rename docs/{third-party.rst => integrations.rst} (67%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65fb4aa..286bb35 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 ``` diff --git a/docs/_static/style.css b/docs/_static/style.css new file mode 100644 index 0000000..fc985d6 --- /dev/null +++ b/docs/_static/style.css @@ -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; + } +} diff --git a/docs/admin.rst b/docs/admin.rst index 37eb3f8..a9d98ec 100644 --- a/docs/admin.rst +++ b/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 `. 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 ~~~~~~~~~~~~~~~~ diff --git a/docs/advanced.rst b/docs/advanced.rst index a82d1c4..ee1319c 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -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) -. -[ , - ] +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) + [ , + ] + +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') ) -. -[ , - ] +.. code-block:: python + + >>> ModelA.objects.filter( Q(ModelB___field2 = 'B2') | Q(ModelC___field3 = 'C3') ) + [ , + ] 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) -. -[ , - ] +.. code-block:: python + + >>> Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY) + + [ , + ] 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 -[ , - , - ] +.. code-block:: python + + >>> qs=ModelA.objects.non_polymorphic().all() + >>> qs + [ , + , + ] 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) -[ , - , - ] +.. code-block:: python + + >>> ModelA.objects.get_real_instances(qs) + [ , + , + ] 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 `_. 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 ` and + :option:`--natural-foreign ` flags in the arguments. diff --git a/docs/api/index.rst b/docs/api/index.rst index 935762d..31cfd1d 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -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 diff --git a/docs/api/polymorphic.showfields.rst b/docs/api/polymorphic.showfields.rst new file mode 100644 index 0000000..d1724ee --- /dev/null +++ b/docs/api/polymorphic.showfields.rst @@ -0,0 +1,7 @@ +polymorphic.showfields +====================== + +.. automodule:: polymorphic.showfields + :members: + :show-inheritance: + :inherited-members: \ No newline at end of file diff --git a/docs/changelog.rst b/docs/changelog.rst index 52ccb39..3122792 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -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: diff --git a/docs/conf.py b/docs/conf.py index 7ab7bd6..834260a 100644 --- a/docs/conf.py +++ b/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 diff --git a/docs/contributing.rst b/docs/contributing.rst deleted file mode 100644 index c114c46..0000000 --- a/docs/contributing.rst +++ /dev/null @@ -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/ diff --git a/docs/formsets.rst b/docs/formsets.rst index 9d75587..ad12cae 100644 --- a/docs/formsets.rst +++ b/docs/formsets.rst @@ -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. diff --git a/docs/index.rst b/docs/index.rst index 4d4845b..e9f69f7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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 diff --git a/docs/third-party.rst b/docs/integrations.rst similarity index 67% rename from docs/third-party.rst rename to docs/integrations.rst index 43790ae..47d48e7 100644 --- a/docs/third-party.rst +++ b/docs/integrations.rst @@ -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 -`_. +This option requires :pypi:`django-guardian` >= 1.4.6. Details about how this option works are +available in the `django-guardian documentation +`_. -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 - `_. - See the `reversion documentation - `_ - 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 diff --git a/docs/managers.rst b/docs/managers.rst index 00b6f73..d27a3f7 100644 --- a/docs/managers.rst +++ b/docs/managers.rst @@ -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 diff --git a/docs/migrating.rst b/docs/migrating.rst index 2db2355..c556ffd 100644 --- a/docs/migrating.rst +++ b/docs/migrating.rst @@ -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 diff --git a/docs/performance.rst b/docs/performance.rst index 4d27dbb..a23fb8e 100644 --- a/docs/performance.rst +++ b/docs/performance.rst @@ -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 -`_. diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 13c1b5c..893e0e2 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -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() -[ , - , - ] +.. code-block:: python -Use ``instance_of`` or ``not_instance_of`` for narrowing the result to specific subtypes: + >>> Project.objects.all() + [ , + , + ] ->>> Project.objects.instance_of(ArtProject) -[ ] +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) -[ , - ] +.. code-block:: python + + >>> Project.objects.instance_of(ArtProject) + [ ] + +.. code-block:: python + + >>> Project.objects.instance_of(ArtProject) | Project.objects.instance_of(ResearchProject) + [ , + ] 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')) [ , - ] + ] 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 ` and + :option:`--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. diff --git a/pyproject.toml b/pyproject.toml index 18e0586..7c23b8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 = [ diff --git a/src/polymorphic/__init__.py b/src/polymorphic/__init__.py index a30ee61..b6096f5 100644 --- a/src/polymorphic/__init__.py +++ b/src/polymorphic/__init__.py @@ -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" +) diff --git a/uv.lock b/uv.lock index 577b2d8..54d589e 100644 --- a/uv.lock +++ b/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"