Skip to content

Commit

Permalink
fix: max_num show/hide bug with recursively nested models
Browse files Browse the repository at this point in the history
fixes #230
  • Loading branch information
fdintino committed Nov 14, 2022
1 parent 54f469a commit 5e45358
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 34 deletions.
8 changes: 7 additions & 1 deletion nested_admin/nested.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,13 @@ def inline_formset_data(self):
@property
def handler_classes(self):
classes = set(getattr(self.opts, "handler_classes", None) or [])
return tuple(classes | {"djn-model-%s" % self.inline_model_id})
return tuple(
classes
| {
"djn-model-%s" % self.inline_model_id,
"djn-level-%s" % getattr(self.formset, "nesting_depth", 0),
}
)


class NestedBaseInlineAdminFormSet(helpers.InlineAdminFormSet):
Expand Down
12 changes: 7 additions & 5 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 @@ -24,15 +24,15 @@ class DjangoFormset {
this._$template = $("#" + this.prefix + "-empty");

var inlineModelClassName = this.$inline.djnData("inlineModel");
const nestingLevel = this.$inline.djnData("nestingLevel");
const handlerSelector = `.djn-model-${inlineModelClassName}.djn-level-${nestingLevel}`;

this.opts = $.extend({}, this.opts, {
childTypes: this.$inline.data("inlineFormset").options.childTypes,
formsetFkModel: this.$inline.djnData("formsetFkModel"),
addButtonSelector: ".djn-add-handler.djn-model-" + inlineModelClassName,
removeButtonSelector:
".djn-remove-handler.djn-model-" + inlineModelClassName,
deleteButtonSelector:
".djn-delete-handler.djn-model-" + inlineModelClassName,
addButtonSelector: ".djn-add-handler" + handlerSelector,
removeButtonSelector: ".djn-remove-handler" + handlerSelector,
deleteButtonSelector: ".djn-delete-handler" + handlerSelector,
formClass:
"dynamic-form grp-dynamic-form djn-dynamic-form-" +
inlineModelClassName,
Expand Down Expand Up @@ -209,7 +209,7 @@ class DjangoFormset {
if (maxForms - totalForms >= 0) {
this.$inline
.find(this.opts.addButtonSelector)
.parents(".djn-add-item")
.parent(".djn-add-item,li")
.show();
}

Expand Down Expand Up @@ -400,7 +400,7 @@ class DjangoFormset {
if (maxForms - (index + 1) <= 0) {
this.$inline
.find(this.opts.addButtonSelector)
.parents(".djn-add-item")
.parent(".djn-add-item,li")
.hide();
}

Expand Down
32 changes: 16 additions & 16 deletions nested_admin/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,12 @@ def add_inline(self, indexes=None, name=None, slug=None):
indexes = self._normalize_indexes(indexes, is_group=True)
new_index = self.get_num_inlines(indexes)
model_name = indexes[-1]
add_selector = "#{} .djn-add-item a.djn-add-handler.djn-model-{}".format(
self.get_group(indexes).get_attribute("id"), model_name
add_selector = (
"#{} .djn-add-item a.djn-add-handler.djn-model-{}.djn-level-{}".format(
self.get_group(indexes).get_attribute("id"),
model_name,
len(indexes),
)
)
with self.clickable_selector(add_selector) as el:
self.click(el)
Expand All @@ -392,8 +396,8 @@ def add_inline(self, indexes=None, name=None, slug=None):
def remove_inline(self, indexes):
indexes = self._normalize_indexes(indexes)
model_name = indexes[-1][0]
remove_selector = "#{} .djn-remove-handler.djn-model-{}".format(
self.get_item(indexes).get_attribute("id"), model_name
remove_selector = "#{} .djn-remove-handler.djn-model-{}.djn-level-{}".format(
self.get_item(indexes).get_attribute("id"), model_name, len(indexes)
)
with self.clickable_selector(remove_selector) as el:
self.click(el)
Expand All @@ -402,33 +406,29 @@ def delete_inline(self, indexes):
indexes = self._normalize_indexes(indexes)
model_name = indexes[-1][0]
item_id = self.get_item(indexes).get_attribute("id")
delete_selector = "#{} .djn-delete-handler.djn-model-{}".format(
item_id, model_name
delete_selector = "#{} .djn-delete-handler.djn-model-{}.djn-level-{}".format(
item_id, model_name, len(indexes)
)
with self.clickable_selector(delete_selector) as el:
self.click(el)
if self.has_grappelli:
undelete_selector = (
"#{}.grp-predelete .grp-delete-handler.djn-model-{}".format(
item_id, model_name
)
undelete_selector = "#{}.grp-predelete .grp-delete-handler.djn-model-{}.djn-level-{}".format(
item_id, model_name, len(indexes)
)
self.wait_until_clickable_selector(undelete_selector)

def undelete_inline(self, indexes):
indexes = self._normalize_indexes(indexes)
model_name = indexes[-1][0]
item_id = self.get_item(indexes).get_attribute("id")
undelete_selector = "#{} .djn-delete-handler.djn-model-{}".format(
item_id, model_name
undelete_selector = "#{} .djn-delete-handler.djn-model-{}.djn-level-{}".format(
item_id, model_name, len(indexes)
)
with self.clickable_selector(undelete_selector) as el:
self.click(el)
if self.has_grappelli:
delete_selector = (
"#{}:not(.grp-predelete) .grp-delete-handler.djn-model-{}".format(
item_id, model_name
)
delete_selector = "#{}:not(.grp-predelete) .grp-delete-handler.djn-model-{}.djn-level-{}".format(
item_id, model_name, len(indexes)
)
self.wait_until_clickable_selector(delete_selector)

Expand Down
8 changes: 6 additions & 2 deletions nested_admin/tests/nested_polymorphic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ def add_inline(self, indexes=None, model=None, **kwargs):
)

if indexes:
level = len(self._normalize_indexes(indexes, is_group=True))
item = self.get_item(indexes)
ctx_id = item.get_attribute("id")
group_el = self.selenium.execute_script(
Expand All @@ -204,11 +205,14 @@ def add_inline(self, indexes=None, model=None, **kwargs):
else:
group_el = self.get_group([base_model_identifier])
ctx_id = group_el.get_attribute("id")
level = 1

error_desc = "{} in inline {}".format(model, indexes)

add_selector = "#{} .djn-add-item a.djn-add-handler.djn-model-{}".format(
ctx_id, base_model_identifier
add_selector = (
"#{} .djn-add-item a.djn-add-handler.djn-model-{}.djn-level-{}".format(
ctx_id, base_model_identifier, level
)
)
add_els = self.selenium.find_elements_by_css_selector(add_selector)
self.assertNotEqual(
Expand Down
Empty file.
26 changes: 26 additions & 0 deletions nested_admin/tests/nested_recursive/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from django.contrib import admin
import nested_admin
from .models import MenuItem


class Level2Inline(nested_admin.NestedTabularInline):
model = MenuItem
extra = 0
min_num = 0
title = "Level 2"


class Level1Inline(nested_admin.NestedTabularInline):
model = MenuItem
inlines = [Level2Inline]
extra = 0
min_num = 1
max_num = 2
title = "Level 1"


@admin.register(MenuItem)
class ParentAdmin(nested_admin.NestedModelAdmin):
inlines = [Level1Inline]
title = "Menu"
fields = ["label"]
16 changes: 16 additions & 0 deletions nested_admin/tests/nested_recursive/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.db import models
from django.db.models import ForeignKey, CASCADE


class MenuItem(models.Model):
label = models.CharField(max_length=255)
parent = ForeignKey(
"MenuItem", related_name="children", on_delete=CASCADE, null=True
)
position = models.PositiveIntegerField(default=0)

class Meta:
ordering = ["position"]

def __str__(self):
return self.name
42 changes: 42 additions & 0 deletions nested_admin/tests/nested_recursive/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# from unittest import SkipTest

# from django.conf import settings

from nested_admin.tests.base import BaseNestedAdminTestCase
from .models import MenuItem


class TestRecursiveNesting(BaseNestedAdminTestCase):

root_model = MenuItem
nested_models = (MenuItem, MenuItem)

def test_max_num_nested_add_handlers_hide(self):
self.load_admin(self.root_model)
level1_add_handler = self.selenium.execute_script(
"""
return django.jQuery(
'.djn-add-handler.djn-model-nested_recursive-menuitem.djn-level-1'
)[0]"""
)
level2_add_handler = self.selenium.execute_script(
"""
return django.jQuery(
'.djn-add-handler.djn-model-nested_recursive-menuitem.djn-level-2'
)[0]"""
)
self.add_inline()
self.wait_until_element_is(
level1_add_handler,
":not(:visible)",
"handler did not hide after max_num reached",
)
assert (
level2_add_handler.is_displayed() is True
), "level2 add handler should not be hidden"
self.remove_inline([0])
self.wait_until_element_is(
level1_add_handler,
":visible",
"handler did not unhide after max_num inline removed",
)

0 comments on commit 5e45358

Please sign in to comment.