Question

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.

Was it helpful?

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)

OTHER TIPS

Doesn't need to be that complicated - just add a kwarg 'person' to the form initialise. I'm calling this new form PeaForm, as what you have listed as PeaForm is actually of type Formset.

class PeaForm(ModelForm):

    def __init__(self, *args, **kwargs):

        person = kwargs.pop('person')
        super(PeaForm, self).__init__(*args, **kwargs)
        qs = Bowl.objects.filter(user=person)
        self.fields['bowl'].queryset = qs


    class Meta():
        model = Pea

As you are using it in a factory, you just have to curry the method when you make the form:

from django.utils.functional import curry

person = request.user
PeaFormset = inlineformset_factory(django.contrib.auth.models.User, Pea, form=PeaForm)
PeaFormSet.form = staticmethod(curry(PeaForm, person=person))

Now only bowls belonging to the user will be in the QS of the PeaForm.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top