fix issue with __prefix__ in nested inline panel

changed following:
change only first __prefix__ when multiple __prefix__ are present in
same name when orderable and blocks are nested

Co-authored-by: anirudhvs <54744932+anirudhvs@users.noreply.github.com>
Co-authored-by: CaptainIRS <36656347+CaptainIRS@users.noreply.github.com>

- fixes #7587
- fixes #5770
pull/7596/head
indreshp135 2021-10-10 17:41:17 +05:30 zatwierdzone przez LB (Ben Johnston)
rodzic 6659c286ce
commit 6e90c877ec
7 zmienionych plików z 252 dodań i 2 usunięć

Wyświetl plik

@ -68,6 +68,7 @@ Changelog
* Fix: CSS build scripts now output to the correct directory paths on Windows (Vince Salvino)
* Fix: Capture log output from style fallback to avoid noise in unit tests (Matt Westcott)
* Fix: Switch widgets on/off states are now visually different for high-contrast mode users (Sakshi Uppoor)
* Fix: Nested InlinePanel usage no longer fails to save when creating two or more items (Indresh P, Rinish Sam)
2.14.1 (12.08.2021)

Wyświetl plik

@ -545,6 +545,8 @@ Contributors
* Joe Howard
* Jochen Wersdörfer
* Sakshi Uppoor
* Indresh P
* Rinish Sam
Translators
===========

Wyświetl plik

@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`buildExpandingFormset should add an expanded item if the add button is not disabled 1`] = `
<li
data-contentpath-disabled=""
data-inline-panel-child=""
id="inline_child_form_fields-2"
>
<input
id="id_form_fields-2-label"
name="form_fields-2-label"
type="text"
/>
<input
id="id_form_fields-2-id"
name="form_fields-2-id"
type="hidden"
/>
<input
id="id_form_fields-2-DELETE"
name="form_fields-2-DELETE"
type="hidden"
/>
</li>
`;

Wyświetl plik

