Première version des modèles ScoDoc7 en SQLAlchemy

This commit is contained in:
IDK 2021-08-07 15:20:30 +02:00
parent 383fdb0e53
commit dad6fdd63c
14 changed files with 1074 additions and 40 deletions

View File

@ -1,7 +0,0 @@
# -*- coding: UTF-8 -*
"""ScoDoc8 models
"""
# None, at this point
# see auth.models for user/role related models

57
app/models/__init__.py Normal file
View File

@ -0,0 +1,57 @@
# -*- coding: UTF-8 -*
"""Modèles base de données ScoDoc
XXX version préliminaire ScoDoc8 #sco8 sans département
"""
CODE_STR_LEN = 16 # chaine pour les codes
SHORT_STR_LEN = 32 # courtes chaine, eg acronymes
APO_CODE_STR_LEN = 16 # nb de car max d'un code Apogée
from app.models.absences import Absence, AbsenceNotification, BilletAbsence
from app.models.entreprises import (
Entreprise,
EntrepriseCorrespondant,
EntrepriseContact,
)
from app.models.etudiants import (
Identite,
Adresse,
Admission,
ItemSuivi,
ItemSuiviTag,
EtudAnnotation,
)
from app.models.events import Scolog, ScolarNews
from app.models.formations import (
NotesFormation,
NotesUE,
NotesMatiere,
NotesModule,
NotesTag,
)
from app.models.formsemestre import (
FormSemestre,
NotesFormsemestreEtape,
FormModalite,
NotesFormsemestreUECoef,
NotesFormsemestreUEComputationExpr,
NotesFormsemestreCustomMenu,
NotesFormsemestreInscription,
NotesModuleImpl,
notes_modules_enseignants,
NotesModuleImplInscription,
NotesEvaluation,
NotesSemSet,
notes_semset_formsemestre,
)
from app.models.groups import Partition, GroupDescr, group_membership
from app.models.notes import (
ScolarEvent,
ScolarFormsemestreValidation,
ScolarAutorisationInscription,
NotesAppreciations,
NotesNotes,
NotesNotesLog,
)
from app.models.preferences import ScoPreferences

75
app/models/absences.py Normal file
View File

@ -0,0 +1,75 @@
# -*- coding: UTF-8 -*
"""Gestion des absences
"""
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
class Absence(db.Model):
"""une absence (sur une demi-journée)"""
__tablename__ = "absences"
id = db.Column(db.Integer, primary_key=True)
etudid = db.Column(db.Integer, db.ForeignKey("identite.id"), index=True)
jour = db.Column(db.Date)
estabs = db.Column(db.Boolean())
estjust = db.Column(db.Boolean())
matin = db.Column(db.Boolean())
# motif de l'absence:
description = db.Column(db.Text())
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
# moduleimpid concerne (optionnel):
moduleimpl_id = db.Column(
db.Integer,
db.ForeignKey("notes_moduleimpl.id"),
)
# XXX TODO: contrainte ajoutée: vérifier suppression du module
# (mettre à NULL sans supprimer)
class AbsenceNotification(db.Model):
"""Notification d'absence émise"""
__tablename__ = "absences_notifications"
id = db.Column(db.Integer, primary_key=True)
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id"),
)
notification_date = db.Column(
db.DateTime(timezone=True), server_default=db.func.now()
)
email = db.Column(db.Text())
nbabs = db.Column(db.Integer)
nbabsjust = db.Column(db.Integer)
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
class BilletAbsence(db.Model):
"""Billet d'absence (signalement par l'étudiant)"""
__tablename__ = "billet_absence"
id = db.Column(db.Integer, primary_key=True)
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id"),
index=True,
)
abs_begin = db.Column(db.DateTime(timezone=True))
abs_end = db.Column(db.DateTime(timezone=True))
# raison de l'absence:
description = db.Column(db.Text())
# False: new, True: processed
etat = db.Column(db.Boolean(), default=False)
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
# true si l'absence _pourrait_ etre justifiée
justified = db.Column(db.Boolean(), default=False)

69
app/models/entreprises.py Normal file
View File

