v1.1 changes.

Lots of fixes and redesign. Also added a full fledged project for
testing out the components.
This commit is contained in:
AppleGrew (applegrew) 2012-08-19 23:13:15 +05:30
parent 14fa11836d
commit 8579993107
23 changed files with 744 additions and 131 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
testapp/tt.py
*.pyc

View file

@ -1,4 +1,4 @@
__version__ = "1.0"
__version__ = "1.1"
from django.conf import settings
if settings.configured:

View file

@ -12,11 +12,12 @@ class AutoViewFieldMixin(object):
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
@ -28,8 +29,8 @@ from .views import NO_ERR_RESP
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 +39,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 +73,105 @@ 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
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 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')
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)
if hasattr(self, 'set_placeholder'):
self.widget.set_placeholder(self.empty_label)
def _get_queryset(self):
if hasattr(self, '_queryset'):
return self._queryset
queryset = property(_get_queryset, forms.ModelChoiceField._set_queryset)
class Select2ChoiceField(forms.ChoiceField):
widget = Select2Widget
class Select2MultipleChoiceField(forms.ChoiceField):
class Select2MultipleChoiceField(forms.MultipleChoiceField):
widget = Select2MultipleWidget
class HeavySelect2FieldBase(forms.Field):
def __init__(self, **kwargs):
class HeavySelect2FieldBase(ChoiceMixin, forms.Field):
def __init__(self, *args, **kwargs):
data_view = kwargs.pop('data_view', None)
kargs = {}
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,6 +179,11 @@ class HeavySelect2ChoiceField(HeavySelect2FieldBase):
class HeavySelect2MultipleChoiceField(HeavySelect2FieldBase):
widget = HeavySelect2MultipleWidget
class HeavyModelSelect2ChoiceField(QuerysetChoiceMixin, HeavySelect2ChoiceField, ModelChoiceField):
def __init__(self, *args, **kwargs):
kwargs.pop('choices', None)
super(HeavyModelSelect2ChoiceField, self).__init__(*args, **kwargs)
class AutoSelect2Field(ModelResultJsonMixin, AutoViewFieldMixin, HeavySelect2ChoiceField):
"""
This needs to be subclassed. The first instance of a class (sub-class) is used to serve all incoming
@ -129,45 +192,26 @@ 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 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
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(AutoModelSelect2Field, self).__init__(**kwargs)
super(AutoModelSelect2Field, 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), ))
class ModelSelect2Field(ModelChoiceField) :
"Light Model Select2 field"
widget = Select2Widget
kwargs['choices'] = choices
super(ModelSelect2Field, self).__init__(**kwargs)
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

View file

@ -1,3 +1,6 @@
.error a.select2-choice {
border: 1px solid #B94A48;
}
.select2-container {
min-width: 150px;
}

View file

@ -1,5 +1,6 @@
var django_select2 = {
MULTISEPARATOR: String.fromCharCode(0),
get_url_params: function (term, page, context) {
var field_id = $(this).data('field_id'),
res = {
@ -35,56 +36,173 @@ 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) {
// 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');
valContainer.empty();
$($e.val()).each(function () {
var inp = $('<input>').appendTo(valContainer);
inp.attr('type', 'hidden');
inp.attr('name', name);
inp.val(this);
});
},
initMultipleHidden: function ($e) {
var valContainer;
$e.data('name', $e.attr('name'));
$e.attr('name', '');
valContainer = $e.after('<div>').css({'display': 'none'});
$e.data('valContainer', valContainer);
$e.change(django_select2.onMultipleHiddenChange);
},
convertArrToStr: function (arr) {
return arr.join(django_select2.MULTISEPARATOR);
}
};
};
(function( $ ){
$.fn.txt = function() {
return this.attr('txt');
};
})( jQuery );

View file

@ -1,3 +1,17 @@
def convert_to_js_string_arr(lst):
lst = ['"%s"' % l for l in lst]
return u"[%s]" % (",".join(lst))
def render_js_script(inner_code):
return u"""
<script>
$(function () {
%s
});
</script>""" % inner_code
### Auto view helper utils ###
import re
import threading
import datetime

View file

@ -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):

View file

@ -1,17 +1,24 @@
import types
from itertools import chain
from django import forms
from django.utils.safestring import mark_safe
from django.core.urlresolvers import reverse
class JSFunction(str):
from .util import render_js_script, convert_to_js_string_arr
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(str):
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,
@ -23,10 +30,10 @@ 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,63 +51,64 @@ 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_):
def render_select2_options_code(self, options, id_):
out = '{'
is_first = True
for name in options:
if not is_first:
out += ", "
out += u", "
else:
is_first = False
out += "'%s': " % name
out += u"'%s': " % name
val = options[name]
if type(val) == types.BooleanType:
out += 'true' if val else 'false'
out += u'true' if val else u'false'
elif type(val) in [types.IntType, types.LongType, types.FloatType]:
out += str(val)
out += unicode(val)
elif isinstance(val, JSFunctionInContext):
out += """function () {
out += u"""function () {
var args = Array.prototype.slice.call(arguments);
return %s.apply($('#%s').get(0), args);
}""" % (val, id_)
elif isinstance(val, JSFunction):
elif isinstance(val, JSVar):
out += val # No quotes here
elif isinstance(val, dict):
out += self.render_options_code(val, id_)
out += self.render_select2_options_code(val, id_)
else:
out += "'%s'" % val
out += u"'%s'" % val
return out + '}'
return out + u'}'
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)
return mark_safe(s)
class Media:
@ -112,6 +120,8 @@ class HeavySelect2Mixin(Select2Mixin):
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.url = None
@ -125,6 +135,15 @@ class HeavySelect2Mixin(Select2Mixin):
self.options['initSelection'] = JSFunction('django_select2.onInit')
super(HeavySelect2Mixin, self).__init__(**kwargs)
def render_texts(self, selected_choices, choices):
txts = []
all_choices = choices if choices else []
for val, txt in chain(self.choices, all_choices):
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.
@ -132,19 +151,40 @@ class HeavySelect2Mixin(Select2Mixin):
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_
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').attr('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 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_):
js = super(AutoHeavySelect2Mixin, self).render_inner_js_code(id_)
js += "$('#%s').data('field_id', '%s');" % (id_, self.field_id)
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):
@ -168,12 +208,18 @@ class HeavySelect2Widget(HeavySelect2Mixin, forms.TextInput):
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 render_js_script(u"$('#%s').attr('txt', %s);" % (id_, texts))
class AutoHeavySelect2Widget(AutoHeavySelect2Mixin, HeavySelect2Widget):
pass

