refactor: Utilisation d'une application "Common" pour tous les modèles

de référence
This commit is contained in:
N3WT DE COMPET
2025-05-18 15:42:21 +02:00
parent 7fe53465ac
commit e65e31014d
24 changed files with 491 additions and 277 deletions

View File

@ -2,7 +2,4 @@ from django.apps import AppConfig
class SchoolConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'School'
def ready(self):
import School.signals
name = 'School'

View File

@ -4,12 +4,15 @@ from Subscriptions.models import (
RegistrationFileGroup
)
from Auth.models import Profile, ProfileRole
from Common.models import (
PaymentModeType,
PaymentPlanType,
Level
)
from School.models import (
FeeType,
Speciality,
Teacher,
PaymentModeType,
PaymentPlanType,
DiscountType,
Fee,
Discount,
@ -280,6 +283,7 @@ class Command(BaseCommand):
def init_school_classes(self):
school_classes_data = self.load_data('school_classes.json')
levels = list(Level.objects.all())
for index, class_data in enumerate(school_classes_data, start=1):
# Randomize establishment
@ -288,7 +292,7 @@ class Command(BaseCommand):
class_data["establishment"] = establishment.id
# Randomize levels
class_data["levels"] = random.sample(range(1, 10), random.randint(1, 5))
class_data["levels"] = [level.id for level in random.sample(levels, random.randint(1, min(5, len(levels))))]
# Randomize teachers
establishment_teachers = list(Teacher.objects.filter(profile_role__establishment=establishment))
@ -331,6 +335,7 @@ class Command(BaseCommand):
fake = Faker('fr_FR') # Utiliser le locale français pour Faker
file_group_count = RegistrationFileGroup.objects.count()
levels = list(Level.objects.all())
# Récupérer tous les profils existants avec un ProfileRole Parent
profiles_with_parent_role = Profile.objects.filter(roles__role_type=ProfileRole.RoleType.PROFIL_PARENT).distinct()
@ -376,7 +381,7 @@ class Command(BaseCommand):
"birth_postal_code": fake.postcode(),
"nationality": fake.country(),
"attending_physician": fake.name(),
"level": fake.random_int(min=1, max=6),
"level": random.choice(levels).id,
"guardians": [guardian_data],
"sibling": []
}

View File

