feat: Signatures électroniques docuseal [#22]

This commit is contained in:
N3WT DE COMPET
2025-02-28 18:30:18 +01:00
parent 8897d523dc
commit c8c8941ec8
41 changed files with 984 additions and 549 deletions

View File

@ -89,7 +89,7 @@ class Student(models.Model):
siblings = models.ManyToManyField(Sibling, blank=True)
# Many-to-Many Relationship
registration_files = models.ManyToManyField('RegistrationFile', blank=True, related_name='students')
registration_files = models.ManyToManyField('RegistrationTemplate', blank=True, related_name='students')
# Many-to-Many Relationship
spoken_languages = models.ManyToManyField(Language, blank=True)
@ -162,7 +162,7 @@ class Student(models.Model):
return None
class RegistrationFileGroup(models.Model):
name = models.CharField(max_length=255)
name = models.CharField(max_length=255, default="")
description = models.TextField(blank=True, null=True)
def __str__(self):
@ -172,10 +172,35 @@ def registration_file_path(instance, filename):
# Génère le chemin : registration_files/dossier_rf_{student_id}/filename
return f'registration_files/dossier_rf_{instance.student_id}/{filename}'
class RegistrationTemplateMaster(models.Model):
groups = models.ManyToManyField(RegistrationFileGroup, related_name='template_masters')
template_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=255, default="")
def __str__(self):
return f'{self.group.name} - {self.template_id}'
class RegistrationTemplate(models.Model):
master = models.ForeignKey(RegistrationTemplateMaster, on_delete=models.CASCADE, related_name='templates')
template_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=255, default="")
registration_form = models.ForeignKey('RegistrationForm', on_delete=models.CASCADE, related_name='templates')
def __str__(self):
return self.name
@staticmethod
def get_files_from_rf(register_form_id):
"""
Récupère tous les fichiers liés à un dossier dinscription donné.
"""
registration_files = RegistrationTemplate.objects.filter(register_form_id=register_form_id).order_by('template__order')
filenames = []
for reg_file in registration_files:
filenames.append(reg_file.file.path)
return filenames
class RegistrationForm(models.Model):
"""
Gère le dossier dinscription lié à un élève donné.
"""
class RegistrationFormStatus(models.IntegerChoices):
RF_ABSENT = 0, _('Pas de dossier d\'inscription')
RF_CREATED = 1, _('Dossier d\'inscription créé')
@ -205,7 +230,7 @@ class RegistrationForm(models.Model):
discounts = models.ManyToManyField(Discount, blank=True, related_name='register_forms')
fileGroup = models.ForeignKey(RegistrationFileGroup,
on_delete=models.CASCADE,
related_name='file_group',
related_name='register_forms',
null=True,
blank=True)
@ -213,56 +238,3 @@ class RegistrationForm(models.Model):
def __str__(self):
return "RF_" + self.student.last_name + "_" + self.student.first_name
class RegistrationFileTemplate(models.Model):
"""
Modèle pour stocker les fichiers "templates" dinscription.
"""
name = models.CharField(max_length=255)
file = models.FileField(upload_to='templates_files/', blank=True, null=True)
order = models.PositiveIntegerField(default=0) # Ajout du champ order
date_added = models.DateTimeField(auto_now_add=True)
is_required = models.BooleanField(default=False)
group = models.ForeignKey(RegistrationFileGroup, on_delete=models.CASCADE, related_name='file_templates', null=True, blank=True)
@property
def formatted_date_added(self):
if self.date_added:
return self.date_added.strftime('%d-%m-%Y')
return None
def __str__(self):
return self.name
def registration_file_upload_to(instance, filename):
return f"registration_files/dossier_rf_{instance.register_form.pk}/{filename}"
class RegistrationFile(models.Model):
"""
Fichier lié à un dossier dinscription particulier.
"""
name = models.CharField(max_length=255)
file = models.FileField(upload_to=registration_file_upload_to)
date_added = models.DateTimeField(auto_now_add=True)
template = models.OneToOneField(RegistrationFileTemplate, on_delete=models.CASCADE)
register_form = models.ForeignKey('RegistrationForm', on_delete=models.CASCADE, related_name='registration_files')
@property
def formatted_date_added(self):
if self.date_added:
return self.date_added.strftime('%d-%m-%Y')
return None
def __str__(self):
return self.name
@staticmethod
def get_files_from_rf(register_form_id):
"""
Récupère tous les fichiers liés à un dossier dinscription donné.
"""
registration_files = RegistrationFile.objects.filter(register_form_id=register_form_id).order_by('template__order')
filenames = []
for reg_file in registration_files:
filenames.append(reg_file.file.path)
return filenames

