from django.http.response import JsonResponse from rest_framework.views import APIView from rest_framework import status from drf_yasg.utils import swagger_auto_schema from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect from django.utils.decorators import method_decorator from Subscriptions.models import StudentCompetency, Student from Common.models import Domain from django.conf import settings from datetime import date from N3wtSchool.renderers import render_to_pdf from django.core.files import File from io import BytesIO import os import logging logger = logging.getLogger(__name__) @method_decorator(csrf_protect, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch') class StudentCompetencyListCreateView(APIView): def get(self, request): student_id = request.GET.get('student_id') if not student_id: return JsonResponse({'error': 'student_id requis'}, status=400) try: student = Student.objects.get(id=student_id) except Student.DoesNotExist: return JsonResponse({'error': 'Élève introuvable'}, status=404) student_competencies = StudentCompetency.objects.filter(student=student).select_related( 'establishment_competency', 'establishment_competency__competency', 'establishment_competency__competency__category', 'establishment_competency__competency__category__domain', 'establishment_competency__custom_category', 'establishment_competency__custom_category__domain', ) result = [] total_competencies = 0 domaines = Domain.objects.all() for domaine in domaines: domaine_dict = { "domaine_id": domaine.id, "domaine_nom": domaine.name, "categories": [] } categories = domaine.categories.all() for categorie in categories: categorie_dict = { "categorie_id": categorie.id, "categorie_nom": categorie.name, "competences": [] } # On ne boucle que sur les compétences du student pour cette catégorie for sc in student_competencies: ec = sc.establishment_competency # Cas compétence de référence if ec.competency and ec.competency.category_id == categorie.id: comp = ec.competency categorie_dict["competences"].append({ "competence_id": ec.id, # <-- retourne l'id de l'EstablishmentCompetency "nom": comp.name, "score": sc.score, "comment": sc.comment or "", }) total_competencies += 1 # Cas compétence custom elif ec.competency is None and ec.custom_category_id == categorie.id: categorie_dict["competences"].append({ "competence_id": ec.id, # <-- retourne l'id de l'EstablishmentCompetency "nom": ec.custom_name, "score": sc.score, "comment": sc.comment or "", }) total_competencies += 1 if categorie_dict["competences"]: domaine_dict["categories"].append(categorie_dict) if domaine_dict["categories"]: result.append(domaine_dict) return JsonResponse({ "count": total_competencies, "data": result }, safe=False, status=200) def put(self, request): """ Met à jour en masse les notes des compétences d'un élève. Attend une liste d'objets {"competenceId": ..., "grade": ...} """ data = request.data if not isinstance(data, list): return JsonResponse({"error": "Une liste est attendue."}, status=400) updated = [] errors = [] for item in data: comp_id = item.get("competenceId") grade = item.get("grade") student_id = item.get('studentId') if comp_id is None or grade is None: errors.append({"competenceId": comp_id, "error": "champ manquant"}) continue try: # Ajoute le filtre student_id sc = StudentCompetency.objects.get( establishment_competency_id=comp_id, student_id=student_id ) sc.score = grade sc.save() updated.append(comp_id) except StudentCompetency.DoesNotExist: errors.append({"competenceId": comp_id, "error": "not found"}) # Génération du PDF si au moins une compétence a été mise à jour if updated: student = Student.objects.get(id=student_id) # Reconstituer la structure "domaines" comme dans le GET student_competencies = StudentCompetency.objects.filter(student=student).select_related( 'establishment_competency', 'establishment_competency__competency', 'establishment_competency__competency__category', 'establishment_competency__competency__category__domain', 'establishment_competency__custom_category', 'establishment_competency__custom_category__domain', ) result = [] domaines = Domain.objects.all() for domaine in domaines: domaine_dict = { "nom": domaine.name, "categories": [] } categories = domaine.categories.all() for categorie in categories: categorie_dict = { "nom": categorie.name, "competences": [] } for sc in student_competencies: ec = sc.establishment_competency if ec.competency and ec.competency.category_id == categorie.id: comp = ec.competency categorie_dict["competences"].append({ "nom": comp.name, "score": sc.score, "comment": sc.comment or "", }) elif ec.competency is None and ec.custom_category_id == categorie.id: categorie_dict["competences"].append({ "nom": ec.custom_name, "score": sc.score, "comment": sc.comment or "", }) if categorie_dict["competences"]: domaine_dict["categories"].append(categorie_dict) if domaine_dict["categories"]: result.append(domaine_dict) context = { "student": { "first_name": student.first_name, "last_name": student.last_name, "level": student.level, "class_name": student.associated_class.atmosphere_name, }, "date": date.today().strftime("%d/%m/%Y"), "domaines": result, } print('génération du PDF...') pdf_result = render_to_pdf('pdfs/bilan_competences.html', context) # Vérifier si un fichier bilan_form existe déjà et le supprimer if student.bilan_form and student.bilan_form.name: if os.path.isabs(student.bilan_form.path): existing_file_path = student.bilan_form.path else: existing_file_path = os.path.join(settings.MEDIA_ROOT, student.bilan_form.name.lstrip('/')) if os.path.exists(existing_file_path): os.remove(existing_file_path) student.bilan_form.delete(save=False) logger.info(f"Ancien PDF supprimé : {existing_file_path}") else: logger.info(f"File does not exist: {existing_file_path}") try: filename = f"bilan_competences_{student.last_name}_{student.first_name}.pdf" student.bilan_form.save( os.path.basename(filename), # Utiliser uniquement le nom de fichier File(BytesIO(pdf_result.content)), save=True ) except Exception as e: logger.error(f"Erreur lors de la sauvegarde du fichier PDF : {e}") raise # Exemple : retour du PDF dans la réponse HTTP (pour test) return JsonResponse({"updated": updated, "errors": errors}, status=200) @method_decorator(csrf_protect, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch') class StudentCompetencySimpleView(APIView): def get(self, request, id): return JsonResponse("ok", safe=False, status=status.HTTP_200_OK) # def put(self, request, id): # try: # absence = AbsenceManagement.objects.get(id=id) # serializer = AbsenceManagementSerializer(absence, data=request.data) # if serializer.is_valid(): # serializer.save() # return JsonResponse(serializer.data, safe=False, status=status.HTTP_200_OK) # return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST) # except AbsenceManagement.DoesNotExist: # return JsonResponse({"error": "Absence not found"}, safe=False, status=status.HTTP_404_NOT_FOUND) # def delete(self, request, id): # return delete_object(AbsenceManagement, id)