3 Commits

9 changed files with 141 additions and 40 deletions

View File

@ -25,7 +25,7 @@ class ProfileRole(models.Model):
profile = models.ForeignKey('Profile', on_delete=models.CASCADE, related_name='roles') profile = models.ForeignKey('Profile', on_delete=models.CASCADE, related_name='roles')
role_type = models.IntegerField(choices=RoleType.choices, default=RoleType.PROFIL_UNDEFINED) role_type = models.IntegerField(choices=RoleType.choices, default=RoleType.PROFIL_UNDEFINED)
establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE, related_name='profile_roles') establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE, related_name='profile_roles')
is_active = models.BooleanField(default=False) is_active = models.BooleanField(default=False, blank=True)
updated_date = models.DateTimeField(auto_now=True) updated_date = models.DateTimeField(auto_now=True)
def __str__(self): def __str__(self):

View File

@ -1,4 +1,4 @@
from django.core.mail import send_mail, get_connection, EmailMultiAlternatives, EmailMessage from django.core.mail import get_connection, EmailMultiAlternatives, EmailMessage
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.html import strip_tags from django.utils.html import strip_tags
from django.conf import settings from django.conf import settings
@ -207,4 +207,22 @@ def isValid(message, fiche_inscription):
responsable = eleve.getMainGuardian() responsable = eleve.getMainGuardian()
mailReponsableAVerifier = responsable.mail 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 sendRegisterTeacher(recipients, establishment_id):
errorMessage = ''
try:
EMAIL_INSCRIPTION_SUBJECT = '[N3WT-SCHOOL] Bienvenue sur N3wt School (Enseignant)'
context = {
'BASE_URL': settings.BASE_URL,
'URL_DJANGO': settings.URL_DJANGO,
'email': recipients,
'establishment': establishment_id
}
connection = getConnection(establishment_id)
subject = EMAIL_INSCRIPTION_SUBJECT
html_message = render_to_string('emails/inscription_teacher.html', context)
sendMail(subject=subject, message=html_message, recipients=recipients, connection=connection)
except Exception as e:
errorMessage = str(e)
return errorMessage

View File

@ -35,6 +35,7 @@ from collections import defaultdict
from Subscriptions.models import Student, StudentCompetency from Subscriptions.models import Student, StudentCompetency
from Subscriptions.util import getCurrentSchoolYear from Subscriptions.util import getCurrentSchoolYear
import logging import logging
from N3wtSchool.mailManager import sendRegisterForm, sendRegisterTeacher
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -102,8 +103,17 @@ class TeacherListCreateView(APIView):
teacher_serializer = TeacherSerializer(data=teacher_data) teacher_serializer = TeacherSerializer(data=teacher_data)
if teacher_serializer.is_valid(): if teacher_serializer.is_valid():
teacher_serializer.save() teacher_instance = teacher_serializer.save()
# Envoi du mail d'inscription enseignant uniquement à la création
email = None
establishment_id = None
if hasattr(teacher_instance, "profile_role") and teacher_instance.profile_role:
if hasattr(teacher_instance.profile_role, "profile") and teacher_instance.profile_role.profile:
email = teacher_instance.profile_role.profile.email
if hasattr(teacher_instance.profile_role, "establishment") and teacher_instance.profile_role.establishment:
establishment_id = teacher_instance.profile_role.establishment.id
if email and establishment_id:
sendRegisterTeacher(email, establishment_id)
return JsonResponse(teacher_serializer.data, safe=False) return JsonResponse(teacher_serializer.data, safe=False)
return JsonResponse(teacher_serializer.errors, safe=False) return JsonResponse(teacher_serializer.errors, safe=False)

View File

@ -21,6 +21,7 @@ from N3wtSchool import settings
from django.utils import timezone from django.utils import timezone
import pytz import pytz
import Subscriptions.util as util import Subscriptions.util as util
from N3wtSchool.mailManager import sendRegisterForm
class AbsenceManagementSerializer(serializers.ModelSerializer): class AbsenceManagementSerializer(serializers.ModelSerializer):
student_name = serializers.SerializerMethodField() student_name = serializers.SerializerMethodField()
@ -215,6 +216,14 @@ class StudentSerializer(serializers.ModelSerializer):
profile_role_serializer = ProfileRoleSerializer(data=profile_role_data) profile_role_serializer = ProfileRoleSerializer(data=profile_role_data)
profile_role_serializer.is_valid(raise_exception=True) profile_role_serializer.is_valid(raise_exception=True)
profile_role = profile_role_serializer.save() profile_role = profile_role_serializer.save()
# Envoi du mail d'inscription si un nouveau profil vient d'être créé
email = None
if profile_data and 'email' in profile_data:
email = profile_data['email']
elif profile_role and profile_role.profile:
email = profile_role.profile.email
if email:
sendRegisterForm(email, establishment_id)
elif profile_role: elif profile_role:
# Récupérer un ProfileRole existant par son ID # Récupérer un ProfileRole existant par son ID
profile_role = ProfileRole.objects.get(id=profile_role.id) profile_role = ProfileRole.objects.get(id=profile_role.id)

