Reputation: 1365
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
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.
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.
Upvotes: 1
Views: 1779
Reputation: 6355
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.
Upvotes: 3
Reputation: 6368
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.
Upvotes: 1