drilldown autocomplete
parent
3832f7b367
commit
3054cc6054
|
|
@ -142,9 +142,33 @@ class EmailAdmin(HiddenModel, PolymorphicParentModelAdmin, PolymorphicChildModel
|
|||
|
||||
|
||||
class RecapitoInline(StackedPolymorphicInline):
|
||||
class IndirizzoInline(StackedPolymorphicInline.Child):
|
||||
class IndirizzoInline(StackedPolymorphicInline.Child, DrillDownAutocompleteModelAdmin):
|
||||
model = models.Indirizzo
|
||||
autocomplete_fields = ('dug','comune','cap','nazione',)
|
||||
drilldown_autocomplete_fields = {
|
||||
'cap': {
|
||||
'linked': {
|
||||
'comune': 'comuni',
|
||||
},
|
||||
'reset_on_included': {},
|
||||
'reset_on_excluded': {},
|
||||
'reset_on_reset': {},
|
||||
'autoupdate_on_reset': False,
|
||||
'autoselect_on_singleton': False,
|
||||
'included_only': False,
|
||||
},
|
||||
'comune': {
|
||||
'linked': {
|
||||
'cap': 'cap',
|
||||
},
|
||||
'reset_on_included': {},
|
||||
'reset_on_excluded': {},
|
||||
'reset_on_reset': {},
|
||||
'autoupdate_on_reset': False,
|
||||
'autoselect_on_singleton': False,
|
||||
'included_only': False,
|
||||
}
|
||||
}
|
||||
|
||||
class SedeInline(StackedPolymorphicInline.Child):
|
||||
model = models.Sede
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
import json
|
||||
from functools import update_wrapper
|
||||
|
||||
from django.apps import apps
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.views.autocomplete import AutocompleteJsonView
|
||||
from django.contrib.admin.widgets import AutocompleteMixin, AutocompleteSelect
|
||||
from django.core.exceptions import FieldDoesNotExist, PermissionDenied
|
||||
from django.db.models import Case, Q, Value, When
|
||||
from django.http import Http404, JsonResponse
|
||||
from django.urls import path
|
||||
|
||||
|
||||
class DrillDownAutocompleteJsonView(AutocompleteJsonView):
|
||||
"""Handle AutocompleteWidget's AJAX requests for data."""
|
||||
|
||||
def process_request(self, request):
|
||||
"""
|
||||
Validate request integrity, extract and return request parameters.
|
||||
|
||||
Since the subsequent view permission check requires the target model
|
||||
admin, which is determined here, raise PermissionDenied if the
|
||||
requested app, model or field are malformed.
|
||||
|
||||
Raise Http404 if the target model admin is not configured properly with
|
||||
search_fields.
|
||||
"""
|
||||
(
|
||||
term,
|
||||
model_admin,
|
||||
source_field,
|
||||
to_field_name
|
||||
) = super().process_request(request)
|
||||
linkedfields = request.GET.get("linkedfields")
|
||||
if linkedfields:
|
||||
try:
|
||||
linkedfields = json.loads(linkedfields)
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
raise PermissionDenied from e
|
||||
app_label = request.GET["app_label"]
|
||||
model_name = request.GET["model_name"]
|
||||
source_model = apps.get_model(app_label, model_name)
|
||||
try:
|
||||
drilldown_field = self.admin_site._registry[
|
||||
source_model].get_drilldown_autocomplete_fields(request)[source_field.name]
|
||||
except KeyError as e:
|
||||
raise PermissionDenied from e
|
||||
remote_model = source_field.remote_field.model
|
||||
try:
|
||||
for v in drilldown_field['linked'].values():
|
||||
remote_model._meta.get_field(v)
|
||||
except FieldDoesNotExist as e:
|
||||
raise PermissionDenied from e
|
||||
self.remote_model = remote_model
|
||||
self.linkedfields = linkedfields
|
||||
self.drilldown_filter_data = {
|
||||
v: linkedfields[k] for k, v in drilldown_field['linked'].items() if k in linkedfields}
|
||||
|
||||
return term, model_admin, source_field, to_field_name
|
||||
|
||||
def get_queryset(self):
|
||||
"""Return queryset based on ModelAdmin.get_search_results()."""
|
||||
qs = super().get_queryset().only()
|
||||
# print('Prima:',qs.query,'\n\n')
|
||||
if hasattr(self, 'linkedfields'):
|
||||
drilldown_filter_conditions = Q(**self.drilldown_filter_data)
|
||||
qs = qs.annotate(ddok=Case(When(drilldown_filter_conditions, then=Value(
|
||||
1)), default=Value(0))).order_by('-ddok', *qs.query.order_by)
|
||||
# print('Dopo:',qs.query,'\n\n')
|
||||
else:
|
||||
qs = qs.annotate(ddok=Value(1))
|
||||
return qs
|
||||
|
||||
def serialize_result(self, obj, to_field_name):
|
||||
"""
|
||||
Convert the provided model object to a dictionary that is added to the
|
||||
results list.
|
||||
"""
|
||||
return {"id": str(getattr(obj, to_field_name)), "text": str(obj), "ddok": obj.ddok}
|
||||
|
||||
|
||||
class DrillDownAutocompleteMixin(AutocompleteMixin):
|
||||
url_name = "%s:drilldown_autocomplete"
|
||||
|
||||
|
||||
class DrillDownAutocompleteSelect(AutocompleteSelect, DrillDownAutocompleteMixin):
|
||||
pass
|
||||
|
||||
|
||||
class DrillDownAutocompleteModelAdmin(admin.options.BaseModelAdmin):
|
||||
drilldown_autocomplete_fields = dict()
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('admin/css/drilldown_autocomplete.css',)
|
||||
}
|
||||
|
||||
def get_drilldown_autocomplete_fields(self, request):
|
||||
return self.drilldown_autocomplete_fields
|
||||
|
||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||
"""
|
||||
Get a form Field for a ForeignKey.
|
||||
"""
|
||||
db = kwargs.get("using")
|
||||
|
||||
if "widget" not in kwargs:
|
||||
daf = self.get_drilldown_autocomplete_fields(request)
|
||||
if db_field.name in daf:
|
||||
kwargs["widget"] = DrillDownAutocompleteSelect(
|
||||
db_field, self.admin_site, attrs={
|
||||
"data-linkedfields": json.dumps(list(daf[db_field.name]['linked'].keys())),
|
||||
}, using=db
|
||||
)
|
||||
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||
|
||||
def drilldown_autocomplete_view(self, request):
|
||||
return DrillDownAutocompleteJsonView.as_view(admin_site=self.admin_site)(request)
|
||||
|
||||
def get_urls(self):
|
||||
def wrap(view, cacheable=False):
|
||||
def wrapper(*args, **kwargs):
|
||||
return self.admin_site.admin_view(view, cacheable)(*args, **kwargs)
|
||||
|
||||
wrapper.admin_site = self
|
||||
return update_wrapper(wrapper, view)
|
||||
|
||||
urls = super().get_urls()
|
||||
drilldown_urls = [
|
||||
path("drilldown/", wrap(self.drilldown_autocomplete_view),
|
||||
name="drilldown_autocomplete"),
|
||||
]
|
||||
return drilldown_urls + urls
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
.select2-container--admin-autocomplete .select2-results__option[aria-selected=true].drilldown_ok:not(:hover) {
|
||||
background-color: var(--selected-row);
|
||||
}
|
||||
|
||||
.select2-container--admin-autocomplete .select2-results__option[aria-selected=false].drilldown_ok:not(.select2-results__option--highlighted):not(:hover) {
|
||||
background-color: var(--body-bg);
|
||||
}
|
||||
.select2-container--admin-autocomplete .select2-results__option[aria-selected].drilldown_ko:not(:hover) {
|
||||
background-color: #888
|
||||
}
|
||||
|
||||
span.drilldown_ko {
|
||||
font-style: italic;
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
'use strict';
|
||||
{
|
||||
const $ = django.jQuery;
|
||||
|
||||
$.fn.djangoAdminSelect2 = function() {
|
||||
$.each(this, function(i, element) {
|
||||
$(element).select2({
|
||||
ajax: {
|
||||
data: (params)=>{
|
||||
var out = {
|
||||
term: params.term,
|
||||
page: params.page,
|
||||
app_label: element.dataset.appLabel,
|
||||
model_name: element.dataset.modelName,
|
||||
field_name: element.dataset.fieldName
|
||||
};
|
||||
if (element.dataset.hasOwnProperty('linkedfields')) {
|
||||
var linkedfields = JSON.parse(element.dataset.linkedfields)
|
||||
var linkedfields_obj = {};
|
||||
var some_obj = false;
|
||||
for (var i in linkedfields) {
|
||||
if (linkedfields.hasOwnProperty(i)) {
|
||||
var field = linkedfields[i];
|
||||
var value = document.querySelectorAll('[data-field-name=' + field + '].admin-autocomplete')[0].value;
|
||||
if (value !== '') {
|
||||
//console.log(field + '=' + value);
|
||||
linkedfields_obj[field] = value;
|
||||
some_obj = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (some_obj) {
|
||||
out['linkedfields'] = JSON.stringify(linkedfields_obj);
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
},
|
||||
templateResult: (item,container)=>{
|
||||
var styleClass = '';
|
||||
if (element.dataset.hasOwnProperty('linkedfields')) {
|
||||
element.classList.add('drilldown');
|
||||
if (item.ddok === 1) {
|
||||
styleClass = 'drilldown_ok';
|
||||
} else {
|
||||
styleClass = 'drilldown_ko';
|
||||
}
|
||||
container.classList.add(styleClass);
|
||||
}
|
||||
return $('<span class="' + styleClass + '">' + item.text + '</span>');
|
||||
}
|
||||
});
|
||||
});
|
||||
return this;
|
||||
}
|
||||
;
|
||||
|
||||
$(function() {
|
||||
// Initialize all autocomplete widgets except the one in the template
|
||||
// form used when a new formset is added.
|
||||
$('.admin-autocomplete').not('[name*=__prefix__]').djangoAdminSelect2();
|
||||
});
|
||||
|
||||
document.addEventListener('formset:added', (event)=>{
|
||||
$(event.target).find('.admin-autocomplete').djangoAdminSelect2();
|
||||
}
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue