Skip to content

Commit a695791

Browse files
committed
[feat] 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 288e8cc commit a695791

4 files changed

Lines changed: 83 additions & 0 deletions

File tree

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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
"""
9+
Select2 autocomplete widget for Django ChoiceFields.
10+
"""
11+
12+
@property
13+
def media(self):
14+
extra = '' if getattr(settings, 'DEBUG', False) else '.min'
15+
i18n_name = SELECT2_TRANSLATIONS.get(get_language())
16+
i18n_file = (
17+
('admin/js/vendor/select2/i18n/{0}.js'.format(i18n_name),)
18+
if i18n_name
19+
else ()
20+
)
21+
return Media(
22+
js=(
23+
'admin/js/vendor/jquery/jquery{0}.js'.format(extra),
24+
'admin/js/vendor/select2/select2.full{0}.js'.format(extra),
25+
)
26+
+ i18n_file
27+
+ ('admin/js/jquery.init.js', 'openwisp-utils/js/select2.js'),
28+
css={
29+
'screen': ('admin/css/vendor/select2/select2{0}.css'.format(extra),),
30+
},
31+
)
32+
33+
def __init__(self, attrs=None, choices=()):
34+
attrs = attrs or {}
35+
attrs['class'] = 'ow-select2 {0}'.format(attrs.get('class', '')).strip()
36+
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: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from django.test import TestCase
2+
3+
from openwisp_utils.widgets import Select2Widget
4+
5+
6+
class TestWidgets(TestCase):
7+
def test_select2_widget_attrs(self):
8+
widget = Select2Widget()
9+
html = widget.render('name', 'value')
10+
self.assertIn('class="ow-select2"', html)
11+
12+
# test overriding works and class is preserved
13+
widget = Select2Widget(attrs={'class': 'my-class'})
14+
html = widget.render('name', 'value')
15+
self.assertIn('ow-select2 my-class', html)
16+
17+
def test_select2_widget_media(self):
18+
widget = Select2Widget()
19+
media = str(widget.media)
20+
self.assertIn('admin/css/vendor/select2/select2', media)
21+
self.assertIn('admin/js/vendor/jquery/jquery', media)
22+
self.assertIn('admin/js/vendor/select2/select2.full', media)
23+
self.assertIn('openwisp-utils/js/select2.js', media)

0 commit comments

Comments
 (0)