chore: Synchronisation IMAP

This commit is contained in:
N3WT DE COMPET
2025-05-27 19:59:53 +02:00
parent c5248d9fd3
commit 39d6a8e909
14 changed files with 187 additions and 47 deletions

View File

@ -2,6 +2,7 @@ from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.core.validators import EmailValidator
from django_mailbox.models import Mailbox
class Profile(AbstractUser):
email = models.EmailField(max_length=255, unique=True, default="", validators=[EmailValidator()])
@ -11,9 +12,13 @@ class Profile(AbstractUser):
roleIndexLoginDefault = models.IntegerField(default=0)
code = models.CharField(max_length=200, default="", blank=True)
datePeremption = models.CharField(max_length=200, default="", blank=True)
def __str__(self):
return self.email
mailbox = models.OneToOneField(
Mailbox,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='profile'
)
class ProfileRole(models.Model):
class RoleType(models.IntegerChoices):

View File

@ -1 +1 @@
default_app_config = 'GestionMessagerie.apps.GestionMessagerieConfig'
default_app_config = 'GestionMessagerie.apps.GestionMessagerieConfig'

View File

@ -1,5 +1,4 @@
from django.apps import AppConfig
class GestionMessagerieConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'GestionMessagerie'
name = 'GestionMessagerie'

View File

@ -1,6 +1,5 @@
from django.db import models
from Auth.models import Profile
from django.utils import timezone
class Messagerie(models.Model):
id = models.AutoField(primary_key=True)

View File

@ -6,7 +6,7 @@ import re
from rest_framework.response import Response
from rest_framework import status
from rest_framework.exceptions import NotFound
from Settings.models import SMTPSettings
from Settings.models import MailSettings
from Establishment.models import Establishment # Importer le modèle Establishment
def getConnection(id_establishement):
@ -15,14 +15,14 @@ def getConnection(id_establishement):
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)
smtp_settings = MailSettings.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,
username=smtp_settings.mail_user,
password=smtp_settings.mail_password,
use_tls=smtp_settings.use_tls,
use_ssl=smtp_settings.use_ssl
)
@ -30,7 +30,7 @@ def getConnection(id_establishement):
except Establishment.DoesNotExist:
raise NotFound(f"Aucun établissement trouvé avec l'ID {id_establishement}")
except SMTPSettings.DoesNotExist:
except MailSettings.DoesNotExist:
raise NotFound(f"Aucun paramètre SMTP trouvé pour l'établissement {id_establishement}")
@ -153,4 +153,25 @@ def isValid(message, fiche_inscription):
responsable = eleve.getMainGuardian()
mailReponsableAVerifier = responsable.mail
return responsableMail == mailReponsableAVerifier and str(idMail) == str(fiche_inscription.eleve.id)
return responsableMail == mailReponsableAVerifier and str(idMail) == str(fiche_inscription.eleve.id)
def test_mailbox_uri(uri):
"""
Teste la validité d'une URI IMAP en tentant une connexion réelle.
Retourne True si la connexion réussit, False sinon.
"""
from django_mailbox.models import Mailbox
try:
mailbox = Mailbox(uri=uri)
conn = mailbox.get_connection()
# Essaye de récupérer un message (ou juste la connexion)
# Pour IMAP, get_message() va ouvrir et fermer la connexion
try:
next(conn.get_message())
except StopIteration:
# Aucun message, mais connexion OK
pass
return True
except Exception as e:
print(f"Erreur de connexion IMAP : {e}")
return False

View File

@ -62,7 +62,8 @@ INSTALLED_APPS = [
'django_celery_beat',
'N3wtSchool',
'drf_yasg',
'rest_framework_simplejwt'
'rest_framework_simplejwt',
'django_mailbox',
]
MIDDLEWARE = [
@ -166,6 +167,16 @@ LOGGING = {
"level": os.getenv("GESTION_ENSEIGNANTS_LOG_LEVEL", "INFO"),
"propagate": False,
},
"django_mailbox": {
"handlers": ["console"],
"level": "DEBUG",
"propagate": False,
},
"django_mailbox.models": {
"handlers": ["console"],
"level": "DEBUG",
"propagate": False,
},
},
}

View File

