diff --git a/django/contatti_app/admin.py b/django/contatti_app/admin.py
index 3e856c9..18c9c9b 100644
--- a/django/contatti_app/admin.py
+++ b/django/contatti_app/admin.py
@@ -12,8 +12,9 @@ from django.contrib import admin
from django.core.cache import cache
from django.core.paginator import Paginator
from django.db.models import F
-
-from . import models
+from django.template.response import TemplateResponse
+from django.urls import path
+from . import models, views
# Modified version of a GIST I found in a SO thread
# cfr. http://masnun.rocks/2017/03/20/django-admin-expensive-count-all-queries/
@@ -71,11 +72,43 @@ class CachingPaginator(Paginator):
# def has_delete_permission(self, request, obj=None):
# return False
+class ImportaDaGoogleMixin():
+ change_list_template = 'admin/contatti_app/import_google_contacts_button.html'
+
+ def get_model_info(self):
+ app_label = self.model._meta.app_label
+ return (app_label, self.model._meta.model_name)
+
+ def get_urls(self):
+ info = self.get_model_info()
+ urls = [path('googleimport/',
+ self.admin_site.admin_view(self.googleimport),
+ name='%s_%s_googleimport' % info),
+ path('googleimport/confirm',
+ self.admin_site.admin_view(self.googleimport_confirm),
+ name='%s_%s_googleimport_confirm' % info),]
+ urls += super().get_urls()
+ return urls
+
+ def googleimport(self, request):
+ context = dict( self.admin_site.each_context(request), )
+ if request.method == 'POST' and request.FILES.get('csv_file'):
+ app_label, model_name = self.get_model_info()
+ context.update({'app_label':app_label, 'model_name':model_name})
+ context.update(views.googleimport_preview(request))
+ return TemplateResponse(request, 'admin/contatti_app/import_google_contacts_preview.html', context)
+ else:
+ return TemplateResponse(request, "admin/contatti_app/import_google_contacts.html", context)
+
+ def googleimport_confirm(self, request):
+ print('QUIIIIII')
+ pass
+
# --------------- FINE PREFISSO TEMPLATE ---------------
@admin.register(models.ContattoAziendale)
-class ContattoAziendaleAdmin(ImportExportModelAdmin, AutocompleteAdmin):
+class ContattoAziendaleAdmin(HiddenModel, ImportExportModelAdmin, AutocompleteAdmin):
# resource = resources.ContattoAziendaleResource
# list_per_page = 15
# paginator = CachingPaginator
@@ -84,8 +117,9 @@ class ContattoAziendaleAdmin(ImportExportModelAdmin, AutocompleteAdmin):
list_display = ('persona','azienda','is_personale')
pass
+
@admin.register(models.Recapito)
-class RecapitoAdmin(ImportExportModelAdmin, PolymorphicParentModelAdmin):
+class RecapitoAdmin(ImportaDaGoogleMixin, ImportExportModelAdmin, PolymorphicParentModelAdmin):
# resource = resources.RecapitoResource
# list_per_page = 15
# paginator = CachingPaginator
@@ -201,7 +235,12 @@ class RecapitoInline(StackedPolymorphicInline, DrillDownAutocompleteAdmin):
FaxInline,
)
-
+class AziendaInline(admin.TabularInline):
+ verbose_name_plural = 'Rapporti con aziende'
+ model = models.ContattoAziendale
+ extra = 0
+ autocomplete_fields = ('azienda',)
+
@admin.register(models.PersonaFisica)
class PersonaFisicaAdmin(PolymorphicInlineSupportMixin, PolymorphicChildModelAdmin, StackedInlineCollassati,AutocompleteAdmin):
# resource = resources.PersonaFisicaResource
@@ -211,7 +250,7 @@ class PersonaFisicaAdmin(PolymorphicInlineSupportMixin, PolymorphicChildModelAdm
show_in_index = False
get_model_perms = lambda self, req: {}
search_fields = ('nome','cognome',)
- inlines = (RecapitoInline,)
+ inlines = (RecapitoInline, AziendaInline)
@admin.register(models.PersonaGiuridica)
@@ -282,7 +321,6 @@ class SoggettoContattabileAdmin(PolymorphicParentModelAdmin):
def get_queryset(self, request):
qs=super().get_queryset(request).prefetch_related('polymorphic_ctype')
return qs
- pass
@admin.register(models.Indirizzo)
@@ -335,7 +373,7 @@ class PersonaInline(admin.TabularInline):
extra = 0
@admin.register(models.Societa)
-class SocietaAdmin(ImportExportModelAdmin, AutocompleteAdmin):
+class SocietaAdmin(ImportExportModelAdmin, AutocompleteAdmin, StackedInlineCollassati):
# resource = resources.SocietaResource
# list_per_page = 15
# paginator = CachingPaginator
diff --git a/django/contatti_app/static/admin/css/import_google_contacts.css b/django/contatti_app/static/admin/css/import_google_contacts.css
new file mode 100644
index 0000000..d2b9d3a
--- /dev/null
+++ b/django/contatti_app/static/admin/css/import_google_contacts.css
@@ -0,0 +1,13 @@
+.tabulator-row.tabulator-group.tabulator-group-level-0 {
+ display: flex;
+}
+
+.contatto_group {
+ display: flex;
+ flex-grow: 1;
+ justify-content: flex-end;
+}
+
+.contatto_group input[type="checkbox"] {
+ margin-left: 10px;
+}
\ No newline at end of file
diff --git a/django/contatti_app/static/admin/css/vendor/tabulator b/django/contatti_app/static/admin/css/vendor/tabulator
new file mode 120000
index 0000000..7d33a42
--- /dev/null
+++ b/django/contatti_app/static/admin/css/vendor/tabulator
@@ -0,0 +1 @@
+/home/guido/2_external_repo/tabulator/dist/css
\ No newline at end of file
diff --git a/django/contatti_app/static/admin/js/import_google_contacts.js b/django/contatti_app/static/admin/js/import_google_contacts.js
new file mode 100644
index 0000000..897ecfa
--- /dev/null
+++ b/django/contatti_app/static/admin/js/import_google_contacts.js
@@ -0,0 +1,92 @@
+/* eslint-env browser */
+var svg_new = '';
+var svg_exists = '';
+var svg_ambig_contact = '';
+var svg_email = '';
+var svg_phone = '';
+var svg_new_contact =' ';
+var svg_exists_contact = ' ';
+document.addEventListener("DOMContentLoaded", function () {
+ var table = new Tabulator("#import_preview", {
+ maxHeight: "80vh",
+ groupStartOpen: true,
+ groupToggleElement:"header",
+ columns: [
+ {
+ "title": "Importare?",
+ formatter: "rowSelection", titleFormatter: "rowSelection", titleFormatterParams: {
+ rowRange: "active" //only toggle the values of the active filtered rows
+ }, hozAlign: "center", headerSort: false
+ },
+ {
+ "title": "",
+ "field": "import_status",
+ formatter: function (cell, formatterParams, onRendered) {
+ switch (cell.getValue()) {
+ case true:
+ return svg_new;
+ case false:
+ return svg_exists;
+ case 'warning':
+ return svg_ambig_contact;
+ default:
+ return cell.getValue();
+ }
+ }
+ },
+ {
+ "title": "",
+ "field": "recapito",
+ "minWidth": 40,
+ "hozAlign": "center",
+ "headerWordWrap": false,
+ "resizable": true,
+ "sorter": "string",
+ "headerSort": true,
+ formatter: function (cell, formatterParams, onRendered) {
+ switch (cell.getValue()) {
+ case "email":
+ return svg_email;
+ case "telefono":
+ return svg_phone;
+ default:
+ return cell.getValue();
+ }
+ }
+ }, {
+ "title": "Recapito",
+ "field": "valore",
+ "minWidth": 40,
+ "headerWordWrap": false,
+ "resizable": true,
+ "sorter": "string",
+ "headerSort": true
+ },
+ // {
+ // "title": "Importare?",
+ // "field": "import",
+ // "minWidth": 40,
+ // "headerWordWrap": false,
+ // "resizable": true,
+ // "sorter": "boolean",
+ // formatter: "tickCross",
+ // "headerSort": true
+ // }
+ ],
+ groupBy: function (data) {
+ return data.nome + " " + data.cognome;
+ }
+ });
+ window.table = table;
+ table.on("tableBuilt", function () {
+ table.setData(contatti);
+ table.setGroupHeader(function (value, count, data, group) {
+ //value - the value all members of this group share
+ //count - the number of rows in this group
+ //data - an array of all the row data objects in this group
+ //group - the group component for the group
+ let nc = data[0].nuovo_contatto? svg_new_contact : svg_exists_contact;
+ return nc + ' '+ value + '
' + ' ' + " (" + count + (count > 1 ? " recapiti)" : " recapito)") + '
'; //return the header contents
+ });
+ });
+});
diff --git a/django/contatti_app/static/admin/js/vendor/tabulator b/django/contatti_app/static/admin/js/vendor/tabulator
new file mode 120000
index 0000000..d95acf1
--- /dev/null
+++ b/django/contatti_app/static/admin/js/vendor/tabulator
@@ -0,0 +1 @@
+/home/guido/2_external_repo/tabulator/dist/js
\ No newline at end of file
diff --git a/django/contatti_app/templates/admin/contatti_app/import_google_contacts.html b/django/contatti_app/templates/admin/contatti_app/import_google_contacts.html
new file mode 100644
index 0000000..601df9c
--- /dev/null
+++ b/django/contatti_app/templates/admin/contatti_app/import_google_contacts.html
@@ -0,0 +1,8 @@
+{% extends "admin/base_site.html" %}
+{% block content %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/django/contatti_app/templates/admin/contatti_app/import_google_contacts_button.html b/django/contatti_app/templates/admin/contatti_app/import_google_contacts_button.html
new file mode 100644
index 0000000..fb198ec
--- /dev/null
+++ b/django/contatti_app/templates/admin/contatti_app/import_google_contacts_button.html
@@ -0,0 +1,7 @@
+{% extends "admin/change_list.html" %}
+{% load admin_urls %}
+
+{% block object-tools-items %}
+ Importa contatti da Google
+ {{ block.super }}
+{% endblock %}
diff --git a/django/contatti_app/templates/admin/contatti_app/import_google_contacts_preview.html b/django/contatti_app/templates/admin/contatti_app/import_google_contacts_preview.html
new file mode 100644
index 0000000..f0528d0
--- /dev/null
+++ b/django/contatti_app/templates/admin/contatti_app/import_google_contacts_preview.html
@@ -0,0 +1,27 @@
+{% extends "admin/base_site.html" %}
+{% load admin_urls static %}
+
+{% block content %}
+
+{% endblock %}
+
+{% block extrastyle %}
+{{ block.super }}
+
+
+{% endblock %}
+
+{% block extrahead %}
+{{ block.super }}
+
+
+
+{% endblock %}
+
diff --git a/django/contatti_app/views.py b/django/contatti_app/views.py
index 107e53e..7212773 100644
--- a/django/contatti_app/views.py
+++ b/django/contatti_app/views.py
@@ -1,20 +1,212 @@
-from copy import deepcopy
-
-from django.shortcuts import render, redirect
-from django.http import JsonResponse, HttpResponse
-from django.utils.http import url_has_allowed_host_and_scheme
-from django.contrib.auth.forms import AuthenticationForm
-from django.contrib.auth import authenticate, login, logout
-from rest_framework import viewsets
-from rest_framework.authentication import SessionAuthentication, BasicAuthentication
-from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated
-
-from django_auto_prefetching import AutoPrefetchViewSetMixin
-from . import models
-from . import serializers
-
# def index(request):
# return HttpResponse("Hello, %s!" % (request.user.username if request.user.is_authenticated else 'World'))
+import csv
+import collections
+import json
+import re
+from copy import deepcopy
+from io import TextIOWrapper
+
+from contatti_app.models import Email, PersonaFisica, Telefono, Recapito
+from dati_geo_app.models import CAP, Comune
+from django_auto_prefetching import AutoPrefetchViewSetMixin
+from rest_framework import viewsets
+from rest_framework.authentication import (BasicAuthentication,
+ SessionAuthentication)
+from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated
+
+from django.contrib import messages
+from django.contrib.auth import authenticate, login, logout
+from django.contrib.auth.forms import AuthenticationForm
+from django.core.exceptions import ObjectDoesNotExist
+from django.db.models import Q
+from django.http import HttpResponse, JsonResponse
+from django.shortcuts import redirect, render
+from django.template.response import TemplateResponse
+from django.utils.http import url_has_allowed_host_and_scheme
+
+from . import models, serializers
+
+
+def iterable_but_not_str(obj):
+ return isinstance(obj, collections.abc.Iterable) and not isinstance(obj, (str, bytes))
+
+
+def googleimport_preview(request):
+ def normalizza_generico(*x):
+ while iterable_but_not_str(x):
+ if len(x) == 1:
+ x = x[0]
+ else:
+ return tuple(normalizza_generico(y) for y in x)
+ return x.strip().lower()
+
+ normalizza_soggetto = normalizza_generico
+ normalizza_persona = normalizza_generico
+ normalizza_email = normalizza_generico
+
+ def normalizza_telefono(x):
+ if iterable_but_not_str(x):
+ x = x[0]
+ x=re.sub(r'[^0-9+]', '', x.strip())
+ if x.startswith('00'):
+ x='+'+x[2:]
+ if not x.startswith('+'):
+ x='+39'+x
+ return x
+
+ csv_file = request.FILES['csv_file']
+ csv_file_text = TextIOWrapper(csv_file, encoding='utf-8')
+ reader = csv.DictReader(csv_file_text)
+
+ contacts = []
+
+ recapiti_db = Recapito.objects.select_related(
+ 'polymorphic_ctype',
+ 'soggetto__personafisica',
+ 'soggetto__personagiuridica'
+ ).filter(
+ polymorphic_ctype__model__in = {'email', 'pec', 'telefono', }
+ )
+
+ def new_contatto():
+ return {'emails': set(), 'telefoni': set()}
+
+ contatti_db = collections.defaultdict(
+ lambda: collections.defaultdict(new_contatto))
+ soggetto_pk_by_recapito = collections.defaultdict(set)
+ for recapito_valore in recapiti_db:
+ soggetto = recapito_valore.soggetto
+ soggetto_pk = soggetto.pk
+ tipo_sogg = recapito_valore.soggetto.polymorphic_ctype.name
+ if tipo_sogg == 'persona fisica':
+ nome = soggetto.personafisica.nome
+ cognome = soggetto.personafisica.cognome
+ soggetto_descr = normalizza_persona(nome, cognome)
+ soggetto_descr_alt = normalizza_soggetto(f'{nome} {cognome}')
+ contatto = contatti_db[soggetto_descr]
+ contatti_db[soggetto_descr_alt] = contatto
+ elif tipo_sogg == 'persona giuridica':
+ soggetto_descr = normalizza_soggetto(
+ soggetto.personagiuridica.denominazione)
+ contatto = contatti_db[soggetto_descr]
+ else:
+ raise NotImplementedError
+ contatto_univoco = contatto[soggetto_pk]
+ if recapito_valore.polymorphic_ctype.model in {'email', 'pec', }:
+ recapito_descr = recapito_valore.email.indirizzo_email
+ contatto_univoco['emails'].add(recapito_descr)
+ elif recapito_valore.polymorphic_ctype.model in {'telefono', }:
+ recapito_descr = recapito_valore.telefono.numero
+ contatto_univoco['telefoni'].add(recapito_descr)
+ soggetto_pk_by_recapito[recapito_descr] = soggetto_pk
+ soggetto_pk_by_recapito = dict(soggetto_pk_by_recapito)
+ contatti_db = {k: dict(v) for k, v in contatti_db.items()}
+
+ warning_altro_sogg_emails = set()
+ warning_altro_sogg_telefoni = set()
+ contacts = []
+ for row in reader:
+ nome = row['Given Name']
+ cognome = row['Family Name']
+ contatto = normalizza_persona(nome, cognome)
+ contatto_alt = normalizza_soggetto(row['Name'])
+ soggetti_gia_presenti = {k:v for c in (contatto, contatto_alt) if c in contatti_db for k,v in contatti_db[c].items()}
+ emails = [row[f'E-mail {n} - Value'] for n in range(1, 4)]
+ emails = [normalizza_email(y)
+ for x in emails for y in x.split(':::') if y.strip()]
+ telefoni = [row[f'Phone {n} - Value'] for n in range(1, 4)]
+ telefoni = [normalizza_telefono(y)
+ for x in telefoni for y in x.split(':::') if y.strip()]
+ for recapiti,tipo_recapito in (
+ (emails,'email',),
+ (telefoni,'telefono')
+ ):
+ for recapito_valore in recapiti:
+ contact = dict()
+ contacts.append(contact)
+ contact['recapito']=tipo_recapito
+ contact['nuovo_contatto'] = not soggetti_gia_presenti
+ contact['nome'], contact['cognome'] = nome, cognome
+ contact['full_name'] = contatto_alt
+ contact['valore']=recapito_valore
+ if recapito_valore in soggetto_pk_by_recapito:
+ pk_recapito = soggetto_pk_by_recapito[recapito_valore]
+ if pk_recapito in soggetti_gia_presenti:
+ contact['warning']=False
+ contact['import_status']=False
+ else:
+ contact['warning']=True
+ contact['import_status']='warning'
+ else:
+ contact['import_status']=True
+
+ # TODO: considerare anche la casistica in cui esiste lo stesso contatto su diversi soggetti
+ # già direttamente nel file da importare.
+ # if warning_altro_sogg_emails:
+ # emails = Email.objects.all().select_related('soggetto')
+ # emails = {normalizza_email(x['indirizzo_email']): str(
+ # x.soggetto) for x in emails}
+ # for soggetto in contacts:
+ # for r in soggetto:
+ # if r['recapito'] == 'email' and r['valore'] in emails:
+ # r['warning_altro_soggetto'] = emails[r['valore']]
+ # if warning_altro_sogg_telefoni:
+ # telefoni = Telefono.objects.all().select_related('soggetto')
+ # telefoni = {normalizza_telefono(x['numero']): str(
+ # x.soggetto) for x in telefoni}
+ # for soggetto in contacts:
+ # for r in soggetto:
+ # if r['recapito'] == 'telefono' and r['valore'] in telefoni:
+ # r['warning_altro_soggetto'] = telefoni[r['valore']]
+ # contacts = [{**{k: v for k, v in soggetto.items() if k != 'recapiti'}, **recapito}
+ # for soggetto in contacts for recapito in soggetto['recapiti']]
+ context = {'contacts': json.dumps(contacts)}
+ return context
+
+
+def import_google_contacts_confirm(request):
+ if request.method == 'POST':
+ selected_indices = [int(key.split('_')[1])
+ for key in request.POST if key.startswith('import_')]
+ csv_file = request.session.get('csv_file')
+
+ if csv_file and selected_indices:
+ reader = csv.DictReader(csv_file)
+ contacts = list(reader)
+ selected_contacts = [contacts[i] for i in selected_indices]
+
+ for row in selected_contacts:
+ nome = row['Name']
+ email = row['E-mail 1 - Value']
+ telefono = row['Phone 1 - Value']
+
+ # Importa il soggetto solo se l'utente lo ha selezionato
+ if nome and row.get('import') == '1':
+ try:
+ soggetto = SoggettoContattabile.objects.get(nome=nome)
+ # Effettua l'aggiornamento del soggetto
+ soggetto.email = email
+ soggetto.telefono = telefono
+ soggetto.save()
+ messages.success(request, f'Updated contact: {nome}')
+ except SoggettoContattabile.DoesNotExist:
+ # Crea un nuovo soggetto
+ soggetto = SoggettoContattabile.objects.create(
+ nome=nome)
+ if email:
+ Email.objects.create(
+ soggetto=soggetto, indirizzo_email=email)
+ if telefono:
+ Telefono.objects.create(
+ soggetto=soggetto, numero=telefono)
+ messages.success(request, f'Imported contact: {nome}')
+
+ # Reindirizza a una pagina dopo l'importazione
+ return redirect('admin')
+
+ # Reindirizza se si verifica un errore o non sono stati selezionati contatti
+ return redirect('import_google_contacts')
# --------------- FINE PREFISSO TEMPLATE ---------------