Skip to content

Commit

Permalink
Fix issue with gfk inlines nested inside polymorphic child inlines
Browse files Browse the repository at this point in the history
  • Loading branch information
fdintino committed Apr 3, 2019
1 parent ce33e39 commit 25b2870
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 45 deletions.
3 changes: 2 additions & 1 deletion nested_admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
all_by_module['nested_admin.polymorphic'] = (
'NestedPolymorphicInlineAdminFormset', 'NestedPolymorphicInlineModelAdmin',
'NestedStackedPolymorphicInline', 'NestedPolymorphicInlineSupportMixin',
'NestedPolymorphicModelAdmin')
'NestedPolymorphicModelAdmin', 'NestedGenericPolymorphicInlineModelAdmin',
'NestedGenericStackedPolymorphicInline')

# modules that should be imported when accessed as attributes of nested_admin
attribute_modules = frozenset(['formsets', 'nested', 'polymorphic'])
Expand Down
2 changes: 1 addition & 1 deletion nested_admin/nested.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ def _create_formsets(self, request, obj, change):
form_obj = None
InlineFormSet = inline.get_formset(request, form_obj)

if has_polymorphic and form_obj:
if has_polymorphic and form_obj and hasattr(InlineFormSet, 'fk'):
rel_model = compat_rel_to(InlineFormSet.fk)
if not isinstance(form_obj, rel_model):
continue
Expand Down
66 changes: 36 additions & 30 deletions nested_admin/polymorphic.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,10 @@
from .nested import (
NestedModelAdminMixin,
NestedInlineModelAdminMixin, NestedGenericInlineModelAdminMixin,
NestedInlineAdminFormsetMixin)
NestedInlineAdminFormsetMixin, NestedInlineAdminFormset)


def get_base_polymorphic_models(child_model):
"""
First the first concrete model in the inheritance chain that inherited from the PolymorphicModel.
"""
models = []
for model in reversed(child_model.mro()):
if (isinstance(model, PolymorphicModelBase)
Expand Down Expand Up @@ -55,11 +52,6 @@ def get_compatible_parents(model):
for m in related_models:
compatibility_map[m] = get_base_polymorphic_models(m)
return compatibility_map
# base_models = get_base_polymorphic_models(child_model)
# compatible = set([])
# for m in base_models:
# compatible |= set(m.__subclasses__())
# return list(compatible)


def get_model_id(model_cls):
Expand Down Expand Up @@ -91,8 +83,8 @@ def __iter__(self):
obj_with_nesting_data = self.formset
formsets = getattr(obj_with_nesting_data, 'nested_formsets', None) or []
inlines = getattr(obj_with_nesting_data, 'nested_inlines', None) or []
form.inlines = self.model_admin.get_inline_formsets(self.request, formsets, inlines,
obj=obj, allow_nested=True)
form.inlines = self.model_admin.get_inline_formsets(self.request, formsets,
inlines, obj=obj, allow_nested=True)
for nested_inline in inline_admin_form.form.inlines:
for nested_form in nested_inline:
inline_admin_form.prepopulated_fields += nested_form.prepopulated_fields
Expand All @@ -107,7 +99,6 @@ def inline_formset_data(self):
else:
formset_fk_model = ''
parent_models = []
# compatible_parents = {}
compatible_parents = get_compatible_parents(self.formset.model)
sub_models = self.formset.model()._get_inheritance_relation_fields_and_models()
data['nestedOptions'].update({
Expand All @@ -118,26 +109,43 @@ def inline_formset_data(self):
get_model_id(k): [get_model_id(m) for m in v]
for k, v in compatible_parents.items()},
})
data['options'].update({
'childTypes': [
{
'type': get_model_id(model),
'name': force_text(model._meta.verbose_name),
} for model in self.formset.child_forms.keys()
],
})
if hasattr(self.formset, 'child_forms'):
data['options'].update({
'childTypes': [
{
'type': get_model_id(model),
'name': force_text(model._meta.verbose_name),
} for model in self.formset.child_forms.keys()
],
})
return json.dumps(data)


class NestedPolymorphicAdminFormsetHelperMixin(object):

@staticmethod
def inline_admin_formset_helper_cls(
inline, formset, fieldsets, prepopulated, readonly, *args, **kwargs):
if hasattr(formset, 'child_forms'):
cls = NestedPolymorphicInlineAdminFormset
else:
cls = NestedInlineAdminFormset
return cls(
inline, formset, fieldsets, prepopulated, readonly, *args, **kwargs)


class NestedPolymorphicInlineModelAdminMixin(
NestedPolymorphicAdminFormsetHelperMixin, NestedInlineModelAdminMixin):
pass


class NestedPolymorphicInlineModelAdmin(
NestedInlineModelAdminMixin, PolymorphicInlineModelAdmin):
NestedPolymorphicInlineModelAdminMixin, PolymorphicInlineModelAdmin):

formset = NestedBasePolymorphicInlineFormSet
inline_admin_formset_helper_cls = NestedPolymorphicInlineAdminFormset