@ -14,8 +14,9 @@ def setup_periodic_tasks(sender, **kwargs):
# Déclarer la tâche périodique
PeriodicTask.objects.get_or_create(
interval=schedule, # Utiliser l'intervalle défini ci-dessus
name='Tâche périodique toutes les 5 secondes',
task='Subscriptions.tasks.check_for_signature_deadlines', # Remplacer par le nom de ta tâche
kwargs=json.dumps({}) # Si nécessaire, ajoute
interval=schedule,
name='getMail',
task='N3wtSchool.tasks.run_getmail',
kwargs=json.dumps({}),
defaults={"enabled": False}
)

View File

@ -0,0 +1,33 @@
from celery import shared_task
from django_mailbox.models import Message, Mailbox
from django_celery_beat.models import PeriodicTask
import logging
logger = logging.getLogger("django_mailbox")
@shared_task
def run_getmail():
"""
Tâche périodique pour lancer l'import IMAP sur toutes les mailboxes actives
et traiter les nouveaux messages interceptés selon des critères.
"""
logger.info("Début import IMAP")
for mailbox in Mailbox.objects.filter(active=True):
messages = mailbox.get_new_mail()
try:
for message in messages:
logger.info(f"[IMAP] Tentative d'import : {message.subject} de {message.from_address}")
# Filtrage sur le sujet et l'émetteur
if (
message.subject == "n3wt"
and message.from_address == "anthony.casini.30@gmail.com"
):
logger.info(f"[IMAP] Message importé : {message.subject} de {message.from_address}")
else:
# Optionnel : supprimer le message importé qui ne correspond pas
message.delete()
except Exception as e:
logger.error(f"Erreur lors de l'import des messages pour la mailbox {mailbox}: {e}", exc_info=True)
logger.info("Fin import IMAP")

View File

@ -4,14 +4,22 @@ from django.utils.translation import gettext_lazy as _
from django.conf import settings
from Establishment.models import Establishment
class SMTPSettings(models.Model):
class MailSettings(models.Model):
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE)
# Paramètres communs (si tu veux un seul user/pass pour SMTP et IMAP)
mail_user = models.CharField(max_length=255)
mail_password = models.CharField(max_length=255)
# SMTP
smtp_server = models.CharField(max_length=255)
smtp_port = models.PositiveIntegerField()
smtp_user = models.CharField(max_length=255)
smtp_password = models.CharField(max_length=255)
use_tls = models.BooleanField(default=True)
use_ssl = models.BooleanField(default=False)
# IMAP
imap_server = models.CharField(max_length=255)
imap_port = models.PositiveIntegerField(default=993)
def __str__(self):
return f"SMTP Settings ({self.smtp_server}:{self.smtp_port})"
return f"MailSettings ({self.establishment} - {self.mail_user})"

View File

@ -1,7 +1,7 @@
from rest_framework import serializers
from .models import SMTPSettings
from .models import MailSettings
class SMTPSettingsSerializer(serializers.ModelSerializer):
class MailSettingsSerializer(serializers.ModelSerializer):
class Meta:
model = SMTPSettings
model = MailSettings
fields = '__all__'

View File

@ -1,6 +1,7 @@
from django.urls import path
from .views import SMTPSettingsView
from .views import MailSettingsView, SyncImapView
urlpatterns = [
path('smtp-settings/', SMTPSettingsView.as_view(), name='smtp_settings'),
path('mail-settings/', MailSettingsView.as_view(), name='smtp_settings'),
path('sync-imap/', SyncImapView.as_view(), name='sync-imap'),
]

View File

