Search code examples
pythondjangodjango-formsdjango-querysetinline-formset

Passing queryset to foreignkeyfield in django inlineform


I got a little problem which I thought must me quite common. Here's the problem described very generic:

class Ownable(models.Model):
    user = models.ForeignKey(django.contrib.auth.models.User)

    class Meta:
        abstract = True


class Bowl(Ownable):
    pass


class Pea(Ownable):
    bowl = models.ForeignKey(bowl)

Relationships are: User [1:n] Bowl, User [1:n] Pea Bowl [1:n] Pea

Now when I want to create a new Pea I also need to assign it to a Bowl like so:

def create_new_pea(request):
    PeaFrom = inlineformset_factory(django.contrib.auth.models.User, Pea)
    return render(request, 'app/pea/create.html', {'formset': PeaFrom()})

How in this process would I be able to pass a QuerySet to the bowl-field as I what the user to be only able to put bean inside his own bowls.

I'd be very glad for suggestions. I tried creating a custom form for the formset-factory, but I need the request instance to know the current user.


Solution

  • One simple way is to do it after instantiating the formset.

    def create_new_pea(request):
        PeaFormset = inlineformset_factory(django.contrib.auth.models.User, Pea)
        formset = PeaFormset(instance=request.user)
        for form in formset:
            form.fields['bowl'].queryset = request.user.bowl_set.all()
        return render(request, 'app/pea/create.html', {'formset': formset}
    

    I think it's possible to build this behavior into a custom Formset class, overriding the _construct_forms method:

    class UserLimitedFormset(BaseInlineFormset):
        def _construct_forms(self):
            super(UserLimitedFormset, self)._construct_forms()
            for form in self:
                form.fields['bowl'].queryset = self.instance.bowl_set.all()
    
    PeaFormset = inlineformset_factory(django.contrib.auth.models.User, Pea, formset=UserLimitedFormset)
    

    To use the callback, I think you'd need a closure or a functools.partial to record the user before creating the formset. Possibly this, although it's untested and I'm rusty on closures:

    def create_new_pea(request):
        user = request.user
        def set_queryset(f, **kwargs):
            formfield = f.formfield(**kwargs)
            if f.name == 'bowl':
                formfield.queryset = user.bowl_set.all()
            return formfield
        PeaFormset = inlineformset_factory(django.contrib.auth.models.User, Pea, formfield_callback=set_queryset)