Update documentation
parent
8bce015199
commit
8b50942292
238
docs/admin.rst
238
docs/admin.rst
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue