Skip to content

Commit 780d3cb

Browse files
committed
[feature] Add Select2Widget for choice fields #254
Implement a reusable Select2Widget that uses Django's native admin assets to avoid extra dependencies. Closes #254
1 parent 98540a6 commit 780d3cb

File tree

4 files changed

+80
-0
lines changed

4 files changed

+80
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"use strict";
2+
3+
django.jQuery(function ($) {
4+
function initSelect2($element) {
5+
$element.not('select[name*="__prefix__"]').each(function () {
6+
var $el = $(this);
7+
if (!$el.hasClass("select2-hidden-accessible")) {
8+
$el.select2();
9+
}
10+
});
11+
}
12+
13+
initSelect2($("select.ow-select2"));
14+
15+
$(document).on("formset:added", function (event, $row) {
16+
initSelect2($row.find("select.ow-select2"));
17+
});
18+
});

openwisp_utils/widgets.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from django.conf import settings
2+
from django.contrib.admin.widgets import SELECT2_TRANSLATIONS
3+
from django.forms import Media, Select
4+
from django.utils.translation import get_language
5+
6+
7+
class Select2Widget(Select):
8+
"""Select2 autocomplete widget for Django ChoiceFields."""
9+
10+
@property
11+
def media(self):
12+
extra = "" if getattr(settings, "DEBUG", False) else ".min"
13+
i18n_name = SELECT2_TRANSLATIONS.get(get_language())
14+
i18n_file = (
15+
("admin/js/vendor/select2/i18n/{0}.js".format(i18n_name),)
16+
if i18n_name
17+
else ()
18+
)
19+
return Media(
20+
js=(
21+
"admin/js/vendor/jquery/jquery{0}.js".format(extra),
22+
"admin/js/vendor/select2/select2.full{0}.js".format(extra),
23+
)
24+
+ i18n_file
25+
+ ("admin/js/jquery.init.js", "openwisp-utils/js/select2.js"),
26+
css={
27+
"screen": ("admin/css/vendor/select2/select2{0}.css".format(extra),),
28+
},
29+
)
30+
31+
def __init__(self, attrs=None, choices=()):
32+
attrs = attrs or {}
33+
attrs["class"] = "ow-select2 {0}".format(attrs.get("class", "")).strip()
34+
super().__init__(attrs, choices)

tests/test_project/admin.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
InputFilter,
1616
SimpleInputFilter,
1717
)
18+
from openwisp_utils.widgets import Select2Widget
1819

1920
from .models import (
2021
Book,
@@ -122,6 +123,11 @@ class ShelfAdmin(TimeReadonlyAdminMixin, admin.ModelAdmin):
122123
]
123124
search_fields = ["name"]
124125

126+
def formfield_for_choice_field(self, db_field, request, **kwargs):
127+
if db_field.name == "books_type":
128+
kwargs["widget"] = Select2Widget()
129+
return super().formfield_for_choice_field(db_field, request, **kwargs)
130+
125131

126132
@admin.register(OrganizationRadiusSettings)
127133
class OrganizationRadiusSettingsAdmin(admin.ModelAdmin):
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from django.test import TestCase
2+
from openwisp_utils.widgets import Select2Widget
3+
4+
5+
class TestWidgets(TestCase):
6+
def test_select2_widget_attrs(self):
7+
widget = Select2Widget()
8+
html = widget.render("name", "value")
9+
self.assertIn('class="ow-select2"', html)
10+
11+
# test overriding works and class is preserved
12+
widget = Select2Widget(attrs={"class": "my-class"})
13+
html = widget.render("name", "value")
14+
self.assertIn("ow-select2 my-class", html)
15+
16+
def test_select2_widget_media(self):
17+
widget = Select2Widget()
18+
media = str(widget.media)
19+
self.assertIn("admin/css/vendor/select2/select2", media)
20+
self.assertIn("admin/js/vendor/jquery/jquery", media)
21+
self.assertIn("admin/js/vendor/select2/select2.full", media)
22+
self.assertIn("openwisp-utils/js/select2.js", media)

0 commit comments

Comments
 (0)