Sławek Ehlert avatar Sławek Ehlert committed c263ebd

some weird attempt regarding multipleselect support

Comments (0)

Files changed (7)

example/core/forms.py

 import selectable.forms as selectable
 
 from example.core.lookups import FruitLookup, CityLookup, CityChainedLookup, FancyFruitLookup, StateLookup
-from example.core.models import Farm, ReferencesTest  # , City
+from example.core.models import Farm, ReferencesTest, MultipleTest  # , City
 from selectable_select2.widgets import AutoCompleteSelect2Widget as Select2Widget
+from selectable_select2.widgets import AutoCompleteMultipleSelect2Widget as Select2MultipleWidget
 # from selectable_select2.forms import Select2DependencyFormMixin
 from selectable_select2.forms import Select2DependencyForm
 from django.contrib.localflavor.us.us_states import STATE_CHOICES
      )
 
 
+class MultipleTestForm(forms.ModelForm):
+
+    class Meta:
+        model = MultipleTest
+        widgets = {
+            #'something' : Select2Widget(lookup_class=FancyFruitLookup, placeholder="select multiple fruits"),
+            'multiple' : Select2MultipleWidget(lookup_class=FancyFruitLookup, placeholder="select multiple fruits"),
+        }
+
+
 class FarmForm(forms.ModelForm):
 
     class Meta(object):

example/core/models.py

 
     @models.permalink
     def get_absolute_url(self):
-        return ('example-detail', [str(self.id)])
+        return ('example-detail', [str(self.pk)])
 
     def __unicode__(self):
         ret = u"{0}: {1} - {2} - {3}".format(str(self.pk), self.city, self.fruit, self.fruit2)
         if self.farm:
             ret += u" - {0}".format(self.farm)
         return ret
+
+
+class MultipleTest(models.Model):
+    something = models.CharField(max_length=100, null=True, blank=True)
+    multiple  = models.ManyToManyField(Fruit)
+
+    def __unicode__(self):
+        ret = u"{0}:".format(self.pk)
+        if self.something:
+            ret += " {0}".format(self.something)
+        return ret
+
+    @models.permalink
+    def get_absolute_url(self):
+        return ('example-multiple', [str(self.pk)])

example/core/templates/base.html

     </style>
     <link rel="stylesheet" href="{{ STATIC_URL }}css/bootstrap.min.css">
     <link rel="stylesheet" href="{{ STATIC_URL }}css/style.css">
