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 .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 ) # # 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) mail = models.EmailField(u"Adresse e-mail permanente", max_length=200, 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="") en_scolarite = models.BooleanField(default=False, blank=True) 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 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) if not created: if not profil.en_scolarite: profil.en_scolarite = True profil.save() return dirs = attributes.get("homeDirectory", "").split("/") if len(dirs) < 4: print("HomeDirectory invalide", dirs) return year = dirs[2] departement = dirs[3] print(departement, dirs) dep = dict(DEPARTEMENTS_DEFAUT).get(departement.lower(), "") profil.promotion = "%s %s" % (dep, year) profil.nom = attributes.get("name", "") profil.en_scolarite = True 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]