Features: 1) Remove AutocompleteFieldListFilter and related admin filters and templates; 2) Remove associated JavaScript, CSS, and Python logic; 3) Simplify admin configurations by dropping unused autocomplete filters;
Fixes: None; Extra: 1) Cleanup unused imports and redundant UI files; 2) Minor adjustments to filter handling in `core/admin.py`.
This commit is contained in:
parent
8a07fc69b1
commit
ce53eec560
5 changed files with 1 additions and 253 deletions
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
|
||||||
from constance.admin import Config
|
from constance.admin import Config
|
||||||
|
|
@ -12,7 +11,6 @@ from modeltranslation.translator import NotRegistered, translator
|
||||||
from modeltranslation.utils import get_translation_fields
|
from modeltranslation.utils import get_translation_fields
|
||||||
from mptt.admin import DraggableMPTTAdmin
|
from mptt.admin import DraggableMPTTAdmin
|
||||||
|
|
||||||
from core.filters import AutocompleteFieldListFilter
|
|
||||||
from core.forms import OrderForm, OrderProductForm, VendorForm
|
from core.forms import OrderForm, OrderProductForm, VendorForm
|
||||||
from core.models import (
|
from core.models import (
|
||||||
Address,
|
Address,
|
||||||
|
|
@ -249,8 +247,6 @@ class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
|
||||||
"stocks__vendor",
|
"stocks__vendor",
|
||||||
"created",
|
"created",
|
||||||
"modified",
|
"modified",
|
||||||
("brand", AutocompleteFieldListFilter),
|
|
||||||
("category", AutocompleteFieldListFilter),
|
|
||||||
)
|
)
|
||||||
search_fields = (
|
search_fields = (
|
||||||
"name",
|
"name",
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.contrib.admin import FieldListFilter
|
from django.core.exceptions import BadRequest
|
||||||
from django.core.exceptions import BadRequest, ValidationError
|
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
Avg,
|
Avg,
|
||||||
Case,
|
Case,
|
||||||
|
|
@ -17,7 +16,6 @@ from django.db.models import (
|
||||||
When,
|
When,
|
||||||
)
|
)
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.forms import Media
|
|
||||||
from django.utils.http import urlsafe_base64_decode
|
from django.utils.http import urlsafe_base64_decode
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_filters import (
|
from django_filters import (
|
||||||
|
|
@ -505,78 +503,3 @@ class AddressFilter(FilterSet):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Address
|
model = Address
|
||||||
fields = ["uuid", "user_uuid", "user_email", "order_by"]
|
fields = ["uuid", "user_uuid", "user_email", "order_by"]
|
||||||
|
|
||||||
|
|
||||||
class AutocompleteFieldListFilter(FieldListFilter):
|
|
||||||
|
|
||||||
template = "admin/autocomplete_filter.html"
|
|
||||||
|
|
||||||
def __init__(self, field, request, params, model, model_admin, field_path):
|
|
||||||
self.lookup_kwarg = f"{field_path}__exact"
|
|
||||||
self.lookup_val = request.GET.get(self.lookup_kwarg)
|
|
||||||
self.field = field
|
|
||||||
self.field_path = field_path
|
|
||||||
self.model = model
|
|
||||||
self.related_model = field.related_model
|
|
||||||
super().__init__(field, request, params, model, model_admin, field_path)
|
|
||||||
|
|
||||||
def has_output(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def choices(self, changelist):
|
|
||||||
choices = []
|
|
||||||
if self.lookup_val:
|
|
||||||
try:
|
|
||||||
obj = self.related_model.objects.get(pk=self.lookup_val)
|
|
||||||
choices.append(
|
|
||||||
{
|
|
||||||
"selected": True,
|
|
||||||
"query_string": changelist.get_query_string(remove=[self.lookup_kwarg]),
|
|
||||||
"display": str(obj),
|
|
||||||
"value": obj.pk,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
except (self.related_model.DoesNotExist, ValidationError):
|
|
||||||
pass
|
|
||||||
return choices
|
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
|
||||||
if self.lookup_val:
|
|
||||||
try:
|
|
||||||
return queryset.filter(**{self.lookup_kwarg: self.lookup_val})
|
|
||||||
except (ValueError, ValidationError):
|
|
||||||
pass
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def expected_parameters(self):
|
|
||||||
return [self.lookup_kwarg]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def media(self):
|
|
||||||
extra = "" if self.used_parameters else ".min"
|
|
||||||
js = [
|
|
||||||
f"admin/js/vendor/jquery/jquery{extra}.js",
|
|
||||||
f"admin/js/vendor/select2/select2.full{extra}.js",
|
|
||||||
"admin/js/autocomplete_filter.js",
|
|
||||||
]
|
|
||||||
css = {
|
|
||||||
"screen": [
|
|
||||||
f"admin/css/vendor/select2/select2{extra}.css",
|
|
||||||
"admin/css/autocomplete_filter.css",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return Media(js=js, css=css)
|
|
||||||
|
|
||||||
def get_context_data(self):
|
|
||||||
field_name = self.field_path.replace("__", "_")
|
|
||||||
related_url = f'/admin/{self.related_model._meta.app_label}/{self.related_model._meta.model_name}/autocomplete/'
|
|
||||||
|
|
||||||
return {
|
|
||||||
'field_name': field_name,
|
|
||||||
'lookup_kwarg': self.lookup_kwarg,
|
|
||||||
'lookup_val': self.lookup_val or '',
|
|
||||||
'related_url': related_url,
|
|
||||||
'placeholder': _('Search %s...') % self.related_model._meta.verbose_name,
|
|
||||||
'clear_text': _('Clear'),
|
|
||||||
'field_verbose_name': self.field.verbose_name or self.field.name.replace('_', ' ').title(),
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
.autocomplete-filter-widget {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
background: #f9f9f9;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.autocomplete-filter-widget h3 {
|
|
||||||
margin: 0 0 8px 0;
|
|
||||||
font-size: 11px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.autocomplete-filter-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.autocomplete-input {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.autocomplete-clear {
|
|
||||||
background: #ba2121;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 11px;
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.autocomplete-clear:hover {
|
|
||||||
background: #a41e1e;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container .select2-selection--single {
|
|
||||||
height: 28px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container .select2-selection--single .select2-selection__rendered {
|
|
||||||
line-height: 26px;
|
|
||||||
padding-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container .select2-selection--single .select2-selection__arrow {
|
|
||||||
height: 26px;
|
|
||||||
right: 4px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
(function ($) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
$('.autocomplete-filter-widget').each(function () {
|
|
||||||
let $widget = $(this);
|
|
||||||
let $input = $widget.find('.autocomplete-input');
|
|
||||||
let $hiddenInput = $widget.find('.autocomplete-hidden');
|
|
||||||
let $clearBtn = $widget.find('.autocomplete-clear');
|
|
||||||
let relatedUrl = $input.data('related-url');
|
|
||||||
let placeholder = $input.attr('placeholder');
|
|
||||||
|
|
||||||
// Initialize Select2
|
|
||||||
$input.select2({
|
|
||||||
ajax: {
|
|
||||||
url: relatedUrl,
|
|
||||||
dataType: 'json',
|
|
||||||
delay: 250,
|
|
||||||
data: function (params) {
|
|
||||||
return {
|
|
||||||
term: params.term,
|
|
||||||
page: params.page || 1
|
|
||||||
};
|
|
||||||
},
|
|
||||||
processResults: function (data, params) {
|
|
||||||
params.page = params.page || 1;
|
|
||||||
return {
|
|
||||||
results: data.results.map(function (item) {
|
|
||||||
return {
|
|
||||||
id: item.id,
|
|
||||||
text: item.text
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
pagination: {
|
|
||||||
more: data.pagination && data.pagination.more
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
cache: true
|
|
||||||
},
|
|
||||||
placeholder: placeholder,
|
|
||||||
allowClear: true,
|
|
||||||
minimumInputLength: 1,
|
|
||||||
width: '250px'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle selection
|
|
||||||
$input.on('select2:select', function (e) {
|
|
||||||
let data = e.params.data;
|
|
||||||
$hiddenInput.val(data.id);
|
|
||||||
updateUrl();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle clearing
|
|
||||||
$input.on('select2:clear', function (e) {
|
|
||||||
$hiddenInput.val('');
|
|
||||||
updateUrl();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear button functionality
|
|
||||||
$clearBtn.on('click', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$input.val(null).trigger('change');
|
|
||||||
$hiddenInput.val('');
|
|
||||||
updateUrl();
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateUrl() {
|
|
||||||
let currentUrl = new URL(window.location);
|
|
||||||
let lookupKwarg = $hiddenInput.data('lookup-kwarg');
|
|
||||||
let value = $hiddenInput.val();
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
currentUrl.searchParams.set(lookupKwarg, value);
|
|
||||||
} else {
|
|
||||||
currentUrl.searchParams.delete(lookupKwarg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset to first page when filtering
|
|
||||||
currentUrl.searchParams.delete('p');
|
|
||||||
|
|
||||||
window.location.href = currentUrl.toString();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})(django.jQuery);
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
<div class="autocomplete-filter-widget">
|
|
||||||
<h3>{% trans "By" %} {{ field_verbose_name }}</h3>
|
|
||||||
<div class="autocomplete-filter-controls">
|
|
||||||
<select class="autocomplete-input"
|
|
||||||
data-related-url="{{ related_url }}"
|
|
||||||
placeholder="{{ placeholder }}">
|
|
||||||
{% if lookup_val %}
|
|
||||||
{% for choice in choices %}
|
|
||||||
{% if choice.selected %}
|
|
||||||
<option value="{{ choice.value }}" selected>{{ choice.display }}</option>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
</select>
|
|
||||||
<input type="hidden"
|
|
||||||
class="autocomplete-hidden"
|
|
||||||
data-lookup-kwarg="{{ lookup_kwarg }}"
|
|
||||||
value="{{ lookup_val }}">
|
|
||||||
{% if lookup_val %}
|
|
||||||
<a href="#" class="autocomplete-clear">{{ clear_text }}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
Loading…
Reference in a new issue