@ -50,7 +50,7 @@ class SchoolClass(models.Model):
school_year = models.CharField(max_length=9, blank=True)
updated_date = models.DateTimeField(auto_now=True)
teachers = models.ManyToManyField(Teacher, blank=True)
levels = ArrayField(models.IntegerField(choices=LEVEL_CHOICES), default=list)
levels = models.ManyToManyField('Common.Level', blank=True, related_name='school_classes')
type = models.IntegerField(choices=PLANNING_TYPE_CHOICES, default=1)
time_range = models.JSONField(default=list)
opening_days = ArrayField(models.IntegerField(), default=list)
@ -75,21 +75,6 @@ class FeeType(models.IntegerChoices):
REGISTRATION_FEE = 0, 'Registration Fee'
TUITION_FEE = 1, 'Tuition Fee'
class PaymentPlanType(models.Model):
code = models.CharField(max_length=50, unique=True)
label = models.CharField(max_length=255)
def __str__(self):
return self.label
class PaymentModeType(models.Model):
code = models.CharField(max_length=50, unique=True)
label = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.label
class Discount(models.Model):
name = models.CharField(max_length=255, null=True, blank=True)
amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
@ -115,7 +100,7 @@ class Fee(models.Model):
return self.name
class PaymentPlan(models.Model):
plan_type = models.ForeignKey(PaymentPlanType, on_delete=models.PROTECT, related_name='payment_plans')
plan_type = models.ForeignKey('Common.PaymentPlanType', on_delete=models.PROTECT, related_name='payment_plans')
due_dates = ArrayField(models.DateField(), null=True, blank=True)
type = models.IntegerField(choices=FeeType.choices, default=FeeType.REGISTRATION_FEE)
establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE, related_name='payment_plans')
@ -124,45 +109,13 @@ class PaymentPlan(models.Model):
return f"{self.plan_type.label} - {self.get_type_display()}"
class PaymentMode(models.Model):
mode = models.ForeignKey(PaymentModeType, on_delete=models.PROTECT, related_name='payment_modes')
mode = models.ForeignKey('Common.PaymentModeType', on_delete=models.PROTECT, related_name='payment_modes')
type = models.IntegerField(choices=FeeType.choices, default=FeeType.REGISTRATION_FEE)
establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE, related_name='payment_modes')
def __str__(self):
return f"{self.mode.label} - {self.get_type_display()}"
class Domain(models.Model):
name = models.CharField(max_length=255)
cycle = models.IntegerField(choices=[(1, 'Cycle 1'), (2, 'Cycle 2'), (3, 'Cycle 3'), (4, 'Cycle 4')])
def __str__(self):
return f"{self.name} (Cycle {self.cycle})"
class Category(models.Model):
name = models.CharField(max_length=255)
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, related_name='categories')
def __str__(self):
return self.name
class Competency(models.Model):
name = models.TextField()
end_of_cycle = models.BooleanField(default=False, null=True, blank=True)
level = models.CharField(max_length=50, null=True, blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='competencies')
establishments = models.ManyToManyField(
'Establishment.Establishment',
through='EstablishmentCompetency',
related_name='competencies',
blank=True
)
def __str__(self):
return self.name
class EstablishmentCompetency(models.Model):
"""
Relation entre un établissement et une compétence.
@ -170,12 +123,12 @@ class EstablishmentCompetency(models.Model):
"""
establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE)
competency = models.ForeignKey(
Competency, on_delete=models.CASCADE, null=True, blank=True,
'Common.Competency', on_delete=models.CASCADE, null=True, blank=True,
help_text="Compétence de référence (optionnelle si custom)"
)
custom_name = models.TextField(null=True, blank=True, help_text="Nom de la compétence custom")
custom_category = models.ForeignKey(
Category, on_delete=models.CASCADE, null=True, blank=True,
'Common.Category', on_delete=models.CASCADE, null=True, blank=True,
help_text="Catégorie de la compétence custom"
)
is_required = models.BooleanField(default=True)

View File