class Child(NestedInlineModelAdminMixin, PolymorphicInlineModelAdmin.Child):
class Child(NestedPolymorphicInlineModelAdminMixin, PolymorphicInlineModelAdmin.Child):
formset = NestedBasePolymorphicInlineFormSet
inline_admin_formset_helper_cls = NestedPolymorphicInlineAdminFormset

def get_formset(self, request, obj=None, **kwargs):
FormSet = BaseFormSet = kwargs.pop('formset', self.formset)
Expand All @@ -147,7 +155,8 @@ class FormSet(BaseFormSet):
sortable_field_name = self.sortable_field_name

kwargs['formset'] = FormSet
return super(PolymorphicInlineModelAdmin.Child, self).get_formset(request, obj, **kwargs)
return super(PolymorphicInlineModelAdmin.Child, self).get_formset(
request, obj, **kwargs)


class NestedStackedPolymorphicInline(NestedPolymorphicInlineModelAdmin):
Expand All @@ -166,11 +175,9 @@ class NestedGenericPolymorphicInlineModelAdmin(
NestedGenericInlineModelAdminMixin, GenericPolymorphicInlineModelAdmin):

formset = NestedBaseGenericPolymorphicInlineFormSet
inline_admin_formset_helper_cls = NestedPolymorphicInlineAdminFormset

class Child(NestedGenericInlineModelAdminMixin, GenericPolymorphicInlineModelAdmin.Child):
formset = NestedBaseGenericPolymorphicInlineFormSet
inline_admin_formset_helper_cls = NestedPolymorphicInlineAdminFormset

def get_formset(self, request, obj=None, **kwargs):
FormSet = BaseFormSet = kwargs.pop('formset', self.formset)
Expand All @@ -193,9 +200,8 @@ class NestedGenericStackedPolymorphicInline(NestedGenericPolymorphicInlineModelA


class NestedPolymorphicInlineSupportMixin(
PolymorphicInlineSupportMixin, NestedModelAdminMixin):

inline_admin_formset_helper_cls = NestedPolymorphicInlineAdminFormset
NestedPolymorphicAdminFormsetHelperMixin, PolymorphicInlineSupportMixin,
NestedModelAdminMixin):

