forked from ScoDoc/ScoDoc
Page bilan ECTS etudiant toutes formations
This commit is contained in:
parent
d4fd6527e5
commit
abb6907a5d
@ -18,6 +18,7 @@ from collections.abc import Iterable
|
||||
from operator import attrgetter
|
||||
|
||||
from flask import g, url_for
|
||||
from flask_sqlalchemy.query import Query
|
||||
|
||||
from app import db, log
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
@ -393,6 +394,26 @@ def but_ects_valides(
|
||||
et ne les compte qu'une fois même en cas de redoublement avec re-validation.
|
||||
Si annees_but est spécifié, un iterable "BUT1, "BUT2" par exemple, ne prend que ces années.
|
||||
"""
|
||||
validations = but_validations_ues(etud, referentiel_competence_id, annees_but)
|
||||
ects_dict = {}
|
||||
for v in validations:
|
||||
key = (v.ue.semestre_idx, v.ue.niveau_competence.id)
|
||||
if v.code in CODES_UE_VALIDES:
|
||||
ects_dict[key] = v.ue.ects
|
||||
|
||||
return int(sum(ects_dict.values())) if ects_dict else 0
|
||||
|
||||
|
||||
def but_validations_ues(
|
||||
etud: Identite,
|
||||
referentiel_competence_id: int,
|
||||
annees_but: None | Iterable[str] = None,
|
||||
) -> Query:
|
||||
"""Query les validations d'UEs pour cet étudiant
|
||||
dans des UEs appartenant à ce référentiel de compétence
|
||||
et en option pour les années BUT indiquées.
|
||||
annees_but : None (tout) ou liste [ "BUT1", ... ]
|
||||
"""
|
||||
validations = (
|
||||
ScolarFormSemestreValidation.query.filter_by(etudid=etud.id)
|
||||
.filter(ScolarFormSemestreValidation.ue_id != None)
|
||||
@ -403,18 +424,10 @@ def but_ects_valides(
|
||||
if annees_but:
|
||||
validations = validations.filter(ApcNiveau.annee.in_(annees_but))
|
||||
# Et restreint au référentiel de compétence:
|
||||
validations = validations.join(ApcCompetence).filter_by(
|
||||
return validations.join(ApcCompetence).filter_by(
|
||||
referentiel_id=referentiel_competence_id
|
||||
)
|
||||
|
||||
ects_dict = {}
|
||||
for v in validations:
|
||||
key = (v.ue.semestre_idx, v.ue.niveau_competence.id)
|
||||
if v.code in CODES_UE_VALIDES:
|
||||
ects_dict[key] = v.ue.ects
|
||||
|
||||
return int(sum(ects_dict.values())) if ects_dict else 0
|
||||
|
||||
|
||||
def etud_ues_de_but1_non_validees(
|
||||
etud: Identite, formation: Formation, parcour: ApcParcours
|
||||
|
@ -845,11 +845,11 @@ class FormSemestre(models.ScoDocModel):
|
||||
else:
|
||||
return ", ".join([u.get_nomcomplet() for u in self.responsables])
|
||||
|
||||
def est_responsable(self, user: User):
|
||||
def est_responsable(self, user: User) -> bool:
|
||||
"True si l'user est l'un des responsables du semestre"
|
||||
return user.id in [u.id for u in self.responsables]
|
||||
|
||||
def est_chef_or_diretud(self, user: User = None):
|
||||
def est_chef_or_diretud(self, user: User | None = None) -> bool:
|
||||
"Vrai si utilisateur (par def. current) est admin, chef dept ou responsable du semestre"
|
||||
user = user or current_user
|
||||
return user.has_permission(Permission.EditFormSemestre) or self.est_responsable(
|
||||
@ -867,7 +867,7 @@ class FormSemestre(models.ScoDocModel):
|
||||
return True # typiquement admin, chef dept
|
||||
return self.est_responsable(user)
|
||||
|
||||
def can_edit_jury(self, user: User = None):
|
||||
def can_edit_jury(self, user: User | None = None):
|
||||
"""Vrai si utilisateur (par def. current) peut saisir decision de jury
|
||||
dans ce semestre: vérifie permission et verrouillage.
|
||||
"""
|
||||
|
@ -2,12 +2,15 @@
|
||||
|
||||
"""Notes, décisions de jury
|
||||
"""
|
||||
from flask_sqlalchemy.query import Query
|
||||
|
||||
from app import db
|
||||
from app import log
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import CODE_STR_LEN
|
||||
from app.models.events import Scolog
|
||||
from app.models.formations import Formation
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.codes_cursus import CODES_UE_VALIDES
|
||||
@ -113,6 +116,7 @@ class ScolarFormSemestreValidation(db.Model):
|
||||
if self.ue.parcours else ""}
|
||||
{("émise par " + link)}
|
||||
: <b>{self.code}</b>{moyenne}
|
||||
<b>{self.ue.ects:g} ECTS</b>
|
||||
le {self.event_date.strftime(scu.DATEATIME_FMT)}
|
||||
"""
|
||||
else:
|
||||
@ -131,6 +135,27 @@ class ScolarFormSemestreValidation(db.Model):
|
||||
else 0.0
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def validations_ues(
|
||||
cls, etud: "Identite", formation_code: str | None = None
|
||||
) -> Query:
|
||||
"""Query les validations d'UE pour cet étudiant dans des UEs de formations
|
||||
du code indiqué, ou toutes si le formation_code est None.
|
||||
"""
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
query = (
|
||||
ScolarFormSemestreValidation.query.filter_by(etudid=etud.id)
|
||||
.filter(ScolarFormSemestreValidation.ue_id != None)
|
||||
.join(UniteEns)
|
||||
.join(FormSemestre, ScolarFormSemestreValidation.formsemestre)
|
||||
)
|
||||
if formation_code is not None:
|
||||
query = query.join(Formation).filter_by(formation_code=formation_code)
|
||||
return query.order_by(
|
||||
FormSemestre.semestre_id, UniteEns.numero, UniteEns.acronyme
|
||||
)
|
||||
|
||||
|
||||
class ScolarAutorisationInscription(db.Model):
|
||||
"""Autorisation d'inscription dans un semestre"""
|
||||
|
@ -31,7 +31,7 @@ import time
|
||||
|
||||
import flask
|
||||
from flask import url_for, flash, g, request
|
||||
from flask_login import current_user
|
||||
from flask.templating import render_template
|
||||
import sqlalchemy as sa
|
||||
|
||||
from app.models import Identite, Evaluation
|
||||
@ -64,7 +64,6 @@ from app.scodoc import sco_cursus_dut
|
||||
from app.scodoc.sco_cursus_dut import etud_est_inscrit_ue
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_pv_dict
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------------
|
||||
@ -1249,7 +1248,7 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
|
||||
<p>On ne peut valider ici que les UEs du cursus <b>{formation.titre}</b></p>
|
||||
</div>
|
||||
|
||||
{_get_etud_ue_cap_html(etud, formsemestre)}
|
||||
{_get_etud_ue_validations_html(etud, formsemestre)}
|
||||
|
||||
<div class="scobox">
|
||||
<div class="scobox-title">
|
||||
@ -1300,7 +1299,7 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
|
||||
return flask.redirect(dest_url)
|
||||
|
||||
|
||||
def _get_etud_ue_cap_html(etud: Identite, formsemestre: FormSemestre) -> str:
|
||||
def _get_etud_ue_validations_html(etud: Identite, formsemestre: FormSemestre) -> str:
|
||||
"""HTML listant les validations d'UEs pour cet étudiant dans des formations de même
|
||||
code que celle du formsemestre indiqué.
|
||||
"""
|
||||
@ -1319,39 +1318,13 @@ def _get_etud_ue_cap_html(etud: Identite, formsemestre: FormSemestre) -> str:
|
||||
|
||||
if not validations:
|
||||
return ""
|
||||
H = [
|
||||
f"""<div class="sco_box sco_lightgreen_bg ue_list_etud_validations">
|
||||
<div class="sco_box_title">Validations d'UEs dans cette formation</div>
|
||||
<div class="help">Liste de toutes les UEs validées par {etud.html_link_fiche()},
|
||||
sur des semestres ou déclarées comme "antérieures" (externes).
|
||||
</div>
|
||||
<ul class="liste_validations">"""
|
||||
]
|
||||
for validation in validations:
|
||||
if validation.formsemestre_id is None:
|
||||
origine = " enregistrée d'un parcours antérieur (hors ScoDoc)"
|
||||
else:
|
||||
origine = f", du semestre {formsemestre.html_link_status()}"
|
||||
if validation.semestre_id is not None:
|
||||
origine += f" (<b>S{validation.semestre_id}</b>)"
|
||||
H.append(f"""<li>{validation.html()}""")
|
||||
if (validation.formsemestre and validation.formsemestre.can_edit_jury()) or (
|
||||
current_user and current_user.has_permission(Permission.EtudInscrit)
|
||||
):
|
||||
H.append(
|
||||
f"""
|
||||
<form class="inline-form">
|
||||
<button
|
||||
data-v_id="{validation.id}" data-type="validation_ue" data-etudid="{etud.id}"
|
||||
>effacer</button>
|
||||
</form>
|
||||
""",
|
||||
)
|
||||
else:
|
||||
H.append(scu.icontag("lock_img", border="0", title="Semestre verrouillé"))
|
||||
H.append("</li>")
|
||||
H.append("</ul></div>")
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"jury/ue_list_etud_validations.j2",
|
||||
edit_mode=True,
|
||||
etud=etud,
|
||||
titre_boite="Validations d'UEs dans cette formation",
|
||||
validations=validations,
|
||||
)
|
||||
|
||||
|
||||
def do_formsemestre_validate_previous_ue(
|
||||
|
@ -121,14 +121,14 @@ def _menu_scolarite(
|
||||
"enabled": def_enabled,
|
||||
},
|
||||
{
|
||||
"title": "Inscrire à un module optionnel (ou au sport)",
|
||||
"endpoint": "notes.formsemestre_inscription_option",
|
||||
"title": "Désinscrire (en cas d'erreur)",
|
||||
"endpoint": "notes.formsemestre_desinscription",
|
||||
"args": args,
|
||||
"enabled": authuser.has_permission(Permission.EtudInscrit) and not locked,
|
||||
},
|
||||
{
|
||||
"title": "Désinscrire (en cas d'erreur)",
|
||||
"endpoint": "notes.formsemestre_desinscription",
|
||||
"title": "Inscrire à un module optionnel (ou au sport)",
|
||||
"endpoint": "notes.formsemestre_inscription_option",
|
||||
"args": args,
|
||||
"enabled": authuser.has_permission(Permission.EtudInscrit) and not locked,
|
||||
},
|
||||
@ -138,12 +138,6 @@ def _menu_scolarite(
|
||||
"args": args,
|
||||
"enabled": formsemestre.can_edit_jury(),
|
||||
},
|
||||
{
|
||||
"title": "Inscrire à un autre semestre",
|
||||
"endpoint": "notes.formsemestre_inscription_with_modules_form",
|
||||
"args": {"etudid": etudid},
|
||||
"enabled": authuser.has_permission(Permission.EtudInscrit),
|
||||
},
|
||||
{
|
||||
"title": "Enregistrer un semestre effectué ailleurs",
|
||||
"endpoint": "notes.formsemestre_ext_create_form",
|
||||
@ -156,6 +150,12 @@ def _menu_scolarite(
|
||||
"args": args,
|
||||
"enabled": authuser.has_permission(Permission.EditAllNotes),
|
||||
},
|
||||
{
|
||||
"title": "Inscrire à un autre semestre",
|
||||
"endpoint": "notes.formsemestre_inscription_with_modules_form",
|
||||
"args": {"etudid": etudid},
|
||||
"enabled": authuser.has_permission(Permission.EtudInscrit),
|
||||
},
|
||||
]
|
||||
|
||||
return htmlutils.make_menu(
|
||||
@ -317,6 +317,12 @@ def fiche_etud(etudid=None):
|
||||
|
||||
else:
|
||||
info["link_inscrire_ailleurs"] = ""
|
||||
info[
|
||||
"link_bilan_ects"
|
||||
] = f"""<span class="link_bul_pdf"><a class="stdlink" href="{
|
||||
url_for("notes.etud_bilan_ects",
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">ECTS</a></span>"""
|
||||
else:
|
||||
# non inscrit
|
||||
l = [f"""<p><b>Étudiant{etud.e} non inscrit{etud.e}"""]
|
||||
@ -331,6 +337,7 @@ def fiche_etud(etudid=None):
|
||||
info["liste_inscriptions"] = "\n".join(l)
|
||||
info["link_bul_pdf"] = ""
|
||||
info["link_inscrire_ailleurs"] = ""
|
||||
info["link_bilan_ects"] = ""
|
||||
|
||||
# Liste des annotations
|
||||
html_annotations_list = "\n".join(
|
||||
@ -433,7 +440,9 @@ def fiche_etud(etudid=None):
|
||||
"inscriptions_mkup"
|
||||
] = f"""<div class="ficheinscriptions" id="ficheinscriptions">
|
||||
<div class="fichetitre">Cursus</div>{info["liste_inscriptions"]}
|
||||
{info["link_bul_pdf"]} {info["link_inscrire_ailleurs"]}
|
||||
{info["link_bul_pdf"]}
|
||||
{info["link_inscrire_ailleurs"]}
|
||||
{info["link_bilan_ects"]}
|
||||
</div>"""
|
||||
|
||||
#
|
||||
|
@ -246,7 +246,7 @@ PREF_CATEGORIES = (
|
||||
"bul_margins",
|
||||
{
|
||||
"title": "Marges additionnelles des bulletins, en millimètres",
|
||||
"subtitle": """Le bulletin de notes notes est toujours redimensionné
|
||||
"subtitle": """Le bulletin de notes classique (pas BUT) est toujours redimensionné
|
||||
pour occuper l'espace disponible entre les marges.
|
||||
""",
|
||||
"related": ("bul", "bul_mail", "pdf"),
|
||||
|
@ -10,3 +10,9 @@ span.parcours {
|
||||
div.ue_list_etud_validations ul.liste_validations li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
div.ue_list_etud_validations div.total_ects {
|
||||
font-weight: bold;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
24
app/templates/jury/etud_bilan_ects.j2
Normal file
24
app/templates/jury/etud_bilan_ects.j2
Normal file
@ -0,0 +1,24 @@
|
||||
{% extends "sco_page.j2" %}
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
<link href="{{scu.STATIC_DIR}}/css/jury_delete_manual.css" rel="stylesheet" type="text/css" />
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block app_content %}
|
||||
<h1>Bilan des ECTS de {{etud.html_link_fiche()|safe}}</h1>
|
||||
|
||||
<div class="help">
|
||||
Cette page donne toutes les UEs acquises par l'étudiant (codes <tt>ADM, ADJ, ADJR, ADSUP, CMP...</tt>)
|
||||
dans chaque formation qu'il a suivi.
|
||||
</div>
|
||||
|
||||
{% for diplome in formsemestre_by_diplome %}
|
||||
{% set titre_boite = "Validations d'UEs dans la formation " + titre_by_diplome[diplome] %}
|
||||
{% set validations = validations_by_diplome[diplome] %}
|
||||
{% set total_ects = ects_by_diplome[diplome] %}
|
||||
{% include "jury/ue_list_etud_validations.j2" %}
|
||||
{% endfor %}
|
||||
|
||||
{% endblock app_content %}
|
31
app/templates/jury/ue_list_etud_validations.j2
Normal file
31
app/templates/jury/ue_list_etud_validations.j2
Normal file
@ -0,0 +1,31 @@
|
||||
{# Fragment de html pour cadre affichage validations d'une formation #}
|
||||
|
||||
<div class="sco_box sco_lightgreen_bg ue_list_etud_validations">
|
||||
<div class="sco_box_title">{{titre_boite}}</div>
|
||||
<div class="help">Liste de toutes les UEs validées par {{etud.html_link_fiche()|safe}},
|
||||
sur des semestres ou déclarées comme "antérieures" (externes).
|
||||
</div>
|
||||
<ul class="liste_validations">
|
||||
{% for validation in validations %}
|
||||
|
||||
<li>{{ validation.html() | safe }}
|
||||
{% if edit_mode %}
|
||||
{% if validation.formsemestre and validation.formsemestre.can_edit_jury() %}
|
||||
<form class="inline-form">
|
||||
<button data-v_id="{{validation.id}}" data-type="validation_ue" data-etudid="{{etud.id}}">
|
||||
effacer
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
{{ scu.icontag("lock_img", border="0", title="Semestre verrouillé") }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if total_ects %}
|
||||
<div class="total_ects">
|
||||
Total ECTS: {{ "%g" % total_ects }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
@ -27,7 +27,7 @@ Vues sur les jurys et validations
|
||||
|
||||
Emmanuel Viennet, 2024
|
||||
"""
|
||||
|
||||
from collections import defaultdict
|
||||
import datetime
|
||||
import flask
|
||||
from flask import flash, g, redirect, render_template, request, url_for
|
||||
@ -55,6 +55,7 @@ from app.models import (
|
||||
FormSemestreInscription,
|
||||
Identite,
|
||||
ScolarAutorisationInscription,
|
||||
ScolarFormSemestreValidation,
|
||||
ScolarNews,
|
||||
ScoDocSiteConfig,
|
||||
)
|
||||
@ -66,6 +67,7 @@ from app.scodoc import (
|
||||
sco_formsemestre_validation,
|
||||
sco_preferences,
|
||||
)
|
||||
from app.scodoc.codes_cursus import CODES_UE_VALIDES
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import (
|
||||
ScoPermissionDenied,
|
||||
@ -78,7 +80,6 @@ from app.scodoc.sco_pv_dict import descr_autorisations
|
||||
from app.views import notes_bp as bp
|
||||
from app.views import ScoData
|
||||
|
||||
|
||||
# --- FORMULAIRE POUR VALIDATION DES UE ET SEMESTRES
|
||||
|
||||
|
||||
@ -902,3 +903,60 @@ def jury_delete_manual(etudid: int):
|
||||
"""Efface toute les décisions d'une année pour cet étudiant"""
|
||||
etud = Identite.get_etud(etudid)
|
||||
return jury_edit_manual.jury_delete_manual(etud)
|
||||
|
||||
|
||||
@bp.route("/etud_bilan_ects/<int:etudid>")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def etud_bilan_ects(etudid: int):
|
||||
"""Page bilan de tous els ECTS acquis par un étudiant.
|
||||
Plusieurs formations (eg DUT, LP) peuvent être concernées.
|
||||
"""
|
||||
etud = Identite.get_etud(etudid)
|
||||
# Cherche les formations différentes (au sens des ECTS)
|
||||
# suivies par l'étudiant: regroupe ses formsemestres
|
||||
# diplome est la clé: en classique le code formation, en BUT le referentiel_competence_id
|
||||
formsemestre_by_diplome = defaultdict(list)
|
||||
for formsemestre in etud.get_formsemestres(recent_first=True):
|
||||
diplome = (
|
||||
formsemestre.formation.referentiel_competence.id
|
||||
if (
|
||||
formsemestre.formation.is_apc()
|
||||
and formsemestre.formation.referentiel_competence
|
||||
)
|
||||
else formsemestre.formation.formation_code
|
||||
)
|
||||
formsemestre_by_diplome[diplome].append(formsemestre)
|
||||
|
||||
# Pour chaque liste de formsemestres d'un même "diplôme"
|
||||
# liste les UE validées avec leurs ECTS
|
||||
ects_by_diplome = {}
|
||||
titre_by_diplome = {} # { diplome : titre }
|
||||
validations_by_diplome = {} # { diplome : query validations UEs }
|
||||
for diplome, formsemestres in formsemestre_by_diplome.items():
|
||||
formsemestre = formsemestres[0]
|
||||
titre_by_diplome[diplome] = formsemestre.formation.get_titre_version()
|
||||
if formsemestre.formation.is_apc():
|
||||
validations = cursus_but.but_validations_ues(etud, diplome)
|
||||
else:
|
||||
validations = ScolarFormSemestreValidation.validations_ues(
|
||||
etud, formsemestre.formation.formation_code
|
||||
)
|
||||
validations_by_diplome[diplome] = [
|
||||
validation
|
||||
for validation in validations
|
||||
if validation.code in CODES_UE_VALIDES
|
||||
]
|
||||
ects_by_diplome[diplome] = sum(
|
||||
(validation.ue.ects or 0.0)
|
||||
for validation in validations_by_diplome[diplome]
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"jury/etud_bilan_ects.j2",
|
||||
etud=etud,
|
||||
ects_by_diplome=ects_by_diplome,
|
||||
formsemestre_by_diplome=formsemestre_by_diplome,
|
||||
titre_by_diplome=titre_by_diplome,
|
||||
validations_by_diplome=validations_by_diplome,
|
||||
)
|
||||
|
@ -56,9 +56,9 @@ cli.register(app)
|
||||
|
||||
@app.context_processor
|
||||
def inject_sco_utils():
|
||||
"Make scu and sco available in all Jinja templates"
|
||||
"Make Permission, sco and scu available in all Jinja templates"
|
||||
# if modified, put the same in conftest.py#27
|
||||
return {"scu": scu, "sco": ScoData()}
|
||||
return {"Permission": Permission, "scu": scu, "sco": ScoData()}
|
||||
|
||||
|
||||
@app.shell_context_processor
|
||||
|
@ -11,6 +11,7 @@ from app import models
|
||||
from app.auth.models import User, Role
|
||||
from app.auth.models import get_super_admin
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.views import ScoData
|
||||
|
||||
@ -28,7 +29,7 @@ def test_client():
|
||||
@apptest.context_processor
|
||||
def inject_sco_utils():
|
||||
"Make scu available in all Jinja templates"
|
||||
return {"scu": scu, "sco": ScoData()}
|
||||
return {"Permission": Permission, "scu": scu, "sco": ScoData()}
|
||||
|
||||
with apptest.test_request_context():
|
||||
# initialize scodoc "g":
|
||||
|
Loading…
Reference in New Issue
Block a user