epcstages/stages/models.py

693 lines
27 KiB
Python
Raw Normal View History

import json
from collections import OrderedDict
from contextlib import suppress
from datetime import date, timedelta
2012-11-06 17:54:33 +01:00
2017-08-30 17:58:53 +02:00
from django.conf import settings
2012-11-06 17:54:33 +01:00
from django.db import models
2018-06-16 18:14:29 +02:00
from django.db.models import Case, Count, When
2012-11-06 17:54:33 +01:00
from . import utils
CIVILITY_CHOICES = (
('Madame', 'Madame'),
('Monsieur', 'Monsieur'),
)
2012-11-06 17:54:33 +01:00
class Section(models.Model):
""" Filières """
2019-11-20 13:34:27 +01:00
name = models.CharField("Nom", max_length=20)
has_stages = models.BooleanField("Planifie la PP sur ce site", default=False)
2012-11-06 17:54:33 +01:00
class Meta:
verbose_name = "Filière"
2015-09-22 20:52:35 +02:00
def __str__(self):
2012-11-06 17:54:33 +01:00
return self.name
2018-07-16 09:19:24 +02:00
@property
def is_fe(self):
"""fe=formation en entreprise"""
return self.name in {'ASA', 'ASE', 'ASSC'}
2018-07-16 09:19:24 +02:00
@property
def is_EPC(self):
return self.name in {'ASA', 'ASE', 'ASSC', 'EDE', 'EDS'}
@property
def is_ESTER(self):
return self.name in {'MP_ASE', 'MP_ASSC'}
2012-11-06 17:54:33 +01:00
2012-11-23 11:31:06 +01:00
class Level(models.Model):
name = models.CharField(max_length=10, verbose_name='Nom')
class Meta:
verbose_name = "Niveau"
2012-12-13 00:00:04 +01:00
verbose_name_plural = "Niveaux"
2012-11-23 11:31:06 +01:00
2015-09-22 20:52:35 +02:00
def __str__(self):
2012-11-23 11:31:06 +01:00
return self.name
def delta(self, diff):
if diff == 0:
return self
try:
return Level.objects.get(name=str(int(self.name)+diff))
except Level.DoesNotExist:
return None
2012-11-23 11:31:06 +01:00
2018-06-16 18:14:29 +02:00
class ActiveKlassManager(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(
num_students=Count(Case(When(student__archived=False, then=1)))
).filter(num_students__gt=0)
class Klass(models.Model):
2017-07-19 10:44:12 +02:00
name = models.CharField(max_length=10, verbose_name='Nom', unique=True)
section = models.ForeignKey(Section, verbose_name='Filière', on_delete=models.PROTECT)
level = models.ForeignKey(Level, verbose_name='Niveau', on_delete=models.PROTECT)
2017-07-11 23:01:46 +02:00
teacher = models.ForeignKey('Teacher', blank=True, null=True,
on_delete=models.SET_NULL, verbose_name='Maître de classe')
2019-02-06 17:46:24 +01:00
teacher_ecg = models.ForeignKey('Teacher', blank=True, null=True,
on_delete=models.SET_NULL, verbose_name='Maître ECG', related_name='+')
teacher_eps = models.ForeignKey('Teacher', blank=True, null=True,
on_delete=models.SET_NULL, verbose_name='Maître EPS', related_name='+')
2018-06-16 18:14:29 +02:00
objects = models.Manager()
active = ActiveKlassManager()
class Meta:
verbose_name = "Classe"
2015-09-22 20:52:35 +02:00
def __str__(self):
return self.name
def is_Ede_pe(self):
2018-11-16 09:17:20 +01:00
return 'EDE' in self.name and 'pe' in self.name
def is_Ede_ps(self):
2018-11-16 09:17:20 +01:00
return 'EDE' in self.name and 'ps' in self.name
2017-07-11 23:01:46 +02:00
class Teacher(models.Model):
civility = models.CharField(max_length=10, choices=CIVILITY_CHOICES, verbose_name='Civilité')
2017-07-11 23:01:46 +02:00
first_name = models.CharField(max_length=40, verbose_name='Prénom')
last_name = models.CharField(max_length=40, verbose_name='Nom')
abrev = models.CharField(max_length=10, verbose_name='Sigle')
2017-07-14 18:27:43 +02:00
birth_date = models.DateField(verbose_name='Date de naissance', blank=True, null=True)
2017-07-11 23:01:46 +02:00
email = models.EmailField(verbose_name='Courriel', blank=True)
contract = models.CharField(max_length=20, verbose_name='Contrat')
rate = models.DecimalField(default=0.0, max_digits=4, decimal_places=1, verbose_name="Taux d'activité")
ext_id = models.IntegerField(blank=True, null=True)
previous_report = models.IntegerField(default=0, verbose_name='Report précédent')
next_report = models.IntegerField(default=0, verbose_name='Report suivant')
can_examinate = models.BooleanField("Peut corriger examens candidats", default=False)
archived = models.BooleanField(default=False)
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True,
verbose_name='Compte utilisateur'
)
2017-07-11 23:01:46 +02:00
class Meta:
verbose_name='Enseignant'
ordering = ('last_name', 'first_name')
def __str__(self):
return '{0} {1}'.format(self.last_name, self.first_name)
@property
def full_name(self):
return '{0} {1}'.format(self.first_name, self.last_name)
@property
def civility_full_name(self):
return '{0} {1} {2}'.format(self.civility, self.first_name, self.last_name)
@property
def role(self):
return {'Monsieur': 'enseignant-formateur', 'Madame': 'enseignante-formatrice'}.get(self.civility, '')
def calc_activity(self):
"""
Return a dictionary of calculations relative to teacher courses.
2017-08-22 11:26:18 +02:00
Store plus/minus periods to self.next_report.
"""
mandats = self.course_set.filter(subject__startswith='#')
ens = self.course_set.exclude(subject__startswith='#')
tot_mandats = mandats.aggregate(models.Sum('period'))['period__sum'] or 0
tot_ens = ens.aggregate(models.Sum('period'))['period__sum'] or 0
2017-08-21 14:01:02 +02:00
# formation periods calculated at pro-rata of total charge
2017-08-30 17:58:53 +02:00
tot_formation = int(round(
(tot_mandats + tot_ens) / settings.MAX_ENS_PERIODS * settings.MAX_ENS_FORMATION
))
tot_trav = self.previous_report + tot_mandats + tot_ens + tot_formation
tot_paye = tot_trav
2017-08-30 17:58:53 +02:00
max_periods = settings.MAX_ENS_PERIODS + settings.MAX_ENS_FORMATION
2017-08-21 14:01:02 +02:00
# Special situations triggering reporting (positive or negative) hours for next year:
# - full-time teacher with a total charge under 100%
# - teachers with a total charge over 100%
2017-08-22 11:26:18 +02:00
self.next_report = 0
2017-08-21 14:01:02 +02:00
if (self.rate == 100 and tot_paye < max_periods) or (tot_paye > max_periods):
tot_paye = max_periods
2017-08-22 11:26:18 +02:00
self.next_report = tot_trav - tot_paye
self.save()
2017-08-21 14:01:02 +02:00
return {
'mandats': mandats,
'tot_mandats': tot_mandats,
'tot_ens': tot_ens,
'tot_formation': tot_formation,
'tot_trav': tot_trav,
'tot_paye': tot_paye,
2017-08-21 14:01:02 +02:00
'report': self.next_report,
}
2018-06-06 08:41:31 +02:00
def calc_imputations(self, ratios):
"""
Return a tuple for accountings charges
"""
activities = self.calc_activity()
imputations = OrderedDict(
2018-06-06 08:41:31 +02:00
[('ASAFE', 0), ('ASSCFE', 0), ('ASEFE', 0), ('MPTS', 0), ('MPS', 0), ('EDEpe', 0), ('EDEps', 0),
('EDS', 0), ('CAS_FPP', 0)]
)
courses = self.course_set.all()
for key in imputations:
imputations[key] = courses.filter(imputation__contains=key).aggregate(models.Sum('period'))['period__sum'] or 0
2018-06-06 08:41:31 +02:00
# Spliting imputations for EDE, ASE and ASSC
ede = courses.filter(imputation='EDE').aggregate(models.Sum('period'))['period__sum'] or 0
if ede > 0:
2018-06-06 08:41:31 +02:00
pe = int(round(ede * ratios['edepe'], 0))
imputations['EDEpe'] += pe
imputations['EDEps'] += ede - pe
ase = courses.filter(imputation='ASE').aggregate(models.Sum('period'))['period__sum'] or 0
if ase > 0:
asefe = int(round(ase * ratios['asefe'], 0))
imputations['ASEFE'] += asefe
imputations['MPTS'] += ase - asefe
assc = courses.filter(imputation='ASSC').aggregate(models.Sum('period'))['period__sum'] or 0
if assc > 0:
asscfe = int(round(assc * ratios['asscfe'], 0))
imputations['ASSCFE'] += asscfe
imputations['MPS'] += assc - asscfe
2018-05-09 09:35:28 +02:00
# Split formation periods in proportions
tot = sum(imputations.values())
if tot > 0:
for key in imputations:
2018-06-06 08:41:31 +02:00
imputations[key] += round(imputations[key] / tot * activities['tot_formation'],0)
2018-05-09 09:35:28 +02:00
return (activities, imputations)
def total_logbook(self):
return LogBook.objects.filter(teacher=self).aggregate(models.Sum('nb_period'))['nb_period__sum']
total_logbook.short_description = 'Solde du carnet du lait'
2017-07-11 23:01:46 +02:00
2018-02-19 10:08:46 +01:00
class LogBookReason(models.Model):
name = models.CharField('Motif', max_length=50, unique=True)
def __str__(self):
return self.name
class Meta:
verbose_name = 'Motif de carnet du lait'
2019-02-21 09:20:13 +01:00
verbose_name_plural = 'Motifs de carnet du lait'
2018-02-19 10:08:46 +01:00
class LogBook(models.Model):
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE, verbose_name='Enseignant')
reason = models.ForeignKey(LogBookReason, on_delete=models.PROTECT, verbose_name='Catégorie de motif')
input_date = models.DateField('Date de saisie', auto_now_add=True)
start_date = models.DateField('Date de début')
end_date = models.DateField('Date de fin')
nb_period = models.IntegerField('Périodes')
comment = models.CharField('Commentaire motif', max_length=200, blank=True)
def __str__(self):
return '{} : {} pér. - {}'.format(self.teacher, self.nb_period, self.comment)
class Meta:
verbose_name = 'Carnet du lait'
verbose_name_plural = 'Carnets du lait'
2017-08-29 16:25:34 +02:00
class Option(models.Model):
name = models.CharField("Nom", max_length=100, unique=True)
def __str__(self):
return self.name
class ExamEDESession(models.Model):
year = models.PositiveIntegerField()
season = models.CharField('saison', max_length=10)
class Meta:
verbose_name = "Session dexamen"
ordering = ['-year', 'season']
def __str__(self):
return '{0} {1}'.format(self.year, self.season)
2017-10-25 08:54:22 +02:00
GENDER_CHOICES = (
('M', 'Masculin'),
('F', 'Féminin'),
)
2012-11-06 17:54:33 +01:00
class Student(models.Model):
2019-10-07 14:42:42 +02:00
ACQ_MARK_CHOICES = (
('non', 'Non acquis'),
('part', 'Partiellement acquis'),
('acq', 'Acquis'),
)
2012-11-12 20:02:06 +01:00
ext_id = models.IntegerField(null=True, unique=True, verbose_name='ID externe')
2012-11-06 17:54:33 +01:00
first_name = models.CharField(max_length=40, verbose_name='Prénom')
last_name = models.CharField(max_length=40, verbose_name='Nom')
2017-10-25 08:54:22 +02:00
gender = models.CharField('Genre', max_length=3, blank=True, choices=GENDER_CHOICES)
birth_date = models.DateField('Date de naissance', null=True, blank=True)
2013-01-02 14:40:24 +01:00
street = models.CharField(max_length=150, blank=True, verbose_name='Rue')
2012-11-06 17:54:33 +01:00
pcode = models.CharField(max_length=4, verbose_name='Code postal')
city = models.CharField(max_length=40, verbose_name='Localité')
2017-07-11 23:15:00 +02:00
district = models.CharField(max_length=20, blank=True, verbose_name='Canton')
2012-12-13 14:41:13 +01:00
tel = models.CharField(max_length=40, blank=True, verbose_name='Téléphone')
2013-01-02 14:40:24 +01:00
mobile = models.CharField(max_length=40, blank=True, verbose_name='Portable')
2012-12-13 14:41:13 +01:00
email = models.EmailField(verbose_name='Courriel', blank=True)
2018-07-04 16:26:38 +02:00
login_rpn = models.CharField(max_length=40, blank=True)
2018-02-06 09:29:48 +01:00
avs = models.CharField(max_length=20, blank=True, verbose_name='No AVS')
2017-08-29 16:25:34 +02:00
option_ase = models.ForeignKey(Option, null=True, blank=True, on_delete=models.SET_NULL)
2017-07-11 23:15:00 +02:00
dispense_ecg = models.BooleanField(default=False)
dispense_eps = models.BooleanField(default=False)
soutien_dys = models.BooleanField(default=False)
corporation = models.ForeignKey('Corporation', null=True, blank=True,
on_delete=models.SET_NULL, verbose_name='Employeur')
instructor = models.ForeignKey('CorpContact', null=True, blank=True,
on_delete=models.SET_NULL, verbose_name='FEE/FPP')
2020-09-11 11:42:24 +02:00
instructor2 = models.ForeignKey('CorpContact', null=True, blank=True, related_name='rel_instructor2',
on_delete=models.SET_NULL, verbose_name='FEE/FPP (2)')
2018-02-14 18:13:22 +01:00
supervisor = models.ForeignKey('CorpContact', related_name='rel_supervisor', verbose_name='Superviseur',
null=True, blank=True, on_delete=models.SET_NULL)
2018-04-26 08:07:14 +02:00
supervision_attest_received = models.BooleanField('Attest. supervision reçue',
default=False)
2018-02-14 18:13:22 +01:00
mentor = models.ForeignKey('CorpContact', related_name='rel_mentor', verbose_name='Mentor',
null=True, blank=True, on_delete=models.SET_NULL)
expert = models.ForeignKey('CorpContact', related_name='rel_expert', verbose_name='Expert externe',
2018-02-15 16:05:25 +01:00
null=True, blank=True, on_delete=models.SET_NULL)
2023-11-09 13:51:09 +01:00
start_educ = models.DateField('Entrée en formation', null=True, blank=True)
2016-08-31 14:49:49 +02:00
klass = models.ForeignKey(Klass, verbose_name='Classe', blank=True, null=True,
2017-07-19 10:44:12 +02:00
on_delete=models.PROTECT)
2018-02-05 13:59:29 +01:00
report_sem1 = models.FileField('Bulletin 1er sem.', null=True, blank=True, upload_to='bulletins')
report_sem1_sent = models.DateTimeField('Date envoi bull. sem 1', null=True, blank=True)
report_sem2 = models.FileField('Bulletin 2e sem.', null=True, blank=True, upload_to='bulletins')
report_sem2_sent = models.DateTimeField('Date envoi bull. sem 2', null=True, blank=True)
2012-11-12 20:02:06 +01:00
archived = models.BooleanField(default=False, verbose_name='Archivé')
archived_text = models.TextField(blank=True)
# ============== Fields for examination ======================
2018-04-26 09:31:04 +02:00
subject = models.TextField('TD: titre provisoire', blank=True)
title = models.TextField('TD: Titre définitif', blank=True)
training_referent = models.ForeignKey(Teacher, null=True, blank=True, related_name='rel_training_referent',
on_delete=models.SET_NULL, verbose_name='Référent de PP')
referent = models.ForeignKey(Teacher, null=True, blank=True, related_name='rel_referent',
on_delete=models.SET_NULL, verbose_name='Référent avant-projet')
# ===============
mc_comment = models.TextField("Commentaires", blank=True)
2012-11-12 20:02:06 +01:00
support_tabimport = True
2012-11-06 17:54:33 +01:00
class Meta:
verbose_name = "Étudiant"
2015-09-22 20:52:35 +02:00
def __str__(self):
2012-11-06 17:54:33 +01:00
return '%s %s' % (self.last_name, self.first_name)
2017-08-08 16:11:55 +02:00
@property
def civility(self):
2018-01-11 16:57:47 +01:00
return {'M': 'Monsieur', 'F': 'Madame'}.get(self.gender, '')
2017-08-08 16:11:55 +02:00
@property
def full_name(self):
return '{0} {1}'.format(self.first_name, self.last_name)
@property
def civility_full_name(self):
return '{0} {1} {2}'.format(self.civility, self.first_name, self.last_name)
2017-08-08 16:11:55 +02:00
@property
def pcode_city(self):
return '{0} {1}'.format(self.pcode, self.city)
@property
def role(self):
2018-07-16 09:19:24 +02:00
if self.klass.section.is_fe:
return {'M': 'apprenti', 'F': 'apprentie'}.get(self.gender, '')
else:
return {'M': 'étudiant', 'F': 'étudiante'}.get(self.gender, '')
def save(self, **kwargs):
if self.archived and not self.archived_text:
# Fill archived_text with training data, JSON-formatted
trainings = [
tr.serialize() for tr in self.training_set.all().select_related('availability')
]
self.archived_text = json.dumps(trainings)
if self.archived_text and not self.archived:
self.archived_text = ""
super().save(**kwargs)
def age_at(self, date_):
"""Return age of student at `date_` time, as a string."""
2019-11-25 16:15:38 +01:00
if not self.birth_date:
return '?'
age = (date.today() - self.birth_date) / timedelta(days=365.2425)
age_y = int(age)
age_m = int((age - age_y) * 12)
return '%d ans%s' % (age_y, ' %d m.' % age_m if age_m > 0 else '')
def can_comment(self, user):
"""Return True if user is authorized to edit comments for this student."""
with suppress(Teacher.DoesNotExist):
return user.has_perm('stages.change_student') or user.teacher == self.klass.teacher
return False
def is_ede(self):
return self.klass and self.klass.section.name == 'EDE'
2020-02-13 09:39:27 +01:00
def is_eds(self):
return self.klass and self.klass.section.name == 'EDS'
2020-02-13 09:39:27 +01:00
2012-11-06 17:54:33 +01:00
2020-02-13 09:39:27 +01:00
class Examination(models.Model):
ACQ_MARK_CHOICES = (
('non', 'Non acquis'),
('part', 'Partiellement acquis'),
('acq', 'Acquis'),
)
TYPE_EXAM_CHOICES = (
('exam', 'Examen qualification'),
('entr', 'Entretien professionnel'),
)
student = models.ForeignKey(Student, on_delete=models.CASCADE)
session = models.ForeignKey(
ExamEDESession, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='Session',
)
type_exam = models.CharField("Type", max_length=10, choices=TYPE_EXAM_CHOICES)
date_exam = models.DateTimeField(blank=True, null=True)
2020-09-09 11:00:08 +02:00
room = models.CharField('Salle', max_length=30, blank=True)
2020-02-13 09:39:27 +01:00
mark = models.DecimalField('Note', max_digits=3, decimal_places=2, blank=True, null=True)
mark_acq = models.CharField('Note', max_length=5, choices=ACQ_MARK_CHOICES, blank=True)
internal_expert = models.ForeignKey(
Teacher, verbose_name='Expert interne',
null=True, blank=True, on_delete=models.SET_NULL
)
external_expert = models.ForeignKey(
'CorpContact', verbose_name='Expert externe',
null=True, blank=True, on_delete=models.SET_NULL
)
date_soutenance_mailed = models.DateTimeField("Convoc. env.", blank=True, null=True)
date_confirm_received = models.DateTimeField("Récept. confirm", blank=True, null=True)
class Meta:
verbose_name = "Examen"
def missing_examination_data(self):
missing = []
if not self.date_exam:
missing.append("La date dexamen est manquante")
if not self.room:
missing.append("La salle dexamen nest pas définie")
if not self.external_expert:
missing.append("Lexpert externe nest pas défini")
if not self.internal_expert:
missing.append("Lexpert interne nest pas défini")
return missing
class StudentFile(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE)
fichier = models.FileField(upload_to='etudiants')
titre = models.CharField("Titre", max_length=200)
def __str__(self):
2019-08-30 13:22:13 +02:00
return self.titre
2012-11-06 17:54:33 +01:00
class Corporation(models.Model):
ext_id = models.IntegerField(null=True, blank=True, verbose_name='ID externe')
name = models.CharField(max_length=100, verbose_name='Nom')
short_name = models.CharField(max_length=40, blank=True, verbose_name='Nom court')
2017-07-11 23:15:00 +02:00
district = models.CharField(max_length=20, blank=True, verbose_name='Canton')
2016-08-31 14:49:49 +02:00
parent = models.ForeignKey('self', null=True, blank=True, verbose_name='Institution mère',
on_delete=models.SET_NULL)
sector = models.CharField(max_length=40, blank=True, verbose_name='Secteur')
typ = models.CharField(max_length=40, blank=True, verbose_name='Type de structure')
2012-11-09 15:32:00 +01:00
street = models.CharField(max_length=100, blank=True, verbose_name='Rue')
2012-11-06 17:54:33 +01:00
pcode = models.CharField(max_length=4, verbose_name='Code postal')
city = models.CharField(max_length=40, verbose_name='Localité')
tel = models.CharField(max_length=20, blank=True, verbose_name='Téléphone')
email = models.EmailField(blank=True, verbose_name='Courriel')
web = models.URLField(blank=True, verbose_name='Site Web')
2012-11-12 20:02:06 +01:00
archived = models.BooleanField(default=False, verbose_name='Archivé')
2012-11-06 17:54:33 +01:00
class Meta:
verbose_name = "Institution"
2012-12-13 14:41:13 +01:00
ordering = ('name',)
2017-08-08 15:21:10 +02:00
unique_together = (('name', 'city'),)
2012-11-06 17:54:33 +01:00
2015-09-22 20:52:35 +02:00
def __str__(self):
sect = ' (%s)' % self.sector if self.sector else ''
return "%s%s, %s %s" % (self.name, sect, self.pcode, self.city)
2012-11-06 17:54:33 +01:00
2017-08-08 16:11:55 +02:00
@property
def pcode_city(self):
return '{0} {1}'.format(self.pcode, self.city)
2012-11-06 17:54:33 +01:00
class CorpContact(models.Model):
corporation = models.ForeignKey(
Corporation, verbose_name='Institution', null=True, blank=True,
on_delete=models.CASCADE
)
2015-10-01 14:47:43 +02:00
ext_id = models.IntegerField(null=True, blank=True, verbose_name='ID externe')
2012-12-13 14:41:13 +01:00
is_main = models.BooleanField(default=False, verbose_name='Contact principal')
always_cc = models.BooleanField(default=False, verbose_name='Toujours en copie')
civility = models.CharField(max_length=40, blank=True, verbose_name='Civilité')
2012-11-09 15:32:00 +01:00
first_name = models.CharField(max_length=40, blank=True, verbose_name='Prénom')
2012-11-06 17:54:33 +01:00
last_name = models.CharField(max_length=40, verbose_name='Nom')
birth_date = models.DateField(blank=True, null=True, verbose_name='Date de naissance')
2020-06-05 11:53:51 +02:00
nation = models.CharField("Nationalité", max_length=40, blank=True)
2022-08-24 14:57:31 +02:00
etat_civil = models.CharField("État-civil", max_length=20, blank=True)
etat_depuis = models.DateField("Depuis le", blank=True, null=True)
2017-07-11 23:15:00 +02:00
role = models.CharField(max_length=40, blank=True, verbose_name='Fonction')
street = models.CharField(max_length=100, blank=True, verbose_name='Rue')
pcode = models.CharField(max_length=4, blank=True, verbose_name='Code postal')
city = models.CharField(max_length=40, blank=True, verbose_name='Localité')
2012-11-06 17:54:33 +01:00
tel = models.CharField(max_length=20, blank=True, verbose_name='Téléphone')
2016-01-15 20:30:29 +01:00
email = models.CharField(max_length=100, blank=True, verbose_name='Courriel')
2016-01-18 12:55:57 +01:00
archived = models.BooleanField(default=False, verbose_name='Archivé')
sections = models.ManyToManyField(Section, blank=True)
2012-11-06 17:54:33 +01:00
permis_sejour = models.CharField("Permis de séjour", max_length=15, blank=True)
date_validite = models.DateField("Date de validité", blank=True, null=True)
2020-04-30 11:06:57 +02:00
avs = models.CharField('No AVS', max_length=20, blank=True)
bank = models.CharField('Banque (nom et ville)', max_length=200, blank=True)
clearing = models.CharField('No clearing', max_length=5, blank=True)
iban = models.CharField('iban', max_length=21, blank=True)
qualification = models.TextField('Titres obtenus', blank=True)
fields_of_interest = models.TextField("Domaines dintérêts", blank=True)
2012-11-06 17:54:33 +01:00
class Meta:
verbose_name = "Contact"
2015-09-22 20:52:35 +02:00
def __str__(self):
return '{0} {1}, {2}'.format(self.last_name, self.first_name, self.corporation or '-')
2012-11-06 17:54:33 +01:00
@property
def full_name(self):
return '{0} {1}'.format(self.first_name, self.last_name)
@property
def civility_full_name(self):
return '{0} {1} {2}'.format(self.civility, self.first_name, self.last_name)
@property
def pcode_city(self):
return '{0} {1}'.format(self.pcode, self.city)
@property
def adjective_ending(self):
return 'e' if self.civility == 'Madame' else ''
2012-11-06 17:54:33 +01:00
class Domain(models.Model):
name = models.CharField(max_length=50, verbose_name='Nom')
class Meta:
verbose_name = "Domaine"
ordering = ('name',)
2012-11-06 17:54:33 +01:00
2015-09-22 20:52:35 +02:00
def __str__(self):
2012-11-06 17:54:33 +01:00
return self.name
class Period(models.Model):
""" Périodes de stages """
title = models.CharField(max_length=150, verbose_name='Titre')
section = models.ForeignKey(Section, verbose_name='Filière', on_delete=models.PROTECT,
2019-11-20 13:34:27 +01:00
limit_choices_to={'has_stages': True})
level = models.ForeignKey(Level, verbose_name='Niveau', on_delete=models.PROTECT)
2012-11-06 17:54:33 +01:00
start_date = models.DateField(verbose_name='Date de début')
end_date = models.DateField(verbose_name='Date de fin')
class Meta:
verbose_name = "Période de pratique professionnelle"
verbose_name_plural = "Périodes de pratique professionnelle"
ordering = ('-start_date',)
2012-11-06 17:54:33 +01:00
2015-09-22 20:52:35 +02:00
def __str__(self):
return '%s (%s)' % (self.dates, self.title)
2012-11-06 17:54:33 +01:00
@property
def dates(self):
return '%s - %s' % (self.start_date, self.end_date)
2013-04-08 14:19:23 +02:00
@property
def school_year(self):
return utils.school_year(self.start_date)
@property
def relative_level(self):
"""
Return the level depending on current school year. For example, if the
period is planned for next school year, level will be level - 1.
"""
diff = (utils.school_year(self.start_date, as_tuple=True)[0] -
utils.school_year(date.today(), as_tuple=True)[0])
return self.level.delta(-diff)
2013-04-08 14:19:23 +02:00
@property
def weeks(self):
""" Return the number of weeks of this period """
return (self.end_date - self.start_date).days // 7
2012-11-06 17:54:33 +01:00
class Availability(models.Model):
""" Disponibilités des institutions """
corporation = models.ForeignKey(Corporation, verbose_name='Institution', on_delete=models.CASCADE)
period = models.ForeignKey(Period, verbose_name='Période', on_delete=models.CASCADE)
domain = models.ForeignKey(Domain, verbose_name='Domaine', on_delete=models.CASCADE)
contact = models.ForeignKey(CorpContact, null=True, blank=True, verbose_name='Contact institution',
on_delete=models.SET_NULL)
priority = models.BooleanField('Prioritaire', default=False)
comment = models.TextField(blank=True, verbose_name='Remarques')
2012-11-06 17:54:33 +01:00
class Meta:
verbose_name = "Disponibilité"
2015-09-22 20:52:35 +02:00
def __str__(self):
return '%s - %s (%s) - %s' % (self.period, self.corporation, self.domain, self.contact)
@property
def free(self):
try:
self.training
except Training.DoesNotExist:
return True
return False
2012-11-06 17:54:33 +01:00
class Training(models.Model):
""" Stages """
student = models.ForeignKey(Student, verbose_name='Étudiant', on_delete=models.CASCADE)
availability = models.OneToOneField(Availability, verbose_name='Disponibilité', on_delete=models.CASCADE)
referent = models.ForeignKey(Teacher, null=True, blank=True, verbose_name='Référent',
on_delete=models.SET_NULL)
comment = models.TextField(blank=True, verbose_name='Remarques')
2012-11-06 17:54:33 +01:00
class Meta:
verbose_name = "Pratique professionnelle"
2019-02-21 09:20:13 +01:00
verbose_name_plural = "Pratique professionnelle"
ordering = ("-availability__period",)
2012-11-06 17:54:33 +01:00
2015-09-22 20:52:35 +02:00
def __str__(self):
return '%s chez %s (%s)' % (self.student, self.availability.corporation, self.availability.period)
def serialize(self):
"""
Compute a summary of the training as a dict representation (for archiving purpose).
"""
return {
'period': str(self.availability.period),
'corporation': str(self.availability.corporation),
'referent': str(self.referent),
'comment': self.comment,
'contact': str(self.availability.contact),
'comment_avail': self.availability.comment,
'domain': str(self.availability.domain),
}
2017-07-14 10:19:12 +02:00
IMPUTATION_CHOICES = (
('ASAFE', 'ASAFE'),
('ASEFE', 'ASEFE'),
2017-08-08 15:21:10 +02:00
('ASSCFE', 'ASSCFE'),
2018-06-06 08:41:31 +02:00
('MPTS', 'MPTS'),
('MPS', 'MPS'),
2017-07-14 10:19:12 +02:00
('EDEpe', 'EDEpe'),
('EDEps', 'EDEps'),
('EDS', 'EDS'),
2017-08-21 10:25:49 +02:00
('CAS_FPP', 'CAS_FPP'),
2018-06-06 08:41:31 +02:00
# To split afterwards
('EDE', 'EDE'),
('#Mandat_ASA', 'ASA'),
('#Mandat_ASE', 'ASE'),
('#Mandat_ASSC', 'ASSC'),
2017-07-14 10:19:12 +02:00
)
2017-08-08 15:21:10 +02:00
2017-07-14 10:19:12 +02:00
class Course(models.Model):
"""Cours et mandats attribués aux enseignants"""
teacher = models.ForeignKey(Teacher, blank=True, null=True,
verbose_name="Enseignant-e", on_delete=models.SET_NULL)
2017-08-18 09:28:17 +02:00
public = models.CharField("Classe(s)", max_length=200, default='')
2017-07-14 10:19:12 +02:00
subject = models.CharField("Sujet", max_length=100, default='')
period = models.IntegerField("Nb de périodes", default=0)
# Imputation comptable: compte dans lequel les frais du cours seront imputés
2019-10-07 14:42:42 +02:00
imputation = models.CharField("Imputation", max_length=12, choices=IMPUTATION_CHOICES)
2017-07-14 10:19:12 +02:00
class Meta:
verbose_name = 'Cours'
verbose_name_plural = 'Cours'
def __str__(self):
return '{0} - {1} - {2} - {3}'.format(
self.teacher, self.public, self.subject, self.period
2017-07-14 10:19:12 +02:00
)
2018-04-26 07:54:29 +02:00
class SupervisionBill(models.Model):
student = models.ForeignKey(Student, verbose_name='étudiant', on_delete=models.CASCADE)
supervisor = models.ForeignKey(CorpContact, verbose_name='superviseur', on_delete=models.CASCADE)
period = models.SmallIntegerField('période', default=0)
date = models.DateField()
class Meta:
verbose_name = 'Facture de supervision'
verbose_name_plural = 'Factures de supervision'
ordering = ['date']
def __str__(self):
return '{0} : {1}'.format(self.student.full_name, self.supervisor.full_name)