【问题标题】:Django MultipleChoiceField does not preserve order of selected valuesDjango MultipleChoiceField 不保留所选值的顺序
【发布时间】:2012-05-04 23:55:06
【问题描述】:

我有一个 Django ModelForm,它通过一个模型公开了一个与多对多关系相对应的多项选择字段,该模型将选择的order(文档列表)作为一个额外的属性。在前端,该字段显示为两个类似于 admin 中的多选字段,一个用于列出可用的选项,另一个用于保存选定的元素。

可以通过正确选择元素来保存表单,但它们始终按原始选择顺序排列,而不是选择。浏览器以正确的顺序发送选择,但form.cleaned_data['documents'] 中的顺序始终是原始选择顺序中的顺序。

如何使 MultipleChoiceField 尊重所选元素的顺序?

谢谢。

【问题讨论】:

  • 如果多对多关系以正确的排序返回服务器(通过直通模型),那么为什么不使用类似于this question的方法来解决它
  • 感谢您的评论。我想在服务器端创建关系时按选择顺序对元素进行排序。但是cleaned_data['documents'] 并没有反映多选字段中的选择顺序。我可以使用request.GET.getlist('documents') 中的 id 重新订购,但感觉很老套。

标签: django django-forms cleaned-data


【解决方案1】:

没有简单的方法。您要么需要覆盖 MultipleChoiceFieldclean 方法,要么正如您在评论中提到的那样,使用 getlist 手动重新排序它们。这可能取决于您需要在代码中多久执行一次。

MultipleChoiceFieldclean 方法通过像这样通过IN 运算符过滤对象列表来创建您正在接收的QuerySet,因此顺序由数据库给出:

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,而是一个普通的列表。

【讨论】:

  • +1 加 sorted(qs, key=lambda x:value.index(x.pk)) 更短
【解决方案2】:

要在覆盖 clean 方法时返回一个有序的 QuerySet,你也可以这样做:

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',)
        )

【讨论】:

    【解决方案3】:

    我是通过一个小部件做到的。它的好处是,它可以在不同的语言中正确排序:

    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)
    

    【讨论】:

      【解决方案4】:

      我可以使用以下方式保持选择的顺序:

      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

      【讨论】:

        猜你喜欢
        • 2011-12-26
        • 1970-01-01
        • 2014-01-21
        • 2014-12-01
        • 2013-04-03
        • 1970-01-01
        • 2018-02-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多