django admin inlines: get object from formfield_for_foreignkey

Question:

I am trying to filter the options shown in a foreignkey field, within a django admin inline. Thus, I want to access the parent object being edited. I have been researching but couldn’t find any solution.

class ProjectGroupMembershipInline(admin.StackedInline):
    model = ProjectGroupMembership
    extra = 1
    formset = ProjectGroupMembershipInlineFormSet
    form = ProjectGroupMembershipInlineForm

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        if db_field.name == 'group':
            kwargs['queryset'] = Group.objects.filter(some_filtering_here=object_being_edited)
        return super(ProjectGroupMembershipInline, self).formfield_for_foreignkey(db_field, request, **kwargs)

I have verified that kwargs is empty when editing an object, so I can’t get the object from there.

Any help please? Thanks

Asked By: sogeking

||

Answers:

To filter the choices available for a foreign key field in an admin inline, I override the form so that I can update the form field’s queryset attribute. That way you have access to self.instance which is the object being edited in the form. So something like this:

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

You don’t need to use formfield_for_foreignkey if you do the above and it should accomplish what you described.

Answered By: dshap

I was able to solve it by using the formfield_for_foreignkey and stripping the object ID from the url. It’s not the sexiest way to get the ID but Django doesn’t provide access to the object ID on the admin object yet (it should).

class ObjectAdmin(admin.ModelAdmin):

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        obj_id = request.META['PATH_INFO'].rstrip('/').split('/')[-1]
        if db_field.name == 'my_field' and obj_id.isdigit():
            obj = self.get_object(request, obj_id)
            if obj:
                kwargs['queryset'] = models.Object.objects.filter(field=obj)
        return super().formfield_for_foreignkey(db_field, request, **kwargs)
Answered By: erichonkanen

Another way, that, IMHO, feels cleaner than, but is similar to @erichonkanen’s answer is something like this:

class ProjectGroupMembershipInline(admin.StackedInline):
    # irrelevant bits....

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "group":
            try:
                parent_id = request.resolver_match.args[0]
                kwargs["queryset"] = Group.objects.filter(some_column=parent_id)
            except IndexError:
                pass
        return super().formfield_for_foreignkey(db_field, request, **kwargs)
Answered By: mkoistinen

The answer provided by @mkoistinen is great but django stores parent id in kwargs and not args so it would be correct to extract it like this.

parent_id = request.resolver_match.kwargs.get('object_id')
Answered By: J Weller

This is the solution I came up with, which seems pretty clean

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "group":
            parent_id = request.resolver_match.kwargs['object_id']
            kwargs["queryset"] = Group.objects.filter(some_column=parent_id)
        return super().formfield_for_foreignkey(db_field, request, **kwargs)
Answered By: Raftos

If you are working with an older Django version, it may not yet support the resolver_match attribute.

In that case i found following solution:

    def formfield_for_foreignkey(self, db_field, request, *args, **kwargs):
        field = super(ProjectGroupMembershipInline, self).formfield_for_foreignkey(db_field, request, *args, **kwargs)
        if db_field.name == 'group':
            resolved_url = resolve(request.path.replace('/{}/'.format(get_language()), '/'))  # remove localisation of url
            if resolved_url and resolved_url.args:  # check we are not in changelist view
                field.queryset = field.queryset.filter(pk=resolved_url.args[0]))  # obj id first and only arg for view.
        return field
Answered By: Martin Achenrainer
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.