【问题标题】:Avoid duplicate query with prefilled ModelMultipleChoiceField避免使用预填充的 ModelMultipleChoiceField 进行重复查询
【发布时间】:2016-07-11 23:34:48
【问题描述】:

我有一个Form 和一个ModelMultipleChoiceField 哪个queryset is generated at Form instanciation。我还希望最初检查三个首选选项。这是我的代码:

class DeliveryForm(forms.Form):

    content = forms.CharField(
        label=_("Contenu"), validators=[
            MinLengthValidator(20),
            MaxLengthValidator(5000),
        ], widget=Wysiwyg)

    sponsors = DeliverySponsorsField(
        label=_("Commanditaires"), validators=[
            MaxLengthValidator(3),
        ], error_messages={
            'max_length': _(
                "Vous ne pouvez pas sélectionner plus de 3 commanditaires."),
        }, queryset=None)

    def __init__(self, *args, **kwargs):
        quote_request = kwargs.pop('quote_request')
        suitable_sponsors = Sponsor.objects.all().suitable_for_quote_request(
            quote_request)

        initial = kwargs.pop('initial', None) or {}
        if 'content' not in initial:
            initial['content'] = quote_request.description
        if 'sponsors' not in initial:
            initial['sponsors'] = suitable_sponsors[:3]

        kwargs['initial'] = initial

        super().__init__(*args, **kwargs)

        self.fields['sponsors'].queryset = suitable_sponsors

DeliverySponsorsFieldModelMultipleChoiceField 的子类,它使我能够显示复杂的小部件:

class DeliverySponsorsRenderer(CheckboxFieldRenderer):
    outer_html = '<ul{id_attr} class="media-list">{content}</ul>'
    inner_html = '<li class="media">[...]</li>'

    def render(self):
        id_ = self.attrs.get('id')
        output = []
        for i, choice in enumerate(self.choices):
            choice_value, sponsor = choice
            widget = self.choice_input_class(self.name, self.value,
                                             self.attrs.copy(), choice, i)
            output.append({
                'x': sponsor.x, 'y': sponsor.y, 'z': sponsor.z, ...})

        content = format_html_join('\n', self.inner_html, output)
        # I have my own `format_html_join` function that handles keyword arguments
        return format_html(self.outer_html,
                           id_attr=format_html(' id="{}"', id_) if id_ else '',
                           content=content)

class DeliverySponsorsWidget(CheckboxSelectMultiple):
    renderer = DeliverySponsorsRenderer

class DeliverySponsorsField(ModelMultipleChoiceField):
    widget = DeliverySponsorsWidget

    def label_from_instance(self, obj):
        return obj

这就像一个魅力。

嗯,不完全是因为以下几行计算查询集:

initial['sponsors'] = suitable_sponsors[:3]

并且查询集也会在之后进行评估以生成可能的选择。虽然只有一个查询就足够了(因为suitable_sponsors[:3]suitable_sponsors 的子集。

我试图强制查询集评估:

# Replaced
suitable_sponsors = Sponsor.objects.all().suitable_for_quote_request(
        quote_request)
# with
suitable_sponsors = list(
    Sponsor.objects.all().suitable_for_quote_request(quote_request))

但是,ModelMultipleChoiceField 抱怨 queryset 不是 QuerySet。更准确地说,它抱怨 queryset.all 未定义:

File "/home/antoine/.venvs/aladom_v6/lib/python3.4/site-packages/django/forms/widgets.py" in get_renderer
  763.         choices = list(chain(self.choices, choices))

File "/home/antoine/.venvs/aladom_v6/lib/python3.4/site-packages/django/forms/models.py" in __iter__
  1105.         queryset = self.queryset.all()

Exception Type: AttributeError at /admin/quotation/requalification/141369/deliver/
Exception Value: 'list' object has no attribute 'all'

我可以“轻松”避免两次查询数据库,而我只能查询一次这种情况吗?

【问题讨论】:

  • 除非额外的查询导致性能问题,否则这可能是过早优化的情况。
  • @Alasdair 当然,这就是我要求“简单”解决方案的原因。关键不是要创建一个自负的机制来避免这种额外的查询。如果我可以轻松避免它,那很好。如果这需要一些异国情调的工作,我宁愿保留额外的查询。
  • @Antoine:“一英寸,一英里”,你必须对表现保持警惕:) 然后有时你必须让步
  • 没问题,我的问题主要是出于好奇。我知道我有更重要的事情要关心。 :)

标签: django django-forms django-queryset


【解决方案1】:

您可以向选择框提供值列表,而不是为您的字段提供查询集。

suitable_sponsors = Sponsor.objects.suitable_for_quote_request(quote_request)\
    .values_list('id', 'sponsor_name')

if 'sponsors' not in initial:
    initial['sponsors'] = [s.id for s in suitable_sponsors[:3]]

self.fields['sponsors'].choices = suitable_sponsors

您可能需要将 ModelMultipleChoiceField 更改为 MultipleChoiceField。

【讨论】:

  • 我实际上使用了ModelMultipleChoiceField 的自定义子类,它使我能够显示一个复杂的小部件,其中包含更多的数据,而不仅仅是赞助商名称。但是,这看起来是个好主意,我想我可以继承 MultipleChoiceField 而不是 ModelMultipleChoiceField
  • 不幸的是,看起来使用 MultipleChoiceFieldTypedMultipleChoiceField 会使我的自定义字段变得比需要的更复杂。
  • 你能试试 self.fields['sponsors'].widget.choices = proper_sponsors 吗?我不知道这是否是一个有效的语法。
  • TypeError: 'Sponsor' object is not iterable。 :P
  • 按预期,choices 应该是元组列表,而不是模型实例列表。
猜你喜欢
  • 2012-02-24
  • 2016-12-05
  • 2019-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-24
  • 2019-04-16
  • 1970-01-01
相关资源
最近更新 更多