Opened 4 years ago

Closed 4 years ago

#32628 closed New feature (wontfix)

Add extra data to autocomplete request

Reported by: Seb G Owned by: nobody
Component: contrib.admin Version: 3.2
Severity: Normal Keywords:
Cc: Johannes Maron Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

More often than not, I need to add extra parameters to requests sent by autocomplete widgets from admin forms. Currently I have 2 options to do so:

  1. Listen to opening/closing Select2 events to add headers to the request:
$(document).on("select2:opening", "#foo_set-group select.admin-autocomplete", function () {
    $.ajaxSetup({headers: {"X-Foo": "bar"}});
  }
).on("select2:closing", "#foo_set-group select.admin-autocomplete", function () {
    delete $.ajaxSettings.headers["X-Foo"];
  }
);
  1. Destroy the widget and rebuild it using $.fn.djangoAdminSelect2 to hack my extra parameters next to the other expected GET parameters:
let $select = $("#id_my_field");
$select.select2("destroy").djangoAdminSelect2({
  ajax: {
    data: function (params) {
      return {
        term: params.term
        page: params.page
        app_label: $select.data("app-label"),
        model_name: $select.data("model-name"),
        field_name: $select.data("field-name")
        foo: "bar"
      }
    }
  }
});

None of these solutions look clean to me, and I would definitely like to have the ability to add extra parameters to $.fn.djangoAdminSelect2 properly as follows:

  • autocomplete.js:
'use strict';
{
    const $ = django.jQuery;
    const init = function($element, options, extraParams) {
        const settings = $.extend({
            ajax: {
                data: function(params) {
                    let defaultParams = {
                        term: params.term,
                        page: params.page,
                        app_label: $element.data('app-label'),
                        model_name: $element.data('model-name'),
                        field_name: $element.data('field-name')
                    };
                    return $.extend(defaultParams, extraParams)
                }
            }
        }, options);
        $element.select2(settings);
    };

    $.fn.djangoAdminSelect2 = function(options, extraParams) {
        const settings = $.extend({}, options);
        $.each(this, function(i, element) {
            const $element = $(element);
            init($element, settings, extraParams);
        });
        return this;
    };

    $(function() {
        // Initialize all autocomplete widgets except the one in the template
        // form used when a new formset is added.
        $('.admin-autocomplete').not('[name*=__prefix__]').djangoAdminSelect2();
    });

    $(document).on('formset:added', (function() {
        return function(event, $newFormset) {
            return $newFormset.find('.admin-autocomplete').djangoAdminSelect2();
        };
    })(this));
}
  • consumer_script.js:
$select = $("#id_my_field");
$select.select2("destroy").djangoAdminSelect2({}, {foo: "bar"});

Change History (9)

comment:2 by Mariusz Felisiak, 4 years ago

Cc: Johannes Maron added
Resolution: duplicate
Status: newclosed

Thanks for this proposition, however I don't think that JavaScript is the right place to inject extra parameters. I would rather customize ModelAdmin.autocomplete_view() and AutocompleteJsonView.

Closing as a duplicate of #29700 as documenting AutocompleteJsonView and ModelAdmin.autocomplete_view() should fix this issue.

comment:3 by Seb G, 4 years ago

Thanks for both answers. However, I respectfully disagree with both of them since both solutions are implemented back-end side and hence lack flexibility for some scenario (including the ones I was opening this ticket for).

Simplest example: I want to dynamically add the value of another field of the form as an extra GET parameter to the autocomplete, to implement some sort of fields-coupling.

comment:4 by Mariusz Felisiak, 4 years ago

Resolution: duplicatewontfix
Type: Cleanup/optimizationNew feature

Thanks for both answers. However, I respectfully disagree with both of them since both solutions are implemented back-end side and hence lack flexibility for some scenario (including the ones I was opening this ticket for).

Simplest example: I want to dynamically add the value of another field of the form as an extra GET parameter to the autocomplete, to implement some sort of fields-coupling.

Agreed, there is a use case that is not covered by the current implementation, however the Django Admin is not a universal tool for building an app. You can always override autocomplete.js in your project.

comment:5 by Johannes Maron, 4 years ago

Hi there,

The author of both the autocomplete integration as well as the django-select2 package here.
So, I understand you point and frustration. However, the second solution you are proposing actually looks a lot cleaner to me, than to arbitrarily add a second options object.

Just of consistency I would keep the options we pass to djangoAdminSelect2 the same as the once you'd pass to select2. It would make it very confusing for many folk if we break with the select2 API.

All that aside, this is not a public API. Therefore, your best bet is to overwrite the whole file, if you need some special implementation or use django-select2 or your special needs.

Please, if you want to change the options object structure, I must kindly ask you to try your luck with the Select2 project first. We simply don't have the resources to maintain a custom implementation.

Best,
Joe

comment:6 by Seb G, 4 years ago

Thanks for clarifying the situation Johannes. I'll stick with my own override then :-)

comment:7 by Seb G, 4 years ago

Dear Johannes,

I found the following code to both add the requested feature (being able to add extra GET params to the AJAX request) and preserve Select2 API.

I also took the liberty of passing the true parameter to the $.extend call that merges default Django settings with user-defined settings in the init function (line 11), since omitting it caused a bug where other parameters (passed through options) nested under the ajax key were overwritten to undefined (see relevant documentation here: https://api.jquery.com/jQuery.extend/#jQuery-extend-deep-target-object1-objectN).

  • autocomplete.js:
'use strict';
{
    const $ = django.jQuery;
    const init = function($element, options) {
        let userData = function (params) {};
        if (options.ajax && options.ajax.data) {
            userData = options.ajax.data;
            delete options.ajax.data;
        }

        const settings = $.extend(true, {
            ajax: {
                data: function(params) {
                    const defaultData = {
                        term: params.term,
                        page: params.page,
                        app_label: $element.data('app-label'),
                        model_name: $element.data('model-name'),
                        field_name: $element.data('field-name')
                    };
                    return $.extend(defaultData, userData(params))
                }
            }
        }, options);
        
        $element.select2(settings);
    };

    $.fn.djangoAdminSelect2 = function(options) {
        const settings = $.extend({}, options);
        $.each(this, function(i, element) {
            const $element = $(element);
            init($element, settings);
        });
        return this;
    };

    $(function() {
        // Initialize all autocomplete widgets except the one in the template
        // form used when a new formset is added.
        $('.admin-autocomplete').not('[name*=__prefix__]').djangoAdminSelect2();
    });

    $(document).on('formset:added', (function() {
        return function(event, $newFormset) {
            return $newFormset.find('.admin-autocomplete').djangoAdminSelect2();
        };
    })(this));
}
  • consumer_script.js:
$select = $("#id_my_field");
$select.select2("destroy").djangoAdminSelect2({
    ajax: {
        data: function (params) {
            return {foo: "bar"}
        }
        // Adding other AJAX settings is now also possible, such as processResults for example
        processResults: function (data) {
            console.log("Now possible")
            return data
        }
    }
});

Do you think this approach would be compatible with your flow?

Regards

comment:8 by Seb G, 4 years ago

Resolution: wontfix
Status: closednew

For the record, I submitted a PR which was rejected since the flow seems to be that an agreement should be reached here on Trac before moving on to a PR. So could you please take a look either at the code here or at the PR?

Link to the PR: https://github.com/django/django/pull/14305

comment:9 by Mariusz Felisiak, 4 years ago

Resolution: wontfix
Status: newclosed
Note: See TracTickets for help on using tickets.
Back to Top