@ -0,0 +1,69 @@
# -*- coding: UTF-8 -*
"""Gestion des absences
"""
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
class Entreprise(db.Model):
"""une entreprise"""
__tablename__ = "entreprises"
id = db.Column(db.Integer, primary_key=True)
entreprise_id = db.synonym("id")
nom = db.Column(db.Text)
adresse = db.Column(db.Text)
ville = db.Column(db.Text)
codepostal = db.Column(db.Text)
pays = db.Column(db.Text)
contact_origine = db.Column(db.Text)
secteur = db.Column(db.Text)
note = db.Column(db.Text)
privee = db.Column(db.Text)
localisation = db.Column(db.Text)
# -1 inconnue, 0, 25, 50, 75, 100:
qualite_relation = db.Column(db.Integer)
plus10salaries = db.Column(db.Boolean())
date_creation = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
class EntrepriseCorrespondant(db.Model):
"""Personne contact en entreprise"""
__tablename__ = "entreprise_correspondant"
id = db.Column(db.Integer, primary_key=True)
entreprise_corresp_id = db.synonym("id")
entreprise_id = db.Column(db.Integer, db.ForeignKey("entreprises.id"))
nom = db.Column(db.Text)
prenom = db.Column(db.Text)
civilite = db.Column(db.Text)
fonction = db.Column(db.Text)
phone1 = db.Column(db.Text)
phone2 = db.Column(db.Text)
mobile = db.Column(db.Text)
mail1 = db.Column(db.Text)
mail2 = db.Column(db.Text)
fax = db.Column(db.Text)
note = db.Column(db.Text)
class EntrepriseContact(db.Model):
"""Evènement (contact) avec une entreprise"""
__tablename__ = "entreprise_contact"
id = db.Column(db.Integer, primary_key=True)
entreprise_contact_id = db.synonym("id")
date = db.Column(db.DateTime(timezone=True))
type_contact = db.Column(db.Text)
entreprise_id = db.Column(db.Integer, db.ForeignKey("entreprises.id"))
entreprise_corresp_id = db.Column(
db.Integer, db.ForeignKey("entreprise_correspondant.id")
)
etudid = db.Column(db.Integer) # sans contrainte pour garder logs après suppression
description = db.Column(db.Text)
enseignant = db.Column(db.Text)

153
app/models/etudiants.py Normal file
View File

@ -0,0 +1,153 @@
# -*- coding: UTF-8 -*
"""Définition d'un étudiant
et données rattachées (adresses, annotations, ...)
"""
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
class Identite(db.Model):
"""étudiant"""
__tablename__ = "identite"
id = db.Column(db.Integer, primary_key=True)
etudid = db.synonym("id")
nom = db.Column(db.Text())
prenom = db.Column(db.Text())
nom_usuel = db.Column(db.Text())
# optionnel (si present, affiché à la place du nom)
civilite = db.Column(db.String(1), nullable=False)
__table_args__ = (db.CheckConstraint("civilite IN ('M', 'F', 'X')"),)
date_naissance = db.Column(db.Date)
lieu_naissance = db.Column(db.Text())
dept_naissance = db.Column(db.Text())
nationalite = db.Column(db.Text())
statut = db.Column(db.Text())
boursier = db.Column(db.Boolean()) # True si boursier ('O' en ScoDoc7)
photo_filename = db.Column(db.Text())
code_nip = db.Column(db.String(CODE_STR_LEN), unique=True)
code_ine = db.Column(db.String(CODE_STR_LEN), unique=True)
class Adresse(db.Model):
"""Adresse d'un étudiant
(le modèle permet plusieurs adresses, mais l'UI n'en gère qu'une seule)
"""
__tablename__ = "adresse"
id = db.Column(db.Integer, primary_key=True)
adresse_id = db.synonym("id")
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id"),
)
email = db.Column(db.Text()) # mail institutionnel
emailperso = db.Column(db.Text) # email personnel (exterieur)
domicile = db.Column(db.Text)
codepostaldomicile = db.Column(db.Text)
villedomicile = db.Column(db.Text)
paysdomicile = db.Column(db.Text)
telephone = db.Column(db.Text)
telephonemobile = db.Column(db.Text)
fax = db.Column(db.Text)
typeadresse = db.Column(db.Text, default="domicile", nullable=False)
description = db.Column(db.Text)
class Admission(db.Model):
"""Informations liées à l'admission d'un étudiant"""
__tablename__ = "admissions"
id = db.Column(db.Integer, primary_key=True)
adm_id = db.synonym("id")
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id"),
)
# Anciens champs de ScoDoc7, à revoir pour être plus générique et souple
# notamment dans le cadre du bac 2021
# de plus, certaines informations liées à APB ne sont plus disponibles
# avec Parcoursup
annee = db.Column(db.Integer)
bac = db.Column(db.Text)
specialite = db.Column(db.Text)
annee_bac = db.Column(db.Integer)
math = db.Column(db.Text)
physique = db.Column(db.Float)
anglais = db.Column(db.Float)
francais = db.Column(db.Float)
# Rang dans les voeux du candidat (inconnu avec APB et PS)
rang = db.Column(db.Integer)
# Qualité et décision du jury d'admission (ou de l'examinateur)
qualite = db.Column(db.Float)
rapporteur = db.Column(db.Text)
decision = db.Column(db.Text)
score = db.Column(db.Float)
commentaire = db.Column(db.Text)
# Lycée d'origine:
nomlycee = db.Column(db.Text)
villelycee = db.Column(db.Text)
codepostallycee = db.Column(db.Text)
codelycee = db.Column(db.Text)
# 'APB', 'APC-PC', 'CEF', 'Direct', '?' (autre)
type_admission = db.Column(db.Text)
# était boursier dans le cycle precedent (lycee) ?
boursier_prec = db.Column(db.Boolean())
# classement par le jury d'admission (1 à N),
# global (pas celui d'APB si il y a des groupes)
classement = db.Column(db.Integer)
# code du groupe APB
apb_groupe = db.Column(db.Text)
# classement (1..Ngr) par le jury dans le groupe APB
apb_classement_gr = db.Column(db.Integer)
# Suivi scolarité / débouchés
class ItemSuivi(db.Model):
__tablename__ = "itemsuivi"
id = db.Column(db.Integer, primary_key=True)
itemsuivi_id = db.synonym("id")
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id"),
)
item_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
situation = db.Column(db.Text)
class ItemSuiviTag(db.Model):
__tablename__ = "itemsuivi_tags"
id = db.Column(db.Integer, primary_key=True)
tag_id = db.synonym("id")
title = db.Column(db.String(SHORT_STR_LEN), nullable=False, unique=True)
# Association tag <-> module
itemsuivi_tags_assoc = db.Table(
"itemsuivi_tags_assoc",
db.Column("tag_id", db.Integer, db.ForeignKey("itemsuivi_tags.id")),
db.Column("itemsuivi_id", db.Integer, db.ForeignKey("itemsuivi.id")),
)
# ON DELETE CASCADE ?
class EtudAnnotation(db.Model):
"""Annotation sur un étudiant"""
__tablename__ = "etud_annotations"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
etudid = db.Column(db.Integer) # sans contrainte pour garder logs après suppression
authenticated_user = db.Column(db.Text)
comment = db.Column(db.Text)

