from django.http.response import JsonResponse from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from rest_framework.response import Response from rest_framework.views import APIView from rest_framework import status import os import glob from Subscriptions.serializers import RegistrationSchoolFileTemplateSerializer from Subscriptions.models import RegistrationSchoolFileTemplate from N3wtSchool import bdd import logging from rest_framework.parsers import MultiPartParser, FormParser, JSONParser import Subscriptions.util as util logger = logging.getLogger(__name__) def _extract_nested_responses(data, max_depth=8): """Extrait le dictionnaire de reponses depuis des structures imbriquees.""" current = data for _ in range(max_depth): if not isinstance(current, dict): return None nested = current.get("responses") if isinstance(nested, dict): current = nested continue return current return current if isinstance(current, dict) else None class RegistrationSchoolFileTemplateView(APIView): @swagger_auto_schema( operation_description="Récupère tous les templates d'inscription pour un établissement donné", manual_parameters=[ openapi.Parameter( 'establishment_id', openapi.IN_QUERY, description="ID de l'établissement", type=openapi.TYPE_INTEGER, required=True ) ], responses={200: RegistrationSchoolFileTemplateSerializer(many=True)} ) def get(self, request): establishment_id = request.GET.get('establishment_id') if not establishment_id: return Response({'error': "Paramètre 'establishment_id' requis"}, status=status.HTTP_400_BAD_REQUEST) # Filtrer les templates liés à l'établissement via master.groups.establishment templates = RegistrationSchoolFileTemplate.objects.filter( master__groups__establishment__id=establishment_id ).distinct() serializer = RegistrationSchoolFileTemplateSerializer(templates, many=True) return Response(serializer.data) @swagger_auto_schema( operation_description="Crée un nouveau template d'inscription", request_body=RegistrationSchoolFileTemplateSerializer, responses={ 201: RegistrationSchoolFileTemplateSerializer, 400: "Données invalides" } ) def post(self, request): serializer = RegistrationSchoolFileTemplateSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class RegistrationSchoolFileTemplateSimpleView(APIView): parser_classes = [MultiPartParser, FormParser, JSONParser] @swagger_auto_schema( operation_description="Récupère un template d'inscription spécifique", responses={ 200: RegistrationSchoolFileTemplateSerializer, 404: "Template non trouvé" } ) def get(self, request, id): template = bdd.getObject(_objectName=RegistrationSchoolFileTemplate, _columnName='id', _value=id) if template is None: return JsonResponse({"errorMessage":'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) serializer = RegistrationSchoolFileTemplateSerializer(template) return JsonResponse(serializer.data, safe=False) @swagger_auto_schema( operation_description="Met à jour un template d'inscription existant", request_body=RegistrationSchoolFileTemplateSerializer, responses={ 200: RegistrationSchoolFileTemplateSerializer, 400: "Données invalides", 404: "Template non trouvé" } ) def put(self, request, id): # Normaliser la payload (support form-data avec champ 'data' JSON ou fichier JSON) payload, resp = util.build_payload_from_request(request) if resp is not None: return resp # Synchroniser fields[].value dans le payload AVANT le serializer (pour les formulaires dynamiques) formTemplateData = payload.get('formTemplateData') if formTemplateData and isinstance(formTemplateData, dict): responses = None if "responses" in formTemplateData: resp = formTemplateData["responses"] responses = _extract_nested_responses(resp) # Nettoyer les meta-cles qui ne sont pas des reponses de champs if isinstance(responses, dict): cleaned = { key: value for key, value in responses.items() if key not in {"responses", "formId", "id", "templateId"} } responses = cleaned if responses and "fields" in formTemplateData: for field in formTemplateData["fields"]: field_id = field.get("id") if field_id and field_id in responses: field["value"] = responses[field_id] # Stocker les reponses aplaties pour eviter l'empilement responses.responses if isinstance(responses, dict): formTemplateData["responses"] = responses payload['formTemplateData'] = formTemplateData template = bdd.getObject(_objectName=RegistrationSchoolFileTemplate, _columnName='id', _value=id) if template is None: return JsonResponse({'erreur': 'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) # Cas 1 : Upload d'un fichier existant (PDF/image) if 'file' in request.FILES: upload = request.FILES['file'] file_field = template.file upload_name = upload.name upload_dir = os.path.dirname(file_field.path) if file_field and file_field.name else None if upload_dir: base_name, _ = os.path.splitext(upload_name) pattern = os.path.join(upload_dir, f"{base_name}.*") for f in glob.glob(pattern): try: if os.path.exists(f): os.remove(f) logger.info(f"Suppression du fichier existant (pattern): {f}") except Exception as e: logger.error(f"Erreur suppression fichier existant (pattern): {e}") target_path = os.path.join(upload_dir, upload_name) if os.path.exists(target_path): try: os.remove(target_path) except Exception as e: logger.error(f"Erreur suppression fichier cible: {e}") # On écrase le fichier existant sans passer par le serializer template.file.save(upload_name, upload, save=True) return Response({'message': 'Template mis à jour avec succès', 'data': RegistrationSchoolFileTemplateSerializer(template).data}, status=status.HTTP_200_OK) # Cas 2 : Formulaire dynamique (JSON) serializer = RegistrationSchoolFileTemplateSerializer(template, data=payload, partial=True) if serializer.is_valid(): serializer.save() # Régénérer le PDF si besoin formTemplateData = serializer.validated_data.get('formTemplateData') if ( formTemplateData and isinstance(formTemplateData, dict) and formTemplateData.get("fields") and hasattr(template, "file") ): # Lire le contenu du fichier source en mémoire AVANT suppression. # Priorité au fichier master (document source admin) pour éviter # de re-générer à partir d'un PDF template déjà enrichi. base_pdf_content = None base_file_ext = None if template.master and template.master.file and template.master.file.name: base_file_ext = os.path.splitext(template.master.file.name)[1].lower() try: template.master.file.open('rb') base_pdf_content = template.master.file.read() template.master.file.close() except Exception as e: logger.error(f"Erreur lecture fichier source master: {e}") elif template.file and template.file.name: base_file_ext = os.path.splitext(template.file.name)[1].lower() try: template.file.open('rb') base_pdf_content = template.file.read() template.file.close() except Exception as e: logger.error(f"Erreur lecture fichier source template: {e}") try: old_path = template.file.path template.file.delete(save=False) if os.path.exists(old_path): os.remove(old_path) except Exception as e: logger.error(f"Erreur lors de la suppression du fichier existant: {e}") from Subscriptions.util import generate_form_json_pdf pdf_file = generate_form_json_pdf( template.registration_form, formTemplateData, base_pdf_content=base_pdf_content, base_file_ext=base_file_ext, ) form_name = (formTemplateData.get("title") or template.name or f"formulaire_{template.id}").strip().replace(" ", "_") pdf_filename = f"{form_name}.pdf" util.save_file_field_without_suffix( template, 'file', pdf_filename, pdf_file, save=True, ) return Response({'message': 'Template mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema( operation_description="Supprime un template d'inscription", responses={ 204: "Suppression réussie", 404: "Template non trouvé" } ) def delete(self, request, id): template = bdd.getObject(_objectName=RegistrationSchoolFileTemplate, _columnName='id', _value=id) if template is not None: # Suppression du fichier PDF associé if template.file and template.file.name: file_path = template.file.path template.file.delete(save=False) # Vérification post-suppression if os.path.exists(file_path): try: os.remove(file_path) logger.info(f"Fichier supprimé manuellement: {file_path}") except Exception as e: logger.error(f"Erreur lors de la suppression manuelle du fichier: {e}") template.delete() return JsonResponse({'message': 'La suppression du template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT) else: return JsonResponse({'erreur': 'Le template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)