View File

@ -1,5 +1,5 @@
from rest_framework import serializers
from .models import RegistrationFileTemplate, RegistrationFile, RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language
from .models import RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationTemplateMaster, RegistrationTemplate
from School.models import SchoolClass, Fee, Discount, FeeType
from School.serializers import FeeSerializer, DiscountSerializer
from Auth.models import Profile
@ -11,20 +11,38 @@ from django.utils import timezone
import pytz
from datetime import datetime
class RegistrationTemplateMasterSerializer(serializers.ModelSerializer):
class Meta:
model = RegistrationTemplateMaster
fields = '__all__'
class RegistrationTemplateSerializer(serializers.ModelSerializer):
class Meta:
model = RegistrationTemplate
fields = '__all__'
class GuardianSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Guardian
fields = ['id', 'email']
class RegistrationFormSimpleSerializer(serializers.ModelSerializer):
guardians = GuardianSimpleSerializer(many=True, source='student.guardians')
class Meta:
model = RegistrationForm
fields = ['student_id', 'guardians']
class RegistrationFileGroupSerializer(serializers.ModelSerializer):
registration_forms = serializers.SerializerMethodField()
class Meta:
model = RegistrationFileGroup
fields = '__all__'
class RegistrationFileSerializer(serializers.ModelSerializer):
class Meta:
model = RegistrationFile
fields = '__all__'
class RegistrationFileTemplateSerializer(serializers.ModelSerializer):
class Meta:
model = RegistrationFileTemplate
fields = '__all__'
def get_registration_forms(self, obj):
forms = RegistrationForm.objects.filter(fileGroup=obj)
return RegistrationFormSimpleSerializer(forms, many=True).data
class LanguageSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
@ -132,7 +150,7 @@ class RegistrationFormSerializer(serializers.ModelSerializer):
registration_file = serializers.FileField(required=False)
status_label = serializers.SerializerMethodField()
formatted_last_update = serializers.SerializerMethodField()
registration_files = RegistrationFileSerializer(many=True, required=False)
registration_files = RegistrationTemplateSerializer(many=True, required=False)
fees = serializers.PrimaryKeyRelatedField(queryset=Fee.objects.all(), many=True, required=False)
discounts = serializers.PrimaryKeyRelatedField(queryset=Discount.objects.all(), many=True, required=False)
totalRegistrationFees = serializers.SerializerMethodField()

View File

