How to ensure attrs are passed into the Autocomplete __init__ within Django Admin

I hit a use case yesterday whereby I have multiple autocomplete fields within one Django admin page where some of the autocomplete fields work better (UX and performance) if they can pass the currently selected value of the first element into the Autocomplete view allowing the get_search_results method of the target models ModelAdmin to then filter the queryset based on the new parameter.

The Background:

Given the following models:

class ModelA(models.Model):
    name = models.CharField()


class ModelB(models.Model):
    name = models.CharField()
    model_a = models.ForeignKey(ModelA)


class ModelC(models.Model):
    name = models.CharField
    model_a = models.ForeignKey(ModelA)
    model_b = models.ForeignKey(ModelB)

Assume developers need Django admin so that the admin page for ModelC instances allows them to select the value for model_a and then select the value for model_b whereby the queryset for model_b is filtered to only include those records with associated with model_a

Currently there’s no built in way to do this which is a shame as autocomplete via SELECT2 and some changes to extend the functionality in Django 4.0 does add additional features.

I’ve gone through the code and have managed to implement it using a work around whereby I overload the autocomplete.js file and add a call to an expected callback function (window.autocompleteCallback) if the function exists. This returns a JSON that is merged with the existing returned object. The results are that additional querystring parameters are then added to the autocomplete url meaning that I can simply overload the get_search_results method on the target class and test for request.GET.get('expected_key'), filtering the queryset further if the value is set.

Then I overload the change_form.html for the model I’m working on (ModelC) and create a Javascript function that returns the additional JSON I need. E.G.

function example () {
    return  {
        "model_b__model_a_id": document.getElementById("id_model_a").value
    }
)

This works well but means I need to keep the function name the same on every admin page I want to overload and I can’t make two autocompletes on the same admin page use different callback functions.

My Aim
My aim was to find a way to extend the way that the Autocomplete widget works to allow this functionality for any autocomplete element with as little overloading of Django as possible.

I’m about 95% of the way there but then I hit an issue.

Looking through the code I saw that the attrs dict passed into the __init___ of the Autocomplete widget are passed through to the point where they would be rendered as HTML attributes against the SELECT element rendered by Select2. This is great as it means if this is set to include say attrs={"dataCallbackFunc": "myFunc"} that this would be added as a HTML5 data attribute and then the autocomplete.js file could check that property on the current element in order to then call a function dynamically using window['funcName'] where the funcName is the value of dataset.CallbackFunc.

If this was a normal form widget then it would simply be a case of overload the form used by ModelAdmin and setting the widget meta for the autocomplete fields to use Autocomplete(attrs={}) but that’s not how autocomplete works sadly.

The issue I have is that the Autocomplete widget it initialised by the formfield_for_foreignkey method within the BaseModelAdmin class. With that method being eventually called upstream wrapped in partial and without the attrs arg/kwarg being passed in.

Does anyone know a clean way of passing attrs into the AutocompleteSelect widget as if I can solve that then I can complete a PR to add this functionality into Django to help extend the autocomplete logic so the querysets can be fitlered more dynamically which will make them more useful

1 Like