4
votes

Im Using a ModelForm for creating a form from a model for using in various places on my site. The form has a Foreign Key field which needs to be filtered based on the user. I have successfully done this using:

class TestForm(ModelForm):
    def __init__(self,user,*args,**kwargs):
        super (TestForm,self ).__init__(*args,**kwargs) # populates the post
        self.fields['controller'].queryset = Controller.objects.filter(user=user)

    class Meta:
        model = Test
        exclude = ['customer']

And then in my view using: form = TestForm(user)

This is working fine for my forms outside of Django Admin, but my site requires that the model be editable inside Django Admin aswell. So I used this code for my ModelAdmin, based on the Django Docs

class TestAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        if request.user.is_superuser:
            kwargs['form'] = SuTestForm
        else:
            kwargs['form'] = TestForm(request.user)
        return super(TestAdmin, self).get_form(request, obj, **kwargs)

I would think this should work just as is does for my other forms but i am getting this error back from django: invalid literal for int() with base 10: 'TestForm'

After a bit of googeling i came across this approach which puts the queryset filtering inside the ModelAdmin:

form = super(TestAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['controller_fk'].queryset = Controller.objects.filter(custid=cust)
return form

This works perfectly fine but it does require me to create multiple copies of my ModelForm which doesn't seem very dry. So i guess does anyone know how to get my ModelForm queryset returned into the ModelAdmin form?

2
I'm not really sure what's the problem (you haven't provided a full stacktrace to see where the querying goes bad) however could you try returning your form instance directly from the get_form method and see what happens? - Serafeim

2 Answers

2
votes

The trouble is that you are actually instantiating your form in the else clause, whereas the other clause returns the class rather than an instance. Both branches need to return a class.

Unfortunately, there's no easy hook in the ModelAdmin class to provide extra kwargs for a form instantiation: it takes place deep inside the changeform_view method which is much harder to override than it should be. You would need to do something clever to return a class with the user value baked in from get_form.

0
votes

Old question, but I thought I'd offer an alternative for future searches.

You can use a factory function which will capture the HttpRequest object for use in TestForm, then return the TestForm class back to the calling function (in this case, get_form(). This way you can access the current request.user object in TestForm and filter accordingly:

forms.py

def _test_form_factory(request):
""" Capture request object for use in TestForm, then return the TestForm class. """

    class TestForm(ModelForm):
        def __init__(self, *args, **kwargs):
            super().__init__(*args,**kwargs)
            if request.user.is_superuser:
                return
            self.fields['controller'].queryset = Controller.objects.filter(user=request.user)

        class Meta:
            model = Test
            exclude = ['customer']

    return TestForm

admin.py

def get_form(self, request, obj=None, **kwargs):
    """ Capture request object for use in the form. """
    self.form = _test_form_factory(request)
    return super().get_form(request, obj, **kwargs)

NOTE: All code logic can be performed in TestForm -- no need for SuTestForm, assuming all it did was pass back the full queryset for the controller form field in it. Even if SuTestForm performs other logic specific to the class, you should find that you can migrate the code to TestForm now, since you have access to the HttpRequest object.