Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-discover child models and inlines #582

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions polymorphic/admin/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def __iter__(self):
for form in self.formset.extra_forms + self.formset.empty_forms:
model = form._meta.model
child_inline = self.opts.get_child_inline_instance(model)

yield PolymorphicInlineAdminForm(
formset=self.formset,
form=form,
Expand Down Expand Up @@ -141,3 +142,22 @@ def get_inline_formsets(self, request, formsets, inline_instances, obj=None, *ar
admin_formset.request = request
admin_formset.obj = obj
return inline_admin_formsets


def get_leaf_subclasses(cls, exclude=None):
"Get leaf subclasses of `cls` class"

result = []

subclasses = cls.__subclasses__()

if subclasses:
for subclass in subclasses:
result.extend(get_leaf_subclasses(subclass, exclude))
elif not (
(hasattr(cls, '_meta') and cls._meta.abstract) or
(exclude and cls in exclude)
):
result.append(cls)

return result
42 changes: 39 additions & 3 deletions polymorphic/admin/inlines.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
)
from polymorphic.formsets.utils import add_media

from .helpers import PolymorphicInlineSupportMixin
from .helpers import PolymorphicInlineSupportMixin, get_leaf_subclasses


class PolymorphicInlineModelAdmin(InlineModelAdmin):
Expand Down Expand Up @@ -53,7 +53,8 @@ class PolymorphicInlineModelAdmin(InlineModelAdmin):

#: Inlines for all model sub types that can be displayed in this inline.
#: Each row is a :class:`PolymorphicInlineModelAdmin.Child`
piranna marked this conversation as resolved.
Show resolved Hide resolved
child_inlines = ()
child_inlines = None
exclude_childs = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
exclude_childs = None
exclude_children = None

This also needs a doc comment to go along with it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you propose the change of childs for children? childs is used everywhere else in the source code...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where can you find childs? I (or git grep) certainly can't:

~/b/django-polymorphic (master) $ git grep children | wc -l
      70
~/b/django-polymorphic (master) $ git grep childs | wc -l
       0


def __init__(self, parent_model, admin_site):
super().__init__(parent_model, admin_site)
Expand All @@ -77,12 +78,47 @@ def __init__(self, parent_model, admin_site):
for child_inline in self.child_inline_instances:
self._child_inlines_lookup[child_inline.model] = child_inline

def get_child_inlines(self):
"""
Return the derived inline classes which this admin should handle

This should return a list of tuples, exactly like
:attr:`child_inlines` is.

The inline classes can be retrieved as
``base_inline.__subclasses__()``, a setting in a config file, or
a query of a plugin registration system at your option
"""
if self.child_inlines is not None:
return self.child_inlines

child_inlines = get_leaf_subclasses(
PolymorphicInlineModelAdmin.Child, self.exclude_childs
)
child_inlines = tuple(
inline
for inline in child_inlines
if (
inline.model is not None and
issubclass(inline.model, self.model)
)
)

if child_inlines:
return child_inlines

raise ImproperlyConfigured(
f"No child inlines found for '{self.model.__name__}', please "
"define the 'child_inlines' attribute or overwrite the "
"'get_child_inlines()' method."
)

def get_child_inline_instances(self):
"""
:rtype List[PolymorphicInlineModelAdmin.Child]
"""
instances = []
for ChildInlineType in self.child_inlines:
for ChildInlineType in self.get_child_inlines():
instances.append(ChildInlineType(parent_inline=self))
return instances

Expand Down
35 changes: 28 additions & 7 deletions polymorphic/admin/parentadmin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from polymorphic.utils import get_base_polymorphic_model

from .forms import PolymorphicModelChoiceForm
from .helpers import get_leaf_subclasses


class RegistrationClosed(RuntimeError):
Expand Down Expand Up @@ -50,6 +51,7 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):

#: The child models that should be displayed
child_models = None
exclude_childs = None
piranna marked this conversation as resolved.
Show resolved Hide resolved

#: Whether the list should be polymorphic too, leave to ``False`` to optimize
polymorphic_list = False
Expand Down Expand Up @@ -109,24 +111,43 @@ def register_child(self, model, model_admin):
def get_child_models(self):
"""
Return the derived model classes which this admin should handle.
This should return a list of tuples, exactly like :attr:`child_models` is.

The model classes can be retrieved as ``base_model.__subclasses__()``,
a setting in a config file, or a query of a plugin registration system at your option
This should return a list of tuples, exactly like
:attr:`child_models` is.

The model classes can be retrieved as
``base_model.__subclasses__()``, a setting in a config file, or
a query of a plugin registration system at your option
"""
if self.child_models is None:
raise NotImplementedError("Implement get_child_models() or child_models")
if self.child_models is not None:
return self.child_models

child_models = get_leaf_subclasses(
self.base_model, self.exclude_childs
)

return self.child_models
if child_models:
return child_models

raise ImproperlyConfigured(
f"No child models found for '{self.base_model.__name__}', please "
"define the 'child_models' attribute or overwrite the "
"'get_child_models' method."
)

def get_child_type_choices(self, request, action):
"""
Return a list of polymorphic types for which the user has the permission to perform the given action.
"""
self._lazy_setup()

child_models = self._child_models
if not child_models:
raise ImproperlyConfigured("No child models are available.")

choices = []
content_types = ContentType.objects.get_for_models(
*self.get_child_models(), for_concrete_models=False
*child_models, for_concrete_models=False
)

for model, ct in content_types.items():
Expand Down