How to filter objects for a field listed in autocomplete_fields ?

I’m using autocomplete_fields for one field where I expect to have many values.

For this specific field I have to filter objects shown in dropdown - for example I don’t want that user see objects which have been “soft deleted”.

I tried to achieve that by using formfield_for_foreignkey but this does not work properly - I still see all objects in a dropdown.
(Yes, it is true that if I select an object from dropdown which does NOT match objects filtered by formfield_for_foreignkey then I cannot select it at the end (validation error) but the goal is to NOT even show this object in dropdown.)

In models.py I have:

class Blog(models.Model):
    soft_deleted = models.BooleanField(default=False)
    pages = models.ManyToManyField(Page, through='BlogPage')

class BlogPage(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    page = models.ForeignKey(Page, on_delete=models.CASCADE)

And in admin.py I have:

class BlogAdmin(admin.ModelAdmin):
    search_fields = ['name']

class BlogPageInline(admin.TabularInline):
    model = BlogPage
    extra = 0
    fields = ['blog','page',]
    list_select_related = ('blog','page')
    autocomplete_fields = ['blog']
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == 'blog':
            kwargs["queryset"] = Blog.objects.filter(soft_deleted=False)
        return super().formfield_for_foreignkey(db_field, request, **kwargs)

Going by what I’m reading in the docs -

The docs for autocomplete_fields say:

You must define search_fields on the related object’s ModelAdmin because the autocomplete search uses it.

The docs for search_fields say:

If you need to customize search you can use ModelAdmin.get_search_results() to provide additional or alternate search behavior.

So my first guess would be that you could implement a custom get_search_results method on that related object to determine what should be returned.

@KenWhitesell Thank you.
I updated admin.py - this is how it looks now:

class BlogAdmin(admin.ModelAdmin):
    search_fields = ['name']
    def get_search_results(self, request, queryset, search_term):
        queryset, may_have_duplicates = super().get_search_results(request, queryset, search_term,)
        try:
            queryset = queryset.exclude(is_deleted=True)
        except:
            pass
        return queryset, may_have_duplicates

class BlogPageInline(admin.TabularInline):
    model = BlogPage
    extra = 0
    fields = ['blog','page',]
    list_select_related = ('blog','page')
    autocomplete_fields = ['blog']

It works nice for general rules.

Additional question: Since I use autocomplete_fields in Inline BlogPageInline, is there a way I exclude from the search results Blog objects which are already associated with BlogPage ?

Since exclude is (generally) the reverse operation of a filter, how would you write a filter to find all the Blog objects that are already associated with a BlogPage?

My question is wrong. I want to exclude all the Blog objects which are already associated with the Page (and not with BlogPage as I wrote).

On the edit page of Page I use BlogPageInline to add Blog objects where the Page has to be shown. Now when adding Blog objects (via BlogPageInline) I want to exclude objects which were already added. I think the first question is not if I should use exclude or filter but how to get Page ID. I mean I am on the edit page of Page so ID is in the URL but how to get it into get_search_results which is defined in BlogAdmin ?

I think I gotcha now.

I think you’re going to have problems doing this on the server side, because ones you are adding to the current page only exist in the browser until the page is submitted.

It sounds like to do what you want to do here, you’re going to need to modify the autoselect JavaScript code to find the other entries added on that page and exclude them from the list before presenting that list of options. (Just a guess - I’ve never even thought of trying something like this - but you could probably write an event handler for the change event, interrogate the other instances on the page, and accept/reject the entry accordingly.)

Thank you for your answer. You are right I will not be able to do this.

But anyway, I’m still interested if there is a way to get Page ID into BlogAdmin in this case ?
I am on the edit page of Page so ID is in the URL but how to get it into get_search_results which is defined in BlogAdmin ?

Specifically addressing your question:

  • Your get_search_results has the signature

That means you have access to the request object within that function. You might be able to get the information you want from it. (See Request and response objects | Django documentation | Django)

But I’m a bit confused here, I’m not sure I’m seeing the objective - what you’re trying to accomplish with this.