mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
604 lines
27 KiB
Python
604 lines
27 KiB
Python
from django.db import models
|
||
from django.utils.timezone import now
|
||
from django.conf import settings
|
||
from django.utils.translation import gettext_lazy as _
|
||
from datetime import datetime
|
||
import logging
|
||
import os
|
||
|
||
logger = logging.getLogger("SubscriptionModels")
|
||
|
||
class Language(models.Model):
|
||
"""
|
||
Représente une langue parlée par l’élève.
|
||
"""
|
||
id = models.AutoField(primary_key=True)
|
||
label = models.CharField(max_length=200, default="")
|
||
|
||
def __str__(self):
|
||
return "LANGUAGE"
|
||
|
||
class Guardian(models.Model):
|
||
"""
|
||
Représente un responsable légal (parent/tuteur) d’un élève.
|
||
"""
|
||
last_name = models.CharField(max_length=200, null=True, blank=True)
|
||
first_name = models.CharField(max_length=200, null=True, blank=True)
|
||
birth_date = models.DateField(null=True, blank=True)
|
||
address = models.CharField(max_length=200, default="", blank=True)
|
||
phone = models.CharField(max_length=200, default="", blank=True)
|
||
profession = models.CharField(max_length=200, default="", blank=True)
|
||
profile_role = models.OneToOneField('Auth.ProfileRole', on_delete=models.CASCADE, related_name='guardian_profile', null=True, blank=True)
|
||
|
||
@property
|
||
def email(self):
|
||
"""
|
||
Retourne l'email du profil associé via le ProfileRole.
|
||
"""
|
||
if self.profile_role and self.profile_role.profile:
|
||
return self.profile_role.profile.email
|
||
return None
|
||
|
||
def __str__(self):
|
||
return self.last_name + "_" + self.first_name
|
||
|
||
class Sibling(models.Model):
|
||
"""
|
||
Représente un frère ou une sœur d’un élève.
|
||
"""
|
||
last_name = models.CharField(max_length=200, null=True, blank=True)
|
||
first_name = models.CharField(max_length=200, null=True, blank=True)
|
||
birth_date = models.DateField(null=True, blank=True)
|
||
|
||
def __str__(self):
|
||
return "SIBLING"
|
||
|
||
def registration_photo_upload_to(instance, filename):
|
||
return f"registration_files/dossier_rf_{instance.pk}/parent/{filename}"
|
||
|
||
def registration_bilan_form_upload_to(instance, filename):
|
||
# On récupère le RegistrationForm lié à l'élève
|
||
register_form = getattr(instance.student, 'registrationform', None)
|
||
if register_form:
|
||
pk = register_form.pk
|
||
else:
|
||
# fallback sur l'id de l'élève si pas de registrationform
|
||
pk = instance.student.pk
|
||
return f"registration_files/dossier_rf_{pk}/bilan/{filename}"
|
||
|
||
class BilanCompetence(models.Model):
|
||
student = models.ForeignKey('Subscriptions.Student', on_delete=models.CASCADE, related_name='bilans')
|
||
file = models.FileField(upload_to=registration_bilan_form_upload_to, null=True, blank=True)
|
||
period = models.CharField(max_length=20, help_text="Période ex: T1-2024_2025, S1-2024_2025, A-2024_2025")
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
|
||
def __str__(self):
|
||
return f"{self.student} - {self.period}"
|
||
|
||
class Student(models.Model):
|
||
"""
|
||
Représente l’élève inscrit ou en cours d’inscription.
|
||
"""
|
||
class StudentGender(models.IntegerChoices):
|
||
NONE = 0, _('Sélection du genre')
|
||
MALE = 1, _('Garçon')
|
||
FEMALE = 2, _('Fille')
|
||
|
||
class StudentLevel(models.IntegerChoices):
|
||
NONE = 0, _('Sélection du niveau')
|
||
TPS = 1, _('TPS - Très Petite Section')
|
||
PS = 2, _('PS - Petite Section')
|
||
MS = 3, _('MS - Moyenne Section')
|
||
GS = 4, _('GS - Grande Section')
|
||
CP = 5, _('CP')
|
||
CE1 = 6, _('CE1')
|
||
CE2 = 7, _('CE2')
|
||
CM1 = 8, _('CM1')
|
||
CM2 = 9, _('CM2')
|
||
|
||
photo = models.FileField(
|
||
upload_to=registration_photo_upload_to,
|
||
null=True,
|
||
blank=True
|
||
)
|
||
last_name = models.CharField(max_length=200, default="")
|
||
first_name = models.CharField(max_length=200, default="")
|
||
gender = models.IntegerField(choices=StudentGender, default=StudentGender.NONE, blank=True)
|
||
level = models.ForeignKey('Common.Level', on_delete=models.SET_NULL, null=True, blank=True, related_name='students')
|
||
nationality = models.CharField(max_length=200, default="", blank=True)
|
||
address = models.CharField(max_length=200, default="", blank=True)
|
||
birth_date = models.DateField(null=True, blank=True)
|
||
birth_place = models.CharField(max_length=200, default="", blank=True)
|
||
birth_postal_code = models.IntegerField(default=0, blank=True)
|
||
attending_physician = models.CharField(max_length=200, default="", blank=True)
|
||
|
||
# Many-to-Many Relationship
|
||
profiles = models.ManyToManyField('Auth.Profile', blank=True)
|
||
|
||
# Many-to-Many Relationship
|
||
guardians = models.ManyToManyField('Subscriptions.Guardian', blank=True)
|
||
|
||
# Many-to-Many Relationship
|
||
siblings = models.ManyToManyField('Subscriptions.Sibling', blank=True)
|
||
|
||
# Many-to-Many Relationship
|
||
registration_files = models.ManyToManyField('Subscriptions.RegistrationSchoolFileTemplate', blank=True, related_name='students')
|
||
|
||
# Many-to-Many Relationship
|
||
spoken_languages = models.ManyToManyField('Subscriptions.Language', blank=True)
|
||
|
||
# One-to-Many Relationship
|
||
associated_class = models.ForeignKey('School.SchoolClass', on_delete=models.SET_NULL, null=True, blank=True, related_name='students')
|
||
|
||
def __str__(self):
|
||
return self.last_name + "_" + self.first_name
|
||
|
||
def getSpokenLanguages(self):
|
||
"""
|
||
Retourne la liste des langues parlées par l’élève.
|
||
"""
|
||
return self.spoken_languages.all()
|
||
|
||
def getMainGuardian(self):
|
||
"""
|
||
Retourne le responsable légal principal de l’élève.
|
||
"""
|
||
return self.guardians.all()[0]
|
||
|
||
def getGuardians(self):
|
||
"""
|
||
Retourne tous les responsables légaux de l’élève.
|
||
"""
|
||
return self.guardians.all()
|
||
|
||
def getProfiles(self):
|
||
"""
|
||
Retourne les profils utilisateurs liés à l’élève.
|
||
"""
|
||
return self.profiles.all()
|
||
|
||
def getSiblings(self):
|
||
"""
|
||
Retourne les frères et sœurs de l’élève.
|
||
"""
|
||
return self.siblings.all()
|
||
|
||
def getNumberOfSiblings(self):
|
||
"""
|
||
Retourne le nombre de frères et sœurs.
|
||
"""
|
||
return self.siblings.count()
|
||
|
||
def get_photo_url(self):
|
||
"""
|
||
Retourne le chemin correct de la photo pour le template HTML.
|
||
Si la photo n'existe pas, retourne le chemin de l'image par défaut.
|
||
"""
|
||
if self.photo and hasattr(self.photo, 'url'):
|
||
# Retourne l'URL complète de la photo
|
||
return self.photo.url
|
||
|
||
@property
|
||
def age(self):
|
||
if self.birth_date:
|
||
today = datetime.today()
|
||
years = today.year - self.birth_date.year
|
||
months = today.month - self.birth_date.month
|
||
if today.day < self.birth_date.day:
|
||
months -= 1
|
||
if months < 0:
|
||
years -= 1
|
||
months += 12
|
||
|
||
# Determine the age format
|
||
if 6 <= months <= 12:
|
||
return f"{years} years 1/2"
|
||
else:
|
||
return f"{years} years"
|
||
return None
|
||
|
||
@property
|
||
def formatted_birth_date(self):
|
||
if self.birth_date:
|
||
return self.birth_date.strftime('%d-%m-%Y')
|
||
return None
|
||
|
||
class RegistrationFileGroup(models.Model):
|
||
name = models.CharField(max_length=255, default="")
|
||
description = models.TextField(blank=True, null=True)
|
||
establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE, related_name='file_group', null=True, blank=True)
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
def __str__(self):
|
||
return f'{self.group.name} - {self.id}'
|
||
|
||
def registration_file_path(instance, filename):
|
||
# Génère le chemin : registration_files/dossier_rf_{student_id}/filename
|
||
return f'registration_files/dossier_rf_{instance.student_id}/{filename}'
|
||
|
||
class RegistrationForm(models.Model):
|
||
class RegistrationFormStatus(models.IntegerChoices):
|
||
RF_IDLE = 0, _('Pas de dossier d\'inscription')
|
||
RF_INITIALIZED = 1, _('Dossier d\'inscription initialisé')
|
||
RF_SENT = 2, _('Dossier d\'inscription envoyé')
|
||
RF_UNDER_REVIEW = 3, _('Dossier d\'inscription en cours de validation')
|
||
RF_TO_BE_FOLLOWED_UP = 4, _('Dossier d\'inscription à relancer')
|
||
RF_VALIDATED = 5, _('Dossier d\'inscription validé')
|
||
RF_ARCHIVED = 6, _('Dossier d\'inscription archivé')
|
||
RF_SEPA_SENT = 7, _('Mandat SEPA envoyé')
|
||
RF_SEPA_TO_SEND = 8, _('Mandat SEPA à envoyer')
|
||
|
||
# One-to-One Relationship
|
||
student = models.OneToOneField(Student, on_delete=models.CASCADE, primary_key=True)
|
||
status = models.IntegerField(choices=RegistrationFormStatus, default=RegistrationFormStatus.RF_IDLE)
|
||
last_update = models.DateTimeField(auto_now=True)
|
||
school_year = models.CharField(max_length=9, default="", blank=True)
|
||
notes = models.CharField(max_length=200, blank=True)
|
||
registration_link_code = models.CharField(max_length=200, default="", blank=True)
|
||
registration_file = models.FileField(
|
||
upload_to=registration_file_path,
|
||
null=True,
|
||
blank=True
|
||
)
|
||
sepa_file = models.FileField(
|
||
upload_to=registration_file_path,
|
||
null=True,
|
||
blank=True
|
||
)
|
||
fusion_file = models.FileField(
|
||
upload_to=registration_file_path,
|
||
null=True,
|
||
blank=True
|
||
)
|
||
associated_rf = models.CharField(max_length=200, default="", blank=True)
|
||
|
||
# Many-to-Many Relationship
|
||
fees = models.ManyToManyField('School.Fee', blank=True, related_name='register_forms')
|
||
|
||
# Many-to-Many Relationship
|
||
discounts = models.ManyToManyField('School.Discount', blank=True, related_name='register_forms')
|
||
fileGroup = models.ForeignKey(RegistrationFileGroup,
|
||
on_delete=models.SET_NULL,
|
||
related_name='register_forms',
|
||
null=True,
|
||
blank=True)
|
||
|
||
establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE, related_name='register_forms')
|
||
registration_payment = models.ForeignKey('School.PaymentMode', on_delete=models.SET_NULL, null=True, blank=True, related_name='registration_payment_modes_forms')
|
||
tuition_payment = models.ForeignKey('School.PaymentMode', on_delete=models.SET_NULL, null=True, blank=True, related_name='tuition_payment_modes_forms')
|
||
registration_payment_plan = models.ForeignKey('School.PaymentPlan', on_delete=models.SET_NULL, null=True, blank=True, related_name='registration_payment_plans_forms')
|
||
tuition_payment_plan = models.ForeignKey('School.PaymentPlan', on_delete=models.SET_NULL, null=True, blank=True, related_name='tuition_payment_plans_forms')
|
||
|
||
def __str__(self):
|
||
return "RF_" + self.student.last_name + "_" + self.student.first_name
|
||
|
||
def save(self, *args, **kwargs):
|
||
# Préparer le flag de création / changement de fileGroup
|
||
was_new = self.pk is None
|
||
old_fileGroup = None
|
||
if not was_new:
|
||
try:
|
||
old_instance = RegistrationForm.objects.get(pk=self.pk)
|
||
old_fileGroup = old_instance.fileGroup
|
||
except RegistrationForm.DoesNotExist:
|
||
old_fileGroup = None
|
||
|
||
# Vérifier si un fichier existant doit être remplacé
|
||
if self.pk: # Si l'objet existe déjà dans la base de données
|
||
try:
|
||
old_instance = RegistrationForm.objects.get(pk=self.pk)
|
||
if old_instance.sepa_file and old_instance.sepa_file != self.sepa_file:
|
||
# Supprimer l'ancien fichier
|
||
old_instance.sepa_file.delete(save=False)
|
||
except RegistrationForm.DoesNotExist:
|
||
pass # L'objet n'existe pas encore, rien à supprimer
|
||
|
||
# Appeler la méthode save originale
|
||
super().save(*args, **kwargs)
|
||
|
||
# Après save : si nouveau ou changement de fileGroup -> créer les templates
|
||
fileGroup_changed = (self.fileGroup is not None) and (old_fileGroup is None or (old_fileGroup and old_fileGroup.id != self.fileGroup.id))
|
||
if was_new or fileGroup_changed:
|
||
try:
|
||
import Subscriptions.util as util
|
||
created = util.create_templates_for_registration_form(self)
|
||
if created:
|
||
logger.info("Created %d templates for RegistrationForm %s", len(created), self.pk)
|
||
except Exception as e:
|
||
logger.exception("Error creating templates for RegistrationForm %s: %s", self.pk, e)
|
||
|
||
#############################################################
|
||
####################### MASTER FILES ########################
|
||
#############################################################
|
||
|
||
####### Formulaires masters (documents école, à signer ou pas) #######
|
||
def registration_school_file_master_upload_to(instance, filename):
|
||
# Stocke les fichiers masters dans un dossier dédié
|
||
# Utilise l'ID si le nom n'est pas encore disponible
|
||
est_name = None
|
||
if instance.establishment and instance.establishment.name:
|
||
est_name = instance.establishment.name
|
||
else:
|
||
# fallback si pas d'établissement (devrait être rare)
|
||
est_name = "unknown_establishment"
|
||
return f"{est_name}/Formulaires/{filename}"
|
||
|
||
class RegistrationSchoolFileMaster(models.Model):
|
||
groups = models.ManyToManyField(RegistrationFileGroup, related_name='school_file_masters', blank=True)
|
||
name = models.CharField(max_length=255, default="")
|
||
is_required = models.BooleanField(default=False)
|
||
formMasterData = models.JSONField(default=list, blank=True, null=True)
|
||
file = models.FileField(
|
||
upload_to=registration_school_file_master_upload_to,
|
||
null=True,
|
||
blank=True,
|
||
help_text="Fichier du formulaire existant (PDF, DOC, etc.)"
|
||
)
|
||
establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE, related_name='school_file_masters', null=True, blank=True)
|
||
|
||
def __str__(self):
|
||
return f'{self.name} - {self.id}'
|
||
|
||
@property
|
||
def file_url(self):
|
||
if self.file and hasattr(self.file, 'url'):
|
||
return self.file.url
|
||
return None
|
||
|
||
def save(self, *args, **kwargs):
|
||
affected_rf_ids = set()
|
||
is_new = self.pk is None
|
||
|
||
# Log création ou modification du master
|
||
if is_new:
|
||
logger.info(f"[FormPerso] Création master '{self.name}' pour établissement '{self.establishment}'")
|
||
else:
|
||
logger.info(f"[FormPerso] Modification master '{self.name}' (id={self.pk}) pour établissement '{self.establishment}'")
|
||
|
||
# --- Suppression de l'ancien fichier master si le nom change (form existant ou dynamique) ---
|
||
if self.pk:
|
||
try:
|
||
old = RegistrationSchoolFileMaster.objects.get(pk=self.pk)
|
||
if old.file and old.file.name:
|
||
old_filename = os.path.basename(old.file.name)
|
||
# Nouveau nom selon le type (dynamique ou existant)
|
||
if (
|
||
self.formMasterData
|
||
and isinstance(self.formMasterData, dict)
|
||
and self.formMasterData.get("fields")
|
||
):
|
||
new_filename = f"{self.name}.pdf"
|
||
else:
|
||
# Pour les forms existants, le nom attendu est self.name + extension du fichier existant
|
||
extension = os.path.splitext(old_filename)[1]
|
||
new_filename = f"{self.name}{extension}" if extension else self.name
|
||
if new_filename and old_filename != new_filename:
|
||
old_file_path = old.file.path
|
||
if os.path.exists(old_file_path):
|
||
try:
|
||
os.remove(old_file_path)
|
||
logger.info(f"[FormPerso] Suppression de l'ancien fichier master: {old_file_path}")
|
||
except Exception as e:
|
||
logger.error(f"[FormPerso] Erreur suppression ancien fichier master: {e}")
|
||
# Correction du nom du fichier pour éviter le suffixe random
|
||
if (
|
||
not self.formMasterData
|
||
or not (isinstance(self.formMasterData, dict) and self.formMasterData.get("fields"))
|
||
):
|
||
# Si le fichier existe et le nom ne correspond pas, renommer le fichier physique et mettre à jour le FileField
|
||
if self.file and self.file.name:
|
||
current_filename = os.path.basename(self.file.name)
|
||
current_path = self.file.path
|
||
expected_filename = new_filename
|
||
expected_path = os.path.join(os.path.dirname(current_path), expected_filename)
|
||
if current_filename != expected_filename:
|
||
try:
|
||
if os.path.exists(current_path):
|
||
os.rename(current_path, expected_path)
|
||
self.file.name = os.path.join(os.path.dirname(self.file.name), expected_filename).replace("\\", "/")
|
||
logger.info(f"[FormPerso] Renommage du fichier master: {current_path} -> {expected_path}")
|
||
except Exception as e:
|
||
logger.error(f"[FormPerso] Erreur lors du renommage du fichier master: {e}")
|
||
except RegistrationSchoolFileMaster.DoesNotExist:
|
||
pass
|
||
|
||
# --- Traitement PDF dynamique AVANT le super().save() ---
|
||
if (
|
||
self.formMasterData
|
||
and isinstance(self.formMasterData, dict)
|
||
and self.formMasterData.get("fields")
|
||
):
|
||
from Subscriptions.util import generate_form_json_pdf
|
||
pdf_filename = f"{self.name}.pdf"
|
||
pdf_file = generate_form_json_pdf(self, self.formMasterData)
|
||
self.file.save(pdf_filename, pdf_file, save=False)
|
||
|
||
super().save(*args, **kwargs)
|
||
|
||
# Synchronisation des templates pour tous les dossiers d'inscription concernés (création ou modification)
|
||
try:
|
||
# Import local pour éviter le circular import
|
||
from Subscriptions.util import create_templates_for_registration_form
|
||
from Subscriptions.models import RegistrationForm, RegistrationSchoolFileTemplate
|
||
# Détermination des RF concernés
|
||
if is_new:
|
||
new_groups = set(self.groups.values_list('id', flat=True))
|
||
affected_rf_ids.update(
|
||
RegistrationForm.objects.filter(fileGroup__in=list(new_groups)).values_list('pk', flat=True)
|
||
)
|
||
else:
|
||
try:
|
||
old = RegistrationSchoolFileMaster.objects.get(pk=self.pk)
|
||
old_groups = set(old.groups.values_list('id', flat=True))
|
||
new_groups = set(self.groups.values_list('id', flat=True))
|
||
affected_rf_ids.update(
|
||
RegistrationForm.objects.filter(fileGroup__in=list(old_groups | new_groups)).values_list('pk', flat=True)
|
||
)
|
||
form_data_changed = (
|
||
old.formMasterData != self.formMasterData
|
||
and self.formMasterData
|
||
and isinstance(self.formMasterData, dict)
|
||
and self.formMasterData.get("fields")
|
||
)
|
||
name_changed = old.name != self.name
|
||
if form_data_changed or name_changed:
|
||
logger.info(f"[FormPerso] Modification du contenu du master '{self.name}' (id={self.pk})")
|
||
except RegistrationSchoolFileMaster.DoesNotExist:
|
||
pass
|
||
|
||
# Pour chaque RF concerné, régénérer les templates
|
||
for rf_id in affected_rf_ids:
|
||
try:
|
||
rf = RegistrationForm.objects.get(pk=rf_id)
|
||
logger.info(f"[FormPerso] Synchronisation template pour élève '{rf.student.last_name}_{rf.student.first_name}' (RF id={rf.pk}) suite à modification/ajout du master '{self.name}'")
|
||
create_templates_for_registration_form(rf)
|
||
except Exception as e:
|
||
logger.error(f"Erreur lors de la synchronisation des templates pour RF {rf_id} après modification du master {self.pk}: {e}")
|
||
except Exception as e:
|
||
logger.error(f"Erreur globale lors de la synchronisation des templates après modification du master {self.pk}: {e}")
|
||
|
||
def delete(self, *args, **kwargs):
|
||
logger.info(f"[FormPerso] Suppression master '{self.name}' (id={self.pk}) et tous ses templates")
|
||
# Import local pour éviter le circular import
|
||
from Subscriptions.models import RegistrationSchoolFileTemplate
|
||
templates = RegistrationSchoolFileTemplate.objects.filter(master=self)
|
||
for tmpl in templates:
|
||
logger.info(f"[FormPerso] Suppression template '{tmpl.name}' pour élève '{tmpl.registration_form.student.last_name}_{tmpl.registration_form.student.first_name}' (RF id={tmpl.registration_form.pk})")
|
||
if self.file and hasattr(self.file, 'path') and os.path.exists(self.file.path):
|
||
try:
|
||
self.file.delete(save=False)
|
||
except Exception as e:
|
||
logger.error(f"Erreur lors de la suppression du fichier master: {e}")
|
||
super().delete(*args, **kwargs)
|
||
|
||
####### Parent files masters (documents à fournir par les parents) #######
|
||
class RegistrationParentFileMaster(models.Model):
|
||
groups = models.ManyToManyField(RegistrationFileGroup, related_name='parent_file_masters', blank=True)
|
||
name = models.CharField(max_length=255, default="")
|
||
description = models.CharField(max_length=500, blank=True, null=True)
|
||
is_required = models.BooleanField(default=False)
|
||
|
||
############################################################
|
||
####################### CLONE FILES ########################
|
||
############################################################
|
||
|
||
def registration_school_file_upload_to(instance, filename):
|
||
return f"{instance.registration_form.establishment.name}/dossier_{instance.registration_form.student.last_name}_{instance.registration_form.student.first_name}/{filename}"
|
||
|
||
def registration_parent_file_upload_to(instance, filename):
|
||
return f"registration_files/{instance.registration_form.establishment.name}/dossier_rf_{instance.registration_form.pk}/parent/{filename}"
|
||
|
||
####### Formulaires templates (par dossier d'inscription) #######
|
||
class RegistrationSchoolFileTemplate(models.Model):
|
||
master = models.ForeignKey(RegistrationSchoolFileMaster, on_delete=models.CASCADE, related_name='school_file_templates', blank=True)
|
||
slug = models.CharField(max_length=255, default="")
|
||
name = models.CharField(max_length=255, default="")
|
||
registration_form = models.ForeignKey(RegistrationForm, on_delete=models.CASCADE, related_name='school_file_templates', blank=True)
|
||
file = models.FileField(null=True,blank=True, upload_to=registration_school_file_upload_to)
|
||
formTemplateData = models.JSONField(default=list, blank=True, null=True)
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
@staticmethod
|
||
def get_files_from_rf(register_form_id):
|
||
"""
|
||
Récupère tous les fichiers liés à un dossier d’inscription donné.
|
||
"""
|
||
registration_files = RegistrationSchoolFileTemplate.objects.filter(registration_form=register_form_id)
|
||
filenames = []
|
||
for reg_file in registration_files:
|
||
filenames.append(reg_file.file.path)
|
||
return filenames
|
||
|
||
class StudentCompetency(models.Model):
|
||
student = models.ForeignKey('Subscriptions.Student', on_delete=models.CASCADE, related_name='competency_scores')
|
||
establishment_competency = models.ForeignKey('School.EstablishmentCompetency', on_delete=models.CASCADE, related_name='student_scores')
|
||
score = models.IntegerField(null=True, blank=True)
|
||
comment = models.TextField(blank=True, null=True)
|
||
period = models.CharField(
|
||
max_length=20,
|
||
help_text="Période d'évaluation ex: T1-2024_2025, S1-2024_2025, A-2024_2025",
|
||
default="",
|
||
blank=True
|
||
)
|
||
|
||
class Meta:
|
||
unique_together = ('student', 'establishment_competency', 'period')
|
||
|
||
indexes = [
|
||
models.Index(fields=['student', 'establishment_competency', 'period']),
|
||
]
|
||
|
||
def __str__(self):
|
||
return f"{self.student} - {self.establishment_competency} - Score: {self.score} - Period: {self.period}"
|
||
|
||
####### Parent files templates (par dossier d'inscription) #######
|
||
class RegistrationParentFileTemplate(models.Model):
|
||
master = models.ForeignKey(RegistrationParentFileMaster, on_delete=models.CASCADE, related_name='parent_file_templates', blank=True)
|
||
registration_form = models.ForeignKey(RegistrationForm, on_delete=models.CASCADE, related_name='parent_file_templates', blank=True)
|
||
file = models.FileField(null=True,blank=True, upload_to=registration_parent_file_upload_to)
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
def save(self, *args, **kwargs):
|
||
if self.pk: # Si l'objet existe déjà dans la base de données
|
||
try:
|
||
old_instance = RegistrationParentFileTemplate.objects.get(pk=self.pk)
|
||
if old_instance.file and (not self.file or self.file.name == ''):
|
||
if os.path.exists(old_instance.file.path):
|
||
old_instance.file.delete(save=False)
|
||
self.file = None
|
||
else:
|
||
print(f"Le fichier {old_instance.file.path} n'existe pas.")
|
||
except RegistrationParentFileTemplate.DoesNotExist:
|
||
print("Ancienne instance introuvable.")
|
||
super().save(*args, **kwargs)
|
||
|
||
@staticmethod
|
||
def get_files_from_rf(register_form_id):
|
||
"""
|
||
Récupère tous les fichiers liés à un dossier d’inscription donné.
|
||
"""
|
||
registration_files = RegistrationParentFileTemplate.objects.filter(registration_form=register_form_id)
|
||
filenames = []
|
||
for reg_file in registration_files:
|
||
if reg_file.file and hasattr(reg_file.file, 'path'):
|
||
filenames.append(reg_file.file.path)
|
||
return filenames
|
||
|
||
class AbsenceMoment(models.IntegerChoices):
|
||
MORNING = 1, 'Morning'
|
||
AFTERNOON = 2, 'Afternoon'
|
||
TOTAL = 3, 'Total'
|
||
|
||
class AbsenceReason(models.IntegerChoices):
|
||
JUSTIFIED_ABSENCE = 1, 'Justified Absence'
|
||
UNJUSTIFIED_ABSENCE = 2, 'Unjustified Absence'
|
||
JUSTIFIED_LATE = 3, 'Justified Late'
|
||
UNJUSTIFIED_LATE = 4, 'Unjustified Late'
|
||
|
||
class AbsenceManagement(models.Model):
|
||
day = models.DateField(blank=True, null=True)
|
||
moment = models.IntegerField(
|
||
choices=AbsenceMoment.choices,
|
||
default=AbsenceMoment.TOTAL
|
||
)
|
||
reason = models.IntegerField(
|
||
choices=AbsenceReason.choices,
|
||
default=AbsenceReason.UNJUSTIFIED_ABSENCE
|
||
)
|
||
student = models.ForeignKey(
|
||
Student,
|
||
on_delete=models.CASCADE,
|
||
related_name='absences',
|
||
blank=True, null=True
|
||
)
|
||
establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE, related_name='absences', blank=True, null=True)
|
||
commentaire = models.TextField(blank=True, null=True)
|
||
|
||
def __str__(self):
|
||
return f"{self.student} - {self.day} - {self.get_moment_display()} - {self.get_reason_display()}" |