mirror of
https://github.com/Hopiu/django-select2.git
synced 2026-04-05 06:30:58 +00:00
v2.0 Changes from v1.1 branch
This commit is contained in:
commit
92ddcf3bb9
25 changed files with 1163 additions and 206 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -2,3 +2,8 @@
|
|||
testapp/tt.py
|
||||
|
||||
*.pyc
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
|
||||
testapp/tt2.py
|
||||
>>>>>>> v1.1
|
||||
|
|
|
|||
17
README.md
17
README.md
|
|
@ -9,8 +9,23 @@ The app includes Select2 driven Django Widgets and Form Fields.
|
|||
|
||||
More details can be found on my blog at - [http://blog.applegrew.com/2012/08/django-select2/](http://blog.applegrew.com/2012/08/django-select2/).
|
||||
|
||||
External dependencies
|
||||
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.
|
||||
|
||||
Example Application
|
||||
===================
|
||||
Please checkout `testapp` application. This application is used to manually test the functionalities of this component. This also serves as a good example.
|
||||
|
||||
You need only django 1.4 or above to run that.
|
||||
|
||||
Changelog Summary
|
||||
=================
|
||||
|
||||
### 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.
|
||||
|
||||
* Auto fields (sub-classes of AutoViewFieldMixin) now accepts `auto_id` parameter. This can be used to provide custom id for the field. The default is 'module.field_class_name'. Ideally only the first instance of an auto field is registered. This parameter can be used to force registration of additional instances by passing a unique value.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
__version__ = "1.0"
|
||||
__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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,35 +1,54 @@
|
|||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class AutoViewFieldMixin(object):
|
||||
"""Registers itself with AutoResponseView."""
|
||||
def __init__(self, *args, **kwargs):
|
||||
name = self.__class__.__name__
|
||||
name = kwargs.pop('auto_id', u"%s.%s" % (self.__module__, self.__class__.__name__))
|
||||
if logger.isEnabledFor(logging.INFO):
|
||||
logger.info("Registering auto field: %s", name)
|
||||
|
||||
from .util import register_field
|
||||
if name not in ['AutoViewFieldMixin', 'AutoModelSelect2Field']:
|
||||
id_ = register_field("%s.%s" % (self.__module__, name), self)
|
||||
self.widget.field_id = id_
|
||||
id_ = register_field(name, self)
|
||||
self.widget.field_id = id_
|
||||
super(AutoViewFieldMixin, self).__init__(*args, **kwargs)
|
||||
|
||||
def security_check(self, request, *args, **kwargs):
|
||||
return True
|
||||
|
||||
def get_results(self, request, term, page, context):
|
||||
raise NotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
import copy
|
||||
|
||||
from django import forms
|
||||
from django.forms.models import ModelChoiceIterator
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
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
|
||||
|
||||
### Light general fields ###
|
||||
|
||||
class Select2ChoiceField(forms.ChoiceField):
|
||||
widget = Select2Widget
|
||||
|
||||
class Select2MultipleChoiceField(forms.MultipleChoiceField):
|
||||
widget = Select2MultipleWidget
|
||||
|
||||
### Model fields related mixins ###
|
||||
|
||||
class ModelResultJsonMixin(object):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if self.queryset is None:
|
||||
def __init__(self, *args, **kwargs):
|
||||
if self.queryset is None and not kwargs.has_key('queryset'):
|
||||
raise ValueError('queryset is required.')
|
||||
|
||||
if not self.search_fields:
|
||||
|
|
@ -38,7 +57,7 @@ class ModelResultJsonMixin(object):
|
|||
self.max_results = getattr(self, 'max_results', None)
|
||||
self.to_field_name = getattr(self, 'to_field_name', 'pk')
|
||||
|
||||
super(ModelResultJsonMixin, self).__init__(**kwargs)
|
||||
super(ModelResultJsonMixin, self).__init__(*args, **kwargs)
|
||||
|
||||
def label_from_instance(self, obj):
|
||||
return smart_unicode(obj)
|
||||
|
|
@ -72,48 +91,114 @@ class ModelResultJsonMixin(object):
|
|||
res = [ (getattr(obj, self.to_field_name), self.label_from_instance(obj), ) for obj in res ]
|
||||
return (NO_ERR_RESP, has_more, res, )
|
||||
|
||||
class ModelValueMixin(object):
|
||||
default_error_messages = {
|
||||
'invalid_choice': _(u'Select a valid choice. That choice is not one of'
|
||||
u' the available choices.'),
|
||||
}
|
||||
class UnhideableQuerysetType(type):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if self.queryset is None:
|
||||
raise ValueError('queryset is required.')
|
||||
def __new__(cls, name, bases, dct):
|
||||
_q = dct.get('queryset', None)
|
||||
if _q is not None and not isinstance(_q, property):
|
||||
# This hack is needed since users are allowed to
|
||||
# provide queryset in sub-classes by declaring
|
||||
# class variable named - queryset, which will
|
||||
# effectively hide the queryset declared in this
|
||||
# mixin.
|
||||
dct.pop('queryset') # Throwing away the sub-class queryset
|
||||
dct['_subclass_queryset'] = _q
|
||||
|
||||
self.to_field_name = getattr(self, 'to_field_name', 'pk')
|
||||
return type.__new__(cls, name, bases, dct)
|
||||
|
||||
super(ModelValueMixin, self).__init__(**kwargs)
|
||||
def __call__(cls, *args, **kwargs):
|
||||
queryset = kwargs.get('queryset', None)
|
||||
if not queryset and hasattr(cls, '_subclass_queryset'):
|
||||
kwargs['queryset'] = getattr(cls, '_subclass_queryset')
|
||||
return type.__call__(cls, *args, **kwargs)
|
||||
|
||||
def to_python(self, value):
|
||||
if value in EMPTY_VALUES:
|
||||
return None
|
||||
try:
|
||||
key = self.to_field_name
|
||||
value = self.queryset.get(**{key: value})
|
||||
except (ValueError, self.queryset.model.DoesNotExist):
|
||||
raise ValidationError(self.error_messages['invalid_choice'])
|
||||
return value
|
||||
class ChoiceMixin(object):
|
||||
def _get_choices(self):
|
||||
if hasattr(self, '_choices'):
|
||||
return self._choices
|
||||
|
||||
class Select2ChoiceField(forms.ChoiceField):
|
||||
def _set_choices(self, value):
|
||||
# Setting choices also sets the choices on the widget.
|
||||
# choices can be any iterable, but we call list() on it because
|
||||
# it will be consumed more than once.
|
||||
self._choices = self.widget.choices = list(value)
|
||||
|
||||
choices = property(_get_choices, _set_choices)
|
||||
|
||||
class QuerysetChoiceMixin(ChoiceMixin):
|
||||
def _get_choices(self):
|
||||
# If self._choices is set, then somebody must have manually set
|
||||
# the property self.choices. In this case, just return self._choices.
|
||||
if hasattr(self, '_choices'):
|
||||
return self._choices
|
||||
|
||||
# Otherwise, execute the QuerySet in self.queryset to determine the
|
||||
# choices dynamically. Return a fresh ModelChoiceIterator that has not been
|
||||
# consumed. Note that we're instantiating a new ModelChoiceIterator *each*
|
||||
# time _get_choices() is called (and, thus, each time self.choices is
|
||||
# accessed) so that we can ensure the QuerySet has not been consumed. This
|
||||
# construct might look complicated but it allows for lazy evaluation of
|
||||
# the queryset.
|
||||
return ModelChoiceIterator(self)
|
||||
|
||||
choices = property(_get_choices, ChoiceMixin._set_choices)
|
||||
|
||||
class ModelChoiceFieldMixin(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
queryset = kwargs.pop('queryset', None)
|
||||
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(ModelChoiceFieldMixin, self).__init__(queryset, **kargs)
|
||||
|
||||
if hasattr(self, 'set_placeholder'):
|
||||
self.widget.set_placeholder(self.empty_label)
|
||||
|
||||
def _get_queryset(self):
|
||||
if hasattr(self, '_queryset'):
|
||||
return self._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)
|
||||
|
||||
### Light Fileds specialized for Models ###
|
||||
|
||||
class ModelSelect2Field(ModelChoiceField) :
|
||||
"Light Model Select2 field"
|
||||
widget = Select2Widget
|
||||
|
||||
class Select2MultipleChoiceField(forms.ChoiceField):
|
||||
class ModelSelect2MultipleField(ModelMultipleChoiceField) :
|
||||
"Light multiple-value Model Select2 field"
|
||||
widget = Select2MultipleWidget
|
||||
|
||||
class HeavySelect2FieldBase(forms.Field):
|
||||
def __init__(self, **kwargs):
|
||||
data_view = kwargs.pop('data_view', None)
|
||||
kargs = {}
|
||||
### Heavy fields ###
|
||||
|
||||
class HeavySelect2FieldBase(ChoiceMixin, forms.Field):
|
||||
def __init__(self, *args, **kwargs):
|
||||
data_view = kwargs.pop('data_view', None)
|
||||
self.choices = kwargs.pop('choices', [])
|
||||
|
||||
kargs = {}
|
||||
if data_view is not None:
|
||||
kargs['widget'] = self.widget(data_view=data_view)
|
||||
elif kwargs.get('widget', None) is None:
|
||||
raise ValueError('data_view is required else you need to provide your own widget instance.')
|
||||
|
||||
kargs.update(kwargs)
|
||||
super(HeavySelect2FieldBase, self).__init__(**kargs)
|
||||
super(HeavySelect2FieldBase, self).__init__(*args, **kargs)
|
||||
|
||||
class HeavySelect2ChoiceField(HeavySelect2FieldBase):
|
||||
widget = HeavySelect2Widget
|
||||
|
|
@ -121,7 +206,21 @@ class HeavySelect2ChoiceField(HeavySelect2FieldBase):
|
|||
class HeavySelect2MultipleChoiceField(HeavySelect2FieldBase):
|
||||
widget = HeavySelect2MultipleWidget
|
||||
|
||||
class AutoSelect2Field(ModelResultJsonMixin, AutoViewFieldMixin, HeavySelect2ChoiceField):
|
||||
### 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(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).
|
||||
|
|
@ -129,45 +228,54 @@ class AutoSelect2Field(ModelResultJsonMixin, AutoViewFieldMixin, HeavySelect2Cho
|
|||
|
||||
widget = AutoHeavySelect2Widget
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.data_view = "django_select2_central_json"
|
||||
kwargs['data_view'] = self.data_view
|
||||
super(AutoSelect2Field, self).__init__(**kwargs)
|
||||
super(AutoSelect2Field, self).__init__(*args, **kwargs)
|
||||
|
||||
class AutoModelSelect2Field(ModelResultJsonMixin, AutoViewFieldMixin, ModelValueMixin, HeavySelect2ChoiceField):
|
||||
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 = AutoHeavySelect2Widget
|
||||
widget = AutoHeavySelect2MultipleWidget
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.data_view = "django_select2_central_json"
|
||||
kwargs['data_view'] = self.data_view
|
||||
super(AutoModelSelect2Field, self).__init__(**kwargs)
|
||||
super(AutoSelect2MultipleField, self).__init__(*args, **kwargs)
|
||||
|
||||
class ModelSelect2Field(ModelValueMixin, Select2ChoiceField):
|
||||
def __init__(self, **kwargs):
|
||||
self.queryset = kwargs.pop('queryset', None)
|
||||
self.to_field_name = kwargs.pop('to_field_name', 'pk')
|
||||
|
||||
choices = kwargs.pop('choices', None)
|
||||
if choices is None:
|
||||
choices = []
|
||||
for obj in self.queryset.all():
|
||||
choices.append((getattr(obj, self.to_field_name), smart_unicode(obj), ))
|
||||
### Heavy field, specialized for Model, that uses central AutoView ###
|
||||
|
||||
kwargs['choices'] = choices
|
||||
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 # Makes sure that user defined queryset class variable is replaced by
|
||||
# queryset property (as it is needed by super classes).
|
||||
|
||||
super(ModelSelect2Field, self).__init__(**kwargs)
|
||||
widget = AutoHeavySelect2Widget
|
||||
|
||||
def valid_value(self, value):
|
||||
val = getattr(value, self.to_field_name)
|
||||
for k, v in self.choices:
|
||||
if k == val:
|
||||
return True
|
||||
return False
|
||||
def __init__(self, *args, **kwargs):
|
||||
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,3 +1,9 @@
|
|||
.error a.select2-choice {
|
||||
border: 1px solid #B94A48;
|
||||
}
|
||||
.select2-container {
|
||||
min-width: 150px;
|
||||
}
|
||||
.select2-container.select2-container-multi {
|
||||
width: 300px;
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
var django_select2 = {
|
||||
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 = {
|
||||
|
|
@ -35,56 +36,208 @@ var django_select2 = {
|
|||
return results;
|
||||
},
|
||||
setCookie: function (c_name, value) {
|
||||
document.cookie=c_name + "=" + escape(value);
|
||||
document.cookie = c_name + "=" + escape(value);
|
||||
},
|
||||
getCookie: function (c_name) {
|
||||
var i,x,y,ARRcookies=document.cookie.split(";");
|
||||
for (i=0; i<ARRcookies.length; i++) {
|
||||
x=ARRcookies[i].substr(0,ARRcookies[i].indexOf("="));
|
||||
y=ARRcookies[i].substr(ARRcookies[i].indexOf("=")+1);
|
||||
x=x.replace(/^\s+|\s+$/g,"");
|
||||
if (x==c_name) {
|
||||
var i, x, y, ARRcookies = document.cookie.split(";");
|
||||
|
||||
for (i = 0; i < ARRcookies.length; i++) {
|
||||
x = ARRcookies[i].substr(0,ARRcookies[i].indexOf("="));
|
||||
y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1);
|
||||
x = x.replace(/^\s+|\s+$/g,"");
|
||||
if (x == c_name) {
|
||||
return unescape(y);
|
||||
}
|
||||
}
|
||||
},
|
||||
delCookie: function (c_name) {
|
||||
document.cookie = c_name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
|
||||
},
|
||||
onValChange: function () {
|
||||
var e = $(this), res = e.data('results'), val = e.val(), txt, id = e.attr('id');
|
||||
|
||||
if (res) {
|
||||
for (var i in res) {
|
||||
if (res[i].id == val) {
|
||||
val = res[i].id; // To set it to correct data type.
|
||||
txt = res[i].text;
|
||||
break;
|
||||
delCookie: function (c_name, isStartsWithPattern) {
|
||||
var i, x, ARRcookies;
|
||||
|
||||
if (!isStartsWithPattern) {
|
||||
document.cookie = c_name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
|
||||
} else {
|
||||
ARRcookies = document.cookie.split(";");
|
||||
|
||||
for (i = 0; i < ARRcookies.length; i++) {
|
||||
x = ARRcookies[i].substr(0,ARRcookies[i].indexOf("="));
|
||||
x = x.replace(/^\s+|\s+$/g,"");
|
||||
if (x.indexOf(c_name) == 0) {
|
||||
document.cookie = c_name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
|
||||
}
|
||||
}
|
||||
if (txt) {
|
||||
// Cookies are used to persist selection's text. This needed
|
||||
//when the form springs back if there is any validation failure.
|
||||
django_select2.setCookie(id + '_heavy_val', val);
|
||||
django_select2.setCookie(id + '_heavy_txt', txt);
|
||||
return;
|
||||
}
|
||||
},
|
||||
onValChange: function () {
|
||||
var e = $(this), res, id = e.attr('id');
|
||||
|
||||
res = django_select2.getValText(e, false);
|
||||
|
||||
if (res && res[1]) {
|
||||
// Cookies are used to persist selection's text. This is needed
|
||||
// when the form springs back if there is any validation failure.
|
||||
$(res[0]).each(function (idx) {
|
||||
django_select2.setCookie(id + '_heavy_val:' + idx, this);
|
||||
django_select2.setCookie(id + '_heavy_txt:' + idx, res[1][idx]);
|
||||
});
|
||||
} else {
|
||||
django_select2.delCookie(id + '_heavy_val:', true);
|
||||
django_select2.delCookie(id + '_heavy_txt:', true);
|
||||
}
|
||||
},
|
||||
prepareValText: function (vals, txts, isMultiple) {
|
||||
var data = []
|
||||
$(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;
|
||||
}
|
||||
}
|
||||
django_select2.delCookie(id + '_heavy_val');
|
||||
django_select2.delCookie(id + '_heavy_txt');
|
||||
},
|
||||
onInit: function (e) {
|
||||
e = $(e);
|
||||
var id = e.attr('id'),
|
||||
val = django_select2.getCookie(id + '_heavy_val'),
|
||||
txt = django_select2.getCookie(id + '_heavy_txt');
|
||||
getValText: function ($e, isGetFromCookieAllowed) {
|
||||
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 || txt === 0) {
|
||||
return [val, txt];
|
||||
}
|
||||
|
||||
f = $e.data('userGetValText');
|
||||
if (f) {
|
||||
txt = f($e, val, isMultiple);
|
||||
if (txt || txt === 0) {
|
||||
return [val, txt];
|
||||
}
|
||||
}
|
||||
|
||||
if (res) {
|
||||
txt = [];
|
||||
$(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];
|
||||
}
|
||||
}
|
||||
|
||||
if (isGetFromCookieAllowed) {
|
||||
txt = [];
|
||||
$(val).each(function (idx) {
|
||||
var value = this, cookieVal;
|
||||
|
||||
cookieVal = django_select2.getCookie(id + '_heavy_val:' + idx);
|
||||
|
||||
if (cookieVal == value) {
|
||||
txt.push(django_select2.getCookie(id + '_heavy_txt:' + idx));
|
||||
}
|
||||
});
|
||||
if (txt || txt === 0) {
|
||||
return [val, txt];
|
||||
}
|
||||
}
|
||||
|
||||
if (txt && e.val() == val) {
|
||||
// Restores persisted value text.
|
||||
return {'id': val, 'text': txt};
|
||||
} else {
|
||||
e.val(null);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onInit: function (e, callback) {
|
||||
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);
|
||||
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
|
||||
},
|
||||
onMultipleHiddenChange: function () {
|
||||
var $e = $(this), valContainer = $e.data('valContainer'), name = $e.data('name'), vals = $e.val();
|
||||
valContainer.empty();
|
||||
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;
|
||||
|
||||
$e.data('name', $e.attr('name'));
|
||||
$e.attr('name', '');
|
||||
|
||||
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( $ ){
|
||||
// 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);
|
||||
}
|
||||
return this;
|
||||
} else {
|
||||
val = this.attr('txt');
|
||||
if (val && this.attr('multiple')) {
|
||||
val = val.split(django_select2.MULTISEPARATOR);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
};
|
||||
})( jQuery );
|
||||
|
|
@ -1,3 +1,95 @@
|
|||
import types
|
||||
import logging
|
||||
|
||||
from django.utils.html import escape
|
||||
from django.utils.encoding import force_unicode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def render_js_script(inner_code):
|
||||
return u"""
|
||||
<script>
|
||||
$(function () {
|
||||
%s
|
||||
});
|
||||
</script>""" % inner_code
|
||||
|
||||
class JSVar(unicode):
|
||||
"Denotes a JS variable name, so it must not be quoted while rendering."
|
||||
pass
|
||||
|
||||
class JSFunction(JSVar):
|
||||
"""
|
||||
Flags that the string is the name of a JS function. Used by Select2Mixin.render_options()
|
||||
to make sure that this string is not quoted like other strings.
|
||||
"""
|
||||
pass
|
||||
|
||||
class JSFunctionInContext(JSVar):
|
||||
"""
|
||||
Like JSFunction, this too flags the string as JS function, but with a special requirement.
|
||||
The JS function needs to be invoked in the context of the current Select2 Html DOM,
|
||||
such that 'this' inside the function refers to the source Select2 DOM.
|
||||
"""
|
||||
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
|
||||
|
||||
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 force_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'" % force_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 = [u'"%s"' % force_unicode(l) for l in lst]
|
||||
return u"[%s]" % (",".join(lst))
|
||||
|
||||
### Auto view helper utils ###
|
||||
|
||||
import re
|
||||
import threading
|
||||
import datetime
|
||||
|
|
@ -30,12 +122,17 @@ def register_field(name, field):
|
|||
|
||||
if name not in __field_store:
|
||||
# Generating id
|
||||
id_ = "%d:%s" % (len(__id_store), str(datetime.datetime.now()))
|
||||
id_ = u"%d:%s" % (len(__id_store), unicode(datetime.datetime.now()))
|
||||
|
||||
__field_store[name] = id_
|
||||
__id_store[id_] = field
|
||||
|
||||
if logger.isEnabledFor(logging.INFO):
|
||||
logger.info("Registering new field: %s; With actual id: %s", name, id_)
|
||||
else:
|
||||
id_ = __field_store[name]
|
||||
if logger.isEnabledFor(logging.INFO):
|
||||
logger.info("Field already registered: %s; With actual id: %s", name, id_)
|
||||
return id_
|
||||
|
||||
def get_field(id_):
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class Select2View(JSONResponseMixin, View):
|
|||
When everything is fine then the `err` must be 'nil'.
|
||||
`has_more` should be true if there are more rows.
|
||||
"""
|
||||
raise NotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AutoResponseView(Select2View):
|
||||
|
|
|
|||
|
|
@ -1,32 +1,27 @@
|
|||
import types
|
||||
import logging
|
||||
from itertools import chain
|
||||
|
||||
from django import forms
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.datastructures import MultiValueDict, MergeDict
|
||||
|
||||
class JSFunction(str):
|
||||
"""
|
||||
Flags that the string is the name of a JS function. Used by Select2Mixin.render_options()
|
||||
to make sure that this string is not quoted like other strings.
|
||||
"""
|
||||
pass
|
||||
from .util import render_js_script, convert_to_js_string_arr, JSVar, JSFunction, JSFunctionInContext, \
|
||||
convert_dict_to_js_map, convert_to_js_arr
|
||||
|
||||
class JSFunctionInContext(str):
|
||||
"""
|
||||
Like JSFunction, this too flags the string as JS function, but with a special requirement.
|
||||
The JS function needs to be invoked in the context of the current Select2 Html DOM,
|
||||
such that 'this' inside the function refers to the source Select2 DOM.
|
||||
"""
|
||||
pass
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
### Light mixin and widgets ###
|
||||
|
||||
class Select2Mixin(object):
|
||||
# For details on these options refer: http://ivaynberg.github.com/select2/#documentation
|
||||
options = {
|
||||
'minimumResultsForSearch': 6, # Only applicable for single value select.
|
||||
'placeholder': '',
|
||||
'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
|
||||
'closeOnSelect': False,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
|
@ -44,109 +39,48 @@ class Select2Mixin(object):
|
|||
def init_options(self):
|
||||
pass
|
||||
|
||||
def set_placeholder(self, val):
|
||||
self.options['placeholder'] = val
|
||||
|
||||
def get_options(self):
|
||||
options = dict(self.options)
|
||||
if options.get('allowClear', None) is not None:
|
||||
options['allowClear'] = not self.is_required
|
||||
return options
|
||||
|
||||
def render_options_code(self, options, id_):
|
||||
out = '{'
|
||||
is_first = True
|
||||
for name in options:
|
||||
if not is_first:
|
||||
out += ", "
|
||||
else:
|
||||
is_first = False
|
||||
def render_select2_options_code(self, options, id_):
|
||||
return convert_dict_to_js_map(options, id_)
|
||||
|
||||
out += "'%s': " % name
|
||||
val = options[name]
|
||||
if type(val) == types.BooleanType:
|
||||
out += 'true' if val else 'false'
|
||||
elif type(val) in [types.IntType, types.LongType, types.FloatType]:
|
||||
out += str(val)
|
||||
elif isinstance(val, JSFunctionInContext):
|
||||
out += """function () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
return %s.apply($('#%s').get(0), args);
|
||||
}""" % (val, id_)
|
||||
elif isinstance(val, JSFunction):
|
||||
out += val # No quotes here
|
||||
elif isinstance(val, dict):
|
||||
out += self.render_options_code(val, id_)
|
||||
else:
|
||||
out += "'%s'" % val
|
||||
|
||||
return out + '}'
|
||||
|
||||
def render_js_code(self, id_):
|
||||
def render_js_code(self, id_, *args):
|
||||
if id_:
|
||||
return u"""
|
||||
<script>
|
||||
$(function () {
|
||||
%s
|
||||
});
|
||||
</script>""" % self.render_inner_js_code(id_);
|
||||
return render_js_script(self.render_inner_js_code(id_, *args))
|
||||
return u''
|
||||
|
||||
def render_inner_js_code(self, id_):
|
||||
def render_inner_js_code(self, id_, *args):
|
||||
options = dict(self.get_options())
|
||||
options = self.render_options_code(options, id_)
|
||||
options = self.render_select2_options_code(options, id_)
|
||||
|
||||
return '$("#%s").select2(%s);' % (id_, options)
|
||||
return u'$("#%s").select2(%s);' % (id_, options)
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
s = unicode(super(Select2Mixin, self).render(name, value, attrs)) # Thanks to @ouhouhsami Issue#1
|
||||
def render(self, name, value, attrs=None, choices=()):
|
||||
args = [name, value, attrs]
|
||||
if choices: args.append(choices)
|
||||
|
||||
s = unicode(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_)
|
||||
s += self.render_js_code(id_, name, value, attrs, choices)
|
||||
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
logger.debug("Generated widget code:-\n%s", s)
|
||||
|
||||
return mark_safe(s)
|
||||
|
||||
class Media:
|
||||
js = ('js/select2.min.js', )
|
||||
css = {'screen': ('css/select2.css', 'css/extra.css', )}
|
||||
|
||||
class HeavySelect2Mixin(Select2Mixin):
|
||||
def __init__(self, **kwargs):
|
||||
self.options = dict(self.options) # Making an instance specific copy
|
||||
self.view = kwargs.pop('data_view', None)
|
||||
self.url = kwargs.pop('data_url', None)
|
||||
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,
|
||||
'data': JSFunctionInContext('django_select2.get_url_params'),
|
||||
'results': JSFunctionInContext('django_select2.process_results'),
|
||||
}
|
||||
self.options['minimumInputLength'] = 2
|
||||
self.options['initSelection'] = JSFunction('django_select2.onInit')
|
||||
super(HeavySelect2Mixin, self).__init__(**kwargs)
|
||||
|
||||
def get_options(self):
|
||||
if self.url is None:
|
||||
self.url = reverse(self.view) # We lazy resolve the view. By this time Url conf would been loaded fully.
|
||||
if self.options['ajax'].get('url', None) is None:
|
||||
self.options['ajax']['url'] = self.url
|
||||
return super(HeavySelect2Mixin, self).get_options()
|
||||
|
||||
def render_inner_js_code(self, id_):
|
||||
js = super(HeavySelect2Mixin, self).render_inner_js_code(id_)
|
||||
js += "$('#%s').change(django_select2.onValChange);" % id_
|
||||
return js
|
||||
|
||||
class Media:
|
||||
js = ('js/select2.min.js', 'js/heavy_data.js', )
|
||||
css = {'screen': ('css/select2.css', 'css/extra.css', )}
|
||||
|
||||
class AutoHeavySelect2Mixin(HeavySelect2Mixin):
|
||||
def render_inner_js_code(self, id_):
|
||||
js = super(AutoHeavySelect2Mixin, self).render_inner_js_code(id_)
|
||||
js += "$('#%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)
|
||||
|
|
@ -163,17 +97,124 @@ class Select2MultipleWidget(Select2Mixin, forms.SelectMultiple):
|
|||
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, u"", attrs))
|
||||
id_ = attrs.get('id', None)
|
||||
if 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):
|
||||
def __init__(self, **kwargs):
|
||||
self.options = dict(self.options) # Making an instance specific copy
|
||||
self.view = kwargs.pop('data_view', None)
|
||||
self.url = kwargs.pop('data_url', None)
|
||||
self.userGetValTextFuncName = kwargs.pop('userGetValTextFuncName', u'null')
|
||||
self.choices = kwargs.pop('choices', [])
|
||||
if not self.view and not self.url:
|
||||
raise ValueError('data_view or data_url is required')
|
||||
self.options['ajax'] = {
|
||||
'dataType': 'json',
|
||||
'quietMillis': 100,
|
||||
'data': JSFunctionInContext('django_select2.get_url_params'),
|
||||
'results': JSFunctionInContext('django_select2.process_results'),
|
||||
}
|
||||
self.options['minimumInputLength'] = 2
|
||||
self.options['initSelection'] = JSFunction('django_select2.onInit')
|
||||
super(HeavySelect2Mixin, self).__init__(**kwargs)
|
||||
|
||||
def render_texts(self, selected_choices, choices):
|
||||
selected_choices = list(force_unicode(v) for v in selected_choices)
|
||||
txts = []
|
||||
all_choices = choices if choices else []
|
||||
for val, txt in chain(self.choices, all_choices):
|
||||
val = force_unicode(val)
|
||||
if val in selected_choices:
|
||||
txts.append(txt)
|
||||
if txts:
|
||||
return convert_to_js_string_arr(txts)
|
||||
|
||||
def get_options(self):
|
||||
if self.url is None:
|
||||
self.url = reverse(self.view) # We lazy resolve the view. By this time Url conf would been loaded fully.
|
||||
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):
|
||||
if value is not None:
|
||||
values = [value] # Just like forms.Select.render() it assumes that value will be single valued.
|
||||
texts = self.render_texts(values, choices)
|
||||
if texts:
|
||||
return u"$('#%s').txt(%s);" % (id_, texts)
|
||||
|
||||
def render_inner_js_code(self, id_, name, value, attrs=None, choices=(), *args):
|
||||
js = u"$('#%s').change(django_select2.onValChange).data('userGetValText', %s);" \
|
||||
% (id_, self.userGetValTextFuncName)
|
||||
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
|
||||
|
||||
class Media:
|
||||
js = ('js/select2.min.js', 'js/heavy_data.js', )
|
||||
css = {'screen': ('css/select2.css', 'css/extra.css', )}
|
||||
|
||||
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):
|
||||
self.options['multiple'] = False
|
||||
|
||||
class HeavySelect2MultipleWidget(HeavySelect2Mixin, forms.TextInput):
|
||||
input_type = 'hidden' # We want it hidden but should be treated as if is_hidden is False
|
||||
class HeavySelect2MultipleWidget(HeavySelect2Mixin, MultipleSelect2HiddenInput):
|
||||
def init_options(self):
|
||||
self.options['multiple'] = True
|
||||
self.options.pop('allowClear', None)
|
||||
self.options.pop('minimumResultsForSearch', None)
|
||||
self.options['separator'] = JSVar('django_select2.MULTISEPARATOR')
|
||||
|
||||
def render_texts_for_value(self, id_, value, choices): # value is expected to be a list of values
|
||||
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 u"$('#%s').txt(%s);" % (id_, texts)
|
||||
|
||||
### Auto Heavy widgets ###
|
||||
|
||||
class AutoHeavySelect2Mixin(HeavySelect2Mixin):
|
||||
def render_inner_js_code(self, id_, *args):
|
||||
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
|
||||
|
|
|
|||
10
testapp/manage.py
Normal file
10
testapp/manage.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testapp.settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
BIN
testapp/test.db
Normal file
BIN
testapp/test.db
Normal file
Binary file not shown.
0
testapp/testapp/__init__.py
Normal file
0
testapp/testapp/__init__.py
Normal file
173
testapp/testapp/settings.py
Normal file
173
testapp/testapp/settings.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
# Django settings for testapp project.
|
||||
|
||||
import os.path
|
||||
import posixpath
|
||||
|
||||
PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
import os, sys
|
||||
|
||||
# Including the great parent so that djang_select2 can be found.
|
||||
parent_folder = PROJECT_ROOT
|
||||
parent_folder = parent_folder.split('/')[:-2]
|
||||
parent_folder = '/'.join(parent_folder)
|
||||
if parent_folder not in sys.path:
|
||||
sys.path.insert(0, parent_folder)
|
||||
###
|
||||
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
ADMINS = (
|
||||
# ('Your Name', 'your_email@example.com'),
|
||||
)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': 'test.db', # Or path to database file if using sqlite3.
|
||||
'USER': '', # Not used with sqlite3.
|
||||
'PASSWORD': '', # Not used with sqlite3.
|
||||
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
|
||||
'PORT': '', # Set to empty string for default. Not used with sqlite3.
|
||||
}
|
||||
}
|
||||
|
||||
# Local time zone for this installation. Choices can be found here:
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# although not all choices may be available on all operating systems.
|
||||
# In a Windows environment this must be set to your system time zone.
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
# Language code for this installation. All choices can be found here:
|
||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
# If you set this to False, Django will make some optimizations so as not
|
||||
# to load the internationalization machinery.
|
||||
USE_I18N = True
|
||||
|
||||
# If you set this to False, Django will not format dates, numbers and
|
||||
# calendars according to the current locale.
|
||||
USE_L10N = True
|
||||
|
||||
# If you set this to False, Django will not use timezone-aware datetimes.
|
||||
USE_TZ = True
|
||||
|
||||
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
||||
# Example: "/home/media/media.lawrence.com/media/"
|
||||
MEDIA_ROOT = os.path.join(PROJECT_ROOT, "site_media", "media")
|
||||
|
||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||
# trailing slash.
|
||||
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
|
||||
MEDIA_URL = "/site_media/media/"
|
||||
|
||||
# Absolute path to the directory static files should be collected to.
|
||||
# Don't put anything in this directory yourself; store your static files
|
||||
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
|
||||
# Example: "/home/media/media.lawrence.com/static/"
|
||||
STATIC_ROOT = os.path.join(PROJECT_ROOT, "site_media", "static")
|
||||
|
||||
# URL prefix for static files.
|
||||
# Example: "http://media.lawrence.com/static/"
|
||||
STATIC_URL = '/site_media/static/'
|
||||
|
||||
# Additional locations of static files
|
||||
STATICFILES_DIRS = (
|
||||
os.path.join(PROJECT_ROOT, "static"),
|
||||
)
|
||||
|
||||
# List of finder classes that know how to find static files in
|
||||
# various locations.
|
||||
STATICFILES_FINDERS = (
|
||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
|
||||
)
|
||||
|
||||
# Make this unique, and don't share it with anybody.
|
||||
SECRET_KEY = 'ps&l59kx$8%&a1vjcj9sim-k^)g9gca0+a@j7o#_ln$(w%-#+k'
|
||||
|
||||
# List of callables that know how to import templates from various sources.
|
||||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
# 'django.template.loaders.eggs.Loader',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
# Uncomment the next line for simple clickjacking protection:
|
||||
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'testapp.urls'
|
||||
|
||||
# Python dotted path to the WSGI application used by Django's runserver.
|
||||
WSGI_APPLICATION = 'testapp.wsgi.application'
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
os.path.join(PROJECT_ROOT, "templates"),
|
||||
)
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
# Uncomment the next line to enable the admin:
|
||||
# 'django.contrib.admin',
|
||||
# Uncomment the next line to enable admin documentation:
|
||||
# 'django.contrib.admindocs',
|
||||
"django_select2",
|
||||
"testmain",
|
||||
)
|
||||
|
||||
# A sample logging configuration. The only tangible logging
|
||||
# performed by this configuration is to send an email to
|
||||
# the site admins on every HTTP 500 error when DEBUG=False.
|
||||
# See http://docs.djangoproject.com/en/dev/topics/logging for
|
||||
# more details on how to customize your logging configuration.
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'filters': {
|
||||
'require_debug_false': {
|
||||
'()': 'django.utils.log.RequireDebugFalse'
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'filters': ['require_debug_false'],
|
||||
'class': 'django.utils.log.AdminEmailHandler'
|
||||
},
|
||||
'console':{
|
||||
'level':'DEBUG',
|
||||
'class':'logging.StreamHandler'
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django_select2': {
|
||||
'handlers':['console'],
|
||||
'propagate': True,
|
||||
'level':'INFO',
|
||||
},
|
||||
'django.request': {
|
||||
'handlers': ['mail_admins'],
|
||||
'level': 'ERROR',
|
||||
'propagate': True,
|
||||
},
|
||||
}
|
||||
}
|
||||
4
testapp/testapp/static/jquery-1.7.2.min.js
vendored
Normal file
4
testapp/testapp/static/jquery-1.7.2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
15
testapp/testapp/templates/form.html
Normal file
15
testapp/testapp/templates/form.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{% load staticfiles %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="{{ STATIC_URL }}jquery-1.7.2.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<form method="post" action="">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
{{form}}
|
||||
</table>
|
||||
<input type="submit" value="Submit Form"/>
|
||||
</form>
|
||||
</body>
|
||||
12
testapp/testapp/templates/index.html
Normal file
12
testapp/testapp/templates/index.html
Normal file
|
|
@ -0,0 +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>
|
||||
</body>
|
||||
13
testapp/testapp/templates/list.html
Normal file
13
testapp/testapp/templates/list.html
Normal file
|
|
@ -0,0 +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>
|
||||
15
testapp/testapp/urls.py
Normal file
15
testapp/testapp/urls.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from django.conf.urls import patterns, include, url
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
# from django.contrib import admin
|
||||
# admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', TemplateView.as_view(template_name="index.html"), name='home'),
|
||||
url(r'^test/', include('testmain.urls')),
|
||||
url(r'^ext/', include('django_select2.urls')),
|
||||
|
||||
# Uncomment the next line to enable the admin:
|
||||
# url(r'^admin/', include(admin.site.urls)),
|
||||
)
|
||||
28
testapp/testapp/wsgi.py
Normal file
28
testapp/testapp/wsgi.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
"""
|
||||
WSGI config for testapp project.
|
||||
|
||||
This module contains the WSGI application used by Django's development server
|
||||
and any production WSGI deployments. It should expose a module-level variable
|
||||
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
|
||||
this application via the ``WSGI_APPLICATION`` setting.
|
||||
|
||||
Usually you will have the standard Django WSGI application here, but it also
|
||||
might make sense to replace the whole Django WSGI application with a custom one
|
||||
that later delegates to the Django one. For example, you could introduce WSGI
|
||||
middleware here, or combine a Django application with an application of another
|
||||
framework.
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testapp.settings")
|
||||
|
||||
# This application object is used by any WSGI server configured to use this
|
||||
# file. This includes Django's development server, if the WSGI_APPLICATION
|
||||
# setting points here.
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
application = get_wsgi_application()
|
||||
|
||||
# Apply WSGI middleware here.
|
||||
# from helloworld.wsgi import HelloWorldApplication
|
||||
# application = HelloWorldApplication(application)
|
||||
0
testapp/testmain/__init__.py
Normal file
0
testapp/testmain/__init__.py
Normal file
143
testapp/testmain/fixtures/initial_data.json
Normal file
143
testapp/testmain/fixtures/initial_data.json
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
[
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "testmain.classroom",
|
||||
"fields": {
|
||||
"number": "100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "testmain.classroom",
|
||||
"fields": {
|
||||
"number": "200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "testmain.classroom",
|
||||
"fields": {
|
||||
"number": "203"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 4,
|
||||
"model": "testmain.classroom",
|
||||
"fields": {
|
||||
"number": "203A"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 5,
|
||||
"model": "testmain.classroom",
|
||||
"fields": {
|
||||
"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_labs": [1]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "testmain.dept",
|
||||
"fields": {
|
||||
"name": "Biology",
|
||||
"allotted_rooms": [3, 4],
|
||||
"allotted_labs": [2]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "testmain.dept",
|
||||
"fields": {
|
||||
"name": "Physics",
|
||||
"allotted_rooms": [1, 2, 5],
|
||||
"allotted_labs": [1, 3]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "testmain.employee",
|
||||
"fields": {
|
||||
"name": "John Roger",
|
||||
"salary": 8000,
|
||||
"dept": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "testmain.employee",
|
||||
"fields": {
|
||||
"name": "John Doe",
|
||||
"salary": 9000,
|
||||
"dept": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "testmain.employee",
|
||||
"fields": {
|
||||
"name": "John Mark",
|
||||
"salary": 10000,
|
||||
"dept": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 4,
|
||||
"model": "testmain.employee",
|
||||
"fields": {
|
||||
"name": "Mary Jane",
|
||||
"salary": 2000,
|
||||
"dept": 1,
|
||||
"manager": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 5,
|
||||
"model": "testmain.employee",
|
||||
"fields": {
|
||||
"name": "Hulk",
|
||||
"salary": 7000,
|
||||
"dept": 2,
|
||||
"manager": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 6,
|
||||
"model": "testmain.employee",
|
||||
"fields": {
|
||||
"name": "Green Gold",
|
||||
"salary": 4000,
|
||||
"dept": 2,
|
||||
"manager": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
32
testapp/testmain/forms.py
Normal file
32
testapp/testmain/forms.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
from django import forms
|
||||
|
||||
from django_select2 import *
|
||||
|
||||
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(required=False)
|
||||
dept = ModelSelect2Field(queryset=Dept.objects)
|
||||
|
||||
class Meta:
|
||||
model = Employee
|
||||
|
||||
class DeptForm(forms.ModelForm):
|
||||
allotted_rooms = ClassRoomChoices()
|
||||
allotted_labs = ModelSelect2MultipleField(queryset=Lab.objects, required=False)
|
||||
|
||||
class Meta:
|
||||
model = Dept
|
||||
|
||||
# These are just for testing Auto registration of fields
|
||||
EmployeeChoices() # Should already be registered
|
||||
EmployeeChoices(auto_id="EmployeeChoices_CustomAutoId") # Should get registered
|
||||
31
testapp/testmain/models.py
Normal file
31
testapp/testmain/models.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
from django.db import models
|
||||
|
||||
class ClassRoom(models.Model):
|
||||
number = models.CharField(max_length=4)
|
||||
|
||||
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)
|
||||
|
||||
class Employee(models.Model):
|
||||
name = models.CharField(max_length=30)
|
||||
salary = models.FloatField()
|
||||
dept = models.ForeignKey(Dept)
|
||||
manager = models.ForeignKey('Employee', null=True, blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.name)
|
||||
|
||||
9
testapp/testmain/urls.py
Normal file
9
testapp/testmain/urls.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns("",
|
||||
url(r'single/model/field/$', 'testmain.views.test_single_value_model_field', name='test_single_value_model_field'),
|
||||
url(r'single/model/field/([0-9]+)/$', 'testmain.views.test_single_value_model_field1', name='test_single_value_model_field1'),
|
||||
|
||||
url(r'multi/model/field/$', 'testmain.views.test_multi_values_model_field', name='test_multi_values_model_field'),
|
||||
url(r'multi/model/field/([0-9]+)/$', 'testmain.views.test_multi_values_model_field1', name='test_multi_values_model_field1'),
|
||||
)
|
||||
45
testapp/testmain/views.py
Normal file
45
testapp/testmain/views.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
from django.core.urlresolvers import reverse
|
||||
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 .models import Employee, Dept
|
||||
|
||||
def test_single_value_model_field(request):
|
||||
return render_to_response('list.html', RequestContext(request, {
|
||||
'title': 'Employees',
|
||||
'href': 'test_single_value_model_field1',
|
||||
'object_list': Employee.objects.all()
|
||||
}))
|
||||
|
||||
def test_single_value_model_field1(request, id):
|
||||
emp = get_object_or_404(Employee, pk=id)
|
||||
if request.POST:
|
||||
form = EmployeeForm(data=request.POST, instance=emp)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return HttpResponseRedirect(reverse('home'))
|
||||
else:
|
||||
form = EmployeeForm(instance=emp)
|
||||
return render_to_response('form.html', RequestContext(request, {'form': form}))
|
||||
|
||||
|
||||
def test_multi_values_model_field(request):
|
||||
return render_to_response('list.html', RequestContext(request, {
|
||||
'title': 'Departments',
|
||||
'href': 'test_multi_values_model_field1',
|
||||
'object_list': Dept.objects.all()
|
||||
}))
|
||||
|
||||
def test_multi_values_model_field1(request, id):
|
||||
dept = get_object_or_404(Dept, pk=id)
|
||||
if request.POST:
|
||||
form = DeptForm(data=request.POST, instance=dept)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return HttpResponseRedirect(reverse('home'))
|
||||
else:
|
||||
form = DeptForm(instance=dept)
|
||||
return render_to_response('form.html', RequestContext(request, {'form': form}))
|
||||
|
||||
Loading…
Reference in a new issue