question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Nested InlinePanel fails to save when creating >=2 items

See original GitHub issue

Issue Summary

When adding new objects within nested InlinePanels, the form fields of the grandchild objects are not allocated unique IDs / names, leading to failures in initialising JS and saving.

Steps to Reproduce

  1. Start a new project with wagtail start eventdemo
  2. Edit home/models.py as follows:
    from django.db import models
    
    from modelcluster.fields import ParentalKey
    from modelcluster.models import ClusterableModel
    from wagtail.core.models import Page, Orderable
    from wagtail.admin.edit_handlers import FieldPanel, InlinePanel
    
    
    class Event(Orderable):
        venue = ParentalKey('Venue', related_name='events', on_delete=models.CASCADE)
        name = models.CharField(max_length=255)
        date = models.DateField()
    
        panels = [
            FieldPanel('name'),
            FieldPanel('date'),
        ]
    
    
    class Venue(ClusterableModel, Orderable):
        page = ParentalKey('HomePage', related_name='venues', on_delete=models.CASCADE)
        name = models.CharField(max_length=255)
    
        panels = [
            FieldPanel('name'),
            InlinePanel('events', label="Events"),
        ]
    
    
    class HomePage(Page):
        content_panels = [
            InlinePanel('venues', label="Venues"),
        ]
    
  3. ./manage.py makemigrations, ./manage.py migrate, ./manage.py createsuperuser, ./manage.py runserver 0:8000
  4. Log in to admin, edit the homepage and add a venue and two events. Note that the second date field does not provide a date picker.
  5. Click ‘save draft’. Note that only one of the events has been saved.
  • I have confirmed that this issue can be reproduced as described on a fresh Wagtail project: yes

The root cause of both issues is in the mechanism for adding child objects to the form: the HTML fragment to be inserted is stored in the page as a <script type="text/django-form-template"> block, with __prefix__ placeholders that get replaced with the correct index number (by search-and-replace on the HTML string) when a child is added. When InlinePanels are nested, the template block itself contains another template block also using __prefix__ as the placeholder string, so we can’t distinguish between first-level and second-level placeholders:

<script type="text/django-form-template" id="id_venues-EMPTY_FORM_TEMPLATE">
    <li data-inline-panel-child id="inline_child_venues-__prefix__">

        ...
        <label for="id_venues-__prefix__-name">Name:</label>
        <input type="text" name="venues-__prefix__-name" maxlength="255" id="id_venues-__prefix__-name">
        ....

        <script type="text/django-form-template" id="id_venues-__prefix__-events-EMPTY_FORM_TEMPLATE">
            <li data-inline-panel-child id="inline_child_venues-__prefix__-events-__prefix__">

                ...
                <label for="id_venues-__prefix__-events-__prefix__-name">Name:</label>
                <input type="text" name="venues-__prefix__-events-__prefix__-name" maxlength="255" id="id_venues-__prefix__-events-__prefix__-name">
                ...
                <label for="id_venues-__prefix__-events-__prefix__-date">Date:</label>
                <input type="text" name="venues-__prefix__-events-__prefix__-date" autocomplete="new-date" id="id_venues-__prefix__-events-__prefix__-date">
                <script>initDateChooser("id_venues\u002D__prefix__\u002Devents\u002D__prefix__\u002Ddate", {"dayOfWeekStart": 0, "format": "Y-m-d"});<--/script>
                ...

            </li>
        <-/script>
    </li>
</script>

Technical details

  • Python version: 3.6.8
  • Django version: 2.2.6
  • Wagtail version: Git master (2.8a0)
  • Browser version: Chrome 79.0.3945.117

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:5
  • Comments:8 (1 by maintainers)

github_iconTop GitHub Comments

2reactions
thaxycommented, Mar 29, 2020

I was looking into this and tried parsing only the first __prefix__ before realizing I was on the wrong track/being stuck and finally almost gave up.

.replace(/__prefix__/g, formCount)  // tried it without /g to replace only the first occurrence

expanding_formset.js

Any thoughts on how a working solution could look like?

  • Is this only a front end Orderable/Delete issue or do those __prefix__ related IDs have to be in another way meaningful?
  • How many levels of InlinePanel nesting does/should wagtail support?
  • Where is the delete button click logic?

The first form of the outermost inline panel seems to work correctly.


The following code is untested and a quick hacky fix for our scenario. I just wanted it to share.

static/js/expanding_formset.js:

// Wagtail 2.8 issue (https://github.com/wagtail/wagtail/issues/5770)
// Nested inline panel fails in saving due to generating wrong names/IDs of input fields
// - we provide a fix working in our situation but not verified against wagtail source code
// - fix implies modification/addition of 2 lines of code (see comments below)


console.log('MODIFIED EXPANDING FORMSET JS LOADED');

function buildExpandingFormset(prefix, opts) {
  console.log('buildExpandingFormset called with: ', prefix);

  if (!opts) {
      opts = {};
  }

  var addButton = $('#' + prefix + '-ADD');
  var formContainer = $('#' + prefix + '-FORMS');
  var totalFormsInput = $('#' + prefix + '-TOTAL_FORMS');
  var formCount = parseInt(totalFormsInput.val(), 10);

  if (opts.onInit) {
      for (var i = 0; i < formCount; i++) {
          opts.onInit(i);
      }
  }

  var emptyFormTemplate = document.getElementById(prefix + '-EMPTY_FORM_TEMPLATE');
  if (emptyFormTemplate.innerText) {
      emptyFormTemplate = emptyFormTemplate.innerText;
  } else if (emptyFormTemplate.textContent) {
      emptyFormTemplate = emptyFormTemplate.textContent;
  }

  addButton.on('click', function() {
      var pre = prefix.replace(/^id_/, '')  // ADDED WITH RESPECT TO WAGTAIL VERSION
      if (addButton.hasClass('disabled')) return false;
      var newFormHtml = emptyFormTemplate
          .replace(new RegExp(`${pre}-__prefix__`, 'g'), `${pre}-${formCount}`)  // MODIFIED WITH RESPECT TO WAGTAIL VERSION
          .replace(/<-(-*)\/script>/g, '<$1/script>');
      formContainer.append(newFormHtml);
      if (opts.onAdd) opts.onAdd(formCount);
      if (opts.onInit) opts.onInit(formCount);

      formCount++;
      totalFormsInput.val(formCount);
  });
}
0reactions
joshuadavidthomascommented, Sep 23, 2021

+1 on the modifications from @thaxy and @the5thbeatle, though I had to edit it to .replace(new RegExp(``${pre}\\\\u002D__prefix__``, 'g'), ``${pre}\\u002D${formCount}``) added after the first RegExp line from @thaxy’s script.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Nested categories/InlinePanel(s) in wagtail - Stack Overflow
1 Answer 1 · But I think this requires the snippet to be created first. · I tried it this way, but as...
Read more >
Creating an interactive event budgeting tool within Wagtail
This tutorial will walk you through a basic set-up of building an interactive event budgeting tool within the Wagtail page editing interface.
Read more >
Wagtail Tip #1: How to replace ParentalManyToManyField ...
In this Wagtail tip, I will talk about How to replace `ParentalManyToManyField` using Wagtail InlinePanel.
Read more >
9025 (Nested Inline Support in Admin) - Django's bug tracker
If creating a new pull request, include a link to the pull request in the ... on this class to not show more...
Read more >
Panel types — Wagtail Documentation 4.1.1 documentation
For a full explanation on the usage of InlinePanel , see Inline Panels and Model Clusters. Collapsing InlinePanels to save space¶. Note that...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found