django-sortedm2m icon indicating copy to clipboard operation
django-sortedm2m copied to clipboard

Drag'n'drop sorting with select2 autocomplete widget

Open amirouche opened this issue 3 years ago • 0 comments

We added the ability to sort using drag'n'drop and select2 autocomplete that is more user friendly that the current filtering approach. We would like to contribute that feature to this awesome project.

Here is the code we use at the moment:

from django.contrib.admin.widgets import AutocompleteSelectMultiple


class OrderedAutocomplete(AutocompleteSelectMultiple):

    def optgroups(self, name, value, attr=None):
        """Return selected options based on the ModelChoiceIterator."""
        # XXX: This is based on django.contrib.admin.widgets.AutocompleteMixin:
        default = (None, [], 0)
        groups = [default]
        has_selected = False
        # Use a list instead of a set to keep around the order returned
        # by SortedManyToManyField
        selected_choices = [
            str(v) for v in value
            if str(v) not in self.choices.field.empty_values
        ]
        if not self.is_required and not self.allow_multiple_selected:
            default[1].append(self.create_option(name, '', '', False, 0))
        choices = (
            (obj.pk, self.choices.field.label_from_instance(obj))
            for obj in self.choices.queryset.using(self.db).filter(pk__in=selected_choices)
        )
        choices = list(choices)
        # Sort choices according to what is returned by SortedManyToManyField
        choices.sort(key=lambda x: selected_choices.index(str(x[0])))
        for option_value, option_label in choices:
            selected = (
                str(option_value) in value and
                (has_selected is False or self.allow_multiple_selected)
            )
            has_selected |= selected
            index = len(default[1])
            subgroup = default[1]
            subgroup.append(self.create_option(name, option_value, option_label, selected_choices, index))
        return groups


class OrderedAutocompleteMixin:

    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
        using = kwargs.get("using")
        if db_field.name in self.ordered_autocomplete_fields:
            kwargs['widget'] = OrderedAutocomplete(
                db_field.remote_field,
                self.admin_site,
                using=using
            )
            if 'queryset' not in kwargs:
                queryset = self.get_field_queryset(using, db_field, request)
                if queryset is not None:
                    kwargs['queryset'] = queryset

            form_field = db_field.formfield(**kwargs)
            return form_field

        return super().formfield_for_manytomany(db_field, request, **kwargs)
         (function($) {
             $(document).ready(function() {
                 // Add drag'n'drop to django select2 autocomplete fields.
                 // https://github.com/select2/select2/issues/3004#issuecomment-485821449
                 let selects = $('select.admin-autocomplete')
                 selects.each(function(index, element) {
                     let select = $(element).select2();
                     let children = select.next().children().children().children();
                     children.sortable({
                         containment: 'parent',
                         stop: function (event, ui) {
                             ui.item.parent().children('[title]').each(function () {
                                 let title = $(this).attr('title');
                                 let original = $('option:contains(' + title + ')', select).first();
                                 original.detach();
                                 select.append(original)
                             });
                             select.change();
                         }
                     });
                 });
             });
         })(grp.jQuery);

ref: https://github.com/select2/select2/issues/3004#issuecomment-485821449

amirouche avatar Aug 28 '20 13:08 amirouche