Skip to content

Drag'n'drop sorting with select2 autocomplete widget #171

@amirouche

Description

@amirouche

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: select2/select2#3004 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions