{# @description A whole Django formset: heading, description, set-level errors, management form, one row per form, and the add-row control. #} {# @prop formset:text | description:"The Django formset to render; nothing renders without it." #} {# @prop title:text | description:"Heading text; defaults to the set's model in plural." #} {# @prop description:text | description:"Developer-supplied help text under the heading." #} {# @prop class:text | description:"Extra classes on the root element." #} {# @prop layout:select['stacked','tabular'] | default:"stacked" | description:"Row presentation; tabular adds column headings from the empty form." #} {# @prop add-label:text | default:"Add row" | description:"Text for the add-row button." #} {# @prop remove-label:text | default:"Remove" | description:"Text for each row's remove control." #} {% load i18n l10n static mvp %} {% if formset %} {% comment %} Alpine behaviour is `mvpFormset` in mvp/static/js/formset.js; `total` (monotonic, seeds TOTAL_FORMS) and `visible` (not marked for removal, checked against max_num) are its two counters. See contracts/formset-component.md. {% endcomment %}
{% firstof title formset.title formset|formset_label as heading %} {% comment %} Unconditional: rows flush against the parent form's fields read as more of the same form without a divider marking the set off. {% endcomment %} {{ heading }} {% firstof description formset.description as help_text %} {% if help_text %}{{ help_text }}{% endif %} {% if formset.non_form_errors %} {{ formset.non_form_errors }} {% endif %} {{ formset.management_form.INITIAL_FORMS }} {{ formset.management_form.MIN_NUM_FORMS }} {{ formset.management_form.MAX_NUM_FORMS }} {% firstof formset|formset_grid_style as grid_style %} {% if layout == "tabular" %} {% endif %} {% for form in formset.forms %} {% endfor %}
{% comment %} Not `defer`: the base template already defers Alpine, so a deferred tag here would run after `alpine:init` fired and never register. {% endcomment %} {% endif %}