structure du projet + docker, back: mise en place BD et apps, front: début de dev pour le header et mise en place du thème et css global (override des variables bootstrap)

This commit is contained in:
2026-06-01 15:21:47 +02:00
parent b3c027794c
commit e8e6122a45
111 changed files with 6778 additions and 1 deletions
View File
+3
View File
@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class CoreConfig(AppConfig):
name = 'api.core'
@@ -0,0 +1,46 @@
# Generated by Django 6.0.5 on 2026-05-28 15:03
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='GDPR_Event',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('event_type', models.CharField(choices=[('DATA_EXPORT', 'Export de données'), ('DATA_ANONYMIZATION', 'Anonymisation de données'), ('CONSENT_WITHDRAWAL', 'Consentement retiré')], max_length=50, verbose_name="type d'événement")),
('subject_reference', models.CharField(help_text="Objet concerné par l'événement : Class:id", max_length=255, verbose_name='objet concerné')),
('fields_touched', models.JSONField(blank=True, help_text="Liste des champs affectés par l'événement.", verbose_name='champs affectés')),
('reason', models.CharField(choices=[('USER_REQUEST', 'Demande utilisateur'), ('RETENTION_POLICY', 'Politique de conservation')], max_length=50, verbose_name='raison')),
('done_at', models.DateTimeField(auto_now_add=True, verbose_name='date et heure')),
],
options={
'verbose_name': 'Événement RGPD',
'verbose_name_plural': 'Événements RGPD',
},
),
migrations.CreateModel(
name='GlobalVariable',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='créé le')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='modifié le')),
('obsolete', models.BooleanField(default=False, verbose_name='obsolète')),
('obsolete_at', models.DateTimeField(blank=True, null=True, verbose_name='obsolète le')),
('key', models.CharField(max_length=255, unique=True, verbose_name='clé')),
('description', models.TextField(blank=True, default='', verbose_name='description')),
('value', models.JSONField(blank=True, null=True, verbose_name='valeur')),
],
options={
'verbose_name': 'Paramètre global',
'verbose_name_plural': 'Paramètres globaux',
},
),
]
@@ -0,0 +1,33 @@
# Generated by Django 6.0.5 on 2026-05-28 15:03
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('core', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='gdpr_event',
name='done_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Effectué par'),
),
migrations.AddField(
model_name='globalvariable',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='créé par'),
),
migrations.AddField(
model_name='globalvariable',
name='updated_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='modifié par'),
),
]
+9
View File
@@ -0,0 +1,9 @@
from .gdpr_event import GDPR_Event
from .global_variable import GlobalVariable
from .trackable_model import TrackableModel
__all__ = [
"GDPR_Event",
"GlobalVariable",
"TrackableModel",
]
+51
View File
@@ -0,0 +1,51 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
class GDPR_Event(models.Model):
"""
Model to log GDPR-related events for auditing purposes.
"""
EVENT_TYPES = [
("DATA_EXPORT", _("Export de données")),
("DATA_ANONYMIZATION", _("Anonymisation de données")),
("CONSENT_WITHDRAWAL", _("Consentement retiré")),
]
EVENT_REASON = [
("USER_REQUEST", _("Demande utilisateur")),
("RETENTION_POLICY", _("Politique de conservation")),
]
event_type = models.CharField(
max_length=50, choices=EVENT_TYPES, verbose_name=_("type d'événement")
)
subject_reference = models.CharField(
max_length=255,
verbose_name=_("objet concerné"),
help_text=_("Objet concerné par l'événement : Class:id"),
)
fields_touched = models.JSONField(
blank=True,
verbose_name=_("champs affectés"),
help_text=_("Liste des champs affectés par l'événement."),
)
reason = models.CharField(
max_length=50, verbose_name=_("raison"), choices=EVENT_REASON
)
done_at = models.DateTimeField(auto_now_add=True, verbose_name=_("date et heure"))
done_by = models.ForeignKey(
"users.User",
null=True,
blank=True,
on_delete=models.SET_NULL,
verbose_name=_("Effectué par"),
)
class Meta:
verbose_name = _("Événement RGPD")
verbose_name_plural = _("Événements RGPD")
def __str__(self):
return f"{self.get_event_type_display()} - {self.done_at} - {self.subject_reference}"
@@ -0,0 +1,23 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from api.core.models.trackable_model import TrackableModel
class GlobalVariable(TrackableModel):
"""
Model to store global key-value pairs for application-wide settings.
"""
key = models.CharField(max_length=255, unique=True, verbose_name=_("clé"))
description = models.TextField(
blank=True, default="", verbose_name=_("description")
)
value = models.JSONField(verbose_name=_("valeur"), null=True, blank=True)
class Meta:
verbose_name = _("Paramètre global")
verbose_name_plural = _("Paramètres globaux")
def __str__(self):
return f"{self.key}: {self.value}"
@@ -0,0 +1,95 @@
from django.utils import timezone
from django.db import models
from django.conf import settings
from django.utils.translation import gettext_lazy as _
class TrackableModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("créé le"))
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
blank=True,
on_delete=models.PROTECT,
related_name="+",
verbose_name=_("créé par"),
)
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("modifié le"))
updated_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
blank=True,
on_delete=models.PROTECT,
related_name="+",
verbose_name=_("modifié par"),
)
obsolete = models.BooleanField(default=False, verbose_name=_("obsolète"))
obsolete_at = models.DateTimeField(
null=True, blank=True, verbose_name=_("obsolète le")
)
class Meta:
abstract = True
def soft_delete(self, cascade_m2m=True):
"""
Soft delete of trackable models. Objct becomes obsolete and obsolescence date is indicated.
"""
if cascade_m2m:
self._cascade_soft_delete_m2m()
self.obsolete = True
self.obsolete_at = timezone.now()
self.save(update_fields=["obsolete", "obsolete_at"])
def _cascade_soft_delete_m2m(self):
"""
Propagation of soft delete to M2M fields.
"""
for field in self._meta.get_fields():
if field.many_to_many and not field.auto_created:
through_model = field.remote_field.through
if issubclass(through_model, TrackableModel):
field_name = None
for through_field in through_model._meta.get_fields():
if through_field.related_model == self.__class__:
field_name = through_field.name
break
if field_name:
filter_kwargs = {field_name: self}
for relation in through_model.objects.filter(
**filter_kwargs, obsolete=False
):
relation.soft_delete(cascade_m2m=False)
def restore(self, cascade_m2m=True):
"""
Restore an object that was soft deleted.
"""
if cascade_m2m:
self._cascade_restore_m2m()
self.obsolete = False
self.obsolete_at = None
self.save(update_fields=["obsolete", "obsolete_at"])
def _cascade_restore_m2m(self):
for field in self._meta.get_fields():
if field.many_to_many and not field.auto_created:
through_model = field.remote_field.through
if issubclass(through_model, TrackableModel):
field_name = None
for through_field in through_model._meta.get_fields():
if through_field.related_model == self.__class__:
field_name = through_field.name
break
if field_name:
filter_kwargs = {field_name: self}
for relation in through_model.objects.filter(
**filter_kwargs, obsolete=True
):
relation.restore(cascade_m2m=False)
+5
View File
@@ -0,0 +1,5 @@
from django.urls import path, include
urlpatterns = [
]
+3
View File
@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.