35
app/models/events.py Normal file
View File

@ -0,0 +1,35 @@
# -*- coding: UTF-8 -*
"""Evenements et logs divers
"""
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
class Scolog(db.Model):
"""Log des actions (journal modif etudiants)"""
__tablename__ = "scolog"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
etudid = db.Column(db.Integer) # sans contrainte pour garder logs après suppression
authenticated_user = db.Column(db.Text) # login, sans contrainte
# zope_remote_addr suppressed
class ScolarNews(db.Model):
"""Nouvelles pour page d'accueil"""
__tablename__ = "scolar_news"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
authenticated_user = db.Column(db.Text) # login, sans contrainte
# type in 'INSCR', 'NOTES', 'FORM', 'SEM', 'MISC'
type = db.Column(db.String(SHORT_STR_LEN))
object = db.Column(db.Integer) # moduleimpl_id, formation_id, formsemestre_id
text = db.Column(db.Text)
url = db.Column(db.Text)

117
app/models/formations.py Normal file
View File

@ -0,0 +1,117 @@
"""ScoDoc8 models : Formations (hors BUT)
"""
from typing import Any
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
class NotesFormation(db.Model):
"""Programme pédagogique d'une formation"""
__tablename__ = "notes_formations"
__table_args__ = (db.UniqueConstraint("acronyme", "titre", "version"),)
id = db.Column(db.Integer, primary_key=True)
formation_id = db.synonym("id")
acronyme = db.Column(db.String(SHORT_STR_LEN), nullable=False)
titre = db.Column(db.Text(), nullable=False)
version = db.Column(db.Integer, default=1)
formation_code = db.Column(db.String(SHORT_STR_LEN), nullable=False)
type_parcours = db.Column(db.Integer, default=0)
code_specialite = db.Column(db.String(SHORT_STR_LEN))
def __init__(self, **kwargs):
super(NotesFormation, self).__init__(**kwargs)
if self.formation_code is None:
# génère formation_code à la création
self.formation_code = f"FCOD{self.id:03d}"
class NotesUE(db.Model):
"""Unité d'Enseignement"""
__tablename__ = "notes_ue"
id = db.Column(db.Integer, primary_key=True)
ue_id = db.synonym("id")
formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
acronyme = db.Column(db.String(SHORT_STR_LEN), nullable=False)
numero = db.Column(db.Integer) # ordre de présentation
titre = db.Column(db.Text())
# Type d'UE: 0 normal ("fondamentale"), 1 "sport", 2 "projet et stage (LP)",
# 4 "élective"
type = db.Column(db.Integer, default=0)
ue_code = db.Column(db.String(SHORT_STR_LEN), nullable=False)
ects = db.Column(db.Float) # nombre de credits ECTS
is_external = db.Column(db.Boolean(), nullable=False, default=False)
# id de l'element pedagogique Apogee correspondant:
code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
# coef UE, utilise seulement si l'option use_ue_coefs est activée:
coefficient = db.Column(db.Float)
def __init__(self, **kwargs):
super(NotesUE, self).__init__(**kwargs)
if self.ue_code is None:
# génère code à la création
self.ue_code = f"UCOD{self.ue_id:03d}"
class NotesMatiere(db.Model):
"""Matières: regroupe les modules d'une UE
La matière a peu d'utilité en dehors de la présentation des modules
d'une UE.
"""
__tablename__ = "notes_matieres"
__table_args__ = (db.UniqueConstraint("ue_id", "titre"),)
id = db.Column(db.Integer, primary_key=True)
matiere_id = db.synonym("id")
ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"))
titre = db.Column(db.Text())
numero = db.Column(db.Integer) # ordre de présentation
class NotesModule(db.Model):
"""Module"""
__tablename__ = "notes_modules"
id = db.Column(db.Integer, primary_key=True)
module_id = db.synonym("id")
titre = db.Column(db.Text())
abbrev = db.Column(db.Text()) # nom court
code = db.Column(db.String(SHORT_STR_LEN), nullable=False)
heures_cours = db.Column(db.Float)
heures_td = db.Column(db.Float)
heures_tp = db.Column(db.Float)
coefficient = db.Column(db.Float) # coef PPN
ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"))
formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
matiere_id = db.Column(db.Integer, db.ForeignKey("notes_matieres.id"))
# pas un id mais le numéro du semestre: 1, 2, ...
semestre_id = db.Column(db.Integer, nullable=False, default=1)
numero = db.Column(db.Integer) # ordre de présentation
# id de l'element pedagogique Apogee correspondant:
code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
module_type = db.Column(db.Integer) # NULL ou 0:defaut, 1: malus (NOTES_MALUS)
class NotesTag(db.Model):
"""Tag sur un module"""
__tablename__ = "notes_tags"
id = db.Column(db.Integer, primary_key=True)
tag_id = db.synonym("id")
title = db.Column(db.String(SHORT_STR_LEN), nullable=False, unique=True)
# Association tag <-> module
notes_modules_tags = db.Table(
"notes_modules_tags",
db.Column("tag_id", db.Integer, db.ForeignKey("notes_tags.id")),
db.Column("module_id", db.Integer, db.ForeignKey("notes_modules.id")),
)