@ -0,0 +1,213 @@
/* global buildExpandingFormset */
import $ from 'jquery';
window.$ = $;
import './expanding_formset';
describe('buildExpandingFormset', () => {
it('exposes module as global', () => {
expect(window.buildExpandingFormset).toBeDefined();
});
it('should add an expanded item if the add button is not disabled', () => {
const prefix = 'id_form_fields';
document.body.innerHTML = `
<div class="object" id="content">
<input type="hidden" name="form_fields-TOTAL_FORMS" value="2" id="${prefix}-TOTAL_FORMS">
<ul id="${prefix}-FORMS">
${[0, 1].map(id => `
<li id="inline_child_form_fields-${id}" data-inline-panel-child data-contentpath-disabled>
<input type="text" name="form_fields-${id}-label" value="Subject" id="id_form_fields-${id}-label">
<input type="hidden" name="form_fields-${id}-id" value="${id + 1}" id="id_form_fields-${id}-id">
<input type="hidden" name="form_fields-${id}-DELETE" id="id_form_fields-${id}-DELETE">
</li>
`)}
</ul>
<button class="button" id="${prefix}-ADD" type="button">
Add form fields
</button>
<script type="text/django-form-template" id="${prefix}-EMPTY_FORM_TEMPLATE">
<li id="inline_child_form_fields-__prefix__" data-inline-panel-child data-contentpath-disabled>
<input type="text" name="form_fields-__prefix__-label" id="id_form_fields-__prefix__-label">
<input type="hidden" name="form_fields-__prefix__-id" id="id_form_fields-__prefix__-id">
<input type="hidden" name="form_fields-__prefix__-DELETE" id="id_form_fields-__prefix__-DELETE">
</li>
</script>
</div>`;
const onAdd = jest.fn();
const onInit = jest.fn();
expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('2');
expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(2);
expect(onAdd).not.toHaveBeenCalled();
expect(onInit).not.toHaveBeenCalled();
// initialise expanding formset
buildExpandingFormset(prefix, { onInit, onAdd });
// check that init calls only were made for existing items
expect(onAdd).not.toHaveBeenCalled();
expect(onInit).toHaveBeenCalledTimes(2);
expect(onInit).toHaveBeenNthCalledWith(1, 0); // zero indexed
expect(onInit).toHaveBeenNthCalledWith(2, 1);
// click the 'add' button
document.getElementById(`${prefix}-ADD`).dispatchEvent(new MouseEvent('click'));
// check that template was generated and additional onInit / onAdd called
expect(onAdd).toHaveBeenCalledWith(2); // zero indexed
expect(onInit).toHaveBeenCalledTimes(3);
expect(onInit).toHaveBeenLastCalledWith(2);
expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('3');
expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(3);
// check template was created into a new form item or malformed
expect(document.getElementById('inline_child_form_fields-__prefix__')).toBeNull();
const newFormHtml = document.getElementById(`inline_child_form_fields-${2}`);
expect(newFormHtml.querySelectorAll('[id*="__prefix__"]')).toHaveLength(0);
expect(newFormHtml.querySelectorAll(`[id*="form_fields-${2}"]`)).toHaveLength(3);
expect(newFormHtml).toMatchSnapshot();
});
it('should not add an expanded item if the add button is disabled', () => {
const prefix = 'id_form_fields';
document.body.innerHTML = `
<div class="object" id="content">
<input type="hidden" name="form_fields-TOTAL_FORMS" value="2" id="${prefix}-TOTAL_FORMS">
<ul id="${prefix}-FORMS">
${[0, 1].map(id => `
<li id="inline_child_form_fields-${id}" data-inline-panel-child data-contentpath-disabled>
<input type="text" name="form_fields-${id}-label" value="Subject" id="id_form_fields-${id}-label">
<input type="hidden" name="form_fields-${id}-id" value="${id + 1}" id="id_form_fields-${id}-id">
<input type="hidden" name="form_fields-${id}-DELETE" id="id_form_fields-${id}-DELETE">
</li>
`)}
</ul>
<button class="button disabled" id="${prefix}-ADD" type="button">
Add form fields (DISABLED)
</button>
<script type="text/django-form-template" id="${prefix}-EMPTY_FORM_TEMPLATE">
<li id="inline_child_form_fields-__prefix__" data-inline-panel-child data-contentpath-disabled>
<input type="text" name="form_fields-__prefix__-label" id="id_form_fields-__prefix__-label">
<input type="hidden" name="form_fields-__prefix__-id" id="id_form_fields-__prefix__-id">
<input type="hidden" name="form_fields-__prefix__-DELETE" id="id_form_fields-__prefix__-DELETE">
</li>
</script>
</div>`;
const onAdd = jest.fn();
const onInit = jest.fn();
expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('2');
expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(2);
expect(onAdd).not.toHaveBeenCalled();
expect(onInit).not.toHaveBeenCalled();
// initialise expanding formset
buildExpandingFormset(prefix, { onInit, onAdd });
// check that init calls only were made for existing items
expect(onInit).toHaveBeenCalledTimes(2);
expect(onInit).toHaveBeenNthCalledWith(1, 0); // zero indexed
expect(onInit).toHaveBeenNthCalledWith(2, 1);
// click the 'add' button
document.getElementById(`${prefix}-ADD`).dispatchEvent(new MouseEvent('click'));
// check that no template was generated and additional onInit / onAdd not called
expect(onAdd).not.toHaveBeenCalled();
expect(onInit).toHaveBeenCalledTimes(2);
expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('2');
expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(2);
// check template was not created into a new form item or malformed
expect(document.getElementById('inline_child_form_fields-__prefix__')).toBeNull();
});
it('should replace the __prefix__ correctly for nested formset templates', () => {
const prefix = 'id_venues';
const nestedPrefix = 'events';
const nestedTemplate = `
<script type="text/django-form-template" id="${prefix}-__prefix__-events-EMPTY_FORM_TEMPLATE">
<ul class="controls">
<li>
<button type="button" class="button" id="${prefix}-__prefix__-${nestedPrefix}-__prefix__-DELETE-button">
Delete
</button>
</li>
</ul>
<fieldset>
<legend>Events</legend>
<input type="text" name="venues-__prefix__-events-__prefix__-name" id="id_venues-__prefix__-events-__prefix__-name">
</fieldset>
<-/script>
`;
document.body.innerHTML = `
<div class="object" id="content">
<input type="hidden" name="form_fields-TOTAL_FORMS" value="2" id="${prefix}-TOTAL_FORMS">
<ul id="${prefix}-FORMS">
${[0, 1].map(id => `
<li id="inline_child_form_fields-${id}" data-inline-panel-child data-contentpath-disabled>
<input type="text" name="form_fields-${id}-label" value="Subject" id="id_form_fields-${id}-label">
<input type="hidden" name="form_fields-${id}-id" value="${id + 1}" id="id_form_fields-${id}-id">
<input type="hidden" name="form_fields-${id}-DELETE" id="id_form_fields-${id}-DELETE">
</li>
`)}
</ul>
<button class="button" id="${prefix}-ADD" type="button">
Add Venue
</button>
<script type="text/django-form-template" id="${prefix}-EMPTY_FORM_TEMPLATE">
<li id="inline_child_form_fields-__prefix__" data-inline-panel-child data-contentpath-disabled>
<input type="text" name="form_fields-__prefix__-label" id="id_form_fields-__prefix__-label">
<input type="hidden" name="form_fields-__prefix__-id" id="id_form_fields-__prefix__-id">
<input type="hidden" name="form_fields-__prefix__-DELETE" id="id_form_fields-__prefix__-DELETE">
</li>
${nestedTemplate}
</script>
</div>`;
const onAdd = jest.fn();
const onInit = jest.fn();
expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('2');
expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(2);
expect(onAdd).not.toHaveBeenCalled();
expect(onInit).not.toHaveBeenCalled();
// initialise expanding formset
buildExpandingFormset(prefix, { onInit, onAdd });
// check that init calls only were made for existing items
expect(onAdd).not.toHaveBeenCalled();
expect(onInit).toHaveBeenCalledTimes(2);
expect(onInit).toHaveBeenNthCalledWith(1, 0); // zero indexed
expect(onInit).toHaveBeenNthCalledWith(2, 1);
// click the 'add' button
document.getElementById(`${prefix}-ADD`).dispatchEvent(new MouseEvent('click'));
// check that template was generated and additional onInit / onAdd called
expect(onAdd).toHaveBeenCalledWith(2); // zero indexed
expect(onInit).toHaveBeenCalledTimes(3);
expect(onInit).toHaveBeenLastCalledWith(2);
expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('3');
expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(3);
// check the nested template was created with the correct prefixes
const newTemplate = document.getElementById(`${prefix}-2-events-EMPTY_FORM_TEMPLATE`);
expect(newTemplate).toBeTruthy();
expect(newTemplate.textContent).toContain('id="id_venues-2-events-__prefix__-DELETE-button"');
expect(newTemplate.textContent).toContain(
'<input type="text" name="venues-2-events-__prefix__-name" id="id_venues-2-events-__prefix__-name">'
);
});
});

