mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-03 16:51:26 +00:00
Compare commits
3 Commits
176edc5c45
...
26d4b5633f
| Author | SHA1 | Date | |
|---|---|---|---|
| 26d4b5633f | |||
| d66db1b019 | |||
| bd7dc2b0c2 |
@ -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):
|
||||||
|
|||||||
@ -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
|
||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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>
|
||||||
@ -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:
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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!',
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user