309 lines
8.9 KiB
Python
309 lines
8.9 KiB
Python
|
|
from django.db import models
|
|||
|
|
from datetime import date
|
|||
|
|
from django.utils import timezone
|
|||
|
|
|
|||
|
|
class Bailleur(models.Model):
|
|||
|
|
"""Modèle représentant un bailleur de fonds pour les projets de recherche."""
|
|||
|
|
nom_organisme = models.CharField(
|
|||
|
|
max_length=200,
|
|||
|
|
unique=True
|
|||
|
|
)
|
|||
|
|
contact = models.CharField(
|
|||
|
|
max_length=100,
|
|||
|
|
blank=True,
|
|||
|
|
null=True
|
|||
|
|
)
|
|||
|
|
email = models.EmailField(
|
|||
|
|
blank=True,
|
|||
|
|
null=True
|
|||
|
|
)
|
|||
|
|
pays = models.CharField(
|
|||
|
|
max_length=100,
|
|||
|
|
blank=True,
|
|||
|
|
null=True
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def __str__(self):
|
|||
|
|
return self.nom_organisme
|
|||
|
|
|
|||
|
|
class DomaineDeRecherche(models.Model):
|
|||
|
|
"""Modèle représentant les domaines de recherche"""
|
|||
|
|
|
|||
|
|
DOMAINE_RECHERCHE = [
|
|||
|
|
('sciences_sociales', 'Sciences sociales'),
|
|||
|
|
('naturelles', 'Naturelles'),
|
|||
|
|
('humaines', 'Humaines'),
|
|||
|
|
('veterinaires', 'Vétérinaires')
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
nom = models.CharField(
|
|||
|
|
max_length=100,
|
|||
|
|
verbose_name="Domaine de recherche",
|
|||
|
|
choices=DOMAINE_RECHERCHE,
|
|||
|
|
primary_key=True
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
class Meta:
|
|||
|
|
verbose_name = 'Domaine de recherche'
|
|||
|
|
verbose_name_plural = 'Domaines de recherche'
|
|||
|
|
|
|||
|
|
def __str__(self):
|
|||
|
|
return self.nom
|
|||
|
|
|
|||
|
|
class Projet(models.Model):
|
|||
|
|
"""Modèle représentant un projet de recherche avec ses caractéristiques et son bailleur associé."""
|
|||
|
|
TYPE_PROJET = [
|
|||
|
|
('laboratoire', 'Laboratoire'),
|
|||
|
|
('épidémiologie', 'Épidémiologie'),
|
|||
|
|
('sciences sociales', 'Sciences sociales'),
|
|||
|
|
('cliniques', 'Cliniques'),
|
|||
|
|
('autre', 'Autre'),
|
|||
|
|
]
|
|||
|
|
id_projet = models.CharField(
|
|||
|
|
max_length=100,
|
|||
|
|
blank=True,
|
|||
|
|
unique=True,
|
|||
|
|
primary_key=True,
|
|||
|
|
verbose_name="ID du projet"
|
|||
|
|
)
|
|||
|
|
nom_projet = models.CharField(
|
|||
|
|
max_length=200,
|
|||
|
|
verbose_name="Nom du projet"
|
|||
|
|
)
|
|||
|
|
date_debut = models.DateField(
|
|||
|
|
verbose_name="Date de début"
|
|||
|
|
)
|
|||
|
|
date_fin = models.DateField(
|
|||
|
|
verbose_name="Date de fin"
|
|||
|
|
)
|
|||
|
|
numero_convention = models.CharField(
|
|||
|
|
max_length=100,
|
|||
|
|
verbose_name="Numéro de convention"
|
|||
|
|
)
|
|||
|
|
description = models.TextField(
|
|||
|
|
verbose_name="Description"
|
|||
|
|
)
|
|||
|
|
type_projet = models.CharField(
|
|||
|
|
max_length=100,
|
|||
|
|
choices=TYPE_PROJET,
|
|||
|
|
default='épidémiologie',
|
|||
|
|
verbose_name="Type de projet"
|
|||
|
|
)
|
|||
|
|
domaine_recherche = models.ManyToManyField(DomaineDeRecherche)
|
|||
|
|
budget=models.DecimalField(
|
|||
|
|
max_digits=12,
|
|||
|
|
decimal_places=2,
|
|||
|
|
verbose_name="Budget"
|
|||
|
|
)
|
|||
|
|
budget_RH = models.DecimalField(
|
|||
|
|
max_digits=12,
|
|||
|
|
decimal_places=2,
|
|||
|
|
verbose_name="Budget RH"
|
|||
|
|
)
|
|||
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|||
|
|
bailleur = models.ForeignKey(
|
|||
|
|
Bailleur,
|
|||
|
|
on_delete=models.SET_NULL,
|
|||
|
|
null=True,
|
|||
|
|
blank=True,
|
|||
|
|
verbose_name="Bailleur de fonds"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def statut(self):
|
|||
|
|
if self.date_fin < date.today():
|
|||
|
|
return "Terminé"
|
|||
|
|
return "En cours"
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def avancement(self):
|
|||
|
|
aujourd_hui = date.today()
|
|||
|
|
if (self.date_debut and self.date_fin) and (self.date_debut < self.date_fin):
|
|||
|
|
duree_projet = (self.date_fin - self.date_debut).days
|
|||
|
|
temps_ecoule = (aujourd_hui - self.date_debut).days
|
|||
|
|
if duree_projet > 0:
|
|||
|
|
return round((temps_ecoule / duree_projet) * 100, 2)
|
|||
|
|
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
def __str__(self):
|
|||
|
|
return f"{self.nom_projet}"
|
|||
|
|
|
|||
|
|
class FinancementProjet(models.Model):
|
|||
|
|
"""
|
|||
|
|
Modèle représentant le financement d'un projet par un bailleur,
|
|||
|
|
avec le pourcentage de contribution.
|
|||
|
|
"""
|
|||
|
|
projet = models.ForeignKey(
|
|||
|
|
Projet,
|
|||
|
|
on_delete=models.CASCADE
|
|||
|
|
)
|
|||
|
|
bailleur = models.ForeignKey(
|
|||
|
|
Bailleur,
|
|||
|
|
on_delete=models.CASCADE
|
|||
|
|
)
|
|||
|
|
pourcentage = models.DecimalField(
|
|||
|
|
max_digits = 5,
|
|||
|
|
decimal_places=2
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
class Meta:
|
|||
|
|
unique_together = ('projet', 'bailleur')
|
|||
|
|
|
|||
|
|
def __str__(self):
|
|||
|
|
return f"{self.bailleur.nom} - {self.projet.nom_projet} ({self.pourcentage}%)"
|
|||
|
|
|
|||
|
|
class DocumentProjet(models.Model):
|
|||
|
|
"""Modèle représentant un document associé à un projet, avec des métadonnées et un fichier attaché."""
|
|||
|
|
NOM_DOCUMENT_CHOICES = [
|
|||
|
|
('protocole', 'Protocole d’étude'),
|
|||
|
|
('ethique', "Approbation du comité d'éthique"),
|
|||
|
|
('autorisation', 'Autorisation (DNLP)'),
|
|||
|
|
('rapport_technique', 'Rapport technique'),
|
|||
|
|
('rapport_financier', 'Rapport financier'),
|
|||
|
|
('rapport_avancement', "Rapport d'avancement"),
|
|||
|
|
('convention', 'Convention'),
|
|||
|
|
('rapport_final', 'Rapport final'),
|
|||
|
|
('autre', 'Autre'),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
projet = models.ForeignKey(
|
|||
|
|
Projet,
|
|||
|
|
on_delete=models.CASCADE,
|
|||
|
|
related_name='documents',
|
|||
|
|
verbose_name="Projet"
|
|||
|
|
)
|
|||
|
|
date_ajout = models.DateTimeField(
|
|||
|
|
auto_now_add=True,
|
|||
|
|
verbose_name="Date d'ajout"
|
|||
|
|
)
|
|||
|
|
nom_document = models.CharField(
|
|||
|
|
max_length = 100,
|
|||
|
|
choices = NOM_DOCUMENT_CHOICES,
|
|||
|
|
verbose_name="Type de document"
|
|||
|
|
)
|
|||
|
|
description = models.TextField(
|
|||
|
|
blank = True,
|
|||
|
|
verbose_name = "Description"
|
|||
|
|
)
|
|||
|
|
numero = models.CharField(
|
|||
|
|
max_length = 100,
|
|||
|
|
blank = True,
|
|||
|
|
null = True,
|
|||
|
|
verbose_name = "Numéro du document"
|
|||
|
|
)
|
|||
|
|
date_validite = models.DateField(
|
|||
|
|
blank = True,
|
|||
|
|
null = True,
|
|||
|
|
verbose_name = "Date de validité"
|
|||
|
|
)
|
|||
|
|
fichier = models.FileField(
|
|||
|
|
upload_to = 'documents_projets/',
|
|||
|
|
verbose_name = "Fichier à télécharger"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def __str__(self):
|
|||
|
|
return f"{self.nom_document} ({self.projet})"
|
|||
|
|
|
|||
|
|
class ActiviteProjet(models.Model):
|
|||
|
|
"""Modèle représentant le planning d'un projet, avec des activités associées et un statut."""
|
|||
|
|
projet = models.ForeignKey(
|
|||
|
|
Projet,
|
|||
|
|
on_delete = models.CASCADE,
|
|||
|
|
verbose_name = "Projet"
|
|||
|
|
)
|
|||
|
|
titre = models.CharField(
|
|||
|
|
max_length = 200,
|
|||
|
|
verbose_name = "Titre de l'activité"
|
|||
|
|
)
|
|||
|
|
description = models.TextField(
|
|||
|
|
blank = True,
|
|||
|
|
null = True,
|
|||
|
|
verbose_name = "Description de l'activité"
|
|||
|
|
)
|
|||
|
|
date_debut = models.DateField(verbose_name="Date de début")
|
|||
|
|
date_fin = models.DateField(verbose_name="Date de fin")
|
|||
|
|
annuler = models.BooleanField(
|
|||
|
|
default = False,
|
|||
|
|
verbose_name = "Annuler l'activité"
|
|||
|
|
)
|
|||
|
|
motif_annulation = models.TextField(
|
|||
|
|
blank = True,
|
|||
|
|
null = True,
|
|||
|
|
verbose_name = "Motif d'annulation"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
motif_changement_budget = models.TextField(
|
|||
|
|
blank = True,
|
|||
|
|
null = True,
|
|||
|
|
verbose_name = "Motif de changement de budget"
|
|||
|
|
)
|
|||
|
|
budget_prevu = models.DecimalField(
|
|||
|
|
max_digits = 15,
|
|||
|
|
decimal_places = 2,
|
|||
|
|
default = 0,
|
|||
|
|
verbose_name = "Budget prévu"
|
|||
|
|
)
|
|||
|
|
budget_depense = models.DecimalField(
|
|||
|
|
max_digits = 15,
|
|||
|
|
decimal_places = 2,
|
|||
|
|
default = 0,
|
|||
|
|
verbose_name = "Budget dépensé"
|
|||
|
|
)
|
|||
|
|
besoin_ressource_materielle = models.TextField(
|
|||
|
|
verbose_name="Besoin de ressources matérielles"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def statut(self):
|
|||
|
|
today = timezone.now().date()
|
|||
|
|
if not self.annuler:
|
|||
|
|
if self.date_fin < today:
|
|||
|
|
return 'Terminé'
|
|||
|
|
elif self.date_debut > today:
|
|||
|
|
return 'À venir'
|
|||
|
|
else:
|
|||
|
|
return 'En cours'
|
|||
|
|
else:
|
|||
|
|
return 'Annulé'
|
|||
|
|
|
|||
|
|
def __str__(self):
|
|||
|
|
return f"{self.titre} ({self.projet.nom_projet})"
|
|||
|
|
|
|||
|
|
# class LivrableAttendu(models.Model):
|
|||
|
|
# """
|
|||
|
|
# Modèle représentant un livrable attendu pour une activité de projet,
|
|||
|
|
# avec des critères de validation.
|
|||
|
|
# """
|
|||
|
|
# activite = models.ForeignKey(
|
|||
|
|
# ActiviteProjet,
|
|||
|
|
# on_delete = models.CASCADE,
|
|||
|
|
# related_name = "livrables_attendus"
|
|||
|
|
# )
|
|||
|
|
# nom = models.CharField(max_length=255)
|
|||
|
|
|
|||
|
|
# def __str__(self):
|
|||
|
|
# return f"{self.nom} (Activité: {self.activite.titre})"
|
|||
|
|
|
|||
|
|
class LivrablesLivres(models.Model):
|
|||
|
|
"""Modèle représentant un livrable livré pour une activité de projet."""
|
|||
|
|
activite = models.ForeignKey(
|
|||
|
|
ActiviteProjet,
|
|||
|
|
on_delete = models.CASCADE
|
|||
|
|
)
|
|||
|
|
# nom = models.ForeignKey(
|
|||
|
|
# LivrableAttendu,
|
|||
|
|
# on_delete = models.CASCADE
|
|||
|
|
# )
|
|||
|
|
nom = models.CharField(
|
|||
|
|
max_length=255,
|
|||
|
|
verbose_name="Nom du livrable"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
fichier = models.FileField(
|
|||
|
|
upload_to = 'fichier_livrables/',
|
|||
|
|
blank = True,
|
|||
|
|
null = True
|
|||
|
|
)
|
|||
|
|
def __str__(self):
|
|||
|
|
return self.nom
|