Did you used AI to write this issue?
Yes — root cause analysis and wording assisted by AI; reproduction and code references verified by hand against the installed source.
What version of Unfold are you using?
0.98.0 (latest release; the offending code is also present on main).
What version of Django are you using?
6.0 (6.0.6), Python 3.14.
What browser are you using?
Browser-independent — the corruption is produced by the server-emitted Alpine.js
bindings, so it happens in any browser with Alpine running. (Observed in Firefox /
Chromium.)
Did you checked changelog/commit history, if the bug is not already fixed?
Yes. changeform_condition in src/unfold/templatetags/unfold.py on main still
ends in the generic else branch that assigns a scalar x-model.fill to any
unrecognised widget — unchanged in 0.98.0.
Did you searched other issues, if the bug is not already fixed?
Yes. #825 is a different ArrayWidget bug (comma-separated values split on
decompress, closed as not planned); #1020 is about choices/Select rendering.
Neither covers the conditional_fields interaction described here.
Did you checked documentation?
Yes — both the ArrayWidget and
Conditional fields
pages. Neither documents a restriction on combining the two.
Are you able to replicate the bug in the demo site?
Not verified on the demo site (no model there appears to combine an ArrayField
with conditional_fields). It reproduces reliably with the minimal admin below.
Repository with reproduced bug
No separate repository; the bug reproduces with the minimal model + admin below,
and the client-side cause can be shown without a browser (see "Minimal proof").
Describe your issue
Summary
When a ModelAdmin declares both conditional_fields and an ArrayField
rendered with ArrayWidget, every item input of the array is rendered with the
same Alpine x-model binding pointing to a single scalar variable. On page load
Alpine collapses all inputs to the first item's value. Removing
conditional_fields makes ArrayWidget work correctly again.
Steps to reproduce
# models.py
from django.contrib.postgres.fields import ArrayField
from django.db import models
class BibEntry(models.Model):
type = models.CharField(max_length=20, choices=[("book", "Book"), ("paper", "Paper")])
authors = ArrayField(models.CharField(max_length=255), default=list)
isbn = models.CharField(max_length=20, blank=True)
# admin.py
from django.contrib import admin
from django.contrib.postgres.fields import ArrayField
from unfold.admin import ModelAdmin
from unfold.contrib.forms.widgets import ArrayWidget
@admin.register(BibEntry)
class BibEntryAdmin(ModelAdmin):
formfield_overrides = {ArrayField: {"widget": ArrayWidget}}
conditional_fields = {
"isbn": "type === 'book'", # any conditional_fields entry triggers it
}
Create a record with several authors, e.g.
authors = ["Thomas Lewis", "Fari Amini", "Richard Lannon"], then open the
change form.
- Expected: three inputs, one per author, each with its own value.
- Actual: three inputs, all showing
"Thomas Lewis" (the first item).
Comment out conditional_fields and the widget renders the distinct values
correctly.
Root cause
The server renders the correct, distinct values — the corruption happens
client-side via the Alpine x-model binding that conditional_fields injects.
-
In unfold/templates/unfold/helpers/fieldset_row.html, when the admin has any
conditional_fields, has_conditional_display is truthy and the
changeform_condition filter is applied to every field in the form — not
only the fields listed in conditional_fields:
{% if has_conditional_display %}
{% with field|changeform_condition as field %}
-
changeform_condition (unfold/templatetags/unfold.py) has special handling
for UnfoldAdminSplitDateTimeWidget / UnfoldAdminMoneyWidget (which use
distinct widgets_names suffixes), but ArrayWidget falls into the generic
else branch:
else:
field.field.field.widget.attrs["x-model.fill"] = field.field.name
So ArrayWidget.attrs["x-model.fill"] = "authors".
-
ArrayWidget is a MultiWidget whose widgets_names are all "" (every item
shares the field name). Django's MultiWidget.get_context copies the parent
attrs onto every subwidget:
widget_attrs = final_attrs.copy() # carries x-model.fill="authors"
Result: every author <input> gets name="authors" and
x-model.fill="authors".
-
changeform_data seeds the Alpine scope with authors: null. With N inputs
two-way-bound to the same scalar via x-model, Alpine synchronises them all to
one value on init — the first item — visually wiping the rest.
Minimal proof (no browser needed)
from unfold.contrib.forms.widgets import ArrayWidget
w = ArrayWidget()
w.attrs["x-model.fill"] = "authors" # what changeform_condition injects
ctx = w.get_context("authors", ["A", "B", "C"], {"id": "id_authors"})
for s in ctx["widget"]["subwidgets"]:
print(s["name"], s["attrs"].get("x-model.fill"))
# authors authors
# authors authors
# authors authors -> all three inputs bound to the same scalar
Suggested fix
changeform_condition should not assign a scalar x-model.fill to widgets whose
subwidgets share a name (i.e. a MultiWidget with empty widgets_names, like
ArrayWidget). Either skip ArrayWidget entirely (an array field can't act as a
boolean condition source and rarely needs to drive visibility), or give it a
per-item binding instead of a shared scalar.
Workaround
Subclass ArrayWidget and strip the injected x-model attributes before render:
class ConditionalSafeArrayWidget(ArrayWidget):
def get_context(self, name, value, attrs):
self.attrs = {k: v for k, v in self.attrs.items() if not k.startswith("x-model")}
if attrs:
attrs = {k: v for k, v in attrs.items() if not k.startswith("x-model")}
return super().get_context(name, value, attrs)
Did you used AI to write this issue?
Yes — root cause analysis and wording assisted by AI; reproduction and code references verified by hand against the installed source.
What version of Unfold are you using?
0.98.0 (latest release; the offending code is also present on
main).What version of Django are you using?
6.0 (6.0.6), Python 3.14.
What browser are you using?
Browser-independent — the corruption is produced by the server-emitted Alpine.js
bindings, so it happens in any browser with Alpine running. (Observed in Firefox /
Chromium.)
Did you checked changelog/commit history, if the bug is not already fixed?
Yes.
changeform_conditioninsrc/unfold/templatetags/unfold.pyonmainstillends in the generic
elsebranch that assigns a scalarx-model.fillto anyunrecognised widget — unchanged in 0.98.0.
Did you searched other issues, if the bug is not already fixed?
Yes. #825 is a different ArrayWidget bug (comma-separated values split on
decompress, closed as not planned); #1020 is aboutchoices/Select rendering.Neither covers the
conditional_fieldsinteraction described here.Did you checked documentation?
Yes — both the ArrayWidget and
Conditional fields
pages. Neither documents a restriction on combining the two.
Are you able to replicate the bug in the demo site?
Not verified on the demo site (no model there appears to combine an
ArrayFieldwith
conditional_fields). It reproduces reliably with the minimal admin below.Repository with reproduced bug
No separate repository; the bug reproduces with the minimal model + admin below,
and the client-side cause can be shown without a browser (see "Minimal proof").
Describe your issue
Summary
When a
ModelAdmindeclares bothconditional_fieldsand anArrayFieldrendered with
ArrayWidget, every item input of the array is rendered with thesame Alpine
x-modelbinding pointing to a single scalar variable. On page loadAlpine collapses all inputs to the first item's value. Removing
conditional_fieldsmakesArrayWidgetwork correctly again.Steps to reproduce
Create a record with several authors, e.g.
authors = ["Thomas Lewis", "Fari Amini", "Richard Lannon"], then open thechange form.
"Thomas Lewis"(the first item).Comment out
conditional_fieldsand the widget renders the distinct valuescorrectly.
Root cause
The server renders the correct, distinct values — the corruption happens
client-side via the Alpine
x-modelbinding thatconditional_fieldsinjects.In
unfold/templates/unfold/helpers/fieldset_row.html, when the admin has anyconditional_fields,has_conditional_displayis truthy and thechangeform_conditionfilter is applied to every field in the form — notonly the fields listed in
conditional_fields:changeform_condition(unfold/templatetags/unfold.py) has special handlingfor
UnfoldAdminSplitDateTimeWidget/UnfoldAdminMoneyWidget(which usedistinct
widgets_namessuffixes), butArrayWidgetfalls into the genericelsebranch:So
ArrayWidget.attrs["x-model.fill"] = "authors".ArrayWidgetis aMultiWidgetwhosewidgets_namesare all""(every itemshares the field name). Django's
MultiWidget.get_contextcopies the parentattrsonto every subwidget:Result: every author
<input>getsname="authors"andx-model.fill="authors".changeform_dataseeds the Alpine scope withauthors: null. With N inputstwo-way-bound to the same scalar via
x-model, Alpine synchronises them all toone value on init — the first item — visually wiping the rest.
Minimal proof (no browser needed)
Suggested fix
changeform_conditionshould not assign a scalarx-model.fillto widgets whosesubwidgets share a name (i.e. a
MultiWidgetwith emptywidgets_names, likeArrayWidget). Either skipArrayWidgetentirely (an array field can't act as aboolean condition source and rarely needs to drive visibility), or give it a
per-item binding instead of a shared scalar.
Workaround
Subclass
ArrayWidgetand strip the injectedx-modelattributes before render: