避免使用预填充的ModelMultipleChoiceField进行重复查询

时间:2016-03-24 11:32:03

标签: django django-forms django-queryset

我有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'

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

1 个答案:

答案 0 :(得分: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。