Table recap. formation, avec édition codes Apogée

This commit is contained in:
Emmanuel Viennet 2022-04-20 22:55:40 +02:00
parent 4fea9701cb
commit 34bbfec443
10 changed files with 269 additions and 105 deletions

View File

@ -36,18 +36,21 @@ class Scolog(db.Model):
class ScolarNews(db.Model): class ScolarNews(db.Model):
"""Nouvelles pour page d'accueil""" """Nouvelles pour page d'accueil"""
NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id)
NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id)
NEWS_FORM = "FORM" # modification formation (object=formation_id)
NEWS_SEM = "SEM" # creation semestre (object=None)
NEWS_ABS = "ABS" # saisie absence NEWS_ABS = "ABS" # saisie absence
NEWS_APO = "APO" # changements de codes APO
NEWS_FORM = "FORM" # modification formation (object=formation_id)
NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id)
NEWS_MISC = "MISC" # unused NEWS_MISC = "MISC" # unused
NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id)
NEWS_SEM = "SEM" # creation semestre (object=None)
NEWS_MAP = { NEWS_MAP = {
NEWS_INSCR: "inscription d'étudiants", NEWS_ABS: "saisie absence",
NEWS_NOTE: "saisie note", NEWS_APO: "modif. code Apogée",
NEWS_FORM: "modification formation", NEWS_FORM: "modification formation",
NEWS_SEM: "création semestre", NEWS_INSCR: "inscription d'étudiants",
NEWS_MISC: "opération", # unused NEWS_MISC: "opération", # unused
NEWS_NOTE: "saisie note",
NEWS_SEM: "création semestre",
} }
NEWS_TYPES = list(NEWS_MAP.keys()) NEWS_TYPES = list(NEWS_MAP.keys())

View File

@ -375,7 +375,7 @@ class FormSemestre(db.Model):
return f"{self.titre} {self.formation.get_parcours().SESSION_NAME} {self.semestre_id}" return f"{self.titre} {self.formation.get_parcours().SESSION_NAME} {self.semestre_id}"
def sem_modalite(self) -> str: def sem_modalite(self) -> str:
"""Le semestre et la modialité, ex "S2 FI" ou "S3 APP" """ """Le semestre et la modalité, ex "S2 FI" ou "S3 APP" """
if self.semestre_id > 0: if self.semestre_id > 0:
descr_sem = f"S{self.semestre_id}" descr_sem = f"S{self.semestre_id}"
else: else:

View File

