experiENS/avisstage/models.py

329 lines
11 KiB
Python

from django.db import models
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.contrib.gis.db import models as geomodels
from django.template.defaultfilters import slugify
from django.forms.widgets import DateInput
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 taggit_autosuggest.managers import TaggableManager
from tinymce.models import HTMLField as RichTextField
from authens.signals import post_cas_connect
from authens.models import CASAccount
from datetime import timedelta
from .utils import choices_length, is_email_ens
from .statics import (
DEPARTEMENTS_DEFAUT, PAYS_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS, TYPE_LIEU_DICT,
TYPE_STAGE_DICT, NIVEAU_SCOL_OPTIONS, NIVEAU_SCOL_DICT
)
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("Nom complet", max_length=255, blank=True)
promotion = models.CharField("Promotion", max_length=40, blank=True)
contactez_moi = models.BooleanField(
"Inviter les visiteurs à me contacter",
default=True, help_text="Affiche votre adresse e-mail principale sur votre profil public")
bio = models.TextField("À propos de moi", blank=True, default="")
last_cas_login = models.DateField(default=_default_cas_login)
class Meta:
verbose_name = "Profil élève"
verbose_name_plural = "Profils élèves"
def __str__(self):
return "%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("Nom de l'institution d'accueil",
max_length=250)
type_lieu = models.CharField("Type de structure d'accueil",
default="universite",
choices=TYPE_LIEU_OPTIONS,
max_length=choices_length(TYPE_LIEU_OPTIONS))
# Infos géographiques
ville = models.CharField("Ville",
max_length=200)
pays = models.CharField("Pays",
choices=PAYS_OPTIONS,
max_length=choices_length(PAYS_OPTIONS))
# Coordonnées
#objects = geomodels.GeoManager() # Requis par GeoDjango
coord = geomodels.PointField("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 "%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("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("Visible publiquement", default=False)
date_creation = models.DateTimeField("Créé le", default=timezone.now)
date_maj = models.DateTimeField("Mis à jour le", default=timezone.now)
len_avis_stage = models.IntegerField("Longueur des avis de stage", default=0)
len_avis_lieux = models.IntegerField("Longueur des avis de lieu", default=0)
# Caractéristiques du stage
sujet = models.CharField("Sujet", max_length=500)
date_debut = models.DateField("Date de début", null=True)
date_fin = models.DateField("Date de fin", null=True)
type_stage = models.CharField("Type",
default="stage",
choices=TYPE_STAGE_OPTIONS,
max_length=choices_length(TYPE_STAGE_OPTIONS))
niveau_scol = models.CharField("Année de scolarité",
default="",
choices=NIVEAU_SCOL_OPTIONS,
max_length=choices_length(NIVEAU_SCOL_OPTIONS),
blank=True)
thematiques = TaggableManager("Thématiques", blank=True)
matieres = models.ManyToManyField(StageMatiere, verbose_name="Matière(s)", related_name="stages")
encadrants = models.CharField("Encadrant⋅e⋅s", max_length=500, blank=True)
structure = models.CharField("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 "%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("En quelques mots", blank=True)
avis_ambiance = RichTextField("L'ambiance de travail", blank=True)
avis_sujet = RichTextField("La mission", blank=True)
avis_admin = RichTextField("Formalités et administration", blank=True)
avis_prestage = RichTextField("Avant le stage", blank=True, default="")
les_plus = models.TextField("Les plus de cette expérience", blank=True)
les_moins = models.TextField("Les moins de cette expérience", blank=True)
def __str__(self):
return "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("En quelques mots", blank=True)
avis_lieustage = RichTextField("Les lieux de travail", blank=True)
avis_pratique = RichTextField("S'installer - conseils pratiques",
blank=True)
avis_tourisme = RichTextField("Dans les parages", blank=True)
les_plus = models.TextField("Les plus du lieu", blank=True)
les_moins = models.TextField("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 "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]