284
app/models/formsemestre.py Normal file
View File

@ -0,0 +1,284 @@
# -*- coding: UTF-8 -*
"""ScoDoc8 models
"""
from typing import Any
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
class FormSemestre(db.Model):
"""Mise en oeuvre d'un semestre de formation
was notes_formsemestre
"""
__tablename__ = "notes_formsemestre"
id = db.Column(db.Integer, primary_key=True)
formsemestre_id = db.synonym("id")
formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
semestre_id = db.Column(db.Integer, nullable=False, default=1)
titre = db.Column(db.Text())
date_debut = db.Column(db.Date())
date_fin = db.Column(db.Date())
etat = db.Column(db.Boolean(), nullable=False, default=True) # False si verrouillé
modalite = db.Column(db.String(16), db.ForeignKey("form_modalite.modalite"))
# gestion compensation sem DUT:
gestion_compensation = db.Column(db.Boolean(), nullable=False, default=False)
# ne publie pas le bulletin XML:
bul_hide_xml = db.Column(db.Boolean(), nullable=False, default=False)
# semestres decales (pour gestion jurys):
gestion_semestrielle = db.Column(db.Boolean(), nullable=False, default=False)
# couleur fond bulletins HTML:
bul_bgcolor = db.Column(db.String(SHORT_STR_LEN), default="white")
# autorise resp. a modifier semestre:
resp_can_edit = db.Column(db.Boolean(), nullable=False, default=False)
# autorise resp. a modifier slt les enseignants:
resp_can_change_ens = db.Column(db.Boolean(), nullable=False, default=True)
# autorise les ens a creer des evals:
ens_can_edit_eval = db.Column(db.Boolean(), nullable=False, default=False)
# code element semestre Apogee, eg VRTW1 ou V2INCS4,V2INLS4
elt_sem_apo = db.Column(db.String(APO_CODE_STR_LEN))
# code element annee Apogee, eg VRT1A ou V2INLA,V2INCA
elt_annee_apo = db.Column(db.String(APO_CODE_STR_LEN))
etapes = db.relationship(
"NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre"
)
def __init__(self, **kwargs):
super(FormSemestre, self).__init__(**kwargs)
if self.modalite is None:
self.modalite = FormModalite.DEFAULT_MODALITE
# Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
notes_formsemestre_responsables = db.Table(
"notes_formsemestre_responsables",
db.Column(
"formsemestre_id",
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
),
db.Column("responsable_id", db.Integer, db.ForeignKey("user.id")),
)
class NotesFormsemestreEtape(db.Model):
"""Étape Apogée associées au semestre"""
__tablename__ = "notes_formsemestre_etapes"
id = db.Column(db.Integer, primary_key=True)
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
etape_apo = db.Column(db.String(APO_CODE_STR_LEN))
class FormModalite(db.Model):
"""Modalités de formation, utilisées pour la présentation
(grouper les semestres, générer des codes, etc.)
"""
id = db.Column(db.Integer, primary_key=True)
modalite = db.Column(db.String(16), unique=True, index=True) # code
titre = db.Column(db.Text()) # texte explicatif
# numero = ordre de presentation)
numero = db.Column(db.Integer)
DEFAULT_MODALITE = "FI"
@staticmethod
def insert_modalites():
"""Create default modalities"""
numero = 0
for (code, titre) in (
(FormModalite.DEFAULT_MODALITE, "Formation Initiale"),
("FAP", "Apprentissage"),
("FC", "Formation Continue"),
("DEC", "Formation Décalées"),
("LIC", "Licence"),
("CPRO", "Contrats de Professionnalisation"),
("DIST", "À distance"),
("ETR", "À l'étranger"),
("EXT", "Extérieur"),
("OTHER", "Autres formations"),
):
modalite = FormModalite.query.filter_by(modalite=code).first()
if modalite is None:
modalite = FormModalite(modalite=code, titre=titre, numero=numero)
db.session.add(modalite)
numero += 1
db.session.commit()
class NotesFormsemestreUECoef(db.Model):
"""Coef des UE capitalisees arrivant dans ce semestre"""
__tablename__ = "notes_formsemestre_uecoef"
__table_args__ = (db.UniqueConstraint("formsemestre_id", "ue_id"),)
id = db.Column(db.Integer, primary_key=True)
formsemestre_uecoef_id = db.synonym("id")
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
ue_id = db.Column(
db.Integer,
db.ForeignKey("notes_ue.id"),
)
coefficient = db.Column(db.Float, nullable=False)
class NotesFormsemestreUEComputationExpr(db.Model):
"""Formules utilisateurs pour calcul moyenne UE"""
__tablename__ = "notes_formsemestre_ue_computation_expr"
__table_args__ = (db.UniqueConstraint("formsemestre_id", "ue_id"),)
id = db.Column(db.Integer, primary_key=True)
notes_formsemestre_ue_computation_expr_id = db.synonym("id")
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
ue_id = db.Column(
db.Integer,
db.ForeignKey("notes_ue.id"),
)
# formule de calcul moyenne
computation_expr = db.Column(db.Text())
class NotesFormsemestreCustomMenu(db.Model):
"""Menu custom associe au semestre"""
__tablename__ = "notes_formsemestre_custommenu"
id = db.Column(db.Integer, primary_key=True)
custommenu_id = db.synonym("id")
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
title = db.Column(db.Text())
url = db.Column(db.Text())
idx = db.Column(db.Integer, default=0) # rang dans le menu
class NotesFormsemestreInscription(db.Model):
"""Inscription à un semestre de formation"""
__tablename__ = "notes_formsemestre_inscription"
__table_args__ = (db.UniqueConstraint("formsemestre_id", "etudid"),)
id = db.Column(db.Integer, primary_key=True)
formsemestre_inscription_id = db.synonym("id")
etudid = db.Column(db.Integer, db.ForeignKey("identite.id"))
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
# I inscrit, D demission en cours de semestre, DEF si "defaillant"
etat = db.Column(db.String(CODE_STR_LEN))
# etape apogee d'inscription (experimental 2020)
etape = db.Column(db.String(APO_CODE_STR_LEN))
class NotesModuleImpl(db.Model):
"""Mise en oeuvre d'un module pour une annee/semestre"""
__tablename__ = "notes_moduleimpl"
__table_args__ = (db.UniqueConstraint("formsemestre_id", "module_id"),)
id = db.Column(db.Integer, primary_key=True)
moduleimpl_id = db.synonym("id")
module_id = db.Column(
db.Integer,
db.ForeignKey("notes_modules.id"),
)
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
responsable_id = db.Column("responsable_id", db.Integer, db.ForeignKey("user.id"))
# formule de calcul moyenne:
computation_expr = db.Column(db.Text())
# Enseignants (chargés de TD ou TP) d'un moduleimpl
notes_modules_enseignants = db.Table(
"notes_modules_enseignants",
db.Column(
"moduleimpl_id",
db.Integer,
db.ForeignKey("notes_moduleimpl.id"),
),
db.Column("ens_id", db.Integer, db.ForeignKey("user.id")),
)
# XXX il manque probablement une relation pour gérer cela
class NotesModuleImplInscription(db.Model):
"""Inscription à un module (etudiants,moduleimpl)"""
__tablename__ = "notes_moduleimpl_inscription"
id = db.Column(db.Integer, primary_key=True)
moduleimpl_inscription_id = db.synonym("id")
db.Column(
"moduleimpl_id",
db.Integer,
db.ForeignKey("notes_moduleimpl.moduleimpl_id"),
)
etudid = db.Column(db.Integer, db.ForeignKey("identite.id"))
class NotesEvaluation(db.Model):
"""Evaluation (contrôle, examen, ...)"""
__tablename__ = "notes_evaluation"
id = db.Column(db.Integer, primary_key=True)
evaluation_id = db.synonym("id")
jour = db.Column(db.Date)
heure_debut = db.Column(db.Time)
heure_fin = db.Column(db.Time)
description = db.Column(db.Text)
note_max = db.Column(db.Float)
coefficient = db.Column(db.Float)
visibulletin = db.Column(db.Boolean, nullable=False, default=True)
publish_incomplete = db.Column(db.Boolean, nullable=False, default=False)
# type d'evaluation: False normale, True rattrapage:
evaluation_type = db.Column(db.Boolean, nullable=False, default=False)
# ordre de presentation (par défaut, le plus petit numero
# est la plus ancienne eval):
numero = db.Column(db.Integer)
class NotesSemSet(db.Model):
"""semsets: ensemble de formsemestres pour exports Apogée"""
__tablename__ = "notes_semset"
id = db.Column(db.Integer, primary_key=True)
semset_id = db.synonym("id")
title = db.Column(db.Text)
annee_scolaire = db.Column(db.Integer, nullable=True, default=None)
# periode: 0 (année), 1 (Simpair), 2 (Spair)
sem_id = db.Column(db.Integer, nullable=True, default=None)
# Association:
notes_semset_formsemestre = db.Table(
"notes_semset_formsemestre",
db.Column("formsemestre_id", db.Integer, db.ForeignKey("notes_formsemestre.id")),
db.Column("semset_id", db.Integer, db.ForeignKey("notes_semset.id")),
db.UniqueConstraint("formsemestre_id", "semset_id"),
)

60
app/models/groups.py Normal file
View File

@ -0,0 +1,60 @@
# -*- coding: UTF-8 -*
"""Groups & partitions
"""
from typing import Any
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
class Partition(db.Model):
"""Partition: découpage d'une promotion en groupes"""
__table_args__ = (db.UniqueConstraint("formsemestre_id", "partition_name"),)
id = db.Column(db.Integer, primary_key=True)
partition_id = db.synonym("id")
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
# "TD", "TP", ... (NULL for 'all')
partition_name = db.Column(db.String(SHORT_STR_LEN))
# numero = ordre de presentation)
numero = db.Column(db.Integer)
bul_show_rank = db.Column(db.Boolean(), nullable=False, default=False)
show_in_lists = db.Column(db.Boolean(), nullable=False, default=True)
def __init__(self, **kwargs):
super(Partition, self).__init__(**kwargs)
if self.numero is None:
# génère numero à la création
last_partition = Partition.query.order_by(Partition.numero.desc()).first()
if last_partition:
self.numero = last_partition.numero + 1
else:
self.numero = 1
class GroupDescr(db.Model):
"""Description d'un groupe d'une partition"""
__tablename__ = "group_descr"
__table_args__ = (db.UniqueConstraint("partition_id", "group_name"),)
id = db.Column(db.Integer, primary_key=True)
group_id = db.synonym("id")
partition_id = db.Column(db.Integer, db.ForeignKey("partition.id"))
# "A", "C2", ... (NULL for 'all'):
group_name = db.Column(db.String(SHORT_STR_LEN))
group_membership = db.Table(
"group_membership",
db.Column("etudid", db.Integer, db.ForeignKey("identite.id")),
db.Column("group_id", db.Integer, db.ForeignKey("group_descr.id")),
db.UniqueConstraint("etudid", "group_id"),
)

160
app/models/notes.py Normal file
View File

@ -0,0 +1,160 @@
# -*- coding: UTF-8 -*
"""Notes, décisions de jury, évènements scolaires
"""
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
class ScolarEvent(db.Model):
"""Evenement dans le parcours scolaire d'un étudiant"""
__tablename__ = "scolar_events"
id = db.Column(db.Integer, primary_key=True)
event_id = db.synonym("id")
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id"),
)
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
ue_id = db.Column(
db.Integer,
db.ForeignKey("notes_ue.id"),
)
# 'CREATION', 'INSCRIPTION', 'DEMISSION',
# 'AUT_RED', 'EXCLUS', 'VALID_UE', 'VALID_SEM'
# 'ECHEC_SEM'
# 'UTIL_COMPENSATION'
event_type = db.Column(db.String(SHORT_STR_LEN))
# Semestre compensé par formsemestre_id:
comp_formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
class ScolarFormsemestreValidation(db.Model):
"""Décisions de jury"""
__tablename__ = "scolar_formsemestre_validation"
# Assure unicité de la décision:
__table_args__ = (db.UniqueConstraint("etudid", "formsemestre_id", "ue_id"),)
id = db.Column(db.Integer, primary_key=True)
formsemestre_validation_id = db.synonym("id")
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id"),
)
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
ue_id = db.Column(
db.Integer,
db.ForeignKey("notes_ue.id"),
)
code = db.Column(db.String(CODE_STR_LEN), nullable=False)
# NULL pour les UE, True|False pour les semestres:
assidu = db.Column(db.Boolean)
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
# NULL sauf si compense un semestre:
compense_formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
moy_ue = db.Column(db.Float)
# (normalement NULL) indice du semestre, utile seulement pour
# UE "antérieures" et si la formation définit des UE utilisées
# dans plusieurs semestres (cas R&T IUTV v2)
semestre_id = db.Column(db.Integer)
# Si UE validée dans le cursus d'un autre etablissement
is_external = db.Column(db.Boolean, default=False)
class ScolarAutorisationInscription(db.Model):
"""Autorisation d'inscription dans un semestre"""
__tablename__ = "scolar_autorisation_inscription"
id = db.Column(db.Integer, primary_key=True)
autorisation_inscription_id = db.synonym("id")
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id"),
)
formation_code = db.Column(db.String(CODE_STR_LEN), nullable=False)
# semestre ou on peut s'inscrire:
semestre_id = db.Column(db.Integer)
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
origin_formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
class NotesAppreciations(db.Model):
"""Appréciations sur bulletins"""
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id"),
index=True,
)
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
author = db.Column(db.Text) # login, sans contrainte
comment = db.Column(db.Text) # texte libre
class NotesNotes(db.Model):
"""Une note"""
__tablename__ = "notes_notes"
__table_args__ = (db.UniqueConstraint("etudid", "evaluation_id"),)
id = db.Column(db.Integer, primary_key=True)
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id"),
)
evaluation_id = db.Column(
db.Integer, db.ForeignKey("notes_evaluation.id"), index=True
)
value = db.Column(db.Float)
# infos sur saisie de cette note:
comment = db.Column(db.Text) # texte libre
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
uid = db.Column(db.Integer, db.ForeignKey("user.id"))
class NotesNotesLog(db.Model):
"""Historique des modifs sur notes (anciennes entrees de notes_notes)"""
__tablename__ = "notes_notes_log"
id = db.Column(db.Integer, primary_key=True)
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id"),
)
evaluation_id = db.Column(
db.Integer,
# db.ForeignKey("notes_evaluation.id"),
index=True,
)
value = db.Column(db.Float)
# infos sur saisie de cette note:
comment = db.Column(db.Text) # texte libre
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
uid = db.Column(db.Integer, db.ForeignKey("user.id"))