Wyświetl plik

@ -23,7 +23,7 @@ function buildExpandingFormset(prefix, opts = {}) {
addButton.on('click', () => {
if (addButton.hasClass('disabled')) return false;
const newFormHtml = emptyFormTemplate
.replace(/__prefix__/g, formCount)
.replace(/__prefix__(.*?['"])/g, formCount + '$1')
.replace(/<-(-*)\/script>/g, '<$1/script>');
formContainer.append(newFormHtml);
if (opts.onAdd) opts.onAdd(formCount);

Wyświetl plik

@ -165,7 +165,7 @@ function InlinePanel(opts) { // lgtm[js/unused-local-variable]
// eslint-disable-next-line no-undef
buildExpandingFormset(opts.formsetPrefix, {
onAdd(formCount) {
const newChildPrefix = opts.emptyChildFormPrefix.replace(/__prefix__/g, formCount);
const newChildPrefix = opts.emptyChildFormPrefix.replace(/__prefix__(.*?['"])/g, formCount + '$1');
self.initChildControls(newChildPrefix);
if (opts.canOrder) {
/* NB form hidden inputs use 0-based index and only increment formCount *after* this function is run.

Wyświetl plik

@ -88,6 +88,7 @@ Bug fixes
* CSS build scripts now output to the correct directory paths on Windows (Vince Salvino)
* Capture log output from style fallback to avoid noise in unit tests (Matt Westcott)
* Switch widgets on/off states are now visually different for high-contrast mode users (Sakshi Uppoor)
* Nested InlinePanel usage no longer fails to save when creating two or more items (Indresh P, Rinish Sam)
Upgrade considerations
======================