@ -7,8 +7,9 @@ from .views import RegisterFormView, RegisterFormWithIdView, send, resend, archi
# SubClasses
from .views import StudentView, GuardianView, ChildrenListView, StudentListView
# Files
from .views import RegistrationFileTemplateView, RegistrationFileTemplateSimpleView, RegistrationFileView, RegistrationFileSimpleView
from .views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView
from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
from .views import registration_file_views
urlpatterns = [
re_path(r'^registerForms/(?P<id>[0-9]+)/archive$', archive, name="archive"),
@ -27,15 +28,13 @@ urlpatterns = [
# Page de formulaire d'inscription - RESPONSABLE
re_path(r'^lastGuardianId$', GuardianView.as_view(), name="lastGuardianId"),
# modèles de fichiers d'inscription
re_path(r'^registrationFileTemplates/(?P<id>[0-9]+)$', RegistrationFileTemplateSimpleView.as_view(), name="registrationFileTemplate"),
re_path(r'^registrationFileTemplates$', RegistrationFileTemplateView.as_view(), name='registrationFileTemplates'),
# fichiers d'inscription
re_path(r'^registrationFiles/(?P<id>[0-9]+)$', RegistrationFileSimpleView.as_view(), name='registrationFiles'),
re_path(r'^registrationFiles$', RegistrationFileView.as_view(), name="registrationFiles"),
re_path(r'^registrationFileGroups/(?P<id>[0-9]+)$', RegistrationFileGroupSimpleView.as_view(), name='registrationFileGroupDetail'),
re_path(r'^registrationFileGroups/(?P<id>[0-9]+)/registrationFiles$', get_registration_files_by_group, name="get_registration_files_by_group"),
re_path(r'^registrationFileGroups$', RegistrationFileGroupView.as_view(), name='registrationFileGroups'),
re_path(r'^registrationTemplateMasters/(?P<id>[0-9]+)$', RegistrationTemplateMasterSimpleView.as_view(), name='registrationTemplateMasters'),
re_path(r'^registrationTemplateMasters$', RegistrationTemplateMasterView.as_view(), name='registrationTemplateMasters'),
re_path(r'^registrationTemplates/(?P<id>[0-9]+)$', RegistrationTemplateSimpleView.as_view(), name='registrationTemplates'),
re_path(r'^registrationTemplates$', RegistrationTemplateView.as_view(), name="registrationTemplates"),
]

View File

@ -1,5 +1,5 @@
from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive
from .registration_file_views import RegistrationFileTemplateView, RegistrationFileTemplateSimpleView, RegistrationFileView, RegistrationFileSimpleView
from .registration_file_views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
from .student_views import StudentView, StudentListView, ChildrenListView
from .guardian_views import GuardianView
@ -10,10 +10,10 @@ __all__ = [
'send',
'resend',
'archive',
'RegistrationFileView',
'RegistrationFileSimpleView',
'RegistrationFileTemplateView',
'RegistrationFileTemplateSimpleView',
'RegistrationTemplateView',
'RegistrationTemplateSimpleView',
'RegistrationTemplateMasterView',
'RegistrationTemplateMasterSimpleView',
'RegistrationFileGroupView',
'RegistrationFileGroupSimpleView',
'get_registration_files_by_group',

View File

@ -19,7 +19,7 @@ import Subscriptions.util as util
from Subscriptions.serializers import RegistrationFormSerializer
from Subscriptions.pagination import CustomPagination
from Subscriptions.signals import clear_cache
from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationFile, RegistrationFileGroup
from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationTemplate, RegistrationFileGroup
from Subscriptions.automate import updateStateMachine
from N3wtSchool import settings, bdd
@ -252,7 +252,7 @@ class RegisterFormWithIdView(APIView):
registerForm.save()
# Récupération des fichiers d'inscription
fileNames = RegistrationFile.get_files_from_rf(registerForm.pk)
fileNames = RegistrationTemplate.get_files_from_rf(registerForm.pk)
if registerForm.registration_file:
fileNames.insert(0, registerForm.registration_file.path)

View File

@ -8,7 +8,7 @@ from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from Subscriptions.serializers import RegistrationFileGroupSerializer
from Subscriptions.models import RegistrationFileGroup, RegistrationFileTemplate
from Subscriptions.models import RegistrationFileGroup, RegistrationTemplateMaster
from N3wtSchool import bdd
class RegistrationFileGroupView(APIView):
@ -118,7 +118,7 @@ class RegistrationFileGroupSimpleView(APIView):
def get_registration_files_by_group(request, id):
try:
group = RegistrationFileGroup.objects.get(id=id)
templates = RegistrationFileTemplate.objects.filter(group=group)
templates = RegistrationTemplateMaster.objects.filter(group=group)
templates_data = list(templates.values())
return JsonResponse(templates_data, safe=False)
except RegistrationFileGroup.DoesNotExist:

View File

@ -1,5 +1,4 @@
from django.http.response import JsonResponse
from django.core.files import File
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from rest_framework.parsers import MultiPartParser, FormParser
@ -7,205 +6,154 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
import os
from Subscriptions.serializers import RegistrationFileTemplateSerializer, RegistrationFileSerializer
from Subscriptions.models import RegistrationFileTemplate, RegistrationFile
from Subscriptions.serializers import RegistrationTemplateMasterSerializer, RegistrationTemplateSerializer
from Subscriptions.models import RegistrationTemplateMaster, RegistrationTemplate
from N3wtSchool import bdd
class RegistrationFileTemplateView(APIView):
class RegistrationTemplateMasterView(APIView):
@swagger_auto_schema(
operation_description="Récupère tous les fichiers templates pour les dossiers d'inscription",
responses={200: RegistrationFileTemplateSerializer(many=True)}
operation_description="Récupère tous les masters de templates d'inscription",
responses={200: RegistrationTemplateMasterSerializer(many=True)}
)
def get(self, request):
"""
Récupère les fichiers templates pour les dossiers dinscription.
"""
files = RegistrationFileTemplate.objects.all()
serializer = RegistrationFileTemplateSerializer(files, many=True)
masters = RegistrationTemplateMaster.objects.all()
serializer = RegistrationTemplateMasterSerializer(masters, many=True)
return Response(serializer.data)
@swagger_auto_schema(
operation_description="Crée un nouveau fichier template pour les dossiers d'inscription",
request_body=RegistrationFileTemplateSerializer,
operation_description="Crée un nouveau master de template d'inscription",
request_body=RegistrationTemplateMasterSerializer,
responses={
201: RegistrationFileTemplateSerializer,
201: RegistrationTemplateMasterSerializer,
400: "Données invalides"
}
)
def post(self, request):
"""
Crée un fichier template pour les dossiers dinscription.
"""
serializer = RegistrationFileTemplateSerializer(data=request.data)
serializer = RegistrationTemplateMasterSerializer(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 RegistrationFileTemplateSimpleView(APIView):
"""
Gère les fichiers templates pour les dossiers dinscription.
"""
parser_classes = (MultiPartParser, FormParser)
class RegistrationTemplateMasterSimpleView(APIView):
@swagger_auto_schema(
operation_description="Récupère un fichier template spécifique",
operation_description="Récupère un master de template d'inscription spécifique",
responses={
200: RegistrationFileTemplateSerializer,
404: "Fichier template non trouvé"
200: RegistrationTemplateMasterSerializer,
404: "Master non trouvé"
}
)
def get(self, request, id):
"""
Récupère les fichiers templates pour les dossiers dinscription.
"""
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
if registationFileTemplate is None:
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileTemplateSerializer(registationFileTemplate)
master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id)
if master is None:
return JsonResponse({"errorMessage":'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationTemplateMasterSerializer(master)
return JsonResponse(serializer.data, safe=False)
@swagger_auto_schema(
operation_description="Met à jour un fichier template existant",
request_body=RegistrationFileTemplateSerializer,
operation_description="Met à jour un master de template d'inscription existant",
request_body=RegistrationTemplateMasterSerializer,
responses={
201: RegistrationFileTemplateSerializer,
200: RegistrationTemplateMasterSerializer,
400: "Données invalides",
404: "Fichier template non trouvé"
404: "Master non trouvé"
}
)
def put(self, request, id):
"""
Met à jour un fichier template existant.
"""
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
if registationFileTemplate is None:
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileTemplateSerializer(registationFileTemplate,data=request.data)
master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id)
if master is None:
return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationTemplateMasterSerializer(master, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@swagger_auto_schema(
operation_description="Supprime un fichier template",
operation_description="Supprime un master de template d'inscription",
responses={
204: "Suppression réussie",
404: "Fichier template non trouvé"
404: "Master non trouvé"
}
)
def delete(self, request, id):
"""
Supprime un fichier template existant.
"""
registrationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
if registrationFileTemplate is not None:
registrationFileTemplate.file.delete() # Supprimer le fichier uploadé
registrationFileTemplate.delete()
return JsonResponse({'message': 'La suppression du fichier d\'inscription a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id)
if master is not None:
master.delete()
return JsonResponse({'message': 'La suppression du master de template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
else:
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
class RegistrationFileView(APIView):
class RegistrationTemplateView(APIView):
@swagger_auto_schema(
operation_description="Récupère tous les fichiers d'inscription",
responses={200: RegistrationFileSerializer(many=True)}
operation_description="Récupère tous les templates d'inscription",
responses={200: RegistrationTemplateSerializer(many=True)}
)
def get(self, request):
"""
Récupère les fichiers liés à un dossier dinscription donné.
"""
files = RegistrationFile.objects.all()
serializer = RegistrationFileSerializer(files, many=True)
templates = RegistrationTemplate.objects.all()
serializer = RegistrationTemplateSerializer(templates, many=True)
return Response(serializer.data)
@swagger_auto_schema(
operation_description="Crée un nouveau fichier d'inscription",
request_body=RegistrationFileSerializer,
operation_description="Crée un nouveau template d'inscription",
request_body=RegistrationTemplateSerializer,
responses={
201: RegistrationFileSerializer,
201: RegistrationTemplateSerializer,
400: "Données invalides"
}
)
def post(self, request):
"""
Crée un RegistrationFile pour le RegistrationForm associé.
"""
serializer = RegistrationFileSerializer(data=request.data)
serializer = RegistrationTemplateSerializer(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 RegistrationFileSimpleView(APIView):
"""
Gère la création, mise à jour et suppression de fichiers liés à un dossier dinscription.
"""
parser_classes = (MultiPartParser, FormParser)
class RegistrationTemplateSimpleView(APIView):
@swagger_auto_schema(
operation_description="Récupère un fichier d'inscription spécifique",
operation_description="Récupère un template d'inscription spécifique",
responses={
200: RegistrationFileSerializer,
404: "Fichier non trouvé"
200: RegistrationTemplateSerializer,
404: "Template non trouvé"
}
)
def get(self, request, id):
"""
Récupère les fichiers liés à un dossier dinscription donné.
"""
registationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
if registationFile is None:
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileSerializer(registationFile)
template = bdd.getObject(_objectName=RegistrationTemplate, _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 = RegistrationTemplateSerializer(template)
return JsonResponse(serializer.data, safe=False)
@swagger_auto_schema(
operation_description="Met à jour un fichier d'inscription existant",
request_body=RegistrationFileSerializer,
operation_description="Met à jour un template d'inscription existant",
request_body=RegistrationTemplateSerializer,
responses={
200: openapi.Response(
description="Fichier mis à jour avec succès",
schema=RegistrationFileSerializer
),
200: RegistrationTemplateSerializer,
400: "Données invalides",
404: "Fichier non trouvé"
404: "Template non trouvé"
}
)
def put(self, request, id):
"""
Met à jour un RegistrationFile existant.
"""
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
if registrationFile is None:
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileSerializer(registrationFile, data=request.data)
template = bdd.getObject(_objectName=RegistrationTemplate, _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)
serializer = RegistrationTemplateSerializer(template, data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'message': 'Fichier mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK)
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 fichier d'inscription",
operation_description="Supprime un template d'inscription",
responses={
200: "Suppression réussie",
404: "Fichier non trouvé"
204: "Suppression réussie",
404: "Template non trouvé"
}
)
def delete(self, request, id):
"""
Supprime un RegistrationFile existant.
"""
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
if registrationFile is not None:
registrationFile.file.delete() # Supprimer le fichier uploadé
registrationFile.delete()
return JsonResponse({'message': 'La suppression du fichier a été effectuée avec succès'}, safe=False)
template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id)
if template is not None:
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 fichier n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
return JsonResponse({'erreur': 'Le template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)