如何过滤Django ModelFormSet的ManyToMany成员的查询集

1 投票
2 回答
1769 浏览
提问于 2025-04-17 22:12

我不能用正确的代码,但我找到了一个关于披萨和配料的问题,觉得很接近,所以我修改了一下来问我的问题。 Django的ModelForm用于多对多字段

我们有披萨和配料,这些都很好。假设我们经营一家连锁店,但并不是所有的店都有所有的配料。这就意味着我们需要一个商店类,它有一个位置,并且有一个多对多的配料关系。然后我们再来处理一个订单,它引用了一个商店,并且在披萨上也是多对多的,因为你通常会点不止一份披萨。

models.py

from django import models

class Topping(models.Model):
    name = models.TextField()

class Pizza(models.Model):
    size = models.TextField()
    toppings = models.ManyToManyField(Topping)

class Restaraunt(models.Model):
    name = models.TextField()
    toppings = models.ManyToManyField(Topping)


class Order(models.Model):
    customer = models.ForeignKey('User')
    location = models.ForeignKey(Restaraunt)
    pizzas = models.ManyToManyField(Pizza)

forms.py

from django import forms
from django.forms import ModelForm
from models import *

class PizzaForm(ModelForm):

    class Meta:
        model = Pizza

    toppings = forms.ModelMultipleChoiceField(
        widget=forms.CheckboxSelectMultiple(),
        queryset=Topping.objects.all()
    )

views.py

from django.shortcuts import render, redirect, get_object_or_404
from django.forms.models import modelformset_factory
from django.forms import CheckboxSelectMultiple

from models import *
from forms import *


def order_pizzas(request, order_id):
    order_id = int(order_id)
    order = get_object_or_404(Order, id=order_id)

    restaraunt = order.restaraunt

    PizzaFormSet = modelformset_factory(Pizza, form=PizzaForm, extra=1)

    pizza_form = PizzaFormSet(request.POST or None)

    return render(request, "place_order.html", {
        'restaraunt': restaraunt,
        'pizza_form': pizza_form,
    })

如果这些代码不能运行,请不要太过责备我。正如我所说的,我不能在这里发布真实的代码,原因有很多,部分原因是代码实在太庞大了。

为了这个例子,假设这个人已经找到了离家最近的餐厅,并开始了点餐的过程。

我尝试过将一个小部件传递给modelformset_factory,并使用正确的名称和选项,但没有成功。

widgets = {'toppings': 
    CheckboxSelectMultiple(
        choices=[t.id for t in restaraunt.toppings.all().order_by('-id')]
    )}

我还尝试扩展BaseModelFormSet,以传递额外的数据,并试图将其放入PizzaForm中,但我卡住了。

基本上,我知道我需要以某种方式将信息从视图传递到表单集,再到表单,这样表单上的查询集才能正确初始化。但我就是搞不清楚具体该怎么做。

所以这是我找到的最接近答案的内容,但我似乎无法确定如何让它工作: Django过滤ModelFormSet字段选择...与限制Formset的查询集不同

回应那些会因为我发布了一个不工作的例子而纠缠我的人

我的意思是,我并不是想让别人给我贴出十行代码来完全解决我的问题,而是想展示我知识的不足。我知道我生成了一个PizzaFormSet,并且最终在那段代码的某个地方使用了我指定的PizzaForm。但我不知道如何成功地将信息从PizzaFormSet传递到PizzaForm。

基本上,我愿意为一个建议付出奖励,告诉我在这个难题中我缺少了哪部分。

问题所在

我在forms.py中定义了一个表单(PizzaForm),它需要获取一个与情况相关的配料查询集。视图order_pizzas决定了哪个餐厅会制作和送达披萨,而那个餐厅可用的配料可能与其他餐厅不同。

我不知道如何将这些信息从视图传递到表单,通常你只需继承表单并添加一些额外的初始化参数来实现你想要的功能。

但在这种情况下,我使用的是表单集,而不是单个表单。这意味着我必须找到(或创建)某种渠道,将餐厅信息和/或特定的查询集从视图传递到表单集,再到表单。我想这就是我主要的困惑和无知之处。

2 个回答

1

你可以有一个工厂来制作PizzaForms:

def pizzaform_factory(restaurant):
    class PizzaForm(ModelForm):

        class Meta:
            model = Pizza

        toppings = forms.ModelMultipleChoiceField(
            widget=forms.CheckboxSelectMultiple(),
            queryset=restaurant.toppings.all()
        )
    return PizzaForm

然后在你的视图中使用它:

PizzaForm = pizzaform_factory(restaurant)
PizzaFormSet = modelformset_factory(Pizza, form=PizzaForm, extra=1)

为了确保你不能创建不可能的订单,给你的模型加个限制会比较好。如果你不使用 pizzaform_factory(比如使用管理员界面、脚本或者API调用),你仍然可能会遇到无法完成的订单。

3

这里有一个解决方案。可能还有更好的方法,但这个应该可以用。你可以遍历所有的表单,然后修改每个表单中“toppings”字段的选项。像这样:

pizza_form = PizzaFormSet(request.POST or None)

choices = [(t.pk, unicode(t)) for t in restaraunt.toppings.all().order_by('-id')]

for form in pizza_form:
    form.fields['toppings'].choices = choices

你也可以重写一下 BaseModelFormset,并修改 _construct_forms 这个方法,把餐厅对象传给表单的 __init__,然后在那儿更改配料的选项。不过我觉得上面的方案是最快和最简单的,只是多了一个循环。

撰写回答