Skip to content

Commit f2d58d6

Browse files
authored
Merge pull request jazzband#719 from prauscher/fix-426
Fix ordering of stacked inline admin forms on add.
2 parents cd3a4da + 1545d85 commit f2d58d6

File tree

3 files changed

+83
-15
lines changed

3 files changed

+83
-15
lines changed

src/polymorphic/static/polymorphic/js/polymorphic_inlines.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,18 @@
118118
// last child element of the form's container:
119119
row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="#">' + options.deleteText + "</a></span>");
120120
}
121+
let totForms = parseInt(totalForms.val(), 10);
121122
row.find("*").each(function() {
122-
updateElementIndex(this, options.prefix, totalForms.val());
123+
updateElementIndex(this, options.prefix, totForms);
124+
});
125+
row.find("h3 span.inline_label").each(function () {
126+
$(this).text($(this).text().replace(/##/g, `#${totForms+1}`));
123127
});
124128
// Insert the new form when it has been fully edited
125-
row.insertBefore($(template));
129+
const firstTemplate = $("#" + options.childTypes[0].type + "-empty");
130+
row.insertBefore($(firstTemplate));
126131
// Update number of total forms
127-
$(totalForms).val(parseInt(totalForms.val(), 10) + 1);
132+
$(totalForms).val(totForms+1);
128133
nextIndex += 1;
129134
// Hide add button in case we've hit the max, except we want to add infinitely
130135
if ((maxForms.val() !== '') && (maxForms.val() - totalForms.val()) <= 0) {
@@ -161,6 +166,9 @@
161166
for (i = 0, formCount = forms.length; i < formCount; i++) {
162167
updateElementIndex($(forms).get(i), options.prefix, i);
163168
$(forms.get(i)).find("*").each(updateElementCallback);
169+
$(forms.get(i)).find("h3 span.inline_label").each(function () {
170+
$(this).text($(this).text().replace(/#\d+/g, `#${i+1}`));
171+
});
164172
}
165173
});
166174
// If a post-add callback was supplied, call it with the added form:

src/polymorphic/templates/admin/polymorphic/edit_inline/stacked.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
1515
id="{% if inline_admin_form.original.pk %}{{ inline_admin_formset.formset.prefix }}-{{ forloop.counter0 }}{% else %}{{ inline_admin_form.model_admin.opts.model_name }}-empty{% endif %}">
1616

1717
<h3><b>{{ inline_admin_form.model_admin.opts.verbose_name|capfirst }}:</b>&nbsp;<span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %} <a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="inlinechangelink">{% trans "Change" %}</a>{% endif %}
18-
{% else %}#{{ forloop.counter }}{% endif %}</span>
18+
{% else %}##{% endif %}</span>
1919
{% if inline_admin_form.show_url %}<a href="{{ inline_admin_form.absolute_url }}">{% trans "View on site" %}</a>{% endif %}
2020
{% if inline_admin_form.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
2121
</h3>

src/polymorphic/tests/test_admin.py

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from time import sleep
23
import pytest
34
from django.urls import reverse
45
from django.contrib.auth import get_user_model
@@ -203,22 +204,19 @@ def setUpClass(cls):
203204
cls.playwright = sync_playwright().start()
204205
cls.browser = cls.playwright.chromium.launch(headless=cls.HEADLESS)
205206

206-
cls.admin = get_user_model().objects.create_superuser(
207-
username=cls.admin_username, email="admin@example.com", password=cls.admin_password
208-
)
209-
210207
@classmethod
211208
def tearDownClass(cls):
212209
"""Clean up Playwright instance after tests."""
213210
cls.browser.close()
214211
cls.playwright.stop()
215-
if cls.admin:
216-
cls.admin.delete()
217212
super().tearDownClass()
218213
del os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"]
219214

220215
def setUp(self):
221216
"""Create an admin user before running tests."""
217+
self.admin = get_user_model().objects.create_superuser(
218+
username=self.admin_username, email="admin@example.com", password=self.admin_password
219+
)
222220
self.page = self.browser.new_page()
223221
# Log in to the Django admin
224222
self.page.goto(f"{self.live_server_url}/admin/login/")
@@ -235,13 +233,10 @@ def tearDown(self):
235233

236234

237235
class StackedInlineTests(_GenericAdminFormTest):
238-
def setUp(self):
239-
super().setUp()
240-
for name in ["Brian", "Alice", "Emma", "Anna"]:
241-
PlainA.objects.create(field1=name)
242-
243236
def test_admin_inline_add_autocomplete(self):
244237
# https://github.com/jazzband/django-polymorphic/issues/546
238+
for name in ["Brian", "Alice", "Emma", "Anna"]:
239+
PlainA.objects.create(field1=name)
245240
self.page.goto(self.add_url(InlineParent))
246241
self.page.fill("input[name='title']", "Parent 1")
247242
with self.page.expect_navigation(timeout=10000) as nav_info:
@@ -283,6 +278,71 @@ def test_admin_inline_add_autocomplete(self):
283278
suggestions = self.page.locator("ul.select2-results__options > li").all_inner_texts()
284279
assert suggestions == ["Brian"]
285280

281+
def test_inline_form_ordering_and_removal(self):
282+
"""
283+
Test that the javascript places the inline forms in the correct order on
284+
repeated adds without a save.
285+
286+
https://github.com/jazzband/django-polymorphic/issues/426
287+
"""
288+
self.page.goto(self.add_url(InlineParent))
289+
290+
polymorphic_menu = self.page.locator(
291+
"div.polymorphic-add-choice div.polymorphic-type-menu"
292+
)
293+
294+
self.page.click("div.polymorphic-add-choice a")
295+
polymorphic_menu.wait_for(state="visible")
296+
self.page.click("div.polymorphic-type-menu a[data-type='inlinemodelb']")
297+
polymorphic_menu.wait_for(state="hidden")
298+
self.page.click("div.polymorphic-add-choice a")
299+
polymorphic_menu.wait_for(state="visible")
300+
self.page.click("div.polymorphic-type-menu a[data-type='inlinemodela']")
301+
polymorphic_menu.wait_for(state="hidden")
302+
self.page.click("div.polymorphic-add-choice a")
303+
polymorphic_menu.wait_for(state="visible")
304+
self.page.click("div.polymorphic-type-menu a[data-type='inlinemodela']")
305+
polymorphic_menu.wait_for(state="hidden")
306+
self.page.click("div.polymorphic-add-choice a")
307+
polymorphic_menu.wait_for(state="visible")
308+
self.page.click("div.polymorphic-type-menu a[data-type='inlinemodelb']")
309+
polymorphic_menu.wait_for(state="hidden")
310+
311+
inline0 = self.page.locator("div#inline_children-0")
312+
inline1 = self.page.locator("div#inline_children-1")
313+
inline2 = self.page.locator("div#inline_children-2")
314+
inline3 = self.page.locator("div#inline_children-3")
315+
316+
inline0.wait_for(state="visible")
317+
inline1.wait_for(state="visible")
318+
inline2.wait_for(state="visible")
319+
inline3.wait_for(state="visible")
320+
321+
assert "model b" in inline0.inner_text() and "#1" in inline0.inner_text()
322+
assert "model a" in inline1.inner_text() and "#2" in inline1.inner_text()
323+
assert "model a" in inline2.inner_text() and "#3" in inline2.inner_text()
324+
assert "model b" in inline3.inner_text() and "#4" in inline3.inner_text()
325+
326+
# Now remove inline 2 and check the numbering is correct
327+
inline1.locator("a.inline-deletelink").click()
328+
# the ids are updated - so we expect the last div id to be removed
329+
inline3.wait_for(state="detached")
330+
assert "model b" in inline0.inner_text() and "#1" in inline0.inner_text()
331+
assert "model a" in inline1.inner_text() and "#2" in inline1.inner_text()
332+
assert "model b" in inline2.inner_text() and "#3" in inline2.inner_text()
333+
334+
inline0.locator("a.inline-deletelink").click()
335+
inline2.wait_for(state="detached")
336+
assert "model a" in inline0.inner_text() and "#1" in inline0.inner_text()
337+
assert "model b" in inline1.inner_text() and "#2" in inline1.inner_text()
338+
339+
inline1.locator("a.inline-deletelink").click()
340+
inline1.wait_for(state="detached")
341+
assert "model a" in inline0.inner_text() and "#1" in inline0.inner_text()
342+
343+
inline0.locator("a.inline-deletelink").click()
344+
inline0.wait_for(state="detached")
345+
286346

287347
class PolymorphicFormTests(_GenericAdminFormTest):
288348
def test_admin_polymorphic_add(self):

0 commit comments

Comments
 (0)