diff --git a/Back-End/Auth/models.py b/Back-End/Auth/models.py index 95b36ea..7dfa25f 100644 --- a/Back-End/Auth/models.py +++ b/Back-End/Auth/models.py @@ -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): diff --git a/Back-End/GestionMessagerie/__init__.py b/Back-End/GestionMessagerie/__init__.py index db2f57d..abdfd0b 100644 --- a/Back-End/GestionMessagerie/__init__.py +++ b/Back-End/GestionMessagerie/__init__.py @@ -1 +1 @@ -default_app_config = 'GestionMessagerie.apps.GestionMessagerieConfig' +default_app_config = 'GestionMessagerie.apps.GestionMessagerieConfig' \ No newline at end of file diff --git a/Back-End/GestionMessagerie/apps.py b/Back-End/GestionMessagerie/apps.py index b9adf5a..b732e41 100644 --- a/Back-End/GestionMessagerie/apps.py +++ b/Back-End/GestionMessagerie/apps.py @@ -1,5 +1,4 @@ from django.apps import AppConfig class GestionMessagerieConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'GestionMessagerie' + name = 'GestionMessagerie' \ No newline at end of file diff --git a/Back-End/GestionMessagerie/models.py b/Back-End/GestionMessagerie/models.py index 6dec066..51b1c0e 100644 --- a/Back-End/GestionMessagerie/models.py +++ b/Back-End/GestionMessagerie/models.py @@ -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) diff --git a/Back-End/N3wtSchool/mailManager.py b/Back-End/N3wtSchool/mailManager.py index aafe115..72ebd52 100644 --- a/Back-End/N3wtSchool/mailManager.py +++ b/Back-End/N3wtSchool/mailManager.py @@ -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) \ No newline at end of file + 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 \ No newline at end of file diff --git a/Back-End/N3wtSchool/settings.py b/Back-End/N3wtSchool/settings.py index bd12a4e..32f00f1 100644 --- a/Back-End/N3wtSchool/settings.py +++ b/Back-End/N3wtSchool/settings.py @@ -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, + }, }, } diff --git a/Back-End/N3wtSchool/signals.py b/Back-End/N3wtSchool/signals.py index a76f431..fd80032 100644 --- a/Back-End/N3wtSchool/signals.py +++ b/Back-End/N3wtSchool/signals.py @@ -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} ) \ No newline at end of file diff --git a/Back-End/N3wtSchool/tasks.py b/Back-End/N3wtSchool/tasks.py new file mode 100644 index 0000000..67fa285 --- /dev/null +++ b/Back-End/N3wtSchool/tasks.py @@ -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") \ No newline at end of file diff --git a/Back-End/Settings/models.py b/Back-End/Settings/models.py index 9f3a999..d6f6dcc 100644 --- a/Back-End/Settings/models.py +++ b/Back-End/Settings/models.py @@ -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})" \ No newline at end of file + return f"MailSettings ({self.establishment} - {self.mail_user})" \ No newline at end of file diff --git a/Back-End/Settings/serializers.py b/Back-End/Settings/serializers.py index 380e5e9..75f4963 100644 --- a/Back-End/Settings/serializers.py +++ b/Back-End/Settings/serializers.py @@ -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__' \ No newline at end of file diff --git a/Back-End/Settings/urls.py b/Back-End/Settings/urls.py index a8842b8..ce799dc 100644 --- a/Back-End/Settings/urls.py +++ b/Back-End/Settings/urls.py @@ -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'), ] \ No newline at end of file diff --git a/Back-End/Settings/views.py b/Back-End/Settings/views.py index c13c356..bb371db 100644 --- a/Back-End/Settings/views.py +++ b/Back-End/Settings/views.py @@ -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) \ No newline at end of file + 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) \ No newline at end of file diff --git a/Back-End/Subscriptions/views/student_competencies_views.py b/Back-End/Subscriptions/views/student_competencies_views.py index b803249..914940a 100644 --- a/Back-End/Subscriptions/views/student_competencies_views.py +++ b/Back-End/Subscriptions/views/student_competencies_views.py @@ -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 diff --git a/Back-End/requirements.txt b/Back-End/requirements.txt index 68b0d90..cee4a15 100644 Binary files a/Back-End/requirements.txt and b/Back-End/requirements.txt differ