diff --git a/Back-End/Subscriptions/models.py b/Back-End/Subscriptions/models.py index 7c28c0c..3e3caba 100644 --- a/Back-End/Subscriptions/models.py +++ b/Back-End/Subscriptions/models.py @@ -2,11 +2,9 @@ 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 os import logging +import os logger = logging.getLogger("SubscriptionModels") @@ -350,123 +348,124 @@ class RegistrationSchoolFileMaster(models.Model): return None def save(self, *args, **kwargs): - import os affected_rf_ids = set() is_new = self.pk is None - super().save(*args, **kwargs) - - # 2. Gestion des groupes pour la synchro des templates + # Log création ou modification du master if is_new: - from Subscriptions.models import RegistrationForm - 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) - ) + 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) - old_groups = set(old.groups.values_list('id', flat=True)) - new_groups = set(self.groups.values_list('id', flat=True)) - from Subscriptions.models import RegistrationForm - 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 - # --- Correction spécifique pour les fichiers existants (PDF, DOC, etc.) --- - # Si le nom change et qu'on a un fichier existant, on doit renommer physiquement le fichier - if old.file and not self.file and name_changed: - old_file_path = old.file.path - ext = os.path.splitext(old_file_path)[1] - clean_name = (self.name or 'document').replace(' ', '_').replace('/', '_') - new_filename = f"{clean_name}{ext}" - new_rel_path = os.path.join(os.path.dirname(old.file.name), new_filename) - new_abs_path = os.path.join(os.path.dirname(old_file_path), new_filename) - logger.info(f"Renommage fichier: {old_file_path} -> {new_abs_path}") - try: - if not os.path.exists(new_abs_path): - os.rename(old_file_path, new_abs_path) - self.file.name = new_rel_path - logger.info(f"self.file.name après renommage: {self.file.name}") - super().save(update_fields=["file"]) - else: - logger.info(f"Le fichier cible existe déjà: {new_abs_path}") - except Exception as e: - logger.error(f"Erreur lors du renommage du fichier master: {e}") - elif old.file and self.file and self.file != old.file: - old.file.delete(save=False) - elif old.file and form_data_changed: - old.file.delete(save=False) - self.file = None + 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 - # Harmonisation : gestion du fichier (dynamique ou existant) sans appeler registration_school_file_master_upload_to - # Pour les formulaires dynamiques (PDF à générer) - if not self.file and self.formMasterData and isinstance(self.formMasterData, dict) and self.formMasterData.get("fields"): - try: - from Subscriptions.util import generate_form_json_pdf - pdf_filename = f"{self.name or 'formulaire'}.pdf" - abs_path = os.path.join(settings.MEDIA_ROOT, self.file.field.upload_to(self, pdf_filename)) - os.makedirs(os.path.dirname(abs_path), exist_ok=True) - if os.path.exists(abs_path): - try: - os.remove(abs_path) - logger.info(f"Suppression fichier existant: {abs_path}") - except Exception as e: - logger.error(f"Erreur suppression fichier existant avant save: {e}") - pdf_file = generate_form_json_pdf(self, self.formMasterData) - logger.info(f"Sauvegarde PDF dynamique: {pdf_filename}") - self.file.save(pdf_filename, pdf_file, save=False) - logger.info(f"self.file.name après save PDF dynamique: {self.file.name}") - super().save(update_fields=["file"]) - except Exception as e: - logger.error(f"Erreur lors de la génération automatique du PDF pour le master {self.pk}: {e}") - # Pour les fichiers existants uploadés - elif self.file and hasattr(self.file, 'name') and self.name: - # On force le nom du fichier (nom du master + extension) si besoin - ext = os.path.splitext(self.file.name)[1] - clean_name = (self.name or 'document').replace(' ', '_').replace('/', '_') - final_file_name = f"{clean_name}{ext}" - # Si le nom ne correspond pas, on renomme le fichier physique et le FileField - if os.path.basename(self.file.name) != final_file_name: - current_path = self.file.path - abs_path = os.path.join(settings.MEDIA_ROOT, self.file.field.upload_to(self, final_file_name)) - os.makedirs(os.path.dirname(abs_path), exist_ok=True) - if os.path.exists(abs_path): - try: - os.remove(abs_path) - logger.info(f"Suppression fichier existant: {abs_path}") - except Exception as e: - logger.error(f"Erreur suppression fichier existant avant renommage: {e}") - try: - os.rename(current_path, abs_path) - self.file.name = os.path.relpath(abs_path, settings.MEDIA_ROOT) - logger.info(f"Déplacement fichier existant: {current_path} -> {abs_path}") - super().save(update_fields=["file"]) - except Exception as e: - logger.error(f"Erreur lors du déplacement du fichier existant: {e}") + # --- 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) - # 4. Synchronisation des templates pour tous les dossiers d'inscription concernés (création ou modification) + 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 + 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): - # Supprimer le fichier physique du master si présent + 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) diff --git a/Back-End/Subscriptions/util.py b/Back-End/Subscriptions/util.py index e4c338e..b112ba5 100644 --- a/Back-End/Subscriptions/util.py +++ b/Back-End/Subscriptions/util.py @@ -136,7 +136,6 @@ def create_templates_for_registration_form(register_form): school_masters = RegistrationSchoolFileMaster.objects.filter(groups=current_group).distinct() parent_masters = RegistrationParentFileMaster.objects.filter(groups=current_group).distinct() - logger.info("util.create_templates_for_registration_form - school_masters récupérés") school_master_ids = {m.pk for m in school_masters} parent_master_ids = {m.pk for m in parent_masters} @@ -163,25 +162,20 @@ def create_templates_for_registration_form(register_form): tmpl.delete() logger.info("Deleted obsolete parent template %s for RF %s", getattr(tmpl, "pk", None), register_form.pk) - # Créer les school templates manquants - logger.info("util.create_templates_for_registration_form - Créer les school templates manquants") + # Créer les school templates manquants ou mettre à jour les existants si le master a changé for m in school_masters: - exists = RegistrationSchoolFileTemplate.objects.filter(master=m, registration_form=register_form).exists() - if exists: - continue + tmpl_qs = RegistrationSchoolFileTemplate.objects.filter(master=m, registration_form=register_form) + tmpl = tmpl_qs.first() if tmpl_qs.exists() else None + base_slug = (m.name or "master").strip().replace(" ", "_")[:40] slug = f"{base_slug}_{register_form.pk}_{m.pk}" - # --- Correction : Générer un nom de fichier unique uniquement si le master n'a pas de fichier --- file_name = None if m.file and hasattr(m.file, 'name') and m.file.name: - # Utiliser le nom du fichier tel qu'il est stocké dans le master (pas de suffixe aléatoire ici) file_name = os.path.basename(m.file.name) - logger.info(f"util.create_templates_for_registration_form - file_name 1 : {file_name}") elif m.file: file_name = str(m.file) else: - # Générer le PDF si besoin (rare ici) try: pdf_file = generate_form_json_pdf(register_form, m.formMasterData) file_name = os.path.basename(pdf_file.name) @@ -189,8 +183,54 @@ def create_templates_for_registration_form(register_form): logger.error(f"Erreur lors de la génération du PDF pour le template: {e}") file_name = None - logger.info(f"util.create_templates_for_registration_form - file_name : {file_name}") + from django.core.files.base import ContentFile + upload_rel_path = registration_school_file_upload_to( + type("Tmp", (), { + "registration_form": register_form, + "establishment": getattr(register_form, "establishment", None), + "student": getattr(register_form, "student", None) + })(), + file_name + ) + abs_path = os.path.join(settings.MEDIA_ROOT, upload_rel_path) + master_file_path = m.file.path if m.file and hasattr(m.file, 'path') else None + if tmpl: + template_file_name = os.path.basename(tmpl.file.name) if tmpl.file and tmpl.file.name else None + master_file_changed = template_file_name != file_name + # --- GESTION FORM EXISTANT : suppression ancien template si nom ou contenu master changé --- + if master_file_changed or ( + master_file_path and os.path.exists(master_file_path) and + (not tmpl.file or not os.path.exists(abs_path) or os.path.getmtime(master_file_path) > os.path.getmtime(abs_path)) + ): + # Supprimer l'ancien fichier du template (même si le nom change) + if tmpl.file and tmpl.file.name: + old_template_path = os.path.join(settings.MEDIA_ROOT, tmpl.file.name) + if os.path.exists(old_template_path): + try: + os.remove(old_template_path) + logger.info(f"util.create_templates_for_registration_form - Suppression ancien fichier template: {old_template_path}") + except Exception as e: + logger.error(f"Erreur suppression ancien fichier template: {e}") + # Copier le nouveau fichier du master (form existant) + if master_file_path and os.path.exists(master_file_path): + try: + os.makedirs(os.path.dirname(abs_path), exist_ok=True) + import shutil + shutil.copy2(master_file_path, abs_path) + logger.info(f"util.create_templates_for_registration_form - Copie du fichier master {master_file_path} -> {abs_path}") + tmpl.file.name = upload_rel_path + tmpl.name = m.name or "" + tmpl.slug = slug + tmpl.formTemplateData = m.formMasterData or [] + tmpl.save() + except Exception as e: + logger.error(f"Erreur lors de la copie du fichier master pour mise à jour du template: {e}") + created.append(tmpl) + logger.info("util.create_templates_for_registration_form - Mise à jour school template %s from master %s for RF %s", tmpl.pk, m.pk, register_form.pk) + continue + + # Sinon, création du template comme avant tmpl = RegistrationSchoolFileTemplate( master=m, registration_form=register_form, @@ -199,20 +239,7 @@ def create_templates_for_registration_form(register_form): slug=slug, ) if file_name: - from django.core.files.base import ContentFile - # Vérifier si le fichier existe déjà dans MEDIA_ROOT (copie du master) - upload_rel_path = registration_school_file_upload_to( - type("Tmp", (), { - "registration_form": register_form, - "establishment": getattr(register_form, "establishment", None), - "student": getattr(register_form, "student", None) - })(), - file_name - ) - abs_path = os.path.join(settings.MEDIA_ROOT, upload_rel_path) - master_file_path = m.file.path if m.file and hasattr(m.file, 'path') else None - - # Si le fichier n'existe pas dans le dossier cible, le copier depuis le master + # Copier le fichier du master si besoin (form existant) if master_file_path and not os.path.exists(abs_path): try: import shutil @@ -221,8 +248,6 @@ def create_templates_for_registration_form(register_form): logger.info(f"util.create_templates_for_registration_form - Copie du fichier master {master_file_path} -> {abs_path}") except Exception as e: logger.error(f"Erreur lors de la copie du fichier master pour le template: {e}") - - # Associer le fichier existant (ou copié) au template tmpl.file.name = upload_rel_path tmpl.save() created.append(tmpl)