From 2b414b83913c2f0f81cf226b78577ad522443d7b Mon Sep 17 00:00:00 2001 From: N3WT DE COMPET Date: Sun, 12 Jan 2025 10:07:06 +0100 Subject: [PATCH] refactor: Traduction en anglais des modules "GestionInscription" et "GestionLogin" --- Back-End/{GestionLogin => Auth}/__init__.py | 0 .../{GestionEnseignants => Auth}/admin.py | 0 Back-End/{GestionLogin => Auth}/apps.py | 2 +- Back-End/{GestionLogin => Auth}/backends.py | 8 +- Back-End/{GestionLogin => Auth}/models.py | 2 +- .../{GestionLogin => Auth}/serializers.py | 10 +- .../templates/GestionLogin/login.html | 0 .../templates/GestionLogin/new-password.html | 0 .../GestionLogin/reset-password.html | 0 .../templates/GestionLogin/subscribe.html | 0 Back-End/Auth/urls.py | 22 ++ Back-End/{GestionLogin => Auth}/validator.py | 0 Back-End/{GestionLogin => Auth}/views.py | 38 +-- Back-End/GestionEnseignants/__init__.py | 1 - Back-End/GestionEnseignants/apps.py | 14 - Back-End/GestionEnseignants/models.py | 68 ----- Back-End/GestionEnseignants/serializers.py | 236 --------------- Back-End/GestionEnseignants/urls.py | 21 -- Back-End/GestionInscriptions/__init__.py | 1 - Back-End/GestionInscriptions/automate.py | 45 --- Back-End/GestionInscriptions/models.py | 179 ----------- Back-End/GestionInscriptions/serializers.py | 202 ------------- .../templatetags/myTemplateTag.py | 23 -- Back-End/GestionInscriptions/urls.py | 36 --- Back-End/GestionInscriptions/util.py | 201 ------------- Back-End/GestionInscriptions/views.py | 282 ------------------ Back-End/GestionLogin/urls.py | 22 -- Back-End/GestionMessagerie/models.py | 6 +- Back-End/GestionMessagerie/serializers.py | 2 +- Back-End/GestionNotification/models.py | 4 +- Back-End/GestionNotification/signals.py | 8 +- Back-End/GestionNotification/views.py | 2 +- Back-End/N3wtSchool/bdd.py | 24 +- Back-End/N3wtSchool/settings.py | 22 +- Back-End/N3wtSchool/signals.py | 2 +- Back-End/N3wtSchool/urls.py | 8 +- Back-End/School/__init__.py | 1 + Back-End/{GestionLogin => School}/admin.py | 0 Back-End/School/apps.py | 14 + Back-End/School/models.py | 66 ++++ Back-End/School/serializers.py | 194 ++++++++++++ .../{GestionEnseignants => School}/tests.py | 0 Back-End/School/urls.py | 21 ++ .../{GestionEnseignants => School}/views.py | 164 +++++----- .../Configuration/application.default.json | 0 .../Configuration/automate.json | 0 .../Configuration/inscriptions.json | 0 Back-End/Subscriptions/__init__.py | 1 + .../admin.py | 4 +- .../apps.py | 4 +- Back-End/Subscriptions/automate.py | 45 +++ .../mailManager.py | 4 +- Back-End/Subscriptions/models.py | 179 +++++++++++ .../pagination.py | 2 +- Back-End/Subscriptions/serializers.py | 214 +++++++++++++ .../signals.py | 20 +- .../tasks.py | 8 +- .../ajouterFicheEleve.html | 2 +- .../GestionInscriptions/configure.html | 2 +- .../GestionInscriptions/creationDossier.html | 4 +- .../GestionInscriptions/editStudent.html | 4 +- .../templates/GestionInscriptions/index.html | 18 +- .../templates/base.html | 0 .../templates/pdfs/dossier_inscription.html | 60 ++-- .../templatetags/myTemplateTag.py | 18 ++ Back-End/Subscriptions/urls.py | 36 +++ Back-End/Subscriptions/util.py | 82 +++++ Back-End/Subscriptions/views.py | 280 +++++++++++++++++ Back-End/start.py | 6 +- 69 files changed, 1393 insertions(+), 1551 deletions(-) rename Back-End/{GestionLogin => Auth}/__init__.py (100%) rename Back-End/{GestionEnseignants => Auth}/admin.py (100%) rename Back-End/{GestionLogin => Auth}/apps.py (87%) rename Back-End/{GestionLogin => Auth}/backends.py (70%) rename Back-End/{GestionLogin => Auth}/models.py (96%) rename Back-End/{GestionLogin => Auth}/serializers.py (90%) rename Back-End/{GestionLogin => Auth}/templates/GestionLogin/login.html (100%) rename Back-End/{GestionLogin => Auth}/templates/GestionLogin/new-password.html (100%) rename Back-End/{GestionLogin => Auth}/templates/GestionLogin/reset-password.html (100%) rename Back-End/{GestionLogin => Auth}/templates/GestionLogin/subscribe.html (100%) create mode 100644 Back-End/Auth/urls.py rename Back-End/{GestionLogin => Auth}/validator.py (100%) rename Back-End/{GestionLogin => Auth}/views.py (90%) delete mode 100644 Back-End/GestionEnseignants/__init__.py delete mode 100644 Back-End/GestionEnseignants/apps.py delete mode 100644 Back-End/GestionEnseignants/models.py delete mode 100644 Back-End/GestionEnseignants/serializers.py delete mode 100644 Back-End/GestionEnseignants/urls.py delete mode 100644 Back-End/GestionInscriptions/__init__.py delete mode 100644 Back-End/GestionInscriptions/automate.py delete mode 100644 Back-End/GestionInscriptions/models.py delete mode 100644 Back-End/GestionInscriptions/serializers.py delete mode 100644 Back-End/GestionInscriptions/templatetags/myTemplateTag.py delete mode 100644 Back-End/GestionInscriptions/urls.py delete mode 100644 Back-End/GestionInscriptions/util.py delete mode 100644 Back-End/GestionInscriptions/views.py delete mode 100644 Back-End/GestionLogin/urls.py create mode 100644 Back-End/School/__init__.py rename Back-End/{GestionLogin => School}/admin.py (100%) create mode 100644 Back-End/School/apps.py create mode 100644 Back-End/School/models.py create mode 100644 Back-End/School/serializers.py rename Back-End/{GestionEnseignants => School}/tests.py (100%) create mode 100644 Back-End/School/urls.py rename Back-End/{GestionEnseignants => School}/views.py (50%) rename Back-End/{GestionInscriptions => Subscriptions}/Configuration/application.default.json (100%) rename Back-End/{GestionInscriptions => Subscriptions}/Configuration/automate.json (100%) rename Back-End/{GestionInscriptions => Subscriptions}/Configuration/inscriptions.json (100%) create mode 100644 Back-End/Subscriptions/__init__.py rename Back-End/{GestionInscriptions => Subscriptions}/admin.py (79%) rename Back-End/{GestionInscriptions => Subscriptions}/apps.py (69%) create mode 100644 Back-End/Subscriptions/automate.py rename Back-End/{GestionInscriptions => Subscriptions}/mailManager.py (95%) create mode 100644 Back-End/Subscriptions/models.py rename Back-End/{GestionInscriptions => Subscriptions}/pagination.py (93%) create mode 100644 Back-End/Subscriptions/serializers.py rename Back-End/{GestionInscriptions => Subscriptions}/signals.py (71%) rename Back-End/{GestionInscriptions => Subscriptions}/tasks.py (82%) rename Back-End/{GestionInscriptions => Subscriptions}/templates/GestionInscriptions/ajouterFicheEleve.html (96%) rename Back-End/{GestionInscriptions => Subscriptions}/templates/GestionInscriptions/configure.html (87%) rename Back-End/{GestionInscriptions => Subscriptions}/templates/GestionInscriptions/creationDossier.html (97%) rename Back-End/{GestionInscriptions => Subscriptions}/templates/GestionInscriptions/editStudent.html (92%) rename Back-End/{GestionInscriptions => Subscriptions}/templates/GestionInscriptions/index.html (88%) rename Back-End/{GestionInscriptions => Subscriptions}/templates/base.html (100%) rename Back-End/{GestionInscriptions => Subscriptions}/templates/pdfs/dossier_inscription.html (52%) create mode 100644 Back-End/Subscriptions/templatetags/myTemplateTag.py create mode 100644 Back-End/Subscriptions/urls.py create mode 100644 Back-End/Subscriptions/util.py create mode 100644 Back-End/Subscriptions/views.py diff --git a/Back-End/GestionLogin/__init__.py b/Back-End/Auth/__init__.py similarity index 100% rename from Back-End/GestionLogin/__init__.py rename to Back-End/Auth/__init__.py diff --git a/Back-End/GestionEnseignants/admin.py b/Back-End/Auth/admin.py similarity index 100% rename from Back-End/GestionEnseignants/admin.py rename to Back-End/Auth/admin.py diff --git a/Back-End/GestionLogin/apps.py b/Back-End/Auth/apps.py similarity index 87% rename from Back-End/GestionLogin/apps.py rename to Back-End/Auth/apps.py index 8aa6c1e..3116bfa 100644 --- a/Back-End/GestionLogin/apps.py +++ b/Back-End/Auth/apps.py @@ -3,5 +3,5 @@ from django.db.models.signals import post_migrate class GestionloginConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'GestionLogin' + name = 'Auth' diff --git a/Back-End/GestionLogin/backends.py b/Back-End/Auth/backends.py similarity index 70% rename from Back-End/GestionLogin/backends.py rename to Back-End/Auth/backends.py index 0db862f..474407e 100644 --- a/Back-End/GestionLogin/backends.py +++ b/Back-End/Auth/backends.py @@ -1,20 +1,20 @@ from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend -from GestionLogin.models import Profil +from Auth.models import Profile from N3wtSchool import bdd class EmailBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): if username is None: - username = kwargs.get(Profil.USERNAME_FIELD) + username = kwargs.get(Profile.USERNAME_FIELD) try: - user = Profil.objects.get(email=username) + user = Profile.objects.get(email=username) # Vérifie le mot de passe de l'utilisateur if user.check_password(password): return user - except Profil.DoesNotExist: + except Profile.DoesNotExist: return None diff --git a/Back-End/GestionLogin/models.py b/Back-End/Auth/models.py similarity index 96% rename from Back-End/GestionLogin/models.py rename to Back-End/Auth/models.py index f6af900..9792319 100644 --- a/Back-End/GestionLogin/models.py +++ b/Back-End/Auth/models.py @@ -3,7 +3,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from django.core.validators import EmailValidator -class Profil(AbstractUser): +class Profile(AbstractUser): class Droits(models.IntegerChoices): PROFIL_UNDEFINED = -1, _('NON DEFINI') PROFIL_ECOLE = 0, _('ECOLE') diff --git a/Back-End/GestionLogin/serializers.py b/Back-End/Auth/serializers.py similarity index 90% rename from Back-End/GestionLogin/serializers.py rename to Back-End/Auth/serializers.py index 7281e41..8ae7737 100644 --- a/Back-End/GestionLogin/serializers.py +++ b/Back-End/Auth/serializers.py @@ -1,18 +1,18 @@ from rest_framework import serializers -from GestionLogin.models import Profil +from Auth.models import Profile from django.core.exceptions import ValidationError -class ProfilSerializer(serializers.ModelSerializer): +class ProfileSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False) password = serializers.CharField(write_only=True) class Meta: - model = Profil + model = Profile fields = ['id', 'password', 'email', 'code', 'datePeremption', 'estConnecte', 'droit', 'username', 'is_active'] extra_kwargs = {'password': {'write_only': True}} def create(self, validated_data): - user = Profil( + user = Profile( username=validated_data['username'], email=validated_data['email'], is_active=validated_data['is_active'], @@ -29,7 +29,7 @@ class ProfilSerializer(serializers.ModelSerializer): class ProfilUpdateSerializer(serializers.ModelSerializer): class Meta: - model = Profil + model = Profile fields = ['id', 'password', 'email', 'code', 'datePeremption', 'estConnecte', 'droit', 'username', 'is_active'] extra_kwargs = { 'password': {'write_only': True, 'required': False} diff --git a/Back-End/GestionLogin/templates/GestionLogin/login.html b/Back-End/Auth/templates/GestionLogin/login.html similarity index 100% rename from Back-End/GestionLogin/templates/GestionLogin/login.html rename to Back-End/Auth/templates/GestionLogin/login.html diff --git a/Back-End/GestionLogin/templates/GestionLogin/new-password.html b/Back-End/Auth/templates/GestionLogin/new-password.html similarity index 100% rename from Back-End/GestionLogin/templates/GestionLogin/new-password.html rename to Back-End/Auth/templates/GestionLogin/new-password.html diff --git a/Back-End/GestionLogin/templates/GestionLogin/reset-password.html b/Back-End/Auth/templates/GestionLogin/reset-password.html similarity index 100% rename from Back-End/GestionLogin/templates/GestionLogin/reset-password.html rename to Back-End/Auth/templates/GestionLogin/reset-password.html diff --git a/Back-End/GestionLogin/templates/GestionLogin/subscribe.html b/Back-End/Auth/templates/GestionLogin/subscribe.html similarity index 100% rename from Back-End/GestionLogin/templates/GestionLogin/subscribe.html rename to Back-End/Auth/templates/GestionLogin/subscribe.html diff --git a/Back-End/Auth/urls.py b/Back-End/Auth/urls.py new file mode 100644 index 0000000..2616d2c --- /dev/null +++ b/Back-End/Auth/urls.py @@ -0,0 +1,22 @@ +from django.urls import path, re_path + +from . import views +import Auth.views +from Auth.views import ProfileView, ProfileListView, SessionView, LoginView, SubscribeView, NewPasswordView, ResetPasswordView + +urlpatterns = [ + re_path(r'^csrf$', Auth.views.csrf, name='csrf'), + + re_path(r'^login$', LoginView.as_view(), name="login"), + re_path(r'^subscribe$', SubscribeView.as_view(), name='subscribe'), + re_path(r'^newPassword$', NewPasswordView.as_view(), name='newPassword'), + re_path(r'^resetPassword/([a-zA-Z]+)$', ResetPasswordView.as_view(), name='resetPassword'), + re_path(r'^infoSession$', Auth.views.infoSession, name='infoSession'), + + re_path(r'^profiles$', ProfileListView.as_view(), name="profile"), + re_path(r'^profile$', ProfileView.as_view(), name="profile"), + re_path(r'^profile/([0-9]+)$', ProfileView.as_view(), name="profile"), + + # Test SESSION VIEW + re_path(r'^session$', SessionView.as_view(), name="session"), +] \ No newline at end of file diff --git a/Back-End/GestionLogin/validator.py b/Back-End/Auth/validator.py similarity index 100% rename from Back-End/GestionLogin/validator.py rename to Back-End/Auth/validator.py diff --git a/Back-End/GestionLogin/views.py b/Back-End/Auth/views.py similarity index 90% rename from Back-End/GestionLogin/views.py rename to Back-End/Auth/views.py index 4fb0902..9f09124 100644 --- a/Back-End/GestionLogin/views.py +++ b/Back-End/Auth/views.py @@ -15,13 +15,13 @@ import jwt import json from . import validator -from .models import Profil +from .models import Profile -from GestionLogin.serializers import ProfilSerializer, ProfilUpdateSerializer -from GestionInscriptions.models import FicheInscription -from GestionInscriptions.signals import clear_cache -import GestionInscriptions.mailManager as mailer -import GestionInscriptions.util as util +from Auth.serializers import ProfileSerializer, ProfilUpdateSerializer +from Subscriptions.models import RegistrationForm +from Subscriptions.signals import clear_cache +import Subscriptions.mailManager as mailer +import Subscriptions.util as util from N3wtSchool import bdd, error @@ -38,7 +38,7 @@ class SessionView(APIView): decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256']) print(f'decode : {decoded_token}') user_id = decoded_token.get('id') - user = Profil.objects.get(id=user_id) + user = Profile.objects.get(id=user_id) response_data = { 'user': { @@ -53,24 +53,24 @@ class SessionView(APIView): except jwt.InvalidTokenError: return JsonResponse({"error": "Invalid token"}, status=status.HTTP_401_UNAUTHORIZED) -class ListProfilView(APIView): +class ProfileListView(APIView): def get(self, request): - profilsList = bdd.getAllObjects(_objectName=Profil) - profils_serializer = ProfilSerializer(profilsList, many=True) + profilsList = bdd.getAllObjects(_objectName=Profile) + profils_serializer = ProfileSerializer(profilsList, many=True) return JsonResponse(profils_serializer.data, safe=False) @method_decorator(csrf_protect, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch') -class ProfilView(APIView): +class ProfileView(APIView): def get(self, request, _id): - profil=bdd.getObject(Profil, "id", _id) - profil_serializer=ProfilSerializer(profil) + profil=bdd.getObject(Profile, "id", _id) + profil_serializer=ProfileSerializer(profil) return JsonResponse(profil_serializer.data, safe=False) def post(self, request): profil_data=JSONParser().parse(request) print(f'{profil_data}') - profil_serializer = ProfilSerializer(data=profil_data) + profil_serializer = ProfileSerializer(data=profil_data) if profil_serializer.is_valid(): profil_serializer.save() @@ -82,7 +82,7 @@ class ProfilView(APIView): def put(self, request, _id): data=JSONParser().parse(request) - profil = Profil.objects.get(id=_id) + profil = Profile.objects.get(id=_id) profil_serializer = ProfilUpdateSerializer(profil, data=data) if profil_serializer.is_valid(): profil_serializer.save() @@ -95,7 +95,7 @@ def infoSession(request): if profilCache: return JsonResponse({"cacheSession":True,"typeProfil":profilCache.droit, "username":profilCache.email}, safe=False) else: - return JsonResponse({"cacheSession":False,"typeProfil":Profil.Droits.PROFIL_UNDEFINED, "username":""}, safe=False) + return JsonResponse({"cacheSession":False,"typeProfil":Profile.Droits.PROFIL_UNDEFINED, "username":""}, safe=False) @method_decorator(csrf_protect, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch') @@ -170,7 +170,7 @@ class SubscribeView(APIView): if validationOk: # On vérifie que l'email existe : si ce n'est pas le cas, on retourne une erreur - profil = bdd.getProfile(Profil.objects.all(), newProfilConnection.get('email')) + profil = bdd.getProfile(Profile.objects.all(), newProfilConnection.get('email')) if profil == None: retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS] else: @@ -214,7 +214,7 @@ class NewPasswordView(APIView): validationOk, errorFields = validatorNewPassword.validate() if validationOk: - profil = bdd.getProfile(Profil.objects.all(), newProfilConnection.get('email')) + profil = bdd.getProfile(Profile.objects.all(), newProfilConnection.get('email')) if profil == None: retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS] else: @@ -247,7 +247,7 @@ class ResetPasswordView(APIView): validatorResetPassword = validator.ValidatorResetPassword(data=newProfilConnection) validationOk, errorFields = validatorResetPassword.validate() - profil = bdd.getObject(Profil, "code", _uuid) + profil = bdd.getObject(Profile, "code", _uuid) if profil: if datetime.strptime(util.convertToStr(util._now(), '%d-%m-%Y %H:%M'), '%d-%m-%Y %H:%M') > datetime.strptime(profil.datePeremption, '%d-%m-%Y %H:%M'): diff --git a/Back-End/GestionEnseignants/__init__.py b/Back-End/GestionEnseignants/__init__.py deleted file mode 100644 index 04932af..0000000 --- a/Back-End/GestionEnseignants/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = 'GestionEnseignants.apps.GestionenseignantsConfig' diff --git a/Back-End/GestionEnseignants/apps.py b/Back-End/GestionEnseignants/apps.py deleted file mode 100644 index b9397d8..0000000 --- a/Back-End/GestionEnseignants/apps.py +++ /dev/null @@ -1,14 +0,0 @@ -from django.apps import AppConfig -from django.db.models.signals import post_migrate - -def create_specialite(sender, **kwargs): - from .models import Specialite - if not Specialite.objects.filter(nom='GROUPE').exists(): - Specialite.objects.create(nom='GROUPE', codeCouleur='#FF0000') - -class GestionenseignantsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'GestionEnseignants' - - def ready(self): - post_migrate.connect(create_specialite, sender=self) diff --git a/Back-End/GestionEnseignants/models.py b/Back-End/GestionEnseignants/models.py deleted file mode 100644 index 8a18bb5..0000000 --- a/Back-End/GestionEnseignants/models.py +++ /dev/null @@ -1,68 +0,0 @@ -from django.db import models -from GestionLogin.models import Profil -from django.db.models import JSONField -from django.dispatch import receiver -from django.contrib.postgres.fields import ArrayField - -NIVEAU_CHOICES = [ - (1, 'Très Petite Section (TPS)'), - (2, 'Petite Section (PS)'), - (3, 'Moyenne Section (MS)'), - (4, 'Grande Section (GS)'), - (5, 'Cours Préparatoire (CP)'), - (6, 'Cours Élémentaire 1 (CE1)'), - (7, 'Cours Élémentaire 2 (CE2)'), - (8, 'Cours Moyen 1 (CM1)'), - (9, 'Cours Moyen 2 (CM2)') -] - -class Specialite(models.Model): - nom = models.CharField(max_length=100) - dateCreation = models.DateTimeField(auto_now=True) - codeCouleur = models.CharField(max_length=7, default='#FFFFFF') - - def __str__(self): - return self.nom - -class Enseignant(models.Model): - nom = models.CharField(max_length=100) - prenom = models.CharField(max_length=100) - mail = models.EmailField(unique=True) - specialites = models.ManyToManyField(Specialite, related_name='enseignants') - profilAssocie = models.ForeignKey(Profil, on_delete=models.CASCADE, null=True, blank=True) - dateCreation = models.DateTimeField(auto_now=True) - - def __str__(self): - return f"{self.nom} {self.prenom}" - -class Classe(models.Model): - PLANNING_TYPE_CHOICES = [ - (1, 'Annuel'), - (2, 'Semestriel'), - (3, 'Trimestriel') - ] - - nom_ambiance = models.CharField(max_length=255, null=True, blank=True) - tranche_age = models.JSONField() - nombre_eleves = models.PositiveIntegerField() - langue_enseignement = models.CharField(max_length=255) - annee_scolaire = models.CharField(max_length=9) - dateCreation = models.DateTimeField(auto_now_add=True) - enseignants = models.ManyToManyField(Enseignant, related_name='classes') - niveaux = ArrayField(models.IntegerField(choices=NIVEAU_CHOICES), default=list) - type = models.IntegerField(choices=PLANNING_TYPE_CHOICES, default=1) - plage_horaire = models.JSONField(default=list) - jours_ouverture = ArrayField(models.IntegerField(), default=list) - - def __str__(self): - return self.nom_ambiance - -class Planning(models.Model): - niveau = models.IntegerField(choices=NIVEAU_CHOICES, null=True, blank=True) - classe = models.ForeignKey(Classe, null=True, blank=True, related_name='plannings', on_delete=models.CASCADE) - emploiDuTemps = JSONField(default=dict) - - def __str__(self): - return f'Planning de {self.niveau} pour {self.classe.nom_ambiance}' - - diff --git a/Back-End/GestionEnseignants/serializers.py b/Back-End/GestionEnseignants/serializers.py deleted file mode 100644 index 979adda..0000000 --- a/Back-End/GestionEnseignants/serializers.py +++ /dev/null @@ -1,236 +0,0 @@ -from rest_framework import serializers -from .models import Enseignant, Specialite, Classe, Planning, NIVEAU_CHOICES -from GestionInscriptions.models import FicheInscription -from GestionInscriptions.serializers import EleveSerializer -from GestionLogin.serializers import ProfilSerializer -from GestionLogin.models import Profil -from N3wtSchool import settings, bdd -from django.utils import timezone -import pytz - -class SpecialiteSerializer(serializers.ModelSerializer): - dateCreation_formattee = serializers.SerializerMethodField() - class Meta: - model = Specialite - fields = '__all__' - - def get_dateCreation_formattee(self, obj): - utc_time = timezone.localtime(obj.dateCreation) # Convertir en heure locale - local_tz = pytz.timezone(settings.TZ_APPLI) - local_time = utc_time.astimezone(local_tz) - - return local_time.strftime("%d-%m-%Y %H:%M") - -class EnseignantDetailSerializer(serializers.ModelSerializer): - specialites = SpecialiteSerializer(many=True, read_only=True) - - class Meta: - model = Enseignant - fields = ['id', 'nom', 'prenom', 'mail', 'specialites'] - -class EnseignantSerializer(serializers.ModelSerializer): - specialites = SpecialiteSerializer(many=True, read_only=True) - specialites_ids = serializers.PrimaryKeyRelatedField(queryset=Specialite.objects.all(), many=True, source='specialites') - profilAssocie_id = serializers.PrimaryKeyRelatedField(queryset=Profil.objects.all(), source='profilAssocie', write_only=False, read_only=False) - classes_principal = serializers.PrimaryKeyRelatedField(many=True, read_only=True, source='classes') - profilAssocie = ProfilSerializer(read_only=True) - DroitLabel = serializers.SerializerMethodField() - DroitValue = serializers.SerializerMethodField() - dateCreation_formattee = serializers.SerializerMethodField() - - class Meta: - model = Enseignant - fields = ['id', 'nom', 'prenom', 'mail', 'specialites', 'specialites_ids', 'classes_principal', 'profilAssocie', 'profilAssocie_id', 'DroitLabel', 'DroitValue', 'dateCreation', 'dateCreation_formattee'] - - def create(self, validated_data): - specialites_data = validated_data.pop('specialites', None) - profilAssocie = validated_data.pop('profilAssocie', None) - enseignant = Enseignant.objects.create(**validated_data) - enseignant.specialites.set(specialites_data) - if profilAssocie: - enseignant.profilAssocie = profilAssocie - enseignant.save() - return enseignant - - def update(self, instance, validated_data): - specialites_data = validated_data.pop('specialites', []) - instance.nom = validated_data.get('nom', instance.nom) - instance.prenom = validated_data.get('prenom', instance.prenom) - instance.mail = validated_data.get('mail', instance.mail) - instance.profilAssocie = validated_data.get('profilAssocie', instance.profilAssocie) - instance.save() - instance.specialites.set(specialites_data) - return instance - - def get_DroitLabel(self, obj): - return obj.profilAssocie.get_droit_display() if obj.profilAssocie else None - - def get_DroitValue(self, obj): - return obj.profilAssocie.droit if obj.profilAssocie else None - - def get_dateCreation_formattee(self, obj): - utc_time = timezone.localtime(obj.dateCreation) # Convertir en heure locale - local_tz = pytz.timezone(settings.TZ_APPLI) - local_time = utc_time.astimezone(local_tz) - return local_time.strftime("%d-%m-%Y %H:%M") - -class PlanningSerializer(serializers.ModelSerializer): - # emploiDuTemps = serializers.SerializerMethodField() - - class Meta: - model = Planning - fields = ['id', 'niveau', 'emploiDuTemps'] - - # def get_emploiDuTemps(self, obj): - # emploi_du_temps = obj.emploiDuTemps - # if obj.classe: - # enseignants = obj.classe.enseignants.all() # Récupérer tous les enseignants associés à la classe - - # # Dictionnaire pour accéder rapidement aux spécialités des enseignants - # specialite_enseignants = {} - # for enseignant in enseignants: - # for specialite in enseignant.specialites.all(): - # if specialite.nom not in specialite_enseignants: - # specialite_enseignants[specialite.nom] = [] - # specialite_enseignants[specialite.nom].append(f"{enseignant.prenom} {enseignant.nom}") - - # if obj.classe.type == 1: # Planning annuel - # for day, events in emploi_du_temps.items(): - # for event in events: - # # Ajouter les enseignants associés à la spécialité - # event['teachers'] = specialite_enseignants.get(event['matiere'], []) - # # Ajouter la couleur de la spécialité - # event['color'] = next( - # (specialite.codeCouleur for enseignant in enseignants for specialite in enseignant.specialites.all() if specialite.nom == event['matiere']), - # "#FFFFFF" # Couleur par défaut si non trouvée - # ) - - # elif obj.classe.type in [2, 3]: # Planning semestriel ou trimestriel - # for period_key, period_value in emploi_du_temps.items(): - # for day, events in period_value.items(): - # if day in ['DateDebut', 'DateFin']: - # continue # Ignorer les clés DateDebut et DateFin - # for event in events: - # print(f'event : {event}') - # # Ajouter les enseignants associés à la spécialité - # event['teachers'] = specialite_enseignants.get(event['matiere'], []) - # # Ajouter la couleur de la spécialité - # event['color'] = next( - # (specialite.codeCouleur for enseignant in enseignants for specialite in enseignant.specialites.all() if specialite.nom == event['matiere']), - # "#FFFFFF" # Couleur par défaut si non trouvée - # ) - - # return emploi_du_temps - - def to_internal_value(self, data): - internal_value = super().to_internal_value(data) - internal_value['emploiDuTemps'] = data.get('emploiDuTemps', {}) - return internal_value - -class ClasseSerializer(serializers.ModelSerializer): - dateCreation_formattee = serializers.SerializerMethodField() - enseignants = EnseignantSerializer(many=True, read_only=True) - enseignants_ids = serializers.PrimaryKeyRelatedField(queryset=Enseignant.objects.all(), many=True, source='enseignants') - eleves = serializers.SerializerMethodField() - niveaux = serializers.ListField(child=serializers.ChoiceField(choices=NIVEAU_CHOICES)) - plannings_read = serializers.SerializerMethodField() - plannings = PlanningSerializer(many=True, write_only=True) - - class Meta: - model = Classe - fields = [ - 'id', 'nom_ambiance', 'tranche_age', 'nombre_eleves', 'langue_enseignement', - 'enseignants', 'enseignants_ids', 'annee_scolaire', 'dateCreation', - 'dateCreation_formattee', 'eleves', 'niveaux', 'type', 'plage_horaire', - 'jours_ouverture', 'plannings', 'plannings_read' - ] - - def create(self, validated_data): - enseignants_data = validated_data.pop('enseignants', []) - niveaux_data = validated_data.pop('niveaux', []) - plannings_data = validated_data.pop('plannings', []) - - classe = Classe.objects.create( - nom_ambiance=validated_data.get('nom_ambiance', ''), - tranche_age=validated_data.get('tranche_age', []), - nombre_eleves=validated_data.get('nombre_eleves', 0), - langue_enseignement=validated_data.get('langue_enseignement', ''), - annee_scolaire=validated_data.get('annee_scolaire', ''), - niveaux=niveaux_data, - type=validated_data.get('type', 1), # Ajouté ici - plage_horaire=validated_data.get('plage_horaire', ['08:30', '17:30']), # Ajouté ici - jours_ouverture=validated_data.get('jours_ouverture', [1, 2, 4, 5]) # Ajouté ici - ) - - classe.enseignants.set(enseignants_data) - - for planning_data in plannings_data: - Planning.objects.create( - classe=classe, - niveau=planning_data['niveau'], - emploiDuTemps=planning_data.get('emploiDuTemps', {}) - ) - - return classe - - def update(self, instance, validated_data): - enseignants_data = validated_data.pop('enseignants', []) - niveaux_data = validated_data.pop('niveaux', []) - plannings_data = validated_data.pop('plannings', []) - - instance.nom_ambiance = validated_data.get('nom_ambiance', instance.nom_ambiance) - instance.tranche_age = validated_data.get('tranche_age', instance.tranche_age) - instance.nombre_eleves = validated_data.get('nombre_eleves', instance.nombre_eleves) - instance.langue_enseignement = validated_data.get('langue_enseignement', instance.langue_enseignement) - instance.annee_scolaire = validated_data.get('annee_scolaire', instance.annee_scolaire) - instance.niveaux = niveaux_data - instance.type = validated_data.get('type', instance.type) # Ajouté ici - instance.plage_horaire = validated_data.get('plage_horaire', instance.plage_horaire) # Ajouté ici - instance.jours_ouverture = validated_data.get('jours_ouverture', instance.jours_ouverture) # Ajouté ici - - instance.save() - instance.enseignants.set(enseignants_data) - - existing_plannings = {planning.niveau: planning for planning in instance.plannings.all()} - - for planning_data in plannings_data: - niveau = planning_data['niveau'] - if niveau in existing_plannings: - # Mettre à jour le planning existant - planning = existing_plannings[niveau] - planning.emploiDuTemps = planning_data.get('emploiDuTemps', planning.emploiDuTemps) - planning.save() - else: - # Créer un nouveau planning si niveau non existant - Planning.objects.create( - classe=instance, - niveau=niveau, - emploiDuTemps=planning_data.get('emploiDuTemps', {}) - ) - - return instance - - def get_dateCreation_formattee(self, obj): - utc_time = timezone.localtime(obj.dateCreation) - local_tz = pytz.timezone(settings.TZ_APPLI) - local_time = utc_time.astimezone(local_tz) - return local_time.strftime("%d-%m-%Y %H:%M") - - def get_eleves(self, obj): - elevesList = obj.eleves.all() - filtered_eleves = [] - for eleve in elevesList: - ficheInscription = bdd.getObject(FicheInscription, "eleve__id", eleve.id) - if ficheInscription.etat == ficheInscription.EtatDossierInscription.DI_VALIDE: - filtered_eleves.append(eleve) - return EleveSerializer(filtered_eleves, many=True, read_only=True).data - - def get_plannings_read(self, obj): - plannings = obj.plannings.all() - niveaux_dict = {niveau: {'niveau': niveau, 'planning': None} for niveau in obj.niveaux} - - for planning in plannings: - if planning.niveau in niveaux_dict: - niveaux_dict[planning.niveau]['planning'] = PlanningSerializer(planning).data - - return list(niveaux_dict.values()) diff --git a/Back-End/GestionEnseignants/urls.py b/Back-End/GestionEnseignants/urls.py deleted file mode 100644 index cfc4420..0000000 --- a/Back-End/GestionEnseignants/urls.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.urls import path, re_path - -from GestionEnseignants.views import EnseignantsView, EnseignantView, SpecialitesView, SpecialiteView, ClassesView, ClasseView, PlanningsView, PlanningView - -urlpatterns = [ - re_path(r'^enseignants$', EnseignantsView.as_view(), name="enseignants"), - re_path(r'^enseignant$', EnseignantView.as_view(), name="enseignant"), - re_path(r'^enseignant/([0-9]+)$', EnseignantView.as_view(), name="enseignant"), - - re_path(r'^specialites$', SpecialitesView.as_view(), name="specialites"), - re_path(r'^specialite$', SpecialiteView.as_view(), name="specialite"), - re_path(r'^specialite/([0-9]+)$', SpecialiteView.as_view(), name="specialite"), - - re_path(r'^classes$', ClassesView.as_view(), name="classes"), - re_path(r'^classe$', ClasseView.as_view(), name="classe"), - re_path(r'^classe/([0-9]+)$', ClasseView.as_view(), name="classe"), - - re_path(r'^plannings$', PlanningsView.as_view(), name="plannings"), - re_path(r'^planning$', PlanningView.as_view(), name="planning"), - re_path(r'^planning/([0-9]+)$', PlanningView.as_view(), name="planning"), -] \ No newline at end of file diff --git a/Back-End/GestionInscriptions/__init__.py b/Back-End/GestionInscriptions/__init__.py deleted file mode 100644 index b290843..0000000 --- a/Back-End/GestionInscriptions/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = 'GestionInscriptions.apps.GestionInscriptionsConfig' \ No newline at end of file diff --git a/Back-End/GestionInscriptions/automate.py b/Back-End/GestionInscriptions/automate.py deleted file mode 100644 index 510876e..0000000 --- a/Back-End/GestionInscriptions/automate.py +++ /dev/null @@ -1,45 +0,0 @@ -# state_machine.py -import json -from GestionInscriptions.models import FicheInscription -from GestionInscriptions.signals import clear_cache - -state_mapping = { - "ABSENT": FicheInscription.EtatDossierInscription.DI_ABSENT, - "CREE": FicheInscription.EtatDossierInscription.DI_CREE, - "ENVOYE": FicheInscription.EtatDossierInscription.DI_ENVOYE, - "EN_VALIDATION": FicheInscription.EtatDossierInscription.DI_EN_VALIDATION, - "A_RELANCER": FicheInscription.EtatDossierInscription.DI_A_RELANCER, - "VALIDE": FicheInscription.EtatDossierInscription.DI_VALIDE, - "ARCHIVE": FicheInscription.EtatDossierInscription.DI_ARCHIVE -} - -def load_config(config_file): - with open(config_file, 'r') as file: - config = json.load(file) - return config - -def getStateMachineObject(etat) : - return Automate_DI_Inscription(etat) - -def getStateMachineObjectState(etat): - return Automate_DI_Inscription(etat).state - -def updateStateMachine(di, transition) : - automateModel = load_config('GestionInscriptions/Configuration/automate.json') - state_machine = getStateMachineObject(di.etat) - print(f'etat DI : {state_machine.state}') - if state_machine.trigger(transition, automateModel): - di.etat = state_machine.state - di.save() - clear_cache() - -class Automate_DI_Inscription: - def __init__(self, initial_state): - self.state = initial_state - - def trigger(self, transition_name, config): - for transition in config["transitions"]: - if transition["name"] == transition_name and self.state == state_mapping[transition["from"]]: - self.state = state_mapping[transition["to"]] - return True - return False \ No newline at end of file diff --git a/Back-End/GestionInscriptions/models.py b/Back-End/GestionInscriptions/models.py deleted file mode 100644 index e8a9d00..0000000 --- a/Back-End/GestionInscriptions/models.py +++ /dev/null @@ -1,179 +0,0 @@ -from django.db import models -from django.utils.timezone import now -from django.conf import settings -from django.utils.translation import gettext_lazy as _ - -from GestionLogin.models import Profil -from GestionEnseignants.models import Classe - -from datetime import datetime - -class FraisInscription(models.Model): - class OptionsPaiements(models.IntegerChoices): - PAIEMENT_1_FOIS = 0, _('Paiement en une seule fois') - PAIEMENT_MENSUEL = 1, _('Paiement mensuel') - PAIEMENT_TRIMESTRIEL = 2, _('Paiement trimestriel') - - nom = models.CharField(max_length=255, unique=True) - description = models.TextField(blank=True) - montant_de_base = models.DecimalField(max_digits=10, decimal_places=2) - reductions = models.JSONField(blank=True, null=True) - supplements = models.JSONField(blank=True, null=True) - date_debut_validite = models.DateField() - date_fin_validite = models.DateField() - options_paiement = models.IntegerField(choices=OptionsPaiements, default=OptionsPaiements.PAIEMENT_1_FOIS) - - def __str__(self): - return self.nom - -class Langue(models.Model): - id = models.AutoField(primary_key=True) - libelle = models.CharField(max_length=200, default="") - - def __str__(self): - return "LANGUE" - -class Responsable(models.Model): - nom = models.CharField(max_length=200, default="") - prenom = models.CharField(max_length=200, default="") - dateNaissance = models.CharField(max_length=200, default="", blank=True) - adresse = models.CharField(max_length=200, default="", blank=True) - mail = models.CharField(max_length=200, default="", blank=True) - telephone = models.CharField(max_length=200, default="", blank=True) - profession = models.CharField(max_length=200, default="", blank=True) - profilAssocie = models.ForeignKey(Profil, on_delete=models.CASCADE) - - def __str__(self): - return self.nom + "_" + self.prenom - -class Frere(models.Model): - id = models.AutoField(primary_key=True) - nom = models.CharField(max_length=200, default="") - prenom = models.CharField(max_length=200, default="") - dateNaissance = models.CharField(max_length=200, default="", blank=True) - - def __str__(self): - return "FRERE" - -class Eleve(models.Model): - - class GenreEleve(models.IntegerChoices): - NONE = 0, _('Sélection du genre') - MALE = 1, _('Garçon') - FEMALE = 2, _('Fille') - - class NiveauEleve(models.IntegerChoices): - NONE = 0, _('Sélection du niveau') - TPS = 1, _('TPS - Très Petite Section') - PS = 2, _('PS - Petite Section') - MS = 3, _('MS - Moyenne Section') - GS = 4, _('GS - Grande Section') - - class ModePaiement(models.IntegerChoices): - NONE = 0, _('Sélection du mode de paiement') - PRELEVEMENT_SEPA = 1, _('Prélèvement SEPA') - CHEQUE = 2, _('Chèque') - - nom = models.CharField(max_length=200, default="") - prenom = models.CharField(max_length=200, default="") - genre = models.IntegerField(choices=GenreEleve, default=GenreEleve.NONE, blank=True) - niveau = models.IntegerField(choices=NiveauEleve, default=NiveauEleve.NONE, blank=True) - nationalite = models.CharField(max_length=200, default="", blank=True) - adresse = models.CharField(max_length=200, default="", blank=True) - dateNaissance = models.DateField(null=True, blank=True) - lieuNaissance = models.CharField(max_length=200, default="", blank=True) - codePostalNaissance = models.IntegerField(default=0, blank=True) - medecinTraitant = models.CharField(max_length=200, default="", blank=True) - modePaiement = models.IntegerField(choices=ModePaiement, default=ModePaiement.NONE, blank=True) - - # Relation N-N - profils = models.ManyToManyField(Profil, blank=True) - - # Relation N-N - responsables = models.ManyToManyField(Responsable, blank=True) - - # Relation N-N - freres = models.ManyToManyField(Frere, blank=True) - - # Relation N-N - languesParlees = models.ManyToManyField(Langue, blank=True) - - # Relation 1-N - classeAssociee = models.ForeignKey(Classe, on_delete=models.SET_NULL, null=True, blank=True, related_name='eleves') - - def __str__(self): - return self.nom + "_" + self.prenom - - def getLanguesParlees(self): - return self.languesParlees.all() - - def getResponsablePrincipal(self): - return self.responsables.all()[0] - - def getResponsables(self): - return self.responsables.all() - - def getProfils(self): - return self.profils.all() - - def getFreres(self): - return self.freres.all() - - def getNbFreres(self): - return self.freres.count() - - @property - def age(self): - if self.dateNaissance: - today = datetime.today() - years = today.year - self.dateNaissance.year - months = today.month - self.dateNaissance.month - if today.day < self.dateNaissance.day: - months -= 1 - if months < 0: - years -= 1 - months += 12 - - # Déterminer le format de l'âge - if months >= 6 and months <= 12: - return f"{years} ans 1/2" - else: - return f"{years} ans" - return None - - @property - def dateNaissance_formattee(self): - if self.dateNaissance: - return self.dateNaissance.strftime('%d-%m-%Y') - return None - -class FicheInscription(models.Model): - - class EtatDossierInscription(models.IntegerChoices): - DI_ABSENT = 0, _('Pas de dossier d\'inscription') - DI_CREE = 1, _('Dossier d\'inscription créé') - DI_ENVOYE = 2, _('Dossier d\'inscription envoyé') - DI_EN_VALIDATION = 3, _('Dossier d\'inscription en cours de validation') - DI_A_RELANCER = 4, _('Dossier d\'inscription à relancer') - DI_VALIDE = 5, _('Dossier d\'inscription validé') - DI_ARCHIVE = 6, _('Dossier d\'inscription archivé') - - # Relation 1-1 - eleve = models.OneToOneField(Eleve, on_delete=models.CASCADE, primary_key=True) - etat = models.IntegerField(choices=EtatDossierInscription, default=EtatDossierInscription.DI_ABSENT) - dateMAJ = models.DateTimeField(auto_now=True) - notes = models.CharField(max_length=200, blank=True) - codeLienInscription = models.CharField(max_length=200, default="", blank=True) - fichierInscription = models.FileField(upload_to=settings.DOCUMENT_DIR, default="", blank=True) - di_associe = models.CharField(max_length=200, default="", blank=True) - - def __str__(self): - return "FI_" + self.eleve.nom + "_" + self.eleve.prenom - -class FichierInscription(models.Model): - name = models.CharField(max_length=255) - file = models.FileField(upload_to='fichiers_inscription/') - date_ajout = models.DateTimeField(auto_now_add=True) - - def __str__(self): - return self.nom \ No newline at end of file diff --git a/Back-End/GestionInscriptions/serializers.py b/Back-End/GestionInscriptions/serializers.py deleted file mode 100644 index 90121fe..0000000 --- a/Back-End/GestionInscriptions/serializers.py +++ /dev/null @@ -1,202 +0,0 @@ -from rest_framework import serializers -from GestionInscriptions.models import FichierInscription, FicheInscription, Eleve, Responsable, Frere, Langue, FraisInscription -from GestionEnseignants.models import Classe -from GestionLogin.models import Profil -from GestionLogin.serializers import ProfilSerializer -from GestionMessagerie.models import Messagerie -from GestionNotification.models import Notification -from N3wtSchool import settings -from django.utils import timezone -import pytz -from datetime import datetime - -class FichierInscriptionSerializer(serializers.ModelSerializer): - class Meta: - model = FichierInscription - fields = '__all__' -class FraisInscriptionSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(required=False) - class Meta: - model = FraisInscription - fields = '__all__' - -class LanguesSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(required=False) - class Meta: - model = Langue - fields = '__all__' - -class FrereSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(required=False) - class Meta: - model = Frere - fields = '__all__' - -class ResponsableSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(required=False) - profil_associe = serializers.SerializerMethodField() - class Meta: - model = Responsable - fields = '__all__' - - def get_profil_associe(self, obj): - return obj.profilAssocie.email - -class EleveSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(required=False) - responsables = ResponsableSerializer(many=True, required=False) - freres = FrereSerializer(many=True, required=False) - langues = LanguesSerializer(many=True, required=False) - classeAssocie_id = serializers.PrimaryKeyRelatedField(queryset=Classe.objects.all(), source='classeAssociee', required=False, write_only=False, read_only=False) - age = serializers.SerializerMethodField() - dateNaissance_formattee = serializers.SerializerMethodField() - dateNaissance = serializers.DateField(input_formats=['%d-%m-%Y', '%Y-%m-%d'], required=False, allow_null=True) - classeAssocieeName = serializers.SerializerMethodField() - - class Meta: - model = Eleve - fields = '__all__' - - def get_or_create_packages(self, responsables_data): - responsables_ids = [] - for responsable_data in responsables_data: - responsable_instance, created = Responsable.objects.get_or_create( id=responsable_data.get('id'), - defaults=responsable_data) - responsables_ids.append(responsable_instance.id) - return responsables_ids - - def create(self, validated_data): - responsables_data = validated_data.pop('responsables', []) - freres_data = validated_data.pop('freres', []) - langues_data = validated_data.pop('languesParlees', []) - eleve = Eleve.objects.create(**validated_data) - eleve.responsables.set(self.get_or_create_packages(responsables_data)) - eleve.freres.set(self.get_or_create_packages(freres_data)) - eleve.languesParlees.set(self.get_or_create_packages(langues_data)) - - return eleve - - def create_or_update_packages(self, responsables_data): - responsables_ids = [] - - - for responsable_data in responsables_data: - responsable_instance, created = Responsable.objects.update_or_create( id=responsable_data.get('id'), - defaults=responsable_data) - - responsables_ids.append(responsable_instance.id) - return responsables_ids - - def update(self, instance, validated_data): - responsables_data = validated_data.pop('responsables', []) - freres_data = validated_data.pop('freres', []) - langues_data = validated_data.pop('languesParlees', []) - if responsables_data: - instance.responsables.set(self.create_or_update_packages(responsables_data)) - if freres_data: - instance.freres.set(self.create_or_update_packages(freres_data)) - if langues_data: - instance.freres.set(self.create_or_update_packages(langues_data)) - - for field in self.fields: - try: - setattr(instance, field, validated_data[field]) - except KeyError: - pass - instance.save() - - return instance - - def get_age(self, obj): - return obj.age - - def get_dateNaissance_formattee(self, obj): - return obj.dateNaissance_formattee - - def get_classeAssocieeName(self, obj): - return obj.classeAssociee.nom_ambiance if obj.classeAssociee else None - -class FicheInscriptionSerializer(serializers.ModelSerializer): - eleve = EleveSerializer(many=False, required=True) - fichierInscription = serializers.FileField(required=False) - etat_label = serializers.SerializerMethodField() - dateMAJ_formattee = serializers.SerializerMethodField() - class Meta: - model = FicheInscription - fields = '__all__' - - def create(self, validated_data): - eleve_data = validated_data.pop('eleve') - eleve = EleveSerializer.create(EleveSerializer(), eleve_data) - ficheEleve = FicheInscription.objects.create(eleve=eleve, **validated_data) - return ficheEleve - - def update(self, instance, validated_data): - eleve_data = validated_data.pop('eleve') - eleve = instance.eleve - eleve_serializer = EleveSerializer.update(EleveSerializer(), eleve, eleve_data) - - for field in self.fields: - try: - setattr(instance, field, validated_data[field]) - except KeyError: - pass - instance.save() - - return instance - - def get_etat_label(self, obj): - return obj.get_etat_display() - - def get_dateMAJ_formattee(self, obj): - utc_time = timezone.localtime(obj.dateMAJ) # Convertir en heure locale - local_tz = pytz.timezone(settings.TZ_APPLI) - local_time = utc_time.astimezone(local_tz) - - return local_time.strftime("%d-%m-%Y %H:%M") - -class EleveByParentSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(required=False) - class Meta: - model = Eleve - fields = ['id', 'nom', 'prenom'] - - def __init__(self, *args, **kwargs): - super(EleveByParentSerializer , self).__init__(*args, **kwargs) - for field in self.fields: - self.fields[field].required = False - -class FicheInscriptionByParentSerializer(serializers.ModelSerializer): - eleve = EleveByParentSerializer(many=False, required=True) - class Meta: - model = FicheInscription - fields = ['eleve', 'etat'] - - def __init__(self, *args, **kwargs): - super(FicheInscriptionByParentSerializer, self).__init__(*args, **kwargs) - for field in self.fields: - self.fields[field].required = False - -class ResponsableByDICreationSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(required=False) - class Meta: - model = Responsable - fields = ['id', 'nom', 'prenom', 'mail', 'profilAssocie'] - -class EleveByDICreationSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(required=False) - responsables = ResponsableByDICreationSerializer(many=True, required=False) - class Meta: - model = Eleve - fields = ['id', 'nom', 'prenom', 'responsables'] - - def __init__(self, *args, **kwargs): - super(EleveByDICreationSerializer , self).__init__(*args, **kwargs) - for field in self.fields: - self.fields[field].required = False - -class NotificationSerializer(serializers.ModelSerializer): - typeNotification_label = serializers.ReadOnlyField() - class Meta: - model = Notification - fields = '__all__' diff --git a/Back-End/GestionInscriptions/templatetags/myTemplateTag.py b/Back-End/GestionInscriptions/templatetags/myTemplateTag.py deleted file mode 100644 index 094cf28..0000000 --- a/Back-End/GestionInscriptions/templatetags/myTemplateTag.py +++ /dev/null @@ -1,23 +0,0 @@ -from GestionInscriptions.models import FicheInscription, Eleve -from django import template -register = template.Library() - -# @register.filter -# def recupereFichiersDossierInscription(pk): - # fichiers_list = FicheInscription.objects.filter(fiche_inscription=pk) - # return fichiers_list - -@register.filter -def recupereModePaiement(pk): - ficheInscription = FicheInscription.objects.get(eleve=pk) - return Eleve.ModePaiement(int(ficheInscription.eleve.modePaiement)).label - -@register.filter -def recupereNiveauEleve(pk): - ficheInscription = FicheInscription.objects.get(eleve=pk) - return Eleve.NiveauEleve(int(ficheInscription.eleve.niveau)).label - -@register.filter -def recupereGenreEleve(pk): - ficheInscription = FicheInscription.objects.get(eleve=pk) - return Eleve.GenreEleve(int(ficheInscription.eleve.genre)).label \ No newline at end of file diff --git a/Back-End/GestionInscriptions/urls.py b/Back-End/GestionInscriptions/urls.py deleted file mode 100644 index 924cad1..0000000 --- a/Back-End/GestionInscriptions/urls.py +++ /dev/null @@ -1,36 +0,0 @@ -from django.urls import path, re_path - -from . import views -from GestionInscriptions.views import FichierInscriptionView, ListFichesInscriptionView, FicheInscriptionView, EleveView, ResponsableView, ListeEnfantsView, ListeElevesView, FraisInscriptionView - -urlpatterns = [ - re_path(r'^fichesInscription/([a-zA-z]+)$', ListFichesInscriptionView.as_view(), name="listefichesInscriptions"), - re_path(r'^ficheInscription$', FicheInscriptionView.as_view(), name="fichesInscriptions"), - re_path(r'^ficheInscription/([0-9]+)$', FicheInscriptionView.as_view(), name="fichesInscriptions"), - - # Page de formulaire d'inscription - ELEVE - re_path(r'^eleve/([0-9]+)$', EleveView.as_view(), name="eleves"), - - # Page de formulaire d'inscription - RESPONSABLE - re_path(r'^recupereDernierResponsable$', ResponsableView.as_view(), name="recupereDernierResponsable"), - - # Envoi d'un dossier d'inscription - re_path(r'^send/([0-9]+)$', views.send, name="send"), - - # Archivage d'un dossier d'inscription - re_path(r'^archive/([0-9]+)$', views.archive, name="archive"), - - # Envoi d'une relance de dossier d'inscription - re_path(r'^sendRelance/([0-9]+)$', views.relance, name="relance"), - - # Page PARENT - Liste des enfants - re_path(r'^enfants/([0-9]+)$', ListeEnfantsView.as_view(), name="enfants"), - - # Page INSCRIPTION - Liste des élèves - re_path(r'^eleves$', ListeElevesView.as_view(), name="enfants"), - - # Frais d'inscription - re_path(r'^tarifsInscription$', FraisInscriptionView.as_view(), name="fraisInscription"), - re_path(r'^fichiersInscription$', FichierInscriptionView.as_view(), name='fichiersInscription'), - re_path(r'^fichiersInscription/([0-9]+)$', FichierInscriptionView.as_view(), name="fichiersInscription"), -] \ No newline at end of file diff --git a/Back-End/GestionInscriptions/util.py b/Back-End/GestionInscriptions/util.py deleted file mode 100644 index 8196240..0000000 --- a/Back-End/GestionInscriptions/util.py +++ /dev/null @@ -1,201 +0,0 @@ -from django.shortcuts import render,get_object_or_404,get_list_or_404 -from .models import FicheInscription, Eleve, Responsable, Frere -import time -from datetime import date, datetime, timedelta -from zoneinfo import ZoneInfo -from django.conf import settings -from N3wtSchool import renderers -from N3wtSchool import bdd - -from io import BytesIO -from django.core.files import File -from pathlib import Path -import os -from enum import Enum - -import random -import string -from rest_framework.parsers import JSONParser - -def recupereListeFichesInscription(): - context = { - "ficheInscriptions_list": bdd.getAllObjects(FicheInscription), - } - return context - -def recupereListeFichesInscriptionEnAttenteSEPA(): - - ficheInscriptionsSEPA_list = FicheInscription.objects.filter(modePaiement="Prélèvement SEPA").filter(etat=FicheInscription.EtatDossierInscription['SEPA_ENVOYE']) - return ficheInscriptionsSEPA_list - -def updateEleve(eleve, inputs, erase=False): - eleve.nom = inputs["nomEleve"] - eleve.prenom = inputs["prenomEleve"] - eleve.ambiance = inputs["ambiance"] - eleve.genre = inputs["genre"] - eleve.adresse = inputs["adresseEleve"] - eleve.dateNaissance = inputs["dateNaissanceEleve"] - eleve.lieuNaissance = inputs["lieuNaissanceEleve"] - eleve.codePostalNaissance = inputs["codePostalNaissanceEleve"] - eleve.nationalite = inputs["nationaliteEleve"] - eleve.medecinTraitant = inputs["medecinTraitantEleve"] - - - responsable=eleve.getResponsablePrincipal() - responsable.adresse = inputs["adresseResponsable1"] - responsable.dateNaissance = inputs["dateNaissanceResponsable1"] - responsable.profession = inputs["professionResponsable1"] - responsable.save() - - # Création du 2ème responsable - if inputs["nomResponsable2"] != "" and inputs["prenomResponsable2"] != "": - responsable2 = Responsable.objects.create(nom=inputs["nomResponsable2"], - prenom=inputs["prenomResponsable2"], - dateNaissance=inputs["dateNaissanceResponsable2"], - adresse=inputs["adresseResponsable2"], - mail=inputs["mailResponsable2"], - telephone=inputs["telephoneResponsable2"], - profession=inputs["professionResponsable2"]) - responsable2.save() - eleve.responsables.add(responsable2) - - # Création du 1er frère - if inputs["nomFrere1"] != "" and inputs["prenomFrere1"] != "": - frere1 = Frere.objects.create(nom=inputs["nomFrere1"], - prenom=inputs["prenomFrere1"], - dateNaissance=inputs["dateNaissanceFrere1"]) - frere1.save() - eleve.freres.add(frere1) - - # Création du 2ème frère - if inputs["nomFrere2"] != "" and inputs["prenomFrere2"] != "": - frere2 = Frere.objects.create(nom=inputs["nomFrere2"], - prenom=inputs["prenomFrere2"], - dateNaissance=inputs["dateNaissanceFrere2"]) - frere2.save() - eleve.freres.add(frere2) - - eleve.save() - -def _now(): - return datetime.now(ZoneInfo(settings.TZ_APPLI)) - -def convertToStr(dateValue, dateFormat): - return dateValue.strftime(dateFormat) - -def convertToDate(date_time): - format = '%d-%m-%Y %H:%M' - datetime_str = datetime.strptime(date_time, format) - - return datetime_str - -def convertTelephone(telephoneValue, separator='-'): - return f"{telephoneValue[:2]}{separator}{telephoneValue[2:4]}{separator}{telephoneValue[4:6]}{separator}{telephoneValue[6:8]}{separator}{telephoneValue[8:10]}" - -def generePDF(ficheEleve): - data = { - 'pdf_title': "Dossier d'inscription de %s"%ficheEleve.eleve.prenom, - 'dateSignature': convertToStr(_now(), '%d-%m-%Y'), - 'heureSignature': convertToStr(_now(), '%H:%M'), - 'eleve':ficheEleve.eleve, - } - - pdf = renderers.render_to_pdf('pdfs/dossier_inscription.html', data) - - nomFichierPDF = "Dossier_Inscription_%s_%s.pdf"%(ficheEleve.eleve.nom, ficheEleve.eleve.prenom) - pathFichier = Path(settings.DOCUMENT_DIR + "/" + nomFichierPDF) - if os.path.exists(str(pathFichier)): - os.remove(str(pathFichier)) - - receipt_file = BytesIO(pdf.content) - # fichier = Fichier.objects.create(fiche_inscription=ficheEleve) - # fichier.document = File(receipt_file, nomFichierPDF) - # fichier.save() - -def genereRandomCode(length): - return ''.join(random.choice(string.ascii_letters) for i in range(length)) - -def calculeDatePeremption(_start, nbDays): - return convertToStr(_start + timedelta(days=nbDays), settings.DATE_FORMAT) - -# Fonction permettant de retourner la valeur du QueryDict -# QueryDict [ index ] -> Dernière valeur d'une liste -# dict (QueryDict [ index ]) -> Toutes les valeurs de la liste -def _(liste): - return liste[0] - -def toNewEleveJSONRequest(jsonOrigin): - etat=FicheInscription.EtatDossierInscription.DI_CREE - telephone = convertTelephone(_(jsonOrigin['telephoneResponsable'])) - finalJSON = { - "eleve": - { - "nom" : _(jsonOrigin['nomEleve']), - "prenom" : _(jsonOrigin['prenomEleve']), - "responsables" : [ - { - "nom" : _(jsonOrigin['nomResponsable']), - "prenom" : _(jsonOrigin['prenomResponsable']), - "mail" : _(jsonOrigin['mailResponsable']), - "telephone" : telephone - } - ], - "profils" : [ - ], - }, - "etat": str(etat), - "dateMAJ": str(convertToStr(_now(), '%d-%m-%Y %H:%M')), - } - print(finalJSON) - return finalJSON - -def toEditEleveJSONRequest(jsonOrigin): - telephone = convertTelephone(_(jsonOrigin['telephoneResponsable']), '.') - finalJSON = { - "eleve": - { - "id" : _(jsonOrigin['fiche_id']), - "nom" : _(jsonOrigin['nomEleve']), - "prenom" : _(jsonOrigin['prenomEleve']), - "responsables" : [ - { - "id" : _(jsonOrigin['responsable_id']), - "nom" : _(jsonOrigin['nomResponsable']), - "prenom" : _(jsonOrigin['prenomResponsable']), - "mail" : _(jsonOrigin['mailResponsable']), - "telephone" : telephone - } - ], - "profils" : [ - ], - }, - "dateMAJ": str(convertToStr(_now(), '%d-%m-%Y %H:%M')), - } - print(finalJSON) - return finalJSON - -def getArgFromRequest(_argument, _request): - resultat = None - data=JSONParser().parse(_request) - resultat = data[_argument] - return resultat - -def diToPDF(ficheEleve): - # Ajout du fichier d'inscriptions - data = { - 'pdf_title': "Dossier d'inscription de %s"%ficheEleve.eleve.prenom, - 'dateSignature': convertToStr(_now(), '%d-%m-%Y'), - 'heureSignature': convertToStr(_now(), '%H:%M'), - 'eleve':ficheEleve.eleve, - } - - pdf = renderers.render_to_pdf('pdfs/dossier_inscription.html', data) - - nomFichierPDF = "Dossier_Inscription_%s_%s.pdf"%(ficheEleve.eleve.nom, ficheEleve.eleve.prenom) - pathFichier = Path(settings.DOCUMENT_DIR + "/" + nomFichierPDF) - if os.path.exists(str(pathFichier)): - print(f'File exists : {str(pathFichier)}') - os.remove(str(pathFichier)) - - receipt_file = BytesIO(pdf.content) - ficheEleve.fichierInscription = File(receipt_file, nomFichierPDF) \ No newline at end of file diff --git a/Back-End/GestionInscriptions/views.py b/Back-End/GestionInscriptions/views.py deleted file mode 100644 index c488b3e..0000000 --- a/Back-End/GestionInscriptions/views.py +++ /dev/null @@ -1,282 +0,0 @@ -from django.http.response import JsonResponse -from django.contrib.auth import login, authenticate, get_user_model -from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect -from django.utils.decorators import method_decorator -from django.core.cache import cache -from django.core.paginator import Paginator -from django.core.files import File -from django.db.models import Q # Ajout de cet import -from rest_framework.parsers import JSONParser,MultiPartParser, FormParser -from rest_framework.response import Response -from rest_framework.views import APIView -from rest_framework import status - -import json -from pathlib import Path -import os -from io import BytesIO - -import GestionInscriptions.mailManager as mailer -import GestionInscriptions.util as util -from GestionInscriptions.serializers import FichierInscriptionSerializer, FicheInscriptionSerializer, EleveSerializer, FicheInscriptionByParentSerializer, EleveByDICreationSerializer, FraisInscriptionSerializer -from GestionInscriptions.pagination import CustomPagination -from GestionInscriptions.signals import clear_cache -from .models import Eleve, Responsable, FicheInscription, FraisInscription, FichierInscription - -from GestionInscriptions.automate import Automate_DI_Inscription, load_config, getStateMachineObjectState, updateStateMachine - -from GestionLogin.models import Profil - -from N3wtSchool import settings, renderers, bdd - -class ListFichesInscriptionView(APIView): - pagination_class = CustomPagination - - def get_fiche_inscriptions(self, _filter, search=None): - """ - Récupère les fiches d'inscriptions en fonction du filtre passé. - _filter: Filtre pour déterminer l'état des fiches ('pending', 'archived', 'subscribed') - search: Terme de recherche (optionnel) - """ - if _filter == 'pending': - exclude_states = [FicheInscription.EtatDossierInscription.DI_VALIDE, FicheInscription.EtatDossierInscription.DI_ARCHIVE] - return bdd.searchObjects(FicheInscription, search, _excludeStates=exclude_states) - elif _filter == 'archived': - return bdd.getObjects(FicheInscription, 'etat', FicheInscription.EtatDossierInscription.DI_ARCHIVE) - elif _filter == 'subscribed': - return bdd.getObjects(FicheInscription, 'etat', FicheInscription.EtatDossierInscription.DI_VALIDE) - return None - - def get(self, request, _filter): - - # Récupération des paramètres - search = request.GET.get('search', '').strip() - page_size = request.GET.get('page_size', None) - - # Gestion du page_size - if page_size is not None: - try: - page_size = int(page_size) - except ValueError: - page_size = settings.NB_RESULT_PER_PAGE - - # Définir le cache_key en fonction du filtre - page_number = request.GET.get('page', 1) - cache_key = f'N3WT_ficheInscriptions_{_filter}_page_{page_number}_search_{search if _filter == "pending" else ""}' - cached_page = cache.get(cache_key) - if cached_page: - return JsonResponse(cached_page, safe=False) - - # Récupérer les fiches d'inscriptions en fonction du filtre - ficheInscriptions_List = self.get_fiche_inscriptions(_filter, search) - - if not ficheInscriptions_List: - return JsonResponse({'error' : 'aucune donnée trouvée', 'count' :0}, safe=False) - - # Pagination - paginator = self.pagination_class() - page = paginator.paginate_queryset(ficheInscriptions_List, request) - if page is not None: - ficheInscriptions_serializer = FicheInscriptionSerializer(page, many=True) - response_data = paginator.get_paginated_response(ficheInscriptions_serializer.data) - cache.set(cache_key, response_data, timeout=60*15) - return JsonResponse(response_data, safe=False) - - return JsonResponse({'error' : 'aucune donnée trouvée', 'count' :0}, safe=False) - - def post(self, request): - fichesEleve_data=JSONParser().parse(request) - for ficheEleve_data in fichesEleve_data: - # Ajout de la date de mise à jour - ficheEleve_data["dateMAJ"] = util.convertToStr(util._now(), '%d-%m-%Y %H:%M') - json.dumps(ficheEleve_data) - # Ajout du code d'inscription - code = util.genereRandomCode(12) - ficheEleve_data["codeLienInscription"] = code - ficheEleve_serializer = FicheInscriptionSerializer(data=ficheEleve_data) - - if ficheEleve_serializer.is_valid(): - ficheEleve_serializer.save() - - return JsonResponse(ficheEleve_serializer.errors, safe=False) - - -@method_decorator(csrf_protect, name='dispatch') -@method_decorator(ensure_csrf_cookie, name='dispatch') -class FicheInscriptionView(APIView): - pagination_class = CustomPagination - - def get(self, request, _id): - ficheInscription=bdd.getObject(FicheInscription, "eleve__id", _id) - fiche_serializer=FicheInscriptionSerializer(ficheInscription) - return JsonResponse(fiche_serializer.data, safe=False) - - def post(self, request): - ficheEleve_data=JSONParser().parse(request) - # Ajout de la date de mise à jour - ficheEleve_data["dateMAJ"] = util.convertToStr(util._now(), '%d-%m-%Y %H:%M') - json.dumps(ficheEleve_data) - # Ajout du code d'inscription - code = util.genereRandomCode(12) - ficheEleve_data["codeLienInscription"] = code - - responsablesId = ficheEleve_data.pop('idResponsables', []) - ficheEleve_serializer = FicheInscriptionSerializer(data=ficheEleve_data) - - if ficheEleve_serializer.is_valid(): - di = ficheEleve_serializer.save() - - # Mise à jour de l'automate - updateStateMachine(di, 'creationDI') - - # Récupération du reponsable associé - for responsableId in responsablesId: - responsable = Responsable.objects.get(id=responsableId) - di.eleve.responsables.add(responsable) - di.save() - - return JsonResponse(ficheEleve_serializer.data, safe=False) - - return JsonResponse(ficheEleve_serializer.errors, safe=False) - - def put(self, request, id): - ficheEleve_data=JSONParser().parse(request) - etat = ficheEleve_data.pop('etat', 0) - ficheEleve_data["dateMAJ"] = str(util.convertToStr(util._now(), '%d-%m-%Y %H:%M')) - ficheEleve = bdd.getObject(_objectName=FicheInscription, _columnName='eleve__id', _value=id) - - if etat == FicheInscription.EtatDossierInscription.DI_EN_VALIDATION: - # Le parent a complété le dossier d'inscription, il est soumis à validation par l'école - print('EN VALIDATION') - json.dumps(ficheEleve_data) - util.diToPDF(ficheEleve) - # Mise à jour de l'automate - updateStateMachine(ficheEleve, 'saisiDI') - elif etat == FicheInscription.EtatDossierInscription.DI_VALIDE: - # L'école a validé le dossier d'inscription - # Mise à jour de l'automate - print('VALIDATION') - updateStateMachine(ficheEleve, 'valideDI') - - - ficheEleve_serializer = FicheInscriptionSerializer(ficheEleve, data=ficheEleve_data) - if ficheEleve_serializer.is_valid(): - ficheEleve_serializer.save() - return JsonResponse(ficheEleve_serializer.data, safe=False) - - return JsonResponse(ficheEleve_serializer.errors, safe=False) - - def delete(self, request, id): - fiche_inscription = bdd.getObject(_objectName=FicheInscription, _columnName='eleve__id', _value=id) - if fiche_inscription != None: - eleve = fiche_inscription.eleve - eleve.responsables.clear() - eleve.profils.clear() - eleve.delete() - clear_cache() - - return JsonResponse("La suppression du dossier a été effectuée avec succès", safe=False) - - return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False) - -class EleveView(APIView): - def get(self, request, _id): - eleve = bdd.getObject(_objectName=Eleve, _columnName='id', _value=_id) - eleve_serializer = EleveSerializer(eleve) - return JsonResponse(eleve_serializer.data, safe=False) - -class ResponsableView(APIView): - def get(self, request): - lastResponsable = bdd.getLastId(Responsable) - return JsonResponse({"lastid":lastResponsable}, safe=False) - -def send(request, id): - fiche_inscription = bdd.getObject(_objectName=FicheInscription, _columnName='eleve__id', _value=id) - if fiche_inscription != None: - eleve = fiche_inscription.eleve - responsable = eleve.getResponsablePrincipal() - mail = responsable.mail - errorMessage = mailer.envoieDossierInscription(mail) - if errorMessage == '': - fiche_inscription.dateMAJ=util.convertToStr(util._now(), '%d-%m-%Y %H:%M') - # Mise à jour de l'automate - updateStateMachine(fiche_inscription, 'envoiDI') - - return JsonResponse({"errorMessage":errorMessage}, safe=False) - - return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False) - -def archive(request, id): - fiche_inscription = bdd.getObject(_objectName=FicheInscription, _columnName='eleve__id', _value=id) - if fiche_inscription != None: - fiche_inscription.dateMAJ=util.convertToStr(util._now(), '%d-%m-%Y %H:%M') - # Mise à jour de l'automate - updateStateMachine(fiche_inscription, 'archiveDI') - - return JsonResponse({"errorMessage":''}, safe=False) - - return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False) - -def relance(request, id): - fiche_inscription = bdd.getObject(_objectName=FicheInscription, _columnName='eleve__id', _value=id) - if fiche_inscription != None: - eleve = fiche_inscription.eleve - responsable = eleve.getResponsablePrincipal() - mail = responsable.mail - errorMessage = mailer.envoieRelanceDossierInscription(mail, fiche_inscription.codeLienInscription) - if errorMessage == '': - fiche_inscription.etat=FicheInscription.EtatDossierInscription.DI_ENVOYE - fiche_inscription.dateMAJ=util.convertToStr(util._now(), '%d-%m-%Y %H:%M') - fiche_inscription.save() - - return JsonResponse({"errorMessage":errorMessage}, safe=False) - - return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False) - -# API utilisée pour la vue parent -class ListeEnfantsView(APIView): - # Récupération des élèves d'un parent - # idProfile : identifiant du profil connecté rattaché aux fiches d'élèves - def get(self, request, _idProfile): - students = bdd.getObjects(_objectName=FicheInscription, _columnName='eleve__responsables__profilAssocie__id', _value=_idProfile) - students_serializer = FicheInscriptionByParentSerializer(students, many=True) - return JsonResponse(students_serializer.data, safe=False) - -# API utilisée pour la vue de création d'un DI -class ListeElevesView(APIView): - # Récupération de la liste des élèves inscrits ou en cours d'inscriptions - def get(self, request): - students = bdd.getAllObjects(_objectName=Eleve) - students_serializer = EleveByDICreationSerializer(students, many=True) - return JsonResponse(students_serializer.data, safe=False) - -# API utilisée pour la vue de personnalisation des frais d'inscription pour la structure -class FraisInscriptionView(APIView): - def get(self, request): - tarifs = bdd.getAllObjects(FraisInscription) - tarifs_serializer = FraisInscriptionSerializer(tarifs, many=True) - return JsonResponse(tarifs_serializer.data, safe=False) - -class FichierInscriptionView(APIView): - parser_classes = (MultiPartParser, FormParser) - - def get(self, request): - fichiers = FichierInscription.objects.all() - serializer = FichierInscriptionSerializer(fichiers, many=True) - return Response(serializer.data) - - def post(self, request): - serializer = FichierInscriptionSerializer(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) - - def delete(self, request, _id): - fichierInscription = bdd.getObject(_objectName=FichierInscription, _columnName='id', _value=_id) - if fichierInscription is not None: - fichierInscription.file.delete() # Supprimer le fichier uploadé - fichierInscription.delete() - return JsonResponse({'message': 'La suppression du fichier d\'inscription a été effectuée avec succès'}, safe=False) - else: - return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False) diff --git a/Back-End/GestionLogin/urls.py b/Back-End/GestionLogin/urls.py deleted file mode 100644 index d0f29b7..0000000 --- a/Back-End/GestionLogin/urls.py +++ /dev/null @@ -1,22 +0,0 @@ -from django.urls import path, re_path - -from . import views -import GestionLogin.views -from GestionLogin.views import ProfilView, ListProfilView, SessionView, LoginView, SubscribeView, NewPasswordView, ResetPasswordView - -urlpatterns = [ - re_path(r'^csrf$', GestionLogin.views.csrf, name='csrf'), - - re_path(r'^login$', LoginView.as_view(), name="login"), - re_path(r'^subscribe$', SubscribeView.as_view(), name='subscribe'), - re_path(r'^newPassword$', NewPasswordView.as_view(), name='newPassword'), - re_path(r'^resetPassword/([a-zA-Z]+)$', ResetPasswordView.as_view(), name='resetPassword'), - re_path(r'^infoSession$', GestionLogin.views.infoSession, name='infoSession'), - - re_path(r'^profils$', ListProfilView.as_view(), name="profil"), - re_path(r'^profil$', ProfilView.as_view(), name="profil"), - re_path(r'^profil/([0-9]+)$', ProfilView.as_view(), name="profil"), - - # Test SESSION VIEW - re_path(r'^session$', SessionView.as_view(), name="session"), -] \ No newline at end of file diff --git a/Back-End/GestionMessagerie/models.py b/Back-End/GestionMessagerie/models.py index 654f6ef..a17f91f 100644 --- a/Back-End/GestionMessagerie/models.py +++ b/Back-End/GestionMessagerie/models.py @@ -2,13 +2,13 @@ from django.contrib.auth.models import AbstractUser from django.db import models from django.utils.translation import gettext_lazy as _ from django.conf import settings -from GestionLogin.models import Profil +from Auth.models import Profile class Messagerie(models.Model): id = models.AutoField(primary_key=True) objet = models.CharField(max_length=200, default="", blank=True) - emetteur = models.ForeignKey(Profil, on_delete=models.PROTECT, related_name='messages_envoyes') - destinataire = models.ForeignKey(Profil, on_delete=models.PROTECT, related_name='messages_recus') + emetteur = models.ForeignKey(Profile, on_delete=models.PROTECT, related_name='messages_envoyes') + destinataire = models.ForeignKey(Profile, on_delete=models.PROTECT, related_name='messages_recus') corpus = models.CharField(max_length=200, default="", blank=True) def __str__(self): diff --git a/Back-End/GestionMessagerie/serializers.py b/Back-End/GestionMessagerie/serializers.py index 9dbebe3..ceea37a 100644 --- a/Back-End/GestionMessagerie/serializers.py +++ b/Back-End/GestionMessagerie/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from GestionLogin.models import Profil +from Auth.models import Profile from GestionMessagerie.models import Messagerie class MessageSerializer(serializers.ModelSerializer): diff --git a/Back-End/GestionNotification/models.py b/Back-End/GestionNotification/models.py index 361c034..a1aa830 100644 --- a/Back-End/GestionNotification/models.py +++ b/Back-End/GestionNotification/models.py @@ -2,7 +2,7 @@ from django.contrib.auth.models import AbstractUser from django.db import models from django.utils.translation import gettext_lazy as _ from django.conf import settings -from GestionLogin.models import Profil +from Auth.models import Profile class TypeNotif(models.IntegerChoices): NOTIF_NONE = 0, _('Aucune notification') @@ -10,7 +10,7 @@ class TypeNotif(models.IntegerChoices): NOTIF_DI = 2, _('Le dossier d\'inscription a été mis à jour') class Notification(models.Model): - user = models.ForeignKey(Profil, on_delete=models.PROTECT) + user = models.ForeignKey(Profile, on_delete=models.PROTECT) message = models.CharField(max_length=255) is_read = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) diff --git a/Back-End/GestionNotification/signals.py b/Back-End/GestionNotification/signals.py index 1f1fc37..20e78f3 100644 --- a/Back-End/GestionNotification/signals.py +++ b/Back-End/GestionNotification/signals.py @@ -2,7 +2,7 @@ from django.db.models.signals import post_save from django.dispatch import receiver from .models import Notification, TypeNotif from GestionMessagerie.models import Messagerie -from GestionInscriptions.models import FicheInscription +from Subscriptions.models import RegistrationForm @receiver(post_save, sender=Messagerie) def notification_MESSAGE(sender, instance, created, **kwargs): @@ -13,11 +13,11 @@ def notification_MESSAGE(sender, instance, created, **kwargs): typeNotification=TypeNotif.NOTIF_MESSAGE ) -@receiver(post_save, sender=FicheInscription) +@receiver(post_save, sender=RegistrationForm) def notification_DI(sender, instance, created, **kwargs): - for responsable in instance.eleve.responsables.all(): + for responsable in instance.student.guardians.all(): Notification.objects.create( - user=responsable.profilAssocie, + user=responsable.associated_profile, message=(TypeNotif.NOTIF_DI).label, typeNotification=TypeNotif.NOTIF_DI ) diff --git a/Back-End/GestionNotification/views.py b/Back-End/GestionNotification/views.py index 8ffbeb6..b43dee8 100644 --- a/Back-End/GestionNotification/views.py +++ b/Back-End/GestionNotification/views.py @@ -3,7 +3,7 @@ from rest_framework.views import APIView from .models import * -from GestionInscriptions.serializers import NotificationSerializer +from Subscriptions.serializers import NotificationSerializer from N3wtSchool import bdd diff --git a/Back-End/N3wtSchool/bdd.py b/Back-End/N3wtSchool/bdd.py index 96e23aa..8d0c0a1 100644 --- a/Back-End/N3wtSchool/bdd.py +++ b/Back-End/N3wtSchool/bdd.py @@ -1,6 +1,6 @@ import logging from django.db.models import Q -from GestionInscriptions.models import FicheInscription, Profil, Eleve +from Subscriptions.models import RegistrationForm, Profile, Student logger = logging.getLogger('N3wtSchool') @@ -43,12 +43,12 @@ def getProfile(objectList, valueToCheck): return result def getEleveByCodeFI(_codeFI): - eleve = None - ficheInscriptions_List=getAllObjects(FicheInscription) - for fi in ficheInscriptions_List: - if fi.codeLienInscription == _codeFI: - eleve = fi.eleve - return eleve + student = None + ficheInscriptions_List=getAllObjects(RegistrationForm) + for rf in ficheInscriptions_List: + if rf.codeLienInscription == _codeFI: + student = rf.student + return student def getLastId(_object): result = 1 @@ -61,7 +61,7 @@ def getLastId(_object): def searchObjects(_objectName, _searchTerm=None, _excludeStates=None): """ Recherche générique sur les objets avec possibilité d'exclure certains états - _objectName: Classe du modèle + _objectName: SchoolClass du modèle _searchTerm: Terme de recherche _excludeStates: Liste d'état à exclure de la recherche (optionnel) """ @@ -70,18 +70,18 @@ def searchObjects(_objectName, _searchTerm=None, _excludeStates=None): # Si on a un état à exclure if _excludeStates is not None: - query = query.exclude(etat__in=_excludeStates) + query = query.exclude(status__in=_excludeStates) # Si on a un terme de recherche if _searchTerm and _searchTerm.strip(): terms = _searchTerm.lower().strip().split() for term in terms: query = query.filter( - Q(eleve__nom__icontains=term) | - Q(eleve__prenom__icontains=term) + Q(student__last_name__icontains=term) | + Q(student__first_name__icontains=term) ) - return query.order_by('eleve__nom', 'eleve__prenom') + return query.order_by('student__last_name', 'student__first_name') except _objectName.DoesNotExist: logging.error(f"Aucun résultat n'a été trouvé - {_objectName.__name__} (recherche: {_searchTerm})") diff --git a/Back-End/N3wtSchool/settings.py b/Back-End/N3wtSchool/settings.py index 695dc20..078d6e8 100644 --- a/Back-End/N3wtSchool/settings.py +++ b/Back-End/N3wtSchool/settings.py @@ -19,7 +19,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent MEDIA_URL = '/data/' MEDIA_ROOT = os.path.join(BASE_DIR, 'data') -LOGIN_REDIRECT_URL = '/GestionInscriptions/fichesInscriptions' +LOGIN_REDIRECT_URL = '/Subscriptions/registerForms' # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ @@ -35,11 +35,11 @@ ALLOWED_HOSTS = ['*'] # Application definition INSTALLED_APPS = [ - 'GestionInscriptions.apps.GestioninscriptionsConfig', - 'GestionLogin.apps.GestionloginConfig', + 'Subscriptions.apps.GestioninscriptionsConfig', + 'Auth.apps.GestionloginConfig', 'GestionMessagerie.apps.GestionMessagerieConfig', 'GestionNotification.apps.GestionNotificationConfig', - 'GestionEnseignants.apps.GestionenseignantsConfig', + 'School.apps.SchoolConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -133,12 +133,12 @@ LOGGING = { "level": os.getenv("GESTION_NOTIFICATION_LOG_LEVEL", "INFO"), "propagate": False, }, - "GestionLogin": { + "Auth": { "handlers": ["console"], "level": os.getenv("GESTION_LOGIN_LOG_LEVEL", "INFO"), "propagate": False, }, - "GestionInscriptions": { + "Subscriptions": { "handlers": ["console"], "level": os.getenv("GESTION_INSCRIPTIONS_LOG_LEVEL", "DEBUG"), "propagate": False, @@ -148,7 +148,7 @@ LOGGING = { "level": os.getenv("GESTION_MESSAGERIE_LOG_LEVEL", "INFO"), "propagate": False, }, - "GestionEnseignants": { + "School": { "handlers": ["console"], "level": os.getenv("GESTION_ENSEIGNANTS_LOG_LEVEL", "INFO"), "propagate": False, @@ -211,7 +211,7 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' #################### Application Settings ############################## ######################################################################## -with open('GestionInscriptions/Configuration/application.json', 'r') as f: +with open('Subscriptions/Configuration/application.json', 'r') as f: jsonObject = json.load(f) DJANGO_SUPERUSER_PASSWORD='admin' @@ -275,8 +275,8 @@ DATABASES = { } } -AUTH_USER_MODEL = 'GestionLogin.Profil' -AUTHENTICATION_BACKENDS = ('GestionLogin.backends.EmailBackend', ) +AUTH_USER_MODEL = 'Auth.Profile' +AUTHENTICATION_BACKENDS = ('Auth.backends.EmailBackend', ) SILENCED_SYSTEM_CHECKS = ["auth.W004"] EXPIRATION_URL_NB_DAYS = 7 @@ -289,7 +289,7 @@ NB_RESULT_PER_PAGE = 8 NB_MAX_PAGE = 100 REST_FRAMEWORK = { - 'DEFAULT_PAGINATION_CLASS': 'GestionInscriptions.pagination.CustomPagination', + 'DEFAULT_PAGINATION_CLASS': 'Subscriptions.pagination.CustomPagination', 'PAGE_SIZE': NB_RESULT_PER_PAGE } diff --git a/Back-End/N3wtSchool/signals.py b/Back-End/N3wtSchool/signals.py index c44aabc..a76f431 100644 --- a/Back-End/N3wtSchool/signals.py +++ b/Back-End/N3wtSchool/signals.py @@ -16,6 +16,6 @@ def setup_periodic_tasks(sender, **kwargs): PeriodicTask.objects.get_or_create( interval=schedule, # Utiliser l'intervalle défini ci-dessus name='Tâche périodique toutes les 5 secondes', - task='GestionInscriptions.tasks.check_for_signature_deadlines', # Remplacer par le nom de ta tâche + task='Subscriptions.tasks.check_for_signature_deadlines', # Remplacer par le nom de ta tâche kwargs=json.dumps({}) # Si nécessaire, ajoute ) \ No newline at end of file diff --git a/Back-End/N3wtSchool/urls.py b/Back-End/N3wtSchool/urls.py index 3ebef11..dfbc490 100644 --- a/Back-End/N3wtSchool/urls.py +++ b/Back-End/N3wtSchool/urls.py @@ -7,7 +7,7 @@ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views +SchoolClass-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf @@ -39,11 +39,11 @@ schema_view = get_schema_view( urlpatterns = [ path('admin/', admin.site.urls), - path("GestionInscriptions/", include(("GestionInscriptions.urls", 'GestionInscriptions'), namespace='GestionInscriptions')), - path("GestionLogin/", include(("GestionLogin.urls", 'GestionLogin'), namespace='GestionLogin')), + path("Subscriptions/", include(("Subscriptions.urls", 'Subscriptions'), namespace='Subscriptions')), + path("Auth/", include(("Auth.urls", 'Auth'), namespace='Auth')), path("GestionMessagerie/", include(("GestionMessagerie.urls", 'GestionMessagerie'), namespace='GestionMessagerie')), path("GestionNotification/", include(("GestionNotification.urls", 'GestionNotification'), namespace='GestionNotification')), - path("GestionEnseignants/", include(("GestionEnseignants.urls", 'GestionEnseignants'), namespace='GestionEnseignants')), + path("School/", include(("School.urls", 'School'), namespace='School')), # Documentation Api re_path(r'^swagger(?P\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'), path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), diff --git a/Back-End/School/__init__.py b/Back-End/School/__init__.py new file mode 100644 index 0000000..c646cf8 --- /dev/null +++ b/Back-End/School/__init__.py @@ -0,0 +1 @@ +default_app_config = 'School.apps.SchoolConfig' diff --git a/Back-End/GestionLogin/admin.py b/Back-End/School/admin.py similarity index 100% rename from Back-End/GestionLogin/admin.py rename to Back-End/School/admin.py diff --git a/Back-End/School/apps.py b/Back-End/School/apps.py new file mode 100644 index 0000000..b19a86f --- /dev/null +++ b/Back-End/School/apps.py @@ -0,0 +1,14 @@ +from django.apps import AppConfig +from django.db.models.signals import post_migrate + +def create_speciality(sender, **kwargs): + from .models import Speciality + if not Speciality.objects.filter(name='GROUPE').exists(): + Speciality.objects.create(name='GROUPE', colorCode='#FF0000') + +class SchoolConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'School' + + def ready(self): + post_migrate.connect(create_speciality, sender=self) diff --git a/Back-End/School/models.py b/Back-End/School/models.py new file mode 100644 index 0000000..e2d27d7 --- /dev/null +++ b/Back-End/School/models.py @@ -0,0 +1,66 @@ +from django.db import models +from Auth.models import Profile +from django.db.models import JSONField +from django.dispatch import receiver +from django.contrib.postgres.fields import ArrayField + +LEVEL_CHOICES = [ + (1, 'Très Petite Section (TPS)'), + (2, 'Petite Section (PS)'), + (3, 'Moyenne Section (MS)'), + (4, 'Grande Section (GS)'), + (5, 'Cours Préparatoire (CP)'), + (6, 'Cours Élémentaire 1 (CE1)'), + (7, 'Cours Élémentaire 2 (CE2)'), + (8, 'Cours Moyen 1 (CM1)'), + (9, 'Cours Moyen 2 (CM2)') +] + +class Speciality(models.Model): + name = models.CharField(max_length=100) + updatedDate = models.DateTimeField(auto_now=True) + colorCode = models.CharField(max_length=7, default='#FFFFFF') + + def __str__(self): + return self.name + +class Teacher(models.Model): + lastName = models.CharField(max_length=100) + firstName = models.CharField(max_length=100) + email = models.EmailField(unique=True) + specialities = models.ManyToManyField(Speciality, related_name='teachers') + associatedProfile = models.ForeignKey(Profile, on_delete=models.CASCADE, null=True, blank=True) + updatedDate = models.DateTimeField(auto_now=True) + + def __str__(self): + return f"{self.lastName} {self.firstName}" + +class SchoolClass(models.Model): + PLANNING_TYPE_CHOICES = [ + (1, 'Annuel'), + (2, 'Semestriel'), + (3, 'Trimestriel') + ] + + atmosphereName = models.CharField(max_length=255, null=True, blank=True) + ageGroup = models.JSONField() + numberOfStudents = models.PositiveIntegerField() + teachingLanguage = models.CharField(max_length=255) + schoolYear = models.CharField(max_length=9) + updatedDate = models.DateTimeField(auto_now_add=True) + teachers = models.ManyToManyField(Teacher, related_name='schoolClasses') + levels = ArrayField(models.IntegerField(choices=LEVEL_CHOICES), default=list) + type = models.IntegerField(choices=PLANNING_TYPE_CHOICES, default=1) + schedule = models.JSONField(default=list) + openingDays = ArrayField(models.IntegerField(), default=list) + + def __str__(self): + return self.atmosphereName + +class Planning(models.Model): + level = models.IntegerField(choices=LEVEL_CHOICES, null=True, blank=True) + classModel = models.ForeignKey(SchoolClass, null=True, blank=True, related_name='plannings', on_delete=models.CASCADE) + schedule = JSONField(default=dict) + + def __str__(self): + return f'Planning for {self.level} of {self.classModel.atmosphereName}' diff --git a/Back-End/School/serializers.py b/Back-End/School/serializers.py new file mode 100644 index 0000000..f58d5a9 --- /dev/null +++ b/Back-End/School/serializers.py @@ -0,0 +1,194 @@ +from rest_framework import serializers +from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES +from Subscriptions.models import RegistrationForm +from Subscriptions.serializers import StudentSerializer +from Auth.serializers import ProfileSerializer +from Auth.models import Profile +from N3wtSchool import settings, bdd +from django.utils import timezone +import pytz + +class SpecialitySerializer(serializers.ModelSerializer): + creationDateFormatted = serializers.SerializerMethodField() + + class Meta: + model = Speciality + fields = '__all__' + + def get_creationDateFormatted(self, obj): + utc_time = timezone.localtime(obj.updatedDate) # Convert to local time + local_tz = pytz.timezone(settings.TZ_APPLI) + local_time = utc_time.astimezone(local_tz) + + return local_time.strftime("%d-%m-%Y %H:%M") + +class TeacherDetailSerializer(serializers.ModelSerializer): + specialities = SpecialitySerializer(many=True, read_only=True) + + class Meta: + model = Teacher + fields = ['id', 'lastName', 'firstName', 'email', 'specialities'] + +class TeacherSerializer(serializers.ModelSerializer): + specialities = SpecialitySerializer(many=True, read_only=True) + specialties_ids = serializers.PrimaryKeyRelatedField(queryset=Speciality.objects.all(), many=True, source='specialities') + associatedProfile_id = serializers.PrimaryKeyRelatedField(queryset=Profile.objects.all(), source='associatedProfile', write_only=False, read_only=False) + mainClasses = serializers.PrimaryKeyRelatedField(many=True, read_only=True, source='schoolClasses') + associatedProfile = ProfileSerializer(read_only=True) + rightLabel = serializers.SerializerMethodField() + rightValue = serializers.SerializerMethodField() + creationDateFormatted = serializers.SerializerMethodField() + + class Meta: + model = Teacher + fields = ['id', 'lastName', 'firstName', 'email', 'specialities', 'specialties_ids', 'mainClasses', 'associatedProfile', 'associatedProfile_id', 'rightLabel', 'rightValue', 'updatedDate', 'creationDateFormatted'] + + def create(self, validated_data): + specialties_data = validated_data.pop('specialities', None) + associatedProfile = validated_data.pop('associatedProfile', None) + teacher = Teacher.objects.create(**validated_data) + teacher.specialities.set(specialties_data) + if associatedProfile: + teacher.associatedProfile = associatedProfile + teacher.save() + return teacher + + def update(self, instance, validated_data): + specialties_data = validated_data.pop('specialities', []) + instance.lastName = validated_data.get('lastName', instance.lastName) + instance.firstName = validated_data.get('firstName', instance.firstName) + instance.email = validated_data.get('email', instance.email) + instance.associatedProfile = validated_data.get('associatedProfile', instance.associatedProfile) + instance.save() + instance.specialities.set(specialties_data) + return instance + + def get_rightLabel(self, obj): + return obj.associatedProfile.get_right_display() if obj.associatedProfile else None + + def get_rightValue(self, obj): + return obj.associatedProfile.right if obj.associatedProfile else None + + def get_creationDateFormatted(self, obj): + utc_time = timezone.localtime(obj.updatedDate) # Convert to local time + local_tz = pytz.timezone(settings.TZ_APPLI) + local_time = utc_time.astimezone(local_tz) + return local_time.strftime("%d-%m-%Y %H:%M") + +class PlanningSerializer(serializers.ModelSerializer): + class Meta: + model = Planning + fields = ['id', 'level', 'schedule'] + + def to_internal_value(self, data): + internal_value = super().to_internal_value(data) + internal_value['schedule'] = data.get('schedule', {}) + return internal_value + +class SchoolClassSerializer(serializers.ModelSerializer): + creationDateFormatted = serializers.SerializerMethodField() + teachers = TeacherSerializer(many=True, read_only=True) + teachers_ids = serializers.PrimaryKeyRelatedField(queryset=Teacher.objects.all(), many=True, source='teachers') + students = serializers.SerializerMethodField() + levels = serializers.ListField(child=serializers.ChoiceField(choices=LEVEL_CHOICES)) + plannings_read = serializers.SerializerMethodField() + plannings = PlanningSerializer(many=True, write_only=True) + + class Meta: + model = SchoolClass + fields = [ + 'id', 'atmosphereName', 'ageGroup', 'numberOfStudents', 'teachingLanguage', + 'teachers', 'teachers_ids', 'schoolYear', 'updatedDate', + 'creationDateFormatted', 'students', 'levels', 'type', 'schedule', + 'openingDays', 'plannings', 'plannings_read' + ] + + def create(self, validated_data): + teachers_data = validated_data.pop('teachers', []) + levels_data = validated_data.pop('levels', []) + plannings_data = validated_data.pop('plannings', []) + + classModel = SchoolClass.objects.create( + atmosphereName=validated_data.get('atmosphereName', ''), + ageGroup=validated_data.get('ageGroup', []), + numberOfStudents=validated_data.get('numberOfStudents', 0), + teachingLanguage=validated_data.get('teachingLanguage', ''), + schoolYear=validated_data.get('schoolYear', ''), + levels=levels_data, + type=validated_data.get('type', 1), # Added here + schedule=validated_data.get('schedule', ['08:30', '17:30']), # Added here + openingDays=validated_data.get('openingDays', [1, 2, 4, 5]) # Added here + ) + + classModel.teachers.set(teachers_data) + + for planning_data in plannings_data: + Planning.objects.create( + classModel=classModel, + level=planning_data['level'], + schedule=planning_data.get('schedule', {}) + ) + + return classModel + + def update(self, instance, validated_data): + teachers_data = validated_data.pop('teachers', []) + levels_data = validated_data.pop('levels', []) + plannings_data = validated_data.pop('plannings', []) + + instance.atmosphereName = validated_data.get('atmosphereName', instance.atmosphereName) + instance.ageGroup = validated_data.get('ageGroup', instance.ageGroup) + instance.numberOfStudents = validated_data.get('numberOfStudents', instance.numberOfStudents) + instance.teachingLanguage = validated_data.get('teachingLanguage', instance.teachingLanguage) + instance.schoolYear = validated_data.get('schoolYear', instance.schoolYear) + instance.levels = levels_data + instance.type = validated_data.get('type', instance.type) # Added here + instance.schedule = validated_data.get('schedule', instance.schedule) # Added here + instance.openingDays = validated_data.get('openingDays', instance.openingDays) # Added here + + instance.save() + instance.teachers.set(teachers_data) + + existing_plannings = {planning.level: planning for planning in instance.plannings.all()} + + for planning_data in plannings_data: + level = planning_data['level'] + if level in existing_plannings: + # Update existing planning + planning = existing_plannings[level] + planning.schedule = planning_data.get('schedule', planning.schedule) + planning.save() + else: + # Create new planning if level not existing + Planning.objects.create( + classModel=instance, + level=level, + schedule=planning_data.get('schedule', {}) + ) + + return instance + + def get_creationDateFormatted(self, obj): + utc_time = timezone.localtime(obj.updatedDate) + local_tz = pytz.timezone(settings.TZ_APPLI) + local_time = utc_time.astimezone(local_tz) + return local_time.strftime("%d-%m-%Y %H:%M") + + def get_students(self, obj): + studentsList = obj.students.all() + filtered_students = [] + for student in studentsList: + registrationForm = bdd.getObject(RegistrationForm, "student__id", student.id) + if registrationForm.status == registrationForm.RegistrationFormStatus.RF_VALIDATED: + filtered_students.append(student) + return StudentSerializer(filtered_students, many=True, read_only=True).data + + def get_plannings_read(self, obj): + plannings = obj.plannings.all() + levels_dict = {level: {'level': level, 'planning': None} for level in obj.levels} + + for planning in plannings: + if planning.level in levels_dict: + levels_dict[planning.level]['planning'] = PlanningSerializer(planning).data + + return list(levels_dict.values()) diff --git a/Back-End/GestionEnseignants/tests.py b/Back-End/School/tests.py similarity index 100% rename from Back-End/GestionEnseignants/tests.py rename to Back-End/School/tests.py diff --git a/Back-End/School/urls.py b/Back-End/School/urls.py new file mode 100644 index 0000000..8dd1050 --- /dev/null +++ b/Back-End/School/urls.py @@ -0,0 +1,21 @@ +from django.urls import path, re_path + +from School.views import TeachersView, EnseignantView, SpecialitiesView, SpecialityView, ClassesView, ClasseView, PlanningsView, PlanningView + +urlpatterns = [ + re_path(r'^teachers$', TeachersView.as_view(), name="teachers"), + re_path(r'^teacher$', EnseignantView.as_view(), name="teacher"), + re_path(r'^teacher/([0-9]+)$', EnseignantView.as_view(), name="teacher"), + + re_path(r'^specialities$', SpecialitiesView.as_view(), name="specialities"), + re_path(r'^speciality$', SpecialityView.as_view(), name="speciality"), + re_path(r'^speciality/([0-9]+)$', SpecialityView.as_view(), name="speciality"), + + re_path(r'^schoolClasses$', ClassesView.as_view(), name="schoolClasses"), + re_path(r'^schoolClass$', ClasseView.as_view(), name="schoolClass"), + re_path(r'^schoolClass/([0-9]+)$', ClasseView.as_view(), name="schoolClass"), + + re_path(r'^plannings$', PlanningsView.as_view(), name="plannings"), + re_path(r'^planning$', PlanningView.as_view(), name="planning"), + re_path(r'^planning/([0-9]+)$', PlanningView.as_view(), name="planning"), +] \ No newline at end of file diff --git a/Back-End/GestionEnseignants/views.py b/Back-End/School/views.py similarity index 50% rename from Back-End/GestionEnseignants/views.py rename to Back-End/School/views.py index 9246da2..112dd2e 100644 --- a/Back-End/GestionEnseignants/views.py +++ b/Back-End/School/views.py @@ -4,121 +4,121 @@ from django.utils.decorators import method_decorator from rest_framework.parsers import JSONParser from rest_framework.views import APIView from django.core.cache import cache -from .models import Enseignant, Specialite, Classe, Planning -from .serializers import EnseignantSerializer, SpecialiteSerializer, ClasseSerializer, PlanningSerializer +from .models import Teacher, Speciality, SchoolClass, Planning +from .serializers import TeacherSerializer, SpecialitySerializer, SchoolClassSerializer, PlanningSerializer from N3wtSchool import bdd -class EnseignantsView(APIView): +class TeachersView(APIView): def get(self, request): - enseignantsList=bdd.getAllObjects(Enseignant) - enseignants_serializer=EnseignantSerializer(enseignantsList, many=True) + teachersList=bdd.getAllObjects(Teacher) + teachers_serializer=TeacherSerializer(teachersList, many=True) - return JsonResponse(enseignants_serializer.data, safe=False) + return JsonResponse(teachers_serializer.data, safe=False) @method_decorator(csrf_protect, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch') class EnseignantView(APIView): def get (self, request, _id): - enseignant = bdd.getObject(_objectName=Enseignant, _columnName='id', _value=_id) - enseignant_serializer=EnseignantSerializer(enseignant) + teacher = bdd.getObject(_objectName=Teacher, _columnName='id', _value=_id) + teacher_serializer=TeacherSerializer(teacher) - return JsonResponse(enseignant_serializer.data, safe=False) + return JsonResponse(teacher_serializer.data, safe=False) def post(self, request): - enseignant_data=JSONParser().parse(request) - enseignant_serializer = EnseignantSerializer(data=enseignant_data) + teacher_data=JSONParser().parse(request) + teacher_serializer = TeacherSerializer(data=teacher_data) - if enseignant_serializer.is_valid(): - enseignant_serializer.save() + if teacher_serializer.is_valid(): + teacher_serializer.save() - return JsonResponse(enseignant_serializer.data, safe=False) + return JsonResponse(teacher_serializer.data, safe=False) - return JsonResponse(enseignant_serializer.errors, safe=False) + return JsonResponse(teacher_serializer.errors, safe=False) def put(self, request, _id): - enseignant_data=JSONParser().parse(request) - enseignant = bdd.getObject(_objectName=Enseignant, _columnName='id', _value=_id) - enseignant_serializer = EnseignantSerializer(enseignant, data=enseignant_data) - if enseignant_serializer.is_valid(): - enseignant_serializer.save() - return JsonResponse(enseignant_serializer.data, safe=False) + teacher_data=JSONParser().parse(request) + teacher = bdd.getObject(_objectName=Teacher, _columnName='id', _value=_id) + teacher_serializer = TeacherSerializer(teacher, data=teacher_data) + if teacher_serializer.is_valid(): + teacher_serializer.save() + return JsonResponse(teacher_serializer.data, safe=False) - return JsonResponse(enseignant_serializer.errors, safe=False) + return JsonResponse(teacher_serializer.errors, safe=False) def delete(self, request, _id): - enseignant = bdd.getObject(_objectName=Enseignant, _columnName='id', _value=_id) - if enseignant is not None: - if enseignant.profilAssocie: + teacher = bdd.getObject(_objectName=Teacher, _columnName='id', _value=_id) + if teacher is not None: + if teacher.profilAssocie: print('Suppression du profil associé') - enseignant.profilAssocie.delete() - enseignant.delete() - return JsonResponse({'message': 'La suppression de l\'enseignant a été effectuée avec succès'}, safe=False) + teacher.profilAssocie.delete() + teacher.delete() + return JsonResponse({'message': 'La suppression de l\'teacher a été effectuée avec succès'}, safe=False) else: - return JsonResponse({'erreur': 'L\'enseignant n\'a pas été trouvé'}, safe=False) + return JsonResponse({'erreur': 'L\'teacher n\'a pas été trouvé'}, safe=False) @method_decorator(csrf_protect, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch') -class SpecialitesView(APIView): +class SpecialitiesView(APIView): def get(self, request): - specialitesList=bdd.getAllObjects(Specialite) - specialites_serializer=SpecialiteSerializer(specialitesList, many=True) + specialitiesList=bdd.getAllObjects(Speciality) + specialities_serializer=SpecialitySerializer(specialitiesList, many=True) - return JsonResponse(specialites_serializer.data, safe=False) + return JsonResponse(specialities_serializer.data, safe=False) def post(self, request): - specialites_data=JSONParser().parse(request) + specialities_data=JSONParser().parse(request) all_valid = True - for specialite_data in specialites_data: - specialite_serializer = SpecialiteSerializer(data=specialite_data) + for speciality_data in specialities_data: + speciality_serializer = SpecialitySerializer(data=speciality_data) - if specialite_serializer.is_valid(): - specialite_serializer.save() + if speciality_serializer.is_valid(): + speciality_serializer.save() else: all_valid = False break if all_valid: - specialitesList = bdd.getAllObjects(Specialite) - specialites_serializer = SpecialiteSerializer(specialitesList, many=True) + specialitiesList = bdd.getAllObjects(Speciality) + specialities_serializer = SpecialitySerializer(specialitiesList, many=True) - return JsonResponse(specialites_serializer.data, safe=False) + return JsonResponse(specialities_serializer.data, safe=False) - return JsonResponse(specialite_serializer.errors, safe=False) + return JsonResponse(speciality_serializer.errors, safe=False) @method_decorator(csrf_protect, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch') -class SpecialiteView(APIView): +class SpecialityView(APIView): def get (self, request, _id): - specialite = bdd.getObject(_objectName=Specialite, _columnName='id', _value=_id) - specialite_serializer=SpecialiteSerializer(specialite) + speciality = bdd.getObject(_objectName=Speciality, _columnName='id', _value=_id) + speciality_serializer=SpecialitySerializer(speciality) - return JsonResponse(specialite_serializer.data, safe=False) + return JsonResponse(speciality_serializer.data, safe=False) def post(self, request): - specialite_data=JSONParser().parse(request) - specialite_serializer = SpecialiteSerializer(data=specialite_data) + speciality_data=JSONParser().parse(request) + speciality_serializer = SpecialitySerializer(data=speciality_data) - if specialite_serializer.is_valid(): - specialite_serializer.save() - return JsonResponse(specialite_serializer.data, safe=False) + if speciality_serializer.is_valid(): + speciality_serializer.save() + return JsonResponse(speciality_serializer.data, safe=False) - return JsonResponse(specialite_serializer.errors, safe=False) + return JsonResponse(speciality_serializer.errors, safe=False) def put(self, request, _id): - specialite_data=JSONParser().parse(request) - specialite = bdd.getObject(_objectName=Specialite, _columnName='id', _value=_id) - specialite_serializer = SpecialiteSerializer(specialite, data=specialite_data) - if specialite_serializer.is_valid(): - specialite_serializer.save() - return JsonResponse(specialite_serializer.data, safe=False) + speciality_data=JSONParser().parse(request) + speciality = bdd.getObject(_objectName=Speciality, _columnName='id', _value=_id) + speciality_serializer = SpecialitySerializer(speciality, data=speciality_data) + if speciality_serializer.is_valid(): + speciality_serializer.save() + return JsonResponse(speciality_serializer.data, safe=False) - return JsonResponse(specialite_serializer.errors, safe=False) + return JsonResponse(speciality_serializer.errors, safe=False) def delete(self, request, _id): - specialite = bdd.getObject(_objectName=Specialite, _columnName='id', _value=_id) - if specialite != None: - specialite.delete() + speciality = bdd.getObject(_objectName=Speciality, _columnName='id', _value=_id) + if speciality != None: + speciality.delete() return JsonResponse("La suppression de la spécialité a été effectuée avec succès", safe=False) @@ -126,15 +126,15 @@ class SpecialiteView(APIView): @method_decorator(ensure_csrf_cookie, name='dispatch') class ClassesView(APIView): def get(self, request): - classesList=bdd.getAllObjects(Classe) - classes_serializer=ClasseSerializer(classesList, many=True) + classesList=bdd.getAllObjects(SchoolClass) + classes_serializer=SchoolClassSerializer(classesList, many=True) return JsonResponse(classes_serializer.data, safe=False) def post(self, request): all_valid = True classes_data=JSONParser().parse(request) for classe_data in classes_data: - classe_serializer = ClasseSerializer(data=classe_data) + classe_serializer = SchoolClassSerializer(data=classe_data) if classe_serializer.is_valid(): classe_serializer.save() @@ -143,8 +143,8 @@ class ClassesView(APIView): break if all_valid: - classesList = bdd.getAllObjects(Classe) - classes_serializer = ClasseSerializer(classesList, many=True) + classesList = bdd.getAllObjects(SchoolClass) + classes_serializer = SchoolClassSerializer(classesList, many=True) return JsonResponse(classes_serializer.data, safe=False) @@ -154,14 +154,14 @@ class ClassesView(APIView): @method_decorator(ensure_csrf_cookie, name='dispatch') class ClasseView(APIView): def get (self, request, _id): - classe = bdd.getObject(_objectName=Classe, _columnName='id', _value=_id) - classe_serializer=ClasseSerializer(classe) + schoolClass = bdd.getObject(_objectName=SchoolClass, _columnName='id', _value=_id) + classe_serializer=SchoolClassSerializer(schoolClass) return JsonResponse(classe_serializer.data, safe=False) def post(self, request): classe_data=JSONParser().parse(request) - classe_serializer = ClasseSerializer(data=classe_data) + classe_serializer = SchoolClassSerializer(data=classe_data) if classe_serializer.is_valid(): classe_serializer.save() @@ -171,8 +171,8 @@ class ClasseView(APIView): def put(self, request, _id): classe_data=JSONParser().parse(request) - classe = bdd.getObject(_objectName=Classe, _columnName='id', _value=_id) - classe_serializer = ClasseSerializer(classe, data=classe_data) + schoolClass = bdd.getObject(_objectName=SchoolClass, _columnName='id', _value=_id) + classe_serializer = SchoolClassSerializer(schoolClass, data=classe_data) if classe_serializer.is_valid(): classe_serializer.save() return JsonResponse(classe_serializer.data, safe=False) @@ -180,23 +180,23 @@ class ClasseView(APIView): return JsonResponse(classe_serializer.errors, safe=False) def delete(self, request, _id): - classe = bdd.getObject(_objectName=Classe, _columnName='id', _value=_id) - if classe is not None: - # Supprimer les plannings associés à la classe - for planning in classe.plannings.all(): + schoolClass = bdd.getObject(_objectName=SchoolClass, _columnName='id', _value=_id) + if schoolClass is not None: + # Supprimer les plannings associés à la schoolClass + for planning in schoolClass.plannings.all(): print(f'Planning à supprimer : {planning}') planning.delete() - # Retirer la classe des élèves associés - for eleve in classe.eleves.all(): - print(f'Eleve à retirer de la classe : {eleve}') + # Retirer la schoolClass des élèves associés + for eleve in schoolClass.eleves.all(): + print(f'Student à retirer de la schoolClass : {eleve}') eleve.classeAssociee = None eleve.save() - # Supprimer la classe - classe.delete() + # Supprimer la schoolClass + schoolClass.delete() - return JsonResponse("La suppression de la classe a été effectuée avec succès", safe=False) + return JsonResponse("La suppression de la schoolClass a été effectuée avec succès", safe=False) @method_decorator(csrf_protect, name='dispatch') diff --git a/Back-End/GestionInscriptions/Configuration/application.default.json b/Back-End/Subscriptions/Configuration/application.default.json similarity index 100% rename from Back-End/GestionInscriptions/Configuration/application.default.json rename to Back-End/Subscriptions/Configuration/application.default.json diff --git a/Back-End/GestionInscriptions/Configuration/automate.json b/Back-End/Subscriptions/Configuration/automate.json similarity index 100% rename from Back-End/GestionInscriptions/Configuration/automate.json rename to Back-End/Subscriptions/Configuration/automate.json diff --git a/Back-End/GestionInscriptions/Configuration/inscriptions.json b/Back-End/Subscriptions/Configuration/inscriptions.json similarity index 100% rename from Back-End/GestionInscriptions/Configuration/inscriptions.json rename to Back-End/Subscriptions/Configuration/inscriptions.json diff --git a/Back-End/Subscriptions/__init__.py b/Back-End/Subscriptions/__init__.py new file mode 100644 index 0000000..91049e1 --- /dev/null +++ b/Back-End/Subscriptions/__init__.py @@ -0,0 +1 @@ +default_app_config = 'Subscriptions.apps.GestionInscriptionsConfig' \ No newline at end of file diff --git a/Back-End/GestionInscriptions/admin.py b/Back-End/Subscriptions/admin.py similarity index 79% rename from Back-End/GestionInscriptions/admin.py rename to Back-End/Subscriptions/admin.py index 2d81adb..f0bf1ee 100644 --- a/Back-End/GestionInscriptions/admin.py +++ b/Back-End/Subscriptions/admin.py @@ -2,8 +2,8 @@ from django.contrib import admin from .models import * -admin.site.register(Eleve) -admin.site.register(Responsable) +admin.site.register(Student) +admin.site.register(Guardian) class EleveAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): diff --git a/Back-End/GestionInscriptions/apps.py b/Back-End/Subscriptions/apps.py similarity index 69% rename from Back-End/GestionInscriptions/apps.py rename to Back-End/Subscriptions/apps.py index f88ea7f..503b621 100644 --- a/Back-End/GestionInscriptions/apps.py +++ b/Back-End/Subscriptions/apps.py @@ -3,8 +3,8 @@ from django.conf import settings class GestioninscriptionsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'GestionInscriptions' + name = 'Subscriptions' def ready(self): - from GestionInscriptions.signals import clear_cache + from Subscriptions.signals import clear_cache clear_cache() diff --git a/Back-End/Subscriptions/automate.py b/Back-End/Subscriptions/automate.py new file mode 100644 index 0000000..3c457cb --- /dev/null +++ b/Back-End/Subscriptions/automate.py @@ -0,0 +1,45 @@ +# state_machine.py +import json +from Subscriptions.models import RegistrationForm +from Subscriptions.signals import clear_cache + +state_mapping = { + "ABSENT": RegistrationForm.RegistrationFormStatus.RF_ABSENT, + "CREE": RegistrationForm.RegistrationFormStatus.RF_CREATED, + "ENVOYE": RegistrationForm.RegistrationFormStatus.RF_SENT, + "EN_VALIDATION": RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW, + "A_RELANCER": RegistrationForm.RegistrationFormStatus.RF_TO_BE_FOLLOWED_UP, + "VALIDE": RegistrationForm.RegistrationFormStatus.RF_VALIDATED, + "ARCHIVE": RegistrationForm.RegistrationFormStatus.RF_ARCHIVED +} + +def load_config(config_file): + with open(config_file, 'r') as file: + config = json.load(file) + return config + +def getStateMachineObject(etat) : + return Automate_RF_Register(etat) + +def getStateMachineObjectState(etat): + return Automate_RF_Register(etat).state + +def updateStateMachine(rf, transition) : + automateModel = load_config('Subscriptions/Configuration/automate.json') + state_machine = getStateMachineObject(rf.status) + print(f'etat DI : {state_machine.state}') + if state_machine.trigger(transition, automateModel): + rf.status = state_machine.state + rf.save() + clear_cache() + +class Automate_RF_Register: + def __init__(self, initial_state): + self.state = initial_state + + def trigger(self, transition_name, config): + for transition in config["transitions"]: + if transition["name"] == transition_name and self.state == state_mapping[transition["from"]]: + self.state = state_mapping[transition["to"]] + return True + return False \ No newline at end of file diff --git a/Back-End/GestionInscriptions/mailManager.py b/Back-End/Subscriptions/mailManager.py similarity index 95% rename from Back-End/GestionInscriptions/mailManager.py rename to Back-End/Subscriptions/mailManager.py index 653658d..02d3826 100644 --- a/Back-End/GestionInscriptions/mailManager.py +++ b/Back-End/Subscriptions/mailManager.py @@ -11,7 +11,7 @@ def envoieReinitMotDePasse(recipients, code): fail_silently=False, ) -def envoieDossierInscription(recipients): +def sendRegisterForm(recipients): errorMessage = '' try: print(f'{settings.EMAIL_HOST_USER}') @@ -68,7 +68,7 @@ def isValid(message, fiche_inscription): idMail = result.group(1).strip() eleve = fiche_inscription.eleve - responsable = eleve.getResponsablePrincipal() + responsable = eleve.getMainGuardian() mailReponsableAVerifier = responsable.mail return responsableMail == mailReponsableAVerifier and str(idMail) == str(fiche_inscription.eleve.id) \ No newline at end of file diff --git a/Back-End/Subscriptions/models.py b/Back-End/Subscriptions/models.py new file mode 100644 index 0000000..fc79a26 --- /dev/null +++ b/Back-End/Subscriptions/models.py @@ -0,0 +1,179 @@ +from django.db import models +from django.utils.timezone import now +from django.conf import settings +from django.utils.translation import gettext_lazy as _ + +from Auth.models import Profile +from School.models import SchoolClass + +from datetime import datetime + +class RegistrationFee(models.Model): + class PaymentOptions(models.IntegerChoices): + SINGLE_PAYMENT = 0, _('Paiement en une seule fois') + MONTHLY_PAYMENT = 1, _('Paiement mensuel') + QUARTERLY_PAYMENT = 2, _('Paiement trimestriel') + + name = models.CharField(max_length=255, unique=True) + description = models.TextField(blank=True) + base_amount = models.DecimalField(max_digits=10, decimal_places=2) + discounts = models.JSONField(blank=True, null=True) + supplements = models.JSONField(blank=True, null=True) + validity_start_date = models.DateField() + validity_end_date = models.DateField() + payment_option = models.IntegerField(choices=PaymentOptions, default=PaymentOptions.SINGLE_PAYMENT) + + def __str__(self): + return self.name + +class Language(models.Model): + id = models.AutoField(primary_key=True) + label = models.CharField(max_length=200, default="") + + def __str__(self): + return "LANGUAGE" + +class Guardian(models.Model): + last_name = models.CharField(max_length=200, default="") + first_name = models.CharField(max_length=200, default="") + birth_date = models.CharField(max_length=200, default="", blank=True) + address = models.CharField(max_length=200, default="", blank=True) + email = models.CharField(max_length=200, default="", blank=True) + phone = models.CharField(max_length=200, default="", blank=True) + profession = models.CharField(max_length=200, default="", blank=True) + associated_profile = models.ForeignKey(Profile, on_delete=models.CASCADE) + + def __str__(self): + return self.last_name + "_" + self.first_name + +class Sibling(models.Model): + id = models.AutoField(primary_key=True) + last_name = models.CharField(max_length=200, default="") + first_name = models.CharField(max_length=200, default="") + birth_date = models.CharField(max_length=200, default="", blank=True) + + def __str__(self): + return "SIBLING" + +class Student(models.Model): + + class StudentGender(models.IntegerChoices): + NONE = 0, _('Sélection du genre') + MALE = 1, _('Garçon') + FEMALE = 2, _('Fille') + + class StudentLevel(models.IntegerChoices): + NONE = 0, _('Sélection du niveau') + TPS = 1, _('TPS - Très Petite Section') + PS = 2, _('PS - Petite Section') + MS = 3, _('MS - Moyenne Section') + GS = 4, _('GS - Grande Section') + + class PaymentMethod(models.IntegerChoices): + NONE = 0, _('Sélection du mode de paiement') + SEPA_DIRECT_DEBIT = 1, _('Prélèvement SEPA') + CHECK = 2, _('Chèques') + + last_name = models.CharField(max_length=200, default="") + first_name = models.CharField(max_length=200, default="") + gender = models.IntegerField(choices=StudentGender, default=StudentGender.NONE, blank=True) + level = models.IntegerField(choices=StudentLevel, default=StudentLevel.NONE, blank=True) + nationality = models.CharField(max_length=200, default="", blank=True) + address = models.CharField(max_length=200, default="", blank=True) + birth_date = models.DateField(null=True, blank=True) + birth_place = models.CharField(max_length=200, default="", blank=True) + birth_postal_code = models.IntegerField(default=0, blank=True) + attending_physician = models.CharField(max_length=200, default="", blank=True) + payment_method = models.IntegerField(choices=PaymentMethod, default=PaymentMethod.NONE, blank=True) + + # Many-to-Many Relationship + profiles = models.ManyToManyField(Profile, blank=True) + + # Many-to-Many Relationship + guardians = models.ManyToManyField(Guardian, blank=True) + + # Many-to-Many Relationship + siblings = models.ManyToManyField(Sibling, blank=True) + + # Many-to-Many Relationship + spoken_languages = models.ManyToManyField(Language, blank=True) + + # One-to-Many Relationship + associated_class = models.ForeignKey(SchoolClass, on_delete=models.SET_NULL, null=True, blank=True, related_name='students') + + def __str__(self): + return self.last_name + "_" + self.first_name + + def getSpokenLanguages(self): + return self.spoken_languages.all() + + def getMainGuardian(self): + return self.guardians.all()[0] + + def getGuardians(self): + return self.guardians.all() + + def getProfiles(self): + return self.profiles.all() + + def getSiblings(self): + return self.siblings.all() + + def getNumberOfSiblings(self): + return self.siblings.count() + + @property + def age(self): + if self.birth_date: + today = datetime.today() + years = today.year - self.birth_date.year + months = today.month - self.birth_date.month + if today.day < self.birth_date.day: + months -= 1 + if months < 0: + years -= 1 + months += 12 + + # Determine the age format + if 6 <= months <= 12: + return f"{years} years 1/2" + else: + return f"{years} years" + return None + + @property + def formatted_birth_date(self): + if self.birth_date: + return self.birth_date.strftime('%d-%m-%Y') + return None + +class RegistrationForm(models.Model): + + class RegistrationFormStatus(models.IntegerChoices): + RF_ABSENT = 0, _('Pas de dossier d\'inscription') + RF_CREATED = 1, _('Dossier d\'inscription créé') + RF_SENT = 2, _('Dossier d\'inscription envoyé') + RF_UNDER_REVIEW = 3, _('Dossier d\'inscription en cours de validation') + RF_TO_BE_FOLLOWED_UP = 4, _('Dossier d\'inscription à relancer') + RF_VALIDATED = 5, _('Dossier d\'inscription validé') + RF_ARCHIVED = 6, _('Dossier d\'inscription archivé') + + # One-to-One Relationship + student = models.OneToOneField(Student, on_delete=models.CASCADE, primary_key=True) + status = models.IntegerField(choices=RegistrationFormStatus, default=RegistrationFormStatus.RF_ABSENT) + last_update = models.DateTimeField(auto_now=True) + notes = models.CharField(max_length=200, blank=True) + registration_link_code = models.CharField(max_length=200, default="", blank=True) + registration_file = models.FileField(upload_to=settings.DOCUMENT_DIR, default="", blank=True) + associated_rf = models.CharField(max_length=200, default="", blank=True) + + def __str__(self): + return "RF_" + self.student.last_name + "_" + self.student.first_name + +class RegistrationFile(models.Model): + name = models.CharField(max_length=255) + file = models.FileField(upload_to='registration_files/') + date_added = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.name diff --git a/Back-End/GestionInscriptions/pagination.py b/Back-End/Subscriptions/pagination.py similarity index 93% rename from Back-End/GestionInscriptions/pagination.py rename to Back-End/Subscriptions/pagination.py index db1809c..f2a97dc 100644 --- a/Back-End/GestionInscriptions/pagination.py +++ b/Back-End/Subscriptions/pagination.py @@ -16,5 +16,5 @@ class CustomPagination(PageNumberPagination): 'count': self.page.paginator.count, 'page_size': self.page_size, 'max_page_size' : self.max_page_size, - 'fichesInscriptions': data } + 'registerForms': data } ) \ No newline at end of file diff --git a/Back-End/Subscriptions/serializers.py b/Back-End/Subscriptions/serializers.py new file mode 100644 index 0000000..2490962 --- /dev/null +++ b/Back-End/Subscriptions/serializers.py @@ -0,0 +1,214 @@ +from rest_framework import serializers +from .models import RegistrationFile, RegistrationForm, Student, Guardian, Sibling, Language, RegistrationFee +from School.models import SchoolClass +from Auth.models import Profile +from Auth.serializers import ProfileSerializer +from GestionMessagerie.models import Messagerie +from GestionNotification.models import Notification +from N3wtSchool import settings +from django.utils import timezone +import pytz +from datetime import datetime + +class RegistrationFileSerializer(serializers.ModelSerializer): + class Meta: + model = RegistrationFile + fields = '__all__' + +class RegistrationFeeSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: + model = RegistrationFee + fields = '__all__' + +class LanguageSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: + model = Language + fields = '__all__' + +class SiblingSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: + model = Sibling + fields = '__all__' + +class GuardianSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + associated_profile = serializers.PrimaryKeyRelatedField(queryset=Profile.objects.all(), required=True) + associated_profile_email = serializers.SerializerMethodField() + + class Meta: + model = Guardian + fields = '__all__' + + def get_associated_profile_email(self, obj): + return obj.associated_profile.email + + +class StudentSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + guardians = GuardianSerializer(many=True, required=False) + siblings = SiblingSerializer(many=True, required=False) + languages = LanguageSerializer(many=True, required=False) + associated_class_id = serializers.PrimaryKeyRelatedField(queryset=SchoolClass.objects.all(), source='associated_class', required=False, write_only=False, read_only=False) + age = serializers.SerializerMethodField() + formatted_birth_date = serializers.SerializerMethodField() + birth_date = serializers.DateField(input_formats=['%d-%m-%Y', '%Y-%m-%d'], required=False, allow_null=True) + associated_class_name = serializers.SerializerMethodField() + + class Meta: + model = Student + fields = '__all__' + + def get_or_create_packages(self, guardians_data): + guardians_ids = [] + for guardian_data in guardians_data: + guardian_instance, created = Guardian.objects.get_or_create( + id=guardian_data.get('id'), + defaults=guardian_data + ) + guardians_ids.append(guardian_instance.id) + return guardians_ids + + def create(self, validated_data): + guardians_data = validated_data.pop('guardians', []) + siblings_data = validated_data.pop('siblings', []) + languages_data = validated_data.pop('spoken_languages', []) + student = Student.objects.create(**validated_data) + student.guardians.set(self.get_or_create_packages(guardians_data)) + student.siblings.set(self.get_or_create_packages(siblings_data)) + student.spoken_languages.set(self.get_or_create_packages(languages_data)) + + return student + + def create_or_update_packages(self, guardians_data): + guardians_ids = [] + for guardian_data in guardians_data: + guardian_instance, created = Guardian.objects.update_or_create( + id=guardian_data.get('id'), + defaults=guardian_data + ) + guardians_ids.append(guardian_instance.id) + return guardians_ids + + def update(self, instance, validated_data): + guardians_data = validated_data.pop('guardians', []) + siblings_data = validated_data.pop('siblings', []) + languages_data = validated_data.pop('spoken_languages', []) + if guardians_data: + instance.guardians.set(self.create_or_update_packages(guardians_data)) + if siblings_data: + instance.siblings.set(self.create_or_update_packages(siblings_data)) + if languages_data: + instance.spoken_languages.set(self.create_or_update_packages(languages_data)) + + for field in self.fields: + try: + setattr(instance, field, validated_data[field]) + except KeyError: + pass + instance.save() + + return instance + + def get_age(self, obj): + return obj.age + + def get_formatted_birth_date(self, obj): + return obj.formatted_birth_date + + def get_associated_class_name(self, obj): + return obj.associated_class.atmosphereName if obj.associated_class else None + +class RegistrationFormSerializer(serializers.ModelSerializer): + student = StudentSerializer(many=False, required=False) + registration_file = serializers.FileField(required=False) + status_label = serializers.SerializerMethodField() + formatted_last_update = serializers.SerializerMethodField() + + class Meta: + model = RegistrationForm + fields = '__all__' + + def create(self, validated_data): + student_data = validated_data.pop('student') + student = StudentSerializer.create(StudentSerializer(), student_data) + registrationForm = RegistrationForm.objects.create(student=student, **validated_data) + return registrationForm + + def update(self, instance, validated_data): + student_data = validated_data.pop('student', None) + if student_data: + student = instance.student + StudentSerializer.update(StudentSerializer(), student, student_data) + + for field in self.fields: + try: + setattr(instance, field, validated_data[field]) + except KeyError: + pass + instance.save() + + return instance + + def get_status_label(self, obj): + return obj.get_status_display() + + def get_formatted_last_update(self, obj): + utc_time = timezone.localtime(obj.last_update) # Convert to local time + local_tz = pytz.timezone(settings.TZ_APPLI) + local_time = utc_time.astimezone(local_tz) + + return local_time.strftime("%d-%m-%Y %H:%M") + +class StudentByParentSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + + class Meta: + model = Student + fields = ['id', 'last_name', 'first_name'] + + def __init__(self, *args, **kwargs): + super(StudentByParentSerializer, self).__init__(*args, **kwargs) + for field in self.fields: + self.fields[field].required = False + +class RegistrationFormByParentSerializer(serializers.ModelSerializer): + student = StudentByParentSerializer(many=False, required=True) + + class Meta: + model = RegistrationForm + fields = ['student', 'status'] + + def __init__(self, *args, **kwargs): + super(RegistrationFormByParentSerializer, self).__init__(*args, **kwargs) + for field in self.fields: + self.fields[field].required = False + +class GuardianByDICreationSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + + class Meta: + model = Guardian + fields = ['id', 'last_name', 'first_name', 'email', 'associated_profile'] + +class StudentByRFCreationSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + guardians = GuardianByDICreationSerializer(many=True, required=False) + + class Meta: + model = Student + fields = ['id', 'last_name', 'first_name', 'guardians'] + + def __init__(self, *args, **kwargs): + super(StudentByRFCreationSerializer, self).__init__(*args, **kwargs) + for field in self.fields: + self.fields[field].required = False + +class NotificationSerializer(serializers.ModelSerializer): + notification_type_label = serializers.ReadOnlyField() + + class Meta: + model = Notification + fields = '__all__' diff --git a/Back-End/GestionInscriptions/signals.py b/Back-End/Subscriptions/signals.py similarity index 71% rename from Back-End/GestionInscriptions/signals.py rename to Back-End/Subscriptions/signals.py index 909148f..0ce2f11 100644 --- a/Back-End/GestionInscriptions/signals.py +++ b/Back-End/Subscriptions/signals.py @@ -1,8 +1,8 @@ from django.db.models.signals import post_save, post_delete, m2m_changed from django.dispatch import receiver from django.core.cache import cache -from GestionInscriptions.models import FicheInscription, Eleve, Responsable -from GestionLogin.models import Profil +from .models import RegistrationForm, Student, Guardian +from Auth.models import Profile from N3wtSchool import settings from N3wtSchool.redis_client import redis_client import logging @@ -20,25 +20,25 @@ def clear_cache(): redis_client.delete(key) logger.debug(f'deleting : {key}') -@receiver(post_save, sender=FicheInscription) -@receiver(post_delete, sender=FicheInscription) +@receiver(post_save, sender=RegistrationForm) +@receiver(post_delete, sender=RegistrationForm) def clear_cache_after_change(sender, instance, **kwargs): clear_cache() -@receiver(m2m_changed, sender=Eleve.responsables.through) +@receiver(m2m_changed, sender=Student.guardians.through) def check_orphan_reponsables(sender, **kwargs): action = kwargs.pop('action', None) instance = kwargs.pop('instance', None) # pre_clear : lors de la suppression d'une FI (on fait un "clear" sur chaque relation) if action in ('post_remove', 'post_clear'): - if instance.responsables.all(): - Responsable.objects.filter(eleve=None).delete() + if instance.guardians.all(): + Guardian.objects.filter(eleve=None).delete() -@receiver(m2m_changed, sender=Eleve.profils.through) +@receiver(m2m_changed, sender=Student.profiles.through) def check_orphan_profils(sender, **kwargs): action = kwargs.pop('action', None) instance = kwargs.pop('instance', None) # pre_clear : lors de la suppression d'une FI (on fait un "clear" sur chaque relation) if action in ('post_remove', 'post_clear'): - if instance.profils.all(): - Profil.objects.filter(eleve=None).delete() + if instance.profiles.all(): + Profile.objects.filter(eleve=None).delete() diff --git a/Back-End/GestionInscriptions/tasks.py b/Back-End/Subscriptions/tasks.py similarity index 82% rename from Back-End/GestionInscriptions/tasks.py rename to Back-End/Subscriptions/tasks.py index b43ef68..e89d2b7 100644 --- a/Back-End/GestionInscriptions/tasks.py +++ b/Back-End/Subscriptions/tasks.py @@ -1,8 +1,8 @@ # tasks.py from celery import shared_task from django.utils import timezone -from GestionInscriptions.automate import Automate_DI_Inscription, updateStateMachine -from .models import FicheInscription +from Subscriptions.automate import Automate_RF_Register, updateStateMachine +from .models import RegistrationForm from GestionMessagerie.models import Messagerie from N3wtSchool import settings, bdd import requests @@ -15,7 +15,7 @@ def check_for_signature_deadlines(): deadline = now - timezone.timedelta(days=settings.EXPIRATION_DI_NB_DAYS) # deadline = now - timezone.timedelta(seconds=settings.EXPIRATION_DI_NB_DAYS) - dossiers_en_attente = FicheInscription.objects.filter(etat=FicheInscription.EtatDossierInscription.DI_ENVOYE, dateMAJ__lt=deadline) + dossiers_en_attente = RegistrationForm.objects.filter(etat=RegistrationForm.RegistrationFormStatus.DI_ENVOYE, dateMAJ__lt=deadline) for dossier in dossiers_en_attente: send_notification(dossier) @@ -28,7 +28,7 @@ def send_notification(dossier): url = settings.URL_DJANGO + 'GestionMessagerie/message' - destinataires = dossier.eleve.profils.all() + destinataires = dossier.eleve.profiles.all() for destinataire in destinataires: message = { "objet": "[RELANCE]", diff --git a/Back-End/GestionInscriptions/templates/GestionInscriptions/ajouterFicheEleve.html b/Back-End/Subscriptions/templates/GestionInscriptions/ajouterFicheEleve.html similarity index 96% rename from Back-End/GestionInscriptions/templates/GestionInscriptions/ajouterFicheEleve.html rename to Back-End/Subscriptions/templates/GestionInscriptions/ajouterFicheEleve.html index b2b5a97..1d12537 100644 --- a/Back-End/GestionInscriptions/templates/GestionInscriptions/ajouterFicheEleve.html +++ b/Back-End/Subscriptions/templates/GestionInscriptions/ajouterFicheEleve.html @@ -3,7 +3,7 @@ {% block content %}

