Features: 1) Add RelatedAutocompleteFilter for admin filters with autocomplete support; 2) Introduce autocomplete_list_filter.html template for the new filter; 3) Enable dynamic filtering with client-side autocomplete functionality;

Fixes: 1) Add missing imports for `FieldListFilter` and `reverse` in `core/admin.py`;

Extra: 1) Include relevant JS and CSS assets for autocomplete functionality; 2) Add comments and structure for better readability in template and script.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-07-01 17:55:24 +03:00
parent a63aa0371a
commit c5397c6608
2 changed files with 89 additions and 5 deletions

View file

@ -3,9 +3,10 @@ from contextlib import suppress
from constance.admin import Config
from constance.admin import ConstanceAdmin as BaseConstanceAdmin
from django.apps import apps
from django.contrib.admin import ModelAdmin, TabularInline, action, register, site
from django.contrib.admin import FieldListFilter, ModelAdmin, TabularInline, action, register, site
from django.contrib.gis.admin import GISModelAdmin
from django.db.models import Model
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from modeltranslation.translator import NotRegistered, translator
from modeltranslation.utils import get_translation_fields
@ -36,6 +37,49 @@ from .models import (
)
class RelatedAutocompleteFilter(FieldListFilter):
template = "admin/autocomplete_list_filter.html"
limit = 10
def __init__(self, field, request, params, model, model_admin, field_path):
self.field = field
self.request = request
self.params = params
self.model_admin = model_admin
self.field_path = field_path
self.lookup_kwarg = f"{field_path}__{field.target_field.name}__exact"
self.lookup_val = params.get(self.lookup_kwarg)
rel_model = field.remote_field.model
app_label = rel_model._meta.app_label
model_name = rel_model._meta.model_name
url_name = f"admin:{app_label}_{model_name}_autocomplete"
self.autocomplete_url = (
reverse(url_name)
+ f"?field_name={field.name}&limit={self.limit}"
)
def expected_parameters(self):
return [self.lookup_kwarg]
def has_output(self):
return True
def choices(self, changelist):
yield {
"selected": bool(self.lookup_val),
"query_string": changelist.get_query_string(
{self.lookup_kwarg: self.lookup_val} if self.lookup_val else {},
[]
),
"display": _(""),
"autocomplete_url": self.autocomplete_url,
"lookup_kwarg": self.lookup_kwarg,
"lookup_val": self.lookup_val or "",
}
class FieldsetsMixin:
general_fields: list = []
relation_fields: list = []
@ -242,16 +286,14 @@ class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
"rating",
"modified",
)
autocomplete_list_filter = (
"category",
"brand",
)
list_filter = (
"is_active",
"is_digital",
"stocks__vendor",
"created",
"modified",
("brand", RelatedAutocompleteFilter),
("category", RelatedAutocompleteFilter),
)
search_fields = (
"name",

View file

@ -0,0 +1,42 @@
{% load i18n admin_urls static %}
<div class="filter-autocomplete">
{# A hidden input to carry the selected value #}
<input type="hidden" name="{{ spec.lookup_kwarg }}" value="{{ spec.lookup_val }}" id="id_{{ spec.lookup_kwarg }}"/>
{# The visible text input #}
<label for="id_autocomplete_{{ spec.lookup_kwarg }}"></label><input type="text"
placeholder="{% trans 'Search…' %}"
id="id_autocomplete_{{ spec.lookup_kwarg }}"
style="width: 90%;"
data-autocomplete-url="{{ spec.autocomplete_url }}"
value="{% if spec.lookup_val %}{{ spec.lookup_val|get_obj_display:spec.field_path }}{% endif %}"
/>
</div>
<script src="{% static 'admin/js/jquery.init.js' %}"></script>
<script src="{% static 'admin/js/jquery.ui.core.js' %}"></script>
<script src="{% static 'admin/js/jquery.ui.autocomplete.js' %}"></script>
<link rel="stylesheet" href="{% static 'admin/css/jquery-ui.css' %}"/>
<script>
(function ($) {
let $input = $('#id_autocomplete_{{ spec.lookup_kwarg }}');
let $hidden = $('#id_{{ spec.lookup_kwarg }}');
$input.autocomplete({
source: function (request, response) {
$.getJSON($input.data('autocomplete-url'), {
term: request.term // admin autocomplete expects "term"
}, response);
},
minLength: 2,
select: function (event, ui) {
// ui.item.id and ui.item.text come from the JSON returned
$hidden.val(ui.item.id);
// trigger a reload of the changelist with the new filter
let params = {};
params["{{ spec.lookup_kwarg }}"] = ui.item.id;
window.location.search = $.param(params);
}
});
})(django.jQuery);
</script>