@ -9,9 +9,6 @@ from .models import (
Fee,
PaymentPlan,
PaymentMode,
Domain,
Category,
Competency,
EstablishmentCompetency
)
from Auth.models import Profile, ProfileRole
@ -22,21 +19,6 @@ from N3wtSchool import settings
from django.utils import timezone
import pytz
class DomainSerializer(serializers.ModelSerializer):
class Meta:
model = Domain
fields = '__all__'
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
class CompetencySerializer(serializers.ModelSerializer):
class Meta:
model = Competency
fields = '__all__'
class EstablishmentCompetencySerializer(serializers.ModelSerializer):
class Meta:
model = EstablishmentCompetency
@ -203,7 +185,6 @@ class SchoolClassSerializer(serializers.ModelSerializer):
number_of_students=validated_data.get('number_of_students', 0),
teaching_language=validated_data.get('teaching_language', ''),
school_year=validated_data.get('school_year', ''),
levels=levels_data,
type=validated_data.get('type', 1),
time_range=validated_data.get('time_range', ['08:30', '17:30']),
opening_days=validated_data.get('opening_days', [1, 2, 4, 5]),
@ -211,6 +192,7 @@ class SchoolClassSerializer(serializers.ModelSerializer):
)
school_class.teachers.set(teachers_data)
school_class.levels.set(levels_data)
for planning_data in plannings_data:
Planning.objects.create(
@ -231,7 +213,6 @@ class SchoolClassSerializer(serializers.ModelSerializer):
instance.number_of_students = validated_data.get('number_of_students', instance.number_of_students)
instance.teaching_language = validated_data.get('teaching_language', instance.teaching_language)
instance.school_year = validated_data.get('school_year', instance.school_year)
instance.levels = levels_data
instance.type = validated_data.get('type', instance.type)
instance.time_range = validated_data.get('time_range', instance.time_range)
instance.opening_days = validated_data.get('opening_days', instance.opening_days)
@ -239,6 +220,7 @@ class SchoolClassSerializer(serializers.ModelSerializer):
instance.save()
instance.teachers.set(teachers_data)
instance.levels.set(levels_data)
existing_plannings = {planning.level: planning for planning in instance.plannings.all()}

View File

@ -1,68 +0,0 @@
import json
import os
from django.db.models.signals import post_migrate
from django.dispatch import receiver
from School.models import Domain, Category, Competency, PaymentModeType, PaymentPlanType
@receiver(post_migrate)
def school_post_migrate(sender, **kwargs):
if sender.name != "School":
return
# Chemin absolu vers le répertoire Back-End
backend_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Chemins vers les fichiers JSON
json_files = [
("Cycle1.json", 1),
("Cycle2.json", 2),
("Cycle3.json", 3),
("Cycle4.json", 4),
]
for file_name, cycle in json_files:
json_file_path = os.path.join(backend_dir, "competences", file_name)
if not os.path.exists(json_file_path):
print(f"Fichier JSON introuvable : {json_file_path}")
continue
with open(json_file_path, 'r', encoding='utf-8') as file:
data = json.load(file)
for domain_data in data['domaines']:
# Vérifiez si le domaine existe déjà
domain, _ = Domain.objects.get_or_create(name=domain_data['nom'], cycle=cycle)
for category_data in domain_data['categories']:
# Vérifiez si la catégorie existe déjà
category, _ = Category.objects.get_or_create(name=category_data['nom'], domain=domain)
for competency_data in category_data['competences']:
# Vérifiez si la compétence existe déjà
competency, _ = Competency.objects.get_or_create(
name=competency_data['nom'],
end_of_cycle=competency_data.get('fin_cycle', False),
level=competency_data.get('niveau'),
category=category
)
print(f"Données importées depuis : {json_file_path}")
payment_mode_types = [
{"code": "SEPA", "label": "Prélèvement SEPA"},
{"code": "TRANSFER", "label": "Virement"},
{"code": "CHECK", "label": "Chèque"},
{"code": "CASH", "label": "Espèce"},
]
for mode in payment_mode_types:
PaymentModeType.objects.get_or_create(code=mode["code"], defaults={"label": mode["label"]})
# ... après la création des PaymentModeType ...
payment_plan_types = [
{"code": "ONE_TIME", "label": "1 fois"},
{"code": "THREE_TIMES", "label": "3 fois"},
{"code": "TEN_TIMES", "label": "10 fois"},
{"code": "TWELVE_TIMES", "label": "12 fois"},
]
for plan in payment_plan_types:
PaymentPlanType.objects.get_or_create(code=plan["code"], defaults={"label": plan["label"]})

View File

@ -9,9 +9,6 @@ from .views import (
DiscountListCreateView, DiscountDetailView,
PaymentPlanListCreateView, PaymentPlanDetailView,
PaymentModeListCreateView, PaymentModeDetailView,
DomainListCreateView, DomainDetailView,
CategoryListCreateView, CategoryDetailView,
CompetencyListCreateView, CompetencyDetailView,
EstablishmentCompetencyListCreateView, EstablishmentCompetencyDetailView,
)
@ -40,15 +37,6 @@ urlpatterns = [
re_path(r'^paymentModes$', PaymentModeListCreateView.as_view(), name="payment_mode_list_create"),
re_path(r'^paymentModes/(?P<id>[0-9]+)$', PaymentModeDetailView.as_view(), name="payment_mode_detail"),
re_path(r'^domains$', DomainListCreateView.as_view(), name="domain_list_create"),
re_path(r'^domains/(?P<id>[0-9]+)$', DomainDetailView.as_view(), name="domain_detail"),
re_path(r'^categories$', CategoryListCreateView.as_view(), name="category_list_create"),
re_path(r'^categories/(?P<id>[0-9]+)$', CategoryDetailView.as_view(), name="category_detail"),
re_path(r'^competencies$', CompetencyListCreateView.as_view(), name="competency_list_create"),
re_path(r'^competencies/(?P<id>[0-9]+)$', CompetencyDetailView.as_view(), name="competency_detail"),
re_path(r'^establishmentCompetencies$', EstablishmentCompetencyListCreateView.as_view(), name="establishment_competency_list_create"),
re_path(r'^establishmentCompetencies/(?P<id>[0-9]+)$', EstablishmentCompetencyDetailView.as_view(), name="establishment_competency_detail"),
]

View File

@ -13,9 +13,6 @@ from .models import (
Fee,
PaymentPlan,
PaymentMode,
Domain,
Category,
Competency,
EstablishmentCompetency
)
from .serializers import (
@ -27,11 +24,9 @@ from .serializers import (
FeeSerializer,
PaymentPlanSerializer,
PaymentModeSerializer,
DomainSerializer,
CategorySerializer,
CompetencySerializer,
EstablishmentCompetencySerializer
)
from Common.models import Domain, Category, Competency
from N3wtSchool.bdd import delete_object, getAllObjects, getObject
from django.db.models import Q
from collections import defaultdict
@ -422,156 +417,6 @@ class PaymentModeDetailView(APIView):
def delete(self, request, id):
return delete_object(PaymentMode, id)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class DomainListCreateView(APIView):
def get(self, request):
domains = Domain.objects.all()
serializer = DomainSerializer(domains, many=True)
return JsonResponse(serializer.data, safe=False)
def post(self, request):
data = JSONParser().parse(request)
serializer = DomainSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False, status=status.HTTP_201_CREATED)
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class DomainDetailView(APIView):
def get(self, request, id):
try:
domain = Domain.objects.get(id=id)
serializer = DomainSerializer(domain)
return JsonResponse(serializer.data, safe=False)
except Domain.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
def put(self, request, id):
try:
domain = Domain.objects.get(id=id)
except Domain.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
data = JSONParser().parse(request)
serializer = DomainSerializer(domain, data=data, partial=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False)
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, id):
try:
domain = Domain.objects.get(id=id)
domain.delete()
return JsonResponse({'message': 'Deleted'}, safe=False)
except Domain.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
# Répète la même logique pour Category, Competency, EstablishmentCompetency
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class CategoryListCreateView(APIView):
def get(self, request):
categories = Category.objects.all()
serializer = CategorySerializer(categories, many=True)
return JsonResponse(serializer.data, safe=False)
def post(self, request):
data = JSONParser().parse(request)
serializer = CategorySerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False, status=status.HTTP_201_CREATED)
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class CategoryDetailView(APIView):
def get(self, request, id):
try:
category = Category.objects.get(id=id)
serializer = CategorySerializer(category)
return JsonResponse(serializer.data, safe=False)
except Category.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
def put(self, request, id):
try:
category = Category.objects.get(id=id)
except Category.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
data = JSONParser().parse(request)
serializer = CategorySerializer(category, data=data, partial=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False)
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, id):
try:
category = Category.objects.get(id=id)
category.delete()
return JsonResponse({'message': 'Deleted'}, safe=False)
except Category.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class CompetencyListCreateView(APIView):
def get(self, request):
cycle = request.GET.get('cycle')
if cycle is None:
return JsonResponse({'error': 'cycle est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
competencies_list = getAllObjects(Competency)
competencies_list = competencies_list.filter(
category__domain__cycle=cycle
).distinct()
serializer = CompetencySerializer(competencies_list, many=True)
return JsonResponse(serializer.data, safe=False)
def post(self, request):
data = JSONParser().parse(request)
serializer = CompetencySerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False, status=status.HTTP_201_CREATED)
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class CompetencyDetailView(APIView):
def get(self, request, id):
try:
competency = Competency.objects.get(id=id)
serializer = CompetencySerializer(competency)
return JsonResponse(serializer.data, safe=False)
except Competency.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
def put(self, request, id):
try:
competency = Competency.objects.get(id=id)
except Competency.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
data = JSONParser().parse(request)
serializer = CompetencySerializer(competency, data=data, partial=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, safe=False)
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, id):
try:
competency = Competency.objects.get(id=id)
competency.delete()
return JsonResponse({'message': 'Deleted'}, safe=False)
except Competency.DoesNotExist:
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class EstablishmentCompetencyListCreateView(APIView):