diff --git a/noethysweb/core/decorators.py b/noethysweb/core/decorators.py index 2a3d24f2..42585211 100644 --- a/noethysweb/core/decorators.py +++ b/noethysweb/core/decorators.py @@ -2,12 +2,12 @@ # Copyright (c) 2019-2021 Ivan LUCAS. # Noethysweb, application de gestion multi-activités. # Distribué sous licence GNU GPL. - +import logging +logger = logging.getLogger(__name__) from django.urls import reverse_lazy, reverse from django.http import HttpResponseRedirect, HttpResponseBadRequest, HttpResponseForbidden -from django.contrib.auth import REDIRECT_FIELD_NAME -from django.contrib.auth.decorators import user_passes_test from reglements.utils import utils_ventilation +from core.models import PortailParametre def Verifie_ventilation(function): @@ -39,6 +39,10 @@ def _function(request, *args, **kwargs): def secure_ajax_portail(function): """ A associer aux requêtes AJAX """ def _function(request, *args, **kwargs): + compte_famille = PortailParametre.objects.filter(code="compte_famille").first() + compte_individu = PortailParametre.objects.filter(code="compte_individu").first() + # logger.debug("compte_famille: %s" % compte_famille.valeur) + # logger.debug("compte_individu: %s" % compte_individu.valeur) # Vérifie que c'est une requête AJAX if not request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest': return HttpResponseBadRequest() @@ -46,8 +50,11 @@ def _function(request, *args, **kwargs): if not request.user.is_authenticated: return HttpResponseForbidden() # Vérifie que c'est un user de type utilisateur - if request.user.categorie != "famille": + # et que si le paramètre de compte individu est true ( coché ) alors interdire l'accès ou l'utilisation des identifiants famille + if((request.user.categorie not in ["famille", "individu"]) # Vérification de la catégorie : Si l'utilisateur n'est ni "famille" ni "individu", l'accès est interdit. + or (compte_famille != "True" and request.user.categorie == "famille") #Compte famille non activé pour une famille: Si compte_famille n'est pas "True" et que la catégorie est "famille", l'accès est interdit. + or ( compte_individu == "True" and request.user.categorie == "famille")): #Compte individu activé pour une famille: Si compte_individu est "True" alors que l'utilisateur est de catégorie "famille", l'accès est interdit. + # if request.user.categorie != "individu": return HttpResponseForbidden() return function(request, *args, **kwargs) return _function - diff --git a/noethysweb/core/models.py b/noethysweb/core/models.py index 3075ab72..f3123c13 100644 --- a/noethysweb/core/models.py +++ b/noethysweb/core/models.py @@ -1649,7 +1649,17 @@ class Meta: def __str__(self): return "Remplissage ID%d" % self.idremplissage if self.idremplissage else "Nouveau" +class CategorieCompteInternet(models.Model): + idcategorie = models.AutoField(verbose_name="ID", db_column='IDcategorie', primary_key=True) + nom = models.CharField(verbose_name="Nom", max_length=200) + + class Meta: + db_table = 'categories_compte_internet' + verbose_name = "catégorie de compte internet" + verbose_name_plural = "catégories de compte internet" + def __str__(self): + return self.nom class Individu(models.Model): idindividu = models.AutoField(verbose_name="ID", db_column='IDindividu', primary_key=True) @@ -1696,6 +1706,17 @@ class Individu(models.Model): type_garde_choix = [(1, "Mère"), (2, "Père"), (3, "Garde alternée"), (4, "Autre personne")] type_garde = models.IntegerField(verbose_name=_("Type de garde"), choices=type_garde_choix, blank=True, null=True) info_garde = models.TextField(verbose_name=_("Information sur la garde"), blank=True, null=True) + # new attributs + internet_categorie = models.ForeignKey(CategorieCompteInternet, verbose_name="Catégorie",related_name="internet_categori", on_delete=models.PROTECT, blank=True,null=True) + internet_actif = models.BooleanField(verbose_name="Compte internet activé", default=True) + internet_identifiant = encrypt(models.CharField(verbose_name="Identifiant", max_length=200, blank=True, null=True)) + internet_mdp = encrypt(models.CharField(verbose_name="Mot de passe", max_length=200, blank=True, null=True)) + internet_secquest = models.CharField(verbose_name="Question", max_length=200, blank=True, null=True) + internet_reservations = models.BooleanField(verbose_name="Autoriser les réservations sur le portail", default=True) + mobile = encrypt(models.CharField(verbose_name="Portable favori", max_length=100, blank=True, null=True)) + utilisateur = models.OneToOneField(Utilisateur, on_delete=models.CASCADE, null=True) + certification_date = models.DateTimeField(verbose_name="Date de certification", blank=True, null=True) + blocage_impayes_off = models.BooleanField(verbose_name="Ne jamais appliquer le blocage des réservations si impayés",default=False,help_text="En cochant cette case, vous permettez à cette famille d'accéder aux réservations du portail même s'il y a des impayés et que le paramètre 'blocage si impayés' a été activé dans les paramètres généraux du portail.") class Meta: db_table = 'individus' @@ -1767,6 +1788,9 @@ def Maj_infos(self): self.ville_resid = dict_adresse["ville"] self.secteur = dict_adresse["secteur"] self.save() + def save_individu(sender, instance, **kwargs): + if hasattr(instance, 'individu'): + instance.individu.save() class Scolarite(models.Model): @@ -1787,19 +1811,6 @@ def __str__(self): return "Etape de scolarité du %s au %s" % (self.date_debut.strftime('%d/%m/%Y'), self.date_fin.strftime('%d/%m/%Y')) -class CategorieCompteInternet(models.Model): - idcategorie = models.AutoField(verbose_name="ID", db_column='IDcategorie', primary_key=True) - nom = models.CharField(verbose_name="Nom", max_length=200) - - class Meta: - db_table = 'categories_compte_internet' - verbose_name = "catégorie de compte internet" - verbose_name_plural = "catégories de compte internet" - - def __str__(self): - return self.nom - - class Famille(models.Model): idfamille = models.AutoField(verbose_name="ID", db_column='IDfamille', primary_key=True) date_creation = models.DateTimeField(verbose_name="Date de création", auto_now_add=True) @@ -1848,7 +1859,8 @@ class Famille(models.Model): mobile_blocage = models.BooleanField(verbose_name="La famille ne souhaite pas recevoir de SMS groupés", default=False, help_text="L'éditeur de SMS groupés du menu Outils ne proposera pas cette famille dans les destinataires.") individus_masques = models.ManyToManyField(Individu, verbose_name="Individus masqués", related_name="individus_masques", blank=True) blocage_impayes_off = models.BooleanField(verbose_name="Ne jamais appliquer le blocage des réservations si impayés", default=False, help_text="En cochant cette case, vous permettez à cette famille d'accéder aux réservations du portail même s'il y a des impayés et que le paramètre 'blocage si impayés' a été activé dans les paramètres généraux du portail.") - + contact_facturation = models.ForeignKey(Individu, verbose_name="Contact facturation", related_name="contact_facturation", on_delete=models.SET_NULL, blank=True, null=True) + utilisateur = models.OneToOneField(Utilisateur, on_delete=models.CASCADE, null=True) class Meta: db_table = 'familles' verbose_name = "famille" @@ -1907,18 +1919,25 @@ def Maj_infos(self, maj_adresse=True, maj_mail=True, maj_mobile=True, maj_titula # Titulaire Hélios if maj_titulaire_helios: - if self.titulaire_helios: - # recherche si le titulaire est toujours dans la famille - found = False - for rattachement in rattachements: - if rattachement.individu == self.titulaire_helios: - found = True - if not found: - self.titulaire_helios = None - if not self.titulaire_helios: - # Recherche un individu valide parmi les titulaires de la famille - if rattachements: - self.titulaire_helios = rattachements.first().individu + try: + if self.titulaire_helios: + # Recherche si le titulaire est toujours dans la famille + found = False + for rattachement in rattachements: + if rattachement.individu == self.titulaire_helios: + found = True + break # Si trouvé, on sort de la boucle + if not found: + # Si le titulaire n'est pas trouvé dans la famille, on le met à None + self.titulaire_helios = None + except Individu.DoesNotExist: + # Si le titulaire a été supprimé, on le met à None + self.titulaire_helios = None + + # Assurez-vous qu'il y a un titulaire dans la famille si nécessaire + if not self.titulaire_helios and rattachements.exists(): + # Recherche un individu valide parmi les rattachements + self.titulaire_helios = rattachements.first().individu if maj_tiers_solidaire: if self.tiers_solidaire: @@ -3042,6 +3061,8 @@ class Destinataire(models.Model): categorie = models.CharField(verbose_name="Catégorie", max_length=300, blank=True, null=True) individu = models.ForeignKey(Individu, verbose_name="Individu", blank=True, null=True, on_delete=models.CASCADE) famille = models.ForeignKey(Famille, verbose_name="Famille", blank=True, null=True, on_delete=models.CASCADE) + inscription = models.ForeignKey(Inscription, verbose_name="Inscription", blank=True, null=True,on_delete=models.CASCADE) + activites = models.ForeignKey(Activite, verbose_name="Activites", blank=True, null=True, on_delete=models.CASCADE) collaborateur = models.ForeignKey("Collaborateur", verbose_name="Collaborateur", blank=True, null=True, on_delete=models.CASCADE) contact = models.ForeignKey(Contact, verbose_name="Contact", blank=True, null=True, on_delete=models.CASCADE) liste_diffusion = models.ForeignKey(ListeDiffusion, verbose_name="Liste de diffusion", blank=True, null=True, on_delete=models.CASCADE) @@ -3144,7 +3165,7 @@ def Is_famille_authorized(self, famille=None): class PortailParametre(models.Model): idparametre = models.AutoField(verbose_name="ID", db_column='IDparametre', primary_key=True) - code = models.CharField(verbose_name="Code", max_length=200, blank=True, null=True) + code = models.CharField(verbose_name="Code", max_length=200, blank=True, null=True, unique=True) valeur = models.TextField(verbose_name="Valeur", blank=True, null=True) class Meta: @@ -3202,9 +3223,10 @@ def __str__(self): class PortailMessage(models.Model): idmessage = models.AutoField(verbose_name="ID", db_column='IDmessage', primary_key=True) - famille = models.ForeignKey(Famille, verbose_name="Famille", on_delete=models.CASCADE) - structure = models.ForeignKey(Structure, verbose_name="Structure", on_delete=models.CASCADE) - utilisateur = models.ForeignKey(Utilisateur, verbose_name="Utilisateur", blank=True, null=True, on_delete=models.PROTECT) + famille = models.ForeignKey(Famille, verbose_name="Famille", on_delete=models.CASCADE, db_index=True) + individu = models.ForeignKey(Individu, verbose_name="Individu", on_delete=models.CASCADE, null=True, db_index=True) + structure = models.ForeignKey(Structure, verbose_name="Structure", on_delete=models.CASCADE, db_index=True) + utilisateur = models.ForeignKey(Utilisateur, verbose_name="Utilisateur", blank=True, null=True,on_delete=models.PROTECT) texte = models.TextField(verbose_name="Texte") date_creation = models.DateTimeField(verbose_name="Date de création", auto_now_add=True) date_lecture = models.DateTimeField(verbose_name="Date de lecture", max_length=200, blank=True, null=True) @@ -3213,6 +3235,14 @@ class Meta: db_table = 'portail_messages' verbose_name = "message" verbose_name_plural = "messages" + indexes = [ + models.Index(fields=["famille"]), + models.Index(fields=["individu"]), + models.Index(fields=["structure"]), + models.Index(fields=["utilisateur"]), + models.Index(fields=["date_creation"]), + models.Index(fields=["date_lecture"]), + ] def __str__(self): return "Message ID%d" % self.idmessage if self.idmessage else "Nouveau message" diff --git a/noethysweb/core/templates/core/header.html b/noethysweb/core/templates/core/header.html index 5b468fdf..3783812d 100644 --- a/noethysweb/core/templates/core/header.html +++ b/noethysweb/core/templates/core/header.html @@ -104,7 +104,7 @@
{{ message.texte|striptags|truncatechars:55 }}
-{{ message.date_creation|timesince }} | {{ message.structure }}
++ {{ message.date_creation|timesince }} + | {{ message.structure }} +
+
+ Si vous ne souhaitez plus recevoir nos mails groupés, cliquez sur le lien suivant : Désinscription
" % url_desinscription headers = {"List-Unsubscribe-Post": "List-Unsubscribe=One-Click", "List-Unsubscribe": url_desinscription} - # Création du message - message = EmailMultiAlternatives(subject=objet, body=utils_texte.Textify(html), from_email=mail.adresse_exp.adresse, to=[destinataire.adresse], connection=connection, headers=headers) + # Construction du message + message = EmailMultiAlternatives( + subject=objet, + body=utils_texte.Textify(html), + from_email=mail.adresse_exp.adresse, + to=[destinataire.adresse], + connection=connection, + headers=headers + ) message.mixed_subtype = 'related' message.attach_alternative(html, "text/html") @@ -203,19 +222,26 @@ def Envoyer_model_mail(idmail=None, request=None): if resultat == 1: liste_envois_succes.append(destinataire) - - # Mémorise l'envoi dans l'historique - utils_historique.Ajouter(titre="Envoi d'un email", detail=objet, utilisateur=request.user if request else None, famille=destinataire.famille_id, - individu=destinataire.individu_id, collaborateur=destinataire.collaborateur_id, objet="Email", idobjet=mail.pk, classe="Mail") - - # Pause si envoi par lot activé - if nbre_mails_lot and len(destinataires) > 1: + # Historique + utils_historique.Ajouter( + titre="Envoi d'un email", + detail=objet, + utilisateur=request.user if request else None, + famille=destinataire.famille_id, + individu=destinataire.individu_id, + collaborateur=destinataire.collaborateur_id, + objet="Email", + idobjet=mail.pk, + classe="Mail" + ) + + # Pause si envoi par lot + if nbre_mails_lot and len(unique_destinataires) > 1: if index_lot >= int(nbre_mails_lot): time.sleep(int(duree_pause)) index_lot = 0 index_lot += 1 - connection.close() return liste_envois_succes diff --git a/noethysweb/outils/views/editeur_emails.py b/noethysweb/outils/views/editeur_emails.py index 1f4072bd..516b90ee 100644 --- a/noethysweb/outils/views/editeur_emails.py +++ b/noethysweb/outils/views/editeur_emails.py @@ -1,40 +1,72 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2019-2021 Ivan LUCAS. -# Noethysweb, application de gestion multi-activités. -# Distribué sous licence GNU GPL. +# Copyright (c) 2019-2021 Ivan LUCAS. +# Noethysweb, application de gestion multi-activités. +# Distribué sous licence GNU GPL. import json, os, uuid +from collections import defaultdict + from django.urls import reverse_lazy from django.http import JsonResponse, HttpResponseRedirect from django.db.models import Q, Count from django.contrib import messages from django.conf import settings + from core.views import crud -from core.models import ModeleEmail, Mail, PieceJointe, Destinataire, Famille, Individu, Collaborateur, Contact, SignatureEmail +from core.models import ModeleEmail, Mail, PieceJointe, Destinataire, Famille, Individu, Activite, Inscription, Collaborateur, Contact, SignatureEmail, PortailParametre from core.utils import utils_texte from outils.forms.editeur_emails import Formulaire from outils.utils import utils_email +def RemoveDuplicates(mail): + """ + Supprime en base les doublons d'adresse pour un même Mail, + en ne gardant que la première occurrence pour chaque adresse. + """ + groups = defaultdict(list) + for d in mail.destinataires.all(): + addr_norm = (d.adresse or "").strip().lower() + groups[addr_norm].append(d) + + for addr_norm, items in groups.items(): + if len(items) > 1: + to_keep = items[0] # On garde le premier + for d in items[1:]: + d.delete() + def Get_modele_email(request): - """ Renvoie le contenu HTML d'un modèle d'emails """ + """Renvoie le contenu HTML d'un modèle d'emails""" idmodele = int(request.POST.get("idmodele")) modele = ModeleEmail.objects.filter(pk=idmodele).first() return JsonResponse({"objet": modele.objet, "html": modele.html}) def Get_signature_email(request): - """ Renvoie le contenu HTML d'une signature d'emails """ + """Renvoie le contenu HTML d'une signature d'emails""" idsignature = int(request.POST.get("idsignature")) signature = SignatureEmail.objects.filter(pk=idsignature).first() return JsonResponse({"html": signature.html}) def Exporter_excel(request): + """Exporte la liste des destinataires ou des envois vers un fichier Excel""" idmail = int(request.POST.get("idmail") or 0) mode = request.POST.get("mode") - destinataires = Destinataire.objects.select_related("famille", "individu", "contact", "collaborateur").filter(mail=idmail).order_by("adresse") - if not destinataires: + + # --- Fetch the Mail --- + try: + mail = Mail.objects.get(pk=idmail) + except Mail.DoesNotExist: + return JsonResponse({"erreur": "Mail inexistant."}, status=401) + + destinataires = ( + mail.destinataires + .select_related("famille", "individu", "activites", "inscription", "contact", "collaborateur") + .order_by("adresse") + ) + + if not destinataires.exists(): return JsonResponse({"erreur": "Aucun destinataire n'a été sélectionné"}, status=401) # Création du répertoire et du nom du fichier @@ -42,150 +74,245 @@ def Exporter_excel(request): rep_destination = os.path.join(settings.MEDIA_ROOT, rep_temp) if not os.path.isdir(rep_destination): os.makedirs(rep_destination) - nom_fichier = "export_%s.xlsx" % mode + nom_fichier = f"export_{mode}.xlsx" - # Création du classeur + # Création du classeur Excel (xlsxwriter) import xlsxwriter classeur = xlsxwriter.Workbook(os.path.join(rep_destination, nom_fichier)) feuille = classeur.add_worksheet(mode) - # Création des colonnes + # Colonnes si on exporte les "envois" if mode == "envois": - colonnes = ("Famille", "Individu", "Collaborateur", "Contact", "Email", "Résultat de l'envoi") + colonnes = ( + "Famille", "Individu", "Activité", "Inscription", + "Collaborateur", "Contact", "Email", "Résultat de l'envoi" + ) for num_colonne, label_colonne in enumerate(colonnes): feuille.set_column(num_colonne, num_colonne, 35) feuille.write(0, num_colonne, label_colonne) + # Remplir le classeur for num_ligne, destinataire in enumerate(destinataires): if mode == "destinataires": + # Juste la colonne avec l'adresse feuille.write(num_ligne, 0, destinataire.adresse) if mode == "envois": + # Remplir chaque colonne feuille.write(num_ligne+1, 0, destinataire.famille.nom if destinataire.famille else "") feuille.write(num_ligne+1, 1, destinataire.individu.Get_nom() if destinataire.individu else "") - feuille.write(num_ligne+1, 2, destinataire.collaborateur.Get_nom() if destinataire.collaborateur else "") - feuille.write(num_ligne+1, 3, destinataire.contact.Get_nom() if destinataire.contact else "") - feuille.write(num_ligne+1, 4, destinataire.adresse) - feuille.write(num_ligne+1, 5, destinataire.resultat_envoi) - classeur.close() + feuille.write(num_ligne+1, 2, destinataire.activites.nom if destinataire.activites else "") + feuille.write(num_ligne+1, 3, destinataire.inscription.Get_nom() if destinataire.inscription else "") + feuille.write(num_ligne+1, 4, destinataire.collaborateur.Get_nom() if destinataire.collaborateur else "") + feuille.write(num_ligne+1, 5, destinataire.contact.Get_nom() if destinataire.contact else "") + feuille.write(num_ligne+1, 6, destinataire.adresse) + feuille.write(num_ligne+1, 7, destinataire.resultat_envoi) + classeur.close() return JsonResponse({"nom_fichier": os.path.join(rep_temp, nom_fichier)}) class Page(crud.Page): + """Page principale de l'éditeur d'emails""" model = Mail menu_code = "editeur_emails" template_name = "outils/editeur_emails.html" + def Get_idmail(self): + return self.kwargs.get('pk', None) + def get_context_data(self, **kwargs): context = super(Page, self).get_context_data(**kwargs) - context['page_titre'] = "Editeur d'emails" - context['afficher_menu_brothers'] = True - context['categories'] = {} if not self.Get_idmail() else {item["categorie"]: item["nbre"] for item in Destinataire.objects.values('categorie').filter(mail=self.Get_idmail()).annotate(nbre=Count("pk"))} - mail = Mail.objects.get(pk=self.Get_idmail()) if self.Get_idmail() else None + context['page_titre'] = "Envoi d'emails par lot" + + mail = None + if self.Get_idmail(): + mail = Mail.objects.get(pk=self.Get_idmail()) + context['mail'] = mail + + # --- Supprimer les doublons si mail existe --- + if mail: + RemoveDuplicates(mail) + + # Charger la catégorie, les signatures, etc. categorie = mail.categorie if mail else "saisie_libre" context['modeles'] = ModeleEmail.objects.filter(categorie=categorie) - context['signatures'] = SignatureEmail.objects.filter(Q(structure__in=self.request.user.structures.all()) | Q(structure__isnull=True)) - context['mail'] = mail + context['signatures'] = SignatureEmail.objects.filter( + Q(structure__in=self.request.user.structures.all()) | Q(structure__isnull=True) + ) + + # Stats par catégorie + context['categories'] = {} + if mail: + categories_data = ( + Destinataire.objects + .values('categorie') + .filter(mail=mail) + .annotate(nbre=Count('pk', distinct=True)) + ) + for item in categories_data: + cat = item["categorie"] + if cat == "famille": + count_value = Destinataire.objects.filter(mail=mail, categorie=cat).values('famille').distinct().count() + elif cat == "activites": + count_value = Destinataire.objects.filter(mail=mail, categorie=cat).values('activite').distinct().count() + elif cat == "individu": + count_value = Destinataire.objects.filter(mail=mail, categorie=cat).values('individu').distinct().count() + else: + count_value = item["nbre"] + context['categories'][cat] = count_value + + # Récupérer tous les destinataires (il n'y a plus de doublons, thanks to RemoveDuplicates) + destinataires = [] + nbre_envois_attente = 0 + nbre_envois_reussis = 0 + nbre_envois_echec = 0 + + if mail: + for d in ( + mail.destinataires + .select_related("famille", "individu", "activites", "inscription", "contact", "collaborateur") + .prefetch_related("documents") + .order_by("adresse") + ): + destinataires.append(d) + if not d.resultat_envoi: + nbre_envois_attente += 1 + elif d.resultat_envoi == "ok": + nbre_envois_reussis += 1 + else: + nbre_envois_echec += 1 - # Importe la liste des destinataires (Elimine les doublons) - destinataires, adresses_temp = [], [] - nbre_envois_attente, nbre_envois_reussis, nbre_envois_echec = 0, 0, 0 - if self.Get_idmail(): - for destinataire in Destinataire.objects.select_related("famille", "individu", "contact", "collaborateur").prefetch_related("documents").filter(mail=self.Get_idmail()).order_by("adresse"): - if True:#destinataire.adresse not in adresses_temp: - destinataires.append(destinataire) - adresses_temp.append(destinataire.adresse) - if not destinataire.resultat_envoi: - nbre_envois_attente += 1 - elif destinataire.resultat_envoi == "ok": - nbre_envois_reussis += 1 - else: - nbre_envois_echec += 1 context['destinataires'] = destinataires context['nbre_envois_attente'] = nbre_envois_attente - context['nbre_envois_echec'] = nbre_envois_echec context['nbre_envois_reussis'] = nbre_envois_reussis + context['nbre_envois_echec'] = nbre_envois_echec - # Création du texte d'intro de la box Envois + # Intro envois intro_envois = [] - if not destinataires: intro_envois.append("Aucun envoi") - if nbre_envois_reussis: intro_envois.append("%d envois réussis" % nbre_envois_reussis) if nbre_envois_reussis > 1 else intro_envois.append("1 envoi réussi") - if nbre_envois_attente: intro_envois.append("%d envois en attente" % nbre_envois_attente) if nbre_envois_attente > 1 else intro_envois.append("1 envoi en attente") - if nbre_envois_echec: intro_envois.append("%d envois en échec" % nbre_envois_echec) if nbre_envois_echec > 1 else intro_envois.append("1 envoi en échec") + if not destinataires: + intro_envois.append("Aucun envoi") + if nbre_envois_reussis == 1: + intro_envois.append("1 envoi réussi") + elif nbre_envois_reussis > 1: + intro_envois.append(f"{nbre_envois_reussis} envois réussis") + + if nbre_envois_attente == 1: + intro_envois.append("1 envoi en attente") + elif nbre_envois_attente > 1: + intro_envois.append(f"{nbre_envois_attente} envois en attente") + + if nbre_envois_echec == 1: + intro_envois.append("1 envoi en échec") + elif nbre_envois_echec > 1: + intro_envois.append(f"{nbre_envois_echec} envois en échec") + context['intro_envoi'] = utils_texte.Convert_liste_to_texte_virgules(intro_envois) + # Parametres + parametres_db = PortailParametre.objects.filter( + code__in=[ + "emails_individus_afficher_page", + "emails_familles_afficher_page", + "emails_activites_afficher_page", + "emails_inscriptions_afficher_page", + "emails_collaborateurs_afficher_page", + "emails_liste_diffusion_afficher_page", + ] + ) + parametres = {} + for p in parametres_db: + if isinstance(p.valeur, str): + parametres[p.code] = p.valeur.strip().lower() in ["true", "1"] + + context["parametres"] = parametres return context - def Get_idmail(self): - return self.kwargs.get('pk', None) - def get_form_kwargs(self, **kwargs): - """ Envoie l'idactivite au formulaire """ - form_kwargs = super(Page, self).get_form_kwargs(**kwargs) + """Envoie l'idmail au formulaire""" + form_kwargs = super().get_form_kwargs(**kwargs) form_kwargs["idmail"] = self.Get_idmail() return form_kwargs def post(self, request, *args, **kwargs): - # Validation du formulaire + """Gère la sauvegarde du Mail ou la redirection vers la sélection de destinataires.""" form = Formulaire(request.POST, request=self.request) - if form.is_valid() == False: + if not form.is_valid(): return self.render_to_response(self.get_context_data(form=form)) - idmail = self.kwargs.get("pk") - - # Modification du mail + idmail = self.Get_idmail() if idmail: mail = Mail.objects.get(pk=idmail) - mail.objet = form.cleaned_data.get("objet") - mail.html = form.cleaned_data.get("html") - mail.adresse_exp = form.cleaned_data.get("adresse_exp") - mail.selection = form.cleaned_data.get("selection") + mail.objet = form.cleaned_data["objet"] + mail.html = form.cleaned_data["html"] + mail.adresse_exp = form.cleaned_data["adresse_exp"] + mail.selection = form.cleaned_data["selection"] mail.save() - - # Création du mail - if not idmail: + else: mail = Mail.objects.create( categorie="saisie_libre", - objet=form.cleaned_data.get("objet"), - html=form.cleaned_data.get("html"), - adresse_exp=form.cleaned_data.get("adresse_exp"), - selection=form.cleaned_data.get("selection"), + objet=form.cleaned_data["objet"], + html=form.cleaned_data["html"], + adresse_exp=form.cleaned_data["adresse_exp"], + selection=form.cleaned_data["selection"], utilisateur=request.user, ) - messages.add_message(request, messages.INFO, "Un brouillon du mail a été enregistré") + messages.info(request, "Un brouillon du mail a été enregistré") - # Enregistrement d'une nouvelle pièce jointe + # Gérer les pièces jointes piece = request.FILES.get('pieces_jointes') if piece: piece_jointe = PieceJointe.objects.create(nom=piece._name, fichier=piece) mail.pieces_jointes.add(piece_jointe) - # Suppression d'une pièce suppr_piece_jointe = request.POST.get("suppr_piece_jointe") if suppr_piece_jointe: - piece_jointe = PieceJointe.objects.get(pk=suppr_piece_jointe) - piece_jointe.delete() + PieceJointe.objects.filter(pk=suppr_piece_jointe).delete() - # Redirection vers pages de sélection des destinataires + # Gérer la redirection pour ajouter des destinataires action = request.POST.get("action") - if action == "ajouter_familles": return HttpResponseRedirect(reverse_lazy("editeur_emails_familles", kwargs={"idmail": mail.pk})) - if action == "ajouter_individus": return HttpResponseRedirect(reverse_lazy("editeur_emails_individus", kwargs={"idmail": mail.pk})) - if action == "ajouter_collaborateurs": return HttpResponseRedirect(reverse_lazy("editeur_emails_collaborateurs", kwargs={"idmail": mail.pk})) - if action == "ajouter_contacts": return HttpResponseRedirect(reverse_lazy("editeur_emails_contacts", kwargs={"idmail": mail.pk})) - if action == "ajouter_diffusion": return HttpResponseRedirect(reverse_lazy("editeur_emails_listes_diffusion", kwargs={"idmail": mail.pk})) - if action == "ajouter_saisie_libre": return HttpResponseRedirect(reverse_lazy("editeur_emails_saisie_libre", kwargs={"idmail": mail.pk})) - - # Envoyer + if action == "ajouter_familles": + return HttpResponseRedirect(reverse_lazy("editeur_emails_familles", kwargs={"idmail": mail.pk})) + if action == "ajouter_individus": + return HttpResponseRedirect(reverse_lazy("editeur_emails_individus", kwargs={"idmail": mail.pk})) + if action == "ajouter_activites": + return HttpResponseRedirect(reverse_lazy("editeur_emails_activites")) + if action == "ajouter_inscription": + return HttpResponseRedirect(reverse_lazy("editeur_emails_inscriptions")) + if action == "ajouter_collaborateurs": + return HttpResponseRedirect(reverse_lazy("editeur_emails_collaborateurs", kwargs={"idmail": mail.pk})) + if action == "ajouter_contacts": + return HttpResponseRedirect(reverse_lazy("editeur_emails_contacts", kwargs={"idmail": mail.pk})) + if action == "ajouter_diffusion": + return HttpResponseRedirect(reverse_lazy("editeur_emails_listes_diffusion", kwargs={"idmail": mail.pk})) + if action == "ajouter_saisie_libre": + return HttpResponseRedirect(reverse_lazy("editeur_emails_saisie_libre", kwargs={"idmail": mail.pk})) + + # Gérer l'envoi if action == "envoyer": - if not mail.destinataires: - messages.add_message(request, messages.ERROR, "Vous devez sélectionner au moins un destinataire") - elif not form.cleaned_data.get("adresse_exp"): - messages.add_message(request, messages.ERROR, "Vous devez sélectionner une adresse d'expédition dans la liste") - elif not form.cleaned_data.get("objet"): - messages.add_message(request, messages.ERROR, "Vous devez saisir un objet") - elif not form.cleaned_data.get("html"): - messages.add_message(request, messages.ERROR, "Vous devez saisir un texte") + # Vérifier la validité minimale + if mail.destinataires.count() == 0: + messages.error(request, "Vous devez sélectionner au moins un destinataire") + elif not form.cleaned_data["adresse_exp"]: + messages.error(request, "Vous devez sélectionner une adresse d'expédition dans la liste") + elif not form.cleaned_data["objet"]: + messages.error(request, "Vous devez saisir un objet") + elif not form.cleaned_data["html"]: + messages.error(request, "Vous devez saisir un texte") else: + # Définir la variable expediteur_email + expediteur_email = mail.adresse_exp + # Récupérer tous les destinataires existants de type "expediteur" + destinataires_expediteurs = list(mail.destinataires.filter(categorie="expediteur")) + # Comparaison en mémoire pour éviter l'erreur sur EncryptedEmailField + already_there = any(str(dest.adresse) == str(expediteur_email) for dest in destinataires_expediteurs) + + # Ajouter l'expéditeur seulement s'il n'existe pas déjà + if not already_there: + destinataire_expediteur = Destinataire.objects.create( + categorie="expediteur", + adresse=expediteur_email + ) + mail.destinataires.add(destinataire_expediteur) # Ajout au ManyToMany utils_email.Envoyer_model_mail(idmail=mail.pk, request=request) return HttpResponseRedirect(reverse_lazy("editeur_emails", kwargs={'pk': mail.pk})) @@ -199,66 +326,106 @@ class Modifier(Page, crud.Modifier): form_class = Formulaire - - -# ------------------------------------------------------------------------------------------- +# -------------------------------------------------------------------------- +# Pages d'ajout de destinataires (familles, individus, etc.) +# -------------------------------------------------------------------------- class Page_destinataires(crud.Page): menu_code = "editeur_emails" def get_context_data(self, **kwargs): - context = super(Page_destinataires, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['page_titre'] = "Editeur d'emails" context['idmail'] = self.kwargs.get("idmail") context['categorie'] = self.categorie return context def post(self, request, **kwargs): - liste_selections = json.loads(request.POST.get("selections")) - - # Importe le mail + liste_selections = json.loads(request.POST.get("selections") or "[]") mail = Mail.objects.get(pk=self.kwargs.get("idmail")) - # Importe les adresses dict_adresses = {} if self.categorie == "famille": - dict_adresses = {famille.pk: famille.mail for famille in Famille.objects.filter(email_blocage=False)} - if self.categorie == "individu": - dict_adresses = {individu.pk: individu.mail for individu in Individu.objects.all()} - if self.categorie == "collaborateur": - dict_adresses = {collaborateur.pk: collaborateur.mail for collaborateur in Collaborateur.objects.all()} - if self.categorie == "contact": - dict_adresses = {contact.pk: contact.mail for contact in Contact.objects.all()} - - # Importe la liste des destinataires actuels - destinataires = Destinataire.objects.filter(categorie=self.categorie, mail=mail) - liste_id = [getattr(destinataire, "%s_id" % self.categorie) for destinataire in destinataires] - - # Recherche le dernier ID de la table Destinataires - dernier_destinataire = Destinataire.objects.last() - idmax = dernier_destinataire.pk if dernier_destinataire else 0 - - # Ajout des destinataires + dict_adresses = { + f.pk: f.mail + for f in Famille.objects.filter(email_blocage=False) + } + elif self.categorie == "individu": + dict_adresses = { + i.pk: i.mail + for i in Individu.objects.all() + } + elif self.categorie == "activites": + inscriptions = ( + Inscription.objects + .filter(activite_id__in=liste_selections, famille__isnull=False) + .select_related("famille") + ) + for ins in inscriptions: + if ins.famille and ins.famille.mail: + dict_adresses[ins.famille.pk] = ins.famille.mail + elif self.categorie == "collaborateur": + dict_adresses = { + c.pk: c.mail + for c in Collaborateur.objects.all() + } + elif self.categorie == "contact": + dict_adresses = { + c.pk: c.mail + for c in Contact.objects.all() + } + + # Destinataires déjà existants pour cette catégorie + destinataires_existants = mail.destinataires.filter(categorie=self.categorie) + liste_id_existants = [ + getattr(dest, f"{self.categorie}_id") for dest in destinataires_existants + ] + + # ### AJOUT : On construit un set des adresses déjà présentes (pour ce mail), + # afin d'éviter les doublons en base + existing_addresses = set( + str(d.adresse).strip().lower() + for d in mail.destinataires.all() + if d.adresse + ) + + # On repère la fin de la table pour faire le bulk_create + dernier = Destinataire.objects.last() + idmax = dernier.pk if dernier else 0 + liste_ajouts = [] - for id in liste_selections: - if id not in liste_id: - adresse = dict_adresses.get(id, None) + for ident in dict_adresses: + # Ne pas ajouter deux fois la même "famille_id"/"individu_id", etc. + if ident not in liste_id_existants: + adresse = dict_adresses[ident] if adresse: - kwargs = {"{0}_id".format(self.categorie): id, "categorie": self.categorie, "adresse": adresse} - liste_ajouts.append(Destinataire(**kwargs)) + addr_norm = adresse.strip().lower() + # Vérifier si cette adresse est déjà utilisée + if addr_norm not in existing_addresses: + existing_addresses.add(addr_norm) # Évite de l'ajouter encore + if self.categorie == "activites": + liste_ajouts.append(Destinataire( + categorie="activites", + famille_id=ident, + adresse=adresse + )) + else: + kwargs2 = { + f"{self.categorie}_id": ident, + "categorie": self.categorie, + "adresse": adresse, + } + liste_ajouts.append(Destinataire(**kwargs2)) + if liste_ajouts: - # Enregistre les destinataires Destinataire.objects.bulk_create(liste_ajouts) - # Associe les destinataires au mail - destinataires = Destinataire.objects.filter(pk__gt=idmax) - ThroughModel = Mail.destinataires.through - ThroughModel.objects.bulk_create([ThroughModel(mail_id=mail.pk, destinataire_id=destinataire.pk) for destinataire in destinataires]) - - # Suppression des destinataires - for id in liste_id: - if id not in liste_selections: - kwargs = {"{0}_id".format(self.categorie): id, "mail": mail} - destinataire = Destinataire.objects.get(**kwargs) - destinataire.delete() + nouveaux = Destinataire.objects.filter(pk__gt=idmax) + mail.destinataires.add(*nouveaux) + + # Suppression si décoché + for ident in liste_id_existants: + if ident not in dict_adresses: + qs_suppr = destinataires_existants.filter(**{f"{self.categorie}_id": ident}) + qs_suppr.delete() return HttpResponseRedirect(reverse_lazy("editeur_emails", kwargs={'pk': mail.pk})) diff --git a/noethysweb/outils/views/editeur_emails_activites.py b/noethysweb/outils/views/editeur_emails_activites.py new file mode 100644 index 00000000..bb89a123 --- /dev/null +++ b/noethysweb/outils/views/editeur_emails_activites.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2025 Faouzia TEKAYA. +# Noethysweb, application de gestion multi-activités. +# Distribué sous licence GNU GPL. + +import logging +logger = logging.getLogger(__name__) +from django.urls import reverse_lazy, reverse +from core.views.mydatatableview import MyDatatable, columns, helpers +from core.views import crud +from django.db.models import Count, F +from core.models import Mail, DocumentJoint, Inscription, Destinataire, Activite +from django.http import JsonResponse +from django.contrib import messages +import json +def Transferer_activites(request): + if request.method != "POST": + return JsonResponse({"erreur": "Méthode invalide."}, status=405) + + data = json.loads(request.POST.get("activites_selectionnees", "[]")) + if not data: + return JsonResponse({"erreur": "Aucune activité sélectionnée."}, status=401) + + # Créer le Mail + mail = Mail.objects.create( + categorie="activites", + objet="Informations sur l'activité", + html="", + adresse_exp=request.user.Get_adresse_exp_defaut(), + selection="NON_ENVOYE", + verrouillage_destinataires=False, + utilisateur=request.user, + ) + + # Trouver toutes les familles ayant une inscription pour ces activités + inscriptions = ( + Inscription.objects + .filter(activite_id__in=data, famille__isnull=False) + .select_related("famille") + ) + + # Ajouter un Destinataire par famille + familles_dict = {} + for insc in inscriptions: + if insc.famille and insc.famille.mail: + familles_dict[insc.famille.pk] = insc.famille.mail + + liste_anomalies = [] + for fam_id, email in familles_dict.items(): + if email: + destinataire = Destinataire.objects.create( + categorie="activites", + famille_id=fam_id, + adresse=email, + ) + mail.destinataires.add(destinataire) + else: + # Facultatif : signaler l'absence d'email + liste_anomalies.append(str(fam_id)) + + if liste_anomalies: + messages.error( + request, + "Familles sans email : %s" % ", ".join(liste_anomalies) + ) + + # Retourne l’URL pour l’ouvrir dans l’éditeur d’emails + url = reverse_lazy("editeur_emails", kwargs={"pk": mail.pk}) + return JsonResponse({"url": url}) + +def Impression_pdf(request): + # Récupération des activites cochées + activites_cochees = json.loads(request.POST.get("activites_cochees")) + if not activites_cochees: + return JsonResponse({"erreur": "Veuillez cocher au moins une activité dans la liste"}, status=401) + + # Création des PDF + from fiche_individu.utils import utils_activites + activite = utils_activites.Activites() + resultat = activite.Impression(liste_activites=activites_cochees, dict_options={},mode_email=True) # Mise à jour du paramètre + if not resultat: + return JsonResponse({"success": False}, status=401) + + # Création du mail + logger.debug("Création d'un nouveau mail...") + mail = Mail.objects.create( + categorie="activites", + objet="Notification d'activité", + html="", + adresse_exp=request.user.Get_adresse_exp_defaut(), + selection="NON_ENVOYE", + verrouillage_destinataires=True, + utilisateur=request.user, + ) + + # Création des destinataires et des documents joints + logger.debug("Enregistrement des destinataires et documents joints...") + liste_anomalies = [] + for IDcotisation, donnees in resultat["noms_fichiers"].items(): + inscriptions = Inscription.objects.select_related('famille').filter(activite_id=IDcotisation) + + for cotisation in inscriptions: + if cotisation.famille.mail: + destinataire = Destinataire.objects.create(categorie="famille", famille=cotisation.famille,adresse=cotisation.famille.mail) + document_joint = DocumentJoint.objects.create(nom="Activite", fichier=donnees["nom_fichier"]) + destinataire.documents.add(document_joint) + mail.destinataires.add(destinataire) + + else: + liste_anomalies.append(cotisation.famille.nom) + + if liste_anomalies: + messages.add_message(request, messages.ERROR, "Adresses mail manquantes : %s" % ", ".join(liste_anomalies)) + + # Création de l'URL pour ouvrir l'éditeur d'emails + logger.debug("Redirection vers l'éditeur d'emails...") + url = reverse_lazy("editeur_emails", kwargs={'pk': mail.pk}) + return JsonResponse({"url": url}) + +class Page(crud.Page): + model = Activite + url_liste = "editeur_emails_activites" + menu_code = "editeur_emails_activites" + +from outils.views.editeur_emails import Page_destinataires +class Liste(Page_destinataires, crud.Liste): + template_name = "outils/editeur_emails_activites.html" + model = Activite + categorie = "activites" + + def get_queryset(self): + queryset = ( + Inscription.objects + .filter(famille__mail__isnull=False) + .annotate( + id_activite=F("activite__idactivite"), # <-- ADDED + nom_activite=F("activite__nom"), + nom_groupe=F("groupe__nom"), + ) + .values("id_activite", "nom_activite", "nom_groupe") + .annotate(nombre_inscriptions=Count("idinscription")) + .order_by("nom_activite", "nom_groupe") + ) + return queryset + + def get_context_data(self, **kwargs): + + context = super(Liste, self).get_context_data(**kwargs) + context['page_titre'] = "Envoi des Inscrits aux Activites par Email" + context['box_titre'] = "Envoyer des Inscrits aux Activites par Email en lot" + context['box_introduction'] = "Cochez les activites puis cliquez sur le bouton Aperçu." + context['onglet_actif'] = "activites_email" + context['active_checkbox'] = True + context['bouton_supprimer'] = False + return context + + class datatable_class(MyDatatable): + filtres = ['id_activite','activite__nom', 'groupe__nom', 'nombre_inscriptions'] + model = None + check = columns.CheckBoxSelectColumn(label="") + c_activite_id = columns.TextColumn("Activité", sources=["id_activite"]) + + c_activite = columns.TextColumn("Activité", sources=["nom_activite"]) + c_groupes = columns.TextColumn("Groupes", sources=["nom_groupe"]) + c_nb = columns.TextColumn("Nombre d'inscriptions", sources=["nombre_inscriptions"]) + + class Meta: + structure_template = MyDatatable.structure_template + columns = ["check","c_activite_id" ,"c_activite", "c_groupes", "c_nb"] + + def get_object_pk(self, row): + # Return the real integer ID + row=row["id_activite"] + return row + + def prepare_results(self, queryset): + results = [] + for row in queryset: + results.append({ + "id_activite": row["id_activite"], + "nom_activite": row["nom_activite"], + "nom_groupe": row["nom_groupe"], + "nombre_inscriptions": row["nombre_inscriptions"], + }) + return results + + def Formate_statut(self, instance, *args, **kwargs): + if instance.statut == "attente": + return " Attente" + elif instance.statut == "refus": + return " Refus" + else: + return " Valide" \ No newline at end of file diff --git a/noethysweb/outils/views/editeur_emails_express.py b/noethysweb/outils/views/editeur_emails_express.py index 02d1e5fe..5adcb73c 100644 --- a/noethysweb/outils/views/editeur_emails_express.py +++ b/noethysweb/outils/views/editeur_emails_express.py @@ -3,13 +3,13 @@ # Noethysweb, application de gestion multi-activités. # Distribué sous licence GNU GPL. -import json -from email.utils import parseaddr from django.http import JsonResponse -from django.shortcuts import render -from core.models import ModeleEmail, Mail, Destinataire, Famille, DocumentJoint +from core.models import ModeleEmail, Mail, PieceJointe, Destinataire, Famille, Individu, Contact, AdresseMail, DocumentJoint from outils.utils import utils_email from outils.forms.editeur_emails_express import Formulaire +from django.shortcuts import render +import json, re +from email.utils import parseaddr def Envoyer_email(request): @@ -79,17 +79,6 @@ def Get_view_editeur_email(request): """ Renvoie l'éditeur d'emails dans un modal """ # Récupère d'éventuelles données donnees = json.loads(request.POST.get("donnees")) - - # Importation de la famille - if "idfamille" in donnees: - categorie = "famille" - famille = Famille.objects.get(pk=donnees["idfamille"]) - adresse = famille.mail - else: - categorie = "saisie_libre" - famille = None - adresse = donnees.get("adresse", "") - # Création du mail modele_email = ModeleEmail.objects.filter(categorie=donnees["categorie"], defaut=True).first() mail = Mail.objects.create( @@ -102,12 +91,25 @@ def Get_view_editeur_email(request): utilisateur=request.user, ) - # Création du destinataire et du document joint - destinataire = Destinataire.objects.create(categorie=categorie, famille=famille, adresse=adresse, valeurs=json.dumps(donnees["champs"])) - if "nom_fichier" in donnees: - document_joint = DocumentJoint.objects.create(nom=donnees["label_fichier"], fichier=donnees["nom_fichier"]) - destinataire.documents.add(document_joint) - mail.destinataires.add(destinataire) + if 'idfamille' in donnees: + + # Importation de la famille + famille = Famille.objects.get(pk=donnees["idfamille"]) + # Création du destinataire et du document joint + destinataire = Destinataire.objects.create(categorie="famille", famille=famille, adresse=famille.mail, valeurs=json.dumps(donnees["champs"])) + if "nom_fichier" in donnees: + document_joint = DocumentJoint.objects.create(nom=donnees["label_fichier"], fichier=donnees["nom_fichier"]) + destinataire.documents.add(document_joint) + mail.destinataires.add(destinataire) + + else : #individu + # Importation de l'individu + individu = Individu.objects.get(pk=donnees["idindividu"]) + destinataire = Destinataire.objects.create(categorie="individu", individu=individu, adresse=individu.mail, valeurs=json.dumps(donnees["champs"])) + if "nom_fichier" in donnees: + document_joint = DocumentJoint.objects.create(nom=donnees["label_fichier"], fichier=donnees["nom_fichier"]) + destinataire.documents.add(document_joint) + mail.destinataires.add(destinataire) # Prépare le context context = { @@ -115,4 +117,4 @@ def Get_view_editeur_email(request): "form": Formulaire(instance=mail, request=request), "modeles": ModeleEmail.objects.filter(categorie=request.POST.get("categorie", donnees["categorie"])), } - return render(request, 'outils/editeur_emails_express.html', context) + return render(request, 'outils/editeur_emails_express.html', context) \ No newline at end of file diff --git a/noethysweb/outils/views/editeur_emails_inscriptions.py b/noethysweb/outils/views/editeur_emails_inscriptions.py new file mode 100644 index 00000000..6dc9282f --- /dev/null +++ b/noethysweb/outils/views/editeur_emails_inscriptions.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2025 Faouzia TEKAYA. +# Noethysweb, application de gestion multi-activités. +# Distribué sous licence GNU GPL. + +import logging + +logger = logging.getLogger(__name__) +from django.urls import reverse_lazy, reverse +from core.views.mydatatableview import MyDatatable, columns, helpers +from core.views import crud +from django.db.models import Q +from core.models import Mail, DocumentJoint, Inscription, Destinataire +from django.http import JsonResponse +from django.contrib import messages +import json + +def Impression_pdf(request): + # Récupération des inscriptions cochées + inscriptions_cochees = json.loads(request.POST.get("inscriptions_cochees")) + if not inscriptions_cochees: + return JsonResponse({"erreur": "Veuillez cocher au moins une inscription dans la liste"}, status=401) + + # Création des PDF + from fiche_individu.utils import utils_inscriptions + inscription = utils_inscriptions.Inscriptions() + resultat = inscription.Impression(liste_inscriptions=inscriptions_cochees, dict_options={}, mode_email=True) + + if not resultat: + return JsonResponse({"success": False}, status=401) + + # Création du mail + logger.debug("Création d'un nouveau mail...") + mail = Mail.objects.create( + categorie="inscription", + objet="Notification d'inscription", + html="", + adresse_exp=request.user.Get_adresse_exp_defaut(), + selection="NON_ENVOYE", + verrouillage_destinataires=True, + utilisateur=request.user, + ) + + # Création des destinataires et des documents joints + logger.debug("Enregistrement des destinataires et documents joints...") + liste_anomalies = [] + for IDcotisation, donnees in resultat["noms_fichiers"].items(): + cotisation = Inscription.objects.select_related('famille').get(pk=IDcotisation) + + if cotisation.famille.mail: + destinataire = Destinataire.objects.create(categorie="famille", famille=cotisation.famille,adresse=cotisation.famille.mail) + document_joint = DocumentJoint.objects.create(nom="Inscription", fichier=donnees["nom_fichier"]) + destinataire.documents.add(document_joint) + mail.destinataires.add(destinataire) + else: + liste_anomalies.append(cotisation.famille.nom) + + if liste_anomalies: + messages.add_message(request, messages.ERROR, "Adresses mail manquantes : %s" % ", ".join(liste_anomalies)) + + # Création de l'URL pour ouvrir l'éditeur d'emails + logger.debug("Redirection vers l'éditeur d'emails...") + url = reverse_lazy("editeur_emails", kwargs={'pk': mail.pk}) + return JsonResponse({"url": url}) + +class Page(crud.Page): + model = Inscription + url_liste = "editeur_emails_inscriptions" + menu_code = "editeur_emails_inscriptions" + +from outils.views.editeur_emails import Page_destinataires +class Liste(Page_destinataires, crud.Liste): + template_name = "outils/editeur_emails_inscriptions.html" + model = Inscription + categorie = "inscriptions" + + def get_queryset(self): + condition = Q(activite__structure__in=self.request.user.structures.all()) + return Inscription.objects.select_related("famille", "individu", "groupe", "categorie_tarif", + "activite").filter(condition) + + def get_context_data(self, **kwargs): + context = super(Liste, self).get_context_data(**kwargs) + context['page_titre'] = "Envoi des Inscriptions par Email" + context['box_titre'] = "Envoyer des Inscriptions par Email en lot" + context['box_introduction'] = "Cochez des inscriptions puis cliquez sur le bouton Aperçu." + context['onglet_actif'] = "inscriptions_email" + context['active_checkbox'] = True + context['bouton_supprimer'] = False + return context + + class datatable_class(MyDatatable): + filtres = ["igenerique:individu", "fgenerique:famille", 'idinscription', 'date_debut', 'date_fin', + 'activite__nom', 'groupe__nom', 'statut', 'categorie_tarif__nom'] + check = columns.CheckBoxSelectColumn(label="") + activite = columns.TextColumn("Activité", sources=['activite__nom']) + groupe = columns.TextColumn("Groupe", sources=['groupe__nom']) + categorie_tarif = columns.TextColumn("Catégorie de tarif", sources=['categorie_tarif__nom']) + individu = columns.CompoundColumn("Individu", sources=['individu__nom', 'individu__prenom']) + famille = columns.TextColumn("Famille", sources=['famille__nom']) + + class Meta: + structure_template = MyDatatable.structure_template + columns = ["check", "idinscription", 'date_debut', 'date_fin', 'individu', 'famille', 'activite', 'groupe', + 'categorie_tarif', 'statut'] + processors = { + 'date_debut': helpers.format_date('%d/%m/%Y'), + 'date_fin': helpers.format_date('%d/%m/%Y'), + 'statut': 'Formate_statut', + } + ordering = ['date_debut'] + + def Formate_statut(self, instance, *args, **kwargs): + if instance.statut == "attente": + return " Attente" + elif instance.statut == "refus": + return " Refus" + else: + return " Valide" \ No newline at end of file diff --git a/noethysweb/outils/views/editeur_sms_express.py b/noethysweb/outils/views/editeur_sms_express.py index a728e5a8..30f05aa5 100644 --- a/noethysweb/outils/views/editeur_sms_express.py +++ b/noethysweb/outils/views/editeur_sms_express.py @@ -6,7 +6,7 @@ import json from django.http import JsonResponse from django.shortcuts import render -from core.models import ModeleSMS, SMS, DestinataireSMS, Famille +from core.models import ModeleSMS, SMS, DestinataireSMS, Famille , Individu from outils.forms.editeur_sms_express import Formulaire from outils.utils import utils_sms @@ -49,9 +49,6 @@ def Get_view_editeur_sms(request): # Récupère d'éventuelles données donnees = json.loads(request.POST.get("donnees")) - # Importation de la famille - famille = Famille.objects.get(pk=donnees["idfamille"]) - # Création du SMS modele_sms = ModeleSMS.objects.filter(categorie=donnees["categorie"], defaut=True).first() sms = SMS.objects.create( @@ -62,9 +59,20 @@ def Get_view_editeur_sms(request): utilisateur=request.user, ) - # Création du destinataire - destinataire = DestinataireSMS.objects.create(categorie="famille", famille=famille, mobile=famille.mobile, valeurs=json.dumps(donnees["champs"])) - sms.destinataires.add(destinataire) + if 'idfamille' in donnees: + + # Importation de la famille + famille = Famille.objects.get(pk=donnees["idfamille"]) + # Création du destinataire + destinataire = DestinataireSMS.objects.create(categorie="famille", famille=famille, mobile=famille.mobile, valeurs=json.dumps(donnees["champs"])) + sms.destinataires.add(destinataire) + + else : #individu + # Importation de l'individu + individu = Individu.objects.get(pk=donnees["idindividu"]) + # Création du destinataire + destinataire = DestinataireSMS.objects.create(categorie="individu", individu=individu, mobile=individu.mobile, valeurs=json.dumps(donnees["champs"])) + sms.destinataires.add(destinataire) # Prépare le context context = { @@ -72,4 +80,4 @@ def Get_view_editeur_sms(request): "form": Formulaire(instance=sms, request=request), "modeles": ModeleSMS.objects.filter(categorie=request.POST.get("categorie", donnees["categorie"])), } - return render(request, 'outils/editeur_sms_express.html', context) + return render(request, 'outils/editeur_sms_express.html', context) \ No newline at end of file diff --git a/noethysweb/outils/views/historique.py b/noethysweb/outils/views/historique.py index f36e4a17..af64c7c9 100644 --- a/noethysweb/outils/views/historique.py +++ b/noethysweb/outils/views/historique.py @@ -32,10 +32,13 @@ class Liste(Page, crud.Liste): template_name = "outils/historique.html" def get_queryset(self): - conditions = (Q(utilisateur__structures__in=self.request.user.structures.all()) | Q(utilisateur__categorie="famille")) + conditions = ( + Q(utilisateur__structures__in=self.request.user.structures.all()) | + Q(utilisateur__categorie__in=["famille", "individu"]) + ) self.afficher_dernier_mois = utils_parametres.Get(nom="afficher_dernier_mois", categorie="historique", utilisateur=self.request.user, valeur=True) if self.afficher_dernier_mois: - conditions &= Q(horodatage__date__gte=datetime.date.today() - datetime.timedelta(days=7)) + conditions &= Q(horodatage__date__gte=datetime.date.today() - datetime.timedelta(days=14)) return Historique.objects.select_related("famille", "individu", "collaborateur", "utilisateur").filter(conditions, self.Get_filtres("Q")) def get_context_data(self, **kwargs): @@ -71,7 +74,15 @@ def Formate_collaborateur(self, instance, **kwargs): return instance.collaborateur.Get_nom() if instance.collaborateur else "" def Formate_utilisateur(self, instance, **kwargs): - if instance.utilisateur.categorie == "utilisateur": - return instance.utilisateur.username if instance.utilisateur else "" - else: - return instance.utilisateur.famille + if instance.utilisateur: + # Vérifie si la catégorie est "utilisateur" + if instance.utilisateur.categorie == "utilisateur": + return instance.utilisateur.username + # Vérifie si la catégorie est "famille" + elif instance.utilisateur.categorie == "famille": + return instance.utilisateur.famille.nom if instance.utilisateur.famille else "" + # Vérifie si la catégorie est "individu" + elif instance.utilisateur.categorie == "individu": + return instance.utilisateur.individu.Get_nom() if instance.utilisateur.individu else "" + # Si aucune catégorie n'est définie, retourne une chaîne vide + return "" \ No newline at end of file diff --git a/noethysweb/parametrage/forms/outils_parametres_generaux.py b/noethysweb/parametrage/forms/outils_parametres_generaux.py new file mode 100644 index 00000000..3013552e --- /dev/null +++ b/noethysweb/parametrage/forms/outils_parametres_generaux.py @@ -0,0 +1,67 @@ +# Copyright (c) 2024 GIP RECIA. +# Noethysweb, application de gestion multi-activités. +# Distribué sous licence GNU GPL. + +from django import forms +from core.forms.base import FormulaireBase +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Layout, Hidden, Submit, HTML, Fieldset, Div, ButtonHolder +from crispy_forms.bootstrap import Field, InlineRadios +from core.utils.utils_commandes import Commandes +from core.models import PortailParametre +from core.utils.utils_parametres_generaux import LISTE_PARAMETRES + + +LISTE_RUBRIQUES = [ + ("Envoi emails par lots", ["emails_individus_afficher_page", "emails_familles_afficher_page", "emails_activites_afficher_page" + ,"emails_inscriptions_afficher_page","emails_collaborateurs_afficher_page","emails_liste_diffusion_afficher_page"]), +] + +class Formulaire(FormulaireBase, forms.Form): + def __init__(self, *args, **kwargs): + super(Formulaire, self).__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.form_id = 'emails_parametres_form' + self.helper.form_method = 'post' + self.helper.form_class = 'form-horizontal' + self.helper.label_class = 'col-md-2' + self.helper.field_class = 'col-md-10' + + # Initialisation du layout + self.helper.layout = Layout() + self.helper.layout.append(Commandes(annuler_url="{% url 'outils_parametres_generaux' %}", ajouter=False)) + + dict_parametres = {parametre.code: parametre for parametre in LISTE_PARAMETRES} + for parametre_db in PortailParametre.objects.all(): + if parametre_db.code in dict_parametres: + dict_parametres[parametre_db.code].From_db(parametre_db.valeur) + + # Création des fields + for titre_rubrique, liste_parametres in LISTE_RUBRIQUES: + liste_fields = [] + for code_parametre in liste_parametres: + self.fields[code_parametre] = dict_parametres[code_parametre].Get_ctrl() + self.fields[code_parametre].initial = dict_parametres[code_parametre].valeur + liste_fields.append(Field(code_parametre)) + self.helper.layout.append(Fieldset(titre_rubrique, *liste_fields)) + + self.helper.layout.append(HTML("- {{ request.user.famille }} -
+ {% if request.user.famille %} +{{ request.user.famille }}
+ {% else %} +{{ request.user.individu }}
+ {% endif %}