feat: Messagerie WIP [#17]

This commit is contained in:
Luc SORIGNET
2025-05-11 14:02:04 +02:00
parent c6d75281a1
commit 23a593dbc7
28 changed files with 1177 additions and 391 deletions

View File

@ -25,7 +25,7 @@ from django.db.models import Q
from Auth.serializers import ProfileSerializer, ProfileRoleSerializer
from Subscriptions.models import RegistrationForm, Guardian
import Subscriptions.mailManager as mailer
import N3wtSchool.mailManager as mailer
import Subscriptions.util as util
import logging
from N3wtSchool import bdd, error, settings
@ -538,7 +538,7 @@ class ProfileRoleView(APIView):
profiles_roles_List = profiles_roles_List.filter(role_type=ProfileRole.RoleType.PROFIL_PARENT)
elif filter == 'school':
profiles_roles_List = profiles_roles_List.filter(
Q(role_type=ProfileRole.RoleType.PROFIL_ECOLE) |
Q(role_type=ProfileRole.RoleType.PROFIL_ECOLE) |
Q(role_type=ProfileRole.RoleType.PROFIL_ADMIN)
)
else:

View File

@ -1,5 +1,5 @@
from django.urls import path, re_path
from .views import SendEmailView
from .views import SendEmailView, search_recipients
from GestionMessagerie.views import MessagerieView, MessageView, MessageSimpleView
urlpatterns = [
@ -7,4 +7,5 @@ urlpatterns = [
re_path(r'^messages$', MessageView.as_view(), name="messages"),
re_path(r'^messages/(?P<id>[0-9]+)$', MessageSimpleView.as_view(), name="messages"),
path('send-email/', SendEmailView.as_view(), name='send_email'),
path('search-recipients/', search_recipients, name='search_recipients'),
]

View File

@ -6,12 +6,18 @@ from django.utils.html import strip_tags
from django.conf import settings
from rest_framework.response import Response
from rest_framework import status
from django.db.models import Q
from Auth.models import Profile # Assurez-vous que le modèle Profile contient les informations nécessaires
from .models import *
from School.models import Teacher, ProfileRole
from Settings.models import SMTPSettings # Assurez-vous que le chemin est correct
from GestionMessagerie.serializers import MessageSerializer
from School.serializers import TeacherSerializer
from N3wtSchool import bdd
import N3wtSchool.mailManager as mailer
class MessagerieView(APIView):
def get(self, request, profile_id):
@ -46,21 +52,89 @@ class SendEmailView(APIView):
recipients = data.get('recipients', [])
subject = data.get('subject', 'Notification')
message = data.get('message', '')
establishment_id = data.get('establishment_id', '')
if not recipients or not message:
return Response({'error': 'Les destinataires et le message sont requis.'}, status=status.HTTP_400_BAD_REQUEST)
try:
plain_message = strip_tags(message)
send_mail(
subject,
plain_message,
settings.EMAIL_HOST_USER,
recipients,
html_message=message,
fail_silently=False,
# Récupérer la connexion SMTP
connection = mailer.getConnection(establishment_id)
# Envoyer l'email
return mailer.sendMail(
recipients=recipients,
subject=subject,
message=message,
connection=connection
)
return Response({'message': 'Email envoyé avec succès.'}, status=status.HTTP_200_OK)
except NotFound as e:
return Response({'error': str(e)}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class ContactsView(APIView):
"""
API pour récupérer les contacts associés à un établissement.
"""
def get(self, request, establishment_id):
try:
# Récupérer les enseignants associés à l'établissement
teachers = Teacher.objects.filter(profile_role__establishment_id=establishment_id)
teachers_serializer = TeacherSerializer(teachers, many=True)
# Ajouter un contact pour l'administration
admin_contact = {
"id": "admin",
"name": "Administration",
"email": "admin@etablissement.com",
"profilePic": "https://www.gravatar.com/avatar/admin"
}
contacts = [admin_contact] + teachers_serializer.data
return Response(contacts, status=status.HTTP_200_OK)
except Exception as e:
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def search_recipients(request):
"""
API pour rechercher des destinataires en fonction d'un terme de recherche et d'un établissement.
"""
query = request.GET.get('q', '').strip() # Récupérer le terme de recherche depuis les paramètres GET
establishment_id = request.GET.get('establishment_id', None) # Récupérer l'ID de l'établissement
if not query:
return JsonResponse([], safe=False) # Retourner une liste vide si aucun terme n'est fourni
if not establishment_id:
return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
# Rechercher dans les champs pertinents (nom, prénom, email) et filtrer par establishment_id
profiles = Profile.objects.filter(
Q(first_name__icontains=query) |
Q(last_name__icontains=query) |
Q(email__icontains=query),
roles__establishment_id=establishment_id, # Utiliser 'roles' au lieu de 'profilerole'
roles__is_active=True # Filtrer uniquement les ProfileRole actifs
).distinct()
# Construire la réponse avec les rôles associés
results = []
for profile in profiles:
profile_roles = ProfileRole.objects.filter(
profile=profile,
establishment_id=establishment_id,
is_active=True # Inclure uniquement les ProfileRole actifs
).values(
'id', 'role_type', 'establishment__name', 'is_active'
)
results.append({
'id': profile.id,
'first_name': profile.first_name,
'last_name': profile.last_name,
'email': profile.email,
'roles': list(profile_roles) # Inclure tous les rôles actifs associés pour cet établissement
})
return JsonResponse(results, safe=False)

View File

@ -0,0 +1,8 @@
{
"hostSMTP": "",
"portSMTP": 25,
"username": "",
"password": "",
"useSSL": false,
"useTLS": false
}

View File

@ -1,8 +1,50 @@
from django.core.mail import send_mail, EmailMultiAlternatives, EmailMessage
from django.core.mail import send_mail, get_connection, EmailMultiAlternatives, EmailMessage
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.conf import settings
import re
from N3wtSchool import settings
from rest_framework.response import Response
from rest_framework import status
from rest_framework.exceptions import NotFound
from Settings.models import SMTPSettings
from Establishment.models import Establishment # Importer le modèle Establishment
def getConnection(id_establishement):
try:
# Récupérer l'instance de l'établissement
establishment = Establishment.objects.get(id=id_establishement)
# Récupérer les paramètres SMTP associés à l'établissement
smtp_settings = SMTPSettings.objects.get(establishment=establishment)
# Créer une connexion SMTP avec les paramètres récupérés
connection = get_connection(
host=smtp_settings.smtp_server,
port=smtp_settings.smtp_port,
username=smtp_settings.smtp_user,
password=smtp_settings.smtp_password,
use_tls=smtp_settings.use_tls,
use_ssl=smtp_settings.use_ssl
)
return connection
except Establishment.DoesNotExist:
raise NotFound(f"Aucun établissement trouvé avec l'ID {id_establishement}")
except SMTPSettings.DoesNotExist:
raise NotFound(f"Aucun paramètre SMTP trouvé pour l'établissement {id_establishement}")
def sendMail(recipients, subject, message, connection=None):
try:
plain_message = strip_tags(message)
from_email = settings.EMAIL_HOST_USER
if connection is None:
send_mail(subject, plain_message, from_email, recipients, html_message=message, fail_silently=False)
else:
send_mail(subject, plain_message, from_email, recipients, html_message=message, connection=connection, fail_silently=False)
return Response({'message': 'Email envoyé avec succès.'}, status=status.HTTP_200_OK)
except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def envoieReinitMotDePasse(recipients, code):
errorMessage = ''
@ -14,9 +56,8 @@ def envoieReinitMotDePasse(recipients, code):
}
subject = EMAIL_REINIT_SUBJECT
html_message = render_to_string('emails/resetPassword.html', context)
plain_message = strip_tags(html_message)
from_email = settings.EMAIL_HOST_USER
send_mail(subject, plain_message, from_email, [recipients], html_message=html_message)
sendMail(recipients, subject, html_message)
except Exception as e:
errorMessage = str(e)
@ -36,10 +77,8 @@ def sendRegisterForm(recipients, establishment_id):
subject = EMAIL_INSCRIPTION_SUBJECT
html_message = render_to_string('emails/inscription.html', context)
plain_message = strip_tags(html_message)
from_email = settings.EMAIL_HOST_USER
sendMail(recipients, subject, html_message)
send_mail(subject, plain_message, from_email, [recipients], html_message=html_message)
except Exception as e:
errorMessage = str(e)
@ -59,10 +98,7 @@ def sendMandatSEPA(recipients, establishment_id):
subject = EMAIL_INSCRIPTION_SUBJECT
html_message = render_to_string('emails/sepa.html', context)
plain_message = strip_tags(html_message)
from_email = settings.EMAIL_HOST_USER
send_mail(subject, plain_message, from_email, [recipients], html_message=html_message)
sendMail(recipients, subject, html_message)
except Exception as e:
errorMessage = str(e)
@ -74,13 +110,8 @@ def envoieRelanceDossierInscription(recipients, code):
EMAIL_RELANCE_CORPUS = 'Bonjour,\nN\'ayant pas eu de retour de votre part, nous vous renvoyons le lien vers le formulaire d\'inscription : ' + BASE_URL + '/users/login\nCordialement'
errorMessage = ''
try:
send_mail(
EMAIL_RELANCE_SUBJECT,
EMAIL_RELANCE_CORPUS%str(code),
settings.EMAIL_HOST_USER,
[recipients],
fail_silently=False,
)
sendMail(recipients, EMAIL_RELANCE_SUBJECT, EMAIL_RELANCE_CORPUS%str(code))
except Exception as e:
errorMessage = str(e)

View File

@ -14,6 +14,10 @@ from pathlib import Path
import json
import os
from datetime import timedelta
import logging
# Configuration du logger
logger = logging.getLogger(__name__)
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@ -219,23 +223,29 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
#################### Application Settings ##############################
########################################################################
with open('Subscriptions/Configuration/application.json', 'r') as f:
jsonObject = json.load(f)
DJANGO_SUPERUSER_PASSWORD='admin'
DJANGO_SUPERUSER_USERNAME='admin'
DJANGO_SUPERUSER_EMAIL='admin@n3wtschool.com'
# Configuration de l'email de l'application
smtp_config_file = 'N3wtSchool/Configuration/application.json'
EMAIL_HOST='smtp.gmail.com'
EMAIL_PORT=587
EMAIL_HOST_USER=jsonObject['mailFrom']
EMAIL_HOST_PASSWORD=jsonObject['password']
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
EMAIL_USE_SSL = False
if os.path.exists(smtp_config_file):
try:
with open(smtp_config_file, 'r') as f:
smtpSettings = json.load(f)
EMAIL_HOST = smtpSettings.get('hostSMTP', '')
EMAIL_PORT = smtpSettings.get('portSMTP', 587)
EMAIL_HOST_USER = smtpSettings.get('username', '')
EMAIL_HOST_PASSWORD = smtpSettings.get('password', '')
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = smtpSettings.get('useTLS', True)
EMAIL_USE_SSL = smtpSettings.get('useSSL', False)
except Exception as e:
logger.error(f"Erreur lors de la lecture du fichier de configuration SMTP : {e}")
else:
logger.error(f"Fichier de configuration SMTP introuvable : {smtp_config_file}")
DOCUMENT_DIR = 'documents'

View File

@ -12,25 +12,51 @@ class SMTPSettingsView(APIView):
"""
@swagger_auto_schema(
operation_description="Récupérer les paramètres SMTP",
operation_description="Récupérer les paramètres SMTP pour un établissement spécifique ou tous les paramètres si aucun ID n'est fourni",
manual_parameters=[
openapi.Parameter(
'establishment_id',
openapi.IN_QUERY,
description="ID de l'établissement (facultatif)",
type=openapi.TYPE_INTEGER,
required=False
)
],
responses={
200: SMTPSettingsSerializer(),
200: SMTPSettingsSerializer(many=True),
404: openapi.Response(description="Aucun paramètre SMTP trouvé."),
500: openapi.Response(description="Erreur interne du serveur."),
},
)
def get(self, request):
establishment_id = request.query_params.get('establishment_id')
try:
smtp_settings = SMTPSettings.objects.first()
if not smtp_settings:
return Response({'error': 'Aucun paramètre SMTP trouvé.'}, status=status.HTTP_404_NOT_FOUND)
serializer = SMTPSettingsSerializer(smtp_settings)
return Response(serializer.data, status=status.HTTP_200_OK)
if establishment_id:
# Récupérer les paramètres SMTP pour un établissement spécifique
smtp_settings = SMTPSettings.objects.filter(establishment_id=establishment_id).first()
if not smtp_settings:
return Response(
{'error': f"Aucun paramètre SMTP trouvé pour l'établissement {establishment_id}."},
status=status.HTTP_404_NOT_FOUND
)
serializer = SMTPSettingsSerializer(smtp_settings)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
# Récupérer tous les paramètres SMTP
smtp_settings = SMTPSettings.objects.all()
if not smtp_settings.exists():
return Response(
{'error': "Aucun paramètre SMTP trouvé."},
status=status.HTTP_404_NOT_FOUND
)
serializer = SMTPSettingsSerializer(smtp_settings, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@swagger_auto_schema(
operation_description="Créer ou mettre à jour les paramètres SMTP",
operation_description="Créer ou mettre à jour les paramètres SMTP pour un établissement spécifique",
request_body=SMTPSettingsSerializer,
responses={
200: SMTPSettingsSerializer(),

View File

@ -1,4 +0,0 @@
{
"mailFrom":"",
"password":""
}

View File

@ -11,7 +11,7 @@ import json
import os
from django.core.files import File
import Subscriptions.mailManager as mailer
import N3wtSchool.mailManager as mailer
import Subscriptions.util as util
from Subscriptions.serializers import RegistrationFormSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileTemplateSerializer
@ -302,7 +302,7 @@ class RegisterFormWithIdView(APIView):
initial_pdf = f"{base_dir}/Inscription_{registerForm.student.last_name}_{registerForm.student.first_name}.pdf"
registerForm.registration_file = util.rfToPDF(registerForm, initial_pdf)
registerForm.save()
# Mise à jour de l'automate
# Vérification de la présence du fichier SEPA
if registerForm.sepa_file:
@ -331,7 +331,7 @@ class RegisterFormWithIdView(APIView):
# Le parent a rempli le dossier d'inscription en sélectionnant "Prélèvement par Mandat SEPA"
# L'école doit désormais envoyer le mandat SEPA pour poursuivre l'inscription
updateStateMachine(registerForm, 'EVENT_WAITING_FOR_SEPA')
elif _status == RegistrationForm.RegistrationFormStatus.RF_VALIDATED:
# Vérifier si le paramètre fusion est activé via l'URL
fusion = data.get('fusionParam', False)
@ -486,15 +486,15 @@ def get_school_file_templates_by_rf(request, id):
try:
# Récupérer les templates associés au RegistrationForm donné
templates = RegistrationSchoolFileTemplate.objects.filter(registration_form=id)
# Sérialiser les données
serializer = RegistrationSchoolFileTemplateSerializer(templates, many=True)
# Retourner les données sérialisées
return JsonResponse(serializer.data, safe=False)
except RegistrationSchoolFileTemplate.DoesNotExist:
return JsonResponse({'error': 'Aucun template trouvé pour ce dossier d\'inscription'}, status=status.HTTP_404_NOT_FOUND)
@swagger_auto_schema(
method='get',
responses={200: openapi.Response('Success', schema=openapi.Schema(
@ -511,12 +511,11 @@ def get_parent_file_templates_by_rf(request, id):
try:
# Récupérer les pièces à fournir associés au RegistrationForm donné
parent_files = RegistrationParentFileTemplate.objects.filter(registration_form=id)
# Sérialiser les données
serializer = RegistrationParentFileTemplateSerializer(parent_files, many=True)
# Retourner les données sérialisées
return JsonResponse(serializer.data, safe=False)
except RegistrationParentFileTemplate.DoesNotExist:
return JsonResponse({'error': 'Aucune pièce à fournir trouvée pour ce dossier d\'inscription'}, status=status.HTTP_404_NOT_FOUND)