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.

Другие вопросы по тегам