View File

@ -0,0 +1,63 @@
<!-- Nouveau template pour l'inscription d'un enseignant -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Bienvenue sur N3wt School</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.header {
background-color: #f4f4f4;
padding: 10px;
text-align: center;
}
.content {
padding: 20px;
}
.footer {
font-size: 12px;
text-align: center;
margin-top: 30px;
color: #777;
}
.logo {
width: 120px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<!-- Utilisation d'un lien absolu pour le logo -->
<img src="{{URL_DJANGO}}/static/img/logo_min.svg" alt="Logo N3wt School" class="logo" style="display:block;margin:auto;" />
<h1>Bienvenue sur N3wt School</h1>
</div>
<div class="content">
<p>Bonjour,</p>
<p>Votre compte enseignant a été créé sur la plateforme N3wt School.</p>
<p>Pour accéder à votre espace personnel, veuillez vous connecter à l'adresse suivante :<br>
<a href="{{BASE_URL}}/users/login">{{BASE_URL}}/users/login</a>
</p>
<p>Votre identifiant est : <b>{{ email }}</b></p>
<p>Si c'est votre première connexion, veuillez activer votre compte ici :<br>
<a href="{{BASE_URL}}/users/subscribe?establishment_id={{establishment}}">{{BASE_URL}}/users/subscribe</a>
</p>
<p>Nous vous souhaitons une excellente prise en main de l'outil.<br>
L'équipe N3wt School reste à votre disposition pour toute question.</p>
</div>
<div class="footer">
<p>Ce message est généré automatiquement, merci de ne pas y répondre.</p>
</div>
</div>
</body>
</html>

View File

@ -323,6 +323,27 @@ class RegisterFormWithIdView(APIView):
registerForm.registration_file = util.rfToPDF(registerForm, initial_pdf) registerForm.registration_file = util.rfToPDF(registerForm, initial_pdf)
registerForm.save() registerForm.save()
# Envoi du mail d'inscription au second guardian si besoin
guardians = registerForm.student.guardians.all()
from Auth.models import Profile
from N3wtSchool.mailManager import sendRegisterForm
for guardian in guardians:
# Recherche de l'email dans le profil lié au guardian (si existant)
email = None
if hasattr(guardian, "profile_role") and guardian.profile_role and hasattr(guardian.profile_role, "profile") and guardian.profile_role.profile:
email = guardian.profile_role.profile.email
# Fallback sur le champ email direct (si jamais il existe)
if not email:
email = getattr(guardian, "email", None)
logger.debug(f"[RF_UNDER_REVIEW] Guardian id={guardian.id}, email={email}")
if email:
profile_exists = Profile.objects.filter(email=email).exists()
logger.debug(f"[RF_UNDER_REVIEW] Profile existe pour {email} ? {profile_exists}")
if not profile_exists:
logger.debug(f"[RF_UNDER_REVIEW] Envoi du mail d'inscription à {email} pour l'établissement {registerForm.establishment.pk}")
sendRegisterForm(email, registerForm.establishment.pk)
# Mise à jour de l'automate # Mise à jour de l'automate
# Vérification de la présence du fichier SEPA # Vérification de la présence du fichier SEPA
if registerForm.sepa_file: if registerForm.sepa_file:
@ -332,6 +353,9 @@ class RegisterFormWithIdView(APIView):
# Mise à jour de l'automate pour une signature classique # Mise à jour de l'automate pour une signature classique
updateStateMachine(registerForm, 'EVENT_SIGNATURE') updateStateMachine(registerForm, 'EVENT_SIGNATURE')
except Exception as e: except Exception as e:
logger.error(f"[RF_UNDER_REVIEW] Exception: {e}")
import traceback
logger.error(traceback.format_exc())
return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
elif _status == RegistrationForm.RegistrationFormStatus.RF_SENT: elif _status == RegistrationForm.RegistrationFormStatus.RF_SENT:
if registerForm.status == RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW: if registerForm.status == RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW:

View File

@ -77,7 +77,7 @@ export default function InscriptionFormShared({
const [parentFileTemplates, setParentFileTemplates] = useState([]); const [parentFileTemplates, setParentFileTemplates] = useState([]);
const [schoolFileMasters, setSchoolFileMasters] = useState([]); const [schoolFileMasters, setSchoolFileMasters] = useState([]);
const [formResponses, setFormResponses] = useState({}); const [formResponses, setFormResponses] = useState({});
const [currentPage, setCurrentPage] = useState(5); const [currentPage, setCurrentPage] = useState(1);
const [isPage1Valid, setIsPage1Valid] = useState(false); const [isPage1Valid, setIsPage1Valid] = useState(false);
const [isPage2Valid, setIsPage2Valid] = useState(false); const [isPage2Valid, setIsPage2Valid] = useState(false);

View File

@ -100,7 +100,7 @@ export default function ResponsableInputFields({
profile_role_data: { profile_role_data: {
establishment: selectedEstablishmentId, establishment: selectedEstablishmentId,
role_type: 2, role_type: 2,
is_active: true, is_active: false,
profile_data: { profile_data: {
email: '', email: '',
password: 'Provisoire01!', password: 'Provisoire01!',

View File

@ -140,38 +140,19 @@ const TeachersSection = ({
const [removePopupMessage, setRemovePopupMessage] = useState(''); const [removePopupMessage, setRemovePopupMessage] = useState('');
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {}); const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
const [confirmPopupVisible, setConfirmPopupVisible] = useState(false);
const [confirmPopupMessage, setConfirmPopupMessage] = useState('');
const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = useState(() => {});
const { selectedEstablishmentId } = useEstablishment(); const { selectedEstablishmentId } = useEstablishment();
// --- UTILS --- // --- UTILS ---
// Retourne le profil existant pour un email, utilisé par un teacher actif ou le teacher en cours d'édition // Retourne le profil existant pour un email
const getUsedProfileForEmail = (email, teacherId = null) => { const getUsedProfileForEmail = (email) => {
const usedProfileIds = new Set( // On cherche tous les profils dont l'email correspond
teachers.map(t => t.profile_role && t.profile).filter(Boolean) const matchingProfiles = profiles.filter(p => p.email === email);
);
// Ajoute le profil du teacher en cours d'édition si besoin // On retourne le premier profil correspondant (ou undefined)
if (teacherId) { const result = matchingProfiles.length > 0 ? matchingProfiles[0] : undefined;
const currentTeacher = teachers.find(t => t.id === teacherId);
if (currentTeacher && currentTeacher.profile_role && currentTeacher.profile) { return result;
const profileObj = profiles.find(p => p.id === currentTeacher.profile);
if (profileObj && profileObj.email === email) {
usedProfileIds.add(profileObj.id);
}
} else {
// Cas création immédiate : on cherche le profil par email dans profiles
const profileObj = profiles.find(p => p.email === email);
if (profileObj) {
usedProfileIds.add(profileObj.id);
}
}
}
return profiles.find(
(profile) => profile.email === email && usedProfileIds.has(profile.id)
);
}; };
// Met à jour le formData et newTeacher si besoin // Met à jour le formData et newTeacher si besoin
@ -189,7 +170,7 @@ const TeachersSection = ({
const handleEmailChange = (e) => { const handleEmailChange = (e) => {
const email = e.target.value; const email = e.target.value;
const existingProfile = getUsedProfileForEmail(email, editingTeacher); const existingProfile = getUsedProfileForEmail(email);
if (existingProfile) { if (existingProfile) {
logger.info(`Adresse email déjà utilisée pour le profil ${existingProfile.id}`); logger.info(`Adresse email déjà utilisée pour le profil ${existingProfile.id}`);
@ -290,9 +271,6 @@ const TeachersSection = ({
}; };
const handleUpdateTeacher = (id, updatedData) => { const handleUpdateTeacher = (id, updatedData) => {
// Simplification : le profil est forcément existant, on utilise directement existingProfileId du formData
const currentTeacher = teachers.find((teacher) => teacher.id === id);
if ( if (
updatedData.last_name && updatedData.last_name &&
updatedData.first_name && updatedData.first_name &&
@ -302,7 +280,6 @@ const TeachersSection = ({
id: updatedData.profile_role, id: updatedData.profile_role,
establishment: selectedEstablishmentId, establishment: selectedEstablishmentId,
role_type: updatedData.role_type || 0, role_type: updatedData.role_type || 0,
is_active: true,
profile: updatedData.existingProfileId, profile: updatedData.existingProfileId,
}; };