-    {{ form.media.css }}
+    <link rel="stylesheet" href="{{ STATIC_URL }}selectable_select2/css/select2.css">
     {% block extra-css %}{% endblock %}
 </head>
 <body>
                         <li class="active"><a href="/advanced/">Chained</a></li>
                         <li class="active"><a href="/advanced2/">Chained2</a></li>
                         {# <li class="active"><a href="/formset/">Formset</a></li> #}
+                        <li class="divider-vertical"></li>
+                        <li class="active"><a href="/listm/">List (multiple)</a></li>
+                        <li class="active"><a href="/multiple/">Add (multiple)</a></li>
+
                     </ul>
                 </div><!--/.nav-collapse -->
             </div>
     </div>
     {% endblock %}
     </div>
-    {% include_jquery_libs %}
-    {{ form.media.js }}
+        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
+        <script type="text/javascript" src="{{ STATIC_URL }}selectable_select2/js/select2.min.js"></script>
+        <script type="text/javascript" src="{{ STATIC_URL }}selectable_select2/js/jquery.dj.selectable.select2.js"></script>
     {% block extra-js %}{% endblock %}
 </body>
 </html>

example/core/urls.py

 
 
 urlpatterns = patterns('example.core.views',
-    url(r'^formset/$',   'formset',  name='example-formset'),
-    url(r'^advanced/$',  'advanced', name='example-advanced'),
-    url(r'^advanced2/$', 'advanced', {'form_type' : 2},  name='example-advanced'),
-    url(r'^list/$',      'list',     name='example-list'),
-    url(r'^edit/(?P<pk>\d+)$',     'detail',     name='example-detail'),
-    url(r'^$',           'add',      name='example-index'),
+    url(r'^formset/$',              'formset',          name='example-formset'),
+    url(r'^advanced/$',             'advanced',         name='example-advanced'),
+    url(r'^advanced2/$',            'advanced', {'form_type' : 2},  name='example-advanced'),
+    url(r'^listm/$',                'multiple_list',    name='example-multiple-list'),
+    url(r'^list/$',                 'list',             name='example-list'),
+    url(r'^editm/(?P<pk>\d+)/$',    'multiple_edit',    name='example-multiple'),
+    url(r'^edit/(?P<pk>\d+)/$',     'detail',           name='example-detail'),
+    url(r'^multiple/$',             'multiple',         name='example-multiple-add'),
+    url(r'^$',                      'add',              name='example-index'),
 )

example/core/views.py

 
 from django.http import HttpResponseRedirect
 from django.core.urlresolvers import reverse
-from example.core.forms import ChainedForm, FarmFormset, ReferencesTestForm, ChainedForm2
-from example.core.models import ReferencesTest
+from example.core.forms import ChainedForm, FarmFormset, ReferencesTestForm, ChainedForm2, MultipleTestForm
+from example.core.models import ReferencesTest, MultipleTest
 
 
 def add(request):
             formset = FarmFormset()
 
     return render_to_response('formset.html', {'formset': formset}, context_instance=RequestContext(request))
+
+
+def multiple(request):
+
+    form = MultipleTestForm()
+    if request.method == 'POST':
+        print "RPD", request.raw_post_data
+        form = MultipleTestForm(request.POST)
+        if form.is_valid():
+            form.save()
+            return HttpResponseRedirect(reverse("example-multiple-list"))
+    return render_to_response('base.html', {'form': form}, context_instance=RequestContext(request))
+
+
+def multiple_edit(request, pk):
+
+    instance = MultipleTest.objects.get(pk=pk)
+    form = MultipleTestForm(instance=instance)
+    if request.method == 'POST':
+        print "RPD", request.raw_post_data
+        form = MultipleTestForm(request.POST, instance=instance)
+        if form.is_valid():
+            form.save()
+            return HttpResponseRedirect(reverse("example-multiple-list"))
+    return render_to_response('base.html', {'form': form}, context_instance=RequestContext(request))
+
+
+def multiple_list(request):
+    rlist = MultipleTest.objects.all()
+    return render_to_response('list.html', {'object_list': rlist}, context_instance=RequestContext(request))

selectable_select2/static/selectable_select2/js/jquery.dj.selectable.select2.js

 
     var djsels2limit = 20;
 
-    var $selectitems = $("[data-selectable-type=select2]");
+    var $selectitems = $("[data-selectable-type^=select2]");
     $selectitems.each( function(index) {
         var loading_more = ""; // a string for indicating of loading more results
         var $selectitem = $(this);
+        var selectable_type = $selectitem.data('selectableType');
+        var is_multiple = (selectable_type === "select2_multiple") ? true : false;
         var djs2url = $selectitem.data('selectableUrl');
         var clearonparentchange = $selectitem.data('djsels2Clearonparentchange');
 
                 /* attach a "clear child" action to parents */
                 if (clearonparentchange) {
                   el.change(function(evo) {
-                    var val = false;
-                    if (!val) {
                       $selectitem.data('select2').clear();
                       $selectitem.trigger('change');
-                    }
                   });
                 }
               }
           return obj;
         };
 
-        $selectitem.select2({
+        var single_config_object = {
             // minimumInputLength : 1,
+            multiple         :  is_multiple,
             width            :  'resolve',
             minimumResultsForSearch: djsels2limit,
             allowClear       :  true,
             initSelection    :  function (element, callback) {
                                   /** TODO: adjust this to work with multiple selection */
                                     var data = {};
+                                    console.log("element", element);
                                     var el_val = element.val();
-                                    var initial_selection = element.data('djsels2Initialselection');
+                                    console.log("elval", el_val, typeof el_val);
+                                    var initial_selection = element.data('djsels2InitialselectionNo0');
+                                    console.log("initsel", initial_selection);
                                     if (initial_selection) {
                                       data = {
-                                        id : el_val,
+                                        id : is_multiple ? el_val[0] : el_val,
                                         value : initial_selection
                                       };
+                                      console.log('data', data);
                                     }
-
+                                    if (is_multiple) {
+                                      data = [data];
+                                      var _is_more = true;
+                                      var _index = 1;
+                                      while (_is_more) {
+                                        var another_initial_selection = element.data('djsels2InitialselectionNo' + _index);
+                                        if (typeof another_initial_selection === "undefined") {
+                                          _is_more = false;
+                                        } else {
+                                          var _to_push = { id: el_val[_index], value: another_initial_selection } ;
+                                          console.log('topush', _to_push);
+                                          data.push(_to_push);
+                                          _index++;
+                                        }
+                                      }
+                                    }
                                     callback(data);
                                 },
             formatResult     :  function (state) {
               return loading_more;
             },
             escapeMarkup: function(markup) { return markup; }
-        });
+        };
+
+        $selectitem.select2(single_config_object);
      });
   });
 })(jQuery);

