1
0
forked from ScoDoc/ScoDoc

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) _modaliteEditor.delete(cnx, oid)
def do_modalite_edit(context, *args, **kw): def do_modalite_edit(context, *args, **kw): # unused
"edit a modalite" "edit a modalite"
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
# check # check

View File

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

View File

@ -19,6 +19,9 @@ from flask.cli import with_appcontext
from app import create_app, cli, db from app import create_app, cli, db
from app.auth.models import User, Role, UserRole 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 from app.views import notes, scolar, absences
import app.utils as utils import app.utils as utils
@ -35,11 +38,17 @@ def make_shell_context():
from flask_login import login_user, logout_user, current_user from flask_login import login_user, logout_user, current_user
admin_role = Role.query.filter_by(name="SuperAdmin").first() admin_role = Role.query.filter_by(name="SuperAdmin").first()
admin = ( if admin_role:
User.query.join(UserRole) admin = (
.filter((UserRole.user_id == User.id) & (UserRole.role_id == admin_role.id)) User.query.join(UserRole)
.first() .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 { return {
"db": db, "db": db,
@ -58,6 +67,10 @@ def make_shell_context():
"logout_user": logout_user, "logout_user": logout_user,
"admin": admin, "admin": admin,
"ctx": app.test_request_context(), "ctx": app.test_request_context(),
# "FormModalite": FormModalite,
# "ScoPreferences": ScoPreferences,
# "FormSemestre": FormSemestre,
"models": models,
} }