experiENS/avisstage/models.py
2021-06-28 23:57:16 +02:00

362 lines
11 KiB
Python

from datetime import timedelta
from authens.models import CASAccount
from authens.signals import post_cas_connect
from taggit_autosuggest.managers import TaggableManager
from tinymce.models import HTMLField as RichTextField
from django.contrib.auth.models import User
from django.contrib.gis.db import models as geomodels
from django.db import models
from django.db.models.signals import post_save
from django.forms.widgets import DateInput
from django.template.defaultfilters import slugify
from django.urls import reverse
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.html import strip_tags
from .statics import (
DEPARTEMENTS_DEFAUT,
NIVEAU_SCOL_DICT,
NIVEAU_SCOL_OPTIONS,
PAYS_OPTIONS,
TYPE_LIEU_DICT,
TYPE_LIEU_OPTIONS,
TYPE_STAGE_DICT,
TYPE_STAGE_OPTIONS,
)
from .utils import choices_length, is_email_ens
def _default_cas_login():
return (timezone.now() - timedelta(days=365)).date()
#
# Profil Normalien (extension du modèle User)
#
class Normalien(models.Model):
user = models.OneToOneField(
User, related_name="profil", on_delete=models.SET_NULL, null=True
)
# Infos spécifiques
nom = models.CharField(u"Nom complet", max_length=255, blank=True)
promotion = models.CharField(u"Promotion", max_length=40, blank=True)
contactez_moi = models.BooleanField(
u"Inviter les visiteurs à me contacter",
default=True,
help_text="Affiche votre adresse e-mail principale sur votre profil public",
)
bio = models.TextField(u"À propos de moi", blank=True, default="")
last_cas_login = models.DateField(default=_default_cas_login)
class Meta:
verbose_name = u"Profil élève"
verbose_name_plural = u"Profils élèves"
def __str__(self):
return u"%s (%s)" % (self.nom, self.user.username)
# Liste des stages publiés
def stages_publics(self):
return self.stages.filter(public=True).order_by("-date_debut")
def has_nonENS_email(self):
return (
self.user.email_address_set.exclude(confirmed_at__isnull=True)
.exclude(email__endswith="ens.fr")
.exclude(email__endswith="ens.psl.eu")
.exists()
)
def nom_complet(self):
if self.nom.strip():
return self.nom
return self.user.username
@property
def en_scolarite(self):
return self.last_cas_login > (timezone.now() - timedelta(days=60)).date()
@property
def preferred_email(self):
return self.user.email
# Hook à la création d'un nouvel utilisateur : information de base
def create_basic_user_profile(sender, instance, created, **kwargs):
if created:
profil, created = Normalien.objects.get_or_create(user=instance)
if not created and profil.promotion != "":
return
if "@" in instance.username:
profil.promotion = instance.username.split("@")[1]
profil.save()
post_save.connect(create_basic_user_profile, sender=User)
# Hook d'authENS : information du CAS
def handle_cas_connection(sender, instance, created, cas_login, attributes, **kwargs):
profil, created = Normalien.objects.get_or_create(user=instance)
profil.last_cas_login = timezone.now().date()
if not created:
profil.save()
return
dirs = attributes.get("homeDirectory", "").split("/")
if len(dirs) < 4:
print("HomeDirectory invalide", dirs)
return
year = dirs[2]
departement = dirs[3]
dep = dict(DEPARTEMENTS_DEFAUT).get(departement.lower(), "")
profil.promotion = "%s %s" % (dep, year)
profil.nom = attributes.get("name", "")
profil.save()
post_cas_connect.connect(handle_cas_connection, sender=User)
#
# Lieu de stage
#
class Lieu(models.Model):
# Général
nom = models.CharField(u"Nom de l'institution d'accueil", max_length=250)
type_lieu = models.CharField(
u"Type de structure d'accueil",
default="universite",
choices=TYPE_LIEU_OPTIONS,
max_length=choices_length(TYPE_LIEU_OPTIONS),
)
# Infos géographiques
ville = models.CharField(u"Ville", max_length=200)
pays = models.CharField(
u"Pays", choices=PAYS_OPTIONS, max_length=choices_length(PAYS_OPTIONS)
)
# Coordonnées
# objects = geomodels.GeoManager() # Requis par GeoDjango
coord = geomodels.PointField(u"Coordonnées", geography=True, srid=4326)
# Type du lieu en plus joli
@property
def type_lieu_fancy(self):
return TYPE_LIEU_DICT.get(self.type_lieu, ("lieu", False))[0]
@property
def type_lieu_fem(self):
return TYPE_LIEU_DICT.get(self.type_lieu, ("lieu", False))[1]
def __str__(self):
return u"%s (%s)" % (self.nom, self.ville)
class Meta:
verbose_name = "Lieu"
verbose_name_plural = "Lieux"
#
# Matières des stages
#
class StageMatiere(models.Model):
nom = models.CharField(u"Nom", max_length=30)
slug = models.SlugField()
class Meta:
verbose_name = "Matière des stages"
verbose_name_plural = "Matières des stages"
def __str__(self):
return self.nom
#
# Un stage
#
class Stage(models.Model):
# Misc
auteur = models.ForeignKey(
Normalien, related_name="stages", on_delete=models.SET_NULL, null=True
)
public = models.BooleanField(u"Visible publiquement", default=False)
date_creation = models.DateTimeField(u"Créé le", default=timezone.now)
date_maj = models.DateTimeField(u"Mis à jour le", default=timezone.now)
len_avis_stage = models.IntegerField(u"Longueur des avis de stage", default=0)
len_avis_lieux = models.IntegerField(u"Longueur des avis de lieu", default=0)
# Caractéristiques du stage
sujet = models.CharField(u"Sujet", max_length=500)
date_debut = models.DateField(u"Date de début", null=True)
date_fin = models.DateField(u"Date de fin", null=True)
type_stage = models.CharField(
u"Type",
default="stage",
choices=TYPE_STAGE_OPTIONS,
max_length=choices_length(TYPE_STAGE_OPTIONS),
)
niveau_scol = models.CharField(
u"Année de scolarité",
default="",
choices=NIVEAU_SCOL_OPTIONS,
max_length=choices_length(NIVEAU_SCOL_OPTIONS),
blank=True,
)
thematiques = TaggableManager(u"Thématiques", blank=True)
matieres = models.ManyToManyField(
StageMatiere, verbose_name=u"Matière(s)", related_name="stages"
)
encadrants = models.CharField(u"Encadrant⋅e⋅s", max_length=500, blank=True)
structure = models.CharField(u"Structure d'accueil", max_length=500, blank=True)
# Avis
lieux = models.ManyToManyField(
Lieu, related_name="stages", through="AvisLieu", blank=True
)
# Affichage des avis ordonnés
@property
def avis_lieux(self):
return self.avislieu_set.order_by("order")
# Shortcut pour affichage rapide
@property
def lieu_principal(self):
avis_lieux = self.avis_lieux
if len(avis_lieux) == 0:
return None
return self.avis_lieux[0].lieu
# Type du stage en plus joli
@property
def type_stage_fancy(self):
return TYPE_STAGE_DICT.get(self.type_stage, ("stage", False))[0]
@property
def type_stage_fem(self):
return TYPE_STAGE_DICT.get(self.type_stage, ("stage", False))[1]
# Niveau scolaire en plus joli
@property
def niveau_scol_fancy(self):
return NIVEAU_SCOL_DICT.get(self.niveau_scol, "")
# Optimisation de requêtes
@cached_property
def all_lieux(self):
return self.lieux.all()
def get_absolute_url(self):
return reverse("avisstage:stage", self)
def __str__(self):
return u"%s (par %s)" % (self.sujet, self.auteur.user.username)
def update_stats(self, save=True):
def get_len(obj):
length = 0
avis = obj.avis_all
for k, av in avis:
length += len(av.split())
length += len(obj.chapo.split())
length += len(obj.les_plus.split())
length += len(obj.les_moins.split())
return length
if self.avis_stage:
self.len_avis_stage = get_len(self.avis_stage)
self.len_avis_lieux = 0
for avis in self.avislieu_set.all():
self.len_avis_lieux += get_len(avis)
if save:
self.save()
class Meta:
verbose_name = "Stage"
#
# Les avis
#
class AvisStage(models.Model):
stage = models.OneToOneField(
Stage, related_name="avis_stage", on_delete=models.CASCADE
)
chapo = models.TextField(u"En quelques mots", blank=True)
avis_ambiance = RichTextField(u"L'ambiance de travail", blank=True)
avis_sujet = RichTextField(u"La mission", blank=True)
avis_admin = RichTextField(u"Formalités et administration", blank=True)
avis_prestage = RichTextField(u"Avant le stage", blank=True, default="")
les_plus = models.TextField(u"Les plus de cette expérience", blank=True)
les_moins = models.TextField(u"Les moins de cette expérience", blank=True)
def __str__(self):
return u"Avis sur {%s} par %s" % (
self.stage.sujet,
self.stage.auteur.user.username,
)
# Liste des champs d'avis, couplés à leur nom (pour l'affichage)
@property
def avis_all(self):
fields = ["avis_sujet", "avis_ambiance", "avis_admin", "avis_prestage"]
return [
(AvisStage._meta.get_field(field).verbose_name, getattr(self, field, ""))
for field in fields
]
class AvisLieu(models.Model):
stage = models.ForeignKey(Stage, on_delete=models.CASCADE)
lieu = models.ForeignKey(Lieu, on_delete=models.CASCADE)
order = models.IntegerField("Ordre", default=0)
chapo = models.TextField(u"En quelques mots", blank=True)
avis_lieustage = RichTextField(u"Les lieux de travail", blank=True)
avis_pratique = RichTextField(u"S'installer - conseils pratiques", blank=True)
avis_tourisme = RichTextField(u"Dans les parages", blank=True)
les_plus = models.TextField(u"Les plus du lieu", blank=True)
les_moins = models.TextField(u"Les moins du lieu", blank=True)
class Meta:
verbose_name = "Avis sur un lieu de stage"
verbose_name_plural = "Avis sur un lieu de stage"
def __str__(self):
return u"Avis sur {%s} par %s" % (self.lieu.nom, self.stage.auteur.user_id)
# Liste des champs d'avis, couplés à leur nom (pour l'affichage)
@property
def avis_all(self):
fields = ["avis_lieustage", "avis_pratique", "avis_tourisme"]
return [
(AvisLieu._meta.get_field(field).verbose_name, getattr(self, field, ""))
for field in fields
]