Frontend Comparison: alight vs select2
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Both frontends share the same Django-side base classes
(``BaseQuerySetView``, ``ViewMixin``, ``forward.py``).
They differ in what the view returns and which JS component renders it.
Core difference
===============
.. list-table::
:header-rows: 1
:widths: 20 40 40
* - Aspect
- ``dal_alight``
- ``dal_select2``
* - Response format
- HTML fragments ``
`` sentinel; select2: JSON flag ``create_id:true``
* - Infinite scroll / pagination
- yes
- yes
- alight: ``
`` sentinel; select2: ``pagination.more``
* - Field forwarding
- yes
- yes
- Both use the same ``forward.py``; alight adapter is vanilla JS
* - Django admin popup sync
- yes
- yes
- alight patches ``dismissAddRelatedObjectPopup`` in ``dal-django.js``
* - Minimum input length
- yes
- yes
- alight: ``minimum-characters`` attribute; select2: ``data-minimum-input-length``
* - Clear / remove selection
- yes
- yes
- alight uses × buttons in the deck
* - Client-side local filtering
- yes
- **no**
- alight: ``Alight`` / ``AlightMultiple`` without a ``url`` filter ````
elements locally — no server round-trip
* - Max-choices cap with auto-eviction
- yes
- **no**
- alight: ``max-choices`` attribute; oldest selection is evicted when cap is exceeded
* - i18n of widget UI strings
- planned
- yes
- alight UI strings can be overridden via custom attributes or Django's i18n
machinery; select2 ships 59 locale files
* - Distinct selected-item label
- **no**
- yes
- alight deck always shows the same label as the dropdown; select2:
``get_selected_result_label()`` / ``data-selected-html``
* - Token separators for tags
- **no**
- yes
- alight requires click or Enter; select2: ``data-token-separators``
(e.g. type ``,`` to commit)
* - Rich HTML result/selection templates
- partial
- yes
- alight renders ``get_result_label()`` as raw HTML but has no separate selection
template; select2: ``templateResult`` / ``templateSelection`` JS callbacks +
``data-html``
When to use ``dal_alight``
===========================
- No jQuery in the stack (modern frontend, HTMX, API-only server-side, etc.).
- Minimising JS payload and eliminating third-party dependencies is a priority.
- You want ``max-choices`` enforcement client-side without extra code.
- Some fields use a small static choice list and local filtering avoids an unnecessary
server round-trip.
- You prefer the server to own result rendering (HTML fragments) rather than templating
in JS — useful when labels contain Django template logic or server-side permissions.
- You are building a web-component or shadow-DOM ecosystem and need a widget that fits
naturally as a Custom Element.
Known gaps in ``dal_alight``
=============================
.. note:: These are deliberate trade-offs or deferred features, not bugs.
1. **i18n** — the three user-visible strings ("Search…", "No result", "Create …")
can be overridden via custom HTML attributes on the widget, or rendered server-side
through Django's i18n machinery.
2. **No** ``selected_text`` — there is no mechanism to show a different label once an
item is selected. The deck always shows ``get_result_label()``. Select2's
``get_selected_result_label()`` hook has no alight equivalent yet.
3. **No token separators** — in tag mode the user must click or press Enter to commit a
tag; typing a separator character does not auto-commit.
When to use ``dal_select2``
============================
Use ``dal_select2`` when you require one of these specific features not yet in
``dal_alight``:
- A distinct label in the "selected chip" versus the dropdown item
(``get_selected_result_label``).
- Token separators for tag creation (typing ``,`` to commit a tag without
clicking).
- Results with rich HTML requiring *different* rendering for dropdown vs selection
display.
- Browsers or CSP policies that disallow Custom Elements.
Class name mapping
==================
.. list-table::
:header-rows: 1
:widths: 40 40 20
* - ``dal_alight``
- ``dal_select2``
- Kind
* - :py:class:`~dal_alight.views.AlightQuerySetView`
- :py:class:`~dal_select2.views.Select2QuerySetView`
- View
* - :py:class:`~dal_alight.views.AlightGroupQuerySetView`
- :py:class:`~dal_select2.views.Select2GroupQuerySetView`
- View
* - :py:class:`~dal_alight.views.AlightListView`
- :py:class:`~dal_select2.views.Select2ListView`
- View
* - :py:class:`~dal_alight.views.AlightGroupListView`
- :py:class:`~dal_select2.views.Select2GroupListView`
- View
* - :py:class:`~dal_alight_queryset_sequence.views.AlightQuerySetSequenceView`
- :py:class:`~dal_select2_queryset_sequence.views.Select2QuerySetSequenceView`
- View (GFK)
* - :py:class:`~dal_alight.widgets.ModelAlight`
- :py:class:`~dal_select2.widgets.ModelSelect2`
- Widget — FK
* - :py:class:`~dal_alight.widgets.ModelAlightMultiple`
- :py:class:`~dal_select2.widgets.ModelSelect2Multiple`
- Widget — M2M
* - :py:class:`~dal_alight.widgets.Alight`
- :py:class:`~dal_select2.widgets.Select2`
- Widget — arbitrary choices, single
* - :py:class:`~dal_alight.widgets.AlightMultiple`
- :py:class:`~dal_select2.widgets.Select2Multiple`
- Widget — arbitrary choices, multiple
* - :py:class:`~dal_alight.widgets.ListAlight`
- :py:class:`~dal_select2.widgets.ListSelect2`
- Widget — list-backed
* - :py:class:`~dal_alight.widgets.TagAlight`
- :py:class:`~dal_select2.widgets.TagSelect2`
- Widget — free-text tags
* - :py:class:`~dal_alight.widgets.TaggitAlight`
- :py:class:`~dal_select2_taggit.widgets.TaggitSelect2`
- Widget — django-taggit
* - :py:class:`~dal_alight_queryset_sequence.widgets.QuerySetSequenceAlight`
- :py:class:`~dal_select2_queryset_sequence.widgets.QuerySetSequenceSelect2`
- Widget — GFK single
* - :py:class:`~dal_alight_queryset_sequence.widgets.QuerySetSequenceAlightMultiple`
- :py:class:`~dal_select2_queryset_sequence.widgets.QuerySetSequenceSelect2Multiple`
- Widget — GFK multiple
* - :py:class:`~dal_alight.fields.AlightListChoiceField`
- :py:class:`~dal_select2.fields.Select2ListChoiceField`
- Form field
* - :py:class:`~dal_alight.fields.AlightListCreateChoiceField`
- :py:class:`~dal_select2.fields.Select2ListCreateChoiceField`
- Form field
* - :py:class:`~dal_alight_queryset_sequence.fields.AlightGenericForeignKeyModelField`
- :py:class:`~dal_select2_queryset_sequence.fields.Select2GenericForeignKeyModelField`
- Form field — GFK auto-view