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 ConstanceAdmin as BaseConstanceAdmin
|
||||
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.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 modeltranslation.translator import NotRegistered, translator
|
||||
from modeltranslation.utils import get_translation_fields
|
||||
|
|
@ -112,6 +115,88 @@ class ActivationActionsMixin:
|
|||
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):
|
||||
model = AttributeValue
|
||||
extra = 0
|
||||
|
|
@ -230,7 +315,7 @@ class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
|
|||
|
||||
|
||||
@register(Product)
|
||||
class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
|
||||
class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, AjaxAutocompleteListFilterModelAdmin):
|
||||
model = Product # type: ignore
|
||||
list_display = (
|
||||
"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