Setup filter for ForeignKey in ModelForm

I have models:

from django.contrib.auth.models import User

class ProductToRecipe(models.Model):
    recipe = models.ForeignKey(Recipe)
    product = models.ForeignKey(Product)
    count = models.IntegerField()
class Product(models.Model):
    name = models.CharField()
    creator = models.ForeignKey(User)

Where ProductToRecipe model is part of modelformset_factory (call from views.py):

ProductRecipeFormSet = modelformset_factory(ProductToRecipe,
                                            form=ProductRecipeForm)

forms.py:

class ProductRecipeForm(ModelForm):
    class Meta():
        model = ProductToRecipe
        fields = ['product', 'count']

How can I add filter in ProductRecipeForm so that only products that have product.creator==request.user are added to the product=ForeignKey(Product) field?

You can pass a custom queryset as a parameter to the formset class (not the factory call), See changing the queryset in the docs.

This idea sounds interesting, but how can I setup queryset, which will have filter on it’s ForeignKey Field? As I understand, I should do in views something like:

recipe = get_object_or_404(Recipe, pk=recipe_id, author=request.user)
products = ProductToRecipe.objects.filter(recipe=recipe,
                                          product.creator=request.user)
ProductRecipeFormSet = modelformset_factory(ProductToRecipe,
                                            form=ProductRecipeForm)
product_formset = ProductRecipeFormSet(queryset=products)

But in Django I can’t setup field of ForeignKey in way product.creator=request.user.

I thought that I can do something like

recipe = get_object_or_404(Recipe, pk=recipe_id, author=request.user)
products = ProductToRecipe.objects.filter(recipe=recipe)
ProductRecipeFormSet = modelformset_factory(ProductToRecipe,
                                            form=ProductRecipeForm)
user_products = Product.objects.filter(creator=request.user)
product_formset = ProductRecipeFormSet(queryset=products,
                                       user_products=user_products)

And than in forms.py write:

class ProductRecipeForm(ModelForm):
    class Meta():
        model = ProductToRecipe
        fields = ['product', 'count']
    def __init__(self, user_products, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["product"].queryset = user_products

But I don’t know how properly pass user_products parameter

I found on StackOverflow python - How do I filter ForeignKey choices in a Django ModelForm? - Stack Overflow solution in the same problem but with formset_factory. However, I didn’t understand how to do the same in modelformsets

Ok, I think I may have been confused with your original question.

Let me try to rephrase this for clarity.

You are creating a Formset of ProductToRecipe.

This means you are going to be rendering multiple instances of a form to create / update the ProductToRecipe models.

In each of those model instances there is a field named product. It (by default) is going to render as a ModelSelect field, allowing the choice of one instance of Product.

The question here is that you’re trying to limit the list of Product for the ModelSelect field.

The list of Product to be available for product is going to be the same across all instances of the ProductToRecipe form.

Do I finally have this right? (Would you say this is an accurate phrasing?)

1 Like

Yes, that’s what I want to do. Sorry for bad phrasing of original question

Nope, no worries - I just wanted to ensure I understood the situation.

For that situation you have the form_kwargs parameter that you can use in the model formset construction call (in your situation above, product_formset = ProductRecipeFormSet(...)). Anything you supply in that dict gets passed through as parameters to the form __init__ method.

See Passing custom parameters to formset forms.

Since you’re wanting to pass the same information to all the instances, you can ignore the references to get_form_kwargs.

1 Like

This is what I really needed. It turned out to be much easier than I thought. Thank you so much for your help!