Création d'une nouvelle fiche d'inscription


-
+ {% csrf_token %}
  • ELEVE
  • diff --git a/Back-End/GestionInscriptions/templates/GestionInscriptions/configure.html b/Back-End/Subscriptions/templates/GestionInscriptions/configure.html similarity index 87% rename from Back-End/GestionInscriptions/templates/GestionInscriptions/configure.html rename to Back-End/Subscriptions/templates/GestionInscriptions/configure.html index c719143..00b59c2 100644 --- a/Back-End/GestionInscriptions/templates/GestionInscriptions/configure.html +++ b/Back-End/Subscriptions/templates/GestionInscriptions/configure.html @@ -2,7 +2,7 @@ {% block content %}

    Configuration des dossiers d'inscriptions


    - + {% csrf_token %}
      diff --git a/Back-End/GestionInscriptions/templates/GestionInscriptions/creationDossier.html b/Back-End/Subscriptions/templates/GestionInscriptions/creationDossier.html similarity index 97% rename from Back-End/GestionInscriptions/templates/GestionInscriptions/creationDossier.html rename to Back-End/Subscriptions/templates/GestionInscriptions/creationDossier.html index 50f3656..860e379 100644 --- a/Back-End/GestionInscriptions/templates/GestionInscriptions/creationDossier.html +++ b/Back-End/Subscriptions/templates/GestionInscriptions/creationDossier.html @@ -2,9 +2,9 @@ {% block content %}

      Création du dossier d'inscription


      - + {% csrf_token %} - {% with responsable=eleve.getResponsablePrincipal %} + {% with responsable=eleve.getMainGuardian %}
      • ELEVE
      • diff --git a/Back-End/GestionInscriptions/templates/GestionInscriptions/editStudent.html b/Back-End/Subscriptions/templates/GestionInscriptions/editStudent.html similarity index 92% rename from Back-End/GestionInscriptions/templates/GestionInscriptions/editStudent.html rename to Back-End/Subscriptions/templates/GestionInscriptions/editStudent.html index f33652e..4196cc5 100644 --- a/Back-End/GestionInscriptions/templates/GestionInscriptions/editStudent.html +++ b/Back-End/Subscriptions/templates/GestionInscriptions/editStudent.html @@ -2,9 +2,9 @@ {% block content %}

        Edition d'une fiche d'inscription


        - + {% csrf_token %} - {% with responsable=eleve.getResponsablePrincipal %} + {% with responsable=eleve.getMainGuardian %}
          diff --git a/Back-End/GestionInscriptions/templates/GestionInscriptions/index.html b/Back-End/Subscriptions/templates/GestionInscriptions/index.html similarity index 88% rename from Back-End/GestionInscriptions/templates/GestionInscriptions/index.html rename to Back-End/Subscriptions/templates/GestionInscriptions/index.html index c428315..fcd5c8e 100644 --- a/Back-End/GestionInscriptions/templates/GestionInscriptions/index.html +++ b/Back-End/Subscriptions/templates/GestionInscriptions/index.html @@ -47,22 +47,22 @@ - {% for ficheInscription in ficheInscriptions_list %} - {% with eleve=ficheInscription.eleve %} - {% with responsable=eleve.getResponsablePrincipal %} - {% with fichiers=ficheInscription|recupereFichiersDossierInscription %} + {% for registerForm in ficheInscriptions_list %} + {% with eleve=registerForm.eleve %} + {% with responsable=eleve.getMainGuardian %} + {% with fichiers=registerForm|recupereFichiersDossierInscription %} {{ eleve.nom }} {{ eleve.prenom }} {{ responsable.mail }} {{ responsable.telephone }} - {{ ficheInscription.dateMAJ }} + {{ registerForm.dateMAJ }} - {% if ficheInscription.etat == 0 %} + {% if registerForm.etat == 0 %} Créé - {% elif ficheInscription.etat == 1 %} + {% elif registerForm.etat == 1 %} Envoyé - {% elif ficheInscription.etat == 2 %} + {% elif registerForm.etat == 2 %} En Validation {% else %} Validé @@ -74,7 +74,7 @@ {% endfor %} - + diff --git a/Back-End/GestionInscriptions/templates/base.html b/Back-End/Subscriptions/templates/base.html similarity index 100% rename from Back-End/GestionInscriptions/templates/base.html rename to Back-End/Subscriptions/templates/base.html diff --git a/Back-End/GestionInscriptions/templates/pdfs/dossier_inscription.html b/Back-End/Subscriptions/templates/pdfs/dossier_inscription.html similarity index 52% rename from Back-End/GestionInscriptions/templates/pdfs/dossier_inscription.html rename to Back-End/Subscriptions/templates/pdfs/dossier_inscription.html index b083a38..4170a06 100644 --- a/Back-End/GestionInscriptions/templates/pdfs/dossier_inscription.html +++ b/Back-End/Subscriptions/templates/pdfs/dossier_inscription.html @@ -45,49 +45,49 @@
          - Signé le : {{ dateSignature }}
          - A : {{ heureSignature }} + Signé le : {{ signatureDate }}
          + A : {{ signatureTime }}

          ELEVE

          - {% with niveau=eleve|recupereNiveauEleve %} - {% with genre=eleve|recupereGenreEleve %} - NOM : {{ eleve.nom }}
          - PRENOM : {{ eleve.prenom }}
          - ADRESSE : {{ eleve.adresse }}
          - GENRE : {{ genre }}
          - NE(E) LE : {{ eleve.dateNaissance }}
          - A : {{ eleve.lieuNaissance }} ({{ eleve.codePostalNaissance }})
          - NATIONALITE : {{ eleve.nationalite }}
          - NIVEAU : {{ niveau }}
          - MEDECIN TRAITANT : {{ eleve.medecinTraitant }}
          + {% with level=student|getStudentLevel %} + {% with gender=student|getStudentGender %} + NOM : {{ student.last_name }}
          + PRENOM : {{ student.first_name }}
          + ADRESSE : {{ student.address }}
          + GENRE : {{ gender }}
          + NE(E) LE : {{ student.birth_date }}
          + A : {{ student.birth_place }} ({{ student.birth_postal_code }})
          + NATIONALITE : {{ student.nationality }}
          + NIVEAU : {{ level }}
          + MEDECIN TRAITANT : {{ student.attending_physician }}
          {% endwith %} {% endwith %}

          RESPONSABLES

          - {% with responsables_List=eleve.getResponsables %} - {% with freres_List=eleve.getFreres %} - {% for responsable in responsables_List%} -

          Responsable {{ forloop.counter }}

          - NOM : {{ responsable.nom }}
          - PRENOM : {{ responsable.prenom }}
          - ADRESSE : {{ responsable.adresse }}
          - NE(E) LE : {{ responsable.dateNaissance }}
          - MAIL : {{ responsable.mail }}
          - TEL : {{ responsable.telephone }}
          - PROFESSION : {{ responsable.profession }}
          + {% with guardians=student.getGuardians %} + {% with siblings=student.getGuardians %} + {% for guardian in guardians%} +

          Guardian {{ forloop.counter }}

          + NOM : {{ guardian.last_name }}
          + PRENOM : {{ guardian.first_name }}
          + ADRESSE : {{ guardian.address }}
          + NE(E) LE : {{ guardian.birth_date }}
          + MAIL : {{ guardian.email }}
          + TEL : {{ guardian.phone }}
          + PROFESSION : {{ guardian.profession }}
          {% endfor %}

          FRATRIE

          - {% for frere in freres_List%} + {% for sibling in siblings%}

          Frère - Soeur {{ forloop.counter }}

          - NOM : {{ frere.nom }}
          - PRENOM : {{ frere.prenom }}
          - NE(E) LE : {{ frere.dateNaissance }}
          + NOM : {{ sibling.last_name }}
          + PRENOM : {{ sibling.first_name }}
          + NE(E) LE : {{ sibling.birth_date }}
          {% endfor %}

          MODALITES DE PAIEMENT

          - {% with modePaiement=eleve|recupereModePaiement %} - {{ modePaiement }}
          + {% with paymentMethod=student|getStudentPaymentMethod %} + {{ paymentMethod }}
          {% endwith %} {% endwith %} {% endwith %} diff --git a/Back-End/Subscriptions/templatetags/myTemplateTag.py b/Back-End/Subscriptions/templatetags/myTemplateTag.py new file mode 100644 index 0000000..67db926 --- /dev/null +++ b/Back-End/Subscriptions/templatetags/myTemplateTag.py @@ -0,0 +1,18 @@ +from Subscriptions.models import RegistrationForm, Student +from django import template +register = template.Library() + +@register.filter +def getStudentPaymentMethod(pk): + registerForm = RegistrationForm.objects.get(student=pk) + return Student.PaymentMethod(int(registerForm.student.payment_method)).label + +@register.filter +def getStudentLevel(pk): + registerForm = RegistrationForm.objects.get(student=pk) + return Student.StudentLevel(int(registerForm.student.level)).label + +@register.filter +def getStudentGender(pk): + registerForm = RegistrationForm.objects.get(student=pk) + return Student.StudentGender(int(registerForm.student.gender)).label \ No newline at end of file diff --git a/Back-End/Subscriptions/urls.py b/Back-End/Subscriptions/urls.py new file mode 100644 index 0000000..5e222a9 --- /dev/null +++ b/Back-End/Subscriptions/urls.py @@ -0,0 +1,36 @@ +from django.urls import path, re_path + +from . import views +from Subscriptions.views import RegisterFileTemplateView, RegisterFormListView, RegisterFormView, StudentView, GuardianView, ChildrenListView, StudentListView, RegisterFeeView + +urlpatterns = [ + re_path(r'^registerForms/([a-zA-z]+)$', RegisterFormListView.as_view(), name="listefichesInscriptions"), + re_path(r'^registerForm$', RegisterFormView.as_view(), name="registerForms"), + re_path(r'^registerForm/([0-9]+)$', RegisterFormView.as_view(), name="registerForms"), + + # Page de formulaire d'inscription - ELEVE + re_path(r'^student/([0-9]+)$', StudentView.as_view(), name="students"), + + # Page de formulaire d'inscription - RESPONSABLE + re_path(r'^fetchLastGuardian$', GuardianView.as_view(), name="fetchLastGuardian"), + + # Envoi d'un dossier d'inscription + re_path(r'^send/([0-9]+)$', views.send, name="send"), + + # Archivage d'un dossier d'inscription + re_path(r'^archive/([0-9]+)$', views.archive, name="archive"), + + # Envoi d'une relance de dossier d'inscription + re_path(r'^sendRelance/([0-9]+)$', views.relance, name="relance"), + + # Page PARENT - Liste des children + re_path(r'^children/([0-9]+)$', ChildrenListView.as_view(), name="children"), + + # Page INSCRIPTION - Liste des élèves + re_path(r'^students$', StudentListView.as_view(), name="students"), + + # Frais d'inscription + re_path(r'^registerFees$', RegisterFeeView.as_view(), name="registerFees"), + re_path(r'^registerFilesTemplates$', RegisterFileTemplateView.as_view(), name='registerFilesTemplates'), + re_path(r'^registerFilesTemplates/([0-9]+)$', RegisterFileTemplateView.as_view(), name="registerFilesTemplates"), +] \ No newline at end of file diff --git a/Back-End/Subscriptions/util.py b/Back-End/Subscriptions/util.py new file mode 100644 index 0000000..9ae17a9 --- /dev/null +++ b/Back-End/Subscriptions/util.py @@ -0,0 +1,82 @@ +from django.shortcuts import render,get_object_or_404,get_list_or_404 +from .models import RegistrationForm, Student, Guardian, Sibling +import time +from datetime import date, datetime, timedelta +from zoneinfo import ZoneInfo +from django.conf import settings +from N3wtSchool import renderers +from N3wtSchool import bdd + +from io import BytesIO +from django.core.files import File +from pathlib import Path +import os +from enum import Enum + +import random +import string +from rest_framework.parsers import JSONParser + +def recupereListeFichesInscription(): + context = { + "ficheInscriptions_list": bdd.getAllObjects(RegistrationForm), + } + return context + +def recupereListeFichesInscriptionEnAttenteSEPA(): + + ficheInscriptionsSEPA_list = RegistrationForm.objects.filter(modePaiement="Prélèvement SEPA").filter(etat=RegistrationForm.RegistrationFormStatus['SEPA_ENVOYE']) + return ficheInscriptionsSEPA_list + +def _now(): + return datetime.now(ZoneInfo(settings.TZ_APPLI)) + +def convertToStr(dateValue, dateFormat): + return dateValue.strftime(dateFormat) + +def convertToDate(date_time): + format = '%d-%m-%Y %H:%M' + datetime_str = datetime.strptime(date_time, format) + + return datetime_str + +def convertTelephone(telephoneValue, separator='-'): + return f"{telephoneValue[:2]}{separator}{telephoneValue[2:4]}{separator}{telephoneValue[4:6]}{separator}{telephoneValue[6:8]}{separator}{telephoneValue[8:10]}" + +def genereRandomCode(length): + return ''.join(random.choice(string.ascii_letters) for i in range(length)) + +def calculeDatePeremption(_start, nbDays): + return convertToStr(_start + timedelta(days=nbDays), settings.DATE_FORMAT) + +# Fonction permettant de retourner la valeur du QueryDict +# QueryDict [ index ] -> Dernière valeur d'une liste +# dict (QueryDict [ index ]) -> Toutes les valeurs de la liste +def _(liste): + return liste[0] + +def getArgFromRequest(_argument, _request): + resultat = None + data=JSONParser().parse(_request) + resultat = data[_argument] + return resultat + +def rfToPDF(registerForm): + # Ajout du fichier d'inscriptions + data = { + 'pdf_title': "Dossier d'inscription de %s"%registerForm.student.first_name, + 'signatureDate': convertToStr(_now(), '%d-%m-%Y'), + 'signatureTime': convertToStr(_now(), '%H:%M'), + 'student':registerForm.student, + } + + pdf = renderers.render_to_pdf('pdfs/dossier_inscription.html', data) + + PDFFileName = "Dossier_Inscription_%s_%s.pdf"%(registerForm.student.last_name, registerForm.student.first_name) + pathFichier = Path(settings.DOCUMENT_DIR + "/" + PDFFileName) + if os.path.exists(str(pathFichier)): + print(f'File exists : {str(pathFichier)}') + os.remove(str(pathFichier)) + + receipt_file = BytesIO(pdf.content) + registerForm.fichierInscription = File(receipt_file, PDFFileName) \ No newline at end of file diff --git a/Back-End/Subscriptions/views.py b/Back-End/Subscriptions/views.py new file mode 100644 index 0000000..0d5ec12 --- /dev/null +++ b/Back-End/Subscriptions/views.py @@ -0,0 +1,280 @@ +from django.http.response import JsonResponse +from django.contrib.auth import login, authenticate, get_user_model +from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect +from django.utils.decorators import method_decorator +from django.core.cache import cache +from django.core.paginator import Paginator +from django.core.files import File +from django.db.models import Q # Ajout de cet import +from rest_framework.parsers import JSONParser,MultiPartParser, FormParser +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework import status + +import json +from pathlib import Path +import os +from io import BytesIO + +import Subscriptions.mailManager as mailer +import Subscriptions.util as util +from Subscriptions.serializers import RegistrationFormSerializer, RegistrationFileSerializer, StudentSerializer, RegistrationFormByParentSerializer, StudentByRFCreationSerializer, RegistrationFeeSerializer +from Subscriptions.pagination import CustomPagination +from Subscriptions.signals import clear_cache +from .models import Student, Guardian, RegistrationForm, RegistrationFee, RegistrationFile + +from Subscriptions.automate import Automate_RF_Register, load_config, getStateMachineObjectState, updateStateMachine + +from Auth.models import Profile + +from N3wtSchool import settings, renderers, bdd + +class RegisterFormListView(APIView): + pagination_class = CustomPagination + + def get_register_form(self, _filter, search=None): + """ + Récupère les fiches d'inscriptions en fonction du filtre passé. + _filter: Filtre pour déterminer l'état des fiches ('pending', 'archived', 'subscribed') + search: Terme de recherche (optionnel) + """ + if _filter == 'pending': + exclude_states = [RegistrationForm.RegistrationFormStatus.RF_VALIDATED, RegistrationForm.RegistrationFormStatus.RF_ARCHIVED] + return bdd.searchObjects(RegistrationForm, search, _excludeStates=exclude_states) + elif _filter == 'archived': + return bdd.getObjects(RegistrationForm, 'status', RegistrationForm.RegistrationFormStatus.RF_ARCHIVED) + elif _filter == 'subscribed': + return bdd.getObjects(RegistrationForm, 'status', RegistrationForm.RegistrationFormStatus.RF_VALIDATED) + return None + + def get(self, request, _filter): + + # Récupération des paramètres + search = request.GET.get('search', '').strip() + page_size = request.GET.get('page_size', None) + + # Gestion du page_size + if page_size is not None: + try: + page_size = int(page_size) + except ValueError: + page_size = settings.NB_RESULT_PER_PAGE + + # Définir le cache_key en fonction du filtre + page_number = request.GET.get('page', 1) + cache_key = f'N3WT_ficheInscriptions_{_filter}_page_{page_number}_search_{search if _filter == "pending" else ""}' + cached_page = cache.get(cache_key) + if cached_page: + return JsonResponse(cached_page, safe=False) + + # Récupérer les fiches d'inscriptions en fonction du filtre + registerForms_List = self.get_register_form(_filter, search) + + if not registerForms_List: + return JsonResponse({'error' : 'aucune donnée trouvée', 'count' :0}, safe=False) + + # Pagination + paginator = self.pagination_class() + page = paginator.paginate_queryset(registerForms_List, request) + if page is not None: + registerForms_serializer = RegistrationFormSerializer(page, many=True) + response_data = paginator.get_paginated_response(registerForms_serializer.data) + cache.set(cache_key, response_data, timeout=60*15) + return JsonResponse(response_data, safe=False) + + return JsonResponse({'error' : 'aucune donnée trouvée', 'count' :0}, safe=False) + + def post(self, request): + studentFormList_serializer=JSONParser().parse(request) + for studentForm_data in studentFormList_serializer: + # Ajout de la date de mise à jour + studentForm_data["last_update"] = util.convertToStr(util._now(), '%d-%m-%Y %H:%M') + json.dumps(studentForm_data) + # Ajout du code d'inscription + code = util.genereRandomCode(12) + studentForm_data["codeLienInscription"] = code + studentForm_serializer = RegistrationFormSerializer(data=studentForm_data) + + if studentForm_serializer.is_valid(): + studentForm_serializer.save() + + return JsonResponse(studentForm_serializer.errors, safe=False) + + +@method_decorator(csrf_protect, name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') +class RegisterFormView(APIView): + pagination_class = CustomPagination + + def get(self, request, _id): + registerForm=bdd.getObject(RegistrationForm, "student__id", _id) + registerForm_serializer=RegistrationFormSerializer(registerForm) + return JsonResponse(registerForm_serializer.data, safe=False) + + def post(self, request): + studentForm_data=JSONParser().parse(request) + # Ajout de la date de mise à jour + studentForm_data["last_update"] = util.convertToStr(util._now(), '%d-%m-%Y %H:%M') + json.dumps(studentForm_data) + # Ajout du code d'inscription + code = util.genereRandomCode(12) + studentForm_data["codeLienInscription"] = code + + guardiansId = studentForm_data.pop('idGuardians', []) + studentForm_serializer = RegistrationFormSerializer(data=studentForm_data) + + if studentForm_serializer.is_valid(): + di = studentForm_serializer.save() + + # Mise à jour de l'automate + updateStateMachine(di, 'creationDI') + + # Récupération du reponsable associé + for guardianId in guardiansId: + guardian = Guardian.objects.get(id=guardianId) + di.student.guardians.add(guardian) + di.save() + + return JsonResponse(studentForm_serializer.data, safe=False) + + return JsonResponse(studentForm_serializer.errors, safe=False) + + def put(self, request, id): + studentForm_data=JSONParser().parse(request) + status = studentForm_data.pop('status', 0) + studentForm_data["last_update"] = str(util.convertToStr(util._now(), '%d-%m-%Y %H:%M')) + registerForm = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id) + + if status == RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW: + # Le parent a complété le dossier d'inscription, il est soumis à validation par l'école + json.dumps(studentForm_data) + util.rfToPDF(registerForm) + # Mise à jour de l'automate + updateStateMachine(registerForm, 'saisiDI') + elif status == RegistrationForm.RegistrationFormStatus.RF_VALIDATED: + # L'école a validé le dossier d'inscription + # Mise à jour de l'automate + updateStateMachine(registerForm, 'valideDI') + + + studentForm_serializer = RegistrationFormSerializer(registerForm, data=studentForm_data) + if studentForm_serializer.is_valid(): + studentForm_serializer.save() + return JsonResponse(studentForm_serializer.data, safe=False) + + return JsonResponse(studentForm_serializer.errors, safe=False) + + def delete(self, request, id): + register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id) + if register_form != None: + student = register_form.student + student.guardians.clear() + student.profiles.clear() + student.delete() + clear_cache() + + return JsonResponse("La suppression du dossier a été effectuée avec succès", safe=False) + + return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False) + +class StudentView(APIView): + def get(self, request, _id): + student = bdd.getObject(_objectName=Student, _columnName='id', _value=_id) + student_serializer = StudentSerializer(student) + return JsonResponse(student_serializer.data, safe=False) + +class GuardianView(APIView): + def get(self, request): + lastGuardian = bdd.getLastId(Guardian) + return JsonResponse({"lastid":lastGuardian}, safe=False) + +def send(request, id): + register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id) + if register_form != None: + student = register_form.student + guardian = student.getMainGuardian() + email = guardian.email + errorMessage = mailer.sendRegisterForm(email) + if errorMessage == '': + register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M') + # Mise à jour de l'automate + updateStateMachine(register_form, 'envoiDI') + + return JsonResponse({"errorMessage":errorMessage}, safe=False) + + return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False) + +def archive(request, id): + register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id) + if register_form != None: + register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M') + # Mise à jour de l'automate + updateStateMachine(register_form, 'archiveDI') + + return JsonResponse({"errorMessage":''}, safe=False) + + return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False) + +def relance(request, id): + register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id) + if register_form != None: + student = register_form.student + guardian = student.getMainGuardian() + email = guardian.email + errorMessage = mailer.envoieRelanceDossierInscription(email, register_form.codeLienInscription) + if errorMessage == '': + register_form.status=RegistrationForm.RegistrationFormStatus.RF_SENT + register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M') + register_form.save() + + return JsonResponse({"errorMessage":errorMessage}, safe=False) + + return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False) + +# API utilisée pour la vue parent +class ChildrenListView(APIView): + # Récupération des élèves d'un parent + # idProfile : identifiant du profil connecté rattaché aux fiches d'élèves + def get(self, request, _idProfile): + students = bdd.getObjects(_objectName=RegistrationForm, _columnName='student__guardians__profilAssocie__id', _value=_idProfile) + students_serializer = RegistrationFormByParentSerializer(students, many=True) + return JsonResponse(students_serializer.data, safe=False) + +# API utilisée pour la vue de création d'un DI +class StudentListView(APIView): + # Récupération de la liste des élèves inscrits ou en cours d'inscriptions + def get(self, request): + students = bdd.getAllObjects(_objectName=Student) + students_serializer = StudentByRFCreationSerializer(students, many=True) + return JsonResponse(students_serializer.data, safe=False) + +# API utilisée pour la vue de personnalisation des frais d'inscription pour la structure +class RegisterFeeView(APIView): + def get(self, request): + tarifs = bdd.getAllObjects(RegistrationFee) + tarifs_serializer = RegistrationFeeSerializer(tarifs, many=True) + return JsonResponse(tarifs_serializer.data, safe=False) + +class RegisterFileTemplateView(APIView): + parser_classes = (MultiPartParser, FormParser) + + def get(self, request): + fichiers = RegistrationFile.objects.all() + serializer = RegistrationFormSerializer(fichiers, many=True) + return Response(serializer.data) + + def post(self, request): + serializer = RegistrationFormSerializer(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) + + def delete(self, request, _id): + fichierInscription = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=_id) + if fichierInscription is not None: + fichierInscription.file.delete() # Supprimer le fichier uploadé + fichierInscription.delete() + return JsonResponse({'message': 'La suppression du fichier d\'inscription a été effectuée avec succès'}, safe=False) + else: + return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False) diff --git a/Back-End/start.py b/Back-End/start.py index 224d3f7..0b0aff0 100644 --- a/Back-End/start.py +++ b/Back-End/start.py @@ -13,11 +13,11 @@ def run_command(command): commands = [ ["python", "manage.py", "collectstatic", "--noinput"], ["python", "manage.py", "flush", "--noinput"], - ["python", "manage.py", "makemigrations", "GestionInscriptions"], + ["python", "manage.py", "makemigrations", "Subscriptions"], ["python", "manage.py", "makemigrations", "GestionNotification"], ["python", "manage.py", "makemigrations", "GestionMessagerie"], - ["python", "manage.py", "makemigrations", "GestionLogin"], - ["python", "manage.py", "makemigrations", "GestionEnseignants"], + ["python", "manage.py", "makemigrations", "Auth"], + ["python", "manage.py", "makemigrations", "School"], ["python", "manage.py", "migrate"] ]