mirror of
https://github.com/Hopiu/django-select2.git
synced 2026-04-21 21:44:46 +00:00
Multi value bu fixes
Many bugs ironed out of Multi value fields. Added many new tests for multi value fields.
This commit is contained in:
parent
8d42210053
commit
691fa14e2e
10 changed files with 204 additions and 67 deletions
|
|
@ -13,10 +13,9 @@ External dependencies
|
|||
* Django - This is obvious.
|
||||
* jQuery - This is not included in the package since it is expected that in most scenarios this would already be available.
|
||||
|
||||
=================
|
||||
Changelog Summary
|
||||
=================
|
||||
|
||||
v2.0
|
||||
----
|
||||
### v2.0
|
||||
|
||||
Mostly major bug fixes in code and design. The changes were many, raising the possibility of backward incompatibilty. However, the backward incompatibilty would be subtle.
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ __version__ = "2.0"
|
|||
|
||||
from django.conf import settings
|
||||
if settings.configured:
|
||||
from .widgets import Select2Widget, Select2MultipleWidget, HeavySelect2Widget, HeavySelect2MultipleWidget, AutoHeavySelect2Widget
|
||||
from .fields import Select2ChoiceField, Select2MultipleChoiceField, \
|
||||
HeavySelect2ChoiceField, HeavySelect2MultipleChoiceField, \
|
||||
ModelSelect2Field, AutoSelect2Field, AutoModelSelect2Field, ModelMultipleSelect2Field
|
||||
from .widgets import Select2Widget, Select2MultipleWidget, HeavySelect2Widget, HeavySelect2MultipleWidget, \
|
||||
AutoHeavySelect2Widget, AutoHeavySelect2MultipleWidget
|
||||
from .fields import Select2ChoiceField, Select2MultipleChoiceField, HeavySelect2ChoiceField, \
|
||||
HeavySelect2MultipleChoiceField, HeavyModelSelect2ChoiceField, HeavyModelSelect2MultipleChoiceField, \
|
||||
ModelSelect2Field, ModelSelect2MultipleField, AutoSelect2Field, AutoSelect2MultipleField, \
|
||||
AutoModelSelect2Field, AutoModelSelect2MultipleField
|
||||
from .views import Select2View, NO_ERR_RESP
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ class AutoViewFieldMixin(object):
|
|||
"""Registers itself with AutoResponseView."""
|
||||
def __init__(self, *args, **kwargs):
|
||||
name = self.__class__.__name__
|
||||
print '<><><><><><>', self.__module__, ' :::: ', name,
|
||||
from .util import register_field
|
||||
if name not in ['AutoViewFieldMixin', 'AutoSelect2Field', 'AutoModelSelect2Field']:
|
||||
if name not in ['AutoViewFieldMixin', 'AutoSelect2Field', 'AutoModelSelect2Field',
|
||||
'AutoSelect2MultipleField', 'AutoModelSelect2MultipleField']:
|
||||
id_ = register_field("%s.%s" % (self.__module__, name), self)
|
||||
self.widget.field_id = id_
|
||||
super(AutoViewFieldMixin, self).__init__(*args, **kwargs)
|
||||
|
|
@ -24,7 +26,8 @@ from django.utils.encoding import smart_unicode
|
|||
from django.core.validators import EMPTY_VALUES
|
||||
|
||||
from .widgets import Select2Widget, Select2MultipleWidget,\
|
||||
HeavySelect2Widget, HeavySelect2MultipleWidget, AutoHeavySelect2Widget
|
||||
HeavySelect2Widget, HeavySelect2MultipleWidget, AutoHeavySelect2Widget, \
|
||||
AutoHeavySelect2MultipleWidget
|
||||
from .views import NO_ERR_RESP
|
||||
from .util import extract_some_key_val
|
||||
|
||||
|
|
@ -173,7 +176,7 @@ class ModelSelect2Field(ModelChoiceField) :
|
|||
"Light Model Select2 field"
|
||||
widget = Select2Widget
|
||||
|
||||
class ModelMultipleSelect2Field(ModelMultipleChoiceField) :
|
||||
class ModelSelect2MultipleField(ModelMultipleChoiceField) :
|
||||
"Light multiple-value Model Select2 field"
|
||||
widget = Select2MultipleWidget
|
||||
|
||||
|
|
@ -199,16 +202,21 @@ class HeavySelect2ChoiceField(HeavySelect2FieldBase):
|
|||
class HeavySelect2MultipleChoiceField(HeavySelect2FieldBase):
|
||||
widget = HeavySelect2MultipleWidget
|
||||
|
||||
### Heavy field specialized for Models (Single valued) ###
|
||||
### Heavy field specialized for Models ###
|
||||
|
||||
class HeavyModelSelect2ChoiceField(QuerysetChoiceMixin, HeavySelect2ChoiceField, ModelChoiceField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.pop('choices', None)
|
||||
super(HeavyModelSelect2ChoiceField, self).__init__(*args, **kwargs)
|
||||
|
||||
class HeavyModelSelect2MultipleChoiceField(QuerysetChoiceMixin, HeavySelect2MultipleChoiceField, ModelMultipleChoiceField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.pop('choices', None)
|
||||
super(HeavyModelSelect2MultipleChoiceField, self).__init__(*args, **kwargs)
|
||||
|
||||
### Heavy general field that uses central AutoView ###
|
||||
|
||||
class AutoSelect2Field(ModelResultJsonMixin, AutoViewFieldMixin, HeavySelect2ChoiceField):
|
||||
class AutoSelect2Field(AutoViewFieldMixin, HeavySelect2ChoiceField):
|
||||
"""
|
||||
This needs to be subclassed. The first instance of a class (sub-class) is used to serve all incoming
|
||||
json query requests for that type (class).
|
||||
|
|
@ -221,6 +229,19 @@ class AutoSelect2Field(ModelResultJsonMixin, AutoViewFieldMixin, HeavySelect2Cho
|
|||
kwargs['data_view'] = self.data_view
|
||||
super(AutoSelect2Field, self).__init__(*args, **kwargs)
|
||||
|
||||
class AutoSelect2MultipleField(AutoViewFieldMixin, HeavySelect2MultipleChoiceField):
|
||||
"""
|
||||
This needs to be subclassed. The first instance of a class (sub-class) is used to serve all incoming
|
||||
json query requests for that type (class).
|
||||
"""
|
||||
|
||||
widget = AutoHeavySelect2MultipleWidget
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.data_view = "django_select2_central_json"
|
||||
kwargs['data_view'] = self.data_view
|
||||
super(AutoSelect2MultipleField, self).__init__(*args, **kwargs)
|
||||
|
||||
### Heavy field, specialized for Model, that uses central AutoView ###
|
||||
|
||||
class AutoModelSelect2Field(ModelResultJsonMixin, AutoViewFieldMixin, HeavyModelSelect2ChoiceField):
|
||||
|
|
@ -237,3 +258,20 @@ class AutoModelSelect2Field(ModelResultJsonMixin, AutoViewFieldMixin, HeavyModel
|
|||
self.data_view = "django_select2_central_json"
|
||||
kwargs['data_view'] = self.data_view
|
||||
super(AutoModelSelect2Field, self).__init__(*args, **kwargs)
|
||||
|
||||
class AutoModelSelect2MultipleField(ModelResultJsonMixin, AutoViewFieldMixin, HeavyModelSelect2MultipleChoiceField):
|
||||
"""
|
||||
This needs to be subclassed. The first instance of a class (sub-class) is used to serve all incoming
|
||||
json query requests for that type (class).
|
||||
"""
|
||||
__metaclass__ = UnhideableQuerysetType # Makes sure that user defined queryset class variable is replaced by
|
||||
# queryset property (as it is needed by super classes).
|
||||
|
||||
widget = AutoHeavySelect2MultipleWidget
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.data_view = "django_select2_central_json"
|
||||
kwargs['data_view'] = self.data_view
|
||||
super(AutoModelSelect2MultipleField, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
var django_select2 = {
|
||||
MULTISEPARATOR: String.fromCharCode(0),
|
||||
MULTISEPARATOR: String.fromCharCode(0), // 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 = $(this).data('field_id'),
|
||||
res = {
|
||||
|
|
@ -163,6 +163,10 @@ var django_select2 = {
|
|||
e = $(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);
|
||||
|
|
@ -176,14 +180,16 @@ var django_select2 = {
|
|||
callback(data); // Change for 2.3.x
|
||||
},
|
||||
onMultipleHiddenChange: function () {
|
||||
var $e = $(this), valContainer = $e.data('valContainer'), name = $e.data('name');
|
||||
var $e = $(this), valContainer = $e.data('valContainer'), name = $e.data('name'), vals = $e.val();
|
||||
valContainer.empty();
|
||||
$($e.val()).each(function () {
|
||||
var inp = $('<input>').appendTo(valContainer);
|
||||
inp.attr('type', 'hidden');
|
||||
inp.attr('name', name);
|
||||
inp.val(this);
|
||||
});
|
||||
if (vals) {
|
||||
vals = vals.split(django_select2.MULTISEPARATOR);
|
||||
$(vals).each(function () {
|
||||
var inp = $('<input type="hidden">').appendTo(valContainer);
|
||||
inp.attr('name', name);
|
||||
inp.val(this);
|
||||
});
|
||||
}
|
||||
},
|
||||
initMultipleHidden: function ($e) {
|
||||
var valContainer;
|
||||
|
|
@ -191,18 +197,41 @@ var django_select2 = {
|
|||
$e.data('name', $e.attr('name'));
|
||||
$e.attr('name', '');
|
||||
|
||||
valContainer = $e.after('<div>').css({'display': 'none'});
|
||||
valContainer = $('<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);
|
||||
return f.apply($('#' + id).get(0), args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(function( $ ){
|
||||
$.fn.txt = function() {
|
||||
return this.attr('txt');
|
||||
// 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 instanceof Array) {
|
||||
val = django_select2.convertArrToStr(val);
|
||||
}
|
||||
this.attr('txt', val);
|
||||
return this;
|
||||
} else {
|
||||
val = this.attr('txt');
|
||||
if (val && this.attr('multiple')) {
|
||||
val = val.split(django_select2.MULTISEPARATOR);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
};
|
||||
})( jQuery );
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
def convert_to_js_string_arr(lst):
|
||||
lst = ['"%s"' % l for l in lst]
|
||||
return u"[%s]" % (",".join(lst))
|
||||
import types
|
||||
|
||||
def render_js_script(inner_code):
|
||||
return u"""
|
||||
|
|
@ -37,6 +35,53 @@ def extract_some_key_val(dct, keys):
|
|||
edct[k] = v
|
||||
return edct
|
||||
|
||||
def convert_py_to_js_data(val, id_):
|
||||
if type(val) == types.BooleanType:
|
||||
return u'true' if val else u'false'
|
||||
elif type(val) in [types.IntType, types.LongType, types.FloatType]:
|
||||
return unicode(val)
|
||||
elif isinstance(val, JSFunctionInContext):
|
||||
return u"django_select2.runInContextHelper(%s, '%s')" % (val, id_)
|
||||
elif isinstance(val, JSVar):
|
||||
return val # No quotes here
|
||||
elif isinstance(val, dict):
|
||||
return convert_dict_to_js_map(val, id_)
|
||||
elif isinstance(val, list):
|
||||
return convert_to_js_arr(val, id_)
|
||||
else:
|
||||
return u"'%s'" % unicode(val)
|
||||
|
||||
def convert_dict_to_js_map(dct, id_):
|
||||
out = u'{'
|
||||
is_first = True
|
||||
for name in dct:
|
||||
if not is_first:
|
||||
out += u", "
|
||||
else:
|
||||
is_first = False
|
||||
|
||||
out += u"'%s': " % name
|
||||
out += convert_py_to_js_data(dct[name], id_)
|
||||
|
||||
return out + u'}'
|
||||
|
||||
def convert_to_js_arr(lst, id_):
|
||||
out = u'['
|
||||
is_first = True
|
||||
for val in lst:
|
||||
if not is_first:
|
||||
out += u", "
|
||||
else:
|
||||
is_first = False
|
||||
|
||||
out += convert_py_to_js_data(val, id_)
|
||||
|
||||
return out + u']'
|
||||
|
||||
def convert_to_js_string_arr(lst):
|
||||
lst = ['"%s"' % l for l in lst]
|
||||
return u"[%s]" % (",".join(lst))
|
||||
|
||||
### Auto view helper utils ###
|
||||
|
||||
import re
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import types
|
||||
from itertools import chain
|
||||
|
||||
from django import forms
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.datastructures import MultiValueDict, MergeDict
|
||||
|
||||
from .util import render_js_script, convert_to_js_string_arr, JSVar, JSFunction, JSFunctionInContext
|
||||
from .util import render_js_script, convert_to_js_string_arr, JSVar, JSFunction, JSFunctionInContext, \
|
||||
convert_dict_to_js_map, convert_to_js_arr
|
||||
|
||||
### Light mixin and widgets ###
|
||||
|
||||
|
|
@ -44,33 +45,7 @@ class Select2Mixin(object):
|
|||
return options
|
||||
|
||||
def render_select2_options_code(self, options, id_):
|
||||
out = '{'
|
||||
is_first = True
|
||||
for name in options:
|
||||
if not is_first:
|
||||
out += u", "
|
||||
else:
|
||||
is_first = False
|
||||
|
||||
out += u"'%s': " % name
|
||||
val = options[name]
|
||||
if type(val) == types.BooleanType:
|
||||
out += u'true' if val else u'false'
|
||||
elif type(val) in [types.IntType, types.LongType, types.FloatType]:
|
||||
out += unicode(val)
|
||||
elif isinstance(val, JSFunctionInContext):
|
||||
out += u"""function () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
return %s.apply($('#%s').get(0), args);
|
||||
}""" % (val, id_)
|
||||
elif isinstance(val, JSVar):
|
||||
out += val # No quotes here
|
||||
elif isinstance(val, dict):
|
||||
out += self.render_select2_options_code(val, id_)
|
||||
else:
|
||||
out += u"'%s'" % val
|
||||
|
||||
return out + u'}'
|
||||
return convert_dict_to_js_map(options, id_)
|
||||
|
||||
def render_js_code(self, id_, *args):
|
||||
if id_:
|
||||
|
|
@ -129,12 +104,22 @@ class MultipleSelect2HiddenInput(forms.TextInput):
|
|||
input_type = 'hidden' # We want it hidden but should be treated as if is_hidden is False
|
||||
def render(self, name, value, attrs=None, choices=()):
|
||||
attrs = self.build_attrs(attrs, multiple='multiple')
|
||||
s = unicode(super(MultipleSelect2HiddenInput, self).render(name, value, attrs, choices))
|
||||
s = unicode(super(MultipleSelect2HiddenInput, self).render(name, u"", attrs))
|
||||
id_ = attrs.get('id', None)
|
||||
if id_:
|
||||
s += render_js_script(u"django_select2.initMultipleHidden($('#%s'));" % id_)
|
||||
jscode = u''
|
||||
if value:
|
||||
jscode = u"$('#%s').val(django_select2.convertArrToStr(%s));" \
|
||||
% (id_, convert_to_js_arr(value, id_))
|
||||
jscode += u"django_select2.initMultipleHidden($('#%s'));" % id_
|
||||
s += render_js_script(jscode)
|
||||
return s
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
if isinstance(data, (MultiValueDict, MergeDict)):
|
||||
return data.getlist(name)
|
||||
return data.get(name, None)
|
||||
|
||||
### Heavy mixins and widgets ###
|
||||
|
||||
class HeavySelect2Mixin(Select2Mixin):
|
||||
|
|
@ -146,7 +131,6 @@ class HeavySelect2Mixin(Select2Mixin):
|
|||
self.choices = kwargs.pop('choices', [])
|
||||
if not self.view and not self.url:
|
||||
raise ValueError('data_view or data_url is required')
|
||||
self.url = None
|
||||
self.options['ajax'] = {
|
||||
'dataType': 'json',
|
||||
'quietMillis': 100,
|
||||
|
|
@ -209,15 +193,18 @@ class HeavySelect2MultipleWidget(HeavySelect2Mixin, MultipleSelect2HiddenInput):
|
|||
if value: # Just like forms.SelectMultiple.render() it assumes that value will be multi-valued (list).
|
||||
texts = self.render_texts(value, choices)
|
||||
if texts:
|
||||
return render_js_script(u"$('#%s').attr('txt', %s);" % (id_, texts))
|
||||
return u"$('#%s').txt(%s);" % (id_, texts)
|
||||
|
||||
### Auto Heavy widgets ###
|
||||
|
||||
class AutoHeavySelect2Mixin(HeavySelect2Mixin):
|
||||
def render_inner_js_code(self, id_, *args):
|
||||
js = super(AutoHeavySelect2Mixin, self).render_inner_js_code(id_, *args)
|
||||
js += u"$('#%s').data('field_id', '%s');" % (id_, self.field_id)
|
||||
js = u"$('#%s').data('field_id', '%s');" % (id_, self.field_id)
|
||||
js += super(AutoHeavySelect2Mixin, self).render_inner_js_code(id_, *args)
|
||||
return js
|
||||
|
||||
class AutoHeavySelect2Widget(AutoHeavySelect2Mixin, HeavySelect2Widget):
|
||||
pass
|
||||
|
||||
class AutoHeavySelect2MultipleWidget(AutoHeavySelect2Mixin, HeavySelect2MultipleWidget):
|
||||
pass
|
||||
|
|
|
|||
BIN
testapp/test.db
BIN
testapp/test.db
Binary file not shown.
|
|
@ -34,12 +34,34 @@
|
|||
"number": "500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "testmain.lab",
|
||||
"fields": {
|
||||
"name": "Crimsion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "testmain.lab",
|
||||
"fields": {
|
||||
"name": "Saffron"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "testmain.lab",
|
||||
"fields": {
|
||||
"name": "Lavender"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "testmain.dept",
|
||||
"fields": {
|
||||
"name": "Chemistry",
|
||||
"allotted_rooms": [1, 2, 3]
|
||||
"allotted_rooms": [1, 2, 3],
|
||||
"allotted_labs": [1]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -47,7 +69,8 @@
|
|||
"model": "testmain.dept",
|
||||
"fields": {
|
||||
"name": "Biology",
|
||||
"allotted_rooms": [3, 4]
|
||||
"allotted_rooms": [3, 4],
|
||||
"allotted_labs": [2]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -55,7 +78,8 @@
|
|||
"model": "testmain.dept",
|
||||
"fields": {
|
||||
"name": "Physics",
|
||||
"allotted_rooms": [1, 2, 5]
|
||||
"allotted_rooms": [1, 2, 5],
|
||||
"allotted_labs": [1, 3]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,21 +2,27 @@ from django import forms
|
|||
|
||||
from django_select2 import *
|
||||
|
||||
from .models import Employee, Dept, ClassRoom
|
||||
from .models import Employee, Dept, ClassRoom, Lab
|
||||
|
||||
class EmployeeChoices(AutoModelSelect2Field):
|
||||
queryset = Employee.objects
|
||||
search_fields = ['name__icontains', ]
|
||||
|
||||
class ClassRoomChoices(AutoModelSelect2MultipleField):
|
||||
queryset = ClassRoom.objects
|
||||
search_fields = ['number__icontains', ]
|
||||
|
||||
|
||||
class EmployeeForm(forms.ModelForm):
|
||||
manager = EmployeeChoices()
|
||||
manager = EmployeeChoices(required=False)
|
||||
dept = ModelSelect2Field(queryset=Dept.objects)
|
||||
|
||||
class Meta:
|
||||
model = Employee
|
||||
|
||||
class DeptForm(forms.ModelForm):
|
||||
allotted_rooms = ModelMultipleSelect2Field(queryset=ClassRoom.objects)
|
||||
allotted_rooms = ClassRoomChoices()
|
||||
allotted_labs = ModelSelect2MultipleField(queryset=Lab.objects, required=False)
|
||||
|
||||
class Meta:
|
||||
model = Dept
|
||||
|
|
|
|||
|
|
@ -6,9 +6,16 @@ class ClassRoom(models.Model):
|
|||
def __unicode__(self):
|
||||
return unicode(self.number)
|
||||
|
||||
class Lab(models.Model):
|
||||
name = models.CharField(max_length=10)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.name)
|
||||
|
||||
class Dept(models.Model):
|
||||
name = models.CharField(max_length=10)
|
||||
allotted_rooms = models.ManyToManyField(ClassRoom)
|
||||
allotted_labs = models.ManyToManyField(Lab)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.name)
|
||||
|
|
|
|||
Loading…
Reference in a new issue