Features: 1) Add data-lookup-kwarg to autocomplete input for better filtering; 2) Streamline admin autocomplete URL structure for consistency;

Fixes: 1) Replace outdated jQuery UI paths with modern equivalents; 2) Ensure proper retrieval of JSON responses in autocomplete widget; 3) Correct misaligned parameter initialization in `AdminFilter`;

Extra: Minor formatting improvements and variable renaming for readability.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-07-01 18:12:50 +03:00
parent 90c8f87502
commit 83ac6b27e6
2 changed files with 37 additions and 38 deletions

View file

@ -42,20 +42,17 @@ class RelatedAutocompleteFilter(FieldListFilter):
limit = 10 limit = 10
def __init__(self, field, request, params, model, model_admin, field_path): 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_kwarg = f"{field_path}__{field.target_field.name}__exact"
self.lookup_val = params.get(self.lookup_kwarg, "") self.lookup_val = params.get(self.lookup_kwarg, "")
opts = model_admin.model._meta remote = field.remote_field.model._meta
url_name = f"admin:{opts.app_label}_{opts.model_name}_autocomplete" base_url = reverse("admin:autocomplete")
self.autocomplete_url = ( self.autocomplete_url = (
reverse(url_name) f"{base_url}"
+ f"?field_name={field_path}&limit={self.limit}" f"?app_label={remote.app_label}"
f"&model_name={remote.model_name}"
f"&field_name={field.name}"
f"&limit={self.limit}"
) )
def expected_parameters(self): def expected_parameters(self):
@ -66,15 +63,15 @@ class RelatedAutocompleteFilter(FieldListFilter):
def choices(self, changelist): def choices(self, changelist):
yield { yield {
"selected": bool(self.lookup_val), "selected": bool(self.lookup_val),
"query_string": changelist.get_query_string( "query_string": changelist.get_query_string(
{self.lookup_kwarg: self.lookup_val} if self.lookup_val else {}, ({self.lookup_kwarg: self.lookup_val} if self.lookup_val else {}),
[] []
), ),
"display": _(""), "display": _(""),
"autocomplete_url": self.autocomplete_url, "autocomplete_url": self.autocomplete_url,
"lookup_kwarg": self.lookup_kwarg, "lookup_kwarg": self.lookup_kwarg,
"lookup_val": self.lookup_val, "lookup_val": self.lookup_val,
} }

View file

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