31

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

sogeking
  • 1,216
  • 2
  • 14
  • 45

6 Answers6

24

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)
mkoistinen
  • 7,724
  • 3
  • 41
  • 56
18

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.

dshap
  • 1,382
  • 14
  • 19
  • 7
    You should point out further that the **parent object** should then be available as a field on self.instance which is not the parent object, but the **inline object**. However, this won't work for new inline objects, since the relationship doesn't exist yet. – mkoistinen Feb 02 '15 at 01:12
13

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')
J Weller
  • 498
  • 8
  • 11
7

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)
erichonkanen
  • 199
  • 1
  • 7
  • This solution is the right one, since it works without a custom form and in all scenarios. For recent versions of Django (as of 3.0), you should use `[-2]` rather than `[-1]`. – WhyNotHugo Jun 17 '20 at 22:03
4

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)
r4F705
  • 65
  • 1
  • 6
0

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