mirror of
https://github.com/Hopiu/django-select2.git
synced 2026-04-13 01:50:58 +00:00
Adding Tagging support.
This commit is contained in:
parent
12ad5255d0
commit
ab57470b02
14 changed files with 289 additions and 18 deletions
3
README
3
README
|
|
@ -49,9 +49,10 @@ Special Thanks
|
|||
Changelog Summary
|
||||
=================
|
||||
|
||||
### v4.1.1
|
||||
### v4.2.0
|
||||
|
||||
* Updated Select2 to version 3.4.2. **Please note**, that if you need any of the Select2 locale files, then you need to download them yourself from http://ivaynberg.github.com/select2/ and add to your project.
|
||||
* Tagging support added. See [Field API reference](http://django-select2.readthedocs.org/en/latest/ref_fields.html) in documentation.
|
||||
|
||||
### v4.1.0
|
||||
|
||||
|
|
|
|||
|
|
@ -49,9 +49,10 @@ Special Thanks
|
|||
Changelog Summary
|
||||
=================
|
||||
|
||||
### v4.1.1
|
||||
### v4.2.0
|
||||
|
||||
* Updated Select2 to version 3.4.2. **Please note**, that if you need any of the Select2 locale files, then you need to download them yourself from http://ivaynberg.github.com/select2/ and add to your project.
|
||||
* Tagging support added. See [Field API reference](http://django-select2.readthedocs.org/en/latest/ref_fields.html) in documentation.
|
||||
|
||||
### v4.1.0
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@ in their names.
|
|||
**Available widgets:**
|
||||
|
||||
:py:class:`.Select2Widget`, :py:class:`.Select2MultipleWidget`, :py:class:`.HeavySelect2Widget`, :py:class:`.HeavySelect2MultipleWidget`,
|
||||
:py:class:`.AutoHeavySelect2Widget`, :py:class:`.AutoHeavySelect2MultipleWidget`
|
||||
:py:class:`.AutoHeavySelect2Widget`, :py:class:`.AutoHeavySelect2MultipleWidget`, :py:class:`.HeavySelect2TagWidget`,
|
||||
:py:class:`.AutoHeavySelect2TagWidget`
|
||||
|
||||
`Read more`_
|
||||
|
||||
|
|
@ -57,7 +58,8 @@ your ease.
|
|||
:py:class:`.HeavySelect2MultipleChoiceField`, :py:class:`.HeavyModelSelect2ChoiceField`,
|
||||
:py:class:`.HeavyModelSelect2MultipleChoiceField`, :py:class:`.ModelSelect2Field`, :py:class:`.ModelSelect2MultipleField`,
|
||||
:py:class:`.AutoSelect2Field`, :py:class:`.AutoSelect2MultipleField`, :py:class:`.AutoModelSelect2Field`,
|
||||
:py:class:`.AutoModelSelect2MultipleField`
|
||||
:py:class:`.AutoModelSelect2MultipleField`, :py:class:`.HeavySelect2TagField`, :py:class:`.AutoSelect2TagField`,
|
||||
:py:class:`.HeavyModelSelect2TagField`, :py:class:`.AutoModelSelect2TagField`
|
||||
|
||||
Views
|
||||
-----
|
||||
|
|
@ -77,7 +79,7 @@ The view - `Select2View`, exposed here is meant to be used with 'Heavy' fields a
|
|||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__version__ = "4.1.1"
|
||||
__version__ = "4.2.0"
|
||||
|
||||
__RENDER_SELECT2_STATICS = False
|
||||
__ENABLE_MULTI_PROCESS_SUPPORT = False
|
||||
|
|
@ -89,6 +91,8 @@ __SECRET_SALT = ''
|
|||
|
||||
try:
|
||||
from django.conf import settings
|
||||
if logger.isEnabledFor(logging.INFO):
|
||||
logger.info("Django found.")
|
||||
if settings.configured:
|
||||
__RENDER_SELECT2_STATICS = getattr(settings, 'AUTO_RENDER_SELECT2_STATICS', True)
|
||||
__ENABLE_MULTI_PROCESS_SUPPORT = getattr(settings, 'ENABLE_SELECT2_MULTI_PROCESS_SUPPORT', False)
|
||||
|
|
@ -103,12 +107,16 @@ try:
|
|||
__ENABLE_MULTI_PROCESS_SUPPORT = False
|
||||
|
||||
from .widgets import Select2Widget, Select2MultipleWidget, HeavySelect2Widget, HeavySelect2MultipleWidget, \
|
||||
AutoHeavySelect2Widget, AutoHeavySelect2MultipleWidget
|
||||
AutoHeavySelect2Widget, AutoHeavySelect2MultipleWidget, HeavySelect2TagWidget, AutoHeavySelect2TagWidget
|
||||
from .fields import Select2ChoiceField, Select2MultipleChoiceField, HeavySelect2ChoiceField, \
|
||||
HeavySelect2MultipleChoiceField, HeavyModelSelect2ChoiceField, HeavyModelSelect2MultipleChoiceField, \
|
||||
ModelSelect2Field, ModelSelect2MultipleField, AutoSelect2Field, AutoSelect2MultipleField, \
|
||||
AutoModelSelect2Field, AutoModelSelect2MultipleField
|
||||
AutoModelSelect2Field, AutoModelSelect2MultipleField, HeavySelect2TagField, AutoSelect2TagField, \
|
||||
HeavyModelSelect2TagField, AutoModelSelect2TagField
|
||||
from .views import Select2View, NO_ERR_RESP
|
||||
|
||||
if logger.isEnabledFor(logging.INFO):
|
||||
logger.info("Django found and fields and widgest loaded.")
|
||||
except ImportError:
|
||||
if logger.isEnabledFor(logging.INFO):
|
||||
logger.info("Django not found.")
|
||||
|
|
|
|||
|
|
@ -82,11 +82,12 @@ from django.core.exceptions import ValidationError
|
|||
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.utils.encoding import smart_unicode, force_unicode
|
||||
|
||||
from .widgets import Select2Widget, Select2MultipleWidget,\
|
||||
HeavySelect2Widget, HeavySelect2MultipleWidget, AutoHeavySelect2Widget, \
|
||||
AutoHeavySelect2MultipleWidget, AutoHeavySelect2Mixin
|
||||
AutoHeavySelect2MultipleWidget, AutoHeavySelect2Mixin, AutoHeavySelect2TagWidget, \
|
||||
HeavySelect2TagWidget
|
||||
from .views import NO_ERR_RESP
|
||||
from .util import extract_some_key_val
|
||||
|
||||
|
|
@ -585,6 +586,38 @@ class HeavySelect2MultipleChoiceField(HeavySelect2FieldBaseMixin, HeavyMultipleC
|
|||
"Heavy Select2 Multiple Choice field."
|
||||
widget = HeavySelect2MultipleWidget
|
||||
|
||||
class HeavySelect2TagField(HeavySelect2MultipleChoiceField):
|
||||
"""
|
||||
Heavy Select2 field for tagging.
|
||||
|
||||
.. warning:: :py:exc:`NotImplementedError` would be thrown if :py:meth:`create_new_value` is not implemented.
|
||||
"""
|
||||
widget = HeavySelect2TagWidget
|
||||
|
||||
def validate(self, value):
|
||||
if self.required and not value:
|
||||
raise ValidationError(self.error_messages['required'])
|
||||
# Check if each value in the value list is in self.choices or
|
||||
# the big data (i.e. validate_value() returns True).
|
||||
# If not then calls create_new_value() to create the new value.
|
||||
for i in range(0, len(value)):
|
||||
val = value[i]
|
||||
if not self.valid_value(val):
|
||||
value[i] = self.create_new_value(val)
|
||||
|
||||
def create_new_value(self, value):
|
||||
"""
|
||||
This is called when the input value is not valid. This
|
||||
allows you to add the value into the data-store. If that
|
||||
is not done then eventually the validation will fail.
|
||||
|
||||
:param value: Invalid value entered by the user.
|
||||
:type value: As coerced by :py:meth:`HeavyChoiceField.coerce_value`.
|
||||
|
||||
:return: The a new value, which could be the id (pk) of the created value.
|
||||
:rtype: Any
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
### Heavy field specialized for Models ###
|
||||
|
||||
|
|
@ -605,6 +638,92 @@ class HeavyModelSelect2MultipleChoiceField(HeavySelect2FieldBaseMixin, ModelMult
|
|||
kwargs.pop('choices', None)
|
||||
super(HeavyModelSelect2MultipleChoiceField, self).__init__(*args, **kwargs)
|
||||
|
||||
class HeavyModelSelect2TagField(HeavySelect2FieldBaseMixin, ModelMultipleChoiceField):
|
||||
"""
|
||||
Heavy Select2 field for tagging, specialized for Models.
|
||||
|
||||
.. warning:: :py:exc:`NotImplementedError` would be thrown if :py:meth:`get_model_field_values` is not implemented.
|
||||
"""
|
||||
widget = HeavySelect2TagWidget
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.pop('choices', None)
|
||||
super(HeavyModelSelect2TagField, self).__init__(*args, **kwargs)
|
||||
|
||||
def to_python(self, value):
|
||||
if value in EMPTY_VALUES:
|
||||
return None
|
||||
try:
|
||||
key = self.to_field_name or 'pk'
|
||||
value = self.queryset.get(**{key: value})
|
||||
except ValueError, e:
|
||||
raise ValidationError(self.error_messages['invalid_choice'])
|
||||
except self.queryset.model.DoesNotExist:
|
||||
value = self.create_new_value(value)
|
||||
return value
|
||||
|
||||
def clean(self, value):
|
||||
if self.required and not value:
|
||||
raise ValidationError(self.error_messages['required'])
|
||||
elif not self.required and not value:
|
||||
return []
|
||||
if not isinstance(value, (list, tuple)):
|
||||
raise ValidationError(self.error_messages['list'])
|
||||
new_values = []
|
||||
key = self.to_field_name or 'pk'
|
||||
for pk in list(value):
|
||||
try:
|
||||
self.queryset.filter(**{key: pk})
|
||||
except ValueError:
|
||||
value.remove(pk)
|
||||
new_values.append(pk)
|
||||
|
||||
for val in new_values:
|
||||
value.append(self.create_new_value(force_unicode(val)))
|
||||
|
||||
# Usually new_values will have list of new tags, but if the tag is
|
||||
# suppose of type int then that could be interpreted as valid pk
|
||||
# value and ValueError above won't be triggered.
|
||||
# Below we find such tags and create them, by check if the pk
|
||||
# actually exists.
|
||||
qs = self.queryset.filter(**{'%s__in' % key: value})
|
||||
pks = set([force_unicode(getattr(o, key)) for o in qs])
|
||||
for i in range(0, len(value)):
|
||||
val = force_unicode(value[i])
|
||||
if val not in pks:
|
||||
value[i] = self.create_new_value(val)
|
||||
# Since this overrides the inherited ModelChoiceField.clean
|
||||
# we run custom validators here
|
||||
self.run_validators(value)
|
||||
return qs
|
||||
|
||||
def create_new_value(self, value):
|
||||
"""
|
||||
This is called when the input value is not valid. This
|
||||
allows you to add the value into the data-store. If that
|
||||
is not done then eventually the validation will fail.
|
||||
|
||||
:param value: Invalid value entered by the user.
|
||||
:type value: As coerced by :py:meth:`HeavyChoiceField.coerce_value`.
|
||||
|
||||
:return: The a new value, which could be the id (pk) of the created value.
|
||||
:rtype: Any
|
||||
"""
|
||||
obj = self.queryset.create(**self.get_model_field_values(value))
|
||||
return getattr(obj, self.to_field_name or 'pk')
|
||||
|
||||
def get_model_field_values(self, value):
|
||||
"""
|
||||
This is called when the input value is not valid and the field
|
||||
tries to create a new model instance.
|
||||
|
||||
:param value: Invalid value entered by the user.
|
||||
:type value: unicode
|
||||
|
||||
:return: Dict with attribute name - attribute value pair.
|
||||
:rtype: dict
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
### Heavy general field that uses central AutoView ###
|
||||
|
||||
|
|
@ -633,6 +752,17 @@ class AutoSelect2MultipleField(AutoViewFieldMixin, HeavySelect2MultipleChoiceFie
|
|||
|
||||
widget = AutoHeavySelect2MultipleWidget
|
||||
|
||||
class AutoSelect2TagField(AutoViewFieldMixin, HeavySelect2TagField):
|
||||
"""
|
||||
Auto Heavy Select2 field for tagging.
|
||||
|
||||
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).
|
||||
|
||||
.. warning:: :py:exc:`NotImplementedError` would be thrown if :py:meth:`get_results` is not implemented.
|
||||
"""
|
||||
|
||||
widget = AutoHeavySelect2TagWidget
|
||||
|
||||
### Heavy field, specialized for Model, that uses central AutoView ###
|
||||
|
||||
|
|
@ -663,3 +793,30 @@ class AutoModelSelect2MultipleField(ModelResultJsonMixin, AutoViewFieldMixin, He
|
|||
|
||||
widget = AutoHeavySelect2MultipleWidget
|
||||
|
||||
class AutoModelSelect2TagField(ModelResultJsonMixin, AutoViewFieldMixin, HeavyModelSelect2TagField):
|
||||
"""
|
||||
Auto Heavy Select2 field for tagging, specialized for Models.
|
||||
|
||||
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).
|
||||
|
||||
.. warning:: :py:exc:`NotImplementedError` would be thrown if :py:meth:`get_model_field_values` is not implemented.
|
||||
|
||||
Example::
|
||||
class Tag(models.Model):
|
||||
tag = models.CharField(max_length=10, unique=True)
|
||||
def __unicode__(self):
|
||||
return unicode(self.tag)
|
||||
|
||||
class TagField(AutoModelSelect2TagField):
|
||||
queryset = Tag.objects
|
||||
search_fields = ['tag__icontains', ]
|
||||
def get_model_field_values(self, value):
|
||||
return {'tag': value}
|
||||
|
||||
"""
|
||||
# Makes sure that user defined queryset class variable is replaced by
|
||||
# queryset property (as it is needed by super classes).
|
||||
__metaclass__ = UnhideableQuerysetType
|
||||
|
||||
widget = AutoHeavySelect2TagWidget
|
||||
|
|
|
|||
|
|
@ -150,6 +150,16 @@ if (!window['django_select2']) {
|
|||
callback(data); // Change for 2.3.x
|
||||
django_select2.updateText(e);
|
||||
},
|
||||
createSearchChoice: function(term, data) {
|
||||
if (!data || $(data).filter(function () {
|
||||
return this.text.localeCompare(term) === 0;
|
||||
}).length === 0) {
|
||||
return {
|
||||
id: term,
|
||||
text: term
|
||||
};
|
||||
}
|
||||
},
|
||||
onMultipleHiddenChange: function () {
|
||||
var $e = $(this), valContainer = $e.data('valContainer'), name = $e.data('name'), vals = $e.val();
|
||||
valContainer.empty();
|
||||
|
|
|
|||
2
django_select2/static/js/heavy_data.min.js
vendored
2
django_select2/static/js/heavy_data.min.js
vendored
|
|
@ -1 +1 @@
|
|||
if(!window.django_select2){var django_select2={MULTISEPARATOR:String.fromCharCode(0),get_url_params:function(c,e,b){var d=$(this).data("field_id"),a={term:c,page:e,context:b};if(d){a.field_id=d}return a},process_results:function(d,c,b){var a;if(d.err&&d.err.toLowerCase()==="nil"){a={results:d.results};if(b){a.context=b}if(d.more===true||d.more===false){a.more=d.more}}else{a={results:[]}}if(a.results){$(this).data("results",a.results)}else{$(this).removeData("results")}return a},onValChange:function(){django_select2.updateText($(this))},prepareValText:function(d,a,c){var b=[];$(d).each(function(e){b.push({id:this,text:a[e]})});if(c){return b}else{if(b.length>0){return b[0]}else{return null}}},updateText:function(b){var f=b.select2("val"),d=b.select2("data"),a=b.txt(),c=!!b.attr("multiple"),e;if(f||f===0){if(c){if(f.length!==a.length){a=[];$(f).each(function(g){var h,j=this,k;for(h in d){k=d[h].id;if(k instanceof String){k=k.valueOf()}if(k==j){a.push(d[h].text)}}})}}else{a=d.text}b.txt(a)}else{b.txt("")}},getValText:function(b){var g=b.select2("val"),c=b.data("results"),a=b.txt(),e=!!b.attr("multiple"),d,h=b.attr("id");if(g||g===0){if(!e){g=[g];if(a||a===0){a=[a]}}if(a===0||(a&&g.length===a.length)){return[g,a]}d=b.data("userGetValText");if(d){a=d(b,g,e);if(a||a===0){return[g,a]}}if(c){a=[];$(g).each(function(f){var j,k=this;for(j in c){if(c[j].id==k){g[f]=c[j].id;a.push(c[j].text)}}});if(a||a===0){return[g,a]}}}return null},onInit:function(b,f){b=$(b);var d=b.attr("id"),a=null,c=b.select2("val");if(!c&&c!==0){c=b.data("initVal")}if(c||c===0){a=django_select2.getValText(b);if(a&&a[0]){a=django_select2.prepareValText(a[0],a[1],!!b.attr("multiple"))}}if(!a){b.val(null)}f(a);django_select2.updateText(b)},onMultipleHiddenChange:function(){var b=$(this),d=b.data("valContainer"),a=b.data("name"),c=b.val();d.empty();if(c){c=c.split(django_select2.MULTISEPARATOR);$(c).each(function(){var e=$('<input type="hidden">').appendTo(d);e.attr("name",a);e.val(this)})}},initMultipleHidden:function(a){var b;a.data("name",a.attr("name"));a.attr("name","");b=$("<div>").insertAfter(a).css({display:"none"});a.data("valContainer",b);a.change(django_select2.onMultipleHiddenChange);if(a.val()){a.change()}},convertArrToStr:function(a){return a.join(django_select2.MULTISEPARATOR)},runInContextHelper:function(a,b){return function(){var c=Array.prototype.slice.call(arguments,0);return a.apply($("#"+b).get(0),c)}},logErr:function(){if(console&&console.error){args=Array.prototype.slice.call(arguments);console.error.apply(console,args)}}};(function(b){if(b){for(var a in django_select2){var c=django_select2[a];if(typeof(c)=="function"){django_select2[a]=(function(d,e){return function(){console.log("Function "+d+" called for object: ",this);return e.apply(this,arguments)}}(a,c))}}}}(false));(function(a){a.fn.txt=function(b){if(typeof(b)!=="undefined"){if(b){if(b instanceof Array){if(this.attr("multiple")){b=django_select2.convertArrToStr(b)}else{b=b[0]}}this.attr("txt",b)}else{this.removeAttr("txt")}return this}else{b=this.attr("txt");if(this.attr("multiple")){if(b){b=b.split(django_select2.MULTISEPARATOR)}else{b=[]}}return b}}})(jQuery)};
|
||||
if(!window.django_select2){var django_select2={MULTISEPARATOR:String.fromCharCode(0),get_url_params:function(c,e,b){var d=$(this).data("field_id"),a={term:c,page:e,context:b};if(d){a.field_id=d}return a},process_results:function(d,c,b){var a;if(d.err&&d.err.toLowerCase()==="nil"){a={results:d.results};if(b){a.context=b}if(d.more===true||d.more===false){a.more=d.more}}else{a={results:[]}}if(a.results){$(this).data("results",a.results)}else{$(this).removeData("results")}return a},onValChange:function(){django_select2.updateText($(this))},prepareValText:function(d,a,c){var b=[];$(d).each(function(e){b.push({id:this,text:a[e]})});if(c){return b}else{if(b.length>0){return b[0]}else{return null}}},updateText:function(b){var f=b.select2("val"),d=b.select2("data"),a=b.txt(),c=!!b.attr("multiple"),e;if(f||f===0){if(c){if(f.length!==a.length){a=[];$(f).each(function(g){var h,j=this,k;for(h in d){k=d[h].id;if(k instanceof String){k=k.valueOf()}if(k==j){a.push(d[h].text)}}})}}else{a=d.text}b.txt(a)}else{b.txt("")}},getValText:function(b){var g=b.select2("val"),c=b.data("results"),a=b.txt(),e=!!b.attr("multiple"),d,h=b.attr("id");if(g||g===0){if(!e){g=[g];if(a||a===0){a=[a]}}if(a===0||(a&&g.length===a.length)){return[g,a]}d=b.data("userGetValText");if(d){a=d(b,g,e);if(a||a===0){return[g,a]}}if(c){a=[];$(g).each(function(f){var j,k=this;for(j in c){if(c[j].id==k){g[f]=c[j].id;a.push(c[j].text)}}});if(a||a===0){return[g,a]}}}return null},onInit:function(b,f){b=$(b);var d=b.attr("id"),a=null,c=b.select2("val");if(!c&&c!==0){c=b.data("initVal")}if(c||c===0){a=django_select2.getValText(b);if(a&&a[0]){a=django_select2.prepareValText(a[0],a[1],!!b.attr("multiple"))}}if(!a){b.val(null)}f(a);django_select2.updateText(b)},createSearchChoice:function(a,b){if(!b||$(b).filter(function(){return this.text.localeCompare(a)===0}).length===0){return{id:a,text:a}}},onMultipleHiddenChange:function(){var b=$(this),d=b.data("valContainer"),a=b.data("name"),c=b.val();d.empty();if(c){c=c.split(django_select2.MULTISEPARATOR);$(c).each(function(){var e=$('<input type="hidden">').appendTo(d);e.attr("name",a);e.val(this)})}},initMultipleHidden:function(a){var b;a.data("name",a.attr("name"));a.attr("name","");b=$("<div>").insertAfter(a).css({display:"none"});a.data("valContainer",b);a.change(django_select2.onMultipleHiddenChange);if(a.val()){a.change()}},convertArrToStr:function(a){return a.join(django_select2.MULTISEPARATOR)},runInContextHelper:function(a,b){return function(){var c=Array.prototype.slice.call(arguments,0);return a.apply($("#"+b).get(0),c)}},logErr:function(){if(console&&console.error){args=Array.prototype.slice.call(arguments);console.error.apply(console,args)}}};(function(b){if(b){for(var a in django_select2){var c=django_select2[a];if(typeof(c)=="function"){django_select2[a]=(function(d,e){return function(){console.log("Function "+d+" called for object: ",this);return e.apply(this,arguments)}}(a,c))}}}}(false));(function(a){a.fn.txt=function(b){if(typeof(b)!=="undefined"){if(b){if(b instanceof Array){if(this.attr("multiple")){b=django_select2.convertArrToStr(b)}else{b=b[0]}}this.attr("txt",b)}else{this.removeAttr("txt")}return this}else{b=this.attr("txt");if(this.attr("multiple")){if(b){b=b.split(django_select2.MULTISEPARATOR)}else{b=[]}}return b}}})(jQuery)};
|
||||
|
|
@ -504,7 +504,7 @@ class HeavySelect2MultipleWidget(HeavySelect2Mixin, MultipleSelect2HiddenInput):
|
|||
|
||||
Following Select2 options from :py:attr:`.Select2Mixin.options` are added or set:-
|
||||
|
||||
* multiple: ``False``
|
||||
* multiple: ``True``
|
||||
* separator: ``JSVar('django_select2.MULTISEPARATOR')``
|
||||
|
||||
"""
|
||||
|
|
@ -539,6 +539,34 @@ class HeavySelect2MultipleWidget(HeavySelect2Mixin, MultipleSelect2HiddenInput):
|
|||
if texts:
|
||||
return u"$('#%s').txt(%s);" % (id_, texts)
|
||||
|
||||
class HeavySelect2TagWidget(HeavySelect2MultipleWidget):
|
||||
"""
|
||||
Heavy widget with tagging support. Based on :py:class:`HeavySelect2MultipleWidget`,
|
||||
unlike other widgets this allows users to create new options (tags).
|
||||
|
||||
Following Select2 options from :py:attr:`.Select2Mixin.options` are removed:-
|
||||
|
||||
* allowClear
|
||||
* minimumResultsForSearch
|
||||
* closeOnSelect
|
||||
|
||||
Following Select2 options from :py:attr:`.Select2Mixin.options` are added or set:-
|
||||
|
||||
* multiple: ``True``
|
||||
* separator: ``JSVar('django_select2.MULTISEPARATOR')``
|
||||
* tags: ``True``
|
||||
* tokenSeparators: ``,`` and `` ``
|
||||
* createSearchChoice: ``JSFunctionInContext('django_select2.createSearchChoice')``
|
||||
* minimumInputLength: ``1``
|
||||
|
||||
"""
|
||||
def init_options(self):
|
||||
super(HeavySelect2TagWidget, self).init_options()
|
||||
self.options.pop('closeOnSelect', None)
|
||||
self.options['minimumInputLength'] = 1
|
||||
self.options['tags'] = True
|
||||
self.options['tokenSeparators'] = [",", " "]
|
||||
self.options['createSearchChoice'] = JSFunctionInContext('django_select2.createSearchChoice')
|
||||
|
||||
### Auto Heavy widgets ###
|
||||
|
||||
|
|
@ -571,3 +599,7 @@ class AutoHeavySelect2Widget(AutoHeavySelect2Mixin, HeavySelect2Widget):
|
|||
class AutoHeavySelect2MultipleWidget(AutoHeavySelect2Mixin, HeavySelect2MultipleWidget):
|
||||
"Auto version of :py:class:`.HeavySelect2MultipleWidget`"
|
||||
pass
|
||||
|
||||
class AutoHeavySelect2TagWidget(AutoHeavySelect2Mixin, HeavySelect2TagWidget):
|
||||
"Auto version of :py:class:`.HeavySelect2TagWidget`"
|
||||
pass
|
||||
|
|
|
|||
BIN
testapp/test.db
BIN
testapp/test.db
Binary file not shown.
|
|
@ -10,5 +10,6 @@
|
|||
<li><a href="{% url 'test_multi_values_model_field' %}">Test multi selection model fields</a></li>
|
||||
<li><a href="{% url 'test_mixed_form' %}">Test mixed form. All fields' search must return their own results, not other fields'.</a></li>
|
||||
<li><a href="{% url 'test_init_values' %}">Test that initial values are honored in unbound form</a></li>
|
||||
<li><a href="{% url 'test_list_questions' %}">Test tagging support</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@
|
|||
</head>
|
||||
<body>
|
||||
<h2>{{title}}</h2>
|
||||
{% if create_new_href != '' %}
|
||||
<a href="{% url create_new_href %}">Create New</a>
|
||||
<br/>
|
||||
{% endif %}
|
||||
<ul>
|
||||
{% for e in object_list %}
|
||||
<li><a href="{% url href e.id %}">{{ e }}</a></li>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from django import forms
|
|||
|
||||
from django_select2 import *
|
||||
|
||||
from .models import Employee, Dept, ClassRoom, Lab, Word, School
|
||||
from .models import Employee, Dept, ClassRoom, Lab, Word, School, Tag, Question
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
|
@ -27,6 +27,12 @@ class WordChoices(AutoModelSelect2Field):
|
|||
queryset = Word.objects
|
||||
search_fields = ['word__icontains', ]
|
||||
|
||||
class TagField(AutoModelSelect2TagField):
|
||||
queryset = Tag.objects
|
||||
search_fields = ['tag__icontains', ]
|
||||
def get_model_field_values(self, value):
|
||||
return {'tag': value}
|
||||
|
||||
class SelfChoices(AutoSelect2Field):
|
||||
def get_val_txt(self, value):
|
||||
if not hasattr(self, 'res_map'):
|
||||
|
|
@ -147,3 +153,11 @@ class InitialValueForm(forms.Form):
|
|||
heavySelect2ChoiceWithQuotes = AutoSelect2Field(initial=2,
|
||||
choices=((1, "'Single-Quote'"), (2, "\"Double-Quotes\""), (3, "\"Mixed-Quotes'"), ))
|
||||
|
||||
class QuestionForm(forms.ModelForm):
|
||||
question = forms.CharField()
|
||||
description = forms.CharField(widget=forms.Textarea)
|
||||
tags = TagField()
|
||||
|
||||
class Meta:
|
||||
model = Question
|
||||
|
||||
|
|
|
|||
|
|
@ -37,5 +37,18 @@ class Word(models.Model):
|
|||
|
||||
|
||||
class School(models.Model):
|
||||
|
||||
classes = models.ManyToManyField(ClassRoom)
|
||||
|
||||
class Tag(models.Model):
|
||||
tag = models.CharField(max_length=10, unique=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.tag)
|
||||
|
||||
class Question(models.Model):
|
||||
question = models.CharField(max_length=200)
|
||||
description = models.CharField(max_length=800)
|
||||
tags = models.ManyToManyField(Tag)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.question)
|
||||
|
|
|
|||
|
|
@ -10,4 +10,8 @@ urlpatterns = patterns('testapp.testmain.views',
|
|||
url(r'mixed/form/$', 'test_mixed_form', name='test_mixed_form'),
|
||||
|
||||
url(r'initial/form/$', 'test_init_values', name='test_init_values'),
|
||||
|
||||
url(r'question/$', 'test_list_questions', name='test_list_questions'),
|
||||
url(r'question/form/([0-9]+)/$', 'test_tagging', name='test_tagging'),
|
||||
url(r'question/form/$', 'test_tagging_new', name='test_tagging_new'),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,15 +2,16 @@ from django.core.urlresolvers import reverse
|
|||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
|
||||
from .forms import EmployeeForm, DeptForm, MixedForm, InitialValueForm
|
||||
from .models import Employee, Dept
|
||||
from .forms import EmployeeForm, DeptForm, MixedForm, InitialValueForm, QuestionForm
|
||||
from .models import Employee, Dept, Question
|
||||
|
||||
def test_single_value_model_field(request):
|
||||
return render(request, 'list.html', {
|
||||
'title': 'Employees',
|
||||
'href': 'test_single_value_model_field1',
|
||||
'object_list': Employee.objects.all()
|
||||
})
|
||||
'object_list': Employee.objects.all(),
|
||||
'create_new_href': ''
|
||||
})
|
||||
|
||||
def test_single_value_model_field1(request, id):
|
||||
emp = get_object_or_404(Employee, pk=id)
|
||||
|
|
@ -28,7 +29,8 @@ def test_multi_values_model_field(request):
|
|||
return render(request, 'list.html', {
|
||||
'title': 'Departments',
|
||||
'href': 'test_multi_values_model_field1',
|
||||
'object_list': Dept.objects.all()
|
||||
'object_list': Dept.objects.all(),
|
||||
'create_new_href': ''
|
||||
})
|
||||
|
||||
def test_multi_values_model_field1(request, id):
|
||||
|
|
@ -53,4 +55,28 @@ def test_mixed_form(request):
|
|||
def test_init_values(request):
|
||||
return render(request, 'form.html', {'form': InitialValueForm()})
|
||||
|
||||
def test_list_questions(request):
|
||||
return render(request, 'list.html', {
|
||||
'title': 'Questions',
|
||||
'href': 'test_tagging',
|
||||
'object_list': Question.objects.all(),
|
||||
'create_new_href': 'test_tagging_new'
|
||||
})
|
||||
|
||||
def test_tagging_new(request):
|
||||
return test_tagging(request, None)
|
||||
|
||||
def test_tagging(request, id):
|
||||
if id is None:
|
||||
question = Question()
|
||||
else:
|
||||
question = get_object_or_404(Question, pk=id)
|
||||
if request.POST:
|
||||
form = QuestionForm(data=request.POST, instance=question)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return HttpResponseRedirect(reverse('home'))
|
||||
else:
|
||||
form = QuestionForm(instance=question)
|
||||
return render(request, 'form.html', {'form': form})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue