feat: Configuration des compétences par cycle [#16]

This commit is contained in:
N3WT DE COMPET
2025-05-18 00:45:49 +02:00
parent 2888f8dcce
commit 4e5aab6db7
29 changed files with 1001 additions and 82 deletions

View File

@ -12,7 +12,11 @@ from .models import (
Discount,
Fee,
PaymentPlan,
PaymentMode
PaymentMode,
Domain,
Category,
Competency,
EstablishmentCompetency
)
from .serializers import (
TeacherSerializer,
@ -22,9 +26,15 @@ from .serializers import (
DiscountSerializer,
FeeSerializer,
PaymentPlanSerializer,
PaymentModeSerializer
PaymentModeSerializer,
DomainSerializer,
CategorySerializer,
CompetencySerializer,
EstablishmentCompetencySerializer
)
from N3wtSchool.bdd import delete_object, getAllObjects, getObject
from django.db.models import Q
from collections import defaultdict
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
@ -411,3 +421,349 @@ class PaymentModeDetailView(APIView):
def delete(self, request, id):
return delete_object(PaymentMode, id)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class DomainListCreateView(APIView):
def get(self, request):
domains = Domain.objects.all()
serializer = DomainSerializer(domains, many=True)
return JsonResponse(serializer.data, safe=False)
def post(self, request):
data = JSONParser().parse(request)
serializer = DomainSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False, status=status.HTTP_201_CREATED)
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class DomainDetailView(APIView):
def get(self, request, id):
try:
domain = Domain.objects.get(id=id)
serializer = DomainSerializer(domain)
return JsonResponse(serializer.data, safe=False)
except Domain.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
def put(self, request, id):
try:
domain = Domain.objects.get(id=id)
except Domain.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
data = JSONParser().parse(request)
serializer = DomainSerializer(domain, data=data, partial=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False)
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, id):
try:
domain = Domain.objects.get(id=id)
domain.delete()
return JsonResponse({'message': 'Deleted'}, safe=False)
except Domain.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
# Répète la même logique pour Category, Competency, EstablishmentCompetency
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class CategoryListCreateView(APIView):
def get(self, request):
categories = Category.objects.all()
serializer = CategorySerializer(categories, many=True)
return JsonResponse(serializer.data, safe=False)
def post(self, request):
data = JSONParser().parse(request)
serializer = CategorySerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False, status=status.HTTP_201_CREATED)
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class CategoryDetailView(APIView):
def get(self, request, id):
try:
category = Category.objects.get(id=id)
serializer = CategorySerializer(category)
return JsonResponse(serializer.data, safe=False)
except Category.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
def put(self, request, id):
try:
category = Category.objects.get(id=id)
except Category.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
data = JSONParser().parse(request)
serializer = CategorySerializer(category, data=data, partial=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False)
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, id):
try:
category = Category.objects.get(id=id)
category.delete()
return JsonResponse({'message': 'Deleted'}, safe=False)
except Category.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class CompetencyListCreateView(APIView):
def get(self, request):
cycle = request.GET.get('cycle')
if cycle is None:
return JsonResponse({'error': 'cycle est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
competencies_list = getAllObjects(Competency)
competencies_list = competencies_list.filter(
category__domain__cycle=cycle
).distinct()
serializer = CompetencySerializer(competencies_list, many=True)
print(f'len : {competencies_list.count()}')
return JsonResponse(serializer.data, safe=False)
def post(self, request):
data = JSONParser().parse(request)
serializer = CompetencySerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False, status=status.HTTP_201_CREATED)
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class CompetencyDetailView(APIView):
def get(self, request, id):
try:
competency = Competency.objects.get(id=id)
serializer = CompetencySerializer(competency)
return JsonResponse(serializer.data, safe=False)
except Competency.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
def put(self, request, id):
try:
competency = Competency.objects.get(id=id)
except Competency.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
data = JSONParser().parse(request)
serializer = CompetencySerializer(competency, data=data, partial=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False)
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, id):
try:
competency = Competency.objects.get(id=id)
competency.delete()
return JsonResponse({'message': 'Deleted'}, safe=False)
except Competency.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class EstablishmentCompetencyListCreateView(APIView):
def get(self, request):
establishment_id = request.GET.get('establishment_id')
cycle = request.GET.get('cycle')
if not establishment_id or not cycle:
return JsonResponse({'error': 'establishment_id et cycle sont requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
# Toutes les compétences du cycle
competencies = Competency.objects.filter(category__domain__cycle=cycle).select_related('category', 'category__domain')
# Récupérer les EstablishmentCompetency pour l'établissement et le cycle
ec_qs = EstablishmentCompetency.objects.filter(
Q(competency__in=competencies) | Q(competency__isnull=True),
establishment_id=establishment_id
).select_related('competency', 'custom_category')
# Required = compétences de référence requises
required_ids = set(ec_qs.filter(is_required=True, competency__isnull=False).values_list('competency_id', flat=True))
# Custom = compétences custom (pas de lien vers Competency)
custom_ecs = ec_qs.filter(is_required=False, competency__isnull=True)
# Organisation par domaine > catégorie
result = []
selected_count = 0
domaines = Domain.objects.filter(cycle=cycle)
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": []
}
# Liste des noms de compétences custom pour cette catégorie
custom_for_cat = custom_ecs.filter(custom_category=categorie)
custom_names = set(ec.custom_name.strip().lower() for ec in custom_for_cat if ec.custom_name)
# Compétences de référence (on saute celles qui sont déjà en custom)
competences = categorie.competencies.all()
for comp in competences:
if comp.name.strip().lower() in custom_names:
continue # On n'affiche pas la compétence de référence si une custom du même nom existe
if comp.id in required_ids:
state = "required"
selected_count += 1
else:
state = "none"
categorie_dict["competences"].append({
"competence_id": comp.id,
"nom": comp.name,
"state": state
})
# Ajout des compétences custom
for ec in custom_for_cat:
categorie_dict["competences"].append({
"competence_id": ec.id,
"nom": ec.custom_name,
"state": "custom"
})
selected_count += 1
domaine_dict["categories"].append(categorie_dict)
result.append(domaine_dict)
return JsonResponse({
"selected_count": selected_count,
"data": result
}, safe=False)
def post(self, request):
"""
Crée une ou plusieurs compétences custom pour un établissement (is_required=False)
Attendu dans le body :
[
{ "establishment_id": ..., "category_id": ..., "nom": ... },
...
]
"""
data = JSONParser().parse(request)
# Si data est un dict (un seul objet), on le met dans une liste
if isinstance(data, dict):
data = [data]
created = []
errors = []
for item in data:
establishment_id = item.get("establishment_id")
category_id = item.get("category_id")
nom = item.get("nom")
if not establishment_id or not category_id or not nom:
errors.append({"error": "establishment_id, category_id et nom sont requis", "item": item})
continue
try:
category = Category.objects.get(id=category_id)
# Vérifier si une compétence custom du même nom existe déjà pour cet établissement et cette catégorie
ec_exists = EstablishmentCompetency.objects.filter(
establishment_id=establishment_id,
competency__isnull=True,
custom_name=nom,
).exists()
if ec_exists:
errors.append({"error": "Une compétence custom de ce nom existe déjà pour cet établissement", "item": item})
continue
ec = EstablishmentCompetency.objects.create(
establishment_id=establishment_id,
competency=None,
custom_name=nom,
custom_category=category,
is_required=False
)
created.append({
"competence_id": ec.id,
"nom": ec.custom_name,
"state": "custom"
})
except Exception as e:
errors.append({"error": str(e), "item": item})
status_code = status.HTTP_201_CREATED if created else status.HTTP_400_BAD_REQUEST
return JsonResponse({
"created": created,
"errors": errors
}, status=status_code, safe=False)
def delete(self, request):
"""
Supprime une ou plusieurs compétences custom (EstablishmentCompetency) à partir d'une liste d'IDs.
Attendu dans le body :
{
"ids": [1, 2, 3, ...]
}
"""
data = JSONParser().parse(request)
ids = data.get("ids", [])
deleted = []
errors = []
for ec_id in ids:
try:
ec = EstablishmentCompetency.objects.get(id=ec_id)
ec.delete()
deleted.append(ec_id)
except EstablishmentCompetency.DoesNotExist:
errors.append({"id": ec_id, "error": "No object found"})
return JsonResponse({
"deleted": deleted,
"errors": errors
}, safe=False)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class EstablishmentCompetencyDetailView(APIView):
def get(self, request, id):
try:
ec = EstablishmentCompetency.objects.get(id=id)
serializer = EstablishmentCompetencySerializer(ec)
return JsonResponse(serializer.data, safe=False)
except EstablishmentCompetency.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
def put(self, request, id):
try:
ec = EstablishmentCompetency.objects.get(id=id)
except EstablishmentCompetency.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
data = JSONParser().parse(request)
serializer = EstablishmentCompetencySerializer(ec, data=data, partial=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False)
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, id):
try:
ec = EstablishmentCompetency.objects.get(id=id)
ec.delete()
return JsonResponse({'message': 'Deleted'}, safe=False)
except EstablishmentCompetency.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)