Features: 1) Add AjaxAutocompleteListFilter to enable autocomplete functionality for admin list filters; 2) Introduce AjaxAutocompleteSelectWidget for enhanced UI integration; 3) Update ProductAdmin to use new autocomplete list filter; 4) Add template and styles for AutocompleteListFilter.
Fixes: None; Extra: Add supporting JS and CSS for autocomplete list filter functionality.
This commit is contained in:
parent
455c3d71b3
commit
c5fe0cb6c6
4 changed files with 131 additions and 2 deletions
|
|
@ -3,9 +3,12 @@ from contextlib import suppress
|
||||||
from constance.admin import Config
|
from constance.admin import Config
|
||||||
from constance.admin import ConstanceAdmin as BaseConstanceAdmin
|
from constance.admin import ConstanceAdmin as BaseConstanceAdmin
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.contrib.admin import ModelAdmin, TabularInline, action, register, site
|
from django.contrib.admin import ModelAdmin, RelatedFieldListFilter, TabularInline, action, register, site
|
||||||
|
from django.contrib.admin.widgets import AutocompleteSelect
|
||||||
from django.contrib.gis.admin import GISModelAdmin
|
from django.contrib.gis.admin import GISModelAdmin
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
|
from django.db.models.fields.related_descriptors import ManyToManyDescriptor, ReverseManyToOneDescriptor
|
||||||
|
from django.forms import CharField, Form, HiddenInput, ModelChoiceField
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from modeltranslation.translator import NotRegistered, translator
|
from modeltranslation.translator import NotRegistered, translator
|
||||||
from modeltranslation.utils import get_translation_fields
|
from modeltranslation.utils import get_translation_fields
|
||||||
|
|
@ -112,6 +115,88 @@ class ActivationActionsMixin:
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
|
|
||||||
|
class AjaxAutocompleteSelectWidget(AutocompleteSelect):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.qs_target_value = kwargs.pop('qs_target_value')
|
||||||
|
self.model_admin = kwargs.pop('model_admin')
|
||||||
|
self.model = kwargs.pop('model')
|
||||||
|
self.field_name = kwargs.pop('field_name')
|
||||||
|
kwargs.update(admin_site=self.model_admin.admin_site)
|
||||||
|
kwargs.update(field=getattr(self.model, self.field_name).field)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def render(self, name, value, attrs=None, renderer=None):
|
||||||
|
rendered = super().render(name, value, attrs, renderer)
|
||||||
|
|
||||||
|
return (f'<div class="ajax-autocomplete-select-widget-wrapper" data-qs-target-value="{self.qs_target_value}">'
|
||||||
|
f'{rendered}</div>')
|
||||||
|
|
||||||
|
|
||||||
|
class AjaxAutocompleteListFilter(RelatedFieldListFilter):
|
||||||
|
title = _('list filter')
|
||||||
|
parameter_name = '%s__%s__exact'
|
||||||
|
template = 'djaa_list_filter/admin/filter/autocomplete_list_filter.html'
|
||||||
|
|
||||||
|
def __init__(self, field, request, params, model, model_admin, field_path):
|
||||||
|
super().__init__(field, request, params, model, model_admin, field_path)
|
||||||
|
|
||||||
|
qs_target_value = self.parameter_name % (field.name, model._meta.pk.name)
|
||||||
|
queryset = self.get_queryset_for_field(model, field.name)
|
||||||
|
widget = AjaxAutocompleteSelectWidget(
|
||||||
|
model_admin=model_admin, model=model, field_name=field.name, qs_target_value=qs_target_value
|
||||||
|
)
|
||||||
|
|
||||||
|
class AutocompleteForm(Form):
|
||||||
|
autocomplete_field = ModelChoiceField(queryset=queryset, widget=widget, required=False)
|
||||||
|
querystring_value = CharField(widget=HiddenInput())
|
||||||
|
|
||||||
|
autocomplete_field_initial_value = request.GET.get(qs_target_value, None)
|
||||||
|
initial_values = {"querystring_value": request.GET.urlencode()}
|
||||||
|
if autocomplete_field_initial_value:
|
||||||
|
initial_values.update(autocomplete_field=autocomplete_field_initial_value)
|
||||||
|
self.autocomplete_form = AutocompleteForm(initial=initial_values, prefix=field.name)
|
||||||
|
|
||||||
|
def get_queryset_for_field(self, model, name):
|
||||||
|
field_desc = getattr(model, name)
|
||||||
|
if isinstance(field_desc, ManyToManyDescriptor):
|
||||||
|
related_model = field_desc.rel.related_model if field_desc.reverse else field_desc.rel.model
|
||||||
|
elif isinstance(field_desc, ReverseManyToOneDescriptor):
|
||||||
|
related_model = field_desc.rel.related_model
|
||||||
|
else:
|
||||||
|
return field_desc.get_queryset()
|
||||||
|
return related_model.objects.get_queryset()
|
||||||
|
|
||||||
|
|
||||||
|
class AjaxAutocompleteListFilterModelAdmin(ModelAdmin):
|
||||||
|
def get_list_filter(self, request):
|
||||||
|
list_filter = list(super().get_list_filter(request))
|
||||||
|
autocomplete_list_filter = self.get_autocomplete_list_filter()
|
||||||
|
if autocomplete_list_filter:
|
||||||
|
for field in autocomplete_list_filter:
|
||||||
|
list_filter.insert(0, (field, AjaxAutocompleteListFilter))
|
||||||
|
return list_filter
|
||||||
|
|
||||||
|
def get_autocomplete_list_filter(self):
|
||||||
|
return list(getattr(self, 'autocomplete_list_filter', []))
|
||||||
|
|
||||||
|
class Media:
|
||||||
|
js = [
|
||||||
|
'admin/js/vendor/jquery/jquery.js',
|
||||||
|
'admin/js/vendor/select2/select2.full.js',
|
||||||
|
'admin/js/vendor/select2/i18n/tr.js',
|
||||||
|
'admin/js/jquery.init.js',
|
||||||
|
'admin/js/autocomplete.js',
|
||||||
|
'djaa_list_filter/admin/js/autocomplete_list_filter.js',
|
||||||
|
]
|
||||||
|
css = {
|
||||||
|
'screen': [
|
||||||
|
'admin/css/vendor/select2/select2.css',
|
||||||
|
'admin/css/autocomplete.css',
|
||||||
|
'djaa_list_filter/admin/css/autocomplete_list_filter.css',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AttributeValueInline(TabularInline):
|
class AttributeValueInline(TabularInline):
|
||||||
model = AttributeValue
|
model = AttributeValue
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
@ -230,7 +315,7 @@ class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
@register(Product)
|
@register(Product)
|
||||||
class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
|
class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, AjaxAutocompleteListFilterModelAdmin):
|
||||||
model = Product # type: ignore
|
model = Product # type: ignore
|
||||||
list_display = (
|
list_display = (
|
||||||
"name",
|
"name",
|
||||||
|
|
|
||||||
3
core/static/admin/css/autocomplete_list_filter.css
Normal file
3
core/static/admin/css/autocomplete_list_filter.css
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.ajax-autocomplete-select-widget-wrapper .select2-container--admin-autocomplete {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
32
core/static/admin/js/autocomplete_list_filter.js
Normal file
32
core/static/admin/js/autocomplete_list_filter.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
function handle_querystring_and_redirect(querystring_value, qs_target_value, selection) {
|
||||||
|
let required_queryset = [];
|
||||||
|
if (querystring_value.length > 0) {
|
||||||
|
for(const field_eq_value of querystring_value.split("&")){
|
||||||
|
let [field, value] = field_eq_value.split("=");
|
||||||
|
if (field !== qs_target_value){
|
||||||
|
required_queryset.push(field_eq_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selection.length > 0) {
|
||||||
|
required_queryset.push(qs_target_value + "=" + selection);
|
||||||
|
}
|
||||||
|
window.location.href = "?" + required_queryset.join("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
django.jQuery(document).ready(function(){
|
||||||
|
django.jQuery(".ajax-autocomplete-select-widget-wrapper select").on('select2:unselect', function(e){
|
||||||
|
let qs_target_value = django.jQuery(this).parent().data("qs-target-value");
|
||||||
|
let querystring_value = django.jQuery(this).closest("form").find('input[name$="querystring_value"]').val();
|
||||||
|
handle_querystring_and_redirect(querystring_value, qs_target_value, "");
|
||||||
|
});
|
||||||
|
|
||||||
|
django.jQuery(".ajax-autocomplete-select-widget-wrapper select").on('change', function(e, choice){
|
||||||
|
let selection = django.jQuery(e.target).val() || "";
|
||||||
|
let qs_target_value = django.jQuery(this).parent().data("qs-target-value");
|
||||||
|
let querystring_value = django.jQuery(this).closest("form").find('input[name$="querystring_value"]').val();
|
||||||
|
if(selection.length > 0){
|
||||||
|
handle_querystring_and_redirect(querystring_value, qs_target_value, selection);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% load i18n static %}
|
||||||
|
<h3>{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}</h3>
|
||||||
|
|
||||||
|
<form method="get">
|
||||||
|
<ul>
|
||||||
|
<li>{{ spec.autocomplete_form.autocomplete_field }}</li>
|
||||||
|
</ul>
|
||||||
|
{{ spec.autocomplete_form.querystring_value }}
|
||||||
|
</form>
|
||||||
Loading…
Reference in a new issue