correction etat branche + tests unitaire ✅ + tests api ✅
This commit is contained in:
parent
a63e14ce06
commit
4d72fec42d
@ -400,8 +400,8 @@ def justif_import(justif_id: int = None):
|
|||||||
return json_error(404, err.args[0])
|
return json_error(404, err.args[0])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/justificatif/export/<int:justif_id>/<filename>", methods=["GET"])
|
@bp.route("/justificatif/export/<int:justif_id>/<filename>", methods=["POST"])
|
||||||
@api_web_bp.route("/justificatif/export/<int:justif_id>/<filename>", methods=["GET"])
|
@api_web_bp.route("/justificatif/export/<int:justif_id>/<filename>", methods=["POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
|
@ -33,10 +33,7 @@ import pandas as pd
|
|||||||
from app import db
|
from app import db
|
||||||
from app import models
|
from app import models
|
||||||
from app.models import (
|
from app.models import (
|
||||||
DispenseUE,
|
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
FormSemestreInscription,
|
|
||||||
Identite,
|
|
||||||
Module,
|
Module,
|
||||||
ModuleImpl,
|
ModuleImpl,
|
||||||
ModuleUECoef,
|
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(
|
def compute_ue_moys_apc(
|
||||||
sem_cube: np.array,
|
sem_cube: np.array,
|
||||||
etuds: list,
|
etuds: list,
|
||||||
|
@ -72,7 +72,7 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
modimpl.module.ue.type != UE_SPORT
|
modimpl.module.ue.type != UE_SPORT
|
||||||
for modimpl in self.formsemestre.modimpls_sorted
|
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.formsemestre, self.modimpl_inscr_df.index, self.ues
|
||||||
)
|
)
|
||||||
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||||
|
@ -256,12 +256,23 @@ class UniteEns(db.Model):
|
|||||||
|
|
||||||
class DispenseUE(db.Model):
|
class DispenseUE(db.Model):
|
||||||
"""Dispense d'UE
|
"""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.
|
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)
|
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(
|
ue_id = db.Column(
|
||||||
db.Integer,
|
db.Integer,
|
||||||
db.ForeignKey(UniteEns.id, ondelete="CASCADE"),
|
db.ForeignKey(UniteEns.id, ondelete="CASCADE"),
|
||||||
@ -280,3 +291,25 @@ class DispenseUE(db.Model):
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"""<{self.__class__.__name__} {self.id} etud={
|
return f"""<{self.__class__.__name__} {self.id} etud={
|
||||||
repr(self.etud)} ue={repr(self.ue)}>"""
|
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
|
||||||
|
@ -36,7 +36,7 @@ from flask_login import current_user
|
|||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
|
|
||||||
from app.models import ModuleImpl, ScolarNews
|
from app.models import Evaluation, ModuleImpl, ScolarNews
|
||||||
from app.models.evaluations import evaluation_enrich_dict, check_evaluation_args
|
from app.models.evaluations import evaluation_enrich_dict, check_evaluation_args
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
|
@ -1621,10 +1621,24 @@ def etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
|
|||||||
ue = UniteEns.query.get_or_404(ue_id)
|
ue = UniteEns.query.get_or_404(ue_id)
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
if ue.formation.is_apc():
|
if ue.formation.is_apc():
|
||||||
if DispenseUE.query.filter_by(etudid=etudid, ue_id=ue_id).count() == 0:
|
if (
|
||||||
disp = DispenseUE(ue_id=ue_id, etudid=etudid)
|
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.add(disp)
|
||||||
db.session.commit()
|
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} de {formsemestre.titre_annee()}",
|
||||||
|
commit=True,
|
||||||
|
)
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
||||||
else:
|
else:
|
||||||
sco_moduleimpl_inscriptions.do_etud_desinscrit_ue_classic(
|
sco_moduleimpl_inscriptions.do_etud_desinscrit_ue_classic(
|
||||||
|
@ -10,58 +10,86 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = 'dbcf2175e87f'
|
revision = "dbcf2175e87f"
|
||||||
down_revision = '5c7b208355df'
|
down_revision = "5c7b208355df"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('justificatifs',
|
op.create_table(
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
"justificatifs",
|
||||||
sa.Column('date_debut', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
sa.Column('date_fin', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
sa.Column(
|
||||||
sa.Column('etudid', sa.Integer(), nullable=False),
|
"date_debut",
|
||||||
sa.Column('etat', sa.Integer(), nullable=False),
|
sa.DateTime(timezone=True),
|
||||||
sa.Column('entry_date', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
server_default=sa.text("now()"),
|
||||||
sa.Column('raison', sa.Text(), nullable=True),
|
nullable=False,
|
||||||
sa.Column('fichier', sa.Text(), nullable=True),
|
),
|
||||||
sa.ForeignKeyConstraint(['etudid'], ['identite.id'], ondelete='CASCADE'),
|
sa.Column(
|
||||||
sa.PrimaryKeyConstraint('id')
|
"date_fin",
|
||||||
|
sa.DateTime(timezone=True),
|
||||||
|
server_default=sa.text("now()"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
sa.Column("etudid", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("etat", sa.Integer(), nullable=False),
|
||||||
|
sa.Column(
|
||||||
|
"entry_date",
|
||||||
|
sa.DateTime(timezone=True),
|
||||||
|
server_default=sa.text("now()"),
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("raison", sa.Text(), nullable=True),
|
||||||
|
sa.Column("fichier", sa.Text(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(["etudid"], ["identite.id"], ondelete="CASCADE"),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
)
|
)
|
||||||
op.create_index(op.f('ix_justificatifs_etudid'), 'justificatifs', ['etudid'], unique=False)
|
op.create_index(
|
||||||
op.create_table('assiduites',
|
op.f("ix_justificatifs_etudid"), "justificatifs", ["etudid"], unique=False
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
)
|
||||||
sa.Column('date_debut', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
op.create_table(
|
||||||
sa.Column('date_fin', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
"assiduites",
|
||||||
sa.Column('moduleimpl_id', sa.Integer(), nullable=True),
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
sa.Column('etudid', sa.Integer(), nullable=False),
|
sa.Column(
|
||||||
sa.Column('etat', sa.Integer(), nullable=False),
|
"date_debut",
|
||||||
sa.Column('desc', sa.Text(), nullable=True),
|
sa.DateTime(timezone=True),
|
||||||
sa.Column('entry_date', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
server_default=sa.text("now()"),
|
||||||
sa.ForeignKeyConstraint(['etudid'], ['identite.id'], ondelete='CASCADE'),
|
nullable=False,
|
||||||
sa.ForeignKeyConstraint(['moduleimpl_id'], ['notes_moduleimpl.id'], ondelete='SET NULL'),
|
),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.Column(
|
||||||
|
"date_fin",
|
||||||
|
sa.DateTime(timezone=True),
|
||||||
|
server_default=sa.text("now()"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
sa.Column("moduleimpl_id", sa.Integer(), nullable=True),
|
||||||
|
sa.Column("etudid", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("etat", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("desc", sa.Text(), nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"entry_date",
|
||||||
|
sa.DateTime(timezone=True),
|
||||||
|
server_default=sa.text("now()"),
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(["etudid"], ["identite.id"], ondelete="CASCADE"),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["moduleimpl_id"], ["notes_moduleimpl.id"], ondelete="SET NULL"
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_assiduites_etudid"), "assiduites", ["etudid"], unique=False
|
||||||
)
|
)
|
||||||
op.create_index(op.f('ix_assiduites_etudid'), 'assiduites', ['etudid'], unique=False)
|
|
||||||
op.drop_constraint('dispenseUE_formsemestre_id_ue_id_etudid_key', 'dispenseUE', type_='unique')
|
|
||||||
op.drop_index('ix_dispenseUE_formsemestre_id', table_name='dispenseUE')
|
|
||||||
op.create_unique_constraint(None, 'dispenseUE', ['ue_id', 'etudid'])
|
|
||||||
op.drop_constraint('dispenseUE_formsemestre_id_fkey', 'dispenseUE', type_='foreignkey')
|
|
||||||
op.drop_column('dispenseUE', 'formsemestre_id')
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.add_column('dispenseUE', sa.Column('formsemestre_id', sa.INTEGER(), autoincrement=False, nullable=True))
|
op.drop_index(op.f("ix_assiduites_etudid"), table_name="assiduites")
|
||||||
op.create_foreign_key('dispenseUE_formsemestre_id_fkey', 'dispenseUE', 'notes_formsemestre', ['formsemestre_id'], ['id'])
|
op.drop_table("assiduites")
|
||||||
op.drop_constraint(None, 'dispenseUE', type_='unique')
|
op.drop_index(op.f("ix_justificatifs_etudid"), table_name="justificatifs")
|
||||||
op.create_index('ix_dispenseUE_formsemestre_id', 'dispenseUE', ['formsemestre_id'], unique=False)
|
op.drop_table("justificatifs")
|
||||||
op.create_unique_constraint('dispenseUE_formsemestre_id_ue_id_etudid_key', 'dispenseUE', ['formsemestre_id', 'ue_id', 'etudid'])
|
|
||||||
op.drop_index(op.f('ix_assiduites_etudid'), table_name='assiduites')
|
|
||||||
op.drop_table('assiduites')
|
|
||||||
op.drop_index(op.f('ix_justificatifs_etudid'), table_name='justificatifs')
|
|
||||||
op.drop_table('justificatifs')
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.4.35"
|
SCOVERSION = "9.4.36"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
@ -342,21 +342,23 @@ def test_list_justificatifs(api_headers):
|
|||||||
check_failure_get(f"/justificatif/list/{FAUX}", api_headers)
|
check_failure_get(f"/justificatif/list/{FAUX}", api_headers)
|
||||||
|
|
||||||
|
|
||||||
def get_export(id: int, fname: str, api_headers):
|
def post_export(id: int, fname: str, api_headers):
|
||||||
url: str = API_URL + f"/justificatif/export/{id}/{fname}"
|
url: str = API_URL + f"/justificatif/export/{id}/{fname}"
|
||||||
res = requests.get(url, headers=api_headers)
|
res = requests.post(url, headers=api_headers)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def test_export(api_headers):
|
def test_export(api_headers):
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
assert get_export(1, "test_api_justificatif.txt", api_headers).status_code == 200
|
assert post_export(1, "test_api_justificatif.txt", api_headers).status_code == 200
|
||||||
|
|
||||||
# Mauvais fonctionnement
|
# Mauvais fonctionnement
|
||||||
assert get_export(FAUX, "test_api_justificatif.txt", api_headers).status_code == 404
|
assert (
|
||||||
assert get_export(1, "blabla.txt", api_headers).status_code == 404
|
post_export(FAUX, "test_api_justificatif.txt", api_headers).status_code == 404
|
||||||
assert get_export(2, "blabla.txt", api_headers).status_code == 404
|
)
|
||||||
|
assert post_export(1, "blabla.txt", api_headers).status_code == 404
|
||||||
|
assert post_export(2, "blabla.txt", api_headers).status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_remove_justificatif(api_headers):
|
def test_remove_justificatif(api_headers):
|
||||||
|
@ -123,7 +123,13 @@ def test_ue_moy(test_client):
|
|||||||
modimpl.module.ue.type != UE_SPORT for modimpl in formsemestre.modimpls_sorted
|
modimpl.module.ue.type != UE_SPORT for modimpl in formsemestre.modimpls_sorted
|
||||||
]
|
]
|
||||||
etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||||
sem_cube, etuds, modimpls, modimpl_inscr_df, modimpl_coefs_df, modimpl_mask
|
sem_cube,
|
||||||
|
etuds,
|
||||||
|
modimpls,
|
||||||
|
modimpl_inscr_df,
|
||||||
|
modimpl_coefs_df,
|
||||||
|
modimpl_mask,
|
||||||
|
set(),
|
||||||
)
|
)
|
||||||
assert etud_moy_ue[ue1.id][etudid] == n1
|
assert etud_moy_ue[ue1.id][etudid] == n1
|
||||||
assert etud_moy_ue[ue2.id][etudid] == n1
|
assert etud_moy_ue[ue2.id][etudid] == n1
|
||||||
|
Loading…
Reference in New Issue
Block a user