5454
5555from django import forms
5656from django .core import signing
57- from django .core .urlresolvers import reverse
5857from django .db .models import Q
5958from django .forms .models import ModelChoiceIterator
6059from django .utils .encoding import force_text
6362from .cache import cache
6463from .conf import settings
6564
65+ try :
66+ from django .urls import reverse
67+ except ImportError :
68+ from django .core .urlresolvers import reverse
69+
6670
6771class Select2Mixin (object ):
6872 """
@@ -73,9 +77,9 @@ class Select2Mixin(object):
7377 form media.
7478 """
7579
76- def build_attrs (self , extra_attrs = None , ** kwargs ):
80+ def build_attrs (self , * args , ** kwargs ):
7781 """Add select2 data attributes."""
78- attrs = super (Select2Mixin , self ).build_attrs (extra_attrs = extra_attrs , ** kwargs )
82+ attrs = super (Select2Mixin , self ).build_attrs (* args , ** kwargs )
7983 if self .is_required :
8084 attrs .setdefault ('data-allow-clear' , 'false' )
8185 else :
@@ -89,9 +93,15 @@ def build_attrs(self, extra_attrs=None, **kwargs):
8993 attrs ['class' ] = 'django-select2'
9094 return attrs
9195
96+ def optgroups (self , name , value , attrs = None ):
97+ """Add empty option for clearable selects."""
98+ if not self .is_required and not self .allow_multiple_selected :
99+ self .choices = list (chain ([('' , '' )], self .choices ))
100+ return super (Select2Mixin , self ).optgroups (name , value , attrs = attrs )
101+
92102 def render_options (self , * args , ** kwargs ):
93103 """Render options including an empty one, if the field is not required."""
94- output = '<option></option>' if not self .is_required and not self .allow_multiple_selected else ''
104+ output = '<option value="" ></option>' if not self .is_required and not self .allow_multiple_selected else ''
95105 output += super (Select2Mixin , self ).render_options (* args , ** kwargs )
96106 return output
97107
@@ -113,12 +123,12 @@ def _get_media(self):
113123class Select2TagMixin (object ):
114124 """Mixin to add select2 tag functionality."""
115125
116- def build_attrs (self , extra_attrs = None , ** kwargs ):
126+ def build_attrs (self , * args , ** kwargs ):
117127 """Add select2's tag attributes."""
118128 self .attrs .setdefault ('data-minimum-input-length' , 1 )
119129 self .attrs .setdefault ('data-tags' , 'true' )
120130 self .attrs .setdefault ('data-token-separators' , '[",", " "]' )
121- return super (Select2TagMixin , self ).build_attrs (extra_attrs , ** kwargs )
131+ return super (Select2TagMixin , self ).build_attrs (* args , ** kwargs )
122132
123133
124134class Select2Widget (Select2Mixin , forms .Select ):
@@ -175,7 +185,7 @@ def value_from_datadict(self, data, files, name):
175185class HeavySelect2Mixin (object ):
176186 """Mixin that adds select2's AJAX options and registers itself on Django's cache."""
177187
178- def __init__ (self , ** kwargs ):
188+ def __init__ (self , attrs = None , choices = (), ** kwargs ):
179189 """
180190 Return HeavySelect2Mixin.
181191
@@ -184,22 +194,27 @@ def __init__(self, **kwargs):
184194 data_url (str): URL
185195
186196 """
197+ self .choices = choices
198+ if attrs is not None :
199+ self .attrs = attrs .copy ()
200+ else :
201+ self .attrs = {}
202+
187203 self .data_view = kwargs .pop ('data_view' , None )
188204 self .data_url = kwargs .pop ('data_url' , None )
189205 if not (self .data_view or self .data_url ):
190206 raise ValueError ('You must ether specify "data_view" or "data_url".' )
191207 self .userGetValTextFuncName = kwargs .pop ('userGetValTextFuncName' , 'null' )
192- super (HeavySelect2Mixin , self ).__init__ (** kwargs )
193208
194209 def get_url (self ):
195210 """Return URL from instance or by reversing :attr:`.data_view`."""
196211 if self .data_url :
197212 return self .data_url
198213 return reverse (self .data_view )
199214
200- def build_attrs (self , extra_attrs = None , ** kwargs ):
215+ def build_attrs (self , * args , ** kwargs ):
201216 """Set select2's AJAX attributes."""
202- attrs = super (HeavySelect2Mixin , self ).build_attrs (extra_attrs = extra_attrs , ** kwargs )
217+ attrs = super (HeavySelect2Mixin , self ).build_attrs (* args , ** kwargs )
203218
204219 # encrypt instance Id
205220 self .widget_id = signing .dumps (id (self ))
@@ -247,7 +262,7 @@ def render_options(self, *args):
247262 choices = chain (self .choices , choices )
248263 else :
249264 choices = self .choices
250- output = ['<option></option>' if not self .is_required and not self .allow_multiple_selected else '' ]
265+ output = ['<option value="" ></option>' if not self .is_required and not self .allow_multiple_selected else '' ]
251266 selected_choices = {force_text (v ) for v in selected_choices }
252267 choices = [(k , v ) for k , v in choices if force_text (k ) in selected_choices ]
253268 for option_value , option_label in choices :
@@ -401,6 +416,36 @@ def get_search_fields(self):
401416 return self .search_fields
402417 raise NotImplementedError ('%s, must implement "search_fields".' % self .__class__ .__name__ )
403418
419+ def optgroups (self , name , value , attrs = None ):
420+ """Return only selected options and set QuerySet from `ModelChoicesIterator`."""
421+ default = (None , [], 0 )
422+ groups = [default ]
423+ has_selected = False
424+ selected_choices = {force_text (v ) for v in value }
425+ if not self .is_required and not self .allow_multiple_selected :
426+ default [1 ].append (self .create_option (name , '' , '' , False , 0 ))
427+ if not isinstance (self .choices , ModelChoiceIterator ):
428+ return super (ModelSelect2Mixin , self ).optgroups (name , value , attrs = attrs )
429+ selected_choices = {
430+ c for c in selected_choices
431+ if c not in self .choices .field .empty_values
432+ }
433+ choices = (
434+ (obj .pk , self .label_from_instance (obj ))
435+ for obj in self .choices .queryset .filter (pk__in = selected_choices )
436+ )
437+ for option_value , option_label in choices :
438+ selected = (
439+ force_text (option_value ) in value and
440+ (has_selected is False or self .allow_multiple_selected )
441+ )
442+ if selected is True and has_selected is False :
443+ has_selected = True
444+ index = len (default [1 ])
445+ subgroup = default [1 ]
446+ subgroup .append (self .create_option (name , option_value , option_label , selected_choices , index ))
447+ return groups
448+
404449 def render_options (self , * args ):
405450 """Render only selected options and set QuerySet from :class:`ModelChoiceIterator`."""
406451 try :
@@ -411,7 +456,7 @@ def render_options(self, *args):
411456 else :
412457 choices = self .choices
413458 selected_choices = {force_text (v ) for v in selected_choices }
414- output = ['<option></option>' if not self .is_required and not self .allow_multiple_selected else '' ]
459+ output = ['<option value="" ></option>' if not self .is_required and not self .allow_multiple_selected else '' ]
415460 if isinstance (self .choices , ModelChoiceIterator ):
416461 if self .queryset is None :
417462 self .queryset = self .choices .queryset
0 commit comments