Multi value tests added and more fixes.

* Fixed infinite recursion causing Python crash bug.
* Multi-value is working.
* Tested added for multi-value field.
This commit is contained in:
AppleGrew (applegrew) 2012-08-21 01:38:31 +05:30
parent ce5b42d531
commit f3569bcea2
11 changed files with 123 additions and 68 deletions

View file

@ -5,5 +5,5 @@ if settings.configured:
from .widgets import Select2Widget, Select2MultipleWidget, HeavySelect2Widget, HeavySelect2MultipleWidget, AutoHeavySelect2Widget
from .fields import Select2ChoiceField, Select2MultipleChoiceField, \
HeavySelect2ChoiceField, HeavySelect2MultipleChoiceField, \
ModelSelect2Field, AutoSelect2Field, AutoModelSelect2Field #, ModelMultipleSelect2Field
ModelSelect2Field, AutoSelect2Field, AutoModelSelect2Field, ModelMultipleSelect2Field
from .views import Select2View, NO_ERR_RESP

View file

@ -26,6 +26,17 @@ from django.core.validators import EMPTY_VALUES
from .widgets import Select2Widget, Select2MultipleWidget,\
HeavySelect2Widget, HeavySelect2MultipleWidget, AutoHeavySelect2Widget
from .views import NO_ERR_RESP
from .util import extract_some_key_val
### Light general fields ###
class Select2ChoiceField(forms.ChoiceField):
widget = Select2Widget
class Select2MultipleChoiceField(forms.MultipleChoiceField):
widget = Select2MultipleWidget
### Model fields related mixins ###
class ModelResultJsonMixin(object):
@ -126,25 +137,20 @@ class QuerysetChoiceMixin(ChoiceMixin):
choices = property(_get_choices, ChoiceMixin._set_choices)
class ModelChoiceFieldMixin(object):
#class ModelChoiceField(forms.ModelChoiceField):
def __init__(self, *args, **kwargs):
queryset = kwargs.pop('queryset', None)
empty_label = kwargs.pop('empty_label', u"---------")
cache_choices = kwargs.pop('cache_choices', False)
required = kwargs.pop('required', True)
widget = kwargs.pop('widget', getattr(self, 'widget', None))
label = kwargs.pop('label', None)
initial = kwargs.pop('initial', None)
help_text = kwargs.pop('help_text', None)
to_field_name = kwargs.pop('to_field_name', 'pk')
kargs = extract_some_key_val(kwargs, [
'empty_label', 'cache_choices', 'required', 'label', 'initial', 'help_text',
])
kargs['widget'] = kwargs.pop('widget', getattr(self, 'widget', None))
kargs['to_field_name'] = kwargs.pop('to_field_name', 'pk')
if hasattr(self, '_choices'): # If it exists then probably it is set by HeavySelect2FieldBase.
# We are not gonna use that anyway.
del self._choices
super(ModelChoiceField, self).__init__(queryset, empty_label, cache_choices, required,
widget, label, initial, help_text, to_field_name)
super(ModelChoiceFieldMixin, self).__init__(queryset, **kargs)
if hasattr(self, 'set_placeholder'):
self.widget.set_placeholder(self.empty_label)
@ -153,20 +159,26 @@ class ModelChoiceFieldMixin(object):
if hasattr(self, '_queryset'):
return self._queryset
# queryset = property(_get_queryset, forms.ModelChoiceField._set_queryset)
### Slightly altered versions of the Django counterparts with the same name in forms module. ###
class ModelChoiceField(ModelChoiceFieldMixin, forms.ModelChoiceField):
queryset = property(ModelChoiceFieldMixin._get_queryset, forms.ModelChoiceField._set_queryset)
#class ModelMultipleChoiceField(ModelChoiceFieldMixin, forms.ModelMultipleChoiceField):
# queryset = property(ModelChoiceFieldMixin._get_queryset, forms.ModelMultipleChoiceField._set_queryset)
class ModelMultipleChoiceField(ModelChoiceFieldMixin, forms.ModelMultipleChoiceField):
queryset = property(ModelChoiceFieldMixin._get_queryset, forms.ModelMultipleChoiceField._set_queryset)
class Select2ChoiceField(forms.ChoiceField):
### Light Fileds specialized for Models ###
class ModelSelect2Field(ModelChoiceField) :
"Light Model Select2 field"
widget = Select2Widget
class Select2MultipleChoiceField(forms.MultipleChoiceField):
class ModelMultipleSelect2Field(ModelMultipleChoiceField) :
"Light multiple-value Model Select2 field"
widget = Select2MultipleWidget
### Heavy fields ###
class HeavySelect2FieldBase(ChoiceMixin, forms.Field):
def __init__(self, *args, **kwargs):
data_view = kwargs.pop('data_view', None)
@ -187,11 +199,15 @@ class HeavySelect2ChoiceField(HeavySelect2FieldBase):
class HeavySelect2MultipleChoiceField(HeavySelect2FieldBase):
widget = HeavySelect2MultipleWidget
### Heavy field specialized for Models (Single valued) ###
class HeavyModelSelect2ChoiceField(QuerysetChoiceMixin, HeavySelect2ChoiceField, ModelChoiceField):
def __init__(self, *args, **kwargs):
kwargs.pop('choices', None)
super(HeavyModelSelect2ChoiceField, self).__init__(*args, **kwargs)
### Heavy general field that uses central AutoView ###
class AutoSelect2Field(ModelResultJsonMixin, AutoViewFieldMixin, HeavySelect2ChoiceField):
"""
This needs to be subclassed. The first instance of a class (sub-class) is used to serve all incoming
@ -205,12 +221,15 @@ class AutoSelect2Field(ModelResultJsonMixin, AutoViewFieldMixin, HeavySelect2Cho
kwargs['data_view'] = self.data_view
super(AutoSelect2Field, self).__init__(*args, **kwargs)
### Heavy field, specialized for Model, that uses central AutoView ###
class AutoModelSelect2Field(ModelResultJsonMixin, AutoViewFieldMixin, HeavyModelSelect2ChoiceField):
"""
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
__metaclass__ = UnhideableQuerysetType # Makes sure that user defined queryset class variable is replaced by
# queryset property (as it is needed by super classes).
widget = AutoHeavySelect2Widget
@ -218,11 +237,3 @@ 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 ModelSelect2Field(ModelChoiceField) :
"Light Model Select2 field"
widget = Select2Widget
#class ModelMultipleSelect2Field(ModelMultipleChoiceField) :
# "Light multiple-value Model Select2 field"
# widget = Select2MultipleWidget

