import os import mimetypes from django.conf import settings from django.http import FileResponse from django.http.response import JsonResponse from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect from django.utils.decorators import method_decorator from rest_framework.parsers import JSONParser from rest_framework.views import APIView from rest_framework import status from rest_framework.permissions import IsAuthenticated from .models import ( Domain, Category ) from .serializers import ( DomainSerializer, CategorySerializer ) @method_decorator(csrf_protect, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch') class DomainListCreateView(APIView): permission_classes = [IsAuthenticated] 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): permission_classes = [IsAuthenticated] 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): permission_classes = [IsAuthenticated] 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): permission_classes = [IsAuthenticated] 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) class ServeFileView(APIView): """Sert les fichiers media de manière sécurisée avec authentification JWT.""" permission_classes = [IsAuthenticated] def get(self, request): file_path = request.query_params.get('path', '') if not file_path: return JsonResponse( {'error': 'Le paramètre "path" est requis'}, status=status.HTTP_400_BAD_REQUEST, ) # Nettoyer le préfixe /data/ si présent if file_path.startswith('/data/'): file_path = file_path[len('/data/'):] elif file_path.startswith('data/'): file_path = file_path[len('data/'):] # Construire le chemin absolu et le résoudre pour éliminer les traversals absolute_path = os.path.realpath( os.path.join(settings.MEDIA_ROOT, file_path) ) # Protection contre le path traversal media_root = os.path.realpath(settings.MEDIA_ROOT) if not absolute_path.startswith(media_root + os.sep) and absolute_path != media_root: return JsonResponse( {'error': 'Accès non autorisé'}, status=status.HTTP_403_FORBIDDEN, ) if not os.path.isfile(absolute_path): return JsonResponse( {'error': 'Fichier introuvable'}, status=status.HTTP_404_NOT_FOUND, ) content_type, _ = mimetypes.guess_type(absolute_path) if content_type is None: content_type = 'application/octet-stream' response = FileResponse( open(absolute_path, 'rb'), content_type=content_type, ) response['Content-Disposition'] = ( f'inline; filename="{os.path.basename(absolute_path)}"' ) return response