Opened 3 years ago
Last modified 3 years ago
#32993 closed Cleanup/optimization
Refactor AutocompleteJsonView to support extra fields in autocomplete response — at Initial Version
Reported by: | mrts | Owned by: | nobody |
---|---|---|---|
Component: | contrib.admin | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Ready for checkin | |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | yes | UI/UX: | no |
Description
Adding data attributes to items in ordinary non-autocomplete foreign key fields that use forms.widgets.Select
-based widgets is relatively easy. This enables powerful and dynamic admin site customizations where fields from related models are updated immediately when users change the selected item.
However, adding new attributes to autocomplete field results currently requires extending contrib.admin.views.autocomplete.AutocompleteJsonView
and fully overriding the AutocompleteJsonView.get()
method. Here's an example:
class MyModelAdmin(admin.ModelAdmin): def get_urls(self): return [ path('autocomplete/', CustomAutocompleteJsonView.as_view(admin_site=self.admin_site)) if url.pattern.match('autocomplete/') else url for url in super().get_urls() ] 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() return JsonResponse({ 'results': [ {'id': str(getattr(obj, to_field_name)), 'text': str(obj), 'notes': obj.notes} # <-- customization here for obj in context['object_list'] ], 'pagination': {'more': context['page_obj'].has_next()}, })
The problem with this is that as AutocompleteJsonView.get()
keeps evolving, there's quite a lot of maintenance overhead required to catch up.
The solutions is simple, side-effect- and risk-free: adding a result customization extension point to get()
by moving the lines that construct the results inside JsonResponse
to a separate method. So instead of
return JsonResponse({ 'results': [ {'id': str(getattr(obj, to_field_name)), 'text': str(obj)} for obj in context['object_list'] ], 'pagination': {'more': context['page_obj'].has_next()}, })
there would be
return JsonResponse({ 'results': [ self.obj_to_dict(obj, to_field_name) for obj in context['object_list'] ], 'pagination': {'more': context['page_obj'].has_next()}, })
where obj_to_dict()
contains the original object to dictionary conversion code that would be now easy to override:
def obj_to_dict(self, obj, to_field_name): return {'id': str(getattr(obj, to_field_name)), 'text': str(obj)}
The example CustomAutocompleteJsonView
from above would now become succinct and maintainable:
class CustomAutocompleteJsonView(AutocompleteJsonView): def obj_to_dict(self, obj, to_field_name): return super.obj_to_dict(obj, to_field_name) | {'notes': obj.notes}
What do you think, is this acceptable? I'm more than happy to provide the patch.