diff --git a/docs/admin.rst b/docs/admin.rst index 3a945f5..c06f98c 100644 --- a/docs/admin.rst +++ b/docs/admin.rst @@ -57,10 +57,12 @@ use the ``base_form`` and ``base_fieldsets`` instead. The ``PolymorphicChildMode automatically detect the additional fields that the child model has, display those in a separate fieldset. -Polymorphic Inlines -------------------- +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". +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". .. _admin-example: diff --git a/docs/formsets.rst b/docs/formsets.rst new file mode 100644 index 0000000..ea4fa99 --- /dev/null +++ b/docs/formsets.rst @@ -0,0 +1,12 @@ +Formsets +======== + +Polymorphic models can be used in formsets. + +Use the :func:`polymorphic.formsets.polymorphic_inlineformset_factory` function to generate the formset. +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 + +.. code-block:: python + + from polymorphic.formsets import polymorphic_child_forms_factory diff --git a/docs/index.rst b/docs/index.rst index 67b5f69..ece7673 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -49,6 +49,7 @@ Getting started quickstart admin + formsets performance Advanced topics diff --git a/polymorphic/admin/childadmin.py b/polymorphic/admin/childadmin.py index 63cda0f..468d3d6 100644 --- a/polymorphic/admin/childadmin.py +++ b/polymorphic/admin/childadmin.py @@ -6,8 +6,10 @@ from django.core.urlresolvers import resolve from django.utils import six from django.utils.translation import ugettext_lazy as _ +from .helpers import PolymorphicInlineSupportMixin -class PolymorphicChildModelAdmin(admin.ModelAdmin): + +class PolymorphicChildModelAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin): """ The *optional* base class for the admin interface of derived models. diff --git a/polymorphic/admin/filters.py b/polymorphic/admin/filters.py index 16b043b..82b4964 100644 --- a/polymorphic/admin/filters.py +++ b/polymorphic/admin/filters.py @@ -7,6 +7,12 @@ class PolymorphicChildModelFilter(admin.SimpleListFilter): """ An admin list filter for the PolymorphicParentModelAdmin which enables filtering by its child models. + + This can be used in the parent admin: + + .. code-block:: python + + list_filter = (PolymorphicChildModelFilter,) """ title = _('Type') parameter_name = 'polymorphic_ctype' diff --git a/polymorphic/admin/helpers.py b/polymorphic/admin/helpers.py index 75822e3..89e9da4 100644 --- a/polymorphic/admin/helpers.py +++ b/polymorphic/admin/helpers.py @@ -5,7 +5,7 @@ This makes sure that admin fieldsets/layout settings are exported to the templat """ from django.contrib.admin.helpers import InlineAdminFormSet, InlineAdminForm -from ..formsets import BasePolymorphicModelFormSet +from polymorphic.formsets import BasePolymorphicModelFormSet class InlinePolymorphicAdminForm(InlineAdminForm): @@ -75,6 +75,14 @@ class InlinePolymorphicAdminFormSet(InlineAdminFormSet): class PolymorphicInlineSupportMixin(object): """ A Mixin to add to the regular admin, so it can work with our polymorphic inlines. + + This mixin needs to be included in the admin that hosts the ``inlines``. + It makes sure the generated admin forms have different fieldsets/fields + depending on the polymorphic type of the form instance. + + This is achieved by overwriting :func:`get_inline_formsets` to return + an :class:`InlinePolymorphicAdminFormSet` instead of a standard Django + :class:`~django.contrib.admin.helpers.InlineAdminFormSet` for the polymorphic formsets. """ def get_inline_formsets(self, request, formsets, inline_instances, obj=None): @@ -88,7 +96,8 @@ class PolymorphicInlineSupportMixin(object): for admin_formset in inline_admin_formsets: if isinstance(admin_formset.formset, BasePolymorphicModelFormSet): - # Downcast the admin + # This is a polymorphic formset, which belongs to our inline. + # Downcast the admin wrapper that generates the form fields. admin_formset.__class__ = InlinePolymorphicAdminFormSet admin_formset.request = request admin_formset.obj = obj diff --git a/polymorphic/admin/inlines.py b/polymorphic/admin/inlines.py index 11fcc53..7817cf2 100644 --- a/polymorphic/admin/inlines.py +++ b/polymorphic/admin/inlines.py @@ -17,11 +17,10 @@ class PolymorphicParentInlineModelAdmin(InlineModelAdmin): """ A polymorphic inline, where each formset row can be a different form. - Note that + Note that: * Permissions are only checked on the base model. * The child inlines can't override the base model fields, only this parent inline can do that. - * Child formset media is not yet processed. """ formset = BasePolymorphicInlineFormSet @@ -79,7 +78,7 @@ class PolymorphicParentInlineModelAdmin(InlineModelAdmin): # Instead of completely redefining super().get_formset(), we use # the regular inlineformset_factory(), and amend that with our extra bits. - # This is identical to what polymorphic_inlineformset_factory() does. + # This code line is the essence of what polymorphic_inlineformset_factory() does. FormSet.child_forms = polymorphic_child_forms_factory( formset_children=self.get_formset_children(request, obj=obj) ) diff --git a/polymorphic/formsets/models.py b/polymorphic/formsets/models.py index 43d0349..4248ab4 100644 --- a/polymorphic/formsets/models.py +++ b/polymorphic/formsets/models.py @@ -14,6 +14,7 @@ class PolymorphicFormSetChild(object): Metadata to define the inline of a polymorphic child. Provide this information in the :func:`polymorphic_inlineformset_factory` construction. """ + def __init__(self, model, form=ModelForm, fields=None, exclude=None, formfield_callback=None, widgets=None, localized_fields=None, labels=None, help_texts=None, error_messages=None): @@ -77,6 +78,9 @@ class PolymorphicFormSetChild(object): def polymorphic_child_forms_factory(formset_children, **kwargs): """ Construct the forms for the formset children. + This is mostly used internally, and rarely needs to be used by external projects. + When using the factory methods (:func:`polymorphic_inlineformset_factory`), + this feature is called already for you. """ child_forms = OrderedDict() @@ -97,6 +101,7 @@ class BasePolymorphicModelFormSet(BaseModelFormSet): note that the ID field will no longer be named ``model_ptr``, but just appear as ``id``. """ + # Assigned by the factory child_forms = OrderedDict() @@ -191,6 +196,9 @@ class BasePolymorphicModelFormSet(BaseModelFormSet): super(BasePolymorphicModelFormSet, self).add_fields(form, index) def get_form_class(self, model): + """ + Return the proper form class for the given model. + """ if not self.child_forms: raise ImproperlyConfigured("No 'child_forms' defined in {0}".format(self.__class__.__name__)) return self.child_forms[model]