def get_inline_formsets(self, request, formsets, inline_instances, obj=None, *args, **kwargs):
return super(PolymorphicInlineSupportMixin, self).get_inline_formsets(
Expand Down
5 changes: 3 additions & 2 deletions nested_admin/static/nested_admin/dist/nested_admin.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion nested_admin/static/nested_admin/dist/nested_admin.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion nested_admin/static/nested_admin/dist/nested_admin.min.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,9 @@ class DjangoFormset {
const compatibleParents = this.$inline.djnData('compatibleParents') || {};
$form.find('> .djn-group').each((i, el) => {
const fkModel = $(el).djnData('formsetFkModel');
const compatibleModels = compatibleParents[ctype] || [];
if (fkModel !== ctype && compatibleModels.indexOf(fkModel) === -1) {
const compatModels = compatibleParents[ctype] || [];
const isPolymorphic = !!($(el).data('inlineFormset').options.childTypes);
if (isPolymorphic && fkModel !== ctype && compatModels.indexOf(fkModel) === -1) {
el.parentNode.removeChild(el);
}
});
Expand Down
24 changes: 21 additions & 3 deletions nested_admin/tests/nested_polymorphic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
from nested_admin.tests.base import BaseNestedAdminTestCase
from nested_admin.tests.utils import xpath_item, xpath_cls, is_sequence, is_integer, is_str

try:
from polymorphic.models import PolymorphicModel
except:
# Temporary until django-polymorphic supports django 3.0
if django.VERSION < (3, 0):
raise
else:
class PolymorphicModel(object):
pass


class BaseNestedPolymorphicTestCase(BaseNestedAdminTestCase):

Expand Down Expand Up @@ -130,7 +140,10 @@ def get_item(self, indexes):
model_id, item_index = indexes[-1]
app_label, model_name = model_id.split('-')
model_cls = apps.get_model(app_label, model_name)
base_model_cls = get_base_polymorphic_model(model_cls)
if issubclass(model_cls, PolymorphicModel):
base_model_cls = get_base_polymorphic_model(model_cls)
else:
base_model_cls = model_cls
base_model_id = "%s-%s" % (
base_model_cls._meta.app_label, base_model_cls._meta.model_name)
group_indexes.append(base_model_id)
Expand All @@ -145,7 +158,10 @@ def get_item(self, indexes):

def add_inline(self, indexes=None, model=None, **kwargs):
model_name = "%s-%s" % (model._meta.app_label, model._meta.model_name)
base_model = get_base_polymorphic_model(model)
if issubclass(model, PolymorphicModel):
base_model = get_base_polymorphic_model(model)
else:
base_model = model
base_model_identifier = "%s-%s" % (
base_model._meta.app_label, base_model._meta.model_name)

Expand All @@ -170,7 +186,9 @@ def add_inline(self, indexes=None, model=None, **kwargs):
add_link_selector = "return $('.polymorphic-type-menu:visible [data-type=\"%s\"]')[0]" % (
model_name)
poly_add_link = self.selenium.execute_script(add_link_selector)
poly_add_link.click()

if poly_add_link:
poly_add_link.click()

indexes = self._normalize_indexes(indexes)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from .models import (
TopLevel, LevelOne, LevelOneA, LevelOneB, LevelTwo, LevelTwoC, LevelTwoD,
ALevelTwo, ALevelTwoC, ALevelTwoD, BLevelTwo, BLevelTwoC, BLevelTwoD)
ALevelTwo, ALevelTwoC, ALevelTwoD, BLevelTwo, BLevelTwoC, BLevelTwoD, GFKX)


class LevelTwoInline(nested_admin.NestedStackedPolymorphicInline):
Expand All @@ -21,6 +21,14 @@ class LevelTwoDInline(nested_admin.NestedStackedPolymorphicInline.Child):
child_inlines = (LevelTwoCInline, LevelTwoDInline)



class GFKXInline(nested_admin.NestedGenericStackedInline):
model = GFKX
extra = 1
sortable_field_name = "position"
inline_classes = ("collapse", "open", "grp-collapse", "grp-open", )


class ALevelTwoInline(nested_admin.NestedStackedPolymorphicInline):
model = ALevelTwo
extra = 0
Expand All @@ -32,6 +40,7 @@ class ALevelTwoCInline(nested_admin.NestedStackedPolymorphicInline.Child):

class ALevelTwoDInline(nested_admin.NestedStackedPolymorphicInline.Child):
model = ALevelTwoD
inlines = [GFKXInline]

child_inlines = (ALevelTwoCInline, ALevelTwoDInline)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import unicode_literals

import django
from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import ForeignKey, CASCADE
from nested_admin.tests.compat import python_2_unicode_compatible
Expand Down Expand Up @@ -104,8 +106,27 @@ class ALevelTwoC(ALevelTwo):
ac = models.CharField(max_length=200)


@python_2_unicode_compatible
class GFKX(models.Model):
name = models.CharField(max_length=255)
position = models.PositiveIntegerField()
content_type = ForeignKey(ContentType, on_delete=CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()

class Meta:
ordering = ['object_id', 'position']

def __str__(self):
parts = ["%s[%d]" % (self.name, self.position)]
if self.content_object:
parts.insert(0, "%s" % self.content_object)
return "/".join(parts)


class ALevelTwoD(ALevelTwo):
ad = models.CharField(max_length=200)
x_set = GenericRelation(GFKX)


@python_2_unicode_compatible
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import time
from unittest import SkipTest
from django.test import TestCase

from .models import (
# LevelOneA, LevelTwoC, LevelTwoD, ALevelTwo, ALevelTwoC, ALevelTwoD, BLevelTwo, LevelTwoC
TopLevel, LevelOne, LevelOneB, LevelTwo, BLevelTwoC, BLevelTwoD)
# ALevelTwoC, LevelTwoC, LevelTwoD, ALevelTwo, BLevelTwo, LevelTwoC,
LevelOneA, ALevelTwoD, TopLevel, LevelOne, LevelOneB,
LevelTwo, BLevelTwoC, BLevelTwoD, GFKX)


try:
from nested_admin.tests.nested_polymorphic.base import BaseNestedPolymorphicTestCase
Expand Down Expand Up @@ -87,3 +90,34 @@ def test_add_level_two_to_empty_drag_and_drop(self):
self.assertIsInstance(b_children[1], BLevelTwoC)
self.assertEqual(b_children[1].name, 'y')
self.assertEqual(b_children[1].bc, 'bcy')

def test_add_level_two_child_gfk_inline(self):
obj = self.root_model.objects.create(name='test')
self.load_admin(obj)
idx_a = self.add_inline(model=LevelOneA, name='x', a='ax')
idx_a_d = self.add_inline(idx_a, model=ALevelTwoD, name='y', ad='ady')
self.add_inline(idx_a_d, model=GFKX)
gfk_name_selector = (
'#id_children-0-a_set-0-test_polymorphic_std-gfkx-content_type-object_id-0-name')
with self.available_selector(gfk_name_selector) as el:
el.clear()
el.send_keys('Z')

self.save_form()

children = obj.children.all()
self.assertEqual(len(children), 1)
self.assertIsInstance(children[0], LevelOneA)
a = children[0]
self.assertEqual(a.name, 'x')
self.assertEqual(a.a, 'ax')

a_children = a.a_set.all()
self.assertEqual(len(a_children), 1)
self.assertIsInstance(a_children[0], ALevelTwoD)
self.assertEqual(a_children[0].name, 'y')
self.assertEqual(a_children[0].ad, 'ady')

gfk_children = a_children[0].x_set.all()
self.assertEqual(len(gfk_children), 1)
self.assertEqual(gfk_children[0].name, 'Z')

0 comments on commit 25b2870

Please sign in to comment.