Source code for dal_select2.widgets
"""Select2 widget implementation module."""
try:
from functools import lru_cache
except ImportError:
# py2
try:
from backports.functools_lru_cache import lru_cache
except ImportError:
lru_cache = None
from dal.widgets import (
QuerySetSelectMixin,
Select,
SelectMultiple,
WidgetMixin
)
from django import forms
from django.conf import settings
try:
# SELECT2_TRANSLATIONS is Django 2.x only
from django.contrib.admin.widgets import SELECT2_TRANSLATIONS
except ImportError:
SELECT2_TRANSLATIONS = {}
from django.contrib.staticfiles import finders
from django.utils import translation
from django.utils.itercompat import is_iterable
I18N_PATH = 'autocomplete_light/i18n/'
[docs]
def get_i18n_name(lang_code):
"""Ensure lang_code is supported by Select2."""
lower_lang = lang_code.lower()
split_lang = lang_code.split('-')[0]
# Use the SELECT2_TRANSLATIONS if available
if SELECT2_TRANSLATIONS:
if lower_lang in SELECT2_TRANSLATIONS:
return SELECT2_TRANSLATIONS.get(lower_lang)
elif split_lang in SELECT2_TRANSLATIONS:
return SELECT2_TRANSLATIONS.get(split_lang)
# Otherwise fallback to manually checking if the static file exists
if finders.find('%s%s.js' % (I18N_PATH, lang_code)):
return lang_code
elif finders.find('%s%s.js' % (I18N_PATH, split_lang)):
return lang_code.split('-')[0]
if lru_cache:
get_i18n_name = lru_cache()(get_i18n_name)
else:
import warnings
warnings.warn(
'Python2: no cache on get_i18n_name, pip install backports.functools-lru-cache'
)
[docs]
class Select2WidgetMixin(object):
"""Mixin for Select2 widgets."""
[docs]
def build_attrs(self, *args, **kwargs):
"""Set data-autocomplete-light-language."""
attrs = super(Select2WidgetMixin, self).build_attrs(*args, **kwargs)
lang_code = self._get_language_code()
if lang_code:
attrs.setdefault('data-autocomplete-light-language', lang_code)
return attrs
def _get_language_code(self):
"""Return language code or None."""
lang_code = translation.get_language()
if lang_code:
lang_code = get_i18n_name(
translation.to_locale(lang_code).replace('_', '-')
)
return lang_code
@property
def media(self):
"""Return JS/CSS resources for the widget."""
extra = '' if settings.DEBUG else '.min'
i18n_name = self._get_language_code()
i18n_file = (
'%s%s.js' % (I18N_PATH, i18n_name),
) if i18n_name else ()
return forms.Media(
js=(
'admin/js/vendor/select2/select2.full.js',
'autocomplete_light/autocomplete_light%s.js' % extra,
'autocomplete_light/select2%s.js' % extra,
) + i18n_file,
css={
'screen': (
'admin/css/vendor/select2/select2%s.css' % extra,
'admin/css/autocomplete.css',
'autocomplete_light/select2.css',
),
},
)
autocomplete_function = 'select2'
[docs]
class Select2Multiple(Select2WidgetMixin, SelectMultiple):
"""Select2Multiple widget for regular choices."""
[docs]
class ListSelect2(WidgetMixin, Select2WidgetMixin, forms.Select):
"""Select widget for regular choices and Select2."""
[docs]
class ModelSelect2(QuerySetSelectMixin,
Select2WidgetMixin,
forms.Select):
"""Select widget for QuerySet choices and Select2."""
[docs]
class ModelSelect2Multiple(QuerySetSelectMixin,
Select2WidgetMixin,
forms.SelectMultiple):
"""SelectMultiple widget for QuerySet choices and Select2."""
[docs]
class TagSelect2(WidgetMixin,
Select2WidgetMixin,
forms.SelectMultiple):
"""Select2 in tag mode."""
[docs]
def build_attrs(self, *args, **kwargs):
"""Automatically set data-tags=1."""
attrs = super(TagSelect2, self).build_attrs(*args, **kwargs)
attrs.setdefault('data-tags', 1)
return attrs
[docs]
def value_from_datadict(self, data, files, name):
"""Return a comma-separated list of options.
This is needed because Select2 uses a multiple select even in tag mode,
and the model field expects a comma-separated list of tags.
"""
values = super(TagSelect2, self).value_from_datadict(data, files, name)
return ','.join(values)
[docs]
def option_value(self, value):
"""Return the HTML option value attribute for a value."""
return value
[docs]
def format_value(self, value):
"""Return the list of HTML option values for a form field value."""
if not isinstance(value, (tuple, list)):
value = [value]
values = set()
for v in value:
if not v:
continue
v = v.split(',') if isinstance(v, str) else v
v = [v] if not is_iterable(v) else v
for t in v:
values.add(self.option_value(t))
return values
[docs]
def options(self, name, value, attrs=None):
"""Return only select options."""
# When the data hasn't validated, we get the raw input
if isinstance(value, str):
value = value.split(',')
for v in value:
if not v:
continue
real_values = v.split(',') if hasattr(v, 'split') else v
real_values = [real_values] if not is_iterable(real_values) else real_values
for rv in real_values:
yield self.option_value(rv)
[docs]
def optgroups(self, name, value, attrs=None):
"""Return a list of one optgroup and selected values."""
default = (None, [], 0)
groups = [default]
for i, v in enumerate(self.options(name, value, attrs)):
default[1].append(
self.create_option(v, v, v, True, i)
)
return groups