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.