forked from ScoDoc/ScoDoc
Dispenses d'UE BUT associées à un formsemestre
This commit is contained in:
parent
3121a6d54c
commit
71116e6b39
@ -33,10 +33,7 @@ import pandas as pd
|
||||
from app import db
|
||||
from app import models
|
||||
from app.models import (
|
||||
DispenseUE,
|
||||
FormSemestre,
|
||||
FormSemestreInscription,
|
||||
Identite,
|
||||
Module,
|
||||
ModuleImpl,
|
||||
ModuleUECoef,
|
||||
@ -218,31 +215,6 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
||||
)
|
||||
|
||||
|
||||
def load_dispense_ues(
|
||||
formsemestre: FormSemestre, etudids: pd.Index, ues: list[UniteEns]
|
||||
) -> set[tuple[int, int]]:
|
||||
"""Construit l'ensemble des
|
||||
etudids = modimpl_inscr_df.index, # les etudids
|
||||
ue_ids : modimpl_coefs_df.index, # les UE du formsemestre sans les UE bonus sport
|
||||
|
||||
Résultat: set de (etudid, ue_id).
|
||||
"""
|
||||
dispense_ues = set()
|
||||
ue_sem_by_code = {ue.ue_code: ue for ue in ues}
|
||||
# Prend toutes les dispenses obtenues par des étudiants de ce formsemestre,
|
||||
# puis filtre sur inscrits et code d'UE UE
|
||||
for dispense_ue in DispenseUE.query.join(
|
||||
Identite, FormSemestreInscription
|
||||
).filter_by(formsemestre_id=formsemestre.id):
|
||||
if dispense_ue.etudid in etudids:
|
||||
# UE dans le semestre avec même code ?
|
||||
ue = ue_sem_by_code.get(dispense_ue.ue.ue_code)
|
||||
if ue is not None:
|
||||
dispense_ues.add((dispense_ue.etudid, ue.id))
|
||||
|
||||
return dispense_ues
|
||||
|
||||
|
||||
def compute_ue_moys_apc(
|
||||
sem_cube: np.array,
|
||||
etuds: list,
|
||||
|
@ -16,7 +16,7 @@ from app.comp.res_compat import NotesTableCompat
|
||||
from app.comp.bonus_spo import BonusSport
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.models.moduleimpls import ModuleImpl
|
||||
from app.models.ues import UniteEns
|
||||
from app.models.ues import DispenseUE, UniteEns
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
@ -72,7 +72,7 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
||||
modimpl.module.ue.type != UE_SPORT
|
||||
for modimpl in self.formsemestre.modimpls_sorted
|
||||
]
|
||||
self.dispense_ues = moy_ue.load_dispense_ues(
|
||||
self.dispense_ues = DispenseUE.load_formsemestre_dispense_ues_set(
|
||||
self.formsemestre, self.modimpl_inscr_df.index, self.ues
|
||||
)
|
||||
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||
|
@ -59,9 +59,8 @@ class FormSemestre(db.Model):
|
||||
titre = db.Column(db.Text(), nullable=False)
|
||||
date_debut = db.Column(db.Date(), nullable=False)
|
||||
date_fin = db.Column(db.Date(), nullable=False)
|
||||
etat = db.Column(
|
||||
db.Boolean(), nullable=False, default=True, server_default="true"
|
||||
) # False si verrouillé
|
||||
etat = db.Column(db.Boolean(), nullable=False, default=True, server_default="true")
|
||||
"False si verrouillé"
|
||||
modalite = db.Column(
|
||||
db.String(SHORT_STR_LEN), db.ForeignKey("notes_form_modalites.modalite")
|
||||
) # "FI", "FAP", "FC", ...
|
||||
|
@ -1,6 +1,8 @@
|
||||
"""ScoDoc 9 models : Unités d'Enseignement (UE)
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from app import db, log
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
@ -249,12 +251,23 @@ class UniteEns(db.Model):
|
||||
|
||||
class DispenseUE(db.Model):
|
||||
"""Dispense d'UE
|
||||
Utilisé en PCC (BUT) pour indiquer les étudiants redoublants avec une UE capitalisée
|
||||
Utilisé en APC (BUT) pour indiquer les étudiants redoublants avec une UE capitalisée
|
||||
qu'ils ne refont pas.
|
||||
La dispense d'UE n'est PAS une validation:
|
||||
- elle n'est pas affectée par les décisions de jury (pas effacée)
|
||||
- elle est associée à un formsemestre
|
||||
- elle ne permet pas la délivrance d'ECTS ou du diplôme.
|
||||
|
||||
On utilise cette dispense et non une "inscription" par souci d'efficacité:
|
||||
en général, la grande majorité des étudiants suivront toutes les UEs de leur parcours,
|
||||
la dispense étant une exception.
|
||||
"""
|
||||
|
||||
__table_args__ = (db.UniqueConstraint("ue_id", "etudid"),)
|
||||
__table_args__ = (db.UniqueConstraint("formsemestre_id", "ue_id", "etudid"),)
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
formsemestre_id = formsemestre_id = db.Column(
|
||||
db.Integer, db.ForeignKey("notes_formsemestre.id"), index=True, nullable=True
|
||||
)
|
||||
ue_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey(UniteEns.id, ondelete="CASCADE"),
|
||||
@ -273,3 +286,25 @@ class DispenseUE(db.Model):
|
||||
def __repr__(self) -> str:
|
||||
return f"""<{self.__class__.__name__} {self.id} etud={
|
||||
repr(self.etud)} ue={repr(self.ue)}>"""
|
||||
|
||||
@classmethod
|
||||
def load_formsemestre_dispense_ues_set(
|
||||
cls, formsemestre: "FormSemestre", etudids: pd.Index, ues: list[UniteEns]
|
||||
) -> set[tuple[int, int]]:
|
||||
"""Construit l'ensemble des
|
||||
etudids = modimpl_inscr_df.index, # les etudids
|
||||
ue_ids : modimpl_coefs_df.index, # les UE du formsemestre sans les UE bonus sport
|
||||
|
||||
Résultat: set de (etudid, ue_id).
|
||||
"""
|
||||
# Prend toutes les dispenses obtenues par des étudiants de ce formsemestre,
|
||||
# puis filtre sur inscrits et ues
|
||||
ue_ids = {ue.id for ue in ues}
|
||||
dispense_ues = {
|
||||
(dispense_ue.etudid, dispense_ue.ue_id)
|
||||
for dispense_ue in DispenseUE.query.filter_by(
|
||||
formsemestre_id=formsemestre.id
|
||||
)
|
||||
if dispense_ue.etudid in etudids and dispense_ue.ue_id in ue_ids
|
||||
}
|
||||
return dispense_ues
|
||||
|
@ -1088,8 +1088,9 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
||||
"""<span class="help">cliquez sur un module pour saisir des notes</span>"""
|
||||
)
|
||||
elif datetime.date.today() > formsemestre.date_fin:
|
||||
if formsemestre.etat:
|
||||
H.append(
|
||||
"""<span class="formsemestre_status_warning">semestre terminé</span>"""
|
||||
"""<span class="formsemestre_status_warning">semestre du passé non verrouillé</span>"""
|
||||
)
|
||||
else:
|
||||
H.append(
|
||||
|
@ -35,15 +35,13 @@ from flask_login import current_user
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import FormSemestre, Identite, UniteEns
|
||||
from app.models import FormSemestre, Identite, ScolarFormSemestreValidation, UniteEns
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc.scolog import logdb
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import htmlutils
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_edit_ue
|
||||
from app.scodoc import sco_etud
|
||||
@ -51,8 +49,10 @@ from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_moduleimpl
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
|
||||
def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
|
||||
@ -527,14 +527,39 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
|
||||
>{etud.nomprenom}</a></td>"""
|
||||
)
|
||||
for ue in ues:
|
||||
td_class = ""
|
||||
est_inscr = ues_etud.get(ue.id) # None si pas concerné
|
||||
if est_inscr is None:
|
||||
content = ""
|
||||
else:
|
||||
# Validations d'UE déjà enregistrées dans d'autres semestres
|
||||
validations_ue = (
|
||||
ScolarFormSemestreValidation.query.filter_by(etudid=etudid)
|
||||
.filter(
|
||||
ScolarFormSemestreValidation.formsemestre_id
|
||||
!= res.formsemestre.id,
|
||||
ScolarFormSemestreValidation.code.in_(
|
||||
sco_codes_parcours.CODES_UE_VALIDES
|
||||
),
|
||||
)
|
||||
.join(UniteEns)
|
||||
.filter_by(ue_code=ue.ue_code)
|
||||
.all()
|
||||
)
|
||||
validations_ue.sort(
|
||||
key=lambda v: sco_codes_parcours.BUT_CODES_ORDERED.get(v.code, 0)
|
||||
)
|
||||
validation = validations_ue[-1] if validations_ue else None
|
||||
expl_validation = (
|
||||
f"""Validée ({validation.code}) le {validation.event_date.strftime("%d/%m/%Y")}"""
|
||||
if validation
|
||||
else ""
|
||||
)
|
||||
td_class = ' class="ue_validee"' if validation else ""
|
||||
content = f"""<input type="checkbox"
|
||||
{'checked' if est_inscr else ''}
|
||||
{'disabled' if read_only else ''}
|
||||
title="{etud.nomprenom} {'inscrit' if est_inscr else 'non inscrit'} à l'UE {ue.acronyme}",
|
||||
title="{etud.nomprenom} {'inscrit' if est_inscr else 'non inscrit'} à l'UE {ue.acronyme}. {expl_validation}",
|
||||
onchange="change_ue_inscr(this);"
|
||||
data-url_inscr={
|
||||
url_for("notes.etud_inscrit_ue",
|
||||
@ -549,7 +574,7 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
|
||||
/>
|
||||
"""
|
||||
|
||||
H.append(f"""<td>{content}</td>""")
|
||||
H.append(f"""<td{td_class}>{content}</td>""")
|
||||
H.append(
|
||||
"""</table>
|
||||
</form>
|
||||
|
@ -1992,6 +1992,10 @@ div.list_but_ue_inscriptions table td {
|
||||
border-bottom: 1px solid salmon;
|
||||
}
|
||||
|
||||
div.list_but_ue_inscriptions table td.ue_validee {
|
||||
background-color: #a1f539;
|
||||
}
|
||||
|
||||
form.list_but_ue_inscriptions {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
@ -2004,7 +2008,6 @@ form.list_but_ue_inscriptions td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/* Formulaire edition des partitions */
|
||||
form#editpart table {
|
||||
border: 1px solid gray;
|
||||
|
@ -1615,15 +1615,22 @@ def etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
|
||||
ue = UniteEns.query.get_or_404(ue_id)
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
if ue.formation.is_apc():
|
||||
if DispenseUE.query.filter_by(etudid=etudid, ue_id=ue_id).count() == 0:
|
||||
disp = DispenseUE(ue_id=ue_id, etudid=etudid)
|
||||
if (
|
||||
DispenseUE.query.filter_by(
|
||||
formsemestre_id=formsemestre_id, etudid=etudid, ue_id=ue_id
|
||||
).count()
|
||||
== 0
|
||||
):
|
||||
disp = DispenseUE(
|
||||
formsemestre_id=formsemestre_id, ue_id=ue_id, etudid=etudid
|
||||
)
|
||||
db.session.add(disp)
|
||||
db.session.commit()
|
||||
log(f"etud_desinscrit_ue {etud} {ue}")
|
||||
Scolog.logdb(
|
||||
method="etud_desinscrit_ue",
|
||||
etudid=etud.id,
|
||||
msg=f"Désinscription de l'UE {ue.acronyme}",
|
||||
msg=f"Désinscription de l'UE {ue.acronyme} de {formsemestre.titre_annee()}",
|
||||
commit=True,
|
||||
)
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
||||
@ -1658,13 +1665,15 @@ def etud_inscrit_ue(etudid, formsemestre_id, ue_id):
|
||||
etud = Identite.query.get_or_404(etudid)
|
||||
ue = UniteEns.query.get_or_404(ue_id)
|
||||
if ue.formation.is_apc():
|
||||
for disp in DispenseUE.query.filter_by(etudid=etud.id, ue_id=ue_id):
|
||||
for disp in DispenseUE.query.filter_by(
|
||||
formsemestre_id=formsemestre_id, etudid=etud.id, ue_id=ue_id
|
||||
):
|
||||
db.session.delete(disp)
|
||||
log(f"etud_inscrit_ue {etud} {ue}")
|
||||
Scolog.logdb(
|
||||
method="etud_inscrit_ue",
|
||||
etudid=etud.id,
|
||||
msg=f"Inscription à l'UE {ue.acronyme}",
|
||||
msg=f"Inscription à l'UE {ue.acronyme} de {formsemestre.titre_annee()}",
|
||||
commit=True,
|
||||
)
|
||||
db.session.commit()
|
||||
|
86
migrations/versions/25e3ca6cc063_dispenseue_par_semestre.py
Normal file
86
migrations/versions/25e3ca6cc063_dispenseue_par_semestre.py
Normal file
@ -0,0 +1,86 @@
|
||||
"""DispenseUE par semestre
|
||||
|
||||
Revision ID: 25e3ca6cc063
|
||||
Revises: 7e5b519a27e1
|
||||
Create Date: 2023-01-13 17:19:49.431591
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm import sessionmaker # added by ev
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "25e3ca6cc063"
|
||||
down_revision = "7e5b519a27e1"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
Session = sessionmaker()
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column(
|
||||
"dispenseUE", sa.Column("formsemestre_id", sa.Integer(), nullable=True)
|
||||
)
|
||||
op.drop_constraint("dispenseUE_ue_id_etudid_key", "dispenseUE", type_="unique")
|
||||
op.create_index(
|
||||
op.f("ix_dispenseUE_formsemestre_id"),
|
||||
"dispenseUE",
|
||||
["formsemestre_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_unique_constraint(
|
||||
None, "dispenseUE", ["formsemestre_id", "ue_id", "etudid"]
|
||||
)
|
||||
op.create_foreign_key(
|
||||
None, "dispenseUE", "notes_formsemestre", ["formsemestre_id"], ["id"]
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
# Affecte les dispenses au formsemestre le plus récent ayant cette UE
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind)
|
||||
dispenses = session.execute(
|
||||
"""SELECT id, ue_id, etudid FROM "dispenseUE" WHERE formsemestre_id IS NULL;"""
|
||||
).all()
|
||||
for dispense_id, ue_id, etudid in dispenses:
|
||||
formsemestre_ids = session.execute(
|
||||
"""
|
||||
SELECT notes_formsemestre.id
|
||||
FROM notes_formsemestre, notes_formations, notes_ue, notes_formsemestre_inscription
|
||||
WHERE notes_formsemestre.formation_id = notes_formations.id
|
||||
and notes_ue.formation_id = notes_formations.id
|
||||
and notes_ue.semestre_idx=notes_formsemestre.semestre_id
|
||||
and notes_formsemestre_inscription.formsemestre_id=notes_formsemestre.id
|
||||
and notes_ue.id = :ue_id
|
||||
and notes_formsemestre_inscription.etudid = :etudid
|
||||
ORDER BY notes_formsemestre.date_debut DESC
|
||||
LIMIT 1;
|
||||
""",
|
||||
{"ue_id": ue_id, "etudid": etudid},
|
||||
).all()
|
||||
if formsemestre_ids:
|
||||
formsemestre_id = formsemestre_ids[0][0]
|
||||
session.execute(
|
||||
"""
|
||||
UPDATE "dispenseUE" SET formsemestre_id=:formsemestre_id WHERE id=:dispense_id""",
|
||||
{"formsemestre_id": formsemestre_id, "dispense_id": dispense_id},
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(
|
||||
"dispenseUE_formsemestre_id_fkey", "dispenseUE", type_="foreignkey"
|
||||
)
|
||||
op.drop_constraint(
|
||||
"dispenseUE_formsemestre_id_ue_id_etudid_key", "dispenseUE", type_="unique"
|
||||
)
|
||||
op.drop_index(op.f("ix_dispenseUE_formsemestre_id"), table_name="dispenseUE")
|
||||
op.create_unique_constraint(
|
||||
"dispenseUE_ue_id_etudid_key", "dispenseUE", ["ue_id", "etudid"]
|
||||
)
|
||||
op.drop_column("dispenseUE", "formsemestre_id")
|
||||
# ### end Alembic commands ###
|
@ -24,7 +24,7 @@ from app import clear_scodoc_cache
|
||||
from app import models
|
||||
|
||||
from app.auth.models import User, Role, UserRole
|
||||
from app.scodoc.sco_logos import make_logo_local
|
||||
from app.entreprises.models import entreprises_reset_database
|
||||
from app.models import departements
|
||||
from app.models import Formation, UniteEns, Matiere, Module
|
||||
from app.models import FormSemestre, FormSemestreInscription
|
||||
@ -32,8 +32,9 @@ from app.models import GroupDescr
|
||||
from app.models import Identite
|
||||
from app.models import ModuleImpl, ModuleImplInscription
|
||||
from app.models import Partition
|
||||
from app.models import ScolarFormSemestreValidation
|
||||
from app.models.evaluations import Evaluation
|
||||
from app.entreprises.models import entreprises_reset_database
|
||||
from app.scodoc.sco_logos import make_logo_local
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.views import notes, scolar
|
||||
import tools
|
||||
@ -52,6 +53,7 @@ def make_shell_context():
|
||||
import app as mapp # le package app
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
return {
|
||||
@ -83,7 +85,9 @@ def make_shell_context():
|
||||
"pp": pp,
|
||||
"Role": Role,
|
||||
"res_sem": res_sem,
|
||||
"ResultatsSemestreBUT": ResultatsSemestreBUT,
|
||||
"scolar": scolar,
|
||||
"ScolarFormSemestreValidation": ScolarFormSemestreValidation,
|
||||
"ScolarNews": models.ScolarNews,
|
||||
"scu": scu,
|
||||
"UniteEns": UniteEns,
|
||||
|
Loading…
Reference in New Issue
Block a user