View file

@ -3,4 +3,7 @@
}
.select2-container {
min-width: 150px;
}
.select2-container.select2-container-multi {
width: 300px;
}

View file

@ -29,6 +29,14 @@ class JSFunctionInContext(JSVar):
"""
pass
def extract_some_key_val(dct, keys):
edct = {}
for k in keys:
v = dct.get(k, None)
if v is not None:
edct[k] = v
return edct
### Auto view helper utils ###
import re

View file

@ -7,6 +7,8 @@ from django.core.urlresolvers import reverse
from .util import render_js_script, convert_to_js_string_arr, JSVar, JSFunction, JSFunctionInContext
### Light mixin and widgets ###
class Select2Mixin(object):
# For details on these options refer: http://ivaynberg.github.com/select2/#documentation
options = {
@ -96,6 +98,45 @@ class Select2Mixin(object):
js = ('js/select2.min.js', )
css = {'screen': ('css/select2.css', 'css/extra.css', )}
class Select2Widget(Select2Mixin, forms.Select):
def init_options(self):
self.options.pop('multiple', None)
def render_options(self, choices, selected_choices):
if not self.is_required:
choices = list(choices)
choices.append(('', '', )) # Adding an empty choice
return super(Select2Widget, self).render_options(choices, selected_choices)
class Select2MultipleWidget(Select2Mixin, forms.SelectMultiple):
def init_options(self):
self.options.pop('multiple', None)
self.options.pop('allowClear', None)
self.options.pop('minimumResultsForSearch', None)
### Specialized Multiple Hidden Input Widget ###
class MultipleSelect2HiddenInput(forms.TextInput):
"""
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,9 then Select2 would would write them
to the 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 was would have been for a <select> multiple field.
"""
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))
id_ = attrs.get('id', None)
if id_:
s += render_js_script(u"django_select2.initMultipleHidden($('#%s'));" % id_)
return s
### Heavy mixins and widgets ###
class HeavySelect2Mixin(Select2Mixin):
def __init__(self, **kwargs):
self.options = dict(self.options) # Making an instance specific copy
@ -152,38 +193,6 @@ class HeavySelect2Mixin(Select2Mixin):
js = ('js/select2.min.js', 'js/heavy_data.js', )
css = {'screen': ('css/select2.css', 'css/extra.css', )}
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))
id_ = attrs.get('id', None)
if id_:
s += render_js_script(u"django_select2.initMultipleHidden($('#%s'));" % id_)
return s
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)
return js
class Select2Widget(Select2Mixin, forms.Select):
def init_options(self):
self.options.pop('multiple', None)
def render_options(self, choices, selected_choices):
if not self.is_required:
choices = list(choices)
choices.append(('', '', )) # Adding an empty choice
return super(Select2Widget, self).render_options(choices, selected_choices)
class Select2MultipleWidget(Select2Mixin, forms.SelectMultiple):
def init_options(self):
self.options.pop('multiple', None)
self.options.pop('allowClear', None)
self.options.pop('minimumResultsForSearch', None)
class HeavySelect2Widget(HeavySelect2Mixin, forms.TextInput):
input_type = 'hidden' # We want it hidden but should be treated as if is_hidden is False
def init_options(self):
@ -202,5 +211,13 @@ class HeavySelect2MultipleWidget(HeavySelect2Mixin, MultipleSelect2HiddenInput):
if texts:
return render_js_script(u"$('#%s').attr('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)
return js
class AutoHeavySelect2Widget(AutoHeavySelect2Mixin, HeavySelect2Widget):
pass

Binary file not shown.

View file

@ -1,9 +1,14 @@
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
</head>
<body>
<form method="post" action="">
{% csrf_token %}
<table>
{{form}}
</table>
<input type="submit" value="Submit Form"/>
</form>
</form>
</body>

View file

@ -1,6 +1,12 @@
{% load url from future %}
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<h1>Manual Tests</h1>
<ul>
<li><a href="{% url 'test_single_value_model_field' %}">Test single selection model fields</a></li>
<li><a href="{% url 'test_multi_values_model_field' %}">Test multi selection model fields</a></li>
</ul>
</ul>
</body>

View file

@ -1,8 +1,13 @@
{% load url from future %}
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<h2>{{title}}</h2>
<ul>
{% for e in object_list %}
<li><a href="{% url href e.id %}">{{ e }}</a></li>
{% endfor %}
</ul>
</body>

View file

@ -15,8 +15,8 @@ class EmployeeForm(forms.ModelForm):
class Meta:
model = Employee
#class DeptForm(forms.ModelForm):
# allotted_rooms = ModelMultipleSelect2Field(queryset=ClassRoom.objects)
class DeptForm(forms.ModelForm):
allotted_rooms = ModelMultipleSelect2Field(queryset=ClassRoom.objects)
# class Meta:
# model = Dept
class Meta:
model = Dept

View file

@ -3,7 +3,7 @@ from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from .forms import EmployeeForm #, DeptForm
from .forms import EmployeeForm, DeptForm
from .models import Employee, Dept
def test_single_value_model_field(request):