Search code examples
pythondjangodjango-adminmanytomanyfield

Django Admin - Filter ManyToManyField with through model


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?


Solution

  • 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