10
testapp/manage.py Normal file
View 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

Binary file not shown.

View file

164
testapp/testapp/settings.py Normal file
View file

@ -0,0 +1,164 @@
# 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&amp;l59kx$8%&amp;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'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}

View file

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

View file

@ -0,0 +1,5 @@
{% load url from future %}
<h1>Manual Tests</h1>
<ul>
<li><a href="{% url 'test_auto_model_field' %}">Test single selection model fields</a></li>
</ul>

View file

@ -0,0 +1,8 @@
{% load url from future %}
<h2>{{title}}</h2>
<ul>
{% for e in object_list %}
<li><a href="{% url href e.id %}">{{ e }}</a></li>
{% endfor %}
</ul>

15
testapp/testapp/urls.py Normal file
View 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
View 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)

View file

View file

@ -0,0 +1,81 @@
[
{
"pk": 1,
"model": "testmain.dept",
"fields": {
"name": "Chemistry"
}
},
{
"pk": 2,
"model": "testmain.dept",
"fields": {
"name": "Biology"
}
},
{
"pk": 3,
"model": "testmain.dept",
"fields": {
"name": "Physics"
}
},
{
"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
}
}
]

17
testapp/testmain/forms.py Normal file
View file

@ -0,0 +1,17 @@
from django import forms
from django_select2 import *
from .models import Employee, Dept
class EmployeeChoices(AutoModelSelect2Field):
queryset = Employee.objects
search_fields = ['name__icontains', ]
class EmployeeForm(forms.ModelForm):
manager = EmployeeChoices()
dept = ModelSelect2Field(queryset=Dept.objects)
class Meta:
model = Employee

View file

@ -0,0 +1,16 @@
from django.db import models
class Dept(models.Model):
name = models.CharField(max_length=10)
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)

6
testapp/testmain/urls.py Normal file
View file

@ -0,0 +1,6 @@
from django.conf.urls.defaults import *
urlpatterns = patterns("",
url(r'auto/model/field/$', 'testmain.views.test_auto_model_field', name='test_auto_model_field'),
url(r'auto/model/field/([0-9]+)/$', 'testmain.views.test_auto_model_field1', name='test_auto_model_field2'),
)

25
testapp/testmain/views.py Normal file
View file

@ -0,0 +1,25 @@
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
from .forms import EmployeeForm
from .models import Employee
def test_auto_model_field(request):
return render_to_response('list.html', RequestContext(request, {
'title': 'Employees',
'href': 'test_auto_model_field2',
'object_list': Employee.objects.all()
}))
def test_auto_model_field1(request, id):
emp = Employee.objects.get(id=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}))