@ -1,12 +1,16 @@
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from .models import SMTPSettings
from .serializers import SMTPSettingsSerializer
from .models import MailSettings
from .serializers import MailSettingsSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django_mailbox.models import Mailbox
from Auth.models import Profile
from django_celery_beat.models import PeriodicTask
import urllib.parse
class SMTPSettingsView(APIView):
class MailSettingsView(APIView):
"""
API pour gérer les paramètres SMTP.
"""
@ -23,7 +27,7 @@ class SMTPSettingsView(APIView):
)
],
responses={
200: SMTPSettingsSerializer(many=True),
200: MailSettingsSerializer(many=True),
404: openapi.Response(description="Aucun paramètre SMTP trouvé."),
500: openapi.Response(description="Erreur interne du serveur."),
},
@ -34,32 +38,48 @@ class SMTPSettingsView(APIView):
try:
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:
mail_settings = MailSettings.objects.filter(establishment_id=establishment_id).first()
if not mail_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)
serializer = MailSettingsSerializer(mail_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():
mail_settings = MailSettings.objects.all()
if not mail_settings.exists():
return Response(
{'error': "Aucun paramètre SMTP trouvé."},
status=status.HTTP_404_NOT_FOUND
)
serializer = SMTPSettingsSerializer(smtp_settings, many=True)
serializer = MailSettingsSerializer(mail_settings, many=True)
# ...dans une vue ou un serializer...
from N3wtSchool.mailManager import test_mailbox_uri
import urllib.parse
imap_user = "anthony.audrey.34@gmail.com"
imap_password = "cztn wyme odjt lbjt"
imap_server = "imap.gmail.com"
imap_port = 993
encoded_user = urllib.parse.quote(imap_user)
encoded_password = urllib.parse.quote(imap_password)
uri = f"imap+ssl://{encoded_user}:{encoded_password}@{imap_server}:{imap_port}"
if not test_mailbox_uri(uri):
print(f'uri : {uri}')
return Response({'error': "Connexion IMAP impossible. Vérifiez les paramètres."}, status=status.HTTP_400_BAD_REQUEST)
# Ensuite, tu peux créer la Mailbox si la connexion est OK
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 pour un établissement spécifique",
request_body=SMTPSettingsSerializer,
request_body=MailSettingsSerializer,
responses={
200: SMTPSettingsSerializer(),
200: MailSettingsSerializer(),
400: openapi.Response(description="Données invalides."),
500: openapi.Response(description="Erreur interne du serveur."),
},
@ -67,15 +87,58 @@ class SMTPSettingsView(APIView):
def post(self, request):
data = request.data
try:
smtp_settings = SMTPSettings.objects.first()
if smtp_settings:
serializer = SMTPSettingsSerializer(smtp_settings, data=data)
mail_settings = MailSettings.objects.first()
if mail_settings:
serializer = MailSettingsSerializer(mail_settings, data=data)
else:
serializer = SMTPSettingsSerializer(data=data)
serializer = MailSettingsSerializer(data=data)
if serializer.is_valid():
serializer.save()
mail_settings_instance = serializer.save()
# Création de la mailbox pour le profil si profil_id fourni
profile_id = data.get('profile_id')
if profile_id:
try:
profile = Profile.objects.get(id=profile_id)
email = mail_settings_instance.mail_user
imap_server = mail_settings_instance.imap_server
imap_port = mail_settings_instance.imap_port
imap_user = mail_settings_instance.mail_user
imap_password = mail_settings_instance.mail_password
# Encodage du username et du mot de passe pour l'URI IMAP
encoded_user = urllib.parse.quote(imap_user)
encoded_password = urllib.parse.quote(imap_password)
uri = f"imap+ssl://{encoded_user}:{encoded_password}@{imap_server}:{imap_port}"
mailbox, created = Mailbox.objects.get_or_create(
name=email,
defaults={
"uri": uri,
"from_email": email,
"active": True,
}
)
# Associer la mailbox au profil si champ prévu
if hasattr(profile, "mailbox"):
profile.mailbox = mailbox
profile.save()
except Profile.DoesNotExist:
pass # Profil non trouvé, on ignore
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class SyncImapView(APIView):
def post(self, request):
sync_imap = request.data.get("sync_imap", False)
try:
task = PeriodicTask.objects.get(name='getMail')
task.enabled = bool(sync_imap)
task.save()
return Response({"success": True, "enabled": task.enabled})
except PeriodicTask.DoesNotExist:
return Response({"error": "Tâche non trouvée."}, status=status.HTTP_404_NOT_FOUND)

View File

@ -4,9 +4,8 @@ from rest_framework import status
from drf_yasg.utils import swagger_auto_schema
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
from django.utils.decorators import method_decorator
from Subscriptions.models import StudentCompetency, Student
from Subscriptions.models import StudentCompetency, Student, BilanCompetence
from Common.models import Domain
from Subscriptions.models import BilanCompetence
from datetime import date
from N3wtSchool.renderers import render_to_pdf
from django.core.files import File

Binary file not shown.