Summary
When multiple forms containing DAL autocomplete widgets appear on the same page,
each {{ form.media }} call independently emits the full DAL script bundle
(autocomplete_light.js, select2.js, etc.). Django's Media system only
deduplicates scripts when media objects are combined with + before rendering;
separate inline {{ form.media }} calls produce duplicate <script> tags.
The result is a console.error on every affected page:
autocomplete_light.js:17 The DAL function "select2" has already been registered.
This fires because select2.js is executed twice, and the second call to
yl.registerFunction('select2', ...) hits the already-registered guard added in
PR #982.
A secondary trigger exists even on pages with a single autocomplete form: the
common workaround of re-dispatching window.load to re-initialise widgets loaded
via Ajax (referenced in issue #1221) causes autocomplete_light.js's load
listener to fire a second dal-init-function event, which again triggers the
duplicate-registration error.
Steps to reproduce
- Create a page with two separate forms, each containing a
ModelSelect2 widget.
- Render them independently in a template:
{{ form_one.media }}
{{ form_two.media }}
- Open the browser console —
The DAL function "select2" has already been registered. appears.
Root cause
Widget.media is the only documented mechanism for including DAL's CSS/JS. On
pages with a single form this works fine. On pages with multiple forms rendered
independently (a very common pattern in Django projects), the same scripts are
injected multiple times because there is no way for the template author to say
"include DAL's assets once, globally".
History
In v1/v2, {% include 'autocomplete_light/static.html' %} could be placed in a
base template for exactly this purpose. It was removed in favour of form.media,
which works correctly only when a single form is on the page or when the
developer manually combines media objects in Python before rendering.
Suggested fix
Add a small template tag to dal (or dal_select2) that renders the library's
required CSS and JS, suitable for placement in a base template:
# dal/templatetags/dal_tags.py
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
@register.simple_tag
def dal_media():
"""Render DAL CSS and JS for global inclusion in a base template."""
from dal_select2.widgets import ModelSelect2
return mark_safe(str(ModelSelect2(url='').media))
Usage in a base template:
{% load dal_tags %}
{% dal_media %}
Projects using this pattern would then strip DAL's scripts from per-widget
form.media to avoid the duplicate, e.g. by subclassing their widgets:
class ModelSelect2(autocomplete.ModelSelect2):
@property
def media(self):
# DAL scripts loaded globally via {% dal_media %} in base template
return forms.Media(js=["my_app/js/dal_reload.js"])
Where dal_reload.js re-initialises any autocomplete widgets inserted
dynamically (e.g. via Ajax), without re-dispatching window.load:
// Only runs when DAL is already initialised (i.e. in an Ajax-loaded context).
// Avoids re-dispatching window.load which would re-fire dal-init-function and
// trigger the "already registered" error.
$(document).ready(function() {
if (window.__dal__initialize) {
$('[data-autocomplete-light-function]').each(window.__dal__initialize);
}
});
Note: DAL 3.9+ already uses a MutationObserver to catch dynamically-added
elements, so the reload script is mostly belt-and-suspenders for edge cases.
Alternatives considered
- Combine media in every view — requires touching every view and template
that has more than one autocomplete form. Error-prone and does not scale.
- Use
{% block media %} — only works if all forms are rendered in a single
template context; breaks for reusable template tags/includes that each render
their own form.
- Rely on the
__dal__initialize guard — prevents the critical double-init
bug but still emits duplicate <script> tags and still logs console.error,
which is noisy and misleading for developers.
Related issues
Summary
When multiple forms containing DAL autocomplete widgets appear on the same page,
each
{{ form.media }}call independently emits the full DAL script bundle(
autocomplete_light.js,select2.js, etc.). Django'sMediasystem onlydeduplicates scripts when media objects are combined with
+before rendering;separate inline
{{ form.media }}calls produce duplicate<script>tags.The result is a
console.erroron every affected page:This fires because
select2.jsis executed twice, and the second call toyl.registerFunction('select2', ...)hits the already-registered guard added inPR #982.
A secondary trigger exists even on pages with a single autocomplete form: the
common workaround of re-dispatching
window.loadto re-initialise widgets loadedvia Ajax (referenced in issue #1221) causes
autocomplete_light.js's loadlistener to fire a second
dal-init-functionevent, which again triggers theduplicate-registration error.
Steps to reproduce
ModelSelect2widget.{{ form_one.media }} {{ form_two.media }}The DAL function "select2" has already been registered.appears.Root cause
Widget.mediais the only documented mechanism for including DAL's CSS/JS. Onpages with a single form this works fine. On pages with multiple forms rendered
independently (a very common pattern in Django projects), the same scripts are
injected multiple times because there is no way for the template author to say
"include DAL's assets once, globally".
History
In v1/v2,
{% include 'autocomplete_light/static.html' %}could be placed in abase template for exactly this purpose. It was removed in favour of
form.media,which works correctly only when a single form is on the page or when the
developer manually combines media objects in Python before rendering.
Suggested fix
Add a small template tag to
dal(ordal_select2) that renders the library'srequired CSS and JS, suitable for placement in a base template:
Usage in a base template:
{% load dal_tags %} {% dal_media %}Projects using this pattern would then strip DAL's scripts from per-widget
form.mediato avoid the duplicate, e.g. by subclassing their widgets:Where
dal_reload.jsre-initialises any autocomplete widgets inserteddynamically (e.g. via Ajax), without re-dispatching
window.load:Note: DAL 3.9+ already uses a
MutationObserverto catch dynamically-addedelements, so the reload script is mostly belt-and-suspenders for edge cases.
Alternatives considered
that has more than one autocomplete form. Error-prone and does not scale.
{% block media %}— only works if all forms are rendered in a singletemplate context; breaks for reusable template tags/includes that each render
their own form.
__dal__initializeguard — prevents the critical double-initbug but still emits duplicate
<script>tags and still logsconsole.error,which is noisy and misleading for developers.
Related issues
form.mediaautocomplete_light/static.html(v1 era)