Rename inline/formset classes to avoid more confusion.

AbstractSingletonProxyFactoryBean danger luked here...
Added the Stacked...Inline for clarity too.
fix_request_path_info
Diederik van der Boor 2016-08-09 01:11:05 +02:00
parent 8c42893abd
commit 1f0ddd8436
6 changed files with 157 additions and 143 deletions

View File

@ -14,22 +14,22 @@ from .filters import PolymorphicChildModelFilter
# Inlines # Inlines
from .inlines import ( from .inlines import (
PolymorphicParentInlineModelAdmin, PolymorphicInlineModelAdmin, # base class
PolymorphicChildInlineModelAdmin, StackedPolymorphicInline, # stacked inline
) )
# Helpers for the inlines # Helpers for the inlines
from .helpers import ( from .helpers import (
InlinePolymorphicAdminForm, PolymorphicInlineAdminForm,
InlinePolymorphicAdminFormSet, PolymorphicInlineAdminFormSet,
PolymorphicInlineSupportMixin, # mixin for the regular model admin! PolymorphicInlineSupportMixin, # mixin for the regular model admin!
) )
# Expose generic admin features too. There is no need to split those # Expose generic admin features too. There is no need to split those
# as the admin already relies on contenttypes. # as the admin already relies on contenttypes.
from .generic import ( from .generic import (
PolymorphicParentGenericInlineModelAdmin, GenericPolymorphicInlineModelAdmin, # base class
PolymorphicChildGenericInlineModelAdmin, GenericStackedPolymorphicInline, # stacked inline
) )
__all__ = ( __all__ = (
@ -37,11 +37,10 @@ __all__ = (
'PolymorphicChildModelAdmin', 'PolymorphicChildModelAdmin',
'PolymorphicModelChoiceForm', 'PolymorphicModelChoiceForm',
'PolymorphicChildModelFilter', 'PolymorphicChildModelFilter',
'InlinePolymorphicAdminForm', 'PolymorphicInlineAdminForm',
'InlinePolymorphicAdminFormSet', 'PolymorphicInlineAdminFormSet',
'PolymorphicInlineSupportMixin', 'PolymorphicInlineSupportMixin',
'PolymorphicParentInlineModelAdmin', 'PolymorphicInlineModelAdmin',
'PolymorphicChildInlineModelAdmin', 'GenericPolymorphicInlineModelAdmin',
'PolymorphicParentGenericInlineModelAdmin', 'GenericStackedPolymorphicInline',
'PolymorphicChildGenericInlineModelAdmin',
) )

View File

@ -2,15 +2,15 @@ from django.contrib.contenttypes.admin import GenericInlineModelAdmin
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.functional import cached_property from django.utils.functional import cached_property
from polymorphic.formsets import polymorphic_child_forms_factory, BasePolymorphicGenericInlineFormSet, PolymorphicGenericFormSetChild from polymorphic.formsets import polymorphic_child_forms_factory, BaseGenericPolymorphicInlineFormSet, GenericPolymorphicFormSetChild
from .inlines import PolymorphicParentInlineModelAdmin, PolymorphicChildInlineModelAdmin from .inlines import PolymorphicInlineModelAdmin
class PolymorphicParentGenericInlineModelAdmin(PolymorphicParentInlineModelAdmin, GenericInlineModelAdmin): class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInlineModelAdmin):
""" """
Variation for inlines based on generic foreign keys. Base class for variation of inlines based on generic foreign keys.
""" """
formset = BasePolymorphicGenericInlineFormSet formset = BaseGenericPolymorphicInlineFormSet
def get_formset(self, request, obj=None, **kwargs): def get_formset(self, request, obj=None, **kwargs):
""" """
@ -26,30 +26,36 @@ class PolymorphicParentGenericInlineModelAdmin(PolymorphicParentInlineModelAdmin
) )
return FormSet return FormSet
class Child(PolymorphicInlineModelAdmin.Child):
class PolymorphicChildGenericInlineModelAdmin(PolymorphicChildInlineModelAdmin):
"""
Variation for generic inlines.
"""
# Make sure that the GFK fields are excluded from the child forms
formset_child = PolymorphicGenericFormSetChild
ct_field = "content_type"
ct_fk_field = "object_id"
@cached_property
def content_type(self):
""" """
Expose the ContentType that the child relates to. Variation for generic inlines.
This can be used for the ``polymorphic_ctype`` field.
""" """
return ContentType.objects.get_for_model(self.model) # Make sure that the GFK fields are excluded from the child forms
formset_child = GenericPolymorphicFormSetChild
ct_field = "content_type"
ct_fk_field = "object_id"
def get_formset_child(self, request, obj=None, **kwargs): @cached_property
# Similar to GenericInlineModelAdmin.get_formset(), def content_type(self):
# make sure the GFK is automatically excluded from the form """
defaults = { Expose the ContentType that the child relates to.
"ct_field": self.ct_field, This can be used for the ``polymorphic_ctype`` field.
"fk_field": self.ct_fk_field, """
} return ContentType.objects.get_for_model(self.model)
defaults.update(kwargs)
return super(PolymorphicChildGenericInlineModelAdmin, self).get_formset_child(request, obj=obj, **defaults) def get_formset_child(self, request, obj=None, **kwargs):
# Similar to GenericInlineModelAdmin.get_formset(),
# make sure the GFK is automatically excluded from the form
defaults = {
"ct_field": self.ct_field,
"fk_field": self.ct_fk_field,
}
defaults.update(kwargs)
return super(GenericPolymorphicInlineModelAdmin.Child, self).get_formset_child(request, obj=obj, **defaults)
class GenericStackedPolymorphicInline(GenericPolymorphicInlineModelAdmin):
"""
The stacked layout for generic inlines.
"""
template = 'admin/polymorphic/edit_inline/stacked.html'

View File

@ -3,19 +3,19 @@ Rendering utils for admin forms;
This makes sure that admin fieldsets/layout settings are exported to the template. This makes sure that admin fieldsets/layout settings are exported to the template.
""" """
from django.contrib.admin.helpers import InlineAdminFormSet, InlineAdminForm import django
from django.contrib.admin.helpers import InlineAdminFormSet, InlineAdminForm, AdminField
from polymorphic.formsets import BasePolymorphicModelFormSet from polymorphic.formsets import BasePolymorphicModelFormSet
class InlinePolymorphicAdminForm(InlineAdminForm): class PolymorphicInlineAdminForm(InlineAdminForm):
""" """
Expose the admin configuration for a form Expose the admin configuration for a form
""" """
pass
class InlinePolymorphicAdminFormSet(InlineAdminFormSet): class PolymorphicInlineAdminFormSet(InlineAdminFormSet):
""" """
Internally used class to expose the formset in the template. Internally used class to expose the formset in the template.
""" """
@ -23,7 +23,7 @@ class InlinePolymorphicAdminFormSet(InlineAdminFormSet):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None) # Assigned later via PolymorphicInlineSupportMixin later. self.request = kwargs.pop('request', None) # Assigned later via PolymorphicInlineSupportMixin later.
self.obj = kwargs.pop('obj', None) self.obj = kwargs.pop('obj', None)
super(InlinePolymorphicAdminFormSet, self).__init__(*args, **kwargs) super(PolymorphicInlineAdminFormSet, self).__init__(*args, **kwargs)
def __iter__(self): def __iter__(self):
""" """
@ -35,7 +35,7 @@ class InlinePolymorphicAdminFormSet(InlineAdminFormSet):
child_inline = self.opts.get_child_inline_instance(model) child_inline = self.opts.get_child_inline_instance(model)
view_on_site_url = self.opts.get_view_on_site_url(original) view_on_site_url = self.opts.get_view_on_site_url(original)
yield InlinePolymorphicAdminForm( yield PolymorphicInlineAdminForm(
formset=self.formset, formset=self.formset,
form=form, form=form,
fieldsets=self.get_child_fieldsets(child_inline), fieldsets=self.get_child_fieldsets(child_inline),
@ -50,7 +50,7 @@ class InlinePolymorphicAdminFormSet(InlineAdminFormSet):
for form in self.formset.extra_forms + self.formset.empty_forms: for form in self.formset.extra_forms + self.formset.empty_forms:
model = form._meta.model model = form._meta.model
child_inline = self.opts.get_child_inline_instance(model) child_inline = self.opts.get_child_inline_instance(model)
yield InlinePolymorphicAdminForm( yield PolymorphicInlineAdminForm(
formset=self.formset, formset=self.formset,
form=form, form=form,
fieldsets=self.get_child_fieldsets(child_inline), fieldsets=self.get_child_fieldsets(child_inline),
@ -81,7 +81,7 @@ class PolymorphicInlineSupportMixin(object):
depending on the polymorphic type of the form instance. depending on the polymorphic type of the form instance.
This is achieved by overwriting :func:`get_inline_formsets` to return This is achieved by overwriting :func:`get_inline_formsets` to return
an :class:`InlinePolymorphicAdminFormSet` instead of a standard Django an :class:`PolymorphicInlineAdminFormSet` instead of a standard Django
:class:`~django.contrib.admin.helpers.InlineAdminFormSet` for the polymorphic formsets. :class:`~django.contrib.admin.helpers.InlineAdminFormSet` for the polymorphic formsets.
""" """
@ -98,7 +98,7 @@ class PolymorphicInlineSupportMixin(object):
if isinstance(admin_formset.formset, BasePolymorphicModelFormSet): if isinstance(admin_formset.formset, BasePolymorphicModelFormSet):
# This is a polymorphic formset, which belongs to our inline. # This is a polymorphic formset, which belongs to our inline.
# Downcast the admin wrapper that generates the form fields. # Downcast the admin wrapper that generates the form fields.
admin_formset.__class__ = InlinePolymorphicAdminFormSet admin_formset.__class__ = PolymorphicInlineAdminFormSet
admin_formset.request = request admin_formset.request = request
admin_formset.obj = obj admin_formset.obj = obj
return inline_admin_formsets return inline_admin_formsets

View File

@ -13,7 +13,7 @@ from polymorphic.formsets import polymorphic_child_forms_factory, BasePolymorphi
from polymorphic.formsets.utils import add_media from polymorphic.formsets.utils import add_media
class PolymorphicParentInlineModelAdmin(InlineModelAdmin): class PolymorphicInlineModelAdmin(InlineModelAdmin):
""" """
A polymorphic inline, where each formset row can be a different form. A polymorphic inline, where each formset row can be a different form.
@ -30,11 +30,11 @@ class PolymorphicParentInlineModelAdmin(InlineModelAdmin):
extra = 0 extra = 0
#: Inlines for all model sub types that can be displayed in this inline. #: Inlines for all model sub types that can be displayed in this inline.
#: Each row is a :class:`PolymorphicChildInlineModelAdmin` #: Each row is a :class:`PolymorphicInlineModelAdmin.Child`
child_inlines = () child_inlines = ()
def __init__(self, parent_model, admin_site): def __init__(self, parent_model, admin_site):
super(PolymorphicParentInlineModelAdmin, self).__init__(parent_model, admin_site) super(PolymorphicInlineModelAdmin, self).__init__(parent_model, admin_site)
# While the inline is created per request, the 'request' object is not known here. # While the inline is created per request, the 'request' object is not known here.
# Hence, creating all child inlines unconditionally, without checking permissions. # Hence, creating all child inlines unconditionally, without checking permissions.
@ -47,7 +47,7 @@ class PolymorphicParentInlineModelAdmin(InlineModelAdmin):
def get_child_inline_instances(self): def get_child_inline_instances(self):
""" """
:rtype List[PolymorphicChildInlineModelAdmin] :rtype List[PolymorphicInlineModelAdmin.Child]
""" """
instances = [] instances = []
for ChildInlineType in self.child_inlines: for ChildInlineType in self.child_inlines:
@ -58,7 +58,7 @@ class PolymorphicParentInlineModelAdmin(InlineModelAdmin):
""" """
Find the child inline for a given model. Find the child inline for a given model.
:rtype: PolymorphicChildInlineModelAdmin :rtype: PolymorphicInlineModelAdmin.Child
""" """
try: try:
return self._child_inlines_lookup[model] return self._child_inlines_lookup[model]
@ -74,7 +74,7 @@ class PolymorphicParentInlineModelAdmin(InlineModelAdmin):
:rtype: type :rtype: type
""" """
# Construct the FormSet class # Construct the FormSet class
FormSet = super(PolymorphicParentInlineModelAdmin, self).get_formset(request, obj=obj, **kwargs) FormSet = super(PolymorphicInlineModelAdmin, self).get_formset(request, obj=obj, **kwargs)
# Instead of completely redefining super().get_formset(), we use # Instead of completely redefining super().get_formset(), we use
# the regular inlineformset_factory(), and amend that with our extra bits. # the regular inlineformset_factory(), and amend that with our extra bits.
@ -115,7 +115,7 @@ class PolymorphicParentInlineModelAdmin(InlineModelAdmin):
# The media of the inline focuses on the admin settings, # The media of the inline focuses on the admin settings,
# whether to expose the scripts for filter_horizontal etc.. # whether to expose the scripts for filter_horizontal etc..
# The admin helper exposes the inline + formset media. # The admin helper exposes the inline + formset media.
base_media = super(PolymorphicParentInlineModelAdmin, self).media base_media = super(PolymorphicInlineModelAdmin, self).media
all_media = Media() all_media = Media()
add_media(all_media, base_media) add_media(all_media, base_media)
@ -127,87 +127,96 @@ class PolymorphicParentInlineModelAdmin(InlineModelAdmin):
if child_media._css != base_media._css and child_media._js != base_media._js: if child_media._css != base_media._css and child_media._js != base_media._js:
add_media(all_media, child_media) add_media(all_media, child_media)
add_media(all_media, self.polymorphic_media)
return all_media return all_media
class Child(InlineModelAdmin):
class PolymorphicChildInlineModelAdmin(InlineModelAdmin):
"""
The child inline; which allows configuring the admin options
for the child appearance.
Note that not all options will be honored by the parent, notably the formset options:
* :attr:`extra`
* :attr:`min_num`
* :attr:`max_num`
The model form options however, will all be read.
"""
formset_child = PolymorphicFormSetChild
extra = 0 # TODO: currently unused for the children.
def __init__(self, parent_inline):
self.parent_inline = parent_inline
super(PolymorphicChildInlineModelAdmin, self).__init__(parent_inline.parent_model, parent_inline.admin_site)
def get_formset(self, request, obj=None, **kwargs):
# The child inline is only used to construct the form,
# and allow to override the form field attributes.
# The formset is created by the parent inline.
raise RuntimeError("The child get_formset() is not used.")
def get_fields(self, request, obj=None):
if self.fields:
return self.fields
# Standard Django logic, use the form to determine the fields.
# The form needs to pass through all factory logic so all 'excludes' are set as well.
# Default Django does: form = self.get_formset(request, obj, fields=None).form
# Use 'fields=None' avoids recursion in the field autodetection.
form = self.get_formset_child(request, obj, fields=None).get_form()
return list(form.base_fields) + list(self.get_readonly_fields(request, obj))
def get_formset_child(self, request, obj=None, **kwargs):
""" """
Return the formset child that the parent inline can use to represent us. The child inline; which allows configuring the admin options
for the child appearance.
:rtype: PolymorphicFormSetChild Note that not all options will be honored by the parent, notably the formset options:
* :attr:`extra`
* :attr:`min_num`
* :attr:`max_num`
The model form options however, will all be read.
""" """
# Similar to the normal get_formset(), the caller may pass fields to override the defaults settings formset_child = PolymorphicFormSetChild
# in the inline. In Django's GenericInlineModelAdmin.get_formset() this is also used in the same way, extra = 0 # TODO: currently unused for the children.
# to make sure the 'exclude' also contains the GFK fields.
#
# Hence this code is almost identical to InlineModelAdmin.get_formset()
# and GenericInlineModelAdmin.get_formset()
#
# Transfer the local inline attributes to the formset child,
# this allows overriding settings.
if 'fields' in kwargs:
fields = kwargs.pop('fields')
else:
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
if self.exclude is None: def __init__(self, parent_inline):
exclude = [] self.parent_inline = parent_inline
else: super(PolymorphicInlineModelAdmin.Child, self).__init__(parent_inline.parent_model, parent_inline.admin_site)
exclude = list(self.exclude)
exclude.extend(self.get_readonly_fields(request, obj)) def get_formset(self, request, obj=None, **kwargs):
# The child inline is only used to construct the form,
# and allow to override the form field attributes.
# The formset is created by the parent inline.
raise RuntimeError("The child get_formset() is not used.")
if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude: def get_fields(self, request, obj=None):
# Take the custom ModelForm's Meta.exclude into account only if the if self.fields:
# InlineModelAdmin doesn't define its own. return self.fields
exclude.extend(self.form._meta.exclude)
#can_delete = self.can_delete and self.has_delete_permission(request, obj) # Standard Django logic, use the form to determine the fields.
defaults = { # The form needs to pass through all factory logic so all 'excludes' are set as well.
"form": self.form, # Default Django does: form = self.get_formset(request, obj, fields=None).form
"fields": fields, # Use 'fields=None' avoids recursion in the field autodetection.
"exclude": exclude or None, form = self.get_formset_child(request, obj, fields=None).get_form()
"formfield_callback": partial(self.formfield_for_dbfield, request=request), return list(form.base_fields) + list(self.get_readonly_fields(request, obj))
}
defaults.update(kwargs)
# This goes through the same logic that get_formset() calls def get_formset_child(self, request, obj=None, **kwargs):
# by passing the inline class attributes to modelform_factory() """
FormSetChildClass = self.formset_child Return the formset child that the parent inline can use to represent us.
return FormSetChildClass(self.model, **defaults)
:rtype: PolymorphicFormSetChild
"""
# Similar to the normal get_formset(), the caller may pass fields to override the defaults settings
# in the inline. In Django's GenericInlineModelAdmin.get_formset() this is also used in the same way,
# to make sure the 'exclude' also contains the GFK fields.
#
# Hence this code is almost identical to InlineModelAdmin.get_formset()
# and GenericInlineModelAdmin.get_formset()
#
# Transfer the local inline attributes to the formset child,
# this allows overriding settings.
if 'fields' in kwargs:
fields = kwargs.pop('fields')
else:
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
if self.exclude is None:
exclude = []
else:
exclude = list(self.exclude)
exclude.extend(self.get_readonly_fields(request, obj))
if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
# Take the custom ModelForm's Meta.exclude into account only if the
# InlineModelAdmin doesn't define its own.
exclude.extend(self.form._meta.exclude)
#can_delete = self.can_delete and self.has_delete_permission(request, obj)
defaults = {
"form": self.form,
"fields": fields,
"exclude": exclude or None,
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
}
defaults.update(kwargs)
# This goes through the same logic that get_formset() calls
# by passing the inline class attributes to modelform_factory()
FormSetChildClass = self.formset_child
return FormSetChildClass(self.model, **defaults)
class StackedPolymorphicInline(PolymorphicInlineModelAdmin):
"""
Stacked inline for django-polymorphic models.
Since tabular doesn't make much sense with changed fields, just offer this one.
"""
template = 'admin/polymorphic/edit_inline/stacked.html'

View File

@ -34,18 +34,18 @@ from .models import (
) )
from .generic import ( from .generic import (
# Can import generic here, as polymorphic already depends on the 'contenttypes' app. # Can import generic here, as polymorphic already depends on the 'contenttypes' app.
BasePolymorphicGenericInlineFormSet, BaseGenericPolymorphicInlineFormSet,
PolymorphicGenericFormSetChild, GenericPolymorphicFormSetChild,
polymorphic_generic_inlineformset_factory, generic_polymorphic_inlineformset_factory,
) )
__all__ = ( __all__ = (
'BasePolymorphicModelFormSet', 'BasePolymorphicModelFormSet',
'BasePolymorphicInlineFormSet', 'BasePolymorphicInlineFormSet',
'BasePolymorphicGenericInlineFormSet',
'PolymorphicFormSetChild', 'PolymorphicFormSetChild',
'PolymorphicGenericFormSetChild',
'polymorphic_inlineformset_factory', 'polymorphic_inlineformset_factory',
'polymorphic_generic_inlineformset_factory',
'polymorphic_child_forms_factory', 'polymorphic_child_forms_factory',
'BaseGenericPolymorphicInlineFormSet',
'GenericPolymorphicFormSetChild',
'generic_polymorphic_inlineformset_factory',
) )

View File

@ -7,14 +7,14 @@ from django.forms.models import ModelForm
from .models import BasePolymorphicModelFormSet, polymorphic_child_forms_factory, PolymorphicFormSetChild from .models import BasePolymorphicModelFormSet, polymorphic_child_forms_factory, PolymorphicFormSetChild
class PolymorphicGenericFormSetChild(PolymorphicFormSetChild): class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
""" """
Formset child for generic inlines Formset child for generic inlines
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.ct_field = kwargs.pop('ct_field', 'content_type') self.ct_field = kwargs.pop('ct_field', 'content_type')
self.fk_field = kwargs.pop('fk_field', 'object_id') self.fk_field = kwargs.pop('fk_field', 'object_id')
super(PolymorphicGenericFormSetChild, self).__init__(*args, **kwargs) super(GenericPolymorphicFormSetChild, self).__init__(*args, **kwargs)
def get_form(self, ct_field="content_type", fk_field="object_id", **kwargs): def get_form(self, ct_field="content_type", fk_field="object_id", **kwargs):
""" """
@ -42,17 +42,17 @@ class PolymorphicGenericFormSetChild(PolymorphicFormSetChild):
exclude.extend([ct_field.name, fk_field.name]) exclude.extend([ct_field.name, fk_field.name])
kwargs['exclude'] = exclude kwargs['exclude'] = exclude
return super(PolymorphicGenericFormSetChild, self).get_form(**kwargs) return super(GenericPolymorphicFormSetChild, self).get_form(**kwargs)
class BasePolymorphicGenericInlineFormSet(BaseGenericInlineFormSet, BasePolymorphicModelFormSet): class BaseGenericPolymorphicInlineFormSet(BaseGenericInlineFormSet, BasePolymorphicModelFormSet):
""" """
Polymorphic formset variation for inline generic formsets Polymorphic formset variation for inline generic formsets
""" """
def polymorphic_generic_inlineformset_factory(model, formset_children, form=ModelForm, def generic_polymorphic_inlineformset_factory(model, formset_children, form=ModelForm,
formset=BasePolymorphicGenericInlineFormSet, formset=BaseGenericPolymorphicInlineFormSet,
ct_field="content_type", fk_field="object_id", ct_field="content_type", fk_field="object_id",
# Base form # Base form
# TODO: should these fields be removed in favor of creating # TODO: should these fields be removed in favor of creating