uso il submodule
parent
93ab4aace2
commit
73a818b87c
|
|
@ -0,0 +1,6 @@
|
|||
[submodule "django/django-drilldown-autocomplete"]
|
||||
path = django/django-drilldown-autocomplete
|
||||
url = https://git.briq.it/Guido/django-drilldown-autocomplete
|
||||
[submodule "django/drilldown_autocomplete"]
|
||||
path = django/drilldown_autocomplete
|
||||
url = https://git.briq.it/Guido/django-drilldown-autocomplete
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
from dati_geo_app.admin import (AjaxAutocompleteListFilterModelAdmin,
|
||||
RicercaOrdinataMixin)
|
||||
from drilldown_autocomplete import DrillDownAutocompleteAdmin
|
||||
from import_export.admin import ImportExportModelAdmin
|
||||
from polymorphic.admin import (PolymorphicChildModelAdmin,
|
||||
PolymorphicChildModelFilter,
|
||||
|
|
@ -13,7 +14,6 @@ from django.core.paginator import Paginator
|
|||
from django.db.models import F
|
||||
|
||||
from . import models
|
||||
from .drilldown_autocomplete import DrillDownAutocompleteAdmin
|
||||
|
||||
# Modified version of a GIST I found in a SO thread
|
||||
# cfr. http://masnun.rocks/2017/03/20/django-admin-expensive-count-all-queries/
|
||||
|
|
|
|||
|
|
@ -1,184 +0,0 @@
|
|||
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, Max, Q, Value, When
|
||||
from django.http import Http404, JsonResponse
|
||||
from django.urls import path
|
||||
|
||||
|
||||
class DrillDownAutocompleteJsonView(AutocompleteJsonView):
|
||||
"""Handle AutocompleteWidget's AJAX requests for data."""
|
||||
admin_context = None
|
||||
|
||||
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)
|
||||
|
||||
if 'filtered_by_dict' in request.GET:
|
||||
try:
|
||||
filtered_by_dict = json.loads(
|
||||
request.GET.get("filtered_by_dict"))
|
||||
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_context.get_drilldown_autocomplete_fields(request)[source_field.name]
|
||||
filtered_by = set(drilldown_field['filtered_by'])
|
||||
except KeyError as e:
|
||||
raise PermissionDenied from e
|
||||
if (set(filtered_by_dict.keys()) > filtered_by):
|
||||
raise PermissionDenied
|
||||
rel_paths = drilldown_field['relationship_path']
|
||||
target_model = source_field.remote_field.model
|
||||
try:
|
||||
for v in rel_paths.values():
|
||||
target_model._meta.get_field(v)
|
||||
except FieldDoesNotExist as e:
|
||||
raise PermissionDenied from e
|
||||
self.target_model = target_model
|
||||
self.filtered_by_dict = filtered_by_dict
|
||||
self.drilldown_field = drilldown_field
|
||||
self.drilldown_filter_data = {
|
||||
rel_paths[k]: v for k, v in filtered_by_dict.items()}
|
||||
|
||||
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()
|
||||
if hasattr(self, 'filtered_by_dict'):
|
||||
dd_field = self.drilldown_field
|
||||
drilldown_filter_conditions = Q(**self.drilldown_filter_data)
|
||||
if dd_field.get('autoselect_on_singleton', False):
|
||||
self.autoselect = False
|
||||
if dd_field.get('included_only', False):
|
||||
qs = qs.filter(drilldown_filter_conditions).annotate(
|
||||
ddok=Value(1))
|
||||
else:
|
||||
qs = qs.annotate(ddok=Max(Case(When(drilldown_filter_conditions, then=Value(
|
||||
1)), default=Value(0)))).order_by('-ddok', *qs.query.order_by)
|
||||
if not getattr(self, 'autoselect', True) and qs.filter(ddok=1).count() == 1:
|
||||
self.autoselect = True
|
||||
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.
|
||||
"""
|
||||
out = super().serialize_result(obj, to_field_name)
|
||||
if hasattr(obj, 'ddok'):
|
||||
out['ddok'] = obj.ddok
|
||||
if getattr(self, 'autoselect', False) and obj.ddok == 1:
|
||||
out['autoselect'] = True
|
||||
return out
|
||||
|
||||
|
||||
class DrillDownAutocompleteMixin(AutocompleteMixin):
|
||||
url_name = "%s:drilldown_autocomplete"
|
||||
|
||||
|
||||
class DrillDownAutocompleteSelect(AutocompleteSelect, DrillDownAutocompleteMixin):
|
||||
pass
|
||||
|
||||
|
||||
class DrillDownAutocompleteAdmin(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:
|
||||
def get_fields_to_reset_recursive(daf, fields_to_reset, seen=None):
|
||||
to_reset = set()
|
||||
if seen is None:
|
||||
seen = set()
|
||||
for f in fields_to_reset:
|
||||
if f in daf:
|
||||
to_reset.add(f)
|
||||
if f not in seen and 'reset_on_reset' in daf[f]:
|
||||
seen.add(f)
|
||||
to_reset.update(get_fields_to_reset_recursive(
|
||||
daf, daf[f]['reset_on_reset'], seen))
|
||||
return to_reset
|
||||
|
||||
attrs = {'data-drilldown_enabled': 1}
|
||||
if 'filtered_by' in daf[db_field.name]:
|
||||
attrs["data-filtered_by"] = json.dumps(
|
||||
list(daf[db_field.name]['filtered_by']))
|
||||
if daf[db_field.name].get('included_only', False):
|
||||
attrs['data-included_only'] = 1
|
||||
else:
|
||||
if 'reset_on_excluded' in daf[db_field.name]:
|
||||
reset_on_excluded = set(
|
||||
daf[db_field.name]['reset_on_excluded'])
|
||||
reset_on_excluded = get_fields_to_reset_recursive(
|
||||
daf, reset_on_excluded)
|
||||
attrs['data-reset_on_excluded'] = json.dumps(
|
||||
list(reset_on_excluded))
|
||||
|
||||
if 'reset_on_included' in daf[db_field.name]:
|
||||
reset_on_included = set(
|
||||
daf[db_field.name]['reset_on_included'])
|
||||
reset_on_included = get_fields_to_reset_recursive(
|
||||
daf, reset_on_included)
|
||||
attrs['data-reset_on_included'] = json.dumps(
|
||||
list(reset_on_included))
|
||||
kwargs["widget"] = DrillDownAutocompleteSelect(
|
||||
db_field, self.admin_site, attrs=attrs, 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, admin_context=self)(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
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
.select2-container--admin-autocomplete .select2-results__option[aria-selected=true].drilldown_ok:not(:hover) {
|
||||
background-color: var(--selected-row);
|
||||
color: var(--body-fg);
|
||||
}
|
||||
|
||||
.select2-container--admin-autocomplete .select2-results__option[aria-selected=false].drilldown_ok:not(.select2-results__option--highlighted):not(:hover) {
|
||||
background-color: var(--body-bg);
|
||||
color: var(--body-fg);
|
||||
}
|
||||
.select2-container--admin-autocomplete .select2-results__option[aria-selected].drilldown_ko:not(:hover) {
|
||||
background-color: #888;
|
||||
color: var(--body-fg);
|
||||
}
|
||||
|
||||
span.drilldown_ko {
|
||||
font-style: italic;
|
||||
}
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
'use strict';
|
||||
{
|
||||
const $ = django.jQuery;
|
||||
|
||||
$.fn.djangoAdminSelect2 = function() {
|
||||
$.each(this, function(i, element) {
|
||||
const select2_config = {};
|
||||
let filtered_by_fields;
|
||||
let reset_on_something = false;
|
||||
const reset_on_excluded = [];
|
||||
const reset_on_included = [];
|
||||
|
||||
if (element.dataset.hasOwnProperty('drilldown_enabled')) {
|
||||
|
||||
if (element.dataset.hasOwnProperty('reset_on_included')) {
|
||||
reset_on_included.push(...JSON.parse(element.dataset.reset_on_included));
|
||||
reset_on_something = true;
|
||||
}
|
||||
|
||||
if (element.dataset.hasOwnProperty('filtered_by')) {
|
||||
filtered_by_fields = JSON.parse(element.dataset.filtered_by);
|
||||
if (element.dataset.hasOwnProperty('reset_on_excluded')) {
|
||||
reset_on_excluded.push(...JSON.parse(element.dataset.reset_on_excluded));
|
||||
reset_on_something = true;
|
||||
}
|
||||
|
||||
}
|
||||
if (reset_on_something) {
|
||||
element.drilldown_items = {};
|
||||
}
|
||||
|
||||
select2_config.templateResult = (item, container)=>{
|
||||
let styleClass = '';
|
||||
|
||||
element.classList.add('drilldown');
|
||||
|
||||
if (item.ddok === 1) {
|
||||
styleClass = 'drilldown_ok';
|
||||
} else {
|
||||
styleClass = 'drilldown_ko';
|
||||
}
|
||||
|
||||
container.classList.add(styleClass);
|
||||
if (reset_on_something) {
|
||||
element.drilldown_items[item.id] = item;
|
||||
}
|
||||
|
||||
if (item.hasOwnProperty('autoselect')) {
|
||||
const includedOnly = element.dataset.included_only ?? false;
|
||||
const dataLength = $(element).select2('data').length;
|
||||
|
||||
if (includedOnly || dataLength == 0) {
|
||||
$(element).select2("trigger", "select", {
|
||||
data: item
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return $(`<span class="${styleClass}"></span>`).text(item.text);
|
||||
}
|
||||
}
|
||||
|
||||
select2_config.ajax = {
|
||||
data: (params)=>{
|
||||
const {fieldName, appLabel, modelName} = element.dataset;
|
||||
const out = {
|
||||
term: params.term,
|
||||
page: params.page,
|
||||
app_label: appLabel,
|
||||
model_name: modelName,
|
||||
field_name: fieldName
|
||||
};
|
||||
|
||||
if (element.dataset.hasOwnProperty('drilldown_enabled')) {
|
||||
|
||||
if (reset_on_something && (params.page ?? 1) < 2) {
|
||||
element.drilldown_items = {};
|
||||
}
|
||||
|
||||
if (element.dataset.hasOwnProperty('filtered_by')) {
|
||||
|
||||
const filtered_by_dict = {};
|
||||
let some_obj = false;
|
||||
|
||||
for (let i in filtered_by_fields) {
|
||||
if (filtered_by_fields.hasOwnProperty(i)) {
|
||||
const filtering_field = filtered_by_fields[i];
|
||||
const field_id = element.dataset.select2Id;
|
||||
const filtering_field_id = `${field_id.slice(0, field_id.length - fieldName.length)}${filtering_field}`;
|
||||
const filtering_value = document.querySelector(`[data-select2-id="${filtering_field_id}"].admin-autocomplete`).value;
|
||||
|
||||
if (filtering_value !== '') {
|
||||
filtered_by_dict[filtering_field] = filtering_value;
|
||||
some_obj = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (some_obj) {
|
||||
out.filtered_by_dict = JSON.stringify(filtered_by_dict);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
$(element).select2(select2_config);
|
||||
|
||||
if (reset_on_something) {
|
||||
$(element).on('select2:select', (ev) => {
|
||||
const data = $(element).select2('data');
|
||||
|
||||
if (element.hasOwnProperty("drilldown_items") && Array.isArray(data) && data.length > 0) {
|
||||
const item = element.drilldown_items[data[0].id];
|
||||
const ddok = item !== undefined ? item.ddok : undefined;
|
||||
|
||||
if (ddok !== undefined) {
|
||||
let all_fields_to_reset = ddok ? reset_on_included : reset_on_excluded;
|
||||
|
||||
if (all_fields_to_reset.length > 0) {
|
||||
all_fields_to_reset.forEach((field_to_reset)=>{
|
||||
const field_name = element.dataset.fieldName;
|
||||
const field_id = element.dataset.select2Id;
|
||||
const id_to_reset = `${field_id.slice(0, field_id.length - field_name.length)}${field_to_reset}`;
|
||||
const element_to_reset = document.querySelector(`#${id_to_reset}.admin-autocomplete`);
|
||||
$(element_to_reset).val(null).trigger('change');
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
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