Opened 3 years ago

Last modified 3 years ago

#32993 closed Cleanup/optimization

Refactor AutocompleteJsonView to support extra fields in autocomplete response — at Version 8

Reported by: mrts Owned by: mrts
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 (last modified by mrts)

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 constructor 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.serialize_result(obj, to_field_name) for obj in context['object_list']
            ],
            'pagination': {'more': context['page_obj'].has_next()},
        })

where serialize_result() contains the original object to dictionary conversion code that would be now easy to override:

def serialize_result(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 serialize_result(self, obj, to_field_name):
        return super.serialize_result(obj, to_field_name) | {'notes': obj.notes}

What do you think, is this acceptable? I'm more than happy to provide the patch.

Change History (8)

comment:1 by mrts, 3 years ago

Description: modified (diff)

comment:2 by Claude Paroz, 3 years ago

Triage Stage: UnreviewedAccepted
Version: 3.2dev

Makes sense to me.

comment:3 by Mariusz Felisiak, 3 years ago

Type: Cleanup/optimizationNew feature

comment:4 by mrts, 3 years ago

Owner: changed from nobody to mrts
Status: newassigned

comment:5 by mrts, 3 years ago

Has patch: set
Owner: mrts removed
Status: assignednew

comment:6 by Jacob Walls, 3 years ago

Owner: set to mrts
Status: newassigned

Just a small note for future: you can keep the ticket assigned to yourself while you're actively maintaining a PR for it.

comment:7 by mrts, 3 years ago

Thanks for the heads up! Will keep it in mind in the future.

comment:8 by mrts, 3 years ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top