selectable_select2/widgets.py

-from selectable.forms.widgets import AutoCompleteWidget
+#from selectable.forms.widgets import AutoCompleteWidget
 from django.conf import settings
-
+from django import forms
 from django.utils import simplejson as json
+from django.utils.http import urlencode
+from collections import Iterable
 
 
 __all__ = ('AutoCompleteSelect2Widget',)
 MEDIA_PREFIX = u'{0}selectable_select2/'.format(STATIC_URL or MEDIA_URL)
 
 # these are the kwargs that u can pass when instantiating the widget
-TRANSFERABLE_ATTRS = ('placeholder', 'initialselection', 'parent_ids', 'clearonparentchange', 'parent_namemap')
+TRANSFERABLE_ATTRS = ('placeholder', 'parent_ids', 'clearonparentchange', 'parent_namemap')
 
 # a subset of TRANSFERABLE_ATTRS that should be serialized on "data-djsels2-*" attrs
 SERIALIZABLE_ATTRS = ('clearonparentchange',)
 # a subset of TRANSFERABLE_ATTRS that should be also on "data-*" attrs
 EXPLICIT_TRANSFERABLE_ATTRS = ('placeholder',)
 
+ARRAY_TRANSFERABLE_ATTRS = ('initialselection',)
+
+ALL_TRANSFERABLE_ATTRS = TRANSFERABLE_ATTRS + ARRAY_TRANSFERABLE_ATTRS
+
 
 class SelectableSelect2MediaMixin(object):
 
         )
 
 
-class Select2BaseWidget(SelectableSelect2MediaMixin, AutoCompleteWidget):
+class AutoCompleteWidget(SelectableSelect2MediaMixin):
 
-    def __init__(self, *args, **kwargs):
-        for attr in TRANSFERABLE_ATTRS:
+    selectable_type = "select2"
+
+    def __init__(self, lookup_class, *args, **kwargs):
+        self.lookup_class = lookup_class
+        self.allow_new = kwargs.pop('allow_new', False)
+        self.qs = kwargs.pop('query_params', {})
+        self.limit = kwargs.pop('limit', None)
+
+        for attr in ALL_TRANSFERABLE_ATTRS:
             setattr(self, attr, kwargs.pop(attr, ''))
 
-        super(Select2BaseWidget, self).__init__(*args, **kwargs)
+        super(AutoCompleteWidget, self).__init__(*args, **kwargs)
+
+    def update_query_parameters(self, qs_dict):
+        self.qs.update(qs_dict)
 
     def build_attrs(self, extra_attrs=None, **kwargs):
-        attrs = super(Select2BaseWidget, self).build_attrs(extra_attrs, **kwargs)
+        attrs = super(AutoCompleteWidget, self).build_attrs(extra_attrs, **kwargs)
+        url = self.lookup_class.url()
+        if self.limit and 'limit' not in self.qs:
+            self.qs['limit'] = self.limit
+        if self.qs:
+            url = '%s?%s' % (url, urlencode(self.qs))
+        attrs[u'data-selectable-url'] = url
+        attrs[u'data-selectable-type'] = self.selectable_type
+        attrs[u'data-selectable-allow-new'] = str(self.allow_new).lower()
+
+        new_attrs = self.custom_build_attrs()
+        attrs.update(new_attrs)
+        return attrs
+
+    def custom_build_attrs(self):
+        attrs = {}
 
         for real_attr in TRANSFERABLE_ATTRS:
             attr = real_attr.replace('_', '-')
             if real_attr in EXPLICIT_TRANSFERABLE_ATTRS:
                 attrs[u'data-' + attr] = value
 
-        attrs[u'data-selectable-type'] = 'select2'
+        for real_attr in ARRAY_TRANSFERABLE_ATTRS:
+            attr = real_attr.replace('_', '-')
+            value_list = getattr(self, real_attr)
+
+            for index, value in enumerate(value_list):
+                attrs[u'data-djsels2-' + attr + "-no" + str(index)] = value
+
+        attrs[u'data-selectable-type'] = self.selectable_type
 
         return attrs
 
