Files
n3wt-school/Back-End/Subscriptions/serializers.py
Luc SORIGNET 4248a589c5 feat(frontend): refonte mobile planning et ameliorations suivi pedagogique [#NEWTS-4]
Fonction PWA et ajout du responsive design

Planning mobile :
- Nouvelle vue DayView avec bandeau semaine scrollable, date picker natif et navigation integree
- ScheduleNavigation converti en drawer overlay sur mobile, sidebar fixe sur desktop
- Suppression double barre navigation mobile, controles deplaces dans DayView
- Date picker natif via label+input sur mobile

Suivi pedagogique :
- Refactorisation page grades avec composant Table partage
- Colonnes stats par periode, absences, actions (Fiche + Evaluer)
- Lien cliquable sur la classe vers SchoolClassManagement

feat(backend): ajout associated_class_id dans StudentByRFCreationSerializer [#NEWTS-4]

UI global :
- Remplacement fleches texte par icones Lucide ChevronDown/ChevronRight
- Pagination conditionnelle sur tous les tableaux plats
- Layout responsive mobile : cartes separees fond transparent
- Table.js : pagination optionnelle, wrapper md uniquement
2026-03-16 12:27:06 +01:00

480 lines
20 KiB
Python

from rest_framework import serializers
from .models import (
RegistrationFileGroup,
RegistrationForm,
Student,
Guardian,
Sibling,
Language,
RegistrationSchoolFileMaster,
RegistrationSchoolFileTemplate,
RegistrationParentFileMaster,
RegistrationParentFileTemplate,
AbsenceManagement,
BilanCompetence
)
from School.models import SchoolClass, Fee, Discount, FeeType
from Auth.models import ProfileRole, Profile
from Auth.serializers import ProfileSerializer, ProfileRoleSerializer
from GestionNotification.models import Notification
from N3wtSchool import settings
from django.utils import timezone
import pytz
import Subscriptions.util as util
from N3wtSchool.mailManager import sendRegisterForm
class AbsenceManagementSerializer(serializers.ModelSerializer):
student_name = serializers.SerializerMethodField()
class Meta:
model = AbsenceManagement
fields = '__all__'
# Ajoute les nouveaux champs au retour JSON
extra_fields = ['student_name']
def get_student_name(self, obj):
if obj.student:
return f"{obj.student.first_name} {obj.student.last_name}"
return None
class RegistrationSchoolFileMasterSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
class Meta:
model = RegistrationSchoolFileMaster
fields = '__all__'
class RegistrationParentFileMasterSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
class Meta:
model = RegistrationParentFileMaster
fields = '__all__'
class RegistrationSchoolFileTemplateSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
file_url = serializers.SerializerMethodField()
class Meta:
model = RegistrationSchoolFileTemplate
fields = '__all__'
def get_file_url(self, obj):
# Retourne l'URL complète du fichier si disponible
return obj.file.url if obj.file else None
class RegistrationParentFileTemplateSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
file_url = serializers.SerializerMethodField()
master_name = serializers.CharField(source='master.name', read_only=True)
master_description = serializers.CharField(source='master.description', read_only=True)
is_required = serializers.BooleanField(source='master.is_required', read_only=True)
class Meta:
model = RegistrationParentFileTemplate
fields = '__all__'
def get_file_url(self, obj):
# Retourne l'URL complète du fichier si disponible
return obj.file.url if obj.file else None
class GuardianSimpleSerializer(serializers.ModelSerializer):
associated_profile_email = serializers.SerializerMethodField()
class Meta:
model = Guardian
fields = ['id', 'associated_profile_email']
def get_associated_profile_email(self, obj):
if obj.profile_role and obj.profile_role.profile:
return obj.profile_role.profile.email
return None
class RegistrationFormSimpleSerializer(serializers.ModelSerializer):
guardians = GuardianSimpleSerializer(many=True, source='student.guardians')
last_name = serializers.SerializerMethodField()
first_name = serializers.SerializerMethodField()
class Meta:
model = RegistrationForm
fields = ['student_id', 'last_name', 'first_name', 'guardians']
def get_last_name(self, obj):
return obj.student.last_name
def get_first_name(self, obj):
return obj.student.first_name
class RegistrationFileGroupSerializer(serializers.ModelSerializer):
registration_forms = serializers.SerializerMethodField()
class Meta:
model = RegistrationFileGroup
fields = '__all__'
def get_registration_forms(self, obj):
forms = RegistrationForm.objects.filter(fileGroup=obj)
return RegistrationFormSimpleSerializer(forms, many=True).data
class LanguageSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
class Meta:
model = Language
fields = '__all__'
class SiblingSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
class Meta:
model = Sibling
fields = '__all__'
class GuardianSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
profile_role = serializers.PrimaryKeyRelatedField(queryset=ProfileRole.objects.all(), required=False)
profile_role_data = ProfileRoleSerializer(write_only=True, required=False)
associated_profile_email = serializers.SerializerMethodField()
class Meta:
model = Guardian
fields = '__all__'
def get_associated_profile_email(self, obj):
if obj.profile_role and obj.profile_role.profile:
return obj.profile_role.profile.email
return None
class StudentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
guardians = GuardianSerializer(many=True, required=False)
siblings = SiblingSerializer(many=True, required=False)
spoken_languages = LanguageSerializer(many=True, required=False)
associated_class_id = serializers.PrimaryKeyRelatedField(queryset=SchoolClass.objects.all(), source='associated_class', required=False, write_only=False, read_only=False)
age = serializers.SerializerMethodField()
formatted_birth_date = serializers.SerializerMethodField()
birth_date = serializers.DateField(input_formats=['%d-%m-%Y', '%Y-%m-%d'], required=False, allow_null=True)
associated_class_name = serializers.SerializerMethodField()
class Meta:
model = Student
fields = '__all__'
def create_or_update_guardians(self, guardians_data):
guardians_ids = []
for guardian_data in guardians_data:
guardian_id = guardian_data.get('id', None)
profile_role_data = guardian_data.pop('profile_role_data', None)
profile_role = guardian_data.pop('profile_role', None)
if guardian_id:
# Si un ID est fourni, récupérer ou mettre à jour le Guardian existant
try:
guardian_instance = Guardian.objects.get(id=guardian_id)
# Mettre à jour explicitement tous les champs y compris birth_date, profession, address
for field, value in guardian_data.items():
if field != 'id': # Ne pas mettre à jour l'ID
setattr(guardian_instance, field, value)
guardian_instance.save()
guardians_ids.append(guardian_instance.id)
continue
except Guardian.DoesNotExist:
# Si le guardian n'existe pas, créer un nouveau
guardian_instance = Guardian.objects.create(**guardian_data)
guardians_ids.append(guardian_instance.id)
continue
if profile_role_data:
# Vérifiez si 'profile_data' est fourni pour créer un nouveau profil
profile_data = profile_role_data.pop('profile_data', None)
if profile_data:
# Créer un nouveau profil
profile_serializer = ProfileSerializer(data=profile_data)
profile_serializer.is_valid(raise_exception=True)
profile = profile_serializer.save()
profile.set_password(profile_data['password'])
profile.save()
profile_role_data['profile'] = profile.id # Associer le profil créé
# Vérifiez si 'profile' est un objet ou une clé primaire
if isinstance(profile_role_data.get('profile'), Profile):
profile_role_data['profile'] = profile_role_data['profile'].id
establishment_id = profile_role_data.pop('establishment').id
profile_role_data['establishment'] = establishment_id
# Vérifiez si un ProfileRole existe déjà pour ce profile et cet établissement
existing_profile_role = ProfileRole.objects.filter(
profile_id=profile_role_data['profile'],
establishment=profile_role_data['establishment'],
role_type=profile_role_data['role_type']
).first()
if existing_profile_role:
# Mettre à jour le ProfileRole existant
profile_role_serializer = ProfileRoleSerializer(existing_profile_role, data=profile_role_data)
profile_role_serializer.is_valid(raise_exception=True)
profile_role = profile_role_serializer.save()
else:
# Créer un nouveau ProfileRole si aucun n'existe
profile_role_serializer = ProfileRoleSerializer(data=profile_role_data)
profile_role_serializer.is_valid(raise_exception=True)
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:
# Récupérer un ProfileRole existant par son ID
profile_role = ProfileRole.objects.get(id=profile_role.id)
if profile_role:
guardian_data['profile_role'] = profile_role
# Vérifiez si un Guardian existe déjà pour ce ProfileRole
existing_guardian = Guardian.objects.filter(profile_role=profile_role).first()
if existing_guardian:
# Mettre à jour le Guardian existant
for key, value in guardian_data.items():
setattr(existing_guardian, key, value)
existing_guardian.save()
guardians_ids.append(existing_guardian.id)
else:
# Créer un nouveau Guardian
guardian_instance = Guardian.objects.create(**guardian_data)
guardians_ids.append(guardian_instance.id)
return guardians_ids
def create_or_update_siblings(self, siblings_data, student_instance):
"""
Crée ou met à jour les frères et sœurs associés à un étudiant.
Supprime les frères et sœurs qui ne sont plus présents dans siblings_data.
"""
# Si siblings_data est vide, supprimer tous les frères et sœurs associés
if not siblings_data:
student_instance.siblings.clear() # Supprime toutes les relations
return []
# Récupérer les IDs des frères et sœurs existants
existing_sibling_ids = set(student_instance.siblings.values_list('id', flat=True))
# Créer ou mettre à jour les frères et sœurs
updated_sibling_ids = []
for sibling_data in siblings_data:
sibling_instance, created = Sibling.objects.update_or_create(
id=sibling_data.get('id'),
defaults=sibling_data
)
updated_sibling_ids.append(sibling_instance.id)
# Supprimer les frères et sœurs qui ne sont plus dans siblings_data
siblings_to_delete = existing_sibling_ids - set(updated_sibling_ids)
Sibling.objects.filter(id__in=siblings_to_delete).delete()
return updated_sibling_ids
def create_or_update_languages(self, languages_data):
languages_ids = []
for language_data in languages_data:
language_instance, created = Language.objects.update_or_create(
id=language_data.get('id'),
defaults=language_data
)
languages_ids.append(language_instance.id)
return languages_ids
def create(self, validated_data):
guardians_data = validated_data.pop('guardians', [])
siblings_data = validated_data.pop('siblings', [])
languages_data = validated_data.pop('spoken_languages', [])
student = Student.objects.create(**validated_data)
student.guardians.set(self.create_or_update_guardians(guardians_data))
student.siblings.set(self.create_or_update_siblings(siblings_data, student))
student.spoken_languages.set(self.create_or_update_languages(languages_data))
return student
def update(self, instance, validated_data):
guardians_data = validated_data.pop('guardians', [])
siblings_data = validated_data.pop('siblings', [])
languages_data = validated_data.pop('spoken_languages', [])
if guardians_data:
instance.guardians.set(self.create_or_update_guardians(guardians_data))
sibling_ids = self.create_or_update_siblings(siblings_data, instance)
instance.siblings.set(sibling_ids)
if languages_data:
instance.spoken_languages.set(self.create_or_update_languages(languages_data))
for field in self.fields:
try:
setattr(instance, field, validated_data[field])
except KeyError:
pass
instance.save()
return instance
def get_age(self, obj):
return obj.age
def get_formatted_birth_date(self, obj):
return obj.formatted_birth_date
def get_associated_class_name(self, obj):
return obj.associated_class.atmosphere_name if obj.associated_class else None
class RegistrationFormSerializer(serializers.ModelSerializer):
student = StudentSerializer(many=False, required=False)
registration_file = serializers.FileField(required=False)
sepa_file = serializers.FileField(required=False)
status_label = serializers.SerializerMethodField()
formatted_last_update = serializers.SerializerMethodField()
registration_files = RegistrationSchoolFileTemplateSerializer(many=True, required=False)
fees = serializers.PrimaryKeyRelatedField(queryset=Fee.objects.all(), many=True, required=False)
discounts = serializers.PrimaryKeyRelatedField(queryset=Discount.objects.all(), many=True, required=False)
totalRegistrationFees = serializers.SerializerMethodField()
totalTuitionFees = serializers.SerializerMethodField()
class Meta:
model = RegistrationForm
fields = '__all__'
def create(self, validated_data):
student_data = validated_data.pop('student')
student = StudentSerializer.create(StudentSerializer(), student_data)
fees_data = validated_data.pop('fees', [])
discounts_data = validated_data.pop('discounts', [])
registrationForm = RegistrationForm.objects.create(student=student, **validated_data)
# Associer les IDs des objets Fee et Discount au RegistrationForm
registrationForm.fees.set([fee.id for fee in fees_data])
registrationForm.discounts.set([discount.id for discount in discounts_data])
return registrationForm
def update(self, instance, validated_data):
student_data = validated_data.pop('student', None)
fees_data = validated_data.pop('fees', None)
discounts_data = validated_data.pop('discounts', None)
if student_data:
student = instance.student
StudentSerializer.update(StudentSerializer(), student, student_data)
for field in self.fields:
try:
setattr(instance, field, validated_data[field])
except KeyError:
pass
instance.last_update = util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
instance.save()
# Associer les IDs des objets Fee et Discount au RegistrationForm
if fees_data is not None:
instance.fees.set([fee.id for fee in fees_data])
if discounts_data is not None:
instance.discounts.set([discount.id for discount in discounts_data])
return instance
def get_status_label(self, obj):
return obj.get_status_display()
def get_formatted_last_update(self, obj):
utc_time = timezone.localtime(obj.last_update) # Convert to local time
local_tz = pytz.timezone(settings.TZ_APPLI)
local_time = utc_time.astimezone(local_tz)
return local_time.strftime("%d-%m-%Y %H:%M")
def get_totalRegistrationFees(self, obj):
return sum(fee.base_amount for fee in obj.fees.filter(type=FeeType.REGISTRATION_FEE))
def get_totalTuitionFees(self, obj):
return sum(fee.base_amount for fee in obj.fees.filter(type=FeeType.TUITION_FEE))
class StudentByParentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
associated_class_name = serializers.SerializerMethodField()
class Meta:
model = Student
fields = ['id', 'last_name', 'first_name', 'level', 'photo', 'associated_class_name']
def __init__(self, *args, **kwargs):
super(StudentByParentSerializer, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = False
def get_associated_class_name(self, obj):
return obj.associated_class.atmosphere_name if obj.associated_class else None
class RegistrationFormByParentSerializer(serializers.ModelSerializer):
student = StudentByParentSerializer(many=False, required=True)
class Meta:
model = RegistrationForm
fields = ['student', 'status', 'sepa_file']
def __init__(self, *args, **kwargs):
super(RegistrationFormByParentSerializer, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = False
class GuardianByDICreationSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
associated_profile_email = serializers.SerializerMethodField()
profile = serializers.SerializerMethodField()
class Meta:
model = Guardian
fields = ['id', 'last_name', 'first_name', 'associated_profile_email', 'phone', 'profile_role', 'profile']
def get_associated_profile_email(self, obj):
if obj.profile_role and obj.profile_role.profile:
return obj.profile_role.profile.email
return None
def get_profile(self, obj):
if obj.profile_role and obj.profile_role.profile:
return obj.profile_role.profile.id # Retourne l'ID du profil associé
return None
class BilanCompetenceSerializer(serializers.ModelSerializer):
class Meta:
model = BilanCompetence
fields = ['id', 'file', 'period', 'created_at']
class StudentByRFCreationSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
guardians = GuardianByDICreationSerializer(many=True, required=False)
associated_class_name = serializers.SerializerMethodField()
associated_class_id = serializers.SerializerMethodField()
bilans = BilanCompetenceSerializer(many=True, read_only=True)
class Meta:
model = Student
fields = ['id', 'last_name', 'first_name', 'guardians', 'level', 'associated_class_name', 'associated_class_id', 'photo', 'bilans']
def __init__(self, *args, **kwargs):
super(StudentByRFCreationSerializer, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = False
def get_associated_class_name(self, obj):
return obj.associated_class.atmosphere_name if obj.associated_class else None
def get_associated_class_id(self, obj):
return obj.associated_class.id if obj.associated_class else None
class NotificationSerializer(serializers.ModelSerializer):
notification_type_label = serializers.ReadOnlyField()
class Meta:
model = Notification
fields = '__all__'