The reason the field shows a normal select
widget is that, when you define your custom field, you don't set the widget as an AutocompleteSelect
.
In the ModelAdmin
class where you specify your autocomplete_fields
, import your CustomDisplay
and AutocompleteSelect
and add the following method:
from django.contrib.admin.widgets import AutocompleteSelect
class YourModelAdmin(admin.ModelAdmin):
autocomplete_fields = ['something']
...
def formfield_for_foreignkey(self, db_field, request, **kwargs):
db = kwargs.get('using')
if db_field.name == 'something':
return CustomDisplay(queryset=Something.object.all(), widget=AutocompleteSelect(db_field.remote_field, self.admin_site, using=db))
return super().formfield_for_foreignkey(db_field, request, **kwargs)
That will only display the custom text when you view an existing instance. When you view the autocomplete drop down, and select an entry, the label is not generated from label_from_instance()
, but from a straightforward str()
call inside AutocompleteJsonView
.
So assuming you only want to change the label in the autocomplete widget (to change the label across the board you would obviously just change the model __str()__
method), you also need to create a custom class in admin.py
that modifies the get()
method in AutocompleteJsonView
:
from django.contrib.admin.options import AutocompleteJsonView
from django.http import Http404, JsonResponse
class CustomAutocompleteJsonView(AutocompleteJsonView):
def get(self, request, *args, **kwargs):
if not self.model_admin.get_search_fields(request):
raise Http404(
'%s must have search_fields for the autocomplete_view.' %
type(self.model_admin).__name__
)
if not self.has_perm(request):
return JsonResponse({'error': '403 Forbidden'}, status=403)
self.term = request.GET.get('term', '')
self.paginator_class = self.model_admin.paginator
self.object_list = self.get_queryset()
context = self.get_context_data()
# Replace this with the code below.
#
# return JsonResponse({
# 'results': [
# {'id': str(obj.pk), 'text': str(obj)}
# for obj in context['object_list']
# ],
# 'pagination': {'more': context['page_obj'].has_next()},
# })
return JsonResponse({
'results': [
{'id': str(obj.pk), 'text': 'Some custom text: {}'.format(obj.name)}
for obj in context['object_list']
],
'pagination': {'more': context['page_obj'].has_next()},
})
Now set the autocomplete_view
on the ModelAdmin
class that the autocomplete is displaying results for (not the ModelAdmin
class where you specify autocomplete_fields):
def autocomplete_view(self, request):
return CustomAutocompleteJsonView.as_view(model_admin=self)(request)
So if you have a ModelAdmin
class called YourModelAdmin
with autocomplete_fields = ['something']
, you would set the autocomplete_view
for the corresponding ModelAdmin
class for your Something
model.
Update for Django 4.2:
from django.contrib.admin.sites import AdminSite
from django.contrib.admin.widgets import AutocompleteSelect
from django.contrib.admin.views.autocomplete import AutocompleteJsonView
from django.core.exceptions import PermissionDenied
class CustomAdminSite(AdminSite):
# This will change the view for all autocompletes.
# You can handle specific cases within the autocomplete get() method.
def autocomplete_view(self, request):
return CustomAutocompleteJsonView.as_view(admin_site=self)(request)
admin_site = CustomAdminSite()
class CustomAutocompleteJsonView(AutocompleteJsonView):
def get(self, request, *args, **kwargs):
(
self.term,
self.model_admin,
self.source_field,
to_field_name,
) = self.process_request(request)
if not self.has_perm(request):
raise PermissionDenied
self.object_list = self.get_queryset()
context = self.get_context_data()
if type(self.model_admin) == SomethingModelAdmin:
return JsonResponse(
{
"results": [
{'id': str(obj.pk), 'text': 'Some custom text: {}'.format(obj.name)}
for obj in context['object_list']
],
"pagination": {"more": context["page_obj"].has_next()},
}
)
else:
return super().get(request, *args, **kwargs)
class YourModelAdmin(admin.ModelAdmin):
autocomplete_fields = ['something']
...
def formfield_for_foreignkey(self, db_field, request, **kwargs):
db = kwargs.get('using')
if db_field.name == 'something':
return CustomDisplay(queryset=Something.objects.all(),
widget=AutocompleteSelect(db_field,
self.admin_site,
using=db))
return super().formfield_for_foreignkey(db_field, request, **kwargs)