简体   繁体   中英

How to filter the queryset of a django modelformset manytomany memeber

I can't use the proper code but I found the pizza/toppings question and it's close so I'm modifying it to ask my question. Django ModelForm for Many-to-Many fields

We have Pizza and Topping which are great. And let's say that we operate a chain of stores and not all stores have all ingredients. Which means we need a Store class that has a location and a manytomany for toppings. And then let's roll an Order which references a store and has a manytomany on Pizza since you often order more than one.

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

Please don't beat me up too much if those don't actually run. Like I said I can't post the real code here for various reasons, partially because it's just massive.

For the sake of this example assume that the person has already located the restaraunt closest to their home and started the ordering process.

I have tried passing in a widget to the modelformset_factory with the proper name and options but that didn't propagate.

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

I also tried extending the BaseModelFormSet to pass in extra data and try and get that into the PizzaForm but I got stuck.

Basically I can tell that I need to somehow propagate information from the view to the formset to the form so that the queryset on the form can be initialized properly. I just can't figure out precisely how to do that.

So this is the closest I've found to an answer but I can't seem to figure out how to make that work for sure: Django filter ModelFormSet field choices... different from limiting the Formset's queryset

In response to people who are going to badger me for posting a non-working example

The point is that I'm not trying to ask for someone to post the exact 10 lines of code that'll completely solve my problem, but rather that I'm showing where my knowledge is lacking. I know that I generate a PizzaFormSet and that eventually somewhere in the bowels of that code it uses the PizzaForm I specify. But I don't have any idea how to successfully pass information from the PizzaFormSet to the PizzaForm.

Basically I'm willing to give away a bounty for a suggestion as to what part of this puzzle I'm missing.

Where the problem is

I have defined a form in forms.py (PizzaForm) which needs to get a situationally-dependent queryset for the Topping. The view order_pizzas determines which restaurant will make and deliver the pizzas and the toppings available at that restaurant might be different than at other restaurants.

I don't know how to propagate that information from the view to the form, normally you just subclass the form and add some extra init kwargs to do whatever you want.

But in this case I'm using a formset rather than a single form. That means I have to find (or make) some channel to pass the restaurant information and/or the specific queryset through from the view to the formset to the form. I think that's my main point of confusion and/or ignorance.

Here's a solution. There may be a better one, but this should work. Loop through all the forms in the formset, and change the choices variable on the toppings field. Like this:

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

You could also override the BaseModelFormset and override the _contruct_forms method, passing in the restaraunt object to the form's __init__ , then change the topping's choices there. But I think the above solution is the quickest and simplest. It just introduces an extra loop.

You can have a factory that makes PizzaForms:

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

        class Meta:
            model = Pizza

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

And use it in your view:

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

Perhaps a constraint on your models to make sure you can't create impossible orders would be nice. If you don't use the pizzaform_factory (ie. using the admin, a shell script or an API call) you can still end up with unfulfillable orders.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM