Merge pull request 'master' (#1) from ScoDoc/ScoDoc:master into master
Reviewed-on: https://scodoc.org/git/lehmann/ScoDoc-Front/pulls/1
This commit is contained in:
commit
19c736c894
@ -149,9 +149,9 @@ class ResultatsSemestreBUT:
|
|||||||
"""dict synthèse résultats des modules indiqués,
|
"""dict synthèse résultats des modules indiqués,
|
||||||
avec évaluations de chacun."""
|
avec évaluations de chacun."""
|
||||||
d = {}
|
d = {}
|
||||||
etud_idx = self.etud_index[etud.id]
|
# etud_idx = self.etud_index[etud.id]
|
||||||
for mi in modimpls:
|
for mi in modimpls:
|
||||||
mod_idx = self.modimpl_coefs_df.columns.get_loc(mi.id)
|
# mod_idx = self.modimpl_coefs_df.columns.get_loc(mi.id)
|
||||||
# # moyennes indicatives (moyennes de moyennes d'UE)
|
# # moyennes indicatives (moyennes de moyennes d'UE)
|
||||||
# try:
|
# try:
|
||||||
# moyennes_etuds = np.nan_to_num(
|
# moyennes_etuds = np.nan_to_num(
|
||||||
|
@ -111,13 +111,14 @@ def df_load_modimpl_notes(moduleimpl_id: int) -> tuple:
|
|||||||
|
|
||||||
N'utilise pas de cache ScoDoc.
|
N'utilise pas de cache ScoDoc.
|
||||||
"""
|
"""
|
||||||
# L'index du dataframe est la liste des étudiants inscrits au semestre, sans les démissionnaires
|
# L'index du dataframe est la liste des étudiants inscrits au semestre,
|
||||||
etudids = {
|
# sans les démissionnaires
|
||||||
|
etudids = [
|
||||||
e.etudid
|
e.etudid
|
||||||
for e in ModuleImpl.query.get(moduleimpl_id).formsemestre.get_inscrits(
|
for e in ModuleImpl.query.get(moduleimpl_id).formsemestre.get_inscrits(
|
||||||
include_dem=False
|
include_dem=False
|
||||||
)
|
)
|
||||||
}
|
]
|
||||||
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all()
|
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all()
|
||||||
# --- Calcul nombre d'inscrits pour détermnier si évaluation "complete":
|
# --- Calcul nombre d'inscrits pour détermnier si évaluation "complete":
|
||||||
if evaluations:
|
if evaluations:
|
||||||
@ -128,7 +129,8 @@ def df_load_modimpl_notes(moduleimpl_id: int) -> tuple:
|
|||||||
nb_inscrits_module = len(inscrits_module)
|
nb_inscrits_module = len(inscrits_module)
|
||||||
else:
|
else:
|
||||||
nb_inscrits_module = 0
|
nb_inscrits_module = 0
|
||||||
evals_notes = pd.DataFrame(index=etudids, dtype=float) # empty df with all students
|
# empty df with all students:
|
||||||
|
evals_notes = pd.DataFrame(index=etudids, dtype=float)
|
||||||
evaluations_completes = []
|
evaluations_completes = []
|
||||||
for evaluation in evaluations:
|
for evaluation in evaluations:
|
||||||
eval_df = pd.read_sql_query(
|
eval_df = pd.read_sql_query(
|
||||||
|
@ -76,11 +76,11 @@ class Evaluation(db.Model):
|
|||||||
db.session.add(copy)
|
db.session.add(copy)
|
||||||
return copy
|
return copy
|
||||||
|
|
||||||
def set_ue_poids(self, ue, poids: float):
|
def set_ue_poids(self, ue, poids: float) -> None:
|
||||||
"""Set poids évaluation vers cette UE"""
|
"""Set poids évaluation vers cette UE"""
|
||||||
self.update_ue_poids_dict({ue.id: poids})
|
self.update_ue_poids_dict({ue.id: poids})
|
||||||
|
|
||||||
def set_ue_poids_dict(self, ue_poids_dict: dict):
|
def set_ue_poids_dict(self, ue_poids_dict: dict) -> None:
|
||||||
"""set poids vers les UE (remplace existants)
|
"""set poids vers les UE (remplace existants)
|
||||||
ue_poids_dict = { ue_id : poids }
|
ue_poids_dict = { ue_id : poids }
|
||||||
"""
|
"""
|
||||||
@ -91,16 +91,23 @@ class Evaluation(db.Model):
|
|||||||
self.ue_poids = L
|
self.ue_poids = L
|
||||||
self.moduleimpl.invalidate_evaluations_poids() # inval cache
|
self.moduleimpl.invalidate_evaluations_poids() # inval cache
|
||||||
|
|
||||||
def update_ue_poids_dict(self, ue_poids_dict: dict):
|
def update_ue_poids_dict(self, ue_poids_dict: dict) -> None:
|
||||||
"""update poids vers UE (ajoute aux existants)"""
|
"""update poids vers UE (ajoute aux existants)"""
|
||||||
current = self.get_ue_poids_dict()
|
current = self.get_ue_poids_dict()
|
||||||
current.update(ue_poids_dict)
|
current.update(ue_poids_dict)
|
||||||
self.set_ue_poids_dict(current)
|
self.set_ue_poids_dict(current)
|
||||||
|
|
||||||
def get_ue_poids_dict(self):
|
def get_ue_poids_dict(self) -> dict:
|
||||||
"""returns { ue_id : poids }"""
|
"""returns { ue_id : poids }"""
|
||||||
return {p.ue.id: p.poids for p in self.ue_poids}
|
return {p.ue.id: p.poids for p in self.ue_poids}
|
||||||
|
|
||||||
|
def get_ue_poids_str(self) -> str:
|
||||||
|
"""string describing poids, for excel cells and pdfs
|
||||||
|
Note: si les poids ne sont pas initialisés (poids par défaut),
|
||||||
|
ils ne sont pas affichés.
|
||||||
|
"""
|
||||||
|
return ", ".join([f"{p.ue.acronyme}: {p.poids}" for p in self.ue_poids])
|
||||||
|
|
||||||
|
|
||||||
class EvaluationUEPoids(db.Model):
|
class EvaluationUEPoids(db.Model):
|
||||||
"""Poids des évaluations (BUT)
|
"""Poids des évaluations (BUT)
|
||||||
|
@ -33,6 +33,7 @@ import json
|
|||||||
|
|
||||||
from app.but import bulletin_but
|
from app.but import bulletin_but
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
|
from app.models.etudiants import Identite
|
||||||
|
|
||||||
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
|
||||||
@ -86,6 +87,7 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
from app.scodoc import sco_bulletins
|
from app.scodoc import sco_bulletins
|
||||||
|
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
|
etud = Identite.query.get(etudid)
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
|
|
||||||
if formsemestre.formation.is_apc():
|
if formsemestre.formation.is_apc():
|
||||||
@ -139,6 +141,11 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
if not published:
|
if not published:
|
||||||
return d # stop !
|
return d # stop !
|
||||||
|
|
||||||
|
etat_inscription = etud.etat_inscription(formsemestre.id)
|
||||||
|
if etat_inscription != scu.INSCRIT:
|
||||||
|
d.update(dict_decision_jury(etudid, formsemestre_id, with_decisions=True))
|
||||||
|
return d
|
||||||
|
|
||||||
# Groupes:
|
# Groupes:
|
||||||
partitions = sco_groups.get_partitions_list(formsemestre_id, with_default=False)
|
partitions = sco_groups.get_partitions_list(formsemestre_id, with_default=False)
|
||||||
partitions_etud_groups = {} # { partition_id : { etudid : group } }
|
partitions_etud_groups = {} # { partition_id : { etudid : group } }
|
||||||
|
@ -104,7 +104,10 @@ def do_ue_create(args):
|
|||||||
# check duplicates
|
# check duplicates
|
||||||
ues = ue_list({"formation_id": args["formation_id"], "acronyme": args["acronyme"]})
|
ues = ue_list({"formation_id": args["formation_id"], "acronyme": args["acronyme"]})
|
||||||
if ues:
|
if ues:
|
||||||
raise ScoValueError('Acronyme d\'UE "%s" déjà utilisé !' % args["acronyme"])
|
raise ScoValueError(
|
||||||
|
f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé !
|
||||||
|
(chaque UE doit avoir un acronyme unique dans la formation)"""
|
||||||
|
)
|
||||||
# create
|
# create
|
||||||
ue_id = _ueEditor.create(cnx, args)
|
ue_id = _ueEditor.create(cnx, args)
|
||||||
|
|
||||||
@ -471,7 +474,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
|||||||
raise ScoValueError("invalid formation_id")
|
raise ScoValueError("invalid formation_id")
|
||||||
parcours = formation.get_parcours()
|
parcours = formation.get_parcours()
|
||||||
is_apc = parcours.APC_SAE
|
is_apc = parcours.APC_SAE
|
||||||
locked = sco_formations.formation_has_locked_sems(formation_id)
|
locked = formation.has_locked_sems()
|
||||||
if semestre_idx == "all":
|
if semestre_idx == "all":
|
||||||
semestre_idx = None
|
semestre_idx = None
|
||||||
else:
|
else:
|
||||||
@ -541,7 +544,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
|||||||
if locked:
|
if locked:
|
||||||
H.append(
|
H.append(
|
||||||
f"""<p class="help">Cette formation est verrouillée car
|
f"""<p class="help">Cette formation est verrouillée car
|
||||||
{len(locked)} semestres verrouillés s'y réferent.
|
des semestres verrouillés s'y réferent.
|
||||||
Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module),
|
Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module),
|
||||||
vous devez:
|
vous devez:
|
||||||
</p>
|
</p>
|
||||||
@ -1146,7 +1149,10 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False):
|
|||||||
new_acro = args["acronyme"]
|
new_acro = args["acronyme"]
|
||||||
ues = ue_list({"formation_id": ue["formation_id"], "acronyme": new_acro})
|
ues = ue_list({"formation_id": ue["formation_id"], "acronyme": new_acro})
|
||||||
if ues and ues[0]["ue_id"] != ue_id:
|
if ues and ues[0]["ue_id"] != ue_id:
|
||||||
raise ScoValueError('Acronyme d\'UE "%s" déjà utilisé !' % args["acronyme"])
|
raise ScoValueError(
|
||||||
|
f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé !
|
||||||
|
(chaque UE doit avoir un acronyme unique dans la formation)"""
|
||||||
|
)
|
||||||
|
|
||||||
# On ne peut pas supprimer le code UE:
|
# On ne peut pas supprimer le code UE:
|
||||||
if "ue_code" in args and not args["ue_code"]:
|
if "ue_code" in args and not args["ue_code"]:
|
||||||
|
@ -109,30 +109,10 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition=
|
|||||||
nb_inscrits = len(
|
nb_inscrits = len(
|
||||||
sco_groups.do_evaluation_listeetuds_groups(evaluation_id, getallstudents=True)
|
sco_groups.do_evaluation_listeetuds_groups(evaluation_id, getallstudents=True)
|
||||||
)
|
)
|
||||||
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(
|
etuds_notes_dict = sco_evaluation_db.do_evaluation_get_all_notes(
|
||||||
evaluation_id
|
evaluation_id
|
||||||
) # { etudid : value }
|
) # { etudid : note }
|
||||||
notes = [x["value"] for x in NotesDB.values()]
|
|
||||||
nb_abs = len([x for x in notes if x is None])
|
|
||||||
nb_neutre = len([x for x in notes if x == scu.NOTES_NEUTRALISE])
|
|
||||||
nb_att = len([x for x in notes if x == scu.NOTES_ATTENTE])
|
|
||||||
moy_num, median_num, mini_num, maxi_num = notes_moyenne_median_mini_maxi(notes)
|
|
||||||
if moy_num is None:
|
|
||||||
median, moy = "", ""
|
|
||||||
median_num, moy_num = None, None
|
|
||||||
mini, maxi = "", ""
|
|
||||||
mini_num, maxi_num = None, None
|
|
||||||
else:
|
|
||||||
median = scu.fmt_note(median_num)
|
|
||||||
moy = scu.fmt_note(moy_num)
|
|
||||||
mini = scu.fmt_note(mini_num)
|
|
||||||
maxi = scu.fmt_note(maxi_num)
|
|
||||||
# cherche date derniere modif note
|
|
||||||
if len(NotesDB):
|
|
||||||
t = [x["date"] for x in NotesDB.values()]
|
|
||||||
last_modif = max(t)
|
|
||||||
else:
|
|
||||||
last_modif = None
|
|
||||||
# ---- Liste des groupes complets et incomplets
|
# ---- Liste des groupes complets et incomplets
|
||||||
E = sco_evaluation_db.do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
|
E = sco_evaluation_db.do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
@ -163,8 +143,32 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition=
|
|||||||
|
|
||||||
# Nombre de notes valides d'étudiants inscrits au module
|
# Nombre de notes valides d'étudiants inscrits au module
|
||||||
# (car il peut y avoir des notes d'étudiants désinscrits depuis l'évaluation)
|
# (car il peut y avoir des notes d'étudiants désinscrits depuis l'évaluation)
|
||||||
nb_notes = len(insmodset.intersection(NotesDB))
|
etudids_avec_note = insmodset.intersection(etuds_notes_dict)
|
||||||
nb_notes_total = len(NotesDB)
|
nb_notes = len(etudids_avec_note)
|
||||||
|
# toutes saisies, y compris chez des non-inscrits:
|
||||||
|
nb_notes_total = len(etuds_notes_dict)
|
||||||
|
|
||||||
|
notes = [etuds_notes_dict[etudid]["value"] for etudid in etudids_avec_note]
|
||||||
|
nb_abs = len([x for x in notes if x is None])
|
||||||
|
nb_neutre = len([x for x in notes if x == scu.NOTES_NEUTRALISE])
|
||||||
|
nb_att = len([x for x in notes if x == scu.NOTES_ATTENTE])
|
||||||
|
moy_num, median_num, mini_num, maxi_num = notes_moyenne_median_mini_maxi(notes)
|
||||||
|
if moy_num is None:
|
||||||
|
median, moy = "", ""
|
||||||
|
median_num, moy_num = None, None
|
||||||
|
mini, maxi = "", ""
|
||||||
|
mini_num, maxi_num = None, None
|
||||||
|
else:
|
||||||
|
median = scu.fmt_note(median_num)
|
||||||
|
moy = scu.fmt_note(moy_num)
|
||||||
|
mini = scu.fmt_note(mini_num)
|
||||||
|
maxi = scu.fmt_note(maxi_num)
|
||||||
|
# cherche date derniere modif note
|
||||||
|
if len(etuds_notes_dict):
|
||||||
|
t = [x["date"] for x in etuds_notes_dict.values()]
|
||||||
|
last_modif = max(t)
|
||||||
|
else:
|
||||||
|
last_modif = None
|
||||||
|
|
||||||
# On considere une note "manquante" lorsqu'elle n'existe pas
|
# On considere une note "manquante" lorsqu'elle n'existe pas
|
||||||
# ou qu'elle est en attente (ATT)
|
# ou qu'elle est en attente (ATT)
|
||||||
@ -181,8 +185,8 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition=
|
|||||||
groups[group["group_id"]] = group
|
groups[group["group_id"]] = group
|
||||||
#
|
#
|
||||||
isMissing = False
|
isMissing = False
|
||||||
if i["etudid"] in NotesDB:
|
if i["etudid"] in etuds_notes_dict:
|
||||||
val = NotesDB[i["etudid"]]["value"]
|
val = etuds_notes_dict[i["etudid"]]["value"]
|
||||||
if val == scu.NOTES_ATTENTE:
|
if val == scu.NOTES_ATTENTE:
|
||||||
isMissing = True
|
isMissing = True
|
||||||
TotalNbAtt += 1
|
TotalNbAtt += 1
|
||||||
|
@ -230,9 +230,10 @@ def formation_import_xml(doc: str, import_tags=True):
|
|||||||
ue_id = sco_edit_ue.do_ue_create(ue_info[1])
|
ue_id = sco_edit_ue.do_ue_create(ue_info[1])
|
||||||
if xml_ue_id:
|
if xml_ue_id:
|
||||||
ues_old2new[xml_ue_id] = ue_id
|
ues_old2new[xml_ue_id] = ue_id
|
||||||
ue_reference = int(ue_info[1].get("reference"))
|
# élément optionnel présent dans les exports BUT:
|
||||||
|
ue_reference = ue_info[1].get("reference")
|
||||||
if ue_reference:
|
if ue_reference:
|
||||||
ue_reference_to_id[ue_reference] = ue_id
|
ue_reference_to_id[int(ue_reference)] = ue_id
|
||||||
# -- create matieres
|
# -- create matieres
|
||||||
for mat_info in ue_info[2]:
|
for mat_info in ue_info[2]:
|
||||||
assert mat_info[0] == "matiere"
|
assert mat_info[0] == "matiere"
|
||||||
|
@ -66,7 +66,9 @@ def do_formsemestre_inscription_list(*args, **kw):
|
|||||||
|
|
||||||
|
|
||||||
def do_formsemestre_inscription_listinscrits(formsemestre_id):
|
def do_formsemestre_inscription_listinscrits(formsemestre_id):
|
||||||
"""Liste les inscrits (état I) à ce semestre et cache le résultat"""
|
"""Liste les inscrits (état I) à ce semestre et cache le résultat.
|
||||||
|
Result: [ { "etudid":, "formsemestre_id": , "etat": , "etape": }]
|
||||||
|
"""
|
||||||
r = sco_cache.SemInscriptionsCache.get(formsemestre_id)
|
r = sco_cache.SemInscriptionsCache.get(formsemestre_id)
|
||||||
if r is None:
|
if r is None:
|
||||||
# retreive list
|
# retreive list
|
||||||
|
@ -1210,6 +1210,7 @@ def formsemestre_tableau_modules(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if mod.module_type in (
|
if mod.module_type in (
|
||||||
|
None, # ne devrait pas être nécessaire car la migration a remplacé les NULLs
|
||||||
ModuleType.STANDARD,
|
ModuleType.STANDARD,
|
||||||
ModuleType.RESSOURCE,
|
ModuleType.RESSOURCE,
|
||||||
ModuleType.SAE,
|
ModuleType.SAE,
|
||||||
@ -1249,7 +1250,7 @@ def formsemestre_tableau_modules(
|
|||||||
% (modimpl["moduleimpl_id"], nb_malus_notes)
|
% (modimpl["moduleimpl_id"], nb_malus_notes)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid module_type") # a bug
|
raise ValueError(f"Invalid module_type {mod.module_type}") # a bug
|
||||||
|
|
||||||
H.append("</td></tr>")
|
H.append("</td></tr>")
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
@ -35,7 +35,6 @@ Optimisation possible:
|
|||||||
"""
|
"""
|
||||||
import collections
|
import collections
|
||||||
import operator
|
import operator
|
||||||
import re
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
@ -45,6 +44,7 @@ import flask
|
|||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask import url_for, make_response
|
from flask import url_for, make_response
|
||||||
|
|
||||||
|
from app import db
|
||||||
from app.models.groups import Partition
|
from app.models.groups import Partition
|
||||||
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
|
||||||
|
@ -32,6 +32,7 @@ import flask
|
|||||||
from flask import url_for, g, request
|
from flask import url_for, g, request
|
||||||
|
|
||||||
from app import models
|
from app import models
|
||||||
|
from app.models.evaluations import Evaluation
|
||||||
from app.models.moduleimpls import ModuleImpl
|
from app.models.moduleimpls import ModuleImpl
|
||||||
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
|
||||||
@ -53,35 +54,34 @@ from app.scodoc.gen_tables import GenTable
|
|||||||
from app.scodoc.htmlutils import histogram_notes
|
from app.scodoc.htmlutils import histogram_notes
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_listenotes():
|
def do_evaluation_listenotes(
|
||||||
|
evaluation_id=None, moduleimpl_id=None, format="html"
|
||||||
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Affichage des notes d'une évaluation
|
Affichage des notes d'une évaluation (si evaluation_id)
|
||||||
|
ou de toutes les évaluations d'un module (si moduleimpl_id)
|
||||||
args: evaluation_id ou moduleimpl_id
|
|
||||||
(si moduleimpl_id, affiche toutes les évaluations du module)
|
|
||||||
"""
|
"""
|
||||||
mode = None
|
mode = None
|
||||||
vals = scu.get_request_args()
|
if moduleimpl_id:
|
||||||
if "evaluation_id" in vals:
|
|
||||||
evaluation_id = int(vals["evaluation_id"])
|
|
||||||
mode = "eval"
|
|
||||||
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
|
|
||||||
if "moduleimpl_id" in vals and vals["moduleimpl_id"]:
|
|
||||||
moduleimpl_id = int(vals["moduleimpl_id"])
|
|
||||||
mode = "module"
|
mode = "module"
|
||||||
evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
|
evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
|
||||||
if not mode:
|
elif evaluation_id:
|
||||||
|
mode = "eval"
|
||||||
|
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
|
||||||
|
else:
|
||||||
raise ValueError("missing argument: evaluation or module")
|
raise ValueError("missing argument: evaluation or module")
|
||||||
if not evals:
|
if not evals:
|
||||||
return "<p>Aucune évaluation !</p>"
|
return "<p>Aucune évaluation !</p>"
|
||||||
|
|
||||||
format = vals.get("format", "html")
|
|
||||||
E = evals[0] # il y a au moins une evaluation
|
E = evals[0] # il y a au moins une evaluation
|
||||||
|
modimpl = ModuleImpl.query.get(E["moduleimpl_id"])
|
||||||
# description de l'evaluation
|
# description de l'evaluation
|
||||||
if mode == "eval":
|
if mode == "eval":
|
||||||
H = [sco_evaluations.evaluation_describe(evaluation_id=evaluation_id)]
|
H = [sco_evaluations.evaluation_describe(evaluation_id=evaluation_id)]
|
||||||
|
page_title = f"Notes {E['description'] or modimpl.module.code}"
|
||||||
else:
|
else:
|
||||||
H = []
|
H = []
|
||||||
|
page_title = f"Notes {modimpl.module.code}"
|
||||||
# groupes
|
# groupes
|
||||||
groups = sco_groups.do_evaluation_listegroupes(
|
groups = sco_groups.do_evaluation_listegroupes(
|
||||||
E["evaluation_id"], include_default=True
|
E["evaluation_id"], include_default=True
|
||||||
@ -187,7 +187,7 @@ def do_evaluation_listenotes():
|
|||||||
is_submitted=True, # toujours "soumis" (démarre avec liste complète)
|
is_submitted=True, # toujours "soumis" (démarre avec liste complète)
|
||||||
)
|
)
|
||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
return "\n".join(H) + "\n" + tf[1]
|
return "\n".join(H) + "\n" + tf[1], page_title
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
"%s/Notes/moduleimpl_status?moduleimpl_id=%s"
|
"%s/Notes/moduleimpl_status?moduleimpl_id=%s"
|
||||||
@ -198,16 +198,19 @@ def do_evaluation_listenotes():
|
|||||||
note_sur_20 = tf[2]["note_sur_20"]
|
note_sur_20 = tf[2]["note_sur_20"]
|
||||||
hide_groups = tf[2]["hide_groups"]
|
hide_groups = tf[2]["hide_groups"]
|
||||||
with_emails = tf[2]["with_emails"]
|
with_emails = tf[2]["with_emails"]
|
||||||
return _make_table_notes(
|
return (
|
||||||
tf[1],
|
_make_table_notes(
|
||||||
evals,
|
tf[1],
|
||||||
format=format,
|
evals,
|
||||||
note_sur_20=note_sur_20,
|
format=format,
|
||||||
anonymous_listing=anonymous_listing,
|
note_sur_20=note_sur_20,
|
||||||
group_ids=tf[2]["group_ids"],
|
anonymous_listing=anonymous_listing,
|
||||||
hide_groups=hide_groups,
|
group_ids=tf[2]["group_ids"],
|
||||||
with_emails=with_emails,
|
hide_groups=hide_groups,
|
||||||
mode=mode,
|
with_emails=with_emails,
|
||||||
|
mode=mode,
|
||||||
|
),
|
||||||
|
page_title,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -393,6 +396,7 @@ def _make_table_notes(
|
|||||||
key_mgr,
|
key_mgr,
|
||||||
note_sur_20,
|
note_sur_20,
|
||||||
keep_numeric,
|
keep_numeric,
|
||||||
|
format=format,
|
||||||
)
|
)
|
||||||
columns_ids.append(e["evaluation_id"])
|
columns_ids.append(e["evaluation_id"])
|
||||||
#
|
#
|
||||||
@ -406,7 +410,7 @@ def _make_table_notes(
|
|||||||
# Si module, ajoute la (les) "moyenne(s) du module:
|
# Si module, ajoute la (les) "moyenne(s) du module:
|
||||||
if mode == "module":
|
if mode == "module":
|
||||||
if len(evals) > 1:
|
if len(evals) > 1:
|
||||||
# Moyenne de l'étudant dans le module
|
# Moyenne de l'étudiant dans le module
|
||||||
# Affichée même en APC à titre indicatif
|
# Affichée même en APC à titre indicatif
|
||||||
_add_moymod_column(
|
_add_moymod_column(
|
||||||
sem["formsemestre_id"],
|
sem["formsemestre_id"],
|
||||||
@ -635,6 +639,7 @@ def _add_eval_columns(
|
|||||||
K,
|
K,
|
||||||
note_sur_20,
|
note_sur_20,
|
||||||
keep_numeric,
|
keep_numeric,
|
||||||
|
format="html",
|
||||||
):
|
):
|
||||||
"""Add eval e"""
|
"""Add eval e"""
|
||||||
nb_notes = 0
|
nb_notes = 0
|
||||||
@ -643,6 +648,7 @@ def _add_eval_columns(
|
|||||||
sum_notes = 0
|
sum_notes = 0
|
||||||
notes = [] # liste des notes numeriques, pour calcul histogramme uniquement
|
notes = [] # liste des notes numeriques, pour calcul histogramme uniquement
|
||||||
evaluation_id = e["evaluation_id"]
|
evaluation_id = e["evaluation_id"]
|
||||||
|
e_o = Evaluation.query.get(evaluation_id) # XXX en attendant ré-écriture
|
||||||
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
etudid = row["etudid"]
|
etudid = row["etudid"]
|
||||||
@ -704,9 +710,12 @@ def _add_eval_columns(
|
|||||||
|
|
||||||
row_coefs[evaluation_id] = "coef. %s" % e["coefficient"]
|
row_coefs[evaluation_id] = "coef. %s" % e["coefficient"]
|
||||||
if is_apc:
|
if is_apc:
|
||||||
row_poids[evaluation_id] = _mini_table_eval_ue_poids(
|
if format == "html":
|
||||||
evaluation_id, evals_poids, ues
|
row_poids[evaluation_id] = _mini_table_eval_ue_poids(
|
||||||
)
|
evaluation_id, evals_poids, ues
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
row_poids[evaluation_id] = e_o.get_ue_poids_str()
|
||||||
if note_sur_20:
|
if note_sur_20:
|
||||||
nmax = 20.0
|
nmax = 20.0
|
||||||
else:
|
else:
|
||||||
|
@ -31,9 +31,10 @@ import time
|
|||||||
|
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from app.auth.models import User
|
|
||||||
|
|
||||||
|
from app.auth.models import User
|
||||||
from app.models import ModuleImpl
|
from app.models import ModuleImpl
|
||||||
|
from app.models.evaluations import Evaluation
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
@ -391,8 +392,9 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
)
|
)
|
||||||
H.append("""<table class="moduleimpl_evaluations">""")
|
H.append("""<table class="moduleimpl_evaluations">""")
|
||||||
eval_index = len(mod_evals) - 1
|
eval_index = len(mod_evals) - 1
|
||||||
first = True
|
first_eval = True
|
||||||
for eval in mod_evals:
|
for eval in mod_evals:
|
||||||
|
evaluation = Evaluation.query.get(eval["evaluation_id"]) # TODO unifier
|
||||||
etat = sco_evaluations.do_evaluation_etat(
|
etat = sco_evaluations.do_evaluation_etat(
|
||||||
eval["evaluation_id"],
|
eval["evaluation_id"],
|
||||||
partition_id=partition_id,
|
partition_id=partition_id,
|
||||||
@ -406,9 +408,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
else:
|
else:
|
||||||
tr_class = "mievr"
|
tr_class = "mievr"
|
||||||
tr_class_1 = "mievr"
|
tr_class_1 = "mievr"
|
||||||
if first:
|
if not first_eval:
|
||||||
first = False
|
|
||||||
else:
|
|
||||||
H.append("""<tr><td colspan="8"> </td></tr>""")
|
H.append("""<tr><td colspan="8"> </td></tr>""")
|
||||||
tr_class_1 += " mievr_spaced"
|
tr_class_1 += " mievr_spaced"
|
||||||
H.append("""<tr class="%s"><td class="mievr_tit" colspan="8">""" % tr_class_1)
|
H.append("""<tr class="%s"><td class="mievr_tit" colspan="8">""" % tr_class_1)
|
||||||
@ -586,19 +586,35 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
H.append("""</td></tr>""")
|
H.append("""</td></tr>""")
|
||||||
#
|
#
|
||||||
if etat["nb_notes"] == 0:
|
if etat["nb_notes"] == 0:
|
||||||
H.append("""<tr class="%s"><td colspan="8"> """ % tr_class)
|
H.append("""<tr class="%s"><td></td>""" % tr_class)
|
||||||
H.append("""</td></tr>""")
|
if modimpl.module.is_apc():
|
||||||
|
H.append(
|
||||||
|
f"""<td colspan="7" class="eval_poids">{
|
||||||
|
evaluation.get_ue_poids_str()}</td>"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
H.append('<td colspan="7"></td>')
|
||||||
|
H.append("""</tr>""")
|
||||||
else: # il y a deja des notes saisies
|
else: # il y a deja des notes saisies
|
||||||
gr_moyennes = etat["gr_moyennes"]
|
gr_moyennes = etat["gr_moyennes"]
|
||||||
|
first_group = True
|
||||||
for gr_moyenne in gr_moyennes:
|
for gr_moyenne in gr_moyennes:
|
||||||
H.append("""<tr class="%s">""" % tr_class)
|
H.append("""<tr class="%s">""" % tr_class)
|
||||||
H.append("""<td colspan="2"> </td>""")
|
H.append("""<td> </td>""")
|
||||||
|
if first_group and modimpl.module.is_apc():
|
||||||
|
H.append(
|
||||||
|
f"""<td class="eval_poids" colspan="3">{
|
||||||
|
evaluation.get_ue_poids_str()}</td>"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
H.append("""<td colspan="3"></td>""")
|
||||||
|
first_group = False
|
||||||
if gr_moyenne["group_name"] is None:
|
if gr_moyenne["group_name"] is None:
|
||||||
name = "Tous" # tous
|
name = "Tous" # tous
|
||||||
else:
|
else:
|
||||||
name = "Groupe %s" % gr_moyenne["group_name"]
|
name = "Groupe %s" % gr_moyenne["group_name"]
|
||||||
H.append(
|
H.append(
|
||||||
"""<td colspan="5" class="mievr_grtit">%s </td><td>""" % name
|
"""<td colspan="3" class="mievr_grtit">%s </td><td>""" % name
|
||||||
)
|
)
|
||||||
if gr_moyenne["gr_nb_notes"] > 0:
|
if gr_moyenne["gr_nb_notes"] > 0:
|
||||||
H.append("%(gr_moy)s" % gr_moyenne)
|
H.append("%(gr_moy)s" % gr_moyenne)
|
||||||
@ -637,6 +653,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
H.append("""</a>""")
|
H.append("""</a>""")
|
||||||
H.append("</span>")
|
H.append("</span>")
|
||||||
H.append("""</td></tr>""")
|
H.append("""</td></tr>""")
|
||||||
|
first_eval = False
|
||||||
|
|
||||||
#
|
#
|
||||||
if caneditevals or not sem["etat"]:
|
if caneditevals or not sem["etat"]:
|
||||||
|
@ -37,7 +37,8 @@ from flask import make_response
|
|||||||
|
|
||||||
from app import log
|
from app import log
|
||||||
from app.but import bulletin_but
|
from app.but import bulletin_but
|
||||||
from app.models import FormSemestre, UniteEns, Module, ModuleImpl
|
from app.models import FormSemestre
|
||||||
|
from app.models.etudiants import Identite
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
@ -937,6 +938,9 @@ def _formsemestre_recapcomplet_json(
|
|||||||
:param force_publishing: donne les bulletins même si non "publiés sur portail"
|
:param force_publishing: donne les bulletins même si non "publiés sur portail"
|
||||||
:returns: dict, "", "json"
|
:returns: dict, "", "json"
|
||||||
"""
|
"""
|
||||||
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
|
is_apc = formsemestre.formation.is_apc()
|
||||||
|
|
||||||
if xml_nodate:
|
if xml_nodate:
|
||||||
docdate = ""
|
docdate = ""
|
||||||
else:
|
else:
|
||||||
@ -958,14 +962,18 @@ def _formsemestre_recapcomplet_json(
|
|||||||
T = nt.get_table_moyennes_triees()
|
T = nt.get_table_moyennes_triees()
|
||||||
for t in T:
|
for t in T:
|
||||||
etudid = t[-1]
|
etudid = t[-1]
|
||||||
bulletins.append(
|
if is_apc:
|
||||||
sco_bulletins_json.formsemestre_bulletinetud_published_dict(
|
etud = Identite.query.get(etudid)
|
||||||
|
r = bulletin_but.ResultatsSemestreBUT(formsemestre)
|
||||||
|
bul = r.bulletin_etud(etud, formsemestre)
|
||||||
|
else:
|
||||||
|
bul = sco_bulletins_json.formsemestre_bulletinetud_published_dict(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
etudid,
|
etudid,
|
||||||
force_publishing=force_publishing,
|
force_publishing=force_publishing,
|
||||||
xml_with_decisions=xml_with_decisions,
|
xml_with_decisions=xml_with_decisions,
|
||||||
)
|
)
|
||||||
)
|
bulletins.append(bul)
|
||||||
return J, "", "json"
|
return J, "", "json"
|
||||||
|
|
||||||
|
|
||||||
|
@ -408,7 +408,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
|||||||
|
|
||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
nb_changed, nb_suppress, existing_decisions = notes_add(
|
nb_changed, nb_suppress, existing_decisions = notes_add(
|
||||||
current_user, evaluation_id, notes, do_it=False
|
current_user, evaluation_id, notes, do_it=False, check_inscription=False
|
||||||
)
|
)
|
||||||
msg = (
|
msg = (
|
||||||
"<p>Confirmer la suppression des %d notes ? <em>(peut affecter plusieurs groupes)</em></p>"
|
"<p>Confirmer la suppression des %d notes ? <em>(peut affecter plusieurs groupes)</em></p>"
|
||||||
@ -426,7 +426,11 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
|||||||
|
|
||||||
# modif
|
# modif
|
||||||
nb_changed, nb_suppress, existing_decisions = notes_add(
|
nb_changed, nb_suppress, existing_decisions = notes_add(
|
||||||
current_user, evaluation_id, notes, comment="effacer tout"
|
current_user,
|
||||||
|
evaluation_id,
|
||||||
|
notes,
|
||||||
|
comment="effacer tout",
|
||||||
|
check_inscription=False,
|
||||||
)
|
)
|
||||||
assert nb_changed == nb_suppress
|
assert nb_changed == nb_suppress
|
||||||
H = ["<p>%s notes supprimées</p>" % nb_suppress]
|
H = ["<p>%s notes supprimées</p>" % nb_suppress]
|
||||||
@ -454,7 +458,14 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
|||||||
return html_sco_header.sco_header() + "\n".join(H) + html_sco_header.sco_footer()
|
return html_sco_header.sco_header() + "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
def notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True) -> tuple:
|
def notes_add(
|
||||||
|
user,
|
||||||
|
evaluation_id: int,
|
||||||
|
notes: list,
|
||||||
|
comment=None,
|
||||||
|
do_it=True,
|
||||||
|
check_inscription=True,
|
||||||
|
) -> tuple:
|
||||||
"""
|
"""
|
||||||
Insert or update notes
|
Insert or update notes
|
||||||
notes is a list of tuples (etudid,value)
|
notes is a list of tuples (etudid,value)
|
||||||
@ -475,7 +486,7 @@ def notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True) -
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
for (etudid, value) in notes:
|
for (etudid, value) in notes:
|
||||||
if etudid not in inscrits:
|
if check_inscription and (etudid not in inscrits):
|
||||||
raise NoteProcessError("etudiant non inscrit dans ce module")
|
raise NoteProcessError("etudiant non inscrit dans ce module")
|
||||||
if not ((value is None) or (type(value) == type(1.0))):
|
if not ((value is None) or (type(value) == type(1.0))):
|
||||||
raise NoteProcessError(
|
raise NoteProcessError(
|
||||||
|
@ -16,7 +16,7 @@ main{
|
|||||||
--couleurPrincipale: rgb(240,250,255);
|
--couleurPrincipale: rgb(240,250,255);
|
||||||
--couleurFondTitresUE: rgb(206,255,235);
|
--couleurFondTitresUE: rgb(206,255,235);
|
||||||
--couleurFondTitresRes: rgb(125, 170, 255);
|
--couleurFondTitresRes: rgb(125, 170, 255);
|
||||||
--couleurFondTitresSAE: rgb(255, 190, 69);
|
--couleurFondTitresSAE: rgb(211, 255, 255);
|
||||||
--couleurSecondaire: #fec;
|
--couleurSecondaire: #fec;
|
||||||
--couleurIntense: #c09;
|
--couleurIntense: #c09;
|
||||||
--couleurSurlignage: rgba(232, 255, 132, 0.47);
|
--couleurSurlignage: rgba(232, 255, 132, 0.47);
|
||||||
|
@ -1476,6 +1476,9 @@ span.evalindex {
|
|||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.moduleimpl_evaluations td.eval_poids {
|
||||||
|
color:rgb(0, 0, 255);
|
||||||
|
}
|
||||||
|
|
||||||
/* Formulaire edition des partitions */
|
/* Formulaire edition des partitions */
|
||||||
form#editpart table {
|
form#editpart table {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if title %}{{ title }} - ScoDoc{% else %}Welcome to ScoDoc{% endif %}
|
{% if title %}{{ title }} - ScoDoc{% else %}ScoDoc{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@ -31,9 +31,9 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
{% if sco.sem %}
|
{% if sco.sem %}
|
||||||
{% block formsemestre_header %}
|
{% block formsemestre_header %}
|
||||||
{% include "formsemestre_header.html" %}
|
{% include "formsemestre_header.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
|
@ -285,6 +285,8 @@ def formsemestre_bulletinetud(
|
|||||||
prefer_mail_perso=False,
|
prefer_mail_perso=False,
|
||||||
code_nip=None,
|
code_nip=None,
|
||||||
):
|
):
|
||||||
|
if not formsemestre_id:
|
||||||
|
flask.abort(404, "argument manquant: formsemestre_id")
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
if formsemestre.formation.is_apc() and format != "oldjson":
|
if formsemestre.formation.is_apc() and format != "oldjson":
|
||||||
if etudid:
|
if etudid:
|
||||||
@ -299,7 +301,7 @@ def formsemestre_bulletinetud(
|
|||||||
elif format == "html":
|
elif format == "html":
|
||||||
return render_template(
|
return render_template(
|
||||||
"but/bulletin.html",
|
"but/bulletin.html",
|
||||||
title="Bulletin BUT",
|
title=f"Bul. {etud.nom} - BUT",
|
||||||
bul_url=url_for(
|
bul_url=url_for(
|
||||||
"notes.formsemestre_bulletinetud",
|
"notes.formsemestre_bulletinetud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
@ -1726,27 +1728,33 @@ def evaluation_create(moduleimpl_id):
|
|||||||
@scodoc7func
|
@scodoc7func
|
||||||
def evaluation_listenotes():
|
def evaluation_listenotes():
|
||||||
"""Affichage des notes d'une évaluation"""
|
"""Affichage des notes d'une évaluation"""
|
||||||
if request.args.get("format", "html") == "html":
|
evaluation_id = None
|
||||||
|
moduleimpl_id = None
|
||||||
|
vals = scu.get_request_args()
|
||||||
|
if "evaluation_id" in vals:
|
||||||
|
evaluation_id = int(vals["evaluation_id"])
|
||||||
|
mode = "eval"
|
||||||
|
if "moduleimpl_id" in vals and vals["moduleimpl_id"]:
|
||||||
|
moduleimpl_id = int(vals["moduleimpl_id"])
|
||||||
|
mode = "module"
|
||||||
|
|
||||||
|
format = vals.get("format", "html")
|
||||||
|
B, page_title = sco_liste_notes.do_evaluation_listenotes(
|
||||||
|
evaluation_id=evaluation_id, moduleimpl_id=moduleimpl_id, format=format
|
||||||
|
)
|
||||||
|
if format == "html":
|
||||||
H = html_sco_header.sco_header(
|
H = html_sco_header.sco_header(
|
||||||
|
page_title=page_title,
|
||||||
cssstyles=["css/verticalhisto.css"],
|
cssstyles=["css/verticalhisto.css"],
|
||||||
javascripts=["js/etud_info.js"],
|
javascripts=["js/etud_info.js"],
|
||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
)
|
)
|
||||||
F = html_sco_header.sco_footer()
|
F = html_sco_header.sco_footer()
|
||||||
else:
|
|
||||||
H, F = "", ""
|
|
||||||
B = sco_liste_notes.do_evaluation_listenotes()
|
|
||||||
if H:
|
|
||||||
return H + B + F
|
return H + B + F
|
||||||
else:
|
else:
|
||||||
return B
|
return B
|
||||||
|
|
||||||
|
|
||||||
sco_publish(
|
|
||||||
"/do_evaluation_listenotes",
|
|
||||||
sco_liste_notes.do_evaluation_listenotes,
|
|
||||||
Permission.ScoView,
|
|
||||||
)
|
|
||||||
sco_publish(
|
sco_publish(
|
||||||
"/evaluation_list_operations",
|
"/evaluation_list_operations",
|
||||||
sco_undo_notes.evaluation_list_operations,
|
sco_undo_notes.evaluation_list_operations,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.1.10"
|
SCOVERSION = "9.1.11"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user