Update documentation

fix_request_path_info
Diederik van der Boor 2016-08-10 13:03:51 +02:00
parent 8bce015199
commit 8b50942292
5 changed files with 224 additions and 84 deletions

View File

@ -4,68 +4,48 @@ Django admin integration
Off 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.
The polymorphic admin interface works in a simple way:
* The add screen gains an additional step where the desired child model is selected.
* 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 forwards the *edit* and *delete* views
to the ``ModelAdmin`` of the derived child model. The *list* page is still implemented by the parent model admin.
Setup
-----
Both the parent model and child model need to have a ``ModelAdmin`` class.
The parent model
----------------
The shared base model should use the :class:`~polymorphic.admin.PolymorphicParentModelAdmin` as base class.
The parent model needs to inherit ``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.
* ``base_model`` should be set
* ``child_models`` or ``get_child_models()`` should return an iterable of Model classes.
The admin class for every child model should inherit from :class:`~polymorphic.admin.PolymorphicChildModelAdmin`
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.
* :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_model` should be set.
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.
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 ``True``.
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.
Fieldset configuration
~~~~~~~~~~~~~~~~~~~~~~
If you use other applications such as django-reversion_ or django-mptt_, please check +:ref:`third-party`.
The parent admin is only used for the list display of models,
and for the edit/delete view of non-subclassed models.
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.
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.
The child models
----------------
.. 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.
The admin interface of the derived models should inherit from ``PolymorphicChildModelAdmin``.
Again, ``base_model`` should be set in this class as well.
This class implements the following features:
* It corrects the breadcrumbs in the admin pages.
* It extends the template lookup paths, to look for both the parent model and child model in the ``admin/app/model/change_form.html`` path.
* It allows to set ``base_form`` so the derived class will automatically include other fields in the form.
* It allows to set ``base_fieldsets`` so the derived class will automatically display any extra fields.
* Although it must be registered with admin site, by default it's hidden from admin site index page.
This can be overriden by adding ``show_in_index = True`` in admin class.
The standard ``ModelAdmin`` attributes ``form`` and ``fieldsets`` should rather be avoided at the base class,
because it will hide any additional fields which are defined in the derived model. Instead,
use the ``base_form`` and ``base_fieldsets`` instead. The ``PolymorphicChildModelAdmin`` will
automatically detect the additional fields that the child model has, display those in a separate fieldset.
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".
.. 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 :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` is set.
.. _admin-example:
@ -93,17 +73,20 @@ The models are taken from :ref:`advanced-features`.
)
@admin.register(ModelB)
class ModelBAdmin(ModelAChildAdmin):
base_model = ModelB
# define custom features here
@admin.register(ModelC)
class ModelCAdmin(ModelBAdmin):
base_model = ModelC
show_in_index = True # makes child model admin visible in main admin site
# define custom features here
@admin.register(ModelA)
class ModelAParentAdmin(PolymorphicParentModelAdmin):
""" The parent model admin """
base_model = ModelA
@ -111,22 +94,6 @@ The models are taken from :ref:`advanced-features`.
list_filter = (PolymorphicChildModelFilter,) # This is optional.
class ModelBInline(admin.StackedInline):
model = ModelB
fk_name = 'modelb'
readonly_fields = ['modela_ptr']
class StandardModelAdmin(admin.ModelAdmin):
inlines = [ModelBInline]
# Only the parent needs to be registered:
admin.site.register(ModelA, ModelAParentAdmin)
admin.site.register(ModelB, ModelBAdmin)
admin.site.register(ModelC, ModelCAdmin)
admin.site.register(StandardModel, StandardModelAdmin)
Filtering child types
---------------------
@ -134,5 +101,146 @@ 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.
Inline models
-------------
.. versionadded:: 1.0
Inline models are handled via a special :class:`~polymorphic.admin.StackedPolymorphicInline` class.
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` /
: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.
.. code-block:: python
from django.contrib import admin
from polymorphic.admin import PolymorphicInlineSupportMixin, StackedPolymorphicInline
from .models import Order, Payment, CreditCardPayment, BankPayment, SepaPayment
class PaymentInline(StackedPolymorphicInline):
"""
An inline for a polymorphic model.
The actual form appearance of each row is determined by
the child inline that corresponds with the actual model type.
"""
class CreditCardPaymentInline(StackedPolymorphicInline.Child):
model = CreditCardPayment
class BankPaymentInline(StackedPolymorphicInline.Child):
model = BankPayment
class SepaPaymentInline(StackedPolymorphicInline.Child):
model = SepaPayment
model = Payment
child_inlines = (
CreditCardPaymentInline,
BankPaymentInline,
SepaPaymentInline,
)
@admin.register(Order)
class OrderAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin):
"""
Admin for orders.
The inline is polymorphic.
To make sure the inlines are properly handled,
the ``PolymorphicInlineSupportMixin`` is needed to
"""
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".
.. code-block:: python
from django.contrib import admin
from .models import StandardModel
class ModelBInline(admin.StackedInline):
model = ModelB
fk_name = 'modelb'
readonly_fields = ['modela_ptr']
@admin.register(StandardModel)
class StandardModelAdmin(admin.ModelAdmin):
inlines = [ModelBInline]
Internal details
----------------
The polymorphic admin interface works in a simple way:
* The add screen gains an additional step where the desired child model is selected.
* 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 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.
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.
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.
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.
If you use other applications such as django-reversion_ or django-mptt_, please check +:ref:`third-party`.
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.
The child models
~~~~~~~~~~~~~~~~
The admin interface of the derived models should inherit from :class:`~polymorphic.admin.PolymorphicChildModelAdmin`.
Again, :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_model` should be set in this class as well.
This class implements the following features:
* It corrects the breadcrumbs in the admin pages.
* It extends the template lookup paths, to look for both the parent model and child model in the ``admin/app/model/change_form.html`` path.
* It allows to set :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_form` so the derived class will automatically include other fields in the form.
* It allows to set :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_fieldsets` so the derived class will automatically display any extra fields.
* Although it must be registered with admin site, by default it's hidden from admin site index page.
This can be overriden by adding :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` = ``True`` in admin class.
.. _django-reversion: https://github.com/etianen/django-reversion
.. _django-mptt: https://github.com/django-mptt/django-mptt

View File

@ -227,6 +227,6 @@ It supports Django 1.1 up till 1.4 and Python 2.4 up till 2.7.
For a detailed list of it's changes, see the :doc:`archived changelog <changelog_archive>`.
.. _Grappelli: http://grappelliproject.com/
.. _django-parler: https://github.com/edoburu/django-parler
.. _django-parler: https://github.com/django-parler/django-parler
.. _django-reversion: https://github.com/etianen/django-reversion
.. _django-reversion-compare: https://github.com/jedie/django-reversion-compare

View File

@ -1,12 +1,44 @@
Formsets
========
.. versionadded:: 1.0
Polymorphic models can be used in formsets.
Use the :func:`polymorphic.formsets.polymorphic_inlineformset_factory` function to generate the formset.
The implementation is almost identical to the regular Django 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
Provide a list of :class:`~polymorphic.formsets.PolymorphicFormSetChild` objects for this.
.. code-block:: python
from polymorphic.formsets import polymorphic_child_forms_factory
from polymorphic.formsets import polymorphic_modelformset_factory, PolymorphicFormSetChild
ModelAFormSet = polymorphic_modelformset_factory(ModelA, formset_children=(
PolymorphicFormSetChild(ModelB),
PolymorphicFormSetChild(ModelC),
))
The formset can be used just like all other formsets:
.. code-block:: python
if request.method == "POST":
formset = ModelAFormSet(request.POST, request.FILES, queryset=ModelA.objects.all())
if formset.is_valid():
formset.save()
else:
formset = ModelAFormSet(queryset=ModelA.objects.all())
Like standard Django 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.
* :func:`~polymorphic.formsets.generic_polymorphic_inlineformset_factory` - create an inline formset for a generic foreign key.
Each one uses a different base class:
* :class:`~polymorphic.formsets.BasePolymorphicModelFormSet`
* :class:`~polymorphic.formsets.BasePolymorphicInlineFormSet`
* :class:`~polymorphic.formsets.BaseGenericPolymorphicInlineFormSet`
When needed, the base class can be overwritten and provided to the factory via the ``formset`` parameter.

View File

@ -55,7 +55,6 @@ Getting started
quickstart
admin
formsets
performance
Advanced topics
@ -64,9 +63,10 @@ Advanced topics
.. toctree::
:maxdepth: 2
formsets
migrating
advanced
managers
advanced
third-party
changelog
contributing

View File

@ -1,14 +1,24 @@
Third-party applications support
================================
django-mptt support
-------------------
Combining polymorphic with 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.
django-reversion support
------------------------
Support for django-reversion_ works as expected with polymorphic models.
However, they require more setup than standard models. That's become:
* The children models are not registered in the admin site.
You will therefore need to manually register them to django-reversion_.
* 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#multi-table-inheritance>`_
how to deal with this by adding a ``follow`` field for the primary key.
@ -87,17 +97,7 @@ This doesn't work, since it needs to look for revisions of the child model. Usin
the view of the actual child model is used, similar to the way the regular change and delete views are redirected.
django-mptt support
-------------------
Combining polymorphic with 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.
.. _django-reversion: https://github.com/etianen/django-reversion
.. _django-reversion-compare: https://github.com/jedie/django-reversion-compare
.. _django-mptt: https://github.com/django-mptt/django-mptt
.. _django-polymorphic-tree: https://github.com/edoburu/django-polymorphic-tree
.. _django-polymorphic-tree: https://github.com/django-polymorphic/django-polymorphic-tree