@ -385,12 +385,16 @@ class GenTable(object):
colspan_count = colspan colspan_count = colspan
else: else:
colspan_txt = "" colspan_txt = ""
attrs = row.get("_%s_td_attrs" % cid, "")
order = row.get(f"_{cid}_order")
if order:
attrs += f' data-order="{order}"'
r.append( r.append(
"<%s%s %s%s%s>%s</%s>" "<%s%s %s%s%s>%s</%s>"
% ( % (
elem, elem,
std, std,
row.get("_%s_td_attrs" % cid, ""), attrs,
klass, klass,
colspan_txt, colspan_txt,
content, content,

View File

@ -43,10 +43,8 @@ import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import ( from app.scodoc.sco_exceptions import (
ScoGenError,
ScoValueError, ScoValueError,
ScoLockedFormError, ScoLockedFormError,
ScoNonEmptyFormationObject, ScoNonEmptyFormationObject,
@ -61,7 +59,6 @@ from app.scodoc import sco_edit_module
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl from app.scodoc import sco_moduleimpl
from app.scodoc import sco_preferences
from app.scodoc import sco_tag_module from app.scodoc import sco_tag_module
_ueEditor = ndb.EditableTable( _ueEditor = ndb.EditableTable(
@ -1355,93 +1352,6 @@ def ue_is_locked(ue_id):
return len(r) > 0 return len(r) > 0
# ---- Table recap formation
def formation_table_recap(formation_id, format="html"):
"""Table recapitulant formation."""
from app.scodoc import sco_formations
F = sco_formations.formation_list(args={"formation_id": formation_id})
if not F:
raise ScoValueError("invalid formation_id")
F = F[0]
T = []
ues = ue_list(args={"formation_id": formation_id})
for ue in ues:
Matlist = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]})
for Mat in Matlist:
Modlist = sco_edit_module.module_list(
args={"matiere_id": Mat["matiere_id"]}
)
for Mod in Modlist:
Mod["nb_moduleimpls"] = sco_edit_module.module_count_moduleimpls(
Mod["module_id"]
)
#
T.append(
{
"UE_acro": ue["acronyme"],
"Mat_tit": Mat["titre"],
"Mod_tit": Mod["abbrev"] or Mod["titre"],
"Mod_code": Mod["code"],
"Mod_coef": Mod["coefficient"],
"Mod_sem": Mod["semestre_id"],
"nb_moduleimpls": Mod["nb_moduleimpls"],
"heures_cours": Mod["heures_cours"],
"heures_td": Mod["heures_td"],
"heures_tp": Mod["heures_tp"],
"ects": Mod["ects"],
}
)
columns_ids = [
"UE_acro",
"Mat_tit",
"Mod_tit",
"Mod_code",
"Mod_coef",
"Mod_sem",
"nb_moduleimpls",
"heures_cours",
"heures_td",
"heures_tp",
"ects",
]
titles = {
"UE_acro": "UE",
"Mat_tit": "Matière",
"Mod_tit": "Module",
"Mod_code": "Code",
"Mod_coef": "Coef.",
"Mod_sem": "Sem.",
"nb_moduleimpls": "Nb utilisé",
"heures_cours": "Cours (h)",
"heures_td": "TD (h)",
"heures_tp": "TP (h)",
"ects": "ECTS",
}
title = (
"""Formation %(titre)s (%(acronyme)s) [version %(version)s] code %(formation_code)s"""
% F
)
tab = GenTable(
columns_ids=columns_ids,
rows=T,
titles=titles,
origin="Généré par %s le " % scu.sco_version.SCONAME
+ scu.timedate_human_repr()
+ "",
caption=title,
html_caption=title,
html_class="table_leftalign",
base_url="%s?formation_id=%s" % (request.base_url, formation_id),
page_title=title,
html_title="<h2>" + title + "</h2>",
pdf_title=title,
preferences=sco_preferences.SemPreferences(),
)
return tab.make_page(format=format)
def ue_list_semestre_ids(ue: dict): def ue_list_semestre_ids(ue: dict):
"""Liste triée des numeros de semestres des modules dans cette UE """Liste triée des numeros de semestres des modules dans cette UE
Il est recommandable que tous les modules d'une UE aient le même indice de semestre. Il est recommandable que tous les modules d'une UE aient le même indice de semestre.

View File

@ -0,0 +1,153 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""Table recap formation (avec champs éditables)
"""
from flask import url_for
from flask import g, request
from flask_login import current_user
from app.models import Formation, UniteEns, Module
from app.models.formations import Matiere
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_permissions import Permission
from app.scodoc import sco_preferences
import app.scodoc.sco_utils as scu
# ---- Table recap formation
def formation_table_recap(formation_id, format="html"):
"""Table recapitulant formation."""
T = []
formation = Formation.query.get_or_404(formation_id)
ues = formation.ues.order_by(UniteEns.semestre_idx, UniteEns.numero)
can_edit = current_user.has_permission(Permission.ScoChangeFormation)
li = 0
for ue in ues:
# L'UE
T.append(
{
"sem": f"S{ue.semestre_idx}" if ue.semestre_idx is not None else "-",
"_sem_order": f"{li:04d}",
"code": ue.acronyme,
"titre": ue.titre or "",
"apo": ue.code_apogee or "",
"_apo_td_attrs": f""" data-oid="{ue.id}" data-value="{ue.code_apogee or ''}" """,
"coef": ue.coefficient or "",
"ects": ue.ects,
"_css_row_class": f"ue ue_",
}
)
li += 1
matieres = ue.matieres.order_by(Matiere.numero)
for mat in matieres:
modules = mat.modules.order_by(Module.numero)
for mod in modules:
nb_moduleimpls = mod.modimpls.count()
# le module (ou ressource ou sae)
T.append(
{
"sem": f"S{mod.semestre_id}"
if mod.semestre_id is not None
else "-",
"_sem_order": f"{li:04d}",
"code": mod.code,
"titre": mod.abbrev or mod.titre,
"_titre_target": url_for(
"notes.module_edit",
scodoc_dept=g.scodoc_dept,
module_id=mod.id,
)
if can_edit
else None,
"apo": mod.code_apogee,
"_apo_td_attrs": f""" data-oid="{mod.id}" data-value="{mod.code_apogee or ''}" """,
"coef": mod.coefficient,
"nb_moduleimpls": nb_moduleimpls,
"heures_cours": mod.heures_cours,
"heures_td": mod.heures_td,
"heures_tp": mod.heures_tp,
"_css_row_class": f"mod {mod.type_abbrv()}",
}
)
columns_ids = [
"sem",
"code",
"apo",
# "mat", inutile d'afficher la matière
"titre",
"coef",
"ects",
"nb_moduleimpls",
"heures_cours",
"heures_td",
"heures_tp",
]
titles = {
"ue": "UE",
"mat": "Matière",
"titre": "Titre",
"code": "Code",
"apo": "Apo",
"coef": "Coef.",
"sem": "Sem.",
"nb_moduleimpls": "Nb utilisé",
"heures_cours": "Cours (h)",
"heures_td": "TD (h)",
"heures_tp": "TP (h)",
"ects": "ECTS",
}
title = f"""Formation {formation.titre} ({formation.acronyme})
[version {formation.version}] code {formation.formation_code}"""
html_class = "stripe cell-border compact hover order-column formation_table_recap"
if current_user.has_permission(Permission.ScoEditApo):
html_class += " apo_editable"
tab = GenTable(
columns_ids=columns_ids,
rows=T,
titles=titles,
origin=f"Généré par {scu.sco_version.SCONAME} le {scu.timedate_human_repr()}",
caption=title,
html_caption=title,
html_class=html_class,
html_class_ignore_default=True,
html_table_attrs=f"""
data-apo_ue_save_url="{url_for('notes.ue_set_apo', scodoc_dept=g.scodoc_dept)}"
data-apo_mod_save_url="{url_for('notes.module_set_apo', scodoc_dept=g.scodoc_dept)}"
""",
html_with_td_classes=True,
base_url=f"{request.base_url}?formation_id={formation_id}",
page_title=title,
html_title=f"<h2>{title}</h2>",
pdf_title=title,
preferences=sco_preferences.SemPreferences(),
table_id="formation_table_recap",
)
return tab.make_page(format=format, javascripts=["js/formation_recap.js"])

View File

@ -3972,3 +3972,17 @@ table.evaluations_recap td.nb_att,
table.evaluations_recap td.nb_exc { table.evaluations_recap td.nb_exc {
text-align: center; text-align: center;
} }
/* ------------- Tableau récap formation ------------ */
table.formation_table_recap tr.ue td {
font-weight: bold;
}
table.formation_table_recap td.coef,
table.formation_table_recap td.ects,
table.formation_table_recap td.nb_moduleimpls,
table.formation_table_recap td.heures_cours,
table.formation_table_recap td.heures_td,
table.formation_table_recap td.heures_tp {
text-align: right;
}

View File

@ -0,0 +1,28 @@
/* Page accueil département */
var apo_ue_editor = null;
var apo_mod_editor = null;
$(document).ready(function () {
var table_options = {
"paging": false,
"searching": false,
"info": false,
/* "autoWidth" : false, */
"fixedHeader": {
"header": true,
"footer": true
},
"orderCellsTop": true, // cellules ligne 1 pour tri
"aaSorting": [], // Prevent initial sorting
};
$('table#formation_table_recap').DataTable(table_options);
let table_editable = document.querySelector("table#formation_table_recap.apo_editable");
if (table_editable) {
let apo_ue_save_url = document.querySelector("table#formation_table_recap.apo_editable").dataset.apo_ue_save_url;
apo_ue_editor = new ScoFieldEditor("table#formation_table_recap tr.ue td.apo", apo_ue_save_url, false);
let apo_mod_save_url = document.querySelector("table#formation_table_recap.apo_editable").dataset.apo_mod_save_url;
apo_mod_editor = new ScoFieldEditor("table#formation_table_recap tr.mod td.apo", apo_mod_save_url, false);
}
});

View File

@ -15,8 +15,11 @@ $(document).ready(function () {
"aaSorting": [], // Prevent initial sorting "aaSorting": [], // Prevent initial sorting
}; };
$('table.semlist').DataTable(table_options); $('table.semlist').DataTable(table_options);
let table_editable = document.querySelector("table#semlist.apo_editable");
if (table_editable) {
let apo_save_url = document.querySelector("table#semlist.apo_editable").dataset.apo_save_url; let apo_save_url = document.querySelector("table#semlist.apo_editable").dataset.apo_save_url;
apo_editor = new ScoFieldEditor(".etapes_apo_str", apo_save_url, false); apo_editor = new ScoFieldEditor(".etapes_apo_str", apo_save_url, false);
}
}); });

View File

@ -45,11 +45,13 @@ from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
from app.models.formsemestre import FormSemestreUEComputationExpr from app.models.formsemestre import FormSemestreUEComputationExpr
from app.models.modules import Module
from app.models.ues import UniteEns from app.models.ues import UniteEns
from app import api from app import api
from app import db from app import db
from app import models from app import models
from app.models import ScolarNews
from app.auth.models import User from app.auth.models import User
from app.but import bulletin_but from app.but import bulletin_but
from app.decorators import ( from app.decorators import (
@ -86,7 +88,6 @@ from app.scodoc import sco_archives
from app.scodoc import sco_bulletins from app.scodoc import sco_bulletins
from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_bulletins_pdf
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import sco_compute_moy
from app.scodoc import sco_cost_formation from app.scodoc import sco_cost_formation
from app.scodoc import sco_debouche from app.scodoc import sco_debouche
from app.scodoc import sco_edit_apc from app.scodoc import sco_edit_apc
@ -103,6 +104,7 @@ from app.scodoc import sco_evaluation_edit
from app.scodoc import sco_evaluation_recap from app.scodoc import sco_evaluation_recap
from app.scodoc import sco_export_results from app.scodoc import sco_export_results
from app.scodoc import sco_formations from app.scodoc import sco_formations
from app.scodoc import sco_formation_recap
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_custommenu from app.scodoc import sco_formsemestre_custommenu
from app.scodoc import sco_formsemestre_edit from app.scodoc import sco_formsemestre_edit
@ -480,7 +482,9 @@ sco_publish(
methods=["GET", "POST"], methods=["GET", "POST"],
) )
sco_publish( sco_publish(
"/formation_table_recap", sco_edit_ue.formation_table_recap, Permission.ScoView "/formation_table_recap",
sco_formation_recap.formation_table_recap,
Permission.ScoView,
) )
sco_publish( sco_publish(
"/formation_add_malus_modules", "/formation_add_malus_modules",
@ -2433,7 +2437,52 @@ def formsemestre_set_apo_etapes():
formsemestre.etapes.append(etape) formsemestre.etapes.append(etape)
db.session.add(formsemestre) db.session.add(formsemestre)
db.session.commit() db.session.commit()
ScolarNews.add(
typ=ScolarNews.NEWS_APO,
text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})",
)
return ("", 204)
@bp.route("/ue_set_apo", methods=["POST"])
@scodoc
@permission_required(Permission.ScoEditApo)
def ue_set_apo():
"""Change le code APO de l'UE
Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE)
"""
ue_id = int(request.form.get("oid"))
code_apo = (request.form.get("value") or "").strip()
ue = UniteEns.query.get_or_404(ue_id)
if code_apo != ue.code_apogee:
ue.code_apogee = code_apo
db.session.add(ue)
db.session.commit()
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
text=f"Modification code Apogée d'UE dans la formation {ue.formation.titre} ({ue.formation.acronyme})",
)
return ("", 204)
@bp.route("/module_set_apo", methods=["POST"])
@scodoc
@permission_required(Permission.ScoEditApo)
def module_set_apo():
"""Change le code APO du module
Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE)
"""
oid = int(request.form.get("oid"))
code_apo = (request.form.get("value") or "").strip()
mod = Module.query.get_or_404(oid)
if code_apo != mod.code_apogee:
mod.code_apogee = code_apo
db.session.add(mod)
db.session.commit()
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
text=f"Modification code Apogée d'UE dans la formation {mod.formation.titre} ({mod.formation.acronyme})",
)
return ("", 204) return ("", 204)

View File

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