chore: Initial Commit
feat: Gestion des inscriptions [#1] feat(frontend): Création des vues pour le paramétrage de l'école [#2] feat: Gestion du login [#6] fix: Correction lors de la migration des modèle [#8] feat: Révision du menu principal [#9] feat: Ajout d'un footer [#10] feat: Création des dockers compose pour les environnements de développement et de production [#12] doc(ci): Mise en place de Husky et d'un suivi de version automatique [#14]
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Back-End/*/Configuration/application.json
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
node_modules/
|
||||||
|
Back-End/*/migrations/*
|
||||||
|
Back-End/documents
|
||||||
|
Back-End/*.dmp
|
||||||
|
Back-End/staticfiles
|
||||||
|
hardcoded-strings-report.md
|
||||||
1
.husky/commit-msg
Normal file
@ -0,0 +1 @@
|
|||||||
|
npx --no -- commitlint --edit $1
|
||||||
0
.husky/pre-commit
Normal file
4
.husky/prepare-commit-msg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
node scripts/prepare-commit-msg.js "$1" "$2"
|
||||||
27
.versionrc
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"header": "# Changelog\n\nToutes les modifications notables apportées à ce projet seront documentées dans ce fichier.\n",
|
||||||
|
"tagPrefix": "",
|
||||||
|
"bumpFiles": [
|
||||||
|
{
|
||||||
|
"filename": "package.json",
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "Front-End/package.json",
|
||||||
|
"updater": "scripts/update-version.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "Back-End/__version__.py",
|
||||||
|
"updater": "scripts/update-version.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
{ "type": "feat", "section": "Nouvelles fonctionnalités", "hidden": false },
|
||||||
|
{ "type": "fix", "section": "Corrections de bugs", "hidden": false },
|
||||||
|
{ "type": "docs", "section": "Documentation", "hidden": false },
|
||||||
|
{ "type": "style", "section": "Mises en forme", "hidden": true },
|
||||||
|
{ "type": "refactor", "section": "Refactorisations", "hidden": false },
|
||||||
|
{ "type": "test", "section": "Tests", "hidden": true },
|
||||||
|
{ "type": "chore", "section": "Tâches diverses", "hidden": true }
|
||||||
|
]
|
||||||
|
}
|
||||||
20
Back-End/Dockerfile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Dockerfile
|
||||||
|
|
||||||
|
# The first instruction is what image we want to base our container on
|
||||||
|
# We Use an official Python runtime as a parent image
|
||||||
|
FROM python:3.12.7
|
||||||
|
|
||||||
|
# Allows docker to cache installed dependencies between builds
|
||||||
|
COPY requirements.txt requirements.txt
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Mounts the application code to the image
|
||||||
|
COPY . .
|
||||||
|
WORKDIR /Back-End
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENV DJANGO_SETTINGS_MODULE N3wtSchool.settings
|
||||||
|
ENV DJANGO_SUPERUSER_PASSWORD=admin
|
||||||
|
ENV DJANGO_SUPERUSER_USERNAME=admin
|
||||||
|
ENV DJANGO_SUPERUSER_EMAIL=admin@n3wtschool.com
|
||||||
1
Back-End/GestionEnseignants/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'GestionEnseignants.apps.GestionenseignantsConfig'
|
||||||
3
Back-End/GestionEnseignants/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
Back-End/GestionEnseignants/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class GestionenseignantsConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'GestionEnseignants'
|
||||||
32
Back-End/GestionEnseignants/models.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
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)
|
||||||
|
specialite = models.ForeignKey(Specialite, on_delete=models.SET_NULL, null=True, blank=True, related_name='enseignants')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.nom} {self.prenom}"
|
||||||
|
|
||||||
|
class Classe(models.Model):
|
||||||
|
nom_ambiance = models.CharField(max_length=255)
|
||||||
|
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)
|
||||||
|
specialites = models.ManyToManyField(Specialite, related_name='classes')
|
||||||
|
enseignant_principal = models.ForeignKey(Enseignant, on_delete=models.SET_NULL, null=True, blank=True, related_name='classes_principal')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.nom_ambiance
|
||||||
|
|
||||||
83
Back-End/GestionEnseignants/serializers.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from .models import Enseignant, Specialite, Classe
|
||||||
|
from N3wtSchool import settings
|
||||||
|
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 ClasseSerializer(serializers.ModelSerializer):
|
||||||
|
specialites = SpecialiteSerializer(many=True, read_only=True)
|
||||||
|
specialites_ids = serializers.PrimaryKeyRelatedField(queryset=Specialite.objects.all(), many=True, source='specialites')
|
||||||
|
dateCreation_formattee = serializers.SerializerMethodField()
|
||||||
|
enseignant_principal = serializers.SerializerMethodField()
|
||||||
|
enseignant_principal_id = serializers.PrimaryKeyRelatedField(queryset=Enseignant.objects.all(), source='enseignant_principal', write_only=False, read_only=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Classe
|
||||||
|
fields = ['id', 'nom_ambiance', 'tranche_age', 'nombre_eleves', 'langue_enseignement', 'specialites', 'specialites_ids', 'enseignant_principal', 'enseignant_principal_id', 'annee_scolaire', 'dateCreation', 'dateCreation_formattee']
|
||||||
|
|
||||||
|
def get_enseignant_principal(self, obj):
|
||||||
|
from .serializers import EnseignantDetailSerializer
|
||||||
|
if obj.enseignant_principal:
|
||||||
|
return EnseignantDetailSerializer(obj.enseignant_principal).data
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
specialites_data = validated_data.pop('specialites', [])
|
||||||
|
classe = Classe.objects.create(**validated_data)
|
||||||
|
classe.specialites.set(specialites_data)
|
||||||
|
return classe
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
specialites_data = validated_data.pop('specialites', [])
|
||||||
|
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.enseignant_principal = validated_data.get('enseignant_principal', instance.enseignant_principal)
|
||||||
|
instance.save()
|
||||||
|
instance.specialites.set(specialites_data)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
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 EnseignantSerializer(serializers.ModelSerializer):
|
||||||
|
specialite = SpecialiteSerializer(read_only=True)
|
||||||
|
specialite_id = serializers.PrimaryKeyRelatedField(queryset=Specialite.objects.all(), source='specialite', write_only=False, read_only=False)
|
||||||
|
classes_principal = ClasseSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Enseignant
|
||||||
|
fields = ['id', 'nom', 'prenom', 'mail', 'specialite', 'specialite_id', 'classes_principal']
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
specialite = validated_data.pop('specialite', None)
|
||||||
|
enseignant = Enseignant.objects.create(**validated_data)
|
||||||
|
enseignant.specialite = specialite
|
||||||
|
enseignant.save()
|
||||||
|
return enseignant
|
||||||
|
|
||||||
|
class EnseignantDetailSerializer(serializers.ModelSerializer):
|
||||||
|
specialite = SpecialiteSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Enseignant
|
||||||
|
fields = ['id', 'nom', 'prenom', 'mail', 'specialite']
|
||||||
3
Back-End/GestionEnseignants/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
17
Back-End/GestionEnseignants/urls.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from django.urls import path, re_path
|
||||||
|
|
||||||
|
from GestionEnseignants.views import EnseignantsView, EnseignantView, SpecialitesView, SpecialiteView, ClassesView, ClasseView
|
||||||
|
|
||||||
|
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"),
|
||||||
|
]
|
||||||
180
Back-End/GestionEnseignants/views.py
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
from django.http.response import JsonResponse
|
||||||
|
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from rest_framework.parsers import JSONParser
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from django.core.cache import cache
|
||||||
|
from .models import Enseignant, Specialite, Classe
|
||||||
|
from .serializers import EnseignantSerializer, SpecialiteSerializer, ClasseSerializer
|
||||||
|
from N3wtSchool import bdd
|
||||||
|
|
||||||
|
class EnseignantsView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
enseignantsList=bdd.getAllObjects(Enseignant)
|
||||||
|
enseignants_serializer=EnseignantSerializer(enseignantsList, many=True)
|
||||||
|
|
||||||
|
return JsonResponse(enseignants_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)
|
||||||
|
|
||||||
|
return JsonResponse(enseignant_serializer.data, safe=False)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
enseignant_data=JSONParser().parse(request)
|
||||||
|
enseignant_serializer = EnseignantSerializer(data=enseignant_data)
|
||||||
|
|
||||||
|
if enseignant_serializer.is_valid():
|
||||||
|
enseignant_serializer.save()
|
||||||
|
|
||||||
|
return JsonResponse(enseignant_serializer.data, safe=False)
|
||||||
|
|
||||||
|
return JsonResponse(enseignant_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)
|
||||||
|
|
||||||
|
return JsonResponse(enseignant_serializer.errors, safe=False)
|
||||||
|
|
||||||
|
def delete(self, request, _id):
|
||||||
|
enseignant = bdd.getObject(_objectName=Enseignant, _columnName='id', _value=_id)
|
||||||
|
if enseignant != None:
|
||||||
|
enseignant.delete()
|
||||||
|
|
||||||
|
return JsonResponse("La suppression de la spécialité a été effectuée avec succès", safe=False)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class SpecialitesView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
specialitesList=bdd.getAllObjects(Specialite)
|
||||||
|
specialites_serializer=SpecialiteSerializer(specialitesList, many=True)
|
||||||
|
|
||||||
|
return JsonResponse(specialites_serializer.data, safe=False)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
specialites_data=JSONParser().parse(request)
|
||||||
|
all_valid = True
|
||||||
|
for specialite_data in specialites_data:
|
||||||
|
specialite_serializer = SpecialiteSerializer(data=specialite_data)
|
||||||
|
|
||||||
|
if specialite_serializer.is_valid():
|
||||||
|
specialite_serializer.save()
|
||||||
|
else:
|
||||||
|
all_valid = False
|
||||||
|
break
|
||||||
|
if all_valid:
|
||||||
|
specialitesList = bdd.getAllObjects(Specialite)
|
||||||
|
specialites_serializer = SpecialiteSerializer(specialitesList, many=True)
|
||||||
|
|
||||||
|
return JsonResponse(specialite_serializer.errors, safe=False)
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class SpecialiteView(APIView):
|
||||||
|
def get (self, request, _id):
|
||||||
|
specialite = bdd.getObject(_objectName=Specialite, _columnName='id', _value=_id)
|
||||||
|
specialite_serializer=SpecialiteSerializer(specialite)
|
||||||
|
|
||||||
|
return JsonResponse(specialite_serializer.data, safe=False)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
specialite_data=JSONParser().parse(request)
|
||||||
|
specialite_serializer = SpecialiteSerializer(data=specialite_data)
|
||||||
|
|
||||||
|
if specialite_serializer.is_valid():
|
||||||
|
specialite_serializer.save()
|
||||||
|
return JsonResponse(specialite_serializer.data, safe=False)
|
||||||
|
|
||||||
|
return JsonResponse(specialite_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)
|
||||||
|
|
||||||
|
return JsonResponse(specialite_serializer.errors, safe=False)
|
||||||
|
|
||||||
|
def delete(self, request, _id):
|
||||||
|
specialite = bdd.getObject(_objectName=Specialite, _columnName='id', _value=_id)
|
||||||
|
if specialite != None:
|
||||||
|
specialite.delete()
|
||||||
|
|
||||||
|
return JsonResponse("La suppression de la spécialité a été effectuée avec succès", safe=False)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class ClassesView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
classesList=bdd.getAllObjects(Classe)
|
||||||
|
classes_serializer=ClasseSerializer(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)
|
||||||
|
|
||||||
|
if classe_serializer.is_valid():
|
||||||
|
classe_serializer.save()
|
||||||
|
else:
|
||||||
|
all_valid = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if all_valid:
|
||||||
|
classesList = bdd.getAllObjects(Classe)
|
||||||
|
classes_serializer = ClasseSerializer(classesList, many=True)
|
||||||
|
|
||||||
|
return JsonResponse(classes_serializer.data, safe=False)
|
||||||
|
|
||||||
|
return JsonResponse(classe_serializer.errors, safe=False)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@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)
|
||||||
|
|
||||||
|
return JsonResponse(classe_serializer.data, safe=False)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
classe_data=JSONParser().parse(request)
|
||||||
|
classe_serializer = ClasseSerializer(data=classe_data)
|
||||||
|
|
||||||
|
if classe_serializer.is_valid():
|
||||||
|
classe_serializer.save()
|
||||||
|
return JsonResponse(classe_serializer.data, safe=False)
|
||||||
|
|
||||||
|
return JsonResponse(classe_serializer.errors, safe=False)
|
||||||
|
|
||||||
|
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)
|
||||||
|
if classe_serializer.is_valid():
|
||||||
|
classe_serializer.save()
|
||||||
|
return JsonResponse(classe_serializer.data, safe=False)
|
||||||
|
|
||||||
|
return JsonResponse(classe_serializer.errors, safe=False)
|
||||||
|
|
||||||
|
def delete(self, request, _id):
|
||||||
|
classe = bdd.getObject(_objectName=Classe, _columnName='id', _value=_id)
|
||||||
|
if classe != None:
|
||||||
|
classe.delete()
|
||||||
|
|
||||||
|
return JsonResponse("La suppression de la classe a été effectuée avec succès", safe=False)
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"mailFrom":"",
|
||||||
|
"password":""
|
||||||
|
}
|
||||||
63
Back-End/GestionInscriptions/Configuration/automate.json
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"states": [
|
||||||
|
"ABSENT",
|
||||||
|
"CREE",
|
||||||
|
"ENVOYE",
|
||||||
|
"EN_VALIDATION",
|
||||||
|
"A_RELANCER",
|
||||||
|
"VALIDE",
|
||||||
|
"ARCHIVE"
|
||||||
|
],
|
||||||
|
"transitions": [
|
||||||
|
{
|
||||||
|
"name": "creationDI",
|
||||||
|
"from": "ABSENT",
|
||||||
|
"to": "CREE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "envoiDI",
|
||||||
|
"from": "CREE",
|
||||||
|
"to": "ENVOYE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "archiveDI",
|
||||||
|
"from": "CREE",
|
||||||
|
"to": "ARCHIVE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "saisiDI",
|
||||||
|
"from": "ENVOYE",
|
||||||
|
"to": "EN_VALIDATION"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "relanceDI",
|
||||||
|
"from": "ENVOYE",
|
||||||
|
"to": "A_RELANCER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "archiveDI",
|
||||||
|
"from": "A_RELANCER",
|
||||||
|
"to": "ARCHIVE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "archiveDI",
|
||||||
|
"from": "ENVOYE",
|
||||||
|
"to": "ARCHIVE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "valideDI",
|
||||||
|
"from": "EN_VALIDATION",
|
||||||
|
"to": "VALIDE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "archiveDI",
|
||||||
|
"from": "EN_VALIDATION",
|
||||||
|
"to": "ARCHIVE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "archiveDI",
|
||||||
|
"from": "VALIDE",
|
||||||
|
"to": "ARCHIVE"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
Back-End/GestionInscriptions/Configuration/inscriptions.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"activationMailRelance": "Oui",
|
||||||
|
"delaiRelance": "30",
|
||||||
|
"ambiances": [
|
||||||
|
"2-3 ans",
|
||||||
|
"3-6 ans",
|
||||||
|
"6-12 ans"
|
||||||
|
],
|
||||||
|
"genres": [
|
||||||
|
"Fille",
|
||||||
|
"Garçon"
|
||||||
|
],
|
||||||
|
"modesPaiement": [
|
||||||
|
"Chèque",
|
||||||
|
"Virement",
|
||||||
|
"Prélèvement SEPA"
|
||||||
|
]
|
||||||
|
}
|
||||||
1
Back-End/GestionInscriptions/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'GestionInscriptions.apps.GestionInscriptionsConfig'
|
||||||
11
Back-End/GestionInscriptions/admin.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import *
|
||||||
|
|
||||||
|
admin.site.register(Eleve)
|
||||||
|
admin.site.register(Responsable)
|
||||||
|
|
||||||
|
class EleveAdmin(admin.ModelAdmin):
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
obj.user = request.user
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
10
Back-End/GestionInscriptions/apps.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
class GestioninscriptionsConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'GestionInscriptions'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from GestionInscriptions.signals import clear_cache
|
||||||
|
clear_cache()
|
||||||
45
Back-End/GestionInscriptions/automate.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# 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
|
||||||
74
Back-End/GestionInscriptions/mailManager.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
from django.core.mail import send_mail
|
||||||
|
import re
|
||||||
|
from N3wtSchool import settings
|
||||||
|
|
||||||
|
def envoieReinitMotDePasse(recipients, code):
|
||||||
|
send_mail(
|
||||||
|
settings.EMAIL_REINIT_SUBJECT,
|
||||||
|
settings.EMAIL_REINIT_CORPUS%(str(code)),
|
||||||
|
settings.EMAIL_HOST_USER,
|
||||||
|
[recipients],
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def envoieDossierInscription(recipients):
|
||||||
|
errorMessage = ''
|
||||||
|
try:
|
||||||
|
print(f'{settings.EMAIL_HOST_USER}')
|
||||||
|
send_mail(
|
||||||
|
settings.EMAIL_INSCRIPTION_SUBJECT,
|
||||||
|
settings.EMAIL_INSCRIPTION_CORPUS%[recipients],
|
||||||
|
settings.EMAIL_HOST_USER,
|
||||||
|
[recipients],
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
errorMessage = str(e)
|
||||||
|
|
||||||
|
return errorMessage
|
||||||
|
|
||||||
|
def envoieRelanceDossierInscription(recipients, code):
|
||||||
|
errorMessage = ''
|
||||||
|
try:
|
||||||
|
send_mail(
|
||||||
|
settings.EMAIL_RELANCE_SUBJECT,
|
||||||
|
settings.EMAIL_RELANCE_CORPUS%str(code),
|
||||||
|
settings.EMAIL_HOST_USER,
|
||||||
|
[recipients],
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
errorMessage = str(e)
|
||||||
|
|
||||||
|
return errorMessage
|
||||||
|
|
||||||
|
|
||||||
|
def envoieSEPA(recipients, ref):
|
||||||
|
send_mail(
|
||||||
|
settings.EMAIL_SEPA_SUBJECT%str(ref),
|
||||||
|
settings.EMAIL_SEPA_CORPUS,
|
||||||
|
settings.EMAIL_HOST_USER,
|
||||||
|
[recipients],
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def isValid(message, fiche_inscription):
|
||||||
|
# Est-ce que la référence du dossier est VALIDE
|
||||||
|
subject = message.subject
|
||||||
|
print ("++++ " + subject)
|
||||||
|
responsableMail = message.from_header
|
||||||
|
result = re.search('<(.*)>', responsableMail)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
responsableMail = result.group(1)
|
||||||
|
|
||||||
|
result = re.search(r'.*\[Ref(.*)\].*', subject)
|
||||||
|
idMail = -1
|
||||||
|
if result:
|
||||||
|
idMail = result.group(1).strip()
|
||||||
|
|
||||||
|
eleve = fiche_inscription.eleve
|
||||||
|
responsable = eleve.getResponsablePrincipal()
|
||||||
|
mailReponsableAVerifier = responsable.mail
|
||||||
|
|
||||||
|
return responsableMail == mailReponsableAVerifier and str(idMail) == str(fiche_inscription.eleve.id)
|
||||||
123
Back-End/GestionInscriptions/models.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
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.CharField(max_length=200, default="", 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)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
20
Back-End/GestionInscriptions/pagination.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from rest_framework.pagination import PageNumberPagination
|
||||||
|
|
||||||
|
from N3wtSchool import settings
|
||||||
|
|
||||||
|
class CustomPagination(PageNumberPagination):
|
||||||
|
page_size_query_param = 'page_size'
|
||||||
|
max_page_size = settings.NB_MAX_PAGE
|
||||||
|
page_size = settings.NB_RESULT_PER_PAGE
|
||||||
|
|
||||||
|
def get_paginated_response(self, data):
|
||||||
|
return ({
|
||||||
|
'links': {
|
||||||
|
'next': self.get_next_link(),
|
||||||
|
'previous': self.get_previous_link()
|
||||||
|
},
|
||||||
|
'count': self.page.paginator.count,
|
||||||
|
'page_size': self.page_size,
|
||||||
|
'max_page_size' : self.max_page_size,
|
||||||
|
'fichesInscriptions': data }
|
||||||
|
)
|
||||||
176
Back-End/GestionInscriptions/serializers.py
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from GestionInscriptions.models import FicheInscription, Eleve, Responsable, Frere, Langue
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
|
||||||
|
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__'
|
||||||
|
|
||||||
43
Back-End/GestionInscriptions/signals.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
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 N3wtSchool import settings
|
||||||
|
from N3wtSchool.redis_client import redis_client
|
||||||
|
|
||||||
|
def clear_cache():
|
||||||
|
# Préfixes des clés à supprimer
|
||||||
|
prefixes = ['N3WT_']
|
||||||
|
|
||||||
|
for prefix in prefixes:
|
||||||
|
# Utiliser le motif pour obtenir les clés correspondant au préfixe
|
||||||
|
pattern = f'*{prefix}*'
|
||||||
|
print(f'pattern : {pattern}')
|
||||||
|
for key in redis_client.scan_iter(pattern):
|
||||||
|
redis_client.delete(key)
|
||||||
|
print(f'deleting : {key}')
|
||||||
|
|
||||||
|
@receiver(post_save, sender=FicheInscription)
|
||||||
|
@receiver(post_delete, sender=FicheInscription)
|
||||||
|
def clear_cache_after_change(sender, instance, **kwargs):
|
||||||
|
clear_cache()
|
||||||
|
|
||||||
|
@receiver(m2m_changed, sender=Eleve.responsables.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()
|
||||||
|
|
||||||
|
@receiver(m2m_changed, sender=Eleve.profils.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()
|
||||||
44
Back-End/GestionInscriptions/tasks.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# 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 GestionMessagerie.models import Messagerie
|
||||||
|
from N3wtSchool import settings, bdd
|
||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def check_for_signature_deadlines():
|
||||||
|
now = timezone.now()
|
||||||
|
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)
|
||||||
|
|
||||||
|
for dossier in dossiers_en_attente:
|
||||||
|
send_notification(dossier)
|
||||||
|
|
||||||
|
def send_notification(dossier):
|
||||||
|
print(f'Dossier en attente.... {dossier} - Positionnement à l\'état A_RELANCER')
|
||||||
|
|
||||||
|
# Changer l'état de l'automate
|
||||||
|
updateStateMachine(dossier, 'relanceDI')
|
||||||
|
|
||||||
|
url = settings.URL_DJANGO + 'GestionMessagerie/message'
|
||||||
|
|
||||||
|
destinataires = dossier.eleve.profils.all()
|
||||||
|
for destinataire in destinataires:
|
||||||
|
message = {
|
||||||
|
"objet": "[RELANCE]",
|
||||||
|
"destinataire" : destinataire.id,
|
||||||
|
"corpus": "RELANCE pour le dossier d'inscription"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(url, json=message)
|
||||||
|
|
||||||
|
# subject = f"Dossier d'inscription non signé - {dossier.objet}"
|
||||||
|
# message = f"Le dossier d'inscription avec l'objet '{dossier.objet}' n'a pas été signé depuis {dossier.created_at}."
|
||||||
|
# send_mail(subject, message, settings.EMAIL_HOST_USER, [dossier.destinataire.email])
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load rest_framework %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Création d'une nouvelle fiche d'inscription</h1>
|
||||||
|
<br>
|
||||||
|
<form action='{% url 'GestionInscriptions:nouvelEleve' %}' method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<ul>
|
||||||
|
<li style="margin-bottom: 15px"><strong>ELEVE</strong></li>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="nomEleve">Nom</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<input type="text" id="nomEleve" name="nomEleve">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="prenomEleve">Prénom</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<input type="text" id="prenomEleve" name="prenomEleve">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<li style="margin-bottom: 15px"><strong>LISTE DES CONTACTS</strong></li>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="mail">Adresse e-mail </label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<input type="text" id="mail" name="mailResponsable">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="telephone">Numéro de téléphone</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<input type="text" id="telephone" name="telephoneResponsable">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="nomContact">Nom</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<input type="text" id="nomContact" name="nomResponsable">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="prenomContact">Prénom</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<input type="text" id="prenomContact" name="prenomResponsable">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
<input class="btn primary" type="submit" value="Créer" name="valider">
|
||||||
|
<br>
|
||||||
|
<input class="btn" type="button" value="Annuler" name="cancel">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Configuration des dossiers d'inscriptions</h1>
|
||||||
|
<br>
|
||||||
|
<form action='{% url 'GestionInscriptions:index' %}' method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<ul>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Relance automatique :</label>
|
||||||
|
<input type="text" name="delaiRelance" value="{{ delaiRelance }}">
|
||||||
|
<label>secondes</label>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
<input class="btn" type="submit" value="Configurer" name="valider">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,162 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Création du dossier d'inscription</h1>
|
||||||
|
<br>
|
||||||
|
<form action='{% url 'GestionInscriptions:validate' eleve.id %}' method="post" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% with responsable=eleve.getResponsablePrincipal %}
|
||||||
|
<ul>
|
||||||
|
<li style="margin-bottom: 15px"><strong>ELEVE</strong></li>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Nom :</label>
|
||||||
|
<input type="text" name="nomEleve" value="{{ eleve.nom }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Prénom :</label>
|
||||||
|
<input type="text" name="prenomEleve" value="{{ eleve.prenom }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Genre :<br></label>
|
||||||
|
{% for genre in genres %}
|
||||||
|
<label>{{ genre }}</label>
|
||||||
|
<input type="radio" id="{{ genre }}" name="genre" value="{{ genre }}">
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Adresse :</label>
|
||||||
|
<input type="text" name="adresseEleve">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Date de naissance :</label>
|
||||||
|
<input type="text" name="dateNaissanceEleve">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Lieu de naissance :</label>
|
||||||
|
<input type="text" name="lieuNaissanceEleve">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Code postal de naissance :</label>
|
||||||
|
<input type="text" name="codePostalNaissanceEleve">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Nationalité :</label>
|
||||||
|
<input type="text" name="nationaliteEleve">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Langue parlée :</label>
|
||||||
|
<input type="text" name="langueEleve">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Ambiance :<br></label>
|
||||||
|
{% for ambiance in ambiances %}
|
||||||
|
<label>{{ ambiance }}</label>
|
||||||
|
<input type="radio" id="{{ ambiance }}" name="ambiance" value="{{ ambiance }}">
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Médecin traitant :</label>
|
||||||
|
<input type="text" name="medecinTraitantEleve">
|
||||||
|
</div>
|
||||||
|
<li style="margin-bottom: 15px"><strong>RESPONSABLES</strong></li>
|
||||||
|
<ul>
|
||||||
|
<li style="margin-bottom: 15px"><strong>RESPONSABLE 1</strong></li>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Nom :</label>
|
||||||
|
<input type="text" name="nomResponsable1" value="{{ responsable.nom }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Prénom :</label>
|
||||||
|
<input type="text" name="prenomResponsable1" value="{{ responsable.prenom }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Adresse :</label>
|
||||||
|
<input type="text" name="adresseResponsable1" value="{{ responsable.adresse }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Date de naissance :</label>
|
||||||
|
<input type="text" name="dateNaissanceResponsable1" value="{{ responsable.dateNaissance }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Mail :</label>
|
||||||
|
<input type="text" name="mailResponsable1" value="{{ responsable.mail }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Téléphone :</label>
|
||||||
|
<input type="text" name="telephoneResponsable1" value="{{ responsable.telephone }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Profession :</label>
|
||||||
|
<input type="text" name="professionResponsable1" value="{{ responsable.profession }}">
|
||||||
|
</div>
|
||||||
|
<li style="margin-bottom: 15px"><strong>RESPONSABLE 2</strong></li>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Nom :</label>
|
||||||
|
<input type="text" name="nomResponsable2">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Prénom :</label>
|
||||||
|
<input type="text" name="prenomResponsable2">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Adresse :</label>
|
||||||
|
<input type="text" name="adresseResponsable2">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Date de naissance :</label>
|
||||||
|
<input type="text" name="dateNaissanceResponsable2">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Mail :</label>
|
||||||
|
<input type="text" name="mailResponsable2">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Téléphone :</label>
|
||||||
|
<input type="text" name="telephoneResponsable2">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Profession :</label>
|
||||||
|
<input type="text" name="professionResponsable2">
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
<li style="margin-bottom: 15px"><strong>FRATRIE</strong></li>
|
||||||
|
<ul>
|
||||||
|
<li style="margin-bottom: 15px"><strong>FRERE - SOEUR 1 :</strong></li>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Nom :</label>
|
||||||
|
<input type="text" name="nomFrere1">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Prénom :</label>
|
||||||
|
<input type="text" name="prenomFrere1">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Date de naissance :</label>
|
||||||
|
<input type="text" name="dateNaissanceFrere1">
|
||||||
|
</div>
|
||||||
|
<li style="margin-bottom: 15px"><strong>FRERE - SOEUR 2 :</strong></li>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Nom :</label>
|
||||||
|
<input type="text" name="nomFrere2">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Prénom :</label>
|
||||||
|
<input type="text" name="prenomFrere2">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Date de naissance :</label>
|
||||||
|
<input type="text" name="dateNaissanceFrere2">
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
<li style="margin-bottom: 15px"><strong>PAIEMENT</strong></li>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Mode Paiement :<br></label>
|
||||||
|
{% for modePaiement in modesPaiement %}
|
||||||
|
<label>{{ modePaiement }}</label>
|
||||||
|
<input type="radio" id="{{ modePaiement }}" name="modePaiement" value="{{ modePaiement }}">
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
<input class="btn" type="submit" value="Valider" name="valider">
|
||||||
|
{% endwith %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Edition d'une fiche d'inscription</h1>
|
||||||
|
<br>
|
||||||
|
<form action='{% url 'GestionInscriptions:index' %}' method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% with responsable=eleve.getResponsablePrincipal %}
|
||||||
|
<ul>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<input type="hidden" name="fiche_id" value="{{ eleve.id }}">
|
||||||
|
</div>
|
||||||
|
<li style="margin-bottom: 15px"><strong>ELEVE</strong></li>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Nom :</label>
|
||||||
|
<input type="text" name="nomEleve" value="{{ eleve.nom }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Prénom :</label>
|
||||||
|
<input type="text" name="prenomEleve" value="{{ eleve.prenom }}">
|
||||||
|
</div>
|
||||||
|
<li style="margin-bottom: 15px"><strong>LISTE DES CONTACTS</strong></li>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Adresse e-mail :</label>
|
||||||
|
<input type="text" name="mail" value="{{ responsable.mail }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Numéro de téléphone :</label>
|
||||||
|
<input type="text" name="telephone" value="{{ responsable.telephone }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Nom :</label>
|
||||||
|
<input type="text" name="nomContact" value="{{ responsable.nom }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 15px">
|
||||||
|
<label>Prénom :</label>
|
||||||
|
<input type="text" name="prenomContact" value="{{ responsable.prenom }}">
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
<input class="btn" type="submit" value="Modifier" name="valider">
|
||||||
|
{% endwith %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,126 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load myTemplateTag %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Inscriptions 2024/2025</h1>
|
||||||
|
<br>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="heading-section">
|
||||||
|
<!-- Search bar -->
|
||||||
|
<div class="input-group max-80">
|
||||||
|
<div class="input-wrapper max">
|
||||||
|
<span class="icon-ctn">
|
||||||
|
<i class="icon user-search"></i>
|
||||||
|
</span>
|
||||||
|
<input type="text" id="username" placeholder="Rechercher">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a class="btn primary" href="nouvelEleve">
|
||||||
|
Ajouter <i class="icon profile-add"></i>
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
<section class="heading-section">
|
||||||
|
<div class="alphabet-filter">
|
||||||
|
{% for letter in "*ABCDEFGHIJKLMNOPQRSTUVWXYZ" %}
|
||||||
|
<a class="item" href="?letter={{ letter }}">{{ letter }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nom</th>
|
||||||
|
<th>Prenom</th>
|
||||||
|
<th>Mail</th>
|
||||||
|
<th>Téléphone</th>
|
||||||
|
<th>MàJ Le</th>
|
||||||
|
<th>Statut</th>
|
||||||
|
<th>Fichiers</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
{% for ficheInscription in ficheInscriptions_list %}
|
||||||
|
{% with eleve=ficheInscription.eleve %}
|
||||||
|
{% with responsable=eleve.getResponsablePrincipal %}
|
||||||
|
{% with fichiers=ficheInscription|recupereFichiersDossierInscription %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ eleve.nom }}</td>
|
||||||
|
<td>{{ eleve.prenom }}</td>
|
||||||
|
<td>{{ responsable.mail }}</td>
|
||||||
|
<td>{{ responsable.telephone }}</td>
|
||||||
|
<td>{{ ficheInscription.dateMAJ }}</td>
|
||||||
|
<td>
|
||||||
|
{% if ficheInscription.etat == 0 %}
|
||||||
|
<span class="tag blue"> Créé</span>
|
||||||
|
{% elif ficheInscription.etat == 1 %}
|
||||||
|
<span class="tag orange"> Envoyé</span>
|
||||||
|
{% elif ficheInscription.etat == 2 %}
|
||||||
|
<span class="tag purple"> En Validation</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="tag green"> Validé</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% for fichier in fichiers %}
|
||||||
|
<a href="{{ fichier.url }}">{{ fichier.nom }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
<td class="actions">
|
||||||
|
<button class="icon-btn" onclick="location.href='{% url 'GestionInscriptions:send' eleve.id %}'" type="submit"> <i class="icon directbox-send"></i></button>
|
||||||
|
<button class="icon-btn"> <i class="icon edit"></i></button>
|
||||||
|
<button class="icon-btn red"> <i class="icon user-minus"></i></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endwith %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td colspan="6">
|
||||||
|
<div class="pagination">
|
||||||
|
{% if ficheInscriptions_list.has_previous %}
|
||||||
|
{% if ficheInscriptions_list.previous_page_number == 1 %}
|
||||||
|
<a class="item" href="?page={{ ficheInscriptions_list.previous_page_number }}"><</a>
|
||||||
|
<a class="item" href="?page=1">1</a>
|
||||||
|
{% else %}
|
||||||
|
<a class="item" href="?page={{ ficheInscriptions_list.previous_page_number }}"><</a>
|
||||||
|
<a class="item" href="?page=1">1</a>
|
||||||
|
<a class="item" >...</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if ficheInscriptions_list %}
|
||||||
|
<a class="item active">{{ ficheInscriptions_list.number }}</a>
|
||||||
|
{% else %}
|
||||||
|
<a class="item">{{ ficheInscriptions_list.number }}</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if ficheInscriptions_list.has_next %}
|
||||||
|
{% if ficheInscriptions_list.next_page_number == ficheInscriptions_list.paginator.num_pages %}
|
||||||
|
<a class="item" href="?page={{ ficheInscriptions_list.next_page_number }}">{{ ficheInscriptions_list.next_page_number }}</a>
|
||||||
|
<a class="item" href="?page={{ ficheInscriptions_list.next_page_number }}">></a>
|
||||||
|
{% else %}
|
||||||
|
<a class="item" >...</a>
|
||||||
|
<a class="item" href="?page={{ ficheInscriptions_list.paginator.num_pages }}">{{ ficheInscriptions_list.paginator.num_pages }}</a>
|
||||||
|
<a class="item" href="?page={{ ficheInscriptions_list.next_page_number }}">></a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
31
Back-End/GestionInscriptions/templates/base.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{% static '/css/main.css' %}">
|
||||||
|
<title>Monteschool</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="itemlogo">
|
||||||
|
<div class="circle"></div>
|
||||||
|
</div>
|
||||||
|
<a class="item active">
|
||||||
|
<i class="icon receipt-edit"></i> Administration
|
||||||
|
</a>
|
||||||
|
<a class="item">
|
||||||
|
<i class="icon user-line"></i> Statistiques
|
||||||
|
</a>
|
||||||
|
<a class="item">
|
||||||
|
<i class="icon book"></i> Paramétrage
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{{ pdf_title }}</title>
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
font-weight: 200;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 100;
|
||||||
|
text-align: center;
|
||||||
|
color: #007cae;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 100;
|
||||||
|
/* text-align: right;*/
|
||||||
|
padding: 10px 20px 0px 20px;
|
||||||
|
}
|
||||||
|
.title span {
|
||||||
|
color: #007cae;
|
||||||
|
}
|
||||||
|
.details {
|
||||||
|
padding: 10px 20px 0px 20px;
|
||||||
|
text-align: left !important;
|
||||||
|
/*margin-left: 40%;*/
|
||||||
|
}
|
||||||
|
.hrItem {
|
||||||
|
border: none;
|
||||||
|
height: 1px;
|
||||||
|
/* Set the hr color */
|
||||||
|
color: #333; /* old IE */
|
||||||
|
background-color: #fff; /* Modern Browsers */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% load myTemplateTag %}
|
||||||
|
<div class='wrapper'>
|
||||||
|
<div class='header'>
|
||||||
|
<p class='title'>{{ pdf_title }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class='details'>
|
||||||
|
Signé le : <b>{{ dateSignature }}</b> <br/>
|
||||||
|
A : <b>{{ heureSignature }}</b>
|
||||||
|
<hr class='hrItem' />
|
||||||
|
<h1>ELEVE</h1>
|
||||||
|
{% with niveau=eleve|recupereNiveauEleve %}
|
||||||
|
{% with genre=eleve|recupereGenreEleve %}
|
||||||
|
NOM : <b>{{ eleve.nom }}</b> <br/>
|
||||||
|
PRENOM : <b>{{ eleve.prenom }}</b> <br/>
|
||||||
|
ADRESSE : <b>{{ eleve.adresse }}</b> <br/>
|
||||||
|
GENRE : <b>{{ genre }}</b> <br/>
|
||||||
|
NE(E) LE : <b>{{ eleve.dateNaissance }}</b> <br/>
|
||||||
|
A : <b>{{ eleve.lieuNaissance }} ({{ eleve.codePostalNaissance }})</b> <br/>
|
||||||
|
NATIONALITE : <b>{{ eleve.nationalite }}</b> <br/>
|
||||||
|
NIVEAU : <b>{{ niveau }}</b> <br/>
|
||||||
|
MEDECIN TRAITANT : <b>{{ eleve.medecinTraitant }}</b> <br/>
|
||||||
|
{% endwith %}
|
||||||
|
{% endwith %}
|
||||||
|
<hr class='hrItem' />
|
||||||
|
<h1>RESPONSABLES</h1>
|
||||||
|
{% with responsables_List=eleve.getResponsables %}
|
||||||
|
{% with freres_List=eleve.getFreres %}
|
||||||
|
{% for responsable in responsables_List%}
|
||||||
|
<h2>Responsable {{ forloop.counter }}</h2>
|
||||||
|
NOM : <b>{{ responsable.nom }}</b> <br/>
|
||||||
|
PRENOM : <b>{{ responsable.prenom }}</b> <br/>
|
||||||
|
ADRESSE : <b>{{ responsable.adresse }}</b> <br/>
|
||||||
|
NE(E) LE : <b>{{ responsable.dateNaissance }}</b> <br/>
|
||||||
|
MAIL : <b>{{ responsable.mail }}</b> <br/>
|
||||||
|
TEL : <b>{{ responsable.telephone }}</b> <br/>
|
||||||
|
PROFESSION : <b>{{ responsable.profession }}</b> <br/>
|
||||||
|
{% endfor %}
|
||||||
|
<hr class='hrItem' />
|
||||||
|
<h1>FRATRIE</h1>
|
||||||
|
{% for frere in freres_List%}
|
||||||
|
<h2>Frère - Soeur {{ forloop.counter }}</h2>
|
||||||
|
NOM : <b>{{ frere.nom }}</b> <br/>
|
||||||
|
PRENOM : <b>{{ frere.prenom }}</b> <br/>
|
||||||
|
NE(E) LE : <b>{{ frere.dateNaissance }}</b> <br/>
|
||||||
|
{% endfor %}
|
||||||
|
<hr class='hrItem' />
|
||||||
|
<h1>MODALITES DE PAIEMENT</h1>
|
||||||
|
{% with modePaiement=eleve|recupereModePaiement %}
|
||||||
|
<b>{{ modePaiement }}</b> <br/>
|
||||||
|
{% endwith %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
Back-End/GestionInscriptions/templatetags/myTemplateTag.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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
|
||||||
31
Back-End/GestionInscriptions/urls.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from django.urls import path, re_path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
from GestionInscriptions.views import ListFichesInscriptionView, FicheInscriptionView, EleveView, ResponsableView, ListeEnfantsView, ListeElevesView
|
||||||
|
|
||||||
|
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"),
|
||||||
|
]
|
||||||
181
Back-End/GestionInscriptions/util.py
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
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
|
||||||
289
Back-End/GestionInscriptions/views.py
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
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
|
||||||
|
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 FicheInscriptionSerializer, EleveSerializer, FicheInscriptionByParentSerializer, EleveByDICreationSerializer
|
||||||
|
from GestionInscriptions.pagination import CustomPagination
|
||||||
|
from GestionInscriptions.signals import clear_cache
|
||||||
|
from .models import Eleve, Responsable, FicheInscription
|
||||||
|
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(self, request, _filter):
|
||||||
|
if _filter == 'all':
|
||||||
|
# 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
|
||||||
|
|
||||||
|
cached_page_size = cache.get('N3WT_page_size')
|
||||||
|
if cached_page_size != page_size:
|
||||||
|
clear_cache()
|
||||||
|
cache.set('N3WT_page_size', page_size)
|
||||||
|
|
||||||
|
# Gestion du cache
|
||||||
|
page_number = request.GET.get('page', 1)
|
||||||
|
cache_key = f'N3WT_ficheInscriptions_page_{page_number}_search_{search}'
|
||||||
|
cached_page = cache.get(cache_key)
|
||||||
|
if cached_page:
|
||||||
|
return JsonResponse(cached_page, safe=False)
|
||||||
|
|
||||||
|
# Filtrage des résultats
|
||||||
|
if search:
|
||||||
|
# Utiliser la nouvelle fonction de recherche
|
||||||
|
ficheInscriptions_List = bdd.searchObjects(
|
||||||
|
FicheInscription,
|
||||||
|
search,
|
||||||
|
_excludeState=6 # Exclure les fiches archivées
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Récupère toutes les fiches non archivées
|
||||||
|
ficheInscriptions_List = bdd.getObjects(FicheInscription, 'etat', 6, _reverseCondition=True)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
elif _filter == 'archived' :
|
||||||
|
page_size = request.GET.get('page_size', None)
|
||||||
|
if page_size is not None:
|
||||||
|
try:
|
||||||
|
page_size = int(page_size)
|
||||||
|
except ValueError:
|
||||||
|
page_size = settings.NB_RESULT_PER_PAGE
|
||||||
|
|
||||||
|
cached_page_size = cache.get('N3WT_archived_page_size')
|
||||||
|
|
||||||
|
# Comparer avec le nouveau page_size
|
||||||
|
if cached_page_size != page_size:
|
||||||
|
# Appeler cached_page() et mettre à jour le cache
|
||||||
|
clear_cache()
|
||||||
|
cache.set('N3WT_archived_page_size',page_size)
|
||||||
|
|
||||||
|
page_number = request.GET.get('page', 1)
|
||||||
|
cache_key_page = f'N3WT_ficheInscriptions_archives_page_{page_number}'
|
||||||
|
cached_page = cache.get(cache_key_page)
|
||||||
|
if cached_page:
|
||||||
|
return JsonResponse(cached_page, safe=False)
|
||||||
|
|
||||||
|
ficheInscriptions_List=bdd.getObjects(FicheInscription, 'etat', 6)
|
||||||
|
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_page, response_data, timeout=60*15)
|
||||||
|
|
||||||
|
return JsonResponse(response_data, safe=False)
|
||||||
|
|
||||||
|
return JsonResponse(status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
ficheInscriptions_List=bdd.getAllObjects(FicheInscription)
|
||||||
|
return JsonResponse({'totalInscrits':len(ficheInscriptions_List)}, safe=False)
|
||||||
|
|
||||||
|
return JsonResponse(ficheEleve_serializer.errors, safe=False)
|
||||||
|
|
||||||
|
def put(self, request, id):
|
||||||
|
ficheEleve_data=JSONParser().parse(request)
|
||||||
|
admin = ficheEleve_data.pop('admin', 1)
|
||||||
|
ficheEleve_data["dateMAJ"] = str(util.convertToStr(util._now(), '%d-%m-%Y %H:%M'))
|
||||||
|
ficheEleve = bdd.getObject(_objectName=FicheInscription, _columnName='eleve__id', _value=id)
|
||||||
|
currentState = getStateMachineObjectState(ficheEleve.etat)
|
||||||
|
if admin == 0 and currentState == FicheInscription.EtatDossierInscription.DI_ENVOYE:
|
||||||
|
json.dumps(ficheEleve_data)
|
||||||
|
|
||||||
|
# Ajout du fichier d'inscriptions
|
||||||
|
data = {
|
||||||
|
'pdf_title': "Dossier d'inscription de %s"%ficheEleve.eleve.prenom,
|
||||||
|
'dateSignature': util.convertToStr(util._now(), '%d-%m-%Y'),
|
||||||
|
'heureSignature': util.convertToStr(util._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)
|
||||||
|
|
||||||
|
# Mise à jour de l'automate
|
||||||
|
updateStateMachine(di, 'saisiDI')
|
||||||
|
|
||||||
|
ficheEleve_serializer = FicheInscriptionSerializer(ficheEleve, data=ficheEleve_data)
|
||||||
|
if ficheEleve_serializer.is_valid():
|
||||||
|
di = ficheEleve_serializer.save()
|
||||||
|
return JsonResponse("Updated Successfully", 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)
|
||||||
0
Back-End/GestionLogin/__init__.py
Normal file
3
Back-End/GestionLogin/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
7
Back-End/GestionLogin/apps.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
from django.db.models.signals import post_migrate
|
||||||
|
|
||||||
|
class GestionloginConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'GestionLogin'
|
||||||
|
|
||||||
20
Back-End/GestionLogin/backends.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.backends import ModelBackend
|
||||||
|
from GestionLogin.models import Profil
|
||||||
|
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)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = Profil.objects.get(email=username)
|
||||||
|
|
||||||
|
# Vérifie le mot de passe de l'utilisateur
|
||||||
|
if user.check_password(password):
|
||||||
|
return user
|
||||||
|
except Profil.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
25
Back-End/GestionLogin/models.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.core.validators import EmailValidator
|
||||||
|
|
||||||
|
class Profil(AbstractUser):
|
||||||
|
class Droits(models.IntegerChoices):
|
||||||
|
PROFIL_UNDEFINED = -1, _('Profil non défini')
|
||||||
|
PROFIL_ECOLE = 0, _('Profil école')
|
||||||
|
PROFIL_PARENT = 1, _('Profil parent')
|
||||||
|
PROFIL_ADMIN = 2, _('Profil administrateur')
|
||||||
|
|
||||||
|
email = models.EmailField(max_length=255, unique=True, default="", validators=[EmailValidator()])
|
||||||
|
|
||||||
|
USERNAME_FIELD = 'email'
|
||||||
|
|
||||||
|
REQUIRED_FIELDS = ('password', )
|
||||||
|
|
||||||
|
code = models.CharField(max_length=200, default="", blank=True)
|
||||||
|
datePeremption = models.CharField(max_length=200, default="", blank=True)
|
||||||
|
droit = models.IntegerField(choices=Droits, default=Droits.PROFIL_UNDEFINED)
|
||||||
|
estConnecte = models.BooleanField(default=False, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.email + " - " + str(self.droit)
|
||||||
28
Back-End/GestionLogin/serializers.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from GestionLogin.models import Profil
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
class ProfilSerializer(serializers.ModelSerializer):
|
||||||
|
id = serializers.IntegerField(required=False)
|
||||||
|
password = serializers.CharField(write_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Profil
|
||||||
|
fields = ['id', 'password', 'email', 'code', 'datePeremption', 'estConnecte', 'droit', 'username', 'is_active']
|
||||||
|
extra_kwargs = {'password': {'write_only': True}}
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
user = Profil(
|
||||||
|
username=validated_data['username'],
|
||||||
|
email=validated_data['email'],
|
||||||
|
is_active=validated_data['is_active'],
|
||||||
|
droit=validated_data['droit']
|
||||||
|
)
|
||||||
|
user.set_password(validated_data['password'])
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
ret = super().to_representation(instance)
|
||||||
|
ret['password'] = '********'
|
||||||
|
return ret
|
||||||
61
Back-End/GestionLogin/templates/GestionLogin/login.html
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/main.css' %}">
|
||||||
|
<title>Monteschool</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="sidebar">
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="logo-circular centered">
|
||||||
|
<img src="{% static 'img/logo_min.svg' %}" alt="">
|
||||||
|
</div>
|
||||||
|
<h1 class="login-heading">Authentification</h1>
|
||||||
|
<form class="centered login-form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="userInput">{{ form.email.label }}</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<span class="icon-ctn">
|
||||||
|
<i class="icon user"></i>
|
||||||
|
</span>
|
||||||
|
<input type="text" id="userInput" placeholder='Identifiant' name="email">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="userInput">{{ form.password.label }}</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<span class="icon-ctn">
|
||||||
|
<i class="icon key"></i>
|
||||||
|
</span>
|
||||||
|
<input type="password" id="userInput" placeholder="Mot de passe" name="password">
|
||||||
|
</div>
|
||||||
|
<p style="color:#FF0000">{{ message }}</p>
|
||||||
|
{% if form.errors %}
|
||||||
|
{% for field in form %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<p style="color:#FF0000">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
<p style="color:#FF0000">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<label><a class="right" href='/reset/{{code}}'>Mot de passe oublié ?</a></label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group-submit">
|
||||||
|
<button href="" class="btn primary" type="submit" name="connect">Se Connecter</button>
|
||||||
|
<br>
|
||||||
|
<h2>Pas de compte ?</h2>
|
||||||
|
<br>
|
||||||
|
<button href="" class="btn " name="register">S'inscrire</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/main.css' %}">
|
||||||
|
<title>Monteschool</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container negative full-size">
|
||||||
|
<div class="logo-circular centered">
|
||||||
|
<img src="{% static 'img/logo_min.svg' %}" alt="">
|
||||||
|
</div>
|
||||||
|
<h1 class="login-heading">Nouveau Mot de Passe</h1>
|
||||||
|
<form class="negative centered login-form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="input-group" hidden>
|
||||||
|
<label for="userInput">Identifiant</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<span class="icon-ctn">
|
||||||
|
<i class="icon user"></i>
|
||||||
|
</span>
|
||||||
|
<input type="text" id="userInput" placeholder='Identifiant' value='{{ identifiant }}' name="email">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="password">{{ form.password1.label }}</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<span class="icon-ctn">
|
||||||
|
<i class="icon key"></i>
|
||||||
|
</span>
|
||||||
|
<input type="password" id="password" placeholder="{{ form.password1.label }}" name="password1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="confirmPassword">{{ form.password2.label }}</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<span class="icon-ctn">
|
||||||
|
<i class="icon key"></i>
|
||||||
|
</span>
|
||||||
|
<input type="password" id="confirmPassword" placeholder="{{ form.password2.label }}" name="password2">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p style="color:#FF0000">{{ message }}</p>
|
||||||
|
{% if form.errors %}
|
||||||
|
{% for field in form %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<p style="color:#FF0000">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
<p style="color:#FF0000">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<div class="form-group-submit negative">
|
||||||
|
<button href="" class="btn primary" type="submit" name="save">Enregistrer</button>
|
||||||
|
<br>
|
||||||
|
<button href="" class="btn" type="submit" name="cancel">Annuler</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/main.css' %}">
|
||||||
|
<title>Monteschool</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container negative full-size">
|
||||||
|
<div class="logo-circular centered">
|
||||||
|
<img src="{% static 'img/logo_min.svg' %}" alt="">
|
||||||
|
</div>
|
||||||
|
<h1 class="login-heading"> Réinitialiser Mot de Passe</h1>
|
||||||
|
<form class="negative centered login-form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="username">Identifiant</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<span class="icon-ctn">
|
||||||
|
<i class="icon user"></i>
|
||||||
|
</span>
|
||||||
|
<input type="text" id="username" placeholder="Identifiant" name="email">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p style="color:#FF0000">{{ message }}</p>
|
||||||
|
<div class="form-group-submit negative">
|
||||||
|
<button href="" class="btn primary" type="submit" name="reinit">Réinitialiser</button>
|
||||||
|
<br>
|
||||||
|
<button href="" class="btn" type="submit" name="cancel">Annuler</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
64
Back-End/GestionLogin/templates/GestionLogin/subscribe.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/main.css' %}">
|
||||||
|
<title>Monteschool</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container negative full-size">
|
||||||
|
<div class="logo-circular centered">
|
||||||
|
<img src="{% static 'img/logo_min.svg' %}" alt="">
|
||||||
|
</div>
|
||||||
|
<h1 class="login-heading">S'inscrire</h1>
|
||||||
|
<form class="negative centered login-form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="username">{{ form.email.label }}</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<span class="icon-ctn">
|
||||||
|
<i class="icon user"></i>
|
||||||
|
</span>
|
||||||
|
<input type="text" id="username" placeholder="Identifiant" name="email">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="password">{{ form.password1.label }}</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<span class="icon-ctn">
|
||||||
|
<i class="icon key"></i>
|
||||||
|
</span>
|
||||||
|
<input type="password" id="password" placeholder="{{ form.password1.label }}" name="password1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="confirmPassword">{{ form.password2.label }}</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<span class="icon-ctn">
|
||||||
|
<i class="icon key"></i>
|
||||||
|
</span>
|
||||||
|
<input type="password" id="confirmPassword" placeholder="{{ form.password2.label }}" name="password2">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p style="color:#FF0000">{{ message }}</p>
|
||||||
|
{% if form.errors %}
|
||||||
|
{% for field in form %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<p style="color:#FF0000">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
<p style="color:#FF0000">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<div class="form-group-submit negative">
|
||||||
|
<button href="" class="btn primary" type="submit" name="validate">Enregistrer</button>
|
||||||
|
<br>
|
||||||
|
<button href="" class="btn" name="cancel">Annuler</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
22
Back-End/GestionLogin/urls.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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"),
|
||||||
|
]
|
||||||
120
Back-End/GestionLogin/validator.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from N3wtSchool import bdd, error
|
||||||
|
|
||||||
|
class Validator(ABC):
|
||||||
|
|
||||||
|
formName=""
|
||||||
|
data = {}
|
||||||
|
errorFields={}
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def validate(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def getErrorFields(self):
|
||||||
|
return self.errorFields
|
||||||
|
|
||||||
|
class ValidatorAuthentication(Validator):
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.data = kwargs['data']
|
||||||
|
self.errorFields = {'email':'','password':''}
|
||||||
|
|
||||||
|
def __str__ (self):
|
||||||
|
return "VALIDATOR_AUTHENTICATION : " + self.data
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
email = self.data.get("email")
|
||||||
|
password = self.data.get("password")
|
||||||
|
|
||||||
|
if email == None or not email:
|
||||||
|
self.errorFields['email'] = error.returnMessage[error.INCOMPLETE]
|
||||||
|
|
||||||
|
if password == None or not password:
|
||||||
|
self.errorFields['password'] = error.returnMessage[error.INCOMPLETE]
|
||||||
|
|
||||||
|
|
||||||
|
return (not self.errorFields['email'] and not self.errorFields['password']), self.errorFields
|
||||||
|
|
||||||
|
def getErrorFields(self):
|
||||||
|
return super().getErrorFields()
|
||||||
|
|
||||||
|
|
||||||
|
class ValidatorSubscription(Validator):
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.data = kwargs['data']
|
||||||
|
self.errorFields = {'email':'','password1':'', 'password2':''}
|
||||||
|
|
||||||
|
def __str__ (self):
|
||||||
|
return "VALIDATOR_SUBSCRIPTION : " + self.data
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
email = self.data.get("email")
|
||||||
|
password1 = self.data.get("password1")
|
||||||
|
password2 = self.data.get("password2")
|
||||||
|
|
||||||
|
if password1 != password2:
|
||||||
|
self.errorFields['password1'] = error.returnMessage[error.DIFFERENT_PASWWORD]
|
||||||
|
self.errorFields['password2'] = error.returnMessage[error.DIFFERENT_PASWWORD]
|
||||||
|
else:
|
||||||
|
if email == None or not email:
|
||||||
|
self.errorFields['email'] = error.returnMessage[error.INCOMPLETE]
|
||||||
|
if password1 == None or not password1:
|
||||||
|
self.errorFields['password1'] = error.returnMessage[error.INCOMPLETE]
|
||||||
|
if password2 == None or not password2:
|
||||||
|
self.errorFields['password2'] = error.returnMessage[error.INCOMPLETE]
|
||||||
|
|
||||||
|
return (not self.errorFields['email'] and not self.errorFields['password1'] and not self.errorFields['password2']), self.errorFields
|
||||||
|
|
||||||
|
def getErrorFields(self):
|
||||||
|
return super().getErrorFields()
|
||||||
|
|
||||||
|
class ValidatorNewPassword(Validator):
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.data = kwargs['data']
|
||||||
|
self.errorFields = {'email':''}
|
||||||
|
|
||||||
|
def __str__ (self):
|
||||||
|
return "VALIDATOR_NEW_PASSWORD : " + self.data
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
email = self.data.get("email")
|
||||||
|
|
||||||
|
if email == None or not email:
|
||||||
|
self.errorFields['email'] = error.returnMessage[error.INCOMPLETE]
|
||||||
|
|
||||||
|
return not self.errorFields['email'], self.errorFields
|
||||||
|
|
||||||
|
def getErrorFields(self):
|
||||||
|
return super().getErrorFields()
|
||||||
|
|
||||||
|
class ValidatorResetPassword(Validator):
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.data = kwargs['data']
|
||||||
|
self.errorFields = {'password1':'', 'password2':''}
|
||||||
|
|
||||||
|
def __str__ (self):
|
||||||
|
return "VALIDATOR_RESET_PASSWORD : " + self.data
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
password1 = self.data.get("password1")
|
||||||
|
password2 = self.data.get("password2")
|
||||||
|
|
||||||
|
if password1 != password2:
|
||||||
|
self.errorFields['password1'] = error.returnMessage[error.DIFFERENT_PASWWORD]
|
||||||
|
self.errorFields['password2'] = error.returnMessage[error.DIFFERENT_PASWWORD]
|
||||||
|
|
||||||
|
else:
|
||||||
|
if password1 == None or not password1:
|
||||||
|
self.errorFields['password1'] = error.returnMessage[error.INCOMPLETE]
|
||||||
|
if password2 == None or not password2:
|
||||||
|
self.errorFields['password2'] = error.returnMessage[error.INCOMPLETE]
|
||||||
|
|
||||||
|
return (not self.errorFields['password1'] and not self.errorFields['password2']), self.errorFields
|
||||||
|
|
||||||
|
def getErrorFields(self):
|
||||||
|
return super().getErrorFields()
|
||||||
264
Back-End/GestionLogin/views.py
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import login, authenticate, get_user_model
|
||||||
|
from django.http.response import JsonResponse
|
||||||
|
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt, csrf_protect
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.middleware.csrf import get_token
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.parsers import JSONParser
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
import jwt
|
||||||
|
import json
|
||||||
|
|
||||||
|
from . import validator
|
||||||
|
from .models import Profil
|
||||||
|
|
||||||
|
from GestionInscriptions.models import FicheInscription
|
||||||
|
from GestionInscriptions.serializers import ProfilSerializer
|
||||||
|
from GestionInscriptions.signals import clear_cache
|
||||||
|
import GestionInscriptions.mailManager as mailer
|
||||||
|
import GestionInscriptions.util as util
|
||||||
|
|
||||||
|
from N3wtSchool import bdd, error
|
||||||
|
|
||||||
|
def csrf(request):
|
||||||
|
token = get_token(request)
|
||||||
|
return JsonResponse({'csrfToken': token})
|
||||||
|
|
||||||
|
class SessionView(APIView):
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
token = request.META.get('HTTP_AUTHORIZATION', '').split('Bearer ')[-1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
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)
|
||||||
|
|
||||||
|
response_data = {
|
||||||
|
'user': {
|
||||||
|
'id': user.id,
|
||||||
|
'email': user.email,
|
||||||
|
'role': user.droit, # Assure-toi que le champ 'droit' existe et contient le rôle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JsonResponse(response_data, status=status.HTTP_200_OK)
|
||||||
|
except jwt.ExpiredSignatureError:
|
||||||
|
return JsonResponse({"error": "Token has expired"}, status=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
except jwt.InvalidTokenError:
|
||||||
|
return JsonResponse({"error": "Invalid token"}, status=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
class ListProfilView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
profilsList = bdd.getAllObjects(_objectName=Profil)
|
||||||
|
profils_serializer = ProfilSerializer(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):
|
||||||
|
def get(self, request, _id):
|
||||||
|
profil=bdd.getObject(Profil, "id", _id)
|
||||||
|
profil_serializer=ProfilSerializer(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)
|
||||||
|
|
||||||
|
if profil_serializer.is_valid():
|
||||||
|
profil_serializer.save()
|
||||||
|
|
||||||
|
return JsonResponse(profil_serializer.data, safe=False)
|
||||||
|
|
||||||
|
|
||||||
|
return JsonResponse(profil_serializer.errors, safe=False)
|
||||||
|
|
||||||
|
def put(self, request, _id):
|
||||||
|
data=JSONParser().parse(request)
|
||||||
|
profil = Profil.objects.get(id=_id)
|
||||||
|
profil_serializer = ProfilSerializer(profil, data=data)
|
||||||
|
if profil_serializer.is_valid():
|
||||||
|
profil_serializer.save()
|
||||||
|
return JsonResponse("Updated Successfully", safe=False)
|
||||||
|
|
||||||
|
return JsonResponse(profil_serializer.errors, safe=False)
|
||||||
|
|
||||||
|
def infoSession(request):
|
||||||
|
profilCache = cache.get('session_cache')
|
||||||
|
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)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class LoginView(APIView):
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
return JsonResponse({
|
||||||
|
'errorFields':'',
|
||||||
|
'errorMessage':'',
|
||||||
|
'profil':0,
|
||||||
|
}, safe=False)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
data=JSONParser().parse(request)
|
||||||
|
validatorAuthentication = validator.ValidatorAuthentication(data=data)
|
||||||
|
retour = error.returnMessage[error.WRONG_ID]
|
||||||
|
validationOk, errorFields = validatorAuthentication.validate()
|
||||||
|
user = None
|
||||||
|
if validationOk:
|
||||||
|
user = authenticate(
|
||||||
|
email=data.get('email'),
|
||||||
|
password=data.get('password'),
|
||||||
|
)
|
||||||
|
if user is not None:
|
||||||
|
if user.is_active:
|
||||||
|
login(request, user)
|
||||||
|
user.estConnecte = True
|
||||||
|
user.save()
|
||||||
|
clear_cache()
|
||||||
|
retour = ''
|
||||||
|
else:
|
||||||
|
retour = error.returnMessage[error.PROFIL_INACTIVE]
|
||||||
|
|
||||||
|
# Génération du token JWT
|
||||||
|
# jwt_token = jwt.encode({
|
||||||
|
# 'id': user.id,
|
||||||
|
# 'email': user.email,
|
||||||
|
# 'role': "admin"
|
||||||
|
# }, settings.SECRET_KEY, algorithm='HS256')
|
||||||
|
else:
|
||||||
|
retour = error.returnMessage[error.WRONG_ID]
|
||||||
|
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'errorFields':errorFields,
|
||||||
|
'errorMessage':retour,
|
||||||
|
'profil':user.id if user else -1,
|
||||||
|
#'jwtToken':jwt_token if profil != -1 else ''
|
||||||
|
}, safe=False)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class SubscribeView(APIView):
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
return JsonResponse({
|
||||||
|
'message':'',
|
||||||
|
'errorFields':'',
|
||||||
|
'errorMessage':''
|
||||||
|
}, safe=False)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
retourErreur = error.returnMessage[error.BAD_URL]
|
||||||
|
retour = ''
|
||||||
|
newProfilConnection=JSONParser().parse(request)
|
||||||
|
|
||||||
|
validatorSubscription = validator.ValidatorSubscription(data=newProfilConnection)
|
||||||
|
validationOk, errorFields = validatorSubscription.validate()
|
||||||
|
|
||||||
|
|
||||||
|
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'))
|
||||||
|
if profil == None:
|
||||||
|
retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS]
|
||||||
|
else:
|
||||||
|
if profil.is_active:
|
||||||
|
retourErreur=error.returnMessage[error.PROFIL_ACTIVE]
|
||||||
|
return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields, "id":profil.id}, safe=False)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
profil.set_password(newProfilConnection.get('password1'))
|
||||||
|
profil.is_active = True
|
||||||
|
profil.full_clean()
|
||||||
|
profil.save()
|
||||||
|
clear_cache()
|
||||||
|
retour = error.returnMessage[error.MESSAGE_ACTIVATION_PROFILE]
|
||||||
|
retourErreur=''
|
||||||
|
return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields, "id":profil.id}, safe=False)
|
||||||
|
except ValidationError as e:
|
||||||
|
retourErreur = error.returnMessage[error.WRONG_MAIL_FORMAT]
|
||||||
|
return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields}, safe=False)
|
||||||
|
|
||||||
|
return JsonResponse({'message':retour, 'errorMessage':retourErreur, "errorFields":errorFields, "id":-1}, safe=False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class NewPasswordView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
return JsonResponse({
|
||||||
|
'message':'',
|
||||||
|
'errorFields':'',
|
||||||
|
'errorMessage':''
|
||||||
|
}, safe=False)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
retourErreur = error.returnMessage[error.BAD_URL]
|
||||||
|
retour = ''
|
||||||
|
newProfilConnection=JSONParser().parse(request)
|
||||||
|
|
||||||
|
validatorNewPassword = validator.ValidatorNewPassword(data=newProfilConnection)
|
||||||
|
validationOk, errorFields = validatorNewPassword.validate()
|
||||||
|
if validationOk:
|
||||||
|
|
||||||
|
profil = bdd.getProfile(Profil.objects.all(), newProfilConnection.get('email'))
|
||||||
|
if profil == None:
|
||||||
|
retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS]
|
||||||
|
else:
|
||||||
|
# Génération d'une URL provisoire pour modifier le mot de passe
|
||||||
|
profil.code = util.genereRandomCode(12)
|
||||||
|
profil.datePeremption = util.calculeDatePeremption(util._now(), settings.EXPIRATION_URL_NB_DAYS)
|
||||||
|
profil.save()
|
||||||
|
clear_cache()
|
||||||
|
retourErreur = ''
|
||||||
|
retour = error.returnMessage[error.MESSAGE_REINIT_PASSWORD]%(newProfilConnection.get('email'))
|
||||||
|
mailer.envoieReinitMotDePasse(newProfilConnection.get('email'), profil.code)
|
||||||
|
|
||||||
|
return JsonResponse({'message':retour, 'errorMessage':retourErreur, "errorFields":errorFields}, safe=False)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class ResetPasswordView(APIView):
|
||||||
|
def get(self, request, _uuid):
|
||||||
|
return JsonResponse({
|
||||||
|
'message':'',
|
||||||
|
'errorFields':'',
|
||||||
|
'errorMessage':''
|
||||||
|
}, safe=False)
|
||||||
|
|
||||||
|
def post(self, request, _uuid):
|
||||||
|
retourErreur = error.returnMessage[error.BAD_URL]
|
||||||
|
retour = ''
|
||||||
|
newProfilConnection=JSONParser().parse(request)
|
||||||
|
|
||||||
|
validatorResetPassword = validator.ValidatorResetPassword(data=newProfilConnection)
|
||||||
|
validationOk, errorFields = validatorResetPassword.validate()
|
||||||
|
|
||||||
|
profil = bdd.getObject(Profil, "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'):
|
||||||
|
retourErreur = error.returnMessage[error.EXPIRED_URL]%(_uuid)
|
||||||
|
elif validationOk:
|
||||||
|
retour = error.returnMessage[error.PASSWORD_CHANGED]
|
||||||
|
|
||||||
|
profil.set_password(newProfilConnection.get('password1'))
|
||||||
|
profil.code = ''
|
||||||
|
profil.datePeremption = ''
|
||||||
|
profil.save()
|
||||||
|
clear_cache()
|
||||||
|
retourErreur=''
|
||||||
|
|
||||||
|
return JsonResponse({'message':retour, "errorMessage":retourErreur, "errorFields":errorFields}, safe=False)
|
||||||
1
Back-End/GestionMessagerie/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'GestionMessagerie.apps.GestionMessagerieConfig'
|
||||||
3
Back-End/GestionMessagerie/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
5
Back-End/GestionMessagerie/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
class GestionMessagerieConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'GestionMessagerie'
|
||||||
15
Back-End/GestionMessagerie/models.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
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')
|
||||||
|
corpus = models.CharField(max_length=200, default="", blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'Messagerie_'+self.id
|
||||||
16
Back-End/GestionMessagerie/serializers.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from GestionLogin.models import Profil
|
||||||
|
from GestionMessagerie.models import Messagerie
|
||||||
|
|
||||||
|
class MessageSerializer(serializers.ModelSerializer):
|
||||||
|
destinataire_profil = serializers.SerializerMethodField()
|
||||||
|
emetteur_profil = serializers.SerializerMethodField()
|
||||||
|
class Meta:
|
||||||
|
model = Messagerie
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
def get_destinataire_profil(self, obj):
|
||||||
|
return obj.destinataire.email
|
||||||
|
|
||||||
|
def get_emetteur_profil(self, obj):
|
||||||
|
return obj.emetteur.email
|
||||||
9
Back-End/GestionMessagerie/urls.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.urls import path, re_path
|
||||||
|
|
||||||
|
from GestionMessagerie.views import MessagerieView, MessageView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
re_path(r'^messagerie/([0-9]+)$', MessagerieView.as_view(), name="messagerie"),
|
||||||
|
re_path(r'^message$', MessageView.as_view(), name="message"),
|
||||||
|
re_path(r'^message/([0-9]+)$', MessageView.as_view(), name="message"),
|
||||||
|
]
|
||||||
32
Back-End/GestionMessagerie/views.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from django.http.response import JsonResponse
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.parsers import JSONParser
|
||||||
|
|
||||||
|
from .models import *
|
||||||
|
|
||||||
|
from GestionMessagerie.serializers import MessageSerializer
|
||||||
|
|
||||||
|
from N3wtSchool import bdd
|
||||||
|
|
||||||
|
class MessagerieView(APIView):
|
||||||
|
def get(self, request, _idProfile):
|
||||||
|
messagesList = bdd.getObjects(_objectName=Messagerie, _columnName='destinataire__id', _value=_idProfile)
|
||||||
|
messages_serializer = MessageSerializer(messagesList, many=True)
|
||||||
|
return JsonResponse(messages_serializer.data, safe=False)
|
||||||
|
|
||||||
|
class MessageView(APIView):
|
||||||
|
def get(self, request, _id):
|
||||||
|
message=bdd.getObject(Messagerie, "id", _id)
|
||||||
|
message_serializer=MessageSerializer(message)
|
||||||
|
return JsonResponse(message_serializer.data, safe=False)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
message_data=JSONParser().parse(request)
|
||||||
|
message_serializer = MessageSerializer(data=message_data)
|
||||||
|
|
||||||
|
if message_serializer.is_valid():
|
||||||
|
message_serializer.save()
|
||||||
|
|
||||||
|
return JsonResponse('Nouveau Message ajouté', safe=False)
|
||||||
|
|
||||||
|
return JsonResponse(message_serializer.errors, safe=False)
|
||||||
1
Back-End/GestionNotification/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'GestionNotification.apps.GestionNotificationConfig'
|
||||||
3
Back-End/GestionNotification/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
9
Back-End/GestionNotification/apps.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
class GestionNotificationConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'GestionNotification'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import GestionNotification.signals
|
||||||
|
|
||||||
28
Back-End/GestionNotification/models.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
class TypeNotif(models.IntegerChoices):
|
||||||
|
NOTIF_NONE = 0, _('Aucune notification')
|
||||||
|
NOTIF_MESSAGE = 1, _('Un message a été reçu')
|
||||||
|
NOTIF_DI = 2, _('Le dossier d\'inscription a été mis à jour')
|
||||||
|
|
||||||
|
class Notification(models.Model):
|
||||||
|
user = models.ForeignKey(Profil, on_delete=models.PROTECT)
|
||||||
|
message = models.CharField(max_length=255)
|
||||||
|
is_read = models.BooleanField(default=False)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
typeNotification = models.IntegerField(choices=TypeNotif, default=0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def typeNotification_label(self):
|
||||||
|
return self.get_typeNotification_display()
|
||||||
|
|
||||||
|
def get_typeNotification_display(self):
|
||||||
|
return TypeNotif(self.typeNotification).label
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Notification for {self.user.username}'
|
||||||
|
|
||||||
23
Back-End/GestionNotification/signals.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
@receiver(post_save, sender=Messagerie)
|
||||||
|
def notification_MESSAGE(sender, instance, created, **kwargs):
|
||||||
|
if created:
|
||||||
|
Notification.objects.create(
|
||||||
|
user=instance.destinataire,
|
||||||
|
message=(TypeNotif.NOTIF_MESSAGE).label,
|
||||||
|
typeNotification=TypeNotif.NOTIF_MESSAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
@receiver(post_save, sender=FicheInscription)
|
||||||
|
def notification_DI(sender, instance, created, **kwargs):
|
||||||
|
for responsable in instance.eleve.responsables.all():
|
||||||
|
Notification.objects.create(
|
||||||
|
user=responsable.profilAssocie,
|
||||||
|
message=(TypeNotif.NOTIF_DI).label,
|
||||||
|
typeNotification=TypeNotif.NOTIF_DI
|
||||||
|
)
|
||||||
7
Back-End/GestionNotification/urls.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.urls import path, re_path
|
||||||
|
|
||||||
|
from GestionNotification.views import NotificationView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
re_path(r'^notification$', NotificationView.as_view(), name="notification"),
|
||||||
|
]
|
||||||
15
Back-End/GestionNotification/views.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from django.http.response import JsonResponse
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
from .models import *
|
||||||
|
|
||||||
|
from GestionInscriptions.serializers import NotificationSerializer
|
||||||
|
|
||||||
|
from N3wtSchool import bdd
|
||||||
|
|
||||||
|
class NotificationView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
notifsList=bdd.getAllObjects(Notification)
|
||||||
|
notifs_serializer=NotificationSerializer(notifsList, many=True)
|
||||||
|
|
||||||
|
return JsonResponse(notifs_serializer.data, safe=False)
|
||||||
5
Back-End/N3wtSchool/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
from .celery import app as celery_app
|
||||||
|
|
||||||
|
__all__ = ('celery_app',)
|
||||||
|
default_app_config = 'N3wtSchool.apps.N3wtSchoolConfig' # Assurer l'utilisation de la configuration d'application
|
||||||
8
Back-End/N3wtSchool/apps.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# n3wtschool/apps.py
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
class N3wtSchoolConfig(AppConfig):
|
||||||
|
name = 'N3wtSchool'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import N3wtSchool.signals
|
||||||
16
Back-End/N3wtSchool/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for N3wtSchool project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'N3wtSchool.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
86
Back-End/N3wtSchool/bdd.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import logging
|
||||||
|
from django.db.models import Q
|
||||||
|
from GestionInscriptions.models import FicheInscription, Profil, Eleve
|
||||||
|
|
||||||
|
def getAllObjects(_objectName):
|
||||||
|
result = _objectName.objects.all()
|
||||||
|
if not result:
|
||||||
|
logging.warning("Aucun résultat n'a été trouvé - " + _objectName.__name__)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def getObject(_objectName, _columnName, _value):
|
||||||
|
result=None
|
||||||
|
try :
|
||||||
|
result = _objectName.objects.get(**{_columnName: _value})
|
||||||
|
except _objectName.DoesNotExist:
|
||||||
|
logging.error("Aucun résultat n'a été trouvé - " + _objectName.__name__ + " (" + _columnName + "=" + str(_value) + ")")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def getObjects(_objectName, _columnName, _value, _reverseCondition=False):
|
||||||
|
results=None
|
||||||
|
try :
|
||||||
|
results = _objectName.objects.filter(**{_columnName: _value}) if _reverseCondition == False else _objectName.objects.filter(~Q(**{_columnName: _value}))
|
||||||
|
except _objectName.DoesNotExist:
|
||||||
|
logging.error("Aucun résultat n'a été trouvé - " + _objectName.__name__ + " (" + _columnName + "=" + str(_value) + ")")
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def existsProfilInList(objectList, valueToCheck):
|
||||||
|
result = False
|
||||||
|
for objectInstance in objectList:
|
||||||
|
if objectInstance.email == valueToCheck:
|
||||||
|
result = True
|
||||||
|
return result
|
||||||
|
|
||||||
|
def getProfile(objectList, valueToCheck):
|
||||||
|
result = None
|
||||||
|
for objectInstance in objectList:
|
||||||
|
if objectInstance.email == valueToCheck:
|
||||||
|
result = objectInstance
|
||||||
|
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
|
||||||
|
|
||||||
|
def getLastId(_object):
|
||||||
|
result = 1
|
||||||
|
try:
|
||||||
|
result = _object.objects.latest('id').id
|
||||||
|
except:
|
||||||
|
logging.warning("Aucun résultat n'a été trouvé - ")
|
||||||
|
return result
|
||||||
|
|
||||||
|
def searchObjects(_objectName, _searchTerm, _excludeState=None):
|
||||||
|
"""
|
||||||
|
Recherche générique sur les objets avec possibilité d'exclure certains états
|
||||||
|
_objectName: Classe du modèle
|
||||||
|
_searchTerm: Terme de recherche
|
||||||
|
_excludeState: État à exclure de la recherche (optionnel)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
query = _objectName.objects.all()
|
||||||
|
|
||||||
|
# Si on a un état à exclure
|
||||||
|
if _excludeState is not None:
|
||||||
|
query = query.filter(etat__lt=_excludeState)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
)
|
||||||
|
|
||||||
|
return query.order_by('eleve__nom', 'eleve__prenom')
|
||||||
|
|
||||||
|
except _objectName.DoesNotExist:
|
||||||
|
logging.error(f"Aucun résultat n'a été trouvé - {_objectName.__name__} (recherche: {_searchTerm})")
|
||||||
|
return None
|
||||||
20
Back-End/N3wtSchool/celery.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
import os
|
||||||
|
from celery import Celery
|
||||||
|
from django.apps import apps
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Définir le module de réglages de Django
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'N3wtSchool.settings')
|
||||||
|
|
||||||
|
app = Celery('N3wtSchool')
|
||||||
|
|
||||||
|
# Lire les configurations de Celery depuis les réglages de Django
|
||||||
|
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||||
|
|
||||||
|
# Découverte automatique des tâches des apps Django
|
||||||
|
app.autodiscover_tasks(lambda: [n.name for n in apps.get_app_configs()])
|
||||||
|
|
||||||
|
# Configurer le logger global pour Celery
|
||||||
|
logger = logging.getLogger('celery')
|
||||||
|
logger.setLevel(logging.WARNING)
|
||||||
31
Back-End/N3wtSchool/error.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from typing import Final
|
||||||
|
|
||||||
|
WRONG_ID: Final = 1
|
||||||
|
INCOMPLETE: Final = 2
|
||||||
|
BAD_URL: Final = 3
|
||||||
|
ALREADY_EXISTS: Final = 4
|
||||||
|
DIFFERENT_PASWWORD: Final = 5
|
||||||
|
PROFIL_NOT_EXISTS: Final = 6
|
||||||
|
MESSAGE_REINIT_PASSWORD: Final = 7
|
||||||
|
EXPIRED_URL: Final = 8
|
||||||
|
PASSWORD_CHANGED: Final = 8
|
||||||
|
WRONG_MAIL_FORMAT: Final = 9
|
||||||
|
PROFIL_INACTIVE: Final = 10
|
||||||
|
MESSAGE_ACTIVATION_PROFILE: Final = 11
|
||||||
|
PROFIL_ACTIVE: Final = 12
|
||||||
|
|
||||||
|
returnMessage = {
|
||||||
|
WRONG_ID:'Identifiants invalides',
|
||||||
|
INCOMPLETE:'Renseignez les champs obligatoires',
|
||||||
|
BAD_URL:'Lien invalide : veuillez contacter l\'administrateur',
|
||||||
|
ALREADY_EXISTS: 'Profil déjà existant',
|
||||||
|
DIFFERENT_PASWWORD: 'Les mots de passe ne correspondent pas',
|
||||||
|
PROFIL_NOT_EXISTS: 'Aucun profil associé à cet utilisateur',
|
||||||
|
MESSAGE_REINIT_PASSWORD: 'Un mail a été envoyé à l\'adresse \'%s\'',
|
||||||
|
EXPIRED_URL:'L\'URL a expiré. Effectuer à nouveau la demande de réinitialisation de mot de passe : http://localhost:3000/password/reset?uuid=%s',
|
||||||
|
PASSWORD_CHANGED: 'Le mot de passe a été réinitialisé',
|
||||||
|
WRONG_MAIL_FORMAT: 'L\'adresse mail est mal formatée',
|
||||||
|
PROFIL_INACTIVE: 'Le profil n\'est pas actif',
|
||||||
|
MESSAGE_ACTIVATION_PROFILE: 'Votre profil a été activé avec succès',
|
||||||
|
PROFIL_ACTIVE: 'Le profil est déjà actif',
|
||||||
|
}
|
||||||
10
Back-End/N3wtSchool/redis_client.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# redis_client.py
|
||||||
|
import redis
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
# Configurer le client Redis
|
||||||
|
redis_client = redis.StrictRedis(
|
||||||
|
host=settings.REDIS_HOST,
|
||||||
|
port=settings.REDIS_PORT,
|
||||||
|
db=settings.REDIS_DB,
|
||||||
|
)
|
||||||
14
Back-End/N3wtSchool/renderers.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from io import BytesIO
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.template.loader import get_template
|
||||||
|
|
||||||
|
from xhtml2pdf import pisa
|
||||||
|
|
||||||
|
def render_to_pdf(template_src, context_dict={}):
|
||||||
|
template = get_template(template_src)
|
||||||
|
html = template.render(context_dict)
|
||||||
|
result = BytesIO()
|
||||||
|
pdf = pisa.pisaDocument(BytesIO(html.encode("UTF-8")), result)
|
||||||
|
if pdf.err:
|
||||||
|
return HttpResponse("Invalid PDF", status_code=400, content_type='text/plain')
|
||||||
|
return HttpResponse(result.getvalue(), content_type='application/pdf')
|
||||||
247
Back-End/N3wtSchool/settings.py
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
"""
|
||||||
|
Django settings for N3wtSchool project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.0.4.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
LOGIN_REDIRECT_URL = '/GestionInscriptions/fichesInscriptions'
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure-afjm6kvigncxzx6jjjf(qb0n(*qvi#je79r=gqflcn007d_ve9'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'GestionInscriptions.apps.GestioninscriptionsConfig',
|
||||||
|
'GestionLogin.apps.GestionloginConfig',
|
||||||
|
'GestionMessagerie.apps.GestionMessagerieConfig',
|
||||||
|
'GestionNotification.apps.GestionNotificationConfig',
|
||||||
|
'GestionEnseignants.apps.GestionenseignantsConfig',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'rest_framework',
|
||||||
|
'corsheaders',
|
||||||
|
'django_celery_beat',
|
||||||
|
'N3wtSchool',
|
||||||
|
'drf_yasg',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware', # Déplacez ici, avant CorsMiddleware
|
||||||
|
'corsheaders.middleware.CorsMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'N3wtSchool.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [BASE_DIR / "templates", BASE_DIR / "static/templates"],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
CACHES = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
|
||||||
|
'LOCATION': 'redis://redis:6379',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
|
||||||
|
SESSION_CACHE_ALIAS = 'default'
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'N3wtSchool.wsgi.application'
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
'OPTIONS': {
|
||||||
|
'min_length': 6,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
#{
|
||||||
|
# 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
#},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
BASE_DIR / 'static',
|
||||||
|
]
|
||||||
|
|
||||||
|
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||||
|
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
#################### Application Settings ##############################
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
with open('GestionInscriptions/Configuration/application.json', 'r') as f:
|
||||||
|
jsonObject = json.load(f)
|
||||||
|
|
||||||
|
DJANGO_SUPERUSER_PASSWORD='admin'
|
||||||
|
DJANGO_SUPERUSER_USERNAME='admin'
|
||||||
|
DJANGO_SUPERUSER_EMAIL='admin@n3wtschool.com'
|
||||||
|
|
||||||
|
EMAIL_HOST='smtp.gmail.com'
|
||||||
|
EMAIL_PORT=587
|
||||||
|
EMAIL_HOST_USER=jsonObject['mailFrom']
|
||||||
|
EMAIL_HOST_PASSWORD=jsonObject['password']
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
EMAIL_USE_SSL = False
|
||||||
|
EMAIL_INSCRIPTION_SUBJECT = '[N3WT-SCHOOL] Dossier Inscription'
|
||||||
|
EMAIL_INSCRIPTION_CORPUS = """Bonjour,
|
||||||
|
|
||||||
|
Afin de procéder à l'inscription de votre petit bout, vous trouverez ci-joint le lien vers la page d'authentification : http://localhost:3000/users/login
|
||||||
|
|
||||||
|
S'il s'agit de votre première connexion, veuillez procéder à l'activation de votre compte : http://localhost:3000/users/subscribe
|
||||||
|
identifiant = %s
|
||||||
|
|
||||||
|
Cordialement,
|
||||||
|
"""
|
||||||
|
|
||||||
|
EMAIL_RELANCE_SUBJECT = '[N3WT-SCHOOL] Relance - Dossier Inscription'
|
||||||
|
EMAIL_RELANCE_CORPUS = 'Bonjour,\nN\'ayant pas eu de retour de votre part, nous vous renvoyons le lien vers le formulaire d\'inscription : http://localhost:3000/users/login\nCordialement'
|
||||||
|
EMAIL_REINIT_SUBJECT = 'Réinitialisation du mot de passe'
|
||||||
|
EMAIL_REINIT_CORPUS = 'Bonjour,\nVous trouverez ci-joint le lien pour réinitialiser votre mot de passe : http://localhost:3000/users/password/reset?uuid=%s\nCordialement'
|
||||||
|
|
||||||
|
DOCUMENT_DIR = 'documents'
|
||||||
|
|
||||||
|
CORS_ORIGIN_ALLOW_ALL = True
|
||||||
|
CORS_ALLOW_ALL_HEADERS = True
|
||||||
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
|
|
||||||
|
CORS_ALLOWED_ORIGINS = [
|
||||||
|
"http://localhost:3000"
|
||||||
|
]
|
||||||
|
|
||||||
|
CSRF_TRUSTED_ORIGINS = [
|
||||||
|
"http://localhost:3000", # Front Next.js
|
||||||
|
"http://localhost:8080" # Insomnia
|
||||||
|
]
|
||||||
|
|
||||||
|
CSRF_COOKIE_HTTPONLY = False
|
||||||
|
CSRF_COOKIE_SECURE = False
|
||||||
|
CSRF_COOKIE_NAME = 'csrftoken'
|
||||||
|
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
TZ_APPLI = 'Europe/Paris'
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
|
"NAME": "school",
|
||||||
|
"USER": "postgres",
|
||||||
|
"PASSWORD": "postgres",
|
||||||
|
"HOST": "database",
|
||||||
|
"PORT": "5432",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTH_USER_MODEL = 'GestionLogin.Profil'
|
||||||
|
AUTHENTICATION_BACKENDS = ('GestionLogin.backends.EmailBackend', )
|
||||||
|
SILENCED_SYSTEM_CHECKS = ["auth.W004"]
|
||||||
|
|
||||||
|
EXPIRATION_URL_NB_DAYS = 7
|
||||||
|
EXPIRATION_DI_NB_DAYS = 20
|
||||||
|
DATE_FORMAT = '%d-%m-%Y %H:%M'
|
||||||
|
|
||||||
|
EXPIRATION_SESSION_NB_SEC = 10
|
||||||
|
|
||||||
|
NB_RESULT_PER_PAGE = 8
|
||||||
|
NB_MAX_PAGE = 100
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_PAGINATION_CLASS': 'GestionInscriptions.pagination.CustomPagination',
|
||||||
|
'PAGE_SIZE': NB_RESULT_PER_PAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
CELERY_BROKER_URL = 'redis://redis:6379/0'
|
||||||
|
CELERY_RESULT_BACKEND = 'redis://redis:6379/0'
|
||||||
|
CELERY_ACCEPT_CONTENT = ['json']
|
||||||
|
CELERY_TASK_SERIALIZER = 'json'
|
||||||
|
CELERY_RESULT_SERIALIZER = 'json'
|
||||||
|
CELERY_TIMEZONE = 'Europe/Paris'
|
||||||
|
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
|
||||||
|
|
||||||
|
URL_DJANGO = 'http://localhost:8080/'
|
||||||
|
|
||||||
|
REDIS_HOST = 'redis'
|
||||||
|
REDIS_PORT = 6379
|
||||||
|
REDIS_DB = 0
|
||||||
|
REDIS_PASSWORD = None
|
||||||
|
|
||||||
|
SECRET_KEY = 'QWQ8bYlCz1NpQ9G0vR5kxMnvWszfH2y3'
|
||||||
21
Back-End/N3wtSchool/signals.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from django.db.models.signals import post_migrate
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from django_celery_beat.models import IntervalSchedule, PeriodicTask
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_migrate)
|
||||||
|
def setup_periodic_tasks(sender, **kwargs):
|
||||||
|
|
||||||
|
schedule, created = IntervalSchedule.objects.get_or_create(
|
||||||
|
every=5,
|
||||||
|
period=IntervalSchedule.SECONDS,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Déclarer la tâche périodique
|
||||||
|
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
|
||||||
|
kwargs=json.dumps({}) # Si nécessaire, ajoute
|
||||||
|
)
|
||||||
49
Back-End/N3wtSchool/urls.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for N3wtSchool project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
||||||
|
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
|
||||||
|
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
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import include, path, re_path
|
||||||
|
from rest_framework import permissions
|
||||||
|
from drf_yasg.views import get_schema_view
|
||||||
|
from drf_yasg import openapi
|
||||||
|
|
||||||
|
|
||||||
|
schema_view = get_schema_view(
|
||||||
|
openapi.Info(
|
||||||
|
title="N3wtSchool API",
|
||||||
|
default_version='v1',
|
||||||
|
description="Documentation de l'API de N3wtSchool",
|
||||||
|
terms_of_service="https://www.google.com/policies/terms/",
|
||||||
|
contact=openapi.Contact(email="contact@example.com"),
|
||||||
|
license=openapi.License(name="BSD License"),
|
||||||
|
),
|
||||||
|
public=True,
|
||||||
|
permission_classes=(permissions.AllowAny,),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path("GestionInscriptions/", include(("GestionInscriptions.urls", 'GestionInscriptions'), namespace='GestionInscriptions')),
|
||||||
|
path("GestionLogin/", include(("GestionLogin.urls", 'GestionLogin'), namespace='GestionLogin')),
|
||||||
|
path("GestionMessagerie/", include(("GestionMessagerie.urls", 'GestionMessagerie'), namespace='GestionMessagerie')),
|
||||||
|
path("GestionNotification/", include(("GestionNotification.urls", 'GestionNotification'), namespace='GestionNotification')),
|
||||||
|
path("GestionEnseignants/", include(("GestionEnseignants.urls", 'GestionEnseignants'), namespace='GestionEnseignants')),
|
||||||
|
# Documentation Api
|
||||||
|
re_path(r'^swagger(?P<format>\.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'),
|
||||||
|
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||||
|
]
|
||||||
16
Back-End/N3wtSchool/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for N3wtSchool project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'N3wtSchool.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
1
Back-End/__version__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
__version__ = "0.0.1"
|
||||||
BIN
Back-End/db.sqlite3
Normal file
22
Back-End/manage.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'N3wtSchool.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
BIN
Back-End/requirements.txt
Normal file
15
Back-End/saveDB.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import subprocess
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument("action", type=str, help="dump | restore")
|
||||||
|
parser.add_argument("fileName", type=str, help="nom du fichier dump")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.action == "dump":
|
||||||
|
process=subprocess.Popen(["pg_dump", "-h", "database", "-d", "school", "-U", "postgres", "-p", "5432", "-Fc", "-f", args.fileName + ".dmp"])
|
||||||
|
process.wait()
|
||||||
|
elif args.action == "restore":
|
||||||
|
process=subprocess.Popen(["pg_restore", "--clean", "-h", "database", "-d", "school", "-U", "postgres", args.fileName + ".dmp"])
|
||||||
|
process.wait()
|
||||||
37
Back-End/start.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
|
||||||
|
def run_command(command):
|
||||||
|
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
stdout, stderr = process.communicate(input=b"y\n")
|
||||||
|
if process.returncode != 0:
|
||||||
|
print(f"Error running command: {' '.join(command)}")
|
||||||
|
print(f"stdout: {stdout.decode()}")
|
||||||
|
print(f"stderr: {stderr.decode()}")
|
||||||
|
return process.returncode
|
||||||
|
|
||||||
|
commands = [
|
||||||
|
["python", "manage.py", "collectstatic", "--noinput"],
|
||||||
|
["python", "manage.py", "flush", "--noinput"],
|
||||||
|
["python", "manage.py", "makemigrations", "GestionInscriptions"],
|
||||||
|
["python", "manage.py", "makemigrations", "GestionNotification"],
|
||||||
|
["python", "manage.py", "makemigrations", "GestionMessagerie"],
|
||||||
|
["python", "manage.py", "makemigrations", "GestionLogin"],
|
||||||
|
["python", "manage.py", "makemigrations", "GestionEnseignants"],
|
||||||
|
["python", "manage.py", "migrate"]
|
||||||
|
]
|
||||||
|
|
||||||
|
for command in commands:
|
||||||
|
if run_command(command) != 0:
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# Lancer les processus en parallèle
|
||||||
|
processes = [
|
||||||
|
subprocess.Popen(["python", "manage.py", "runserver", "0.0.0.0:8080"]),
|
||||||
|
subprocess.Popen(["celery", "-A", "N3wtSchool", "worker", "--loglevel=info"]),
|
||||||
|
subprocess.Popen(["celery", "-A", "N3wtSchool", "beat", "--loglevel=info", "--scheduler", "django_celery_beat.schedulers:DatabaseScheduler"])
|
||||||
|
]
|
||||||
|
|
||||||
|
# Attendre la fin des processus
|
||||||
|
for process in processes:
|
||||||
|
process.wait()
|
||||||
22
Back-End/static/css/headings.css
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
h1,h2,h3,h4,h5,h6{
|
||||||
|
font-family: var(--font-text);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
h1,h2,h3,h4,h5,h6{
|
||||||
|
color: var(--dark-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.negative,h2.negative,h3.negative,h4.negative,h5.negative,h6.negative{
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading-title{
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
80
Back-End/static/css/icons.css
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* ICONS
|
||||||
|
**/
|
||||||
|
.icon{
|
||||||
|
display: block;
|
||||||
|
height: 1.5rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
background-color: #8F8F8F;
|
||||||
|
background: no-repeat;
|
||||||
|
-webkit-mask-size: cover;
|
||||||
|
mask-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.user-add{
|
||||||
|
mask: url(../img/icons/user-add.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/user-add.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
.icon.directbox-send{
|
||||||
|
mask: url(../img/icons/directbox-send.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/directbox-send.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
.icon.arrow-square-up{
|
||||||
|
mask: url(../img/icons/arrow-square-up.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/arrow-square-up.svg) no-repeat 50% 50% ;
|
||||||
|
}
|
||||||
|
.icon.arrow-square-down{
|
||||||
|
transform: rotate(180deg);
|
||||||
|
mask: url(../img/icons/arrow-square-up.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/arrow-square-up.svg) no-repeat 50% 50% ;
|
||||||
|
}
|
||||||
|
.icon.book{
|
||||||
|
mask: url(../img/icons/book.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/book.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
.icon.briefcase{
|
||||||
|
mask: url(../img/icons/briefcase.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/briefcase.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
.icon.calculator{
|
||||||
|
mask: url(../img/icons/calculator.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/calculator.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
.icon.key{
|
||||||
|
mask: url(../img/icons/key.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/key.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
.icon.receipt-edit{
|
||||||
|
mask: url(../img/icons/receipt-edit.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/receipt-edit.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
.icon.teacher{
|
||||||
|
mask: url(../img/icons/teacher.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/teacher.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
.icon.user-line{
|
||||||
|
mask: url(../img/icons/user-line.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/user-line.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
.icon.user{
|
||||||
|
mask: url(../img/icons/user.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/user.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
.icon.user-minus{
|
||||||
|
mask: url(../img/icons/user-minus.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/user-minus.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
.icon.profile-add{
|
||||||
|
mask: url(../img/icons/profile-add.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/profile-add.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
.icon.edit{
|
||||||
|
mask: url(../img/icons/edit.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/edit.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.user-search {
|
||||||
|
mask: url(../img/icons/user-search.svg) no-repeat 50% 50%;
|
||||||
|
-webkit-mask: url(../img/icons/user-search.svg) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
506
Back-End/static/css/main.css
Normal file
@ -0,0 +1,506 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
|
||||||
|
@import url('./icons.css');
|
||||||
|
@import url('./headings.css');
|
||||||
|
:root{
|
||||||
|
/*Colors*/
|
||||||
|
--deep-blue:#3C87F4;
|
||||||
|
--artic-blue:#DFE9F5;
|
||||||
|
--light_grey:#8F8F8F;
|
||||||
|
--darker-blue:#3572CA;
|
||||||
|
--dark-teal:#011922;
|
||||||
|
--darker-teal:#000E14;
|
||||||
|
--clear-blue:#63A5BF;
|
||||||
|
--lighter-grey:#F5F5F5;
|
||||||
|
|
||||||
|
/*component settings*/
|
||||||
|
--primary-color:var(--dark-teal);
|
||||||
|
--background-color:var(#FFF);
|
||||||
|
--font-color:var(--light_grey);
|
||||||
|
--font-text:"Roboto", sans-serif;
|
||||||
|
--topbar-bg-color:#FFF;
|
||||||
|
--sidebar-item-bg:var(--darker-teal);
|
||||||
|
--sidebar-item-font-color:#FFF;
|
||||||
|
--topbar-item-font-color:#000;
|
||||||
|
--topbar-item-bg-color:#FFF;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
body{
|
||||||
|
color:var(--font-color);
|
||||||
|
background:var(--background-color);
|
||||||
|
font-family: var(--font-text);
|
||||||
|
font-size: 16px;
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
a{
|
||||||
|
text-decoration: none;
|
||||||
|
color:var(--dark-teal)
|
||||||
|
}
|
||||||
|
a.negative{
|
||||||
|
color: var(--clear-blue);
|
||||||
|
}
|
||||||
|
.right{
|
||||||
|
text-align: right;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar{
|
||||||
|
display: block;
|
||||||
|
width: 300px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-radius: 0px 1em 1em 0px ;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
.sidebar>.itemlogo{
|
||||||
|
height: 300px;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
.sidebar>.item{
|
||||||
|
display: block;
|
||||||
|
height: 75px;
|
||||||
|
line-height: 75px;
|
||||||
|
background-color: var(--sidebar-item-bg);
|
||||||
|
color: var(--sidebar-item-font-color);
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar>.item:hover{
|
||||||
|
background-color: var(--clear-blue);
|
||||||
|
color: var(--dark-teal);
|
||||||
|
}
|
||||||
|
.sidebar>.item.active{
|
||||||
|
background-color: #FFF;
|
||||||
|
color: var(--dark-teal);
|
||||||
|
}
|
||||||
|
.sidebar>.item>.icon{
|
||||||
|
background-color: var(--sidebar-item-font-color);
|
||||||
|
line-height: 75px;
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
height: 32px;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-left: 40px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.sidebar>.item:hover>.icon{
|
||||||
|
background-color: var(--dark-teal);
|
||||||
|
}
|
||||||
|
.sidebar>.item.active>.icon{
|
||||||
|
background-color: var(--dark-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.circle{
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
background-color: #FFF;
|
||||||
|
border-radius: 100px;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container{
|
||||||
|
display: block;
|
||||||
|
padding: 20px;
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - 340px);
|
||||||
|
top: 0px;
|
||||||
|
bottom:0px;
|
||||||
|
right: 0px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.negative{
|
||||||
|
background-color: var(--dark-teal);
|
||||||
|
color:#FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.full-size{
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.input-wrapper{
|
||||||
|
width: 300px;
|
||||||
|
height: 35px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #FFF;
|
||||||
|
border-color: #C3C3C3;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
.input-group>label{
|
||||||
|
font-size: 0.6em;
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #011922;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.negative>.input-group>label{
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
.input-wrapper>span.icon-ctn{
|
||||||
|
height: 35px;
|
||||||
|
width: 35px;
|
||||||
|
float: left;
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-radius: 8px 35px 35px 8px;
|
||||||
|
}
|
||||||
|
.input-wrapper>span.icon-ctn>.icon{
|
||||||
|
height: 35px;
|
||||||
|
width: 35px;
|
||||||
|
background-size: 20px;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
background-color: #8F8F8F;
|
||||||
|
}
|
||||||
|
.input-wrapper>input{
|
||||||
|
line-height: 35px;
|
||||||
|
width: calc(100% - 50px);
|
||||||
|
height: 35px;
|
||||||
|
border: none;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 5px;
|
||||||
|
outline: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.max{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.max-90{
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
.max-80{
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Bouton
|
||||||
|
*/
|
||||||
|
.btn{
|
||||||
|
display: flex;
|
||||||
|
font-family: var(--font-text);
|
||||||
|
background-color: #8F8F8F;
|
||||||
|
color: #FFF;
|
||||||
|
height: 35px;
|
||||||
|
min-width: 75px;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
font-size: 1em;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
line-height: 35px;
|
||||||
|
font-size: 0.6em;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 15%;
|
||||||
|
vertical-align: middle;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.btn>*{
|
||||||
|
display: flexbox;
|
||||||
|
}
|
||||||
|
.negative>.btn{
|
||||||
|
background-color: #FFF;
|
||||||
|
color: var(--dark-teal);
|
||||||
|
}
|
||||||
|
.btn>.icon{
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #FFF;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.btn:hover{
|
||||||
|
background-color:#606060;
|
||||||
|
}
|
||||||
|
.btn.primary{
|
||||||
|
background-color: var(--dark-teal);
|
||||||
|
}
|
||||||
|
.negative>.btn.primary{
|
||||||
|
background-color: var(--clear-blue);
|
||||||
|
}
|
||||||
|
.btn.primary:hover{
|
||||||
|
background-color: var(--darker-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.negative>.btn.primary:hover{
|
||||||
|
background-color: #3b687a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-circular{
|
||||||
|
margin-top: 50px;
|
||||||
|
text-align: center;
|
||||||
|
width: 125px;
|
||||||
|
height: 125px;
|
||||||
|
border-radius: 125px;
|
||||||
|
background-color: #FFF;
|
||||||
|
}
|
||||||
|
.login-heading{
|
||||||
|
margin-top: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.login-heading.negative{
|
||||||
|
color: #FFF;;
|
||||||
|
}
|
||||||
|
.login-form{
|
||||||
|
margin-top: 50px;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
.form-group-submit{
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
.centered{
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered>*{
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-action{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splited-row-table{
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
width: 90%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.splited-row-table>thead>tr>th{
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
text-align: left;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.splited-row-table>tbody>tr{
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
color:#8F8F8F;
|
||||||
|
line-height:50px;
|
||||||
|
}
|
||||||
|
.table-actions .icon{
|
||||||
|
background-color: #8F8F8F;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.table{
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
width: 90%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
.table>thead>tr>th{
|
||||||
|
padding: 15px;
|
||||||
|
text-align: left;
|
||||||
|
color: #48586B;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: Capitalise;
|
||||||
|
background-color: #F5F6F8;
|
||||||
|
border-bottom: 1px solid #EBECF0;
|
||||||
|
border-top: 1px solid #EBECF0;
|
||||||
|
}
|
||||||
|
.table>thead>tr>th:first-child{
|
||||||
|
border-radius: 15px 0 0 0;
|
||||||
|
border-left: 1px solid #EBECF0;
|
||||||
|
}
|
||||||
|
.table>thead>tr>th:last-child{
|
||||||
|
border-radius: 0 15px 0 0 ;
|
||||||
|
border-right: 1px solid #EBECF0;
|
||||||
|
}
|
||||||
|
.table>tbody>tr>td>.avatar{
|
||||||
|
height: 42px;
|
||||||
|
width: 42px;
|
||||||
|
border-radius: 42px;
|
||||||
|
background-color: #8F8F8F;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
float: left;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
.table>tbody>tr>td{
|
||||||
|
padding: 15px;
|
||||||
|
color: #48586B;
|
||||||
|
border-bottom: 1px solid #EBECF0;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.table>tbody>tr:nth-child(even)>td{
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #F5F6F8;
|
||||||
|
border-bottom: 1px solid #EBECF0;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.table>tbody>tr>td:first-child{
|
||||||
|
border-left: 1px solid #EBECF0;
|
||||||
|
}
|
||||||
|
.table>tbody>tr>td:last-child{
|
||||||
|
border-right: 1px solid #EBECF0;
|
||||||
|
}
|
||||||
|
.table>tfoot>tr>td{
|
||||||
|
padding: 35px;
|
||||||
|
color: #48586B;
|
||||||
|
border-bottom: 1px solid #EBECF0;
|
||||||
|
}
|
||||||
|
.table>tfoot>tr>td:first-child{
|
||||||
|
border-radius: 0 0 0 15px;
|
||||||
|
border-left: 1px solid #EBECF0;
|
||||||
|
}
|
||||||
|
.table>tfoot>tr>td:last-child{
|
||||||
|
border-radius: 0 0 15px 0 ;
|
||||||
|
border-right: 1px solid #EBECF0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination{
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
.pagination>a.item{
|
||||||
|
display: inline-block;
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
line-height: 35px;
|
||||||
|
background-color: #F5F6F8;
|
||||||
|
color: #48586B;
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination>a.item.active{
|
||||||
|
background-color: var(--dark-teal);
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
.alphabet-filter{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.alphabet-filter>a.item{
|
||||||
|
display: inline-block;
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
line-height: 35px;
|
||||||
|
background-color: #F5F6F8;
|
||||||
|
color: #48586B;
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.alphabet-filter>a.item.active{
|
||||||
|
background-color: var(--dark-teal);
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
.tag{
|
||||||
|
padding: 5px;
|
||||||
|
display: block;
|
||||||
|
background-color: #F5F6F8;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 2px;
|
||||||
|
color: #48586B;
|
||||||
|
border: 1px solid #48586B;
|
||||||
|
font-weight: bold
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag.green{
|
||||||
|
background-color: #E0F5EE;
|
||||||
|
border: 1px solid #4BC097;
|
||||||
|
color: #4BC097;
|
||||||
|
}
|
||||||
|
.tag.red{
|
||||||
|
background-color: #ffe4e4;
|
||||||
|
border: 1px solid #ff6a6a;
|
||||||
|
color: #ff6a6a;
|
||||||
|
}
|
||||||
|
.tag.orange{
|
||||||
|
background-color: #FFF0E4;
|
||||||
|
border: 1px solid #FFB06A;
|
||||||
|
color: #FFB06A;
|
||||||
|
}
|
||||||
|
.tag.purple{
|
||||||
|
background-color: #fae4ff;
|
||||||
|
border: 1px solid #e66aff;
|
||||||
|
color: #e66aff;
|
||||||
|
}
|
||||||
|
.tag.blue{
|
||||||
|
background-color: #E7E9FD;
|
||||||
|
border: 1px solid #7E89F1;
|
||||||
|
color: #7E89F1;
|
||||||
|
}
|
||||||
|
.tag.teal{
|
||||||
|
background-color: #b3f5f9;
|
||||||
|
border: 1px solid #359ab3;
|
||||||
|
color: #359ab3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn {
|
||||||
|
display: block;
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 35px;
|
||||||
|
background-color: #F5F6F8;
|
||||||
|
color: #48586B;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.icon-btn:hover{
|
||||||
|
background-color: #dfe0e1;
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
.icon-btn>.icon{
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-size: 20px;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
margin: auto;
|
||||||
|
background-color: #8F8F8F;
|
||||||
|
}
|
||||||
|
.icon-btn.primary{
|
||||||
|
background-color: var(--dark-teal);
|
||||||
|
}
|
||||||
|
.icon-btn.primary>.icon{
|
||||||
|
background-color: #FFF;
|
||||||
|
}
|
||||||
|
.icon-btn.primary:hover{
|
||||||
|
background-color: var(--darker-teal);
|
||||||
|
}
|
||||||
|
.icon-btn.red{
|
||||||
|
background-color: rgb(202, 34, 34);
|
||||||
|
}
|
||||||
|
.icon-btn.red>.icon{
|
||||||
|
background-color: #FFF;
|
||||||
|
}
|
||||||
|
.icon-btn.red:hover{
|
||||||
|
background-color: rgb(148, 25, 25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions > .icon-btn{
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading-section{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width:90%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
4
Back-End/static/img/icons/arrow-square-up.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9 22H15C20 22 22 20 22 15V9C22 4 20 2 15 2H9C4 2 2 4 2 9V15C2 20 4 22 9 22Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8.47021 13.4599L12.0002 9.93994L15.5302 13.4599" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 429 B |
6
Back-End/static/img/icons/book.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M22 16.7402V4.67019C22 3.47019 21.02 2.58019 19.83 2.68019H19.77C17.67 2.86019 14.48 3.93019 12.7 5.05019L12.53 5.16019C12.24 5.34019 11.76 5.34019 11.47 5.16019L11.22 5.01019C9.44 3.90019 6.26 2.84019 4.16 2.67019C2.97 2.57019 2 3.47019 2 4.66019V16.7402C2 17.7002 2.78 18.6002 3.74 18.7202L4.03 18.7602C6.2 19.0502 9.55 20.1502 11.47 21.2002L11.51 21.2202C11.78 21.3702 12.21 21.3702 12.47 21.2202C14.39 20.1602 17.75 19.0502 19.93 18.7602L20.26 18.7202C21.22 18.6002 22 17.7002 22 16.7402Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 5.49023V20.4902" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7.75 8.49023H5.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8.5 11.4902H5.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
7
Back-End/static/img/icons/briefcase.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.99983 22H15.9998C20.0198 22 20.7398 20.39 20.9498 18.43L21.6998 10.43C21.9698 7.99 21.2698 6 16.9998 6H6.99983C2.72983 6 2.02983 7.99 2.29983 10.43L3.04983 18.43C3.25983 20.39 3.97983 22 7.99983 22Z" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8 6V5.2C8 3.43 8 2 11.2 2H12.8C16 2 16 3.43 16 5.2V6" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M14 13V14C14 14.01 14 14.01 14 14.02C14 15.11 13.99 16 12 16C10.02 16 10 15.12 10 14.03V13C10 12 10 12 11 12H13C14 12 14 12 14 13Z" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M21.65 11C19.34 12.68 16.7 13.68 14 14.02" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M2.62012 11.27C4.87012 12.81 7.41012 13.74 10.0001 14.03" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
10
Back-End/static/img/icons/calculator.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14 22.75H10C4.57 22.75 2.25 20.43 2.25 15V9C2.25 3.57 4.57 1.25 10 1.25H14C19.43 1.25 21.75 3.57 21.75 9V15C21.75 20.43 19.43 22.75 14 22.75ZM10 2.75C5.39 2.75 3.75 4.39 3.75 9V15C3.75 19.61 5.39 21.25 10 21.25H14C18.61 21.25 20.25 19.61 20.25 15V9C20.25 4.39 18.61 2.75 14 2.75H10Z" fill="currentColor"/>
|
||||||
|
<path d="M15 10.8301H9C7.76 10.8301 6.75 9.82008 6.75 8.58008V7.58008C6.75 6.34008 7.76 5.33008 9 5.33008H15C16.24 5.33008 17.25 6.34008 17.25 7.58008V8.58008C17.25 9.82008 16.24 10.8301 15 10.8301ZM9 6.83008C8.59 6.83008 8.25 7.17008 8.25 7.58008V8.58008C8.25 8.99008 8.59 9.33008 9 9.33008H15C15.41 9.33008 15.75 8.99008 15.75 8.58008V7.58008C15.75 7.17008 15.41 6.83008 15 6.83008H9Z" fill="currentColor"/>
|
||||||
|
<path d="M8.15016 14.9198C8.02016 14.9198 7.89016 14.8898 7.77016 14.8398C7.65016 14.7898 7.54016 14.7198 7.45016 14.6298C7.26016 14.4398 7.16016 14.1898 7.16016 13.9198C7.16016 13.7898 7.18016 13.6598 7.23016 13.5398C7.28016 13.4098 7.35016 13.3098 7.45016 13.2098C7.49016 13.1698 7.54016 13.1198 7.60016 13.0898C7.65016 13.0498 7.71016 13.0198 7.77016 12.9998C7.83016 12.9698 7.90016 12.9498 7.96016 12.9398C8.28016 12.8698 8.63016 12.9798 8.86016 13.2098C8.95016 13.2998 9.03016 13.4098 9.08016 13.5398C9.13016 13.6598 9.16016 13.7898 9.16016 13.9198C9.16016 14.1898 9.05016 14.4398 8.86016 14.6298C8.67016 14.8198 8.42016 14.9198 8.15016 14.9198Z" fill="currentColor"/>
|
||||||
|
<path d="M12.1602 14.9199C11.8902 14.9199 11.6402 14.8199 11.4502 14.6299C11.2602 14.4399 11.1602 14.1899 11.1602 13.9199C11.1602 13.7899 11.1802 13.6599 11.2302 13.5399C11.2802 13.4099 11.3502 13.3099 11.4502 13.2099C11.4902 13.1699 11.5402 13.1199 11.6002 13.0899C11.6502 13.0499 11.7102 13.0199 11.7702 12.9999C11.8302 12.9699 11.9002 12.9499 11.9602 12.9399C12.1502 12.8999 12.3502 12.9199 12.5402 12.9999C12.6602 13.0499 12.7702 13.1199 12.8602 13.2099C12.9502 13.3099 13.0302 13.4099 13.0802 13.5399C13.1302 13.6599 13.1602 13.7899 13.1602 13.9199C13.1602 14.1899 13.0502 14.4399 12.8602 14.6299C12.6702 14.8199 12.4202 14.9199 12.1602 14.9199Z" fill="currentColor"/>
|
||||||
|
<path d="M16.1502 14.9201C16.0202 14.9201 15.8902 14.8901 15.7702 14.8401C15.6502 14.7901 15.5402 14.7201 15.4502 14.6301C15.3502 14.5301 15.2802 14.4201 15.2302 14.3001C15.1802 14.1801 15.1602 14.0501 15.1602 13.9201C15.1602 13.7901 15.1802 13.6601 15.2302 13.5401C15.2802 13.4101 15.3502 13.3101 15.4502 13.2101C15.8202 12.8401 16.4902 12.8401 16.8602 13.2101C17.0502 13.4001 17.1602 13.6601 17.1602 13.9201C17.1602 14.1901 17.0502 14.4401 16.8602 14.6301C16.6802 14.8101 16.4302 14.9201 16.1502 14.9201Z" fill="currentColor"/>
|
||||||
|
<path d="M8.1599 18.9199C7.8899 18.9199 7.6399 18.8199 7.4499 18.6299C7.2599 18.4399 7.1499 18.1899 7.1499 17.9199C7.1499 17.6599 7.2599 17.3999 7.4499 17.2099C7.4899 17.1699 7.5499 17.1199 7.5999 17.0899C7.6499 17.0499 7.7099 17.0199 7.7699 16.9999C7.8299 16.9699 7.8999 16.9499 7.9599 16.9399C8.2899 16.8799 8.6299 16.9799 8.8599 17.2099C8.9099 17.2599 8.9499 17.3099 8.9899 17.3599C9.0199 17.4199 9.0499 17.4799 9.0799 17.5399C9.0999 17.5999 9.1199 17.6599 9.1399 17.7199C9.1499 17.7899 9.1599 17.8499 9.1599 17.9199C9.1599 18.1899 9.0499 18.4399 8.8599 18.6299C8.6699 18.8199 8.4199 18.9199 8.1599 18.9199Z" fill="currentColor"/>
|
||||||
|
<path d="M12.1599 18.9199C11.8899 18.9199 11.6399 18.8199 11.4499 18.6299C11.2599 18.4399 11.1499 18.1899 11.1499 17.9199C11.1499 17.6599 11.2599 17.3999 11.4499 17.2099C11.6799 16.9799 12.0299 16.8799 12.3499 16.9399C12.4099 16.9499 12.4799 16.9699 12.5399 16.9999C12.5999 17.0199 12.6599 17.0499 12.7099 17.0899C12.7599 17.1199 12.8099 17.1699 12.8599 17.2099C13.0499 17.3999 13.1499 17.6599 13.1499 17.9199C13.1499 18.1899 13.0499 18.4399 12.8599 18.6299C12.6699 18.8199 12.4199 18.9199 12.1599 18.9199Z" fill="currentColor"/>
|
||||||
|
<path d="M16.1502 18.9202C15.8902 18.9202 15.6402 18.8202 15.4502 18.6302C15.2602 18.4402 15.1602 18.1902 15.1602 17.9202C15.1602 17.6602 15.2602 17.4002 15.4502 17.2102C15.5402 17.1202 15.6502 17.0502 15.7702 17.0002C16.1402 16.8502 16.5902 16.9302 16.8602 17.2102C17.0502 17.4002 17.1502 17.6602 17.1502 17.9202C17.1502 18.1902 17.0502 18.4402 16.8602 18.6302C16.7702 18.7202 16.6602 18.7902 16.5402 18.8402C16.4202 18.8902 16.2902 18.9202 16.1502 18.9202Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
7
Back-End/static/img/icons/directbox-send.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 8V2L10 4" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 2L14 4" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7 12C3 12 3 13.79 3 16V17C3 19.76 3 22 8 22H16C20 22 21 19.76 21 17V16C21 13.79 21 12 17 12C16 12 15.72 12.21 15.2 12.6L14.18 13.68C13 14.94 11 14.94 9.81 13.68L8.8 12.6C8.28 12.21 8 12 7 12Z" stroke="#171717" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M5 12V10C5 7.99004 5 6.33004 8 6.04004" stroke="#171717" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M19 12V10C19 7.99004 19 6.33004 16 6.04004" stroke="#171717" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 946 B |
5
Back-End/static/img/icons/edit.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.7601 3.59997L5.5501 12.29C5.2401 12.62 4.9401 13.27 4.8801 13.72L4.5101 16.96C4.3801 18.13 5.2201 18.93 6.3801 18.73L9.6001 18.18C10.0501 18.1 10.6801 17.77 10.9901 17.43L19.2001 8.73997C20.6201 7.23997 21.2601 5.52997 19.0501 3.43997C16.8501 1.36997 15.1801 2.09997 13.7601 3.59997Z" stroke="#171717" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12.3899 5.05005C12.8199 7.81005 15.0599 9.92005 17.8399 10.2" stroke="#171717" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3.5 22H21.5" stroke="#171717" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 820 B |
4
Back-End/static/img/icons/key.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M17.0625 8.06542C17.8909 8.06542 18.5625 7.39385 18.5625 6.56542C18.5625 5.737 17.8909 5.06543 17.0625 5.06543C16.2341 5.06543 15.5625 5.737 15.5625 6.56542C15.5625 7.39385 16.2341 8.06542 17.0625 8.06542Z" fill="#8F8F8F"/>
|
||||||
|
<path d="M23.7411 7.77903C23.2404 6.50432 22.3224 4.66509 20.7919 3.14185C19.2669 1.62385 17.4471 0.732108 16.1891 0.251266C14.808 -0.276591 13.2386 0.0561262 12.1909 1.09886L8.50947 4.76306C7.41817 5.8492 7.10364 7.52047 7.72675 8.92179C7.91997 9.35642 8.13011 9.78153 8.35464 10.1922L0.274593 18.2722C0.0987652 18.4481 0 18.6865 0 18.9352V23.0602C0 23.578 0.419717 23.9977 0.937496 23.9977H5.06248C5.58026 23.9977 5.99998 23.578 5.99998 23.0602V21.3727L7.68747 21.3726C8.20525 21.3726 8.62497 20.9529 8.62497 20.4351V18.7477H10.3125C10.8302 18.7477 11.25 18.328 11.25 17.8102C11.25 17.2924 10.8302 16.8727 10.3125 16.8727H7.68747C7.16969 16.8727 6.74997 17.2924 6.74997 17.8102V19.4976L5.06248 19.4977C4.5447 19.4977 4.12498 19.9174 4.12498 20.4352V22.1227H1.87499V19.3235L10.1885 11.0101C10.4939 10.7046 10.5511 10.2301 10.3271 9.86084C10.0022 9.32534 9.70379 8.75309 9.44012 8.15998C9.13079 7.46432 9.28838 6.63327 9.83227 6.09192L13.5137 2.42776C14.0393 1.90468 14.8267 1.73785 15.5198 2.0027C16.6084 2.41876 18.1772 3.1846 19.4693 4.47075C20.7694 5.76468 21.5609 7.35669 21.9959 8.46434C22.2673 9.15565 22.1024 9.94104 21.5756 10.4653L17.8497 14.1737C17.3072 14.7137 16.4974 14.8716 15.7865 14.5759C15.2002 14.3321 14.6324 14.0424 14.099 13.715C13.6578 13.4441 13.0805 13.5823 12.8096 14.0236C12.5388 14.4649 12.6769 15.0422 13.1182 15.313C13.7347 15.6914 14.3902 16.0259 15.0664 16.3071C16.4793 16.8949 18.091 16.579 19.1724 15.5027L22.8983 11.7943C23.9551 10.7425 24.2859 9.16639 23.7411 7.77903Z" fill="#8F8F8F"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
6
Back-End/static/img/icons/profile-add.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18.5 19.5H14.5" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M16.5 21.5V17.5" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12.16 10.87C12.06 10.86 11.94 10.86 11.83 10.87C9.44997 10.79 7.55997 8.84 7.55997 6.44C7.54997 3.99 9.53997 2 11.99 2C14.44 2 16.43 3.99 16.43 6.44C16.43 8.84 14.53 10.79 12.16 10.87Z" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11.99 21.81C10.17 21.81 8.36004 21.35 6.98004 20.43C4.56004 18.81 4.56004 16.17 6.98004 14.56C9.73004 12.72 14.24 12.72 16.99 14.56" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 834 B |
7
Back-End/static/img/icons/receipt-edit.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20.5 11.3V7.04001C20.5 3.01001 19.56 2 15.78 2H8.22C4.44 2 3.5 3.01001 3.5 7.04001V18.3C3.5 20.96 4.96001 21.59 6.73001 19.69L6.73999 19.68C7.55999 18.81 8.80999 18.88 9.51999 19.83L10.53 21.18" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8 7H16" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M9 11H15" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M18.211 14.7703L14.671 18.3103C14.531 18.4503 14.401 18.7103 14.371 18.9003L14.181 20.2503C14.111 20.7403 14.451 21.0803 14.941 21.0103L16.291 20.8203C16.481 20.7903 16.751 20.6603 16.881 20.5203L20.421 16.9803C21.031 16.3703 21.321 15.6603 20.421 14.7603C19.531 13.8703 18.821 14.1603 18.211 14.7703Z" stroke="black" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M17.6992 15.2803C17.9992 16.3603 18.8392 17.2003 19.9192 17.5003" stroke="black" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
5
Back-End/static/img/icons/teacher.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12.0098 16.9998C11.1598 16.9998 10.2998 16.7798 9.62982 16.3498L3.60982 12.4198C2.48982 11.6898 1.81982 10.4598 1.81982 9.11979C1.81982 7.77979 2.48982 6.54979 3.60982 5.81979L9.63982 1.89979C10.9798 1.02979 13.0698 1.02979 14.3998 1.90979L20.3898 5.83979C21.4998 6.56979 22.1698 7.79979 22.1698 9.12979C22.1698 10.4598 21.4998 11.6898 20.3898 12.4198L14.3998 16.3498C13.7298 16.7898 12.8698 16.9998 12.0098 16.9998ZM12.0098 2.74979C11.4398 2.74979 10.8698 2.87979 10.4598 3.15979L4.43982 7.07979C3.73982 7.53979 3.32982 8.27979 3.32982 9.11979C3.32982 9.95979 3.72982 10.6998 4.43982 11.1598L10.4598 15.0898C11.2898 15.6298 12.7498 15.6298 13.5798 15.0898L19.5698 11.1598C20.2698 10.6998 20.6698 9.95979 20.6698 9.11979C20.6698 8.27979 20.2698 7.53979 19.5698 7.07979L13.5798 3.14979C13.1598 2.88979 12.5898 2.74979 12.0098 2.74979Z" fill="currentColor"/>
|
||||||
|
<path d="M12.0002 22.7501C11.5602 22.7501 11.1102 22.6901 10.7502 22.5701L7.56018 21.5101C6.05018 21.0101 4.86018 19.3601 4.87018 17.7701L4.88018 13.0801C4.88018 12.6701 5.22018 12.3301 5.63018 12.3301C6.04018 12.3301 6.38018 12.6701 6.38018 13.0801L6.37018 17.7701C6.37018 18.7101 7.15018 19.7901 8.04018 20.0901L11.2302 21.1501C11.6302 21.2801 12.3702 21.2801 12.7702 21.1501L15.9602 20.0901C16.8502 19.7901 17.6302 18.7101 17.6302 17.7801V13.1401C17.6302 12.7301 17.9702 12.3901 18.3802 12.3901C18.7902 12.3901 19.1302 12.7301 19.1302 13.1401V17.7801C19.1302 19.3701 17.9502 21.0101 16.4402 21.5201L13.2502 22.5801C12.8902 22.6901 12.4402 22.7501 12.0002 22.7501Z" fill="currentColor"/>
|
||||||
|
<path d="M21.3999 15.75C20.9899 15.75 20.6499 15.41 20.6499 15V9C20.6499 8.59 20.9899 8.25 21.3999 8.25C21.8099 8.25 22.1499 8.59 22.1499 9V15C22.1499 15.41 21.8099 15.75 21.3999 15.75Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
7
Back-End/static/img/icons/user-add.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 12C14.7614 12 17 9.76142 17 7C17 4.23858 14.7614 2 12 2C9.23858 2 7 4.23858 7 7C7 9.76142 9.23858 12 12 12Z" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3.40991 22C3.40991 18.13 7.25991 15 11.9999 15C12.9599 15 13.8899 15.13 14.7599 15.37" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M22 18C22 18.32 21.96 18.63 21.88 18.93C21.79 19.33 21.63 19.72 21.42 20.06C20.73 21.22 19.46 22 18 22C16.97 22 16.04 21.61 15.34 20.97C15.04 20.71 14.78 20.4 14.58 20.06C14.21 19.46 14 18.75 14 18C14 16.92 14.43 15.93 15.13 15.21C15.86 14.46 16.88 14 18 14C19.18 14 20.25 14.51 20.97 15.33C21.61 16.04 22 16.98 22 18Z" stroke="#171717" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M19.49 17.98H16.51" stroke="#171717" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M18 16.52V19.51" stroke="#171717" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
4
Back-End/static/img/icons/user-line.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 12C14.7614 12 17 9.76142 17 7C17 4.23858 14.7614 2 12 2C9.23858 2 7 4.23858 7 7C7 9.76142 9.23858 12 12 12Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M20.5901 22C20.5901 18.13 16.7402 15 12.0002 15C7.26015 15 3.41016 18.13 3.41016 22" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 485 B |
6
Back-End/static/img/icons/user-minus.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 12C14.7614 12 17 9.76142 17 7C17 4.23858 14.7614 2 12 2C9.23858 2 7 4.23858 7 7C7 9.76142 9.23858 12 12 12Z" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3.40991 22C3.40991 18.13 7.25994 15 11.9999 15C12.9599 15 13.8899 15.13 14.7599 15.37" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M22 18C22 18.32 21.96 18.63 21.88 18.93C21.79 19.33 21.63 19.72 21.42 20.06C20.73 21.22 19.46 22 18 22C16.97 22 16.04 21.61 15.34 20.97C15.04 20.71 14.78 20.4 14.58 20.06C14.21 19.46 14 18.75 14 18C14 16.92 14.43 15.93 15.13 15.21C15.86 14.46 16.88 14 18 14C19.18 14 20.25 14.51 20.97 15.33C21.61 16.04 22 16.98 22 18Z" stroke="#171717" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M19.49 17.98H16.51" stroke="#171717" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
6
Back-End/static/img/icons/user-search.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 12C14.7614 12 17 9.76142 17 7C17 4.23858 14.7614 2 12 2C9.23858 2 7 4.23858 7 7C7 9.76142 9.23858 12 12 12Z" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3.40991 22C3.40991 18.13 7.25994 15 11.9999 15" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M18.2 21.4C19.9673 21.4 21.4 19.9673 21.4 18.2C21.4 16.4327 19.9673 15 18.2 15C16.4327 15 15 16.4327 15 18.2C15 19.9673 16.4327 21.4 18.2 21.4Z" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M22 22L21 21" stroke="#171717" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 800 B |