Reputation: 1276
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
Upvotes: 33
Views: 25177
Reputation: 1402
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().__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.
Upvotes: 20
Reputation: 136
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
Upvotes: 0
Reputation: 65
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)
Upvotes: 4
Reputation: 518
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')
Upvotes: 14
Reputation: 7773
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)
Upvotes: 26
Reputation: 199
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)
Upvotes: 7