Skip to content

Instantly share code, notes, and snippets.

@jrobichaud
Last active April 3, 2025 18:13
Show Gist options
  • Save jrobichaud/4ce6c4071b0631d22cf4a6d1c06c7130 to your computer and use it in GitHub Desktop.
Save jrobichaud/4ce6c4071b0631d22cf4a6d1c06c7130 to your computer and use it in GitHub Desktop.

django autocomplete filtering

Context

If you need to override an autocomplete's behaviour based on its use in a django form, it is how I managed to do it.

Normally you would use formfield_for_foreignkey but when using autocomplete_fields is not taken in account by django admin.

Note: this is not dynamic based based on the values in the form

static file to add

This file is a modified version of django/contrib/admin/static/admin/js/autocomplete.js

It overrides the existing function of existing file.

my_app/static/my_app/admin/js/autocomplete_filtered.js

'use strict';
{
  const $ = django.jQuery;

  $.fn.djangoAdminSelect2 = function() {
    $.each(this, function(i, element) {
      $(element).select2({
        ajax: {
          data: (params) => {
            return {
              term: params.term,
              page: params.page,
              app_label: element.dataset.appLabel,
              model_name: element.dataset.modelName,
              field_name: element.dataset.fieldName,
              params: element.dataset.fieldParams // <-- adds this
            };
          }
        }
      });
    });
    return this;
  };
}

widgets.py

my_app/widgets.py

from urllib.parse import urlencode

from django.contrib.admin import widgets
from django import forms


class FilteredAutoCompleteSelect(widgets.AutocompleteSelect):
    def __init__(self, field, admin_site, params: dict, attrs=None, using=None):
        super().__init__(field, admin_site, attrs, using)
        self.params = params

    def build_attrs(self, base_attrs, extra_attrs=None):
        attrs = super().build_attrs(base_attrs, extra_attrs)
        attrs["data-field-params"] = urlencode(self.params)
        return attrs

    @property
    def media(self):
        return super().media + forms.Media(
            js=(
                "admin/js/jquery.init.js",
                "my_app/admin/js/autocomplete_filtered.js",
            )
        )

how to use example

models.py

from django.db import models

class Parent(models.Model):
    pass

class Child(models.Model):
    parent = models.ForeignKey(Parent, on_delete=django.db.models.deletion.CASCADE,)
    name = models.Charfield(max_length=100)

forms.py

from django import forms
from django.contrib import admin

from .widgets import FilteredAutoCompleteSelect
from .models import Child


class ChildForm(forms.ModelForm):
    class Meta:
        model = Child
        widgets = {
            "crop": FilteredAutoCompleteSelect(
                Child.parent.field,
                admin.site,
                params={"foo": "1"},
            )
        }
        fields = "__all__"

admin.py

from django.contrib import admin

from .models import Parent, Child
from .forms import ChildForm

@admin.register(Child)
class ChildAdmin(admin.ModelAdmin):
    model = Child
    form = ChildForm
    

@admin.register(Parent)
class ParentAdmin(admin.ModelAdmin):
    search_fields = ("name", )
    model = Parent
    
    def get_search_results(self, request, queryset, search_term):
        queryset, use_distinct = super().get_search_results(
            request, queryset, search_term
        )
        params = request.GET.get("params")
        if params:
            q_params = QueryDict(params)
            if "foo" in q_params:
                # DO SOMETHING WITH QUERYSET HERE
                queryset = queryset.filter()

        return queryset, use_distinct

The url will look like this in the Network Tab of your browser http://example.com/admin/autocomplete/?app_label=my_app&model_name=child&field_name=parent&params=foo%3D1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment