v2.0 Changes from v1.1 branch

This commit is contained in:
AppleGrew (applegrew) 2012-08-22 12:06:49 +05:30
commit 92ddcf3bb9
25 changed files with 1163 additions and 206 deletions

5
.gitignore vendored
View file

@ -2,3 +2,8 @@
testapp/tt.py
*.pyc
<<<<<<< HEAD
=======
testapp/tt2.py
>>>>>>> v1.1

View file

@ -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.

View file

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

View file

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

View file

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

View file

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

View file

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

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,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
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

173
testapp/testapp/settings.py Normal file
View 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&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'
},
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler'
},
},
'loggers': {
'django_select2': {
'handlers':['console'],
'propagate': True,
'level':'INFO',
},
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}

File diff suppressed because one or more lines are too long

View 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>

View 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>

View 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
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,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
View 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

View 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
View 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
View 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}))