Feature Request: An easier way to pass the parent object to the form of an InlineModelAdmin

2,761 views
Skip to first unread message

Al Johri

unread,
Sep 10, 2016, 2:50:11 PM9/10/16
to Django developers (Contributions to Django itself)
Hi All,

Within the Django Admin, I'm trying to pass the current object from the ModelAdmin to the InlineModelAdmin. While I've come up with a few solutions, I'm finding them all to be extremely hacky and hopefully a better solution is possible, perhaps via the addition of a feature. I apologize if this is more relevant to Django Users.

Solution 1 (doesn't work in all cases)

The cleanest solution seems to override the __init__ method of the InlineModelAdmin's ModelForm class which allows access to the current inline instance. Because the current inline instance must by definition have a foreign key relationship to the parent, one is able to access the page's current object. However, this does not work for new inline forms.

class MyInlineModelForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['group'].queryset = Car.objects.filter(some_filtering_here=self.instance.parent)

Solution 2

Another solution, is to override the formfield_callback within the ModelAdmin as we still have access to the obj parameter at this point. We pass the obj parameter through to the formfield_for_dbfield function allowing a particular field to change its queryset depending on the page's current obj (i.e filtering a select box).

class MyInlineModelAdmin(admin.StackedInline):
    def get_formset(self, request, obj=None, **kwargs):
        kwargs['formfield_callback'] = partial(self.formfield_for_dbfield, request=request, obj=obj)
        return super().get_formset(request, obj, **kwargs)

    def formfield_for_dbfield(self, db_field, **kwargs):
        obj = kwargs.pop('obj', None)
        formfield = super().formfield_for_dbfield(db_field, **kwargs)
        if db_field.name == "group" and person:
            formfield.queryset = Car.objects.filter(some_filtering_here=obj)
return formfield

Solution 3

Yet another solution is to run a regex on the url and utilize the formfield_for_foreignkey to filter a select box using the current obj id. This is definitely the hackiest and only allows for filtering a select box, not setting initial parameters on the inline ModelForm based its the parent object.

class MyInlineModelAdmin(admin.StackedInline):
def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == "group": try: parent_obj_id = request.resolver_match.args[0] kwargs["queryset"] = Car.objects.filter(some_filtering_here=parent_obj_id) except IndexError: pass return super(
).formfield_for_foreignkey(db_field, request, **kwargs)

Expansion on Solution 2 and Summary

The root of the issue is the ModelForm has no idea of its current context as an inline form within the parent ModelAdmin.

I've found Solution 2 be the cleanest at this point, but it still has its issues.

For example, my child object has a foreign key field (myfield) which needs to be filled out based on a relationship to the parent object. I would ideally like to just override the save method of the model form and add the myfield parameter but I can't since I don't have access to the parent object. Instead, I need to pass it in via a hidden input field to the form.

def formfield_for_dbfield(self, db_field, **kwargs):
    obj = kwargs.pop('obj', None)
    formfield = super().formfield_for_dbfield(db_field, **kwargs)
    if db_field.name == "myfield" and obj:
        formfield = forms.CharField(initial=obj.some_relationship.id, widget=forms.HiddenInput())
    return formfield

Ideally I could just add the HiddenInput widget and set the initial attribute on the formfield but apparently that doesn't work with the ModelChoiceField. Instead, I have to completely override the formfield with a CharField.

However, because its now a CharField I need to "clean" the parameter in the ModelForm to convert it back into a model. (hacky!)

def clean_myfield(self):
    data = self.cleaned_data['myfield']
    return MyModel.objects.get(id=int(data))

There must be an easier way to do all of this! What am I missing? Any help would be appreciated.

Thanks,

Al

Al Johri

unread,
Sep 18, 2017, 9:03:21 PM9/18/17
to Django developers (Contributions to Django itself)
Just bumping this issue.
Reply all
Reply to author
Forward
0 new messages