+
+class Select2BaseWidget(AutoCompleteWidget):
+
     def render(self, name, value, attrs=None):
         # when there is a value and no initialselection was passed to the widget
         if value is not None and (self.initialselection is None or self.initialselection == ''):
             lookup = self.lookup_class()
-            item = lookup.get_item(value)
-            if item is not None:
-                initialselection = lookup.get_item_value(item)
-                self.initialselection = initialselection
+            if not isinstance(value, Iterable):
+                new_value = [value]
+            else:
+                new_value = value
+            selections = []
+            for val in new_value:
+                item = lookup.get_item(val)
+                if item is not None:
+                    initialselection = lookup.get_item_value(item)
+                    selections.append(initialselection)
+            self.initialselection = selections
+
         return super(Select2BaseWidget, self).render(name, value, attrs)
 
 
-class AutoCompleteSelect2Widget(Select2BaseWidget):
+class AutoCompleteSelect2Widget(Select2BaseWidget, forms.TextInput):
     pass
+
+
+#class AutoCompleteMultipleSelect2Widget(Select2BaseWidget, BaseAutoCompleteWidget, forms.MultipleHiddenInput):
+
+from django.utils.encoding import force_unicode
+from django.utils.html import escape
+from django.utils.safestring import mark_safe
+from django.forms.util import flatatt
+# from itertools import
+from django.utils.datastructures import MultiValueDict, MergeDict
+
+
+class SelectMultipleSelectedOnly(forms.TextInput):
+    allow_multiple_selected = True
+
+    def render_options(self, choices, selected_choices):
+        # Normalize to strings.
+        selected_choices = set(force_unicode(v) for v in selected_choices if v != ',')
+        # output = []
+
+        lookup = self.lookup_class()
+        options_values = []
+        options_labels = []
+
+        for _pk in selected_choices:
+            options_values.append(_pk)
+            item = lookup.get_item(_pk)
+            if item is not None:
+                selection = lookup.get_item_value(item)
+            options_labels.append(selection)
+
+        self.initialselection = options_labels
+
+        # for option_value, option_label in options:
+
+        #     if isinstance(option_label, (list, tuple)):
+        #         output.append(u'<optgroup label="%s">' % escape(force_unicode(option_value)))
+        #         for option in option_label:
+        #             output.append(self.render_option(selected_choices, *option))
+        #         output.append(u'</optgroup>')
+        #     else:
+        #         output.append(self.render_option(selected_choices, option_value, option_label))
+        return u', '.join(options_values)
+
+    def render(self, name, value, attrs=None, choices=()):
+        print "V", value
+        if value is None:
+            value = ''
+        options = self.render_options(choices, value)
+        final_attrs = self.build_attrs(attrs, name=name, type='text')
+        if value != '':
+            # Only add the 'value' attribute if a value is non-empty.
+            final_attrs['value'] = force_unicode(options)
+        return mark_safe(u'<input%s />' % flatatt(final_attrs))
+
+    def value_from_datadict(self, data, files, name):
+        print "DATA", data, type(data), files, name
+        if isinstance(data, (MultiValueDict, MergeDict)):
+            return data.getlist(name)
+        return data.get(name, None)
+
+    # def _has_changed(self, initial, data):
+    #     if initial is None:
+    #         initial = []
+    #     if data is None:
+    #         data = []
+    #     if len(initial) != len(data):
+    #         return True
+    #     initial_set = set([force_unicode(value) for value in initial])
+    #     data_set = set([force_unicode(value) for value in data])
+    #     return data_set != initial_set
+
+# TODO: zrobic tak by to byly dwa widgety (normalny input i jakies multiple-hidden) i nasluchiwac na eventy zmiany i dodania
+
+class AutoCompleteMultipleSelect2Widget(AutoCompleteWidget, SelectMultipleSelectedOnly):
+# class AutoCompleteMultipleSelect2Widget(AutoCompleteWidget, forms.SelectMultiple):
+
+    selectable_type = "selectx2_multiple"
+
+    # def render(self, *args, **kwargs):
+    #     print args, kwargs, "render"
+    #     return super(AutoCompleteMultipleSelect2Widget, self).render(*args, **kwargs)
+
+    # def value_from_datadict(self, data, files, name):
+    #     print data, type(data), "data"
+    #     return super(AutoCompleteMultipleSelect2Widget, self).value_from_datadict(data, files, name)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.