1
0
forked from ScoDoc/ScoDoc

correction etat branche + tests unitaire + tests api

This commit is contained in:
iziram 2023-02-03 14:51:05 +01:00
parent a63e14ce06
commit 4d72fec42d
10 changed files with 140 additions and 85 deletions

View File

@ -400,8 +400,8 @@ def justif_import(justif_id: int = None):
return json_error(404, err.args[0])
@bp.route("/justificatif/export/<int:justif_id>/<filename>", methods=["GET"])
@api_web_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=["POST"])
@scodoc
@login_required
@permission_required(Permission.ScoView)

View File

@ -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,

View File

@ -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(

View File

@ -256,12 +256,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"),
@ -280,3 +291,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

View File

@ -36,7 +36,7 @@ from flask_login import current_user
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
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb

View File

@ -1621,10 +1621,24 @@ 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} de {formsemestre.titre_annee()}",
commit=True,
)
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
else:
sco_moduleimpl_inscriptions.do_etud_desinscrit_ue_classic(

View File

@ -10,58 +10,86 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'dbcf2175e87f'
down_revision = '5c7b208355df'
revision = "dbcf2175e87f"
down_revision = "5c7b208355df"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('justificatifs',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_debut', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('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_table(
"justificatifs",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column(
"date_debut",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.Column(
"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_table('assiduites',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_debut', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
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_justificatifs_etudid"), "justificatifs", ["etudid"], unique=False
)
op.create_table(
"assiduites",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column(
"date_debut",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
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 ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('dispenseUE', sa.Column('formsemestre_id', sa.INTEGER(), autoincrement=False, nullable=True))
op.create_foreign_key('dispenseUE_formsemestre_id_fkey', 'dispenseUE', 'notes_formsemestre', ['formsemestre_id'], ['id'])
op.drop_constraint(None, 'dispenseUE', type_='unique')
op.create_index('ix_dispenseUE_formsemestre_id', 'dispenseUE', ['formsemestre_id'], unique=False)
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')
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 ###

View File

@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.4.35"
SCOVERSION = "9.4.36"
SCONAME = "ScoDoc"

View File

@ -342,21 +342,23 @@ def test_list_justificatifs(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}"
res = requests.get(url, headers=api_headers)
res = requests.post(url, headers=api_headers)
return res
def test_export(api_headers):
# 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
assert get_export(FAUX, "test_api_justificatif.txt", api_headers).status_code == 404
assert get_export(1, "blabla.txt", api_headers).status_code == 404
assert get_export(2, "blabla.txt", api_headers).status_code == 404
assert (
post_export(FAUX, "test_api_justificatif.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):

View File

@ -123,7 +123,13 @@ def test_ue_moy(test_client):
modimpl.module.ue.type != UE_SPORT for modimpl in formsemestre.modimpls_sorted
]
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[ue2.id][etudid] == n1