django-sortedm2m
django-sortedm2m copied to clipboard
Drag'n'drop sorting with select2 autocomplete widget
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