Django MultipleChoiceField не сохраняет порядок выбранных значений
У меня есть Django ModelForm, которая предоставляет поле с множественным выбором, соответствующее отношению многие ко многим через модель, которая содержит order
выбора (список документов) в качестве дополнительного атрибута. Во внешнем интерфейсе поле отображается в виде двух полей множественного выбора, аналогичных полям admin, одно из которых содержит список доступных вариантов, а другое содержит выбранные элементы.
Форма может быть сохранена с правильным выбором элементов, но они всегда имеют порядок оригинального выбора, а не выбора. Браузер отправляет выбор в правильном порядке, но порядок в form.cleaned_data['documents']
всегда порядок в оригинальном порядке выбора.
Как я могу заставить MultipleChoiceField соблюдать порядок выбранных элементов?
Благодарю.
4 ответа
Простого пути нет. Вам либо нужно переопределить clean
метод MultipleChoiceField
или, как вы упомянули в своем комментарии, используйте getlist, чтобы переупорядочить их вручную. Вероятно, это зависит от того, как часто в вашем коде вам нужно это делать.
clean
метод MultipleChoiceField
создает QuerySet
что вы получаете, фильтруя список объектов через IN
Оператор подобен этому, поэтому порядок задается базой данных:
qs = self.queryset.filter(**{'%s__in' % key: value})
Вы можете унаследовать от ModelMultipleChoiceField
:
class OrderedModelMultipleChoiceField(ModelMultipleChoiceField):
def clean(self, value):
qs = super(OrderedModelMultipleChoiceField, self).clean(value)
return sorted(qs, lambda a,b: sorted(qs, key=lambda x:value.index(x.pk)))
Недостатком является то, что возвращаемое значение больше не является QuerySet
но обычный список.
Я сделал это через виджет. Преимущество в том, что он будет правильно сортироваться на разных языках:
class SortedSelectMultiple(SelectMultiple):
def render_options(self, selected_choices):
self.choices = sorted(self.choices)
self.choices.sort(key=lambda x: x[1])
return super(SortedSelectMultiple, self).render_options(selected_choices)
Чтобы вернуть упорядоченный QuerySet при переопределении метода clean, вы также можете сделать это:
class OrderedModelMultipleChoiceField(ModelMultipleChoiceField):
def clean(self, value):
qs = super(OrderedModelMultipleChoiceField, self).clean(value)
clauses = ' '.join(['WHEN id=%s THEN %s' % (pk, i) for i, pk in enumerate(value)])
return qs.filter(pk__in=value).extra(
select={'ordering': 'CASE %s END' % clauses},
order_by=('ordering',)
)
Я могу поддерживать порядок выбора следующим образом:
class OrderedModelMultipleChoiceField(models.ModelMultipleChoiceField):
def clean(self, value):
qs = super(OrderedModelMultipleChoiceField, self).clean(value)
preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(value)])
return qs.filter(pk__in=value).order_by(preserved)
Примечание: я использую Django 2.2.