sangue/django/dati_geo_app/admin.py

325 lines
12 KiB
Python

from django.contrib import admin
from django import forms
from django.core.cache import cache
from django.core.paginator import Paginator
from django.db.models import Count, Value, Case, When, Value
from django.db.models.functions import Concat
from . import models
from . import autocomplete_extras
from djaa_list_filter.admin import AjaxAutocompleteListFilterModelAdmin
from django.contrib.admin.views.main import ChangeList
# Modified version of a GIST I found in a SO thread
# cfr. http://masnun.rocks/2017/03/20/django-admin-expensive-count-all-queries/
class CachingPaginator(Paginator):
def _get_count(self):
if not hasattr(self, "_count"):
self._count = None
if self._count is None:
try:
key = "adm:{0}:count".format(
hash(self.object_list.query.__str__()))
self._count = cache.get(key, -1)
if self._count == -1:
self._count = super().count
cache.set(key, self._count, 60)
except:
self._count = len(self.object_list)
return self._count
count = property(_get_count)
def prefix_match_len(str1, str2):
tot = 0
for char1, char2 in zip(str1, str2):
if char1 == char2:
tot += 1
else:
break
return tot
class RicercaOrdinataChangeList(ChangeList):
# super() è django.contrib.admin.views.main.ChangeList
def get_queryset(self, request):
qs = super().get_queryset(request)
if not(hasattr(request, 'search_term') and hasattr(request, 'disable_default_ordering') and request.disable_default_ordering):
return qs
search_term_cf = request.search_term.casefold()
tok_q = set(x.strip()
for x in search_term_cf.split(' ') if x.strip())
search_cache = {x.pk: str(x) for x in qs}
scores = []
l_s_t = len(search_term_cf) or 1
for obj_pk, obj_str in search_cache.items():
m_l = prefix_match_len(
obj_str.casefold(), search_term_cf)
score_non_tok = m_l / max(l_s_t, len(obj_str))
tok = set(x.strip().casefold()
for x in obj_str.split(' ') if x.strip())
if tok and tok_q:
score_tok = sum(prefix_match_len(token1, token2) / len(token1)
for token1 in tok_q
for token2 in tok) / (len(tok_q) + len(tok))
else:
score_tok = 0
scores.append((-score_non_tok, -score_tok, obj_pk))
scores.sort()
rank_by_pk = {obj_pk: rank for rank,
(_, _, obj_pk) in enumerate(scores)}
whens = [When(pk=-1, then=None)]
for obj_pk, rank in rank_by_pk.items():
whens.append(When(pk=obj_pk, then=Value(rank)))
qs = qs.annotate(briq_search_results_rank=Case(*whens, default=None))
ordering = self.get_ordering(request,qs)
qs = qs.order_by('briq_search_results_rank',*ordering)
return qs
class RicercaOrdinataMixin(admin.ModelAdmin):
def get_changelist(self, *args, **kwargs):
return RicercaOrdinataChangeList
def get_search_results(self, request, qs, search_term):
qs, may_have_duplicates = super().get_search_results(
request, qs, search_term,
)
if search_term and qs:
request.disable_default_ordering = True
request.search_term = search_term
if not request.resolver_match._func_path.split('.')[-1].lower().startswith('changelist'):
search_term_cf = request.search_term.casefold()
tok_q = set(x.strip()
for x in search_term_cf.split(' ') if x.strip())
search_cache = {x.pk: str(x) for x in qs}
scores = []
l_s_t = len(search_term_cf) or 1
for obj_pk, obj_str in search_cache.items():
m_l = prefix_match_len(
obj_str.casefold(), search_term_cf)
score_non_tok = m_l / max(l_s_t, len(obj_str))
tok = set(x.strip().casefold()
for x in obj_str.split(' ') if x.strip())
if tok and tok_q:
score_tok = sum(prefix_match_len(token1, token2) / len(token1)
for token1 in tok_q
for token2 in tok) / (len(tok_q) + len(tok))
else:
score_tok = 0
scores.append((-score_non_tok, -score_tok, obj_pk))
scores.sort()
rank_by_pk = {obj_pk: rank for rank,
(_, _, obj_pk) in enumerate(scores)}
whens = [When(pk=-1, then=None)]
for obj_pk, rank in rank_by_pk.items():
whens.append(When(pk=obj_pk, then=Value(rank)))
qs = qs.annotate(briq_search_results_rank=Case(*whens, default=None))
qs = qs.order_by('briq_search_results_rank')
return qs, may_have_duplicates
# # Main reusable Admin class for only viewing
# class ViewAdminMixin(admin.ModelAdmin):
# def has_add_permission(self, request):
# return False
#
# def has_change_permission(self, request, obj=None):
# return False
#
# def has_delete_permission(self, request, obj=None):
# return False
@admin.register(models.Comune)
class ComuneAdmin(RicercaOrdinataMixin, AjaxAutocompleteListFilterModelAdmin):
list_per_page = 15
paginator = CachingPaginator
show_full_result_count = False
fields=('nome','provincia','cap','codice_istat','codice_nazionale',)
search_fields = ('nome','cap__codice__exact','codice_istat__exact','codice_nazionale__iexact',)
autocomplete_fields = ('provincia','cap',)
list_display = ('nome', 'provincia','regione_key','lista_cap',)
ordering = ('nome','provincia_id',)
autocomplete_list_filter = ('provincia',)
list_filter = ('provincia__regione_id',)
def regione_key(self, obj):
return obj.provincia.regione_id
regione_key.short_description = 'Regione'
regione_key.admin_order_field = 'regione_id'
def get_queryset(self, request):
queryset = super().get_queryset(request).prefetch_related('provincia').prefetch_related('cap')
return queryset
def lista_cap(self,obj):
lista = sorted((c.codice for c in obj.cap.all()),key=int)
aggr_cap=''
if len(lista) > 0:
gruppi = [[lista[0]]*2]
for i in lista[1:]:
if int(i)-1 == int(gruppi[-1][1]):
gruppi[-1][1] = i
else:
gruppi.append([i]*2)
aggr_cap=', '.join(f'{str(g[0])}-{str(g[1])}' if g[1]!=g[0] else str(g[0]) for g in gruppi)
return aggr_cap
# from django.db.models.fields.related import ManyToManyRel
# import django.db.models as dj_models
# asd = dj_models.ManyToManyField(models.Comune, through=models.Comune.cap.through)
# asd.model = models.CAP
# asd.name = "comuni2"
# asd.remote_field = models.Comune.cap.through._meta.get_field('comune')
# models.CAP._meta.add_field(asd)
class CAPAdminForm(forms.ModelForm):
#asd = ManyToManyRel(models.Comune.cap, to=models.Comune, related_name="asd",through=models.Comune.cap.through, related_query_name="comuni")
comuni = forms.ModelMultipleChoiceField(
models.Comune.objects.all(),
#widget=admin.widgets.AutocompleteSelectMultiple(models.CAP._meta.get_field("comuni2"), admin_site= None),
widget=admin.widgets.FilteredSelectMultiple('Comune', False),
required=False,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#print(self.base_fields)
if self.instance.pk:
self.initial['comuni'] = self.instance.comuni.values_list('pk', flat=True)
def save(self, *args, **kwargs):
instance = super().save(*args, **kwargs)
if instance.pk:
instance.comuni.clear()
instance.comuni.add(*self.cleaned_data['comuni'])
return instance
class Meta:
model = models.CAP
fields = "__all__"
@admin.register(models.CAP)
class CAPAdmin(RicercaOrdinataMixin, AjaxAutocompleteListFilterModelAdmin):
list_per_page = 15
paginator = CachingPaginator
show_full_result_count = False
fields=('codice', 'comuni')
search_fields=('codice','comuni__nome__iexact')
ordering = ('codice',)
list_display = ('codice','numero_comuni','lista_comuni',)
list_filter = ('comuni__provincia__regione_id',)
form = CAPAdminForm
# def get_form(self, request, obj=None, change=False, **kwargs):
# form = super().get_form(request, obj, change, **kwargs)
# form.base_fields['comuni'].widget.admin_site = self.admin_site
# return form
def get_queryset(self, request):
queryset = super().get_queryset(request).prefetch_related('comuni')
queryset = queryset.annotate(
conteggio_comuni=Count('comuni')
)
return queryset
def numero_comuni(self, obj):
return obj.conteggio_comuni
numero_comuni.admin_order_field = 'conteggio_comuni'
def lista_comuni(self,obj):
return ', '.join(str(c) for c in obj.comuni.all())
@admin.register(models.Provincia)
class ProvinciaAdmin(RicercaOrdinataMixin, admin.ModelAdmin):
list_per_page = 15
paginator = CachingPaginator
show_full_result_count = False
search_fields = ('sigla', 'nome_esteso',)
list_display = ('nome_esteso', 'sigla', 'numero_comuni', 'regione_key',)
list_filter = ('regione_id',)
#ordering = ('regione_id', 'nome_esteso',)
def regione_key(self, obj):
return obj.regione_id
regione_key.short_description = 'Regione'
regione_key.admin_order_field = 'regione_id'
def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
conteggio_comuni=Count('comuni')
)
return queryset
def numero_comuni(self, obj):
return obj.conteggio_comuni
numero_comuni.admin_order_field = 'conteggio_comuni'
@admin.register(models.Regione)
class RegioneAdmin(RicercaOrdinataMixin, admin.ModelAdmin):
# list_per_page = 15
# paginator = CachingPaginator
# show_full_result_count = False
search_fields = ('nome',)
list_display = ('nome', 'numero_province',)
ordering = ('nome',)
def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
conteggio_province=Count('province')
)
return queryset
def numero_province(self, obj):
return obj.conteggio_province
numero_province.admin_order_field = 'conteggio_province'
@admin.register(models.DUG)
class DUGAdmin(RicercaOrdinataMixin, admin.ModelAdmin):
list_per_page = 15
paginator = CachingPaginator
show_full_result_count = False
fields=('nome',)
search_fields=('nome',)
ordering = ('nome',)
list_display = ('nome',)
@admin.register(models.Indirizzo)
class IndirizzoAdmin(RicercaOrdinataMixin, AjaxAutocompleteListFilterModelAdmin):
list_per_page = 15
paginator = CachingPaginator
show_full_result_count = False
fields=('dug','duf','civico','cap','comune','altro',)
search_fields = ('indirizzo','cap_id__exact',)
autocomplete_fields = ('dug','cap','comune',)
list_display = ('indirizzo', 'cap','comune','provincia','regione',)
ordering = ('comune_id','duf','civico',)
autocomplete_list_filter = ('comune',)
list_filter = ('comune__provincia__regione_id',)
def get_queryset(self, request):
queryset = super().get_queryset(request).select_related('comune__provincia__regione').annotate(indirizzo=Concat('dug_id',Value(' '),'duf',Value(' '),'civico'))
return queryset
def provincia(self, obj):
return obj.comune.provincia
provincia.short_description = 'Provincia'
provincia.admin_order_field = 'comune__provincia_id'
def regione(self, obj):
return obj.comune.provincia.regione_id
regione.short_description = 'Regione'
regione.admin_order_field = 'comune__provincia__regione_id'
def indirizzo(self, obj):
return obj.indirizzo
indirizzo.short_description = 'Indirizzo'
indirizzo.admin_order_field = ('comune_id','duf','civico',)