How can I filter a queryset inside the Admin page of an object that has a ManyToManyField
relation with a manually defined through
model?
Given models.py
class Foo(models.Model):
foo_field1 = models.CharField(max_length=50)
class Main(models.Model):
main_field1 = models.CharField(max_length=50)
m2mfield = models.ManyToManyField(Foo, through="FooBar")
class FooBar(models.Model):
main = models.ForeignKey(Main, on_delete=models.CASCADE)
foo = models.ForeignKey(Foo, on_delete=models.CASCADE)
new_field = models.CharField(max_length=50)
Inside admin.py
class M2MInlineAdmin(admin.TabularInline):
model = Main.m2mfield.through
extra = 1
class MainAdmin(admin.ModelAdmin):
inlines = [M2MInlineAdmin,]
...
def formfield_for_manytomany(self, db_field, request, **kwargs):
print('called formfield_for_manytomany')
return super().formfield_for_manytomany(db_field, request, **kwargs)
def get_field_queryset(self, db, db_field, request):
print('called get_field_queryset')
return super().get_field_queryset(db, db_field, request)
I try to access both of these methods, but none of them are called if I specify a through
table. However, they do get called if the ManyToMany relation is simply defined as like this:
class Main(models.Model):
main_field1 = models.CharField(max_length=50)
m2mfield = models.ManyToManyField(Foo)
Is there a method to filter the queryset when a through table is specified (while being able to access the request
context)?
EDIT:
The methods are indeed called when the ManyToManyField has a through
model specified, only if there are no fieldsets
specified inside the modelAdmin
class.
How to access these methods when fieldsets
are defined?
formfield_for_manytomany
method seems to be called only when default form is used. When fieldsets is defined, it is using a different form which is why above method is not getting called.
Since you are using tabular admin for many to many field, you can override get_queryset to filter with field.
class M2MInlineAdmin(admin.TabularInline):
model = Main.fruits.through
extra = 1
def get_queryset(self, request):
qs = super(M2MInlineAdmin, self).get_queryset(request)
qs = qs.filter(some_arg=some_value)
return qs
Alternatively, you can write a custom model form and use it in admin instead of default form.
class MainAdminForm(forms.ModelForm):
class Meta:
model = Main
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# custom setup
class MainAdmin(admin.ModelAdmin):
form = MainAdminForm