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.Nazione) class NazioneAdmin(admin.ModelAdmin): # resource = resources.NazioneResource # list_per_page = 15 # paginator = CachingPaginator # show_full_result_count = False pass