Upgrade to select2 4.0

This commit is contained in:
Johannes Hoppe 2015-09-06 05:23:24 +02:00
parent 95297a362e
commit 6dad55eb11
28 changed files with 152 additions and 5357 deletions

View file

@ -55,10 +55,10 @@ Light widgets are normally named, i.e. there is no
:py:class:`.Select2MultipleWidget`,
:py:class:`.HeavySelect2Widget`,
:py:class:`.HeavySelect2MultipleWidget`,
:py:class:`.AutoHeavySelect2Widget`,
:py:class:`.AutoHeavySelect2MultipleWidget`,
:py:class:`.ModelSelect2Widget`,
:py:class:`.ModelSelect2MultipleWidget`,
:py:class:`.HeavySelect2TagWidget`,
:py:class:`.AutoHeavySelect2TagWidget`
:py:class:`.ModelSelect2TagWidget`
`Read more`_

View file

@ -2,27 +2,19 @@
"""Contains all the Django widgets for Select2."""
from __future__ import absolute_import, unicode_literals
import json
import logging
import re
from functools import reduce
from itertools import chain
from django import forms
from django.core import signing
from django.core.urlresolvers import reverse
from django.core.validators import EMPTY_VALUES
from django.core.urlresolvers import reverse_lazy
from django.db.models import Q
from django.utils.datastructures import MergeDict, MultiValueDict
from django.utils.encoding import force_text
from django.utils.safestring import mark_safe
from django.utils.six import text_type
from .cache import cache
from .conf import settings
from .media import (
get_select2_css_libs, get_select2_heavy_js_libs, get_select2_js_libs
)
logger = logging.getLogger(__name__)
@ -36,193 +28,19 @@ class Select2Mixin(object):
The base mixin of all Select2 widgets.
This mixin is responsible for rendering the necessary
JavaScript and CSS codes which turns normal ``<select>``
markups into Select2 choice list.
The following Select2 options are added by this mixin:-
* minimumResultsForSearch: ``6``
* placeholder: ``''``
* allowClear: ``True``
* multiple: ``False``
* closeOnSelect: ``False``
.. note:: Many of them would be removed by sub-classes depending on requirements.
data attributes for select2 as well as adding the static
form media.
"""
# For details on these options refer: http://ivaynberg.github.com/select2/#documentation
options = {
'minimumResultsForSearch': 6, # Only applicable for single value select.
'placeholder': '', # Empty text label
'allowClear': True, # Not allowed when field is multiple since there each value has a clear button.
'multiple': False, # Not allowed when attached to <select>
'closeOnSelect': False,
}
"""
The options listed here are rendered as JS map and passed to Select2 JS code.
Complete description of these options are available in Select2_ JS' site.
.. _Select2: http://ivaynberg.github.com/select2/#documentation.
"""
def __init__(self, **kwargs):
"""
Constructor of the class.
The following additional kwarg is allowed:-
:param select2_options: This is similar to standard Django way to pass extra attributes to widgets.
This is meant to override values of existing :py:attr:`.options`.
Example::
class MyForm(ModelForm):
class Meta:
model = MyModel
widgets = {
'name': Select2WidgetName(select2_options={
'minimumResultsForSearch': 10,
'closeOnSelect': True,
})
}
.. tip:: You cannot introduce new options using this.
For that you should sub-class and override
:py:meth:`.init_options`. The reason for this is,
few options are not compatible with each other
or are not applicable in some scenarios. For example,
when Select2 is attached to a ``<select>`` tag,
it can detect if it is being used with a single or
multiple values from that tag itself. If you specified the
``multiple`` option in this case, it would not only be
useless but an error from Select2 JS' point of view.
There are other such intricacies, based on which
some options are removed. By enforcing this
restriction we make sure to not break the code by
passing some wrong concoction of options.
.. tip:: According to the select2 documentation, in order to
get the ``placeholder`` and ``allowClear``
settings working, you have to specify an empty
``<option></option>`` as the first entry in your
``<select>`` list. Otherwise the field will be
rendered without a placeholder and the clear feature
will stay disabled.
:type select2_options: :py:obj:`dict` or None
"""
# Making an instance specific copy
self.options = dict(self.options)
select2_options = kwargs.pop('select2_options', None)
if select2_options:
for name, value in select2_options.items():
self.options[name] = value
self.init_options()
super(Select2Mixin, self).__init__(**kwargs)
def init_options(self):
"""
Initialize options.
Sub-classes can use this to suppress or override options passed to Select2 JS library.
Example::
def init_options(self):
self.options['createSearchChoice'] = 'Your_js_function'
In the above example we are setting ``Your_js_function``
as Select2's ``createSearchChoice`` function.
"""
pass
def set_placeholder(self, val):
"""
Placeholder is a value which Select2 JS library shows when nothing is selected.
This should be string.
:return: None
"""
self.options['placeholder'] = val
def get_options(self):
"""
Return select2 js options.
:return: Dictionary of options to be passed to Select2 JS.
:rtype: :py:obj:`dict`
"""
options = dict(self.options)
if options.get('allowClear', None) is not None:
options['allowClear'] = not self.is_required
if options.get('placeholder'):
options['placeholder'] = force_text(options['placeholder'])
return options
def render_js_code(self, id_, *args):
"""
Render the ``<script>`` block which contains the JS code for this widget.
:return: The rendered JS code enclosed inside ``<script>`` block.
:rtype: :py:obj:`unicode`
"""
if id_:
return self.render_js_script(self.render_inner_js_code(id_, *args))
return ''
def render_js_script(self, inner_code):
"""
Wrap ``inner_code`` string inside a code block.
Example::
<script type="text/javascript">
jQuery(function ($) {
// inner_code here
});
</script>
:rtype: :py:obj:`unicode`
"""
return """
<script type="text/javascript">
jQuery(function ($) {
%s
});
</script>
""" % inner_code
def render_inner_js_code(self, id_, *args):
"""
Render all the JS code required for this widget.
:return: The rendered JS code which will be later enclosed inside ``<script>`` block.
:rtype: :py:obj:`unicode`
"""
options = json.dumps(self.get_options())
options = options.replace('"*START*', '').replace('*END*"', '')
js = 'var hashedSelector = "#" + "%s";' % id_
js += '$(hashedSelector).select2(%s);' % (options)
return js
def render(self, name, value, attrs=None, choices=()):
args = [name, value, attrs]
if choices:
args.append(choices)
s = text_type(super(Select2Mixin, self).render(*args)) # Thanks to @ouhouhsami Issue#1
s += self.media.render()
final_attrs = self.build_attrs(attrs)
id_ = final_attrs.get('id', None)
s += self.render_js_code(id_, name, value, attrs, choices)
return mark_safe(s)
def build_attrs(self, extra_attrs=None, **kwargs):
attrs = super(Select2Mixin, self).build_attrs(extra_attrs=None, **kwargs)
attrs.setdefault('data-allowClear', 'true' if self.is_required else 'false')
attrs.setdefault('data-closeOnSelect', 'false')
if 'class' in attrs:
attrs['class'] += ' django-select2'
else:
attrs['class'] = 'django-select2'
return attrs
def _get_media(self):
"""
@ -235,217 +53,102 @@ class Select2Mixin(object):
https://docs.djangoproject.com/en/1.8/topics/forms/media/#media-as-a-dynamic-property
"""
return forms.Media(
js=get_select2_js_libs(),
css={'screen': get_select2_css_libs(light=True)}
js=('//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.min.js',
'django_select2/django_select2.js'),
css={'screen': ('//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.min.css',)}
)
media = property(_get_media)
class Select2Widget(Select2Mixin, forms.Select):
"""
Drop-in Select2 replacement for :py:class:`forms.Select`.
Following Select2 option from :py:attr:`.Select2Mixin.options` is removed:-
* multiple
"""
def init_options(self):
self.options.pop('multiple', None)
def render_options(self, choices, selected_choices):
all_choices = chain(self.choices, choices)
if not self.is_required \
and len([value for value, txt in all_choices if value == '']) == 0:
# Checking if list already has empty choice
# as in the case of Model based Light fields.
choices = list(choices)
choices.append(('', '', )) # Adding an empty choice
return super(Select2Widget, self).render_options(choices, selected_choices)
pass
class Select2MultipleWidget(Select2Mixin, forms.SelectMultiple):
"""
Drop-in Select2 replacement for :py:class:`forms.SelectMultiple`.
Following Select2 options from :py:attr:`.Select2Mixin.options` are removed:-
* multiple
* allowClear
* minimumResultsForSearch
"""
def init_options(self):
self.options.pop('multiple', None)
self.options.pop('allowClear', None)
self.options.pop('minimumResultsForSearch', None)
pass
# ## Specialized Multiple Hidden Input Widget ##
class HeavySelect2Mixin(object):
"""Mixin that adds select2's ajax options and registers itself on django's cache."""
def __init__(self, **kwargs):
self.data_view = kwargs.pop('data_view')
self.userGetValTextFuncName = kwargs.pop('userGetValTextFuncName', 'null')
super(HeavySelect2Mixin, self).__init__(**kwargs)
def get_url(self):
return reverse_lazy(self.data_view)
def build_attrs(self, extra_attrs=None, **kwargs):
attrs = super(HeavySelect2Mixin, self).build_attrs(extra_attrs, **kwargs)
# encrypt instance Id
widget_id = signing.dumps(id(self))
# add widget object to cache
cache.set(self._get_cache_key(), self)
attrs['data-field_id'] = widget_id
attrs.setdefault('data-ajax--url', self.get_url())
attrs.setdefault('data-ajax--cache', "true")
attrs.setdefault('data-ajax--type', "GET")
attrs.setdefault('data-minimumInputLength', 2)
return attrs
def _get_cache_key(self):
return "%s%s" % (settings.SELECT2_CACHE_PREFIX, id(self))
def value_from_datadict(self, *args, **kwargs):
return super(HeavySelect2Mixin, self).value_from_datadict(*args, **kwargs)
def render_options(self, choices, selected_choices):
selected_choices = set(force_text(v) for v in selected_choices)
output = []
for option_value, option_label in selected_choices:
output.append(self.render_option(selected_choices, option_value, option_label))
return '\n'.join(output)
class MultipleSelect2HiddenInput(forms.TextInput):
"""
Multiple hidden input for Select2.
This is a specialized multiple Hidden Input widget. This includes a special
JS component which renders multiple Hidden Input boxes as there are values.
So, if user suppose chooses values 1, 4 and 9 then Select2 would would write them
to the primary hidden input. The JS component of this widget will read that value and
will render three more hidden input boxes each with values 1, 4 and 9 respectively.
They will all share the name of this field, and the name of the primary source
hidden input would be removed. This way, when submitted all the selected values
would be available as list.
"""
def render(self, name, value, attrs=None, choices=()):
attrs = self.build_attrs(attrs, multiple='multiple')
s = text_type(super(MultipleSelect2HiddenInput, self).render(name, "", attrs))
id_ = attrs.get('id', None)
if id_:
jscode = ''
if value:
jscode = '$("#%s").val(django_select2.convertArrToStr(%s));' % (id_, json.dumps(value))
jscode += "django_select2.initMultipleHidden($('#%s'));" % id_
s += self.render_js_script(jscode)
return mark_safe(s)
def value_from_datadict(self, data, files, name):
if isinstance(data, (MultiValueDict, MergeDict)):
return data.getlist(name)
return data.get(name, None)
def _has_changed(self, initial, data):
if initial is None:
initial = []
if data is None:
data = []
if len(initial) != len(data):
return True
initial_set = set([force_text(value) for value in initial])
data_set = set([force_text(value) for value in data])
return data_set != initial_set
@property
def is_hidden(self):
# we return false because even if input_type is 'hidden'
# , the final field will be displayed by javascript
# and we want label and other layout elements.
return False
class HeavySelect2Widget(HeavySelect2Mixin, forms.Select):
pass
# ## Heavy mixins and widgets ###
class HeavySelect2MultipleWidget(HeavySelect2Mixin, forms.SelectMultiple):
pass
class HeavySelect2Mixin(Select2Mixin):
"""
The base mixin of all Heavy Select2 widgets. It sub-classes :py:class:`Select2Mixin`.
class HeavySelect2TagWidget(HeavySelect2MultipleWidget):
def build_attrs(self, extra_attrs=None, **kwargs):
attrs = super(HeavySelect2TagWidget, self).build_attrs(self, extra_attrs, **kwargs)
attrs['data-minimumInputLength'] = 1
attrs['data-tags'] = 'true'
attrs['data-tokenSeparators'] = [",", " "]
return attrs
This mixin adds more Select2 options to :py:attr:`.Select2Mixin.options`. These are:-
* minimumInputLength: ``2``
* initSelection: ``'django_select2.onInit'``
* ajax:
* dataType: ``'json'``
* quietMillis: ``100``
* data: ``'django_select2.get_url_params'``
* results: ``'django_select2.process_results'``
# Auto Heavy widgets
.. tip:: You can override these options by passing ``select2_options``
kwarg to :py:meth:`.__init__`.
"""
class ModelSelect2Mixin(object):
"""Mixin for """
model = None
queryset = None
search_fields = []
max_results = 25
def __init__(self, **kwargs):
"""
Constructor of the class.
The following kwargs are allowed:-
:param data_view: A :py:class:`~.views.Select2View` sub-class which
can respond to this widget's Ajax queries.
:type data_view: :py:class:`django.views.generic.base.View` or None
:param data_url: Url which will respond to Ajax queries with JSON object.
:type data_url: :py:obj:`str` or None
.. tip:: When ``data_view`` is provided then it is converted into an URL using
:py:func:`~django.core.urlresolvers.reverse`.
.. warning:: Either of ``data_view`` or ``data_url`` must be specified,
otherwise :py:exc:`ValueError` will
be raised.
:param choices: The list of available choices.
If not provided then empty list is used instead.
It should be of the form -- ``[(val1, 'Label1'), (val2, 'Label2'), ...]``.
:type choices: :py:obj:`list` or :py:obj:`tuple`
:param userGetValTextFuncName: The name of the custom JS function which
you want to use to convert value to label.
In ``heavy_data.js``, ``django_select2.getValText()`` employs
the following logic to convert value to label :-
1. First check if the Select2 input field has ``txt`` attribute
set along with ``value``. If found then use it.
2. Otherwise, check if user has provided any custom method for this.
Then use that. If it returns a label then use it.
3. Otherwise, check the cached results. When the user searches
in the fields then all the returned responses from server,
which has the value and label mapping, are cached by ``heavy_data.js``.
:type userGetValTextFuncName: :py:obj:`str`
.. tip:: Since version 3.2.0, cookies or localStorage are no longer checked or used.
All :py:class:`~.field.HeavyChoiceField` must override
:py:meth:`~.fields.HeavyChoiceField.get_val_txt`.
If you are only using heavy widgets in your own fields
then you should override :py:meth:`.render_texts`.
"""
self.field = None
self.options = dict(self.options) # Making an instance specific copy
self.view = kwargs.pop('data_view', 'django_select2_central_json')
self.url = kwargs.pop('data_url', None)
self.userGetValTextFuncName = kwargs.pop('userGetValTextFuncName', 'null')
self.choices = kwargs.pop('choices', [])
def __init__(self, *args, **kwargs):
self.model = kwargs.pop('model', self.model)
self.queryset = kwargs.pop('queryset', self.queryset)
self.search_fields = kwargs.pop('search_fields', self.search_fields)
self.max_results = kwargs.pop('max_results', self.max_results)
self.options['ajax'] = {
'dataType': 'json',
'quietMillis': 100,
'data': ('*START*django_select2.runInContextHelper('
'django_select2.get_url_params, selector'
')*END*'),
'results': ('*START*django_select2.runInContextHelper('
'django_select2.process_results, selector'
')*END*'),
}
self.options['minimumInputLength'] = 2
self.options['initSelection'] = '*START*django_select2.onInit*END*'
super(HeavySelect2Mixin, self).__init__(**kwargs)
defaults = {'data_view': 'django_select2_central_json'}
defaults.update(kwargs)
super(ModelSelect2Mixin, self).__init__(*args, **defaults)
def filter_queryset(self, term):
"""
See :py:meth:`.views.Select2View.get_results`.
This implementation takes care of detecting if more results are available.
"""
qs = self.get_queryset()
search_fields = self.get_search_fields()
select = reduce(lambda x, y: Q(**{x: term}) | Q(**{y: term}), search_fields,
@ -472,314 +175,22 @@ class HeavySelect2Mixin(Select2Mixin):
return self.search_fields
raise NotImplementedError('%s, must implement "search_fields".' % self.__class__.__name__)
def render_texts(self, selected_choices, choices):
"""
Render a JS array with labels for the ``selected_choices``.
:param selected_choices: List of selected choices' values.
:type selected_choices: :py:obj:`list` or :py:obj:`tuple`
:param choices: Extra choices, if any. This is a list of tuples. In each tuple, the first
item is the choice value and the second item is choice label.
:type choices: :py:obj:`list` or :py:obj:`tuple`
:return: The rendered JS array code.
:rtype: :py:obj:`unicode`
"""
selected_choices = list(force_text(v) for v in selected_choices)
txts = []
all_choices = choices if choices else []
choices_dict = dict()
self_choices = self.choices
for val, txt in chain(self_choices, all_choices):
val = force_text(val)
choices_dict[val] = force_text(txt)
for val in selected_choices:
try:
txts.append(choices_dict[val])
except KeyError:
logger.error("Value '%s' is not a valid choice.", val)
if hasattr(self.field, '_get_val_txt') and selected_choices:
for val in selected_choices:
txt = self.field._get_val_txt(val)
if txt is not None:
txts.append(txt)
if txts:
return json.dumps(txts)
def get_options(self):
if self.url is None:
# We lazy resolve the view. By this time Url conf would been loaded fully.
self.url = reverse(self.view)
if self.options['ajax'].get('url', None) is None:
self.options['ajax']['url'] = self.url
return super(HeavySelect2Mixin, self).get_options()
def render_texts_for_value(self, id_, value, choices):
"""
Render the JS code which sets the ``txt`` attribute on the field.
It gets the array of lables from :py:meth:`.render_texts`.
:param id_: Id of the field. This can be used to get reference of this field's DOM in JS.
:type id_: :py:obj:`str`
:param value: Currently set value on the field.
:type value: Any
:param choices: Extra choices, if any. This is a list of tuples. In each tuple, the first
item is the choice value and the second item is choice label.
:type choices: :py:obj:`list` or :py:obj:`tuple`
:return: JS code which sets the ``txt`` attribute.
:rtype: :py:obj:`unicode`
"""
empty_values = getattr(self.field, 'empty_values', EMPTY_VALUES)
if value is not None and (self.field is None or value not in empty_values):
# Just like forms.Select.render() it assumes that value will be single valued.
values = [value]
texts = self.render_texts(values, choices)
if texts:
return "$('#%s').txt(%s);" % (id_, texts)
def render_inner_js_code(self, id_, name, value, attrs=None, choices=(), *args):
js = '$(hashedSelector).change(django_select2.onValChange).data("userGetValText", null);'
texts = self.render_texts_for_value(id_, value, choices)
if texts:
js += texts
js += super(HeavySelect2Mixin, self).render_inner_js_code(id_, name, value, attrs, choices, *args)
return js
def _get_cache_key(self):
return "%s%s" % (settings.SELECT2_CACHE_PREFIX, id(self))
def render(self, name, value, attrs={}, choices=()):
self.widget_id = signing.dumps(id(self))
cache.set(self._get_cache_key(), self)
attrs.setdefault('data-field_id', self.widget_id)
output = super(HeavySelect2Mixin, self).render(name, value, attrs, choices)
return output
def value_from_datadict(self, *args, **kwargs):
return super(HeavySelect2Mixin, self).value_from_datadict(*args, **kwargs)
def _get_media(self):
"""
Construct Media as a dynamic property.
This is essential because we need to check RENDER_SELECT2_STATICS
before returning our assets.
for more information:
https://docs.djangoproject.com/en/1.8/topics/forms/media/#media-as-a-dynamic-property
"""
return forms.Media(
js=get_select2_heavy_js_libs(),
css={'screen': get_select2_css_libs()}
)
media = property(_get_media)
class HeavySelect2Widget(HeavySelect2Mixin, forms.TextInput):
"""
Single selection heavy widget.
Following Select2 option from :py:attr:`.Select2Mixin.options` is added or set:-
* multiple: ``False``
"""
def init_options(self):
self.options['multiple'] = False
@property
def is_hidden(self):
# we return false because even if input_type is 'hidden'
# , the final field will be displayed by javascript
# and we want label and other layout elements.
return False
def render_inner_js_code(self, id_, *args):
field_id = self.widget_id
fieldset_id = re.sub(r'-\d+-', '_', id_).replace('-', '_')
if '__prefix__' in id_:
return ''
else:
js = '''
window.django_select2.%s = function (selector, fieldID) {
var hashedSelector = "#" + selector;
''' % (fieldset_id)
js += super(HeavySelect2Widget, self).render_inner_js_code(id_, *args)
js += '};'
js += 'django_select2.%s("%s", "%s");' % (fieldset_id, id_, field_id)
return js
class HeavySelect2MultipleWidget(HeavySelect2Mixin, MultipleSelect2HiddenInput):
"""
Multiple selection heavy widget.
Following Select2 options from :py:attr:`.Select2Mixin.options` are removed:-
* allowClear
* minimumResultsForSearch
Following Select2 options from :py:attr:`.Select2Mixin.options` are added or set:-
* multiple: ``True``
* separator: ``django_select2.MULTISEPARATOR``
"""
def init_options(self):
self.options['multiple'] = True
self.options.pop('allowClear', None)
self.options.pop('minimumResultsForSearch', None)
self.options['separator'] = '*START*django_select2.MULTISEPARATOR*END*'
def render_texts_for_value(self, id_, value, choices):
"""
Render the JS code which sets the ``txt`` attribute on the field.
It gets the array of lables from :py:meth:`.render_texts`.
:param id_: Id of the field. This can be used to get reference of this field's DOM in JS.
:type id_: :py:obj:`str`
:param value: **List** of currently set value on the field.
:type value: :py:obj:`list`
:param choices: Extra choices, if any. This is a list of tuples. In each tuple, the first
item is the choice value and the second item is choice label.
:type choices: :py:obj:`list` or :py:obj:`tuple`
:return: JS code which sets the ``txt`` attribute.
:rtype: :py:obj:`unicode`
"""
# Just like forms.SelectMultiple.render()
# it assumes that value will be multi-valued (list).
if value:
texts = self.render_texts(value, choices)
if texts:
return '$("#%s").txt(%s);' % (id_, texts)
def render_inner_js_code(self, id_, *args):
field_id = self.widget_id
fieldset_id = re.sub(r'-\d+-', '_', id_).replace('-', '_')
if '__prefix__' in id_:
return ''
else:
js = '''
window.django_select2.%s = function (selector, fieldID) {
var hashedSelector = "#" + selector;
''' % (fieldset_id)
js += super(HeavySelect2MultipleWidget, self).render_inner_js_code(id_, *args)
js += '};'
js += 'django_select2.%s("%s", "%s");' % (fieldset_id, id_, field_id)
return js
class HeavySelect2TagWidget(HeavySelect2MultipleWidget):
"""
Heavy widget with tagging support.
Based on :py:class:`HeavySelect2MultipleWidget`,
unlike other widgets this allows users to create new options (tags).
Following Select2 options from :py:attr:`.Select2Mixin.options` are removed:-
* allowClear
* minimumResultsForSearch
* closeOnSelect
Following Select2 options from :py:attr:`.Select2Mixin.options` are added or set:-
* multiple: ``True``
* separator: ``django_select2.MULTISEPARATOR``
* tags: ``True``
* tokenSeparators: ``,`` and `` ``
* createSearchChoice: ``django_select2.createSearchChoice``
* minimumInputLength: ``1``
"""
def init_options(self):
super(HeavySelect2TagWidget, self).init_options()
self.options.pop('closeOnSelect', None)
self.options['minimumInputLength'] = 1
self.options['tags'] = True
self.options['tokenSeparators'] = [",", " "]
self.options['createSearchChoice'] = '*START*django_select2.createSearchChoice*END*'
def render_inner_js_code(self, id_, *args):
field_id = self.widget_id
fieldset_id = re.sub(r'-\d+-', '_', id_).replace('-', '_')
if '__prefix__' in id_:
return ''
else:
js = '''
window.django_select2.%s = function (selector, fieldID) {
var hashedSelector = "#" + selector;
''' % (fieldset_id)
js += super(HeavySelect2TagWidget, self).render_inner_js_code(id_, *args)
js += '};'
js += 'django_select2.%s("%s", "%s");' % (fieldset_id, id_, field_id)
return js
# ## Auto Heavy widgets ##
class AutoHeavySelect2Mixin(object):
"""
This mixin is needed for Auto heavy fields.
This mixin adds extra JS code to notify the field's DOM object of the generated id.
The generated id is not the same as the ``id`` attribute of the field's HTML markup.
This id is generated by :py:func:`~.util.register_field` when the Auto field is registered.
The client side (DOM) sends this id along with the Ajax request, so that the central
view can identify which field should be used to serve the request.
The js call to dynamically add the `django_select2` is as follows::
django_select2.id_cities('id_cities', django_select2.id_cities_field_id);
For an inline formset::
django_select2.id_musician_set_name(
'id_musician_set-0-name', django_select2.id_musician_set_name_field_id);
"""
def __init__(self, *args, **kwargs):
kwargs['data_view'] = "django_select2_central_json"
super(AutoHeavySelect2Mixin, self).__init__(*args, **kwargs)
class AutoHeavySelect2Widget(AutoHeavySelect2Mixin, HeavySelect2Widget):
class ModelSelect2Widget(ModelSelect2Mixin, HeavySelect2Widget):
"""Auto version of :py:class:`.HeavySelect2Widget`."""
pass
class AutoHeavySelect2MultipleWidget(AutoHeavySelect2Mixin, HeavySelect2MultipleWidget):
class ModelSelect2MultipleWidget(ModelSelect2Mixin, HeavySelect2MultipleWidget):
"""Auto version of :py:class:`.HeavySelect2MultipleWidget`."""
pass
class AutoHeavySelect2TagWidget(AutoHeavySelect2Mixin, HeavySelect2TagWidget):
class ModelSelect2TagWidget(ModelSelect2Mixin, HeavySelect2TagWidget):
"""Auto version of :py:class:`.HeavySelect2TagWidget`."""

View file

@ -1,48 +0,0 @@
from django.contrib.staticfiles.templatetags.staticfiles import static
from .conf import settings
def django_select2_static(file):
return static('django_select2/' + file)
def get_select2_js_libs():
if settings.DEBUG:
js_file = 'js/select2.js'
else:
js_file = 'js/select2.min.js'
return django_select2_static(js_file),
def get_select2_heavy_js_libs():
libs = get_select2_js_libs()
if settings.DEBUG:
js_file = 'js/heavy_data.js'
else:
js_file = 'js/heavy_data.min.js'
return libs + (django_select2_static(js_file), )
def get_select2_css_libs(light=False):
if settings.DEBUG:
if light:
css_files = 'css/select2.css',
else:
css_files = 'css/select2.css', 'css/extra.css'
if settings.SELECT2_BOOTSTRAP:
css_files += 'css/select2-bootstrap.css',
else:
if settings.SELECT2_BOOTSTRAP:
if light:
css_files = 'css/select2-bootstrapped.min.css',
else:
css_files = 'css/all-bootstrapped.min.css',
else:
if light:
css_files = 'css/select2.min.css',
else:
css_files = 'css/all.min.css',
return [django_select2_static(f) for f in css_files]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,9 +0,0 @@
.error a.select2-choice {
border: 1px solid #B94A48;
}
.select2-container {
min-width: 150px;
}
.select2-container.select2-container-multi {
width: 300px;
}

View file

@ -1,87 +0,0 @@
.form-control .select2-choice {
border: 0;
border-radius: 2px;
}
.form-control .select2-choice .select2-arrow {
border-radius: 0 2px 2px 0;
}
.form-control.select2-container {
height: auto !important;
padding: 0;
}
.form-control.select2-container.select2-dropdown-open {
border-color: #5897FB;
border-radius: 3px 3px 0 0;
}
.form-control .select2-container.select2-dropdown-open .select2-choices {
border-radius: 3px 3px 0 0;
}
.form-control.select2-container .select2-choices {
border: 0 !important;
border-radius: 3px;
}
.control-group.warning .select2-container .select2-choice,
.control-group.warning .select2-container .select2-choices,
.control-group.warning .select2-container-active .select2-choice,
.control-group.warning .select2-container-active .select2-choices,
.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choice,
.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choices,
.control-group.warning .select2-container-multi.select2-container-active .select2-choices {
border: 1px solid #C09853 !important;
}
.control-group.warning .select2-container .select2-choice div {
border-left: 1px solid #C09853 !important;
background: #FCF8E3 !important;
}
.control-group.error .select2-container .select2-choice,
.control-group.error .select2-container .select2-choices,
.control-group.error .select2-container-active .select2-choice,
.control-group.error .select2-container-active .select2-choices,
.control-group.error .select2-dropdown-open.select2-drop-above .select2-choice,
.control-group.error .select2-dropdown-open.select2-drop-above .select2-choices,
.control-group.error .select2-container-multi.select2-container-active .select2-choices {
border: 1px solid #B94A48 !important;
}
.control-group.error .select2-container .select2-choice div {
border-left: 1px solid #B94A48 !important;
background: #F2DEDE !important;
}
.control-group.info .select2-container .select2-choice,
.control-group.info .select2-container .select2-choices,
.control-group.info .select2-container-active .select2-choice,
.control-group.info .select2-container-active .select2-choices,
.control-group.info .select2-dropdown-open.select2-drop-above .select2-choice,
.control-group.info .select2-dropdown-open.select2-drop-above .select2-choices,
.control-group.info .select2-container-multi.select2-container-active .select2-choices {
border: 1px solid #3A87AD !important;
}
.control-group.info .select2-container .select2-choice div {
border-left: 1px solid #3A87AD !important;
background: #D9EDF7 !important;
}
.control-group.success .select2-container .select2-choice,
.control-group.success .select2-container .select2-choices,
.control-group.success .select2-container-active .select2-choice,
.control-group.success .select2-container-active .select2-choices,
.control-group.success .select2-dropdown-open.select2-drop-above .select2-choice,
.control-group.success .select2-dropdown-open.select2-drop-above .select2-choices,
.control-group.success .select2-container-multi.select2-container-active .select2-choices {
border: 1px solid #468847 !important;
}
.control-group.success .select2-container .select2-choice div {
border-left: 1px solid #468847 !important;
background: #DFF0D8 !important;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,704 +0,0 @@
/*
Version: 3.5.2 Timestamp: Sat Nov 1 14:43:36 EDT 2014
*/
.select2-container {
margin: 0;
position: relative;
display: inline-block;
/* inline-block for ie7 */
zoom: 1;
*display: inline;
vertical-align: middle;
}
.select2-container,
.select2-drop,
.select2-search,
.select2-search input {
/*
Force border-box so that % widths fit the parent
container without overlap because of margin/padding.
More Info : http://www.quirksmode.org/css/box.html
*/
-webkit-box-sizing: border-box; /* webkit */
-moz-box-sizing: border-box; /* firefox */
box-sizing: border-box; /* css3 */
}
.select2-container .select2-choice {
display: block;
height: 26px;
padding: 0 0 0 8px;
overflow: hidden;
position: relative;
border: 1px solid #aaa;
white-space: nowrap;
line-height: 26px;
color: #444;
text-decoration: none;
border-radius: 4px;
background-clip: padding-box;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-color: #fff;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff));
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%);
background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0);
background-image: linear-gradient(to top, #eee 0%, #fff 50%);
}
html[dir="rtl"] .select2-container .select2-choice {
padding: 0 8px 0 0;
}
.select2-container.select2-drop-above .select2-choice {
border-bottom-color: #aaa;
border-radius: 0 0 4px 4px;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff));
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%);
background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);
background-image: linear-gradient(to bottom, #eee 0%, #fff 90%);
}
.select2-container.select2-allowclear .select2-choice .select2-chosen {
margin-right: 42px;
}
.select2-container .select2-choice > .select2-chosen {
margin-right: 26px;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
float: none;
width: auto;
}
html[dir="rtl"] .select2-container .select2-choice > .select2-chosen {
margin-left: 26px;
margin-right: 0;
}
.select2-container .select2-choice abbr {
display: none;
width: 12px;
height: 12px;
position: absolute;
right: 24px;
top: 8px;
font-size: 1px;
text-decoration: none;
border: 0;
background: url('select2.png') right top no-repeat;
cursor: pointer;
outline: 0;
}
.select2-container.select2-allowclear .select2-choice abbr {
display: inline-block;
}
.select2-container .select2-choice abbr:hover {
background-position: right -11px;
cursor: pointer;
}
.select2-drop-mask {
border: 0;
margin: 0;
padding: 0;
position: fixed;
left: 0;
top: 0;
min-height: 100%;
min-width: 100%;
height: auto;
width: auto;
opacity: 0;
z-index: 9998;
/* styles required for IE to work */
background-color: #fff;
filter: alpha(opacity=0);
}
.select2-drop {
width: 100%;
margin-top: -1px;
position: absolute;
z-index: 9999;
top: 100%;
background: #fff;
color: #000;
border: 1px solid #aaa;
border-top: 0;
border-radius: 0 0 4px 4px;
-webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
}
.select2-drop.select2-drop-above {
margin-top: 1px;
border-top: 1px solid #aaa;
border-bottom: 0;
border-radius: 4px 4px 0 0;
-webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
}
.select2-drop-active {
border: 1px solid #5897fb;
border-top: none;
}
.select2-drop.select2-drop-above.select2-drop-active {
border-top: 1px solid #5897fb;
}
.select2-drop-auto-width {
border-top: 1px solid #aaa;
width: auto;
}
.select2-drop-auto-width .select2-search {
padding-top: 4px;
}
.select2-container .select2-choice .select2-arrow {
display: inline-block;
width: 18px;
height: 100%;
position: absolute;
right: 0;
top: 0;
border-left: 1px solid #aaa;
border-radius: 0 4px 4px 0;
background-clip: padding-box;
background: #ccc;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0);
background-image: linear-gradient(to top, #ccc 0%, #eee 60%);
}
html[dir="rtl"] .select2-container .select2-choice .select2-arrow {
left: 0;
right: auto;
border-left: none;
border-right: 1px solid #aaa;
border-radius: 4px 0 0 4px;
}
.select2-container .select2-choice .select2-arrow b {
display: block;
width: 100%;
height: 100%;
background: url('select2.png') no-repeat 0 1px;
}
html[dir="rtl"] .select2-container .select2-choice .select2-arrow b {
background-position: 2px 1px;
}
.select2-search {
display: inline-block;
width: 100%;
min-height: 26px;
margin: 0;
padding-left: 4px;
padding-right: 4px;
position: relative;
z-index: 10000;
white-space: nowrap;
}
.select2-search input {
width: 100%;
height: auto !important;
min-height: 26px;
padding: 4px 20px 4px 5px;
margin: 0;
outline: 0;
font-family: sans-serif;
font-size: 1em;
border: 1px solid #aaa;
border-radius: 0;
-webkit-box-shadow: none;
box-shadow: none;
background: #fff url('select2.png') no-repeat 100% -22px;
background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
background: url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
}
html[dir="rtl"] .select2-search input {
padding: 4px 5px 4px 20px;
background: #fff url('select2.png') no-repeat -37px -22px;
background: url('select2.png') no-repeat -37px -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
background: url('select2.png') no-repeat -37px -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
background: url('select2.png') no-repeat -37px -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
background: url('select2.png') no-repeat -37px -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
}
.select2-drop.select2-drop-above .select2-search input {
margin-top: 4px;
}
.select2-search input.select2-active {
background: #fff url('select2-spinner.gif') no-repeat 100%;
background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
}
.select2-container-active .select2-choice,
.select2-container-active .select2-choices {
border: 1px solid #5897fb;
outline: none;
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
}
.select2-dropdown-open .select2-choice {
border-bottom-color: transparent;
-webkit-box-shadow: 0 1px 0 #fff inset;
box-shadow: 0 1px 0 #fff inset;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
background-color: #eee;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee));
background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%);
background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
background-image: linear-gradient(to top, #fff 0%, #eee 50%);
}
.select2-dropdown-open.select2-drop-above .select2-choice,
.select2-dropdown-open.select2-drop-above .select2-choices {
border: 1px solid #5897fb;
border-top-color: transparent;
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee));
background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%);
background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
background-image: linear-gradient(to bottom, #fff 0%, #eee 50%);
}
.select2-dropdown-open .select2-choice .select2-arrow {
background: transparent;
border-left: none;
filter: none;
}
html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow {
border-right: none;
}
.select2-dropdown-open .select2-choice .select2-arrow b {
background-position: -18px 1px;
}
html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow b {
background-position: -16px 1px;
}
.select2-hidden-accessible {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
/* results */
.select2-results {
max-height: 200px;
padding: 0 0 0 4px;
margin: 4px 4px 4px 0;
position: relative;
overflow-x: hidden;
overflow-y: auto;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
html[dir="rtl"] .select2-results {
padding: 0 4px 0 0;
margin: 4px 0 4px 4px;
}
.select2-results ul.select2-result-sub {
margin: 0;
padding-left: 0;
}
.select2-results li {
list-style: none;
display: list-item;
background-image: none;
}
.select2-results li.select2-result-with-children > .select2-result-label {
font-weight: bold;
}
.select2-results .select2-result-label {
padding: 3px 7px 4px;
margin: 0;
cursor: pointer;
min-height: 1em;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.select2-results-dept-1 .select2-result-label { padding-left: 20px }
.select2-results-dept-2 .select2-result-label { padding-left: 40px }
.select2-results-dept-3 .select2-result-label { padding-left: 60px }
.select2-results-dept-4 .select2-result-label { padding-left: 80px }
.select2-results-dept-5 .select2-result-label { padding-left: 100px }
.select2-results-dept-6 .select2-result-label { padding-left: 110px }
.select2-results-dept-7 .select2-result-label { padding-left: 120px }
.select2-results .select2-highlighted {
background: #3875d7;
color: #fff;
}
.select2-results li em {
background: #feffde;
font-style: normal;
}
.select2-results .select2-highlighted em {
background: transparent;
}
.select2-results .select2-highlighted ul {
background: #fff;
color: #000;
}
.select2-results .select2-no-results,
.select2-results .select2-searching,
.select2-results .select2-ajax-error,
.select2-results .select2-selection-limit {
background: #f4f4f4;
display: list-item;
padding-left: 5px;
}
/*
disabled look for disabled choices in the results dropdown
*/
.select2-results .select2-disabled.select2-highlighted {
color: #666;
background: #f4f4f4;
display: list-item;
cursor: default;
}
.select2-results .select2-disabled {
background: #f4f4f4;
display: list-item;
cursor: default;
}
.select2-results .select2-selected {
display: none;
}
.select2-more-results.select2-active {
background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%;
}
.select2-results .select2-ajax-error {
background: rgba(255, 50, 50, .2);
}
.select2-more-results {
background: #f4f4f4;
display: list-item;
}
/* disabled styles */
.select2-container.select2-container-disabled .select2-choice {
background-color: #f4f4f4;
background-image: none;
border: 1px solid #ddd;
cursor: default;
}
.select2-container.select2-container-disabled .select2-choice .select2-arrow {
background-color: #f4f4f4;
background-image: none;
border-left: 0;
}
.select2-container.select2-container-disabled .select2-choice abbr {
display: none;
}
/* multiselect */
.select2-container-multi .select2-choices {
height: auto !important;
height: 1%;
margin: 0;
padding: 0 5px 0 0;
position: relative;
border: 1px solid #aaa;
cursor: text;
overflow: hidden;
background-color: #fff;
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff));
background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%);
background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%);
background-image: linear-gradient(to bottom, #eee 1%, #fff 15%);
}
html[dir="rtl"] .select2-container-multi .select2-choices {
padding: 0 0 0 5px;
}
.select2-locked {
padding: 3px 5px 3px 5px !important;
}
.select2-container-multi .select2-choices {
min-height: 26px;
}
.select2-container-multi.select2-container-active .select2-choices {
border: 1px solid #5897fb;
outline: none;
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
}
.select2-container-multi .select2-choices li {
float: left;
list-style: none;
}
html[dir="rtl"] .select2-container-multi .select2-choices li
{
float: right;
}
.select2-container-multi .select2-choices .select2-search-field {
margin: 0;
padding: 0;
white-space: nowrap;
}
.select2-container-multi .select2-choices .select2-search-field input {
padding: 5px;
margin: 1px 0;
font-family: sans-serif;
font-size: 100%;
color: #666;
outline: 0;
border: 0;
-webkit-box-shadow: none;
box-shadow: none;
background: transparent !important;
}
.select2-container-multi .select2-choices .select2-search-field input.select2-active {
background: #fff url('select2-spinner.gif') no-repeat 100% !important;
}
.select2-default {
color: #999 !important;
}
.select2-container-multi .select2-choices .select2-search-choice {
padding: 3px 5px 3px 18px;
margin: 3px 0 3px 5px;
position: relative;
line-height: 13px;
color: #333;
cursor: default;
border: 1px solid #aaaaaa;
border-radius: 3px;
-webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
background-clip: padding-box;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-color: #e4e4e4;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0);
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee));
background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
background-image: linear-gradient(to bottom, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
}
html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice
{
margin: 3px 5px 3px 0;
padding: 3px 18px 3px 5px;
}
.select2-container-multi .select2-choices .select2-search-choice .select2-chosen {
cursor: default;
}
.select2-container-multi .select2-choices .select2-search-choice-focus {
background: #d4d4d4;
}
.select2-search-choice-close {
display: block;
width: 12px;
height: 13px;
position: absolute;
right: 3px;
top: 4px;
font-size: 1px;
outline: none;
background: url('select2.png') right top no-repeat;
}
html[dir="rtl"] .select2-search-choice-close {
right: auto;
left: 3px;
}
.select2-container-multi .select2-search-choice-close {
left: 3px;
}
html[dir="rtl"] .select2-container-multi .select2-search-choice-close {
left: auto;
right: 2px;
}
.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
background-position: right -11px;
}
.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
background-position: right -11px;
}
/* disabled styles */
.select2-container-multi.select2-container-disabled .select2-choices {
background-color: #f4f4f4;
background-image: none;
border: 1px solid #ddd;
cursor: default;
}
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
padding: 3px 5px 3px 5px;
border: 1px solid #ddd;
background-image: none;
background-color: #f4f4f4;
}
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none;
background: none;
}
/* end multiselect */
.select2-result-selectable .select2-match,
.select2-result-unselectable .select2-match {
text-decoration: underline;
}
.select2-offscreen, .select2-offscreen:focus {
clip: rect(0 0 0 0) !important;
width: 1px !important;
height: 1px !important;
border: 0 !important;
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
position: absolute !important;
outline: 0 !important;
left: 0px !important;
top: 0px !important;
}
.select2-display-none {
display: none;
}
.select2-measure-scrollbar {
position: absolute;
top: -10000px;
left: -10000px;
width: 100px;
height: 100px;
overflow: scroll;
}
/* Retina-ize icons */
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 2dppx) {
.select2-search input,
.select2-search-choice-close,
.select2-container .select2-choice abbr,
.select2-container .select2-choice .select2-arrow b {
background-image: url('select2x2.png') !important;
background-repeat: no-repeat !important;
background-size: 60px 40px !important;
}
.select2-search input {
background-position: 100% -21px !important;
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 845 B

View file

@ -0,0 +1,22 @@
$(function () {
$('.django-select2').each(function () {
var field_id = $(this).data('field_id');
$(this).select2({
ajax: {
data: function (params) {
return {
term: params.term,
page: params.page,
field_id: field_id
};
},
processResults: function (data, page) {
return {
results: data.results
};
}
}
});
});
});

View file

@ -1,253 +0,0 @@
if (!window['django_select2']) {
// This JS file can be included multiple times. So, as not to overwrite previous states, we run this only once.
window.django_select2 = {
MULTISEPARATOR: String.fromCharCode(31), // We use this unprintable char as separator,
// since this can't be entered by user.
get_url_params: function (term, page, context) {
var field_id = jQuery(this).data('field_id'),
res = {
'term': term,
'page': page,
'context': context
};
if (field_id) {
res['field_id'] = field_id;
}
return res;
},
process_results: function (data, page, context) {
var results;
if (data.err && data.err.toLowerCase() === 'nil') {
results = {
'results': data.results
};
if (context) {
results['context'] = context;
}
if (data.more === true || data.more === false) {
results['more'] = data.more;
}
} else {
results = {'results':[]};
}
if (results.results) {
jQuery(this).data('results', results.results);
} else {
jQuery(this).removeData('results');
}
return results;
},
onValChange: function () {
django_select2.updateText(jQuery(this));
},
prepareValText: function (vals, txts, isMultiple) {
var data = []
jQuery(vals).each(function (index) {
data.push({id: this, text: txts[index]});
});
if (isMultiple) {
return data;
} else {
if (data.length > 0) {
return data[0];
} else {
return null;
}
}
},
updateText: function ($e) {
var val = $e.select2('val'), data = $e.select2('data'), txt = $e.txt(), isMultiple = !!$e.attr('multiple'),
diff;
if (val || val === 0) { // Means value is set. A numerical 0 is also a valid value.
if (isMultiple) {
if (val.length !== txt.length) {
txt = [];
jQuery(val).each(function (idx) {
var i, value = this, id;
for (i in data) {
id = data [i].id;
if (id instanceof String) {
id = id.valueOf();
}
if (id == value) {
txt.push(data[i].text);
}
}
});
}
} else {
txt = data.text;
}
$e.txt(txt);
} else {
$e.txt('');
}
},
getValText: function ($e) {
var val = $e.select2('val'), res = $e.data('results'), txt = $e.txt(), isMultiple = !!$e.attr('multiple'),
f, id = $e.attr('id');
if (val || val === 0) { // Means value is set. A numerical 0 is also a valid value.
if (!isMultiple) {
val = [val];
if (txt || txt === 0) {
txt = [txt];
}
}
if (txt === 0 || (txt && val.length === txt.length)) {
return [val, txt];
}
f = $e.data('userGetValText');
if (f) {
txt = f($e, val, isMultiple);
if (txt || txt === 0) {
return [val, txt];
}
}
if (res) {
txt = [];
jQuery(val).each(function (idx) {
var i, value = this;
for (i in res) {
if (res[i].id == value) {
val[idx] = res[i].id; // To set it to correct data type.
txt.push(res[i].text);
}
}
});
if (txt || txt === 0) {
return [val, txt];
}
}
}
return null;
},
onInit: function (e, callback) {
e = jQuery(e);
var id = e.attr('id'), data = null, val = e.select2('val');
if (!val && val !== 0) {
val = e.data('initVal');
}
if (val || val === 0) {
// Value is set so need to get the text.
data = django_select2.getValText(e);
if (data && data[0]) {
data = django_select2.prepareValText(data[0], data[1], !!e.attr('multiple'));
}
}
if (!data) {
e.val(null); // Nulling out set value so as not to confuse users.
}
callback(data); // Change for 2.3.x
django_select2.updateText(e);
},
createSearchChoice: function(term, data) {
if (!data || jQuery(data).filter(function () {
return this.text.localeCompare(term) === 0;
}).length === 0) {
return {
id: term,
text: term
};
}
},
onMultipleHiddenChange: function () {
var $e = jQuery(this), valContainer = $e.data('valContainer'), name = $e.data('name'), vals = $e.val();
valContainer.empty();
if (vals) {
vals = vals.split(django_select2.MULTISEPARATOR);
jQuery(vals).each(function () {
var inp = jQuery('<input type="hidden">').appendTo(valContainer);
inp.attr('name', name);
inp.val(this);
});
}
},
initMultipleHidden: function ($e) {
var valContainer;
$e.data('name', $e.attr('name'));
$e.attr('name', '');
valContainer = jQuery('<div>').insertAfter($e).css({'display': 'none'});
$e.data('valContainer', valContainer);
$e.change(django_select2.onMultipleHiddenChange);
if ($e.val()) {
$e.change();
}
},
convertArrToStr: function (arr) {
return arr.join(django_select2.MULTISEPARATOR);
},
runInContextHelper: function (f, id) {
return function () {
var args = Array.prototype.slice.call(arguments, 0);
return f.apply(jQuery('#' + id).get(0), args);
}
},
logErr: function () {
if (console && console.error) {
args = Array.prototype.slice.call(arguments);
console.error.apply(console, args);
}
}
};
(function (isDebug) { // Only used for debugging.
if (isDebug) {
for (var i in django_select2) {
var f = django_select2[i];
if (typeof(f) == "function") {
django_select2[i] = (function (i, f) {
return function () {
console.log('Function ' + i + ' called for object: ', this);
return f.apply(this, arguments);
};
}(i, f));
}
}
}
}(false));
(function( $ ){
// This sets or gets the text lables for an element. It merely takes care returing array or single
// value, based on if element is multiple type.
$.fn.txt = function(val) {
if (typeof(val) !== 'undefined') {
if (val) {
if (val instanceof Array) {
if (this.attr('multiple')) {
val = django_select2.convertArrToStr(val);
} else {
val = val[0]
}
}
this.attr('txt', val);
} else {
this.removeAttr('txt');
}
return this;
} else {
val = this.attr('txt');
if (this.attr('multiple')) {
if (val) {
val = val.split(django_select2.MULTISEPARATOR);
} else {
val = [];
}
}
return val;
}
};
})( jQuery );
}

View file

@ -1 +0,0 @@
if(!window.django_select2){window.django_select2={MULTISEPARATOR:String.fromCharCode(31),get_url_params:function(c,e,b){var d=jQuery(this).data("field_id"),a={term:c,page:e,context:b};if(d){a.field_id=d}return a},process_results:function(d,c,b){var a;if(d.err&&d.err.toLowerCase()==="nil"){a={results:d.results};if(b){a.context=b}if(d.more===true||d.more===false){a.more=d.more}}else{a={results:[]}}if(a.results){jQuery(this).data("results",a.results)}else{jQuery(this).removeData("results")}return a},onValChange:function(){django_select2.updateText(jQuery(this))},prepareValText:function(d,a,c){var b=[];jQuery(d).each(function(e){b.push({id:this,text:a[e]})});if(c){return b}else{if(b.length>0){return b[0]}else{return null}}},updateText:function(b){var f=b.select2("val"),d=b.select2("data"),a=b.txt(),c=!!b.attr("multiple"),e;if(f||f===0){if(c){if(f.length!==a.length){a=[];jQuery(f).each(function(g){var h,j=this,k;for(h in d){k=d[h].id;if(k instanceof String){k=k.valueOf()}if(k==j){a.push(d[h].text)}}})}}else{a=d.text}b.txt(a)}else{b.txt("")}},getValText:function(b){var g=b.select2("val"),c=b.data("results"),a=b.txt(),e=!!b.attr("multiple"),d,h=b.attr("id");if(g||g===0){if(!e){g=[g];if(a||a===0){a=[a]}}if(a===0||(a&&g.length===a.length)){return[g,a]}d=b.data("userGetValText");if(d){a=d(b,g,e);if(a||a===0){return[g,a]}}if(c){a=[];jQuery(g).each(function(f){var j,k=this;for(j in c){if(c[j].id==k){g[f]=c[j].id;a.push(c[j].text)}}});if(a||a===0){return[g,a]}}}return null},onInit:function(b,f){b=jQuery(b);var d=b.attr("id"),a=null,c=b.select2("val");if(!c&&c!==0){c=b.data("initVal")}if(c||c===0){a=django_select2.getValText(b);if(a&&a[0]){a=django_select2.prepareValText(a[0],a[1],!!b.attr("multiple"))}}if(!a){b.val(null)}f(a);django_select2.updateText(b)},createSearchChoice:function(a,b){if(!b||jQuery(b).filter(function(){return this.text.localeCompare(a)===0}).length===0){return{id:a,text:a}}},onMultipleHiddenChange:function(){var b=jQuery(this),d=b.data("valContainer"),a=b.data("name"),c=b.val();d.empty();if(c){c=c.split(django_select2.MULTISEPARATOR);jQuery(c).each(function(){var e=jQuery('<input type="hidden">').appendTo(d);e.attr("name",a);e.val(this)})}},initMultipleHidden:function(a){var b;a.data("name",a.attr("name"));a.attr("name","");b=jQuery("<div>").insertAfter(a).css({display:"none"});a.data("valContainer",b);a.change(django_select2.onMultipleHiddenChange);if(a.val()){a.change()}},convertArrToStr:function(a){return a.join(django_select2.MULTISEPARATOR)},runInContextHelper:function(a,b){return function(){var c=Array.prototype.slice.call(arguments,0);return a.apply(jQuery("#"+b).get(0),c)}},logErr:function(){if(console&&console.error){args=Array.prototype.slice.call(arguments);console.error.apply(console,args)}}};(function(b){if(b){for(var a in django_select2){var c=django_select2[a];if(typeof(c)=="function"){django_select2[a]=(function(d,e){return function(){console.log("Function "+d+" called for object: ",this);return e.apply(this,arguments)}}(a,c))}}}}(false));(function(a){a.fn.txt=function(b){if(typeof(b)!=="undefined"){if(b){if(b instanceof Array){if(this.attr("multiple")){b=django_select2.convertArrToStr(b)}else{b=b[0]}}this.attr("txt",b)}else{this.removeAttr("txt")}return this}else{b=this.attr("txt");if(this.attr("multiple")){if(b){b=b.split(django_select2.MULTISEPARATOR)}else{b=[]}}return b}}})(jQuery)};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -8,5 +8,5 @@ from .views import AutoResponseView
urlpatterns = patterns(
"",
url(r"^fields/auto.json$",
AutoResponseView.as_view(), name="django_select2_central_json"),
AutoResponseView.as_view(), name="django_select2-json"),
)

View file

@ -27,8 +27,6 @@ class AutoResponseView(BaseListView):
}
for obj in context['object_list']
],
'err': NO_ERR_RESP,
'more': context['is_paginated']
})
def get_queryset(self):

View file

@ -13,7 +13,7 @@ from selenium.common.exceptions import NoSuchElementException
from six import text_type
from django_select2.cache import cache
from django_select2.forms import AutoHeavySelect2Widget
from django_select2.forms import ModelSelect2Widget
from tests.testapp.models import Genre
@ -82,7 +82,7 @@ class TestHeavySelect2Mixin(object):
mommy.make(Genre, 100)
def test_get_queryset(self):
widget = AutoHeavySelect2Widget()
widget = ModelSelect2Widget()
with pytest.raises(NotImplementedError):
widget.get_queryset()
widget.model = Genre
@ -92,7 +92,7 @@ class TestHeavySelect2Mixin(object):
assert isinstance(widget.get_queryset(), QuerySet)
def test_get_search_fields(self):
widget = AutoHeavySelect2Widget()
widget = ModelSelect2Widget()
with pytest.raises(NotImplementedError):
widget.get_search_fields()
@ -101,32 +101,32 @@ class TestHeavySelect2Mixin(object):
assert all(isinstance(x, text_type) for x in widget.get_search_fields())
def test_model_kwarg(self):
widget = AutoHeavySelect2Widget(model=Genre, search_fields=['title__icontains'])
widget = ModelSelect2Widget(model=Genre, search_fields=['title__icontains'])
genre = Genre.objects.last()
result = widget.filter_queryset(genre.title)
assert result.exists()
def test_queryset_kwarg(self):
widget = AutoHeavySelect2Widget(queryset=Genre.objects, search_fields=['title__icontains'])
widget = ModelSelect2Widget(queryset=Genre.objects, search_fields=['title__icontains'])
genre = Genre.objects.last()
result = widget.filter_queryset(genre.title)
assert result.exists()
def test_widget_id(self):
widget = AutoHeavySelect2Widget()
widget = ModelSelect2Widget()
widget.render('name', 'value')
assert widget.widget_id
assert signing.loads(widget.widget_id) == id(widget)
def test_render(self):
widget = AutoHeavySelect2Widget()
widget = ModelSelect2Widget()
widget.render('name', 'value')
cached_widget = cache.get(widget._get_cache_key())
assert isinstance(cached_widget, AutoHeavySelect2Widget)
assert isinstance(cached_widget, ModelSelect2Widget)
assert cached_widget.widget_id == widget.widget_id
def test_ajax_view_registration(self, client):
widget = AutoHeavySelect2Widget(queryset=Genre.objects, search_fields=['title__icontains'])
widget = ModelSelect2Widget(queryset=Genre.objects, search_fields=['title__icontains'])
widget.render('name', 'value')
url = reverse('django_select2_central_json')
genre = Genre.objects.last()

View file

@ -5,8 +5,8 @@ from django import forms
from django_select2.forms import (
HeavySelect2MultipleWidget, HeavySelect2Widget, Select2MultipleWidget,
Select2Widget
)
Select2Widget,
ModelSelect2MultipleWidget, ModelSelect2Widget)
from tests.testapp import models
@ -38,9 +38,9 @@ class ArtistModelForm(forms.ModelForm):
class ArtistForm(forms.Form):
title = forms.CharField(max_length=50)
genres = forms.ModelMultipleChoiceField(widget=HeavySelect2MultipleWidget(
genres = forms.ModelMultipleChoiceField(widget=ModelSelect2MultipleWidget(
queryset=models.Genre.objects.all(),
search_fields=['title'],
search_fields=['title__icontains'],
), queryset=models.Genre.objects.all())
@ -55,9 +55,9 @@ class AlbumModelForm(forms.ModelForm):
class AlbumForm(forms.Form):
title = forms.CharField(max_length=255)
artist = forms.ModelChoiceField(widget=HeavySelect2Widget(
artist = forms.ModelChoiceField(widget=ModelSelect2Widget(
model=models.Artist,
search_fields=['title']
search_fields=['title__icontains']
), queryset=models.Artist.objects.all())

10
tests/testapp/manage.py Executable file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

View file

@ -1,11 +1,12 @@
import os.path
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DEBUG = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
'NAME': 'testdb.sqlite',
}
}
@ -48,4 +49,4 @@ if os.environ.get('TRAVIS'):
}
}
AUTO_RENDER_SELECT2_STATICS = False
SELECT2_BOOTSTRAP = True

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2,18 +2,20 @@
<!DOCTYPE html>
<html>
<head>
<script src="{% static 'jquery-1.7.2.min.js' %}"></script>
<script type="text/javascript">
window.onerror = function (msg) {
$("body").attr("JSError", msg);
}
</script>
{{ form.media }}
{{ form.media.css }}
<style type="text/css">select{width:200px}</style>
</head>
<body>
<form method="post" action="">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit Form"/>
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit Form"/>
</form>
<script src="{% static 'jquery-2.1.4.min.js' %}"></script>
<script type="text/javascript">
window.onerror = function (msg) {
$("body").attr("JSError", msg);
}
</script>
{{ form.media.js }}
</body>