18
app/models/preferences.py Normal file
View File

@ -0,0 +1,18 @@
# -*- coding: UTF-8 -*
"""Model : preferences
"""
from app import db
class ScoPreferences(db.Model):
"""ScoDoc preferences"""
__tablename__ = "sco_prefs"
id = db.Column(db.Integer, primary_key=True)
pref_id = db.synonym("id")
dept = db.Column(db.String(16), index=True)
name = db.Column(db.String(128), nullable=False)
value = db.Column(db.Text())
formsemestre_id = db.Column(db.Integer, db.ForeignKey("notes_formsemestre.id"))

View File

@ -102,7 +102,7 @@ def do_modalite_delete(context, oid, REQUEST=None):
_modaliteEditor.delete(cnx, oid)
def do_modalite_edit(context, *args, **kw):
def do_modalite_edit(context, *args, **kw): # unused
"edit a modalite"
cnx = ndb.GetDBConnexion()
# check

View File

@ -202,34 +202,34 @@ CREATE TABLE etud_annotations (
) WITH OIDS;
-- ------------ Nouvelle gestion des absences ------------
CREATE SEQUENCE abs_idgen;
CREATE FUNCTION abs_newid( text ) returns text as '
select $1 || to_char( nextval(''abs_idgen''), ''FM999999999'' )
as result;
' language SQL;
-- CREATE SEQUENCE abs_idgen;
-- CREATE FUNCTION abs_newid( text ) returns text as '
-- select $1 || to_char( nextval(''abs_idgen''), ''FM999999999'' )
-- as result;
-- ' language SQL;
CREATE TABLE abs_absences (
absid text default abs_newid('AB') PRIMARY KEY,
etudid character(32),
abs_begin timestamp with time zone,
abs_end timestamp with time zone
) WITH OIDS;
-- CREATE TABLE abs_absences (
-- absid text default abs_newid('AB') PRIMARY KEY,
-- etudid character(32),
-- abs_begin timestamp with time zone,
-- abs_end timestamp with time zone
-- ) WITH OIDS;
CREATE TABLE abs_presences (
absid text default abs_newid('PR') PRIMARY KEY,
etudid character(32),
abs_begin timestamp with time zone,
abs_end timestamp with time zone
) WITH OIDS;
-- CREATE TABLE abs_presences (
-- absid text default abs_newid('PR') PRIMARY KEY,
-- etudid character(32),
-- abs_begin timestamp with time zone,
-- abs_end timestamp with time zone
-- ) WITH OIDS;
CREATE TABLE abs_justifs (
absid text default abs_newid('JU') PRIMARY KEY,
etudid character(32),
abs_begin timestamp with time zone,
abs_end timestamp with time zone,
category text,
description text
) WITH OIDS;
-- CREATE TABLE abs_justifs (
-- absid text default abs_newid('JU') PRIMARY KEY,
-- etudid character(32),
-- abs_begin timestamp with time zone,
-- abs_end timestamp with time zone,
-- category text,
-- description text
-- ) WITH OIDS;
@ -600,12 +600,12 @@ CREATE TABLE scolar_events (
etudid text,
event_date timestamp default now(),
formsemestre_id text REFERENCES notes_formsemestre(formsemestre_id),
ue_id text REFERENCES notes_ue(ue_id),
ue_id text REFERENCES notes_ue(ue_id),
event_type text, -- 'CREATION', 'INSCRIPTION', 'DEMISSION',
-- 'AUT_RED', 'EXCLUS', 'VALID_UE', 'VALID_SEM'
-- 'ECHEC_SEM'
-- 'UTIL_COMPENSATION'
comp_formsemestre_id text REFERENCES notes_formsemestre(formsemestre_id)
comp_formsemestre_id text REFERENCES notes_formsemestre(formsemestre_id)
-- semestre compense par formsemestre_id
) WITH OIDS;

View File

@ -19,6 +19,9 @@ from flask.cli import with_appcontext
from app import create_app, cli, db
from app.auth.models import User, Role, UserRole
from app import models
from app.models import ScoPreferences, FormSemestre, FormModalite
from app.views import notes, scolar, absences
import app.utils as utils
@ -35,11 +38,17 @@ def make_shell_context():
from flask_login import login_user, logout_user, current_user
admin_role = Role.query.filter_by(name="SuperAdmin").first()
admin = (
User.query.join(UserRole)
.filter((UserRole.user_id == User.id) & (UserRole.role_id == admin_role.id))
.first()
)
if admin_role:
admin = (
User.query.join(UserRole)
.filter((UserRole.user_id == User.id) & (UserRole.role_id == admin_role.id))
.first()
)
else:
click.echo(
"Warning: user database not initialized !\n (use: flask user-db-init)"
)
admin = None
return {
"db": db,
@ -58,6 +67,10 @@ def make_shell_context():
"logout_user": logout_user,
"admin": admin,
"ctx": app.test_request_context(),
# "FormModalite": FormModalite,
# "ScoPreferences": ScoPreferences,
# "FormSemestre": FormSemestre,
"models": models,
}