Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
27 changed files with 399 additions and 542 deletions
Showing only changes of commit d140240909 - Show all commits

View File

@ -38,14 +38,11 @@ import datetime
from xml.etree import ElementTree from xml.etree import ElementTree
from xml.etree.ElementTree import Element from xml.etree.ElementTree import Element
from app import log from app import db, log
from app.but import bulletin_but from app.but import bulletin_but
from app.models import BulAppreciations, FormSemestre, Identite from app.models import BulAppreciations, FormSemestre, Identite, UniteEns
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_edit_ue
from app.scodoc import sco_etud
from app.scodoc import sco_photos from app.scodoc import sco_photos
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_xml from app.scodoc import sco_xml
@ -202,12 +199,12 @@ def bulletin_but_xml_compat(
if e.visibulletin or version == "long": if e.visibulletin or version == "long":
x_eval = Element( x_eval = Element(
"evaluation", "evaluation",
date_debut=e.date_debut.isoformat() date_debut=(
if e.date_debut e.date_debut.isoformat() if e.date_debut else ""
else "", ),
date_fin=e.date_fin.isoformat() date_fin=(
if e.date_debut e.date_fin.isoformat() if e.date_debut else ""
else "", ),
coefficient=str(e.coefficient), coefficient=str(e.coefficient),
# pas les poids en XML compat # pas les poids en XML compat
evaluation_type=str(e.evaluation_type), evaluation_type=str(e.evaluation_type),
@ -215,9 +212,9 @@ def bulletin_but_xml_compat(
# notes envoyées sur 20, ceci juste pour garder trace: # notes envoyées sur 20, ceci juste pour garder trace:
note_max_origin=str(e.note_max), note_max_origin=str(e.note_max),
# --- deprecated # --- deprecated
jour=e.date_debut.isoformat() jour=(
if e.date_debut e.date_debut.isoformat() if e.date_debut else ""
else "", ),
heure_debut=e.heure_debut(), heure_debut=e.heure_debut(),
heure_fin=e.heure_fin(), heure_fin=e.heure_fin(),
) )
@ -294,17 +291,18 @@ def bulletin_but_xml_compat(
"decisions_ue" "decisions_ue"
]: # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id): always publish (car utile pour export Apogee) ]: # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id): always publish (car utile pour export Apogee)
for ue_id in decision["decisions_ue"].keys(): for ue_id in decision["decisions_ue"].keys():
ue = sco_edit_ue.ue_list({"ue_id": ue_id})[0] ue = db.session.get(UniteEns, ue_id)
doc.append( if ue:
Element( doc.append(
"decision_ue", Element(
ue_id=str(ue["ue_id"]), "decision_ue",
numero=quote_xml_attr(ue["numero"]), ue_id=str(ue.id),
acronyme=quote_xml_attr(ue["acronyme"]), numero=quote_xml_attr(ue.numero),
titre=quote_xml_attr(ue["titre"]), acronyme=quote_xml_attr(ue.acronyme),
code=decision["decisions_ue"][ue_id]["code"], titre=quote_xml_attr(ue.titre or ""),
code=decision["decisions_ue"][ue_id]["code"],
)
) )
)
for aut in decision["autorisations"]: for aut in decision["autorisations"]:
doc.append( doc.append(

View File

@ -58,7 +58,6 @@ class NotesTableCompat(ResultatsSemestre):
self.moy_moy = "NA" self.moy_moy = "NA"
self.moy_gen_rangs_by_group = {} # { group_id : (Series, Series) } self.moy_gen_rangs_by_group = {} # { group_id : (Series, Series) }
self.ue_rangs_by_group = {} # { ue_id : {group_id : (Series, Series)}} self.ue_rangs_by_group = {} # { ue_id : {group_id : (Series, Series)}}
self.expr_diagnostics = ""
self.parcours = self.formsemestre.formation.get_cursus() self.parcours = self.formsemestre.formation.get_cursus()
self._modimpls_dict_by_ue = {} # local cache self._modimpls_dict_by_ue = {} # local cache
@ -217,9 +216,9 @@ class NotesTableCompat(ResultatsSemestre):
# Rangs / UEs: # Rangs / UEs:
for ue in ues: for ue in ues:
group_moys_ue = self.etud_moy_ue[ue.id][group_members] group_moys_ue = self.etud_moy_ue[ue.id][group_members]
self.ue_rangs_by_group.setdefault(ue.id, {})[ self.ue_rangs_by_group.setdefault(ue.id, {})[group.id] = (
group.id moy_sem.comp_ranks_series(group_moys_ue * mask_inscr)
] = moy_sem.comp_ranks_series(group_moys_ue * mask_inscr) )
def get_etud_rang(self, etudid: int) -> str: def get_etud_rang(self, etudid: int) -> str:
"""Le rang (classement) de l'étudiant dans le semestre. """Le rang (classement) de l'étudiant dans le semestre.

View File

@ -79,6 +79,7 @@ class Evaluation(db.Model):
): ):
"""Create an evaluation. Check permission and all arguments. """Create an evaluation. Check permission and all arguments.
Ne crée pas les poids vers les UEs. Ne crée pas les poids vers les UEs.
Add to session, do not commit.
""" """
if not moduleimpl.can_edit_evaluation(current_user): if not moduleimpl.can_edit_evaluation(current_user):
raise AccessDenied( raise AccessDenied(
@ -94,6 +95,8 @@ class Evaluation(db.Model):
args["numero"] = cls.get_new_numero(moduleimpl, args["date_debut"]) args["numero"] = cls.get_new_numero(moduleimpl, args["date_debut"])
# #
evaluation = Evaluation(**args) evaluation = Evaluation(**args)
db.session.add(evaluation)
db.session.flush()
sco_cache.invalidate_formsemestre(formsemestre_id=moduleimpl.formsemestre_id) sco_cache.invalidate_formsemestre(formsemestre_id=moduleimpl.formsemestre_id)
url = url_for( url = url_for(
"notes.moduleimpl_status", "notes.moduleimpl_status",
@ -210,9 +213,9 @@ class Evaluation(db.Model):
"visibulletin": self.visibulletin, "visibulletin": self.visibulletin,
# Deprecated (supprimer avant #sco9.7) # Deprecated (supprimer avant #sco9.7)
"date": self.date_debut.date().isoformat() if self.date_debut else "", "date": self.date_debut.date().isoformat() if self.date_debut else "",
"heure_debut": self.date_debut.time().isoformat() "heure_debut": (
if self.date_debut self.date_debut.time().isoformat() if self.date_debut else ""
else "", ),
"heure_fin": self.date_fin.time().isoformat() if self.date_fin else "", "heure_fin": self.date_fin.time().isoformat() if self.date_fin else "",
} }

View File

@ -1,8 +1,10 @@
"""ScoDoc 9 models : Modules """ScoDoc 9 models : Modules
""" """
from flask import current_app, g from flask import current_app, g
from app import db from app import db
from app import models
from app.models import APO_CODE_STR_LEN from app.models import APO_CODE_STR_LEN
from app.models.but_refcomp import ApcParcours, app_critiques_modules, parcours_modules from app.models.but_refcomp import ApcParcours, app_critiques_modules, parcours_modules
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
@ -11,7 +13,7 @@ from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
class Module(db.Model): class Module(models.ScoDocModel):
"""Module""" """Module"""
__tablename__ = "notes_modules" __tablename__ = "notes_modules"
@ -76,6 +78,28 @@ class Module(db.Model):
return f"""<Module{ModuleType(self.module_type or ModuleType.STANDARD).name return f"""<Module{ModuleType(self.module_type or ModuleType.STANDARD).name
} id={self.id} code={self.code!r} semestre_id={self.semestre_id}>""" } id={self.id} code={self.code!r} semestre_id={self.semestre_id}>"""
@classmethod
def convert_dict_fields(cls, args: dict) -> dict:
"""Convert fields in the given dict. No other side effect.
returns: dict to store in model's db.
"""
# s'assure que ects etc est non ''
fs_empty_stored_as_nulls = {
"coefficient",
"ects",
"heures_cours",
"heures_td",
"heures_tp",
}
args_dict = {}
for key, value in args.items():
if hasattr(cls, key) and not isinstance(getattr(cls, key, None), property):
if key in fs_empty_stored_as_nulls and value == "":
value = None
args_dict[key] = value
return args_dict
def clone(self): def clone(self):
"""Create a new copy of this module.""" """Create a new copy of this module."""
mod = Module( mod = Module(

View File

@ -409,6 +409,7 @@ class CursusBUT(TypeCursus):
APC_SAE = True APC_SAE = True
USE_REFERENTIEL_COMPETENCES = True USE_REFERENTIEL_COMPETENCES = True
ALLOWED_UE_TYPES = [UE_STANDARD, UE_SPORT] ALLOWED_UE_TYPES = [UE_STANDARD, UE_SPORT]
ECTS_DIPLOME = 180
register_cursus(CursusBUT()) register_cursus(CursusBUT())

View File

@ -44,13 +44,15 @@ import random
from collections import OrderedDict from collections import OrderedDict
from xml.etree import ElementTree from xml.etree import ElementTree
import json import json
from typing import Any
from urllib.parse import urlparse, urlencode, parse_qs, urlunparse
from openpyxl.utils import get_column_letter from openpyxl.utils import get_column_letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak from reportlab.platypus import Paragraph, Spacer
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame from reportlab.platypus import Table, KeepInFrame
from reportlab.lib.colors import Color from reportlab.lib.colors import Color
from reportlab.lib import styles from reportlab.lib import styles
from reportlab.lib.units import inch, cm, mm from reportlab.lib.units import cm
from reportlab.rl_config import defaultPageSize # pylint: disable=no-name-in-module
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
@ -62,16 +64,32 @@ from app.scodoc.sco_pdf import SU
from app import log, ScoDocJSONEncoder from app import log, ScoDocJSONEncoder
def mark_paras(L, tags) -> list[str]: def mark_paras(items: list[Any], tags: list[str]) -> list[str]:
"""Put each (string) element of L between <tag>...</tag>, """Put each string element of items between <tag>...</tag>,
for each supplied tag. for each supplied tag.
Leave non string elements untouched. Leave non string elements untouched.
""" """
for tag in tags: for tag in tags:
start = "<" + tag + ">" start = "<" + tag + ">"
end = "</" + tag.split()[0] + ">" end = "</" + tag.split()[0] + ">"
L = [(start + (x or "") + end) if isinstance(x, str) else x for x in L] items = [(start + (x or "") + end) if isinstance(x, str) else x for x in items]
return L return items
def add_query_param(url: str, key: str, value: str) -> str:
"add parameter key=value to the given URL"
# Parse the URL
parsed_url = urlparse(url)
# Parse the query parameters
query_params = parse_qs(parsed_url.query)
# Add or update the query parameter
query_params[key] = [value]
# Encode the query parameters
encoded_query_params = urlencode(query_params, doseq=True)
# Construct the new URL
new_url_parts = parsed_url._replace(query=encoded_query_params)
new_url = urlunparse(new_url_parts)
return new_url
class DEFAULT_TABLE_PREFERENCES(object): class DEFAULT_TABLE_PREFERENCES(object):
@ -477,13 +495,15 @@ class GenTable:
H.append('<span class="gt_export_icons">') H.append('<span class="gt_export_icons">')
if self.xls_link: if self.xls_link:
H.append( H.append(
' <a href="%s&fmt=xls">%s</a>' % (self.base_url, scu.ICON_XLS) f""" <a href="{add_query_param(self.base_url, "fmt", "xls")
}">{scu.ICON_XLS}</a>"""
) )
if self.xls_link and self.pdf_link: if self.xls_link and self.pdf_link:
H.append("&nbsp;") H.append("&nbsp;")
if self.pdf_link: if self.pdf_link:
H.append( H.append(
' <a href="%s&fmt=pdf">%s</a>' % (self.base_url, scu.ICON_PDF) f""" <a href="{add_query_param(self.base_url, "fmt", "pdf")
}">{scu.ICON_PDF}</a>"""
) )
H.append("</span>") H.append("</span>")
H.append("</p>") H.append("</p>")
@ -582,9 +602,11 @@ class GenTable:
for line in data_list: for line in data_list:
Pt.append( Pt.append(
[ [
Paragraph(SU(str(x)), CellStyle) (
if (not isinstance(x, Paragraph)) Paragraph(SU(str(x)), CellStyle)
else x if (not isinstance(x, Paragraph))
else x
)
for x in line for x in line
] ]
) )

View File

@ -109,7 +109,7 @@ def sidebar_common():
{sidebar_dept()} {sidebar_dept()}
<h2 class="insidebar">Scolarité</h2> <h2 class="insidebar">Scolarité</h2>
<a href="{scu.ScoURL()}" class="sidebar">Semestres</a> <br> <a href="{scu.ScoURL()}" class="sidebar">Semestres</a> <br>
<a href="{scu.NotesURL()}" class="sidebar">Programmes</a> <br> <a href="{scu.NotesURL()}" class="sidebar">Formations</a> <br>
""" """
] ]
if current_user.has_permission(Permission.AbsChange): if current_user.has_permission(Permission.AbsChange):

View File

@ -114,7 +114,7 @@ def index_html(showcodes=0, showsemtable=0):
# aucun semestre courant: affiche aide # aucun semestre courant: affiche aide
H.append( H.append(
"""<h2 class="listesems">Aucune session en cours !</h2> """<h2 class="listesems">Aucune session en cours !</h2>
<p>Pour ajouter une session, aller dans <a href="Notes" id="link-programmes">Programmes</a>, <p>Pour ajouter une session, aller dans <a href="Notes" id="link-programmes">Formations</a>,
choisissez une formation, puis suivez le lien "<em>UE, modules, semestres</em>". choisissez une formation, puis suivez le lien "<em>UE, modules, semestres</em>".
</p><p> </p><p>
, en bas de page, suivez le lien , en bas de page, suivez le lien
@ -336,15 +336,15 @@ def _style_sems(sems):
else: else:
sem["semestre_id_n"] = sem["semestre_id"] sem["semestre_id_n"] = sem["semestre_id"]
# pour édition codes Apogée: # pour édition codes Apogée:
sem[ sem["_etapes_apo_str_td_attrs"] = (
"_etapes_apo_str_td_attrs" f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['etapes_apo_str']}" """
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['etapes_apo_str']}" """ )
sem[ sem["_elt_annee_apo_td_attrs"] = (
"_elt_annee_apo_td_attrs" f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_annee_apo']}" """
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_annee_apo']}" """ )
sem[ sem["_elt_sem_apo_td_attrs"] = (
"_elt_sem_apo_td_attrs" f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """ )
def delete_dept(dept_id: int) -> str: def delete_dept(dept_id: int) -> str:

View File

@ -412,7 +412,7 @@ def module_move(module_id, after=0, redirect=True):
db.session.add(neigh) db.session.add(neigh)
db.session.commit() db.session.commit()
module.formation.invalidate_cached_sems() module.formation.invalidate_cached_sems()
# redirect to ue_list page: # redirect to ue_table page:
if redirect: if redirect:
return flask.redirect( return flask.redirect(
url_for( url_for(
@ -454,7 +454,7 @@ def ue_move(ue_id, after=0, redirect=1):
db.session.commit() db.session.commit()
ue.formation.invalidate_cached_sems() ue.formation.invalidate_cached_sems()
# redirect to ue_list page # redirect to ue_table page
if redirect: if redirect:
return flask.redirect( return flask.redirect(
url_for( url_for(

View File

@ -106,9 +106,9 @@ def do_module_create(args) -> int:
if int(args.get("semestre_id", 0)) != ue.semestre_idx: if int(args.get("semestre_id", 0)) != ue.semestre_idx:
raise ScoValueError("Formation incompatible: contacter le support ScoDoc") raise ScoValueError("Formation incompatible: contacter le support ScoDoc")
# create # create
cnx = ndb.GetDBConnexion() module = Module.create_from_dict(args)
module_id = _moduleEditor.create(cnx, args) db.session.commit()
log(f"do_module_create: created {module_id} with {args}") log(f"do_module_create: created {module.id} with {args}")
# news # news
ScolarNews.add( ScolarNews.add(
@ -117,7 +117,7 @@ def do_module_create(args) -> int:
text=f"Modification de la formation {formation.acronyme}", text=f"Modification de la formation {formation.acronyme}",
) )
formation.invalidate_cached_sems() formation.invalidate_cached_sems()
return module_id return module.id
def module_create( def module_create(
@ -666,7 +666,7 @@ def module_edit(
"explanation": "numéro (1, 2, 3, 4, ...) pour ordre d'affichage", "explanation": "numéro (1, 2, 3, 4, ...) pour ordre d'affichage",
"type": "int", "type": "int",
"default": default_num, "default": default_num,
"allow_null": False, "allow_null": True,
}, },
), ),
] ]
@ -811,6 +811,10 @@ def module_edit(
) )
) )
else: else:
if isinstance(tf[2]["numero"], str):
tf[2]["numero"] = tf[2]["numero"].strip()
if not isinstance(tf[2]["numero"], int) and not tf[2]["numero"]:
tf[2]["numero"] = tf[2]["numero"] or default_num
if create: if create:
if not matiere_id: if not matiere_id:
# formulaire avec choix UE de rattachement # formulaire avec choix UE de rattachement

View File

@ -766,7 +766,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
"libjs/jQuery-tagEditor/jquery.caret.min.js", "libjs/jQuery-tagEditor/jquery.caret.min.js",
"js/module_tag_editor.js", "js/module_tag_editor.js",
], ],
page_title=f"Programme {formation.acronyme} v{formation.version}", page_title=f"Formation {formation.acronyme} v{formation.version}",
), ),
f"""<h2>{formation.html()} {lockicon} f"""<h2>{formation.html()} {lockicon}
</h2> </h2>
@ -888,7 +888,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
H.append( H.append(
f""" f"""
<div class="formation_ue_list"> <div class="formation_ue_list">
<div class="ue_list_tit">Programme pédagogique:</div> <div class="ue_list_tit">Formation (programme pédagogique):</div>
<form> <form>
<input type="checkbox" class="sco_tag_checkbox" <input type="checkbox" class="sco_tag_checkbox"
{'checked' if show_tags else ''} {'checked' if show_tags else ''}
@ -1054,7 +1054,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
) )
# <li>(debug) <a class="stdlink" href="check_form_integrity?formation_id=%(formation_id)s">Vérifier cohérence</a></li> # <li>(debug) <a class="stdlink" href="check_form_integrity?formation_id=%(formation_id)s">Vérifier cohérence</a></li>
warn, _ = sco_formsemestre_validation.check_formation_ues(formation_id) warn, _ = sco_formsemestre_validation.check_formation_ues(formation)
H.append(warn) H.append(warn)
H.append(html_sco_header.sco_footer()) H.append(html_sco_header.sco_footer())

View File

@ -30,7 +30,7 @@
import xml.dom.minidom import xml.dom.minidom
import flask import flask
from flask import flash, g, url_for from flask import flash, g, request, url_for
from flask_login import current_user from flask_login import current_user
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -495,7 +495,7 @@ def formation_list_table() -> GenTable:
returns a table returns a table
""" """
formations: list[Formation] = Formation.query.filter_by(dept_id=g.scodoc_dept_id) formations: list[Formation] = Formation.query.filter_by(dept_id=g.scodoc_dept_id)
title = "Programmes pédagogiques" title = "Formations (programmes pédagogiques)"
lockicon = scu.icontag( lockicon = scu.icontag(
"lock32_img", title="Comporte des semestres verrouillés", border="0" "lock32_img", title="Comporte des semestres verrouillés", border="0"
) )
@ -627,7 +627,7 @@ def formation_list_table() -> GenTable:
html_class="formation_list_table table_leftalign", html_class="formation_list_table table_leftalign",
html_with_td_classes=True, html_with_td_classes=True,
html_sortable=True, html_sortable=True,
base_url="{request.base_url}?formation_id={formation_id}", base_url=f"{request.base_url}",
page_title=title, page_title=title,
pdf_title=title, pdf_title=title,
preferences=sco_preferences.SemPreferences(), preferences=sco_preferences.SemPreferences(),

View File

@ -304,12 +304,16 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
{ {
"input_type": "text_suggest", "input_type": "text_suggest",
"size": 50, "size": 50,
"title": "(Co-)Directeur(s) des études" "title": (
if index "(Co-)Directeur(s) des études"
else "Directeur des études", if index
"explanation": "(facultatif) taper le début du nom et choisir dans le menu" else "Directeur des études"
if index ),
else "(obligatoire) taper le début du nom et choisir dans le menu", "explanation": (
"(facultatif) taper le début du nom et choisir dans le menu"
if index
else "(obligatoire) taper le début du nom et choisir dans le menu"
),
"allowed_values": allowed_user_names, "allowed_values": allowed_user_names,
"allow_null": index, # > 0, # il faut au moins un responsable de semestre "allow_null": index, # > 0, # il faut au moins un responsable de semestre
"text_suggest_options": { "text_suggest_options": {
@ -356,9 +360,11 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
"title": "Semestre dans la formation", "title": "Semestre dans la formation",
"allowed_values": semestre_id_list, "allowed_values": semestre_id_list,
"labels": semestre_id_labels, "labels": semestre_id_labels,
"explanation": "en BUT, on ne peut pas modifier le semestre après création" "explanation": (
if is_apc "en BUT, on ne peut pas modifier le semestre après création"
else "", if is_apc
else ""
),
"attributes": ['onchange="change_semestre_id();"'] if is_apc else "", "attributes": ['onchange="change_semestre_id();"'] if is_apc else "",
}, },
), ),
@ -1636,13 +1642,13 @@ def formsemestre_change_publication_bul(
def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
"""Changement manuel des coefficients des UE capitalisées.""" """Changement manuel des coefficients des UE capitalisées."""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id) ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
if not ok: if not ok:
return err return err
footer = html_sco_header.sco_footer() footer = html_sco_header.sco_footer()
help = """<p class="help"> help_msg = """<p class="help">
Seuls les modules ont un coefficient. Cependant, il est nécessaire d'affecter un coefficient aux UE capitalisée pour pouvoir les prendre en compte dans la moyenne générale. Seuls les modules ont un coefficient. Cependant, il est nécessaire d'affecter un coefficient aux UE capitalisée pour pouvoir les prendre en compte dans la moyenne générale.
</p> </p>
<p class="help">ScoDoc calcule normalement le coefficient d'une UE comme la somme des <p class="help">ScoDoc calcule normalement le coefficient d'une UE comme la somme des
@ -1665,17 +1671,16 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
""" """
H = [ H = [
html_sco_header.html_sem_header("Coefficients des UE du semestre"), html_sco_header.html_sem_header("Coefficients des UE du semestre"),
help, help_msg,
] ]
# #
ues, modimpls = _get_sem_ues_modimpls(formsemestre_id) ues, modimpls = _get_sem_ues_modimpls(formsemestre)
sum_coefs_by_ue_id = {}
for ue in ues: for ue in ues:
ue["sum_coefs"] = sum( sum_coefs_by_ue_id[ue.id] = sum(
[ modimpl.module.coefficient
mod["module"]["coefficient"] for modimpl in modimpls
for mod in modimpls if modimpl.module.ue_id == ue.id
if mod["module"]["ue_id"] == ue["ue_id"]
]
) )
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
@ -1684,20 +1689,20 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
form = [("formsemestre_id", {"input_type": "hidden"})] form = [("formsemestre_id", {"input_type": "hidden"})]
for ue in ues: for ue in ues:
coefs = sco_formsemestre.formsemestre_uecoef_list( coefs = sco_formsemestre.formsemestre_uecoef_list(
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]} cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue.id}
) )
if coefs: if coefs:
initvalues["ue_" + str(ue["ue_id"])] = coefs[0]["coefficient"] initvalues["ue_" + str(ue.id)] = coefs[0]["coefficient"]
else: else:
initvalues["ue_" + str(ue["ue_id"])] = "auto" initvalues["ue_" + str(ue.id)] = "auto"
descr = { descr = {
"size": 10, "size": 10,
"title": ue["acronyme"], "title": ue.acronyme,
"explanation": "somme coefs modules = %s" % ue["sum_coefs"], "explanation": f"somme coefs modules = {sum_coefs_by_ue_id[ue.id]}",
} }
if ue["ue_id"] == err_ue_id: if ue.id == err_ue_id:
descr["dom_id"] = "erroneous_ue" descr["dom_id"] = "erroneous_ue"
form.append(("ue_" + str(ue["ue_id"]), descr)) form.append(("ue_" + str(ue.id), descr))
tf = TrivialFormulator( tf = TrivialFormulator(
request.base_url, request.base_url,
@ -1722,12 +1727,12 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
# 1- supprime les coef qui ne sont plus forcés # 1- supprime les coef qui ne sont plus forcés
# 2- modifie ou cree les coefs # 2- modifie ou cree les coefs
ue_deleted = [] ue_deleted = []
ue_modified = [] ue_modified: list[tuple[UniteEns, float]] = []
msg = [] msg = []
for ue in ues: for ue in ues:
val = tf[2]["ue_" + str(ue["ue_id"])] val = tf[2]["ue_" + str(ue.id)]
coefs = sco_formsemestre.formsemestre_uecoef_list( coefs = sco_formsemestre.formsemestre_uecoef_list(
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]} cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue.id}
) )
if val == "" or val == "auto": if val == "" or val == "auto":
# supprime ce coef (il sera donc calculé automatiquement) # supprime ce coef (il sera donc calculé automatiquement)
@ -1737,13 +1742,11 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
try: try:
val = float(val) val = float(val)
if (not coefs) or (coefs[0]["coefficient"] != val): if (not coefs) or (coefs[0]["coefficient"] != val):
ue["coef"] = val ue_modified.append((ue, val))
ue_modified.append(ue) except ValueError:
except:
ok = False ok = False
msg.append( msg.append(
"valeur invalide (%s) pour le coefficient de l'UE %s" f"valeur invalide ({val}) pour le coefficient de l'UE {ue.acronyme}"
% (val, ue["acronyme"])
) )
if not ok: if not ok:
@ -1755,26 +1758,24 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
) )
# apply modifications # apply modifications
for ue in ue_modified: for ue, val in ue_modified:
sco_formsemestre.do_formsemestre_uecoef_edit_or_create( sco_formsemestre.do_formsemestre_uecoef_edit_or_create(
cnx, formsemestre_id, ue["ue_id"], ue["coef"] cnx, formsemestre_id, ue.id, val
) )
for ue in ue_deleted: for ue in ue_deleted:
sco_formsemestre.do_formsemestre_uecoef_delete( sco_formsemestre.do_formsemestre_uecoef_delete(cnx, formsemestre_id, ue.id)
cnx, formsemestre_id, ue["ue_id"]
)
if ue_modified or ue_deleted: if ue_modified or ue_deleted:
message = ["""<h3>Modification effectuées</h3>"""] message = ["""<h3>Modification effectuées</h3>"""]
if ue_modified: if ue_modified:
message.append("""<h4>Coefs modifiés dans les UE:<h4><ul>""") message.append("""<h4>Coefs modifiés dans les UE:<h4><ul>""")
for ue in ue_modified: for ue, val in ue_modified:
message.append("<li>%(acronyme)s : %(coef)s</li>" % ue) message.append(f"<li>{ue.acronyme} : {val}</li>")
message.append("</ul>") message.append("</ul>")
if ue_deleted: if ue_deleted:
message.append("""<h4>Coefs supprimés dans les UE:<h4><ul>""") message.append("""<h4>Coefs supprimés dans les UE:<h4><ul>""")
for ue in ue_deleted: for ue in ue_deleted:
message.append("<li>%(acronyme)s</li>" % ue) message.append(f"<li>{ue.acronyme}</li>")
message.append("</ul>") message.append("</ul>")
else: else:
message = ["""<h3>Aucune modification</h3>"""] message = ["""<h3>Aucune modification</h3>"""]
@ -1792,21 +1793,19 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
""" """
def _get_sem_ues_modimpls(formsemestre_id, modimpls=None): def _get_sem_ues_modimpls(
formsemestre: FormSemestre,
) -> tuple[list[UniteEns], list[ModuleImpl]]:
"""Get liste des UE du semestre (à partir des moduleimpls) """Get liste des UE du semestre (à partir des moduleimpls)
(utilisé quand on ne peut pas construire nt et faire nt.get_ues_stat_dict()) (utilisé quand on ne peut pas construire nt et faire nt.get_ues_stat_dict())
""" """
if modimpls is None:
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
uedict = {} uedict = {}
modimpls = formsemestre.modimpls.all()
for modimpl in modimpls: for modimpl in modimpls:
mod = sco_edit_module.module_list(args={"module_id": modimpl["module_id"]})[0] if not modimpl.module.ue_id in uedict:
modimpl["module"] = mod uedict[modimpl.module.ue.id] = modimpl.module.ue
if not mod["ue_id"] in uedict:
ue = sco_edit_ue.ue_list(args={"ue_id": mod["ue_id"]})[0]
uedict[ue["ue_id"]] = ue
ues = list(uedict.values()) ues = list(uedict.values())
ues.sort(key=lambda u: u["numero"]) ues.sort(key=lambda u: u.numero)
return ues, modimpls return ues, modimpls

View File

@ -305,18 +305,15 @@ def do_formsemestre_inscription_with_modules(
# 2- inscrit aux groupes # 2- inscrit aux groupes
for group_id in group_ids: for group_id in group_ids:
if group_id and group_id not in gdone: if group_id and group_id not in gdone:
group = GroupDescr.query.get_or_404(group_id) _ = GroupDescr.query.get_or_404(group_id)
sco_groups.set_group(etudid, group_id) sco_groups.set_group(etudid, group_id)
gdone[group_id] = 1 gdone[group_id] = 1
# Inscription à tous les modules de ce semestre # Inscription à tous les modules de ce semestre
modimpls = sco_moduleimpl.moduleimpl_withmodule_list( for modimpl in formsemestre.modimpls:
formsemestre_id=formsemestre_id if modimpl.module.ue.type != UE_SPORT:
)
for mod in modimpls:
if mod["ue"]["type"] != UE_SPORT:
sco_moduleimpl.do_moduleimpl_inscription_create( sco_moduleimpl.do_moduleimpl_inscription_create(
{"moduleimpl_id": mod["moduleimpl_id"], "etudid": etudid}, {"moduleimpl_id": modimpl.id, "etudid": etudid},
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,
) )
# Mise à jour des inscriptions aux parcours: # Mise à jour des inscriptions aux parcours:
@ -531,19 +528,17 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
if not sem["etat"]: if not sem["etat"]:
raise ScoValueError("Modification impossible: semestre verrouille") raise ScoValueError("Modification impossible: semestre verrouille")
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] etud = Identite.get_etud(etudid)
formsemestre = FormSemestre.get_formsemestre(formsemestre_id) formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
footer = html_sco_header.sco_footer() footer = html_sco_header.sco_footer()
H = [ H = [
html_sco_header.sco_header() html_sco_header.sco_header(),
+ "<h2>Inscription de %s aux modules de %s (%s - %s)</h2>" f"""<h2>Inscription de {etud.nomprenom} aux modules de {formsemestre.titre_mois()}</h2>""",
% (etud["nomprenom"], sem["titre_num"], sem["date_debut"], sem["date_fin"])
] ]
# Cherche les moduleimpls et les inscriptions # Cherche les moduleimpls et les inscriptions
mods = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
inscr = sco_moduleimpl.do_moduleimpl_inscription_list(etudid=etudid) inscr = sco_moduleimpl.do_moduleimpl_inscription_list(etudid=etudid)
# Formulaire # Formulaire
modimpls_by_ue_ids = collections.defaultdict(list) # ue_id : [ moduleimpl_id ] modimpls_by_ue_ids = collections.defaultdict(list) # ue_id : [ moduleimpl_id ]
@ -551,26 +546,26 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
ues = [] ues = []
ue_ids = set() ue_ids = set()
initvalues = {} initvalues = {}
for mod in mods: for modimpl in formsemestre.modimpls:
ue_id = mod["ue"]["ue_id"] ue_id = modimpl.module.ue.id
if not ue_id in ue_ids: if not ue_id in ue_ids:
ues.append(mod["ue"]) ues.append(modimpl.module.ue)
ue_ids.add(ue_id) ue_ids.add(ue_id)
modimpls_by_ue_ids[ue_id].append(mod["moduleimpl_id"]) modimpls_by_ue_ids[ue_id].append(modimpl.id)
modimpls_by_ue_names[ue_id].append( modimpls_by_ue_names[ue_id].append(
"%s %s" % (mod["module"]["code"] or "", mod["module"]["titre"] or "") f"{modimpl.module.code or ''} {modimpl.module.titre or ''}"
) )
vals = scu.get_request_args() vals = scu.get_request_args()
if not vals.get("tf_submitted", False): if not vals.get("tf_submitted", False):
# inscrit ? # inscrit ?
for ins in inscr: for ins in inscr:
if ins["moduleimpl_id"] == mod["moduleimpl_id"]: if ins["moduleimpl_id"] == modimpl.id:
key = "moduleimpls_%s" % ue_id key = f"moduleimpls_{ue_id}"
if key in initvalues: if key in initvalues:
initvalues[key].append(str(mod["moduleimpl_id"])) initvalues[key].append(str(modimpl.id))
else: else:
initvalues[key] = [str(mod["moduleimpl_id"])] initvalues[key] = [str(modimpl.id)]
break break
descr = [ descr = [
@ -578,10 +573,10 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
("etudid", {"input_type": "hidden"}), ("etudid", {"input_type": "hidden"}),
] ]
for ue in ues: for ue in ues:
ue_id = ue["ue_id"] ue_id = ue.id
ue_descr = ue["acronyme"] ue_descr = ue.acronyme
if ue["type"] != UE_STANDARD: if ue.type != UE_STANDARD:
ue_descr += " <em>%s</em>" % UE_TYPE_NAME[ue["type"]] ue_descr += " <em>%s</em>" % UE_TYPE_NAME[ue.type]
ue_status = nt.get_etud_ue_status(etudid, ue_id) ue_status = nt.get_etud_ue_status(etudid, ue_id)
if ue_status and ue_status["is_capitalized"]: if ue_status and ue_status["is_capitalized"]:
sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"]) sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"])
@ -606,7 +601,7 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
) )
descr.append( descr.append(
( (
"moduleimpls_%s" % ue_id, f"moduleimpls_{ue_id}",
{ {
"input_type": "checkbox", "input_type": "checkbox",
"title": "", "title": "",
@ -654,112 +649,98 @@ function chkbx_select(field_id, state) {
""" """
) )
return "\n".join(H) + "\n" + tf[1] + footer return "\n".join(H) + "\n" + tf[1] + footer
elif tf[0] == -1: if tf[0] == -1:
return flask.redirect( return flask.redirect(
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
else:
# Inscriptions aux modules choisis # Inscriptions aux modules choisis
# il faut desinscrire des modules qui ne figurent pas # il faut desinscrire des modules qui ne figurent pas
# et inscrire aux autres, sauf si deja inscrit # et inscrire aux autres, sauf si deja inscrit
a_desinscrire = {}.fromkeys([x["moduleimpl_id"] for x in mods]) a_desinscrire = {}.fromkeys([x.id for x in formsemestre.modimpls])
insdict = {} insdict = {}
for ins in inscr: for ins in inscr:
insdict[ins["moduleimpl_id"]] = ins insdict[ins["moduleimpl_id"]] = ins
for ue in ues: for ue in ues:
ue_id = ue["ue_id"] for moduleimpl_id in [int(x) for x in tf[2][f"moduleimpls_{ue.id}"]]:
for moduleimpl_id in [int(x) for x in tf[2]["moduleimpls_%s" % ue_id]]: if moduleimpl_id in a_desinscrire:
if moduleimpl_id in a_desinscrire:
del a_desinscrire[moduleimpl_id]
# supprime ceux auxquel pas inscrit
moduleimpls_a_desinscrire = list(a_desinscrire.keys())
for moduleimpl_id in moduleimpls_a_desinscrire:
if moduleimpl_id not in insdict:
del a_desinscrire[moduleimpl_id] del a_desinscrire[moduleimpl_id]
# supprime ceux auxquel pas inscrit
moduleimpls_a_desinscrire = list(a_desinscrire.keys())
for moduleimpl_id in moduleimpls_a_desinscrire:
if moduleimpl_id not in insdict:
del a_desinscrire[moduleimpl_id]
a_inscrire = set() a_inscrire = set()
for ue in ues: for ue in ues:
ue_id = ue["ue_id"] a_inscrire.update(
a_inscrire.update( int(x) for x in tf[2][f"moduleimpls_{ue.id}"]
int(x) for x in tf[2]["moduleimpls_%s" % ue_id] ) # conversion en int !
) # conversion en int ! # supprime ceux auquel deja inscrit:
# supprime ceux auquel deja inscrit: for ins in inscr:
for ins in inscr: if ins["moduleimpl_id"] in a_inscrire:
if ins["moduleimpl_id"] in a_inscrire: a_inscrire.remove(ins["moduleimpl_id"])
a_inscrire.remove(ins["moduleimpl_id"]) # dict des modules:
# dict des modules: modimpls_by_id = {modimpl.id: modimpl for modimpl in formsemestre.modimpls}
modsdict = {} #
for mod in mods: if (not a_inscrire) and (not a_desinscrire):
modsdict[mod["moduleimpl_id"]] = mod
#
if (not a_inscrire) and (not a_desinscrire):
H.append(
"""<h3>Aucune modification à effectuer</h3>
<p><a class="stdlink" href="%s">retour à la fiche étudiant</a></p>
"""
% url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
)
return "\n".join(H) + footer
H.append("<h3>Confirmer les modifications:</h3>")
if a_desinscrire:
H.append(
"<p>%s va être <b>désinscrit%s</b> des modules:<ul><li>"
% (etud["nomprenom"], etud["ne"])
)
H.append(
"</li><li>".join(
[
"%s (%s)"
% (
modsdict[x]["module"]["titre"],
modsdict[x]["module"]["code"] or "(module sans code)",
)
for x in a_desinscrire
]
)
+ "</p>"
)
H.append("</li></ul>")
if a_inscrire:
H.append(
"<p>%s va être <b>inscrit%s</b> aux modules:<ul><li>"
% (etud["nomprenom"], etud["ne"])
)
H.append(
"</li><li>".join(
[
"%s (%s)"
% (
modsdict[x]["module"]["titre"],
modsdict[x]["module"]["code"] or "(module sans code)",
)
for x in a_inscrire
]
)
+ "</p>"
)
H.append("</li></ul>")
modulesimpls_ainscrire = ",".join(str(x) for x in a_inscrire)
modulesimpls_adesinscrire = ",".join(str(x) for x in a_desinscrire)
H.append( H.append(
"""<form action="do_moduleimpl_incription_options"> f"""<h3>Aucune modification à effectuer</h3>
<input type="hidden" name="etudid" value="%s"/> <p><a class="stdlink" href="{
<input type="hidden" name="modulesimpls_ainscrire" value="%s"/> url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
<input type="hidden" name="modulesimpls_adesinscrire" value="%s"/> }">retour à la fiche étudiant</a></p>
<input type ="submit" value="Confirmer"/>
<input type ="button" value="Annuler" onclick="document.location='%s';"/>
</form>
""" """
% (
etudid,
modulesimpls_ainscrire,
modulesimpls_adesinscrire,
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
)
) )
return "\n".join(H) + footer return "\n".join(H) + footer
H.append("<h3>Confirmer les modifications:</h3>")
if a_desinscrire:
H.append(
f"""<p>{etud.nomprenom} va être <b>désinscrit{etud.e}</b> des modules:<ul><li>"""
)
H.append(
"</li><li>".join(
[
f"""{modimpls_by_id[x].module.titre or ''} ({
modimpls_by_id[x].module.code or '(module sans code)'})"""
for x in a_desinscrire
]
)
+ "</p>"
)
H.append("</li></ul>")
if a_inscrire:
H.append(
f"""<p>{etud.nomprenom} va être <b>inscrit{etud.e}</b> aux modules:<ul><li>"""
)
H.append(
"</li><li>".join(
[
f"""{modimpls_by_id[x].module.titre or ''} ({
modimpls_by_id[x].module.code or '(module sans code)'})"""
for x in a_inscrire
]
)
+ "</p>"
)
H.append("</li></ul>")
modulesimpls_ainscrire = ",".join(str(x) for x in a_inscrire)
modulesimpls_adesinscrire = ",".join(str(x) for x in a_desinscrire)
H.append(
f"""
<form action="do_moduleimpl_incription_options">
<input type="hidden" name="etudid" value="{etudid}"/>
<input type="hidden" name="modulesimpls_ainscrire" value="{modulesimpls_ainscrire}"/>
<input type="hidden" name="modulesimpls_adesinscrire" value="{modulesimpls_adesinscrire}"/>
<input type ="submit" value="Confirmer"/>
<input type ="button" value="Annuler" onclick="document.location='{
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
}';"/>
</form>
"""
)
return "\n".join(H) + footer
def do_moduleimpl_incription_options( def do_moduleimpl_incription_options(
etudid, modulesimpls_ainscrire, modulesimpls_adesinscrire etudid, modulesimpls_ainscrire, modulesimpls_adesinscrire

View File

@ -909,37 +909,6 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
return "\n".join(H) return "\n".join(H)
def html_expr_diagnostic(diagnostics):
"""Affiche messages d'erreur des formules utilisateurs"""
H = []
H.append('<div class="ue_warning">Erreur dans des formules utilisateurs:<ul>')
last_id, last_msg = None, None
for diag in diagnostics:
if "moduleimpl_id" in diag:
mod = sco_moduleimpl.moduleimpl_withmodule_list(
moduleimpl_id=diag["moduleimpl_id"]
)[0]
H.append(
'<li>module <a href="moduleimpl_status?moduleimpl_id=%s">%s</a>: %s</li>'
% (
diag["moduleimpl_id"],
mod["module"]["abbrev"] or mod["module"]["code"] or "?",
diag["msg"],
)
)
else:
if diag["ue_id"] != last_id or diag["msg"] != last_msg:
ue = sco_edit_ue.ue_list({"ue_id": diag["ue_id"]})[0]
H.append(
'<li>UE "%s": %s</li>'
% (ue["acronyme"] or ue["titre"] or "?", diag["msg"])
)
last_id, last_msg = diag["ue_id"], diag["msg"]
H.append("</ul></div>")
return "".join(H)
def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None): def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None):
"""En-tête HTML des pages "semestre" """ """En-tête HTML des pages "semestre" """
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id) formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
@ -1081,9 +1050,6 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
>Toutes évaluations (même incomplètes) visibles</div>""" >Toutes évaluations (même incomplètes) visibles</div>"""
) )
if nt.expr_diagnostics:
H.append(html_expr_diagnostic(nt.expr_diagnostics))
if nt.parcours.APC_SAE: if nt.parcours.APC_SAE:
# BUT: tableau ressources puis SAE # BUT: tableau ressources puis SAE
ressources = [ ressources = [

View File

@ -1217,7 +1217,7 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
<div id="ue_list_code" class="sco_box sco_green_bg"> <div id="ue_list_code" class="sco_box sco_green_bg">
<!-- filled by ue_sharing_code --> <!-- filled by ue_sharing_code -->
</div> </div>
{check_formation_ues(formation.id)[0]} {check_formation_ues(formation)[0]}
{html_sco_header.sco_footer()} {html_sco_header.sco_footer()}
""" """
@ -1376,15 +1376,14 @@ def _invalidate_etud_formation_caches(etudid, formation_id):
) # > modif decision UE (inval tous semestres avec cet etudiant, ok mais conservatif) ) # > modif decision UE (inval tous semestres avec cet etudiant, ok mais conservatif)
def check_formation_ues(formation_id): def check_formation_ues(formation: Formation) -> tuple[str, dict[int, list[UniteEns]]]:
"""Verifie que les UE d'une formation sont chacune utilisée dans un seul semestre_id """Verifie que les UE d'une formation sont chacune utilisée dans un seul semestre_id
Si ce n'est pas le cas, c'est probablement (mais pas forcément) une erreur de Si ce n'est pas le cas, c'est probablement (mais pas forcément) une erreur de
définition du programme: cette fonction retourne un bout de HTML définition du programme: cette fonction retourne un bout de HTML
à afficher pour prévenir l'utilisateur, ou '' si tout est ok. à afficher pour prévenir l'utilisateur, ou '' si tout est ok.
""" """
ues = sco_edit_ue.ue_list({"formation_id": formation_id})
ue_multiples = {} # { ue_id : [ liste des formsemestre ] } ue_multiples = {} # { ue_id : [ liste des formsemestre ] }
for ue in ues: for ue in formation.ues:
# formsemestres utilisant cette ue ? # formsemestres utilisant cette ue ?
sems = ndb.SimpleDictFetch( sems = ndb.SimpleDictFetch(
"""SELECT DISTINCT sem.id AS formsemestre_id, sem.* """SELECT DISTINCT sem.id AS formsemestre_id, sem.*
@ -1394,9 +1393,9 @@ def check_formation_ues(formation_id):
AND mi.formsemestre_id = sem.id AND mi.formsemestre_id = sem.id
AND mod.ue_id = %(ue_id)s AND mod.ue_id = %(ue_id)s
""", """,
{"ue_id": ue["ue_id"], "formation_id": formation_id}, {"ue_id": ue.id, "formation_id": formation.id},
) )
semestre_ids = set([x["semestre_id"] for x in sems]) semestre_ids = {x["semestre_id"] for x in sems}
if ( if (
len(semestre_ids) > 1 len(semestre_ids) > 1
): # plusieurs semestres d'indices differents dans le cursus ): # plusieurs semestres d'indices differents dans le cursus
@ -1416,11 +1415,11 @@ def check_formation_ues(formation_id):
<ul> <ul>
""" """
] ]
for ue in ues: for ue in formation.ues:
if ue["ue_id"] in ue_multiples: if ue.id in ue_multiples:
sems = [ sems = [
sco_formsemestre.get_formsemestre(x["formsemestre_id"]) sco_formsemestre.get_formsemestre(x["formsemestre_id"])
for x in ue_multiples[ue["ue_id"]] for x in ue_multiples[ue.id]
] ]
slist = ", ".join( slist = ", ".join(
[ [
@ -1429,7 +1428,7 @@ def check_formation_ues(formation_id):
for s in sems for s in sems
] ]
) )
H.append("<li><b>%s</b> : %s</li>" % (ue["acronyme"], slist)) H.append("<li><b>%s</b> : %s</li>" % (ue.acronyme, slist))
H.append("</ul></div>") H.append("</ul></div>")
return "\n".join(H), ue_multiples return "\n".join(H), ue_multiples

View File

@ -56,6 +56,7 @@ _moduleimplEditor = ndb.EditableTable(
def do_moduleimpl_create(args): def do_moduleimpl_create(args):
"create a moduleimpl" "create a moduleimpl"
# TODO remplacer par une methode de ModuleImpl qui appelle super().create_from_dict() puis invalide le formsemestre
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
r = _moduleimplEditor.create(cnx, args) r = _moduleimplEditor.create(cnx, args)
sco_cache.invalidate_formsemestre( sco_cache.invalidate_formsemestre(
@ -109,91 +110,6 @@ def do_moduleimpl_edit(args, formsemestre_id=None, cnx=None):
) # > modif moduleimpl ) # > modif moduleimpl
def moduleimpl_withmodule_list(
moduleimpl_id=None, formsemestre_id=None, module_id=None, sort_by_ue=False
) -> list:
"""Liste les moduleimpls et ajoute dans chacun
l'UE, la matière et le module auxquels ils appartiennent.
Tri la liste par:
- pour les formations classiques: semestre/UE/numero_matiere/numero_module;
- pour le BUT: ignore UEs sauf si sort_by_ue et matières dans le tri.
NB: Cette fonction faisait partie de l'API ScoDoc 7.
"""
from app.scodoc import sco_edit_ue
from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module
modimpls = moduleimpl_list(
**{
"moduleimpl_id": moduleimpl_id,
"formsemestre_id": formsemestre_id,
"module_id": module_id,
}
)
if not modimpls:
return []
ues = {}
matieres = {}
modules = {}
for mi in modimpls:
module_id = mi["module_id"]
if not mi["module_id"] in modules:
modules[module_id] = sco_edit_module.module_list(
args={"module_id": module_id}
)[0]
mi["module"] = modules[module_id]
ue_id = mi["module"]["ue_id"]
if not ue_id in ues:
ues[ue_id] = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0]
mi["ue"] = ues[ue_id]
matiere_id = mi["module"]["matiere_id"]
if not matiere_id in matieres:
matieres[matiere_id] = sco_edit_matiere.matiere_list(
args={"matiere_id": matiere_id}
)[0]
mi["matiere"] = matieres[matiere_id]
mod = modimpls[0]["module"]
formation = db.session.get(Formation, mod["formation_id"])
if formation.is_apc():
# tri par numero_module
if sort_by_ue:
modimpls.sort(
key=lambda x: (
x["ue"]["numero"],
x["ue"]["ue_id"],
x["module"]["module_type"],
x["module"]["numero"],
x["module"]["code"],
)
)
else:
modimpls.sort(
key=lambda x: (
x["module"]["module_type"],
x["module"]["numero"],
x["module"]["code"],
)
)
else:
# Formations classiques, avec matières:
# tri par semestre/UE/numero_matiere/numero_module
modimpls.sort(
key=lambda x: (
x["ue"]["numero"],
x["ue"]["ue_id"],
x["matiere"]["numero"],
x["matiere"]["matiere_id"],
x["module"]["numero"],
x["module"]["code"],
)
)
return modimpls
def moduleimpls_in_external_ue(ue_id): def moduleimpls_in_external_ue(ue_id):
"""List of modimpls in this ue""" """List of modimpls in this ue"""
cursor = ndb.SimpleQuery( cursor = ndb.SimpleQuery(
@ -254,9 +170,9 @@ _moduleimpl_inscriptionEditor = ndb.EditableTable(
) )
def do_moduleimpl_inscription_create(args, formsemestre_id=None): def do_moduleimpl_inscription_create(args, formsemestre_id=None, cnx=None):
"create a moduleimpl_inscription" "create a moduleimpl_inscription"
cnx = ndb.GetDBConnexion() cnx = cnx or ndb.GetDBConnexion()
try: try:
r = _moduleimpl_inscriptionEditor.create(cnx, args) r = _moduleimpl_inscriptionEditor.create(cnx, args)
except psycopg2.errors.UniqueViolation as exc: except psycopg2.errors.UniqueViolation as exc:
@ -270,7 +186,7 @@ def do_moduleimpl_inscription_create(args, formsemestre_id=None):
cnx, cnx,
method="moduleimpl_inscription", method="moduleimpl_inscription",
etudid=args["etudid"], etudid=args["etudid"],
msg="inscription module %s" % args["moduleimpl_id"], msg=f"inscription module {args['moduleimpl_id']}",
commit=False, commit=False,
) )
return r return r
@ -297,32 +213,29 @@ def do_moduleimpl_inscrit_etuds(moduleimpl_id, formsemestre_id, etudids, reset=F
args={"formsemestre_id": formsemestre_id, "etudid": etudid} args={"formsemestre_id": formsemestre_id, "etudid": etudid}
) )
if not insem: if not insem:
raise ScoValueError("%s n'est pas inscrit au semestre !" % etudid) raise ScoValueError(f"{etudid} n'est pas inscrit au semestre !")
cnx = ndb.GetDBConnexion()
# Desinscriptions # Desinscriptions
if reset: if reset:
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute( cursor.execute(
"delete from notes_moduleimpl_inscription where moduleimpl_id = %(moduleimpl_id)s", "delete from notes_moduleimpl_inscription where moduleimpl_id = %(moduleimpl_id)s",
{"moduleimpl_id": moduleimpl_id}, {"moduleimpl_id": moduleimpl_id},
) )
# Inscriptions au module: # Inscriptions au module:
inmod_set = set( inmod_set = {
[ x["etudid"] for x in do_moduleimpl_inscription_list(moduleimpl_id=moduleimpl_id)
# hum ? }
x["etudid"]
for x in do_moduleimpl_inscription_list(moduleimpl_id=moduleimpl_id)
]
)
for etudid in etudids: for etudid in etudids:
# deja inscrit ? # déja inscrit ?
if not etudid in inmod_set: if not etudid in inmod_set:
do_moduleimpl_inscription_create( do_moduleimpl_inscription_create(
{"moduleimpl_id": moduleimpl_id, "etudid": etudid}, {"moduleimpl_id": moduleimpl_id, "etudid": etudid},
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,
cnx=cnx,
) )
cnx.commit()
sco_cache.invalidate_formsemestre( sco_cache.invalidate_formsemestre(
formsemestre_id=formsemestre_id formsemestre_id=formsemestre_id
) # > moduleimpl_inscrit_etuds ) # > moduleimpl_inscrit_etuds

View File

@ -409,34 +409,32 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
H.append( H.append(
'<h3>Étudiants avec UEs capitalisées (ADM):</h3><ul class="ue_inscr_list">' '<h3>Étudiants avec UEs capitalisées (ADM):</h3><ul class="ue_inscr_list">'
) )
ues = [ ues = [UniteEns.query.get_or_404(ue_id) for ue_id in ues_cap_info.keys()]
sco_edit_ue.ue_list({"ue_id": ue_id})[0] for ue_id in ues_cap_info.keys() ues.sort(key=lambda u: u.numero)
]
ues.sort(key=lambda u: u["numero"])
for ue in ues: for ue in ues:
H.append( H.append(
f"""<li class="tit"><span class="tit">{ue['acronyme']}: {ue['titre']}</span>""" f"""<li class="tit"><span class="tit">{ue.acronyme}: {ue.titre or ''}</span>"""
) )
H.append("<ul>") H.append("<ul>")
for info in ues_cap_info[ue["ue_id"]]: for info in ues_cap_info[ue.id]:
etud = sco_etud.get_etud_info(etudid=info["etudid"], filled=True)[0] etud = Identite.get_etud(info["etudid"])
H.append( H.append(
f"""<li class="etud"><a class="discretelink etudinfo" f"""<li class="etud"><a class="discretelink etudinfo"
id="{info['etudid']}" id="{etud.id}"
href="{ href="{
url_for( url_for(
"scolar.fiche_etud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=etud["etudid"], etudid=etud.id,
) )
}">{etud["nomprenom"]}</a>""" }">{etud.nomprenom}</a>"""
) )
if info["ue_status"]["event_date"]: if info["ue_status"]["event_date"]:
H.append( H.append(
f"""(cap. le {info["ue_status"]["event_date"].strftime("%d/%m/%Y")})""" f"""(cap. le {info["ue_status"]["event_date"].strftime("%d/%m/%Y")})"""
) )
if is_apc: if is_apc:
is_inscrit_ue = (etud["etudid"], ue["id"]) not in res.dispense_ues is_inscrit_ue = (etud.id, ue.id) not in res.dispense_ues
else: else:
# CLASSIQUE # CLASSIQUE
is_inscrit_ue = info["is_ins"] is_inscrit_ue = info["is_ins"]
@ -468,8 +466,8 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
H.append( H.append(
f"""<div><a class="stdlink" href="{ f"""<div><a class="stdlink" href="{
url_for("notes.etud_desinscrit_ue", url_for("notes.etud_desinscrit_ue",
scodoc_dept=g.scodoc_dept, etudid=etud["etudid"], scodoc_dept=g.scodoc_dept, etudid=etud.id,
formsemestre_id=formsemestre_id, ue_id=ue["ue_id"]) formsemestre_id=formsemestre_id, ue_id=ue.id)
}">désinscrire {"des modules" if not is_apc else ""} de cette UE</a></div> }">désinscrire {"des modules" if not is_apc else ""} de cette UE</a></div>
""" """
) )
@ -479,8 +477,8 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
H.append( H.append(
f"""<div><a class="stdlink" href="{ f"""<div><a class="stdlink" href="{
url_for("notes.etud_inscrit_ue", url_for("notes.etud_inscrit_ue",
scodoc_dept=g.scodoc_dept, etudid=etud["etudid"], scodoc_dept=g.scodoc_dept, etudid=etud.id,
formsemestre_id=formsemestre_id, ue_id=ue["ue_id"]) formsemestre_id=formsemestre_id, ue_id=ue.id)
}">inscrire à {"" if is_apc else "tous les modules de"} cette UE</a></div> }">inscrire à {"" if is_apc else "tous les modules de"} cette UE</a></div>
""" """
) )

View File

@ -127,12 +127,12 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
"args": { "args": {
"group_ids": group_id, "group_ids": group_id,
"evaluation_id": evaluation.id, "evaluation_id": evaluation.id,
"date_debut": evaluation.date_debut.isoformat() "date_debut": (
if evaluation.date_debut evaluation.date_debut.isoformat() if evaluation.date_debut else ""
else "", ),
"date_fin": evaluation.date_fin.isoformat() "date_fin": (
if evaluation.date_fin evaluation.date_fin.isoformat() if evaluation.date_fin else ""
else "", ),
}, },
"enabled": evaluation.date_debut is not None "enabled": evaluation.date_debut is not None
and evaluation.date_fin is not None, and evaluation.date_fin is not None,
@ -355,10 +355,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
Ses notes ne peuvent pas être prises en compte dans les moyennes d'UE. Ses notes ne peuvent pas être prises en compte dans les moyennes d'UE.
</div>""" </div>"""
) )
#
if has_expression and nt.expr_diagnostics:
H.append(sco_formsemestre_status.html_expr_diagnostic(nt.expr_diagnostics))
#
if formsemestre_has_decisions(formsemestre_id): if formsemestre_has_decisions(formsemestre_id):
H.append( H.append(
"""<ul class="tf-msg"> """<ul class="tf-msg">

View File

@ -139,9 +139,8 @@ def dict_pvjury(
dec_ue_list = _descr_decisions_ues( dec_ue_list = _descr_decisions_ues(
nt, etudid, d["decisions_ue"], d["decision_sem"] nt, etudid, d["decisions_ue"], d["decision_sem"]
) )
d["decisions_ue_nb"] = len( # avec les UE capitalisées, donc des éventuels doublons:
dec_ue_list d["decisions_ue_nb"] = len(dec_ue_list)
) # avec les UE capitalisées, donc des éventuels doublons
# Mais sur la description (eg sur les bulletins), on ne veut pas # Mais sur la description (eg sur les bulletins), on ne veut pas
# afficher ces doublons: on uniquifie sur ue_code # afficher ces doublons: on uniquifie sur ue_code
_codes = set() _codes = set()
@ -291,8 +290,10 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem) -> list[dict]:
) )
) )
): ):
ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0] ue = UniteEns.query.get(ue_id)
uelist.append(ue) assert ue
# note modernisation code: on utilise des dict tant que get_etud_ue_status renvoie des dicts
uelist.append(ue.to_dict())
# Les UE capitalisées dans d'autres semestres: # Les UE capitalisées dans d'autres semestres:
if etudid in nt.validations.ue_capitalisees.index: if etudid in nt.validations.ue_capitalisees.index:
for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]: for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]:

View File

@ -528,6 +528,7 @@ def notes_add(
Return: tuple (etudids_changed, nb_suppress, etudids_with_decision) Return: tuple (etudids_changed, nb_suppress, etudids_with_decision)
""" """
assert evaluation_id is not None
now = psycopg2.Timestamp(*time.localtime()[:6]) now = psycopg2.Timestamp(*time.localtime()[:6])
# Vérifie inscription et valeur note # Vérifie inscription et valeur note
@ -539,7 +540,7 @@ def notes_add(
} }
for etudid, value in notes: for etudid, value in notes:
if check_inscription and (etudid not in inscrits): if check_inscription and (etudid not in inscrits):
raise NoteProcessError(f"etudiant {etudid} non inscrit dans ce module") raise NoteProcessError(f"étudiant {etudid} non inscrit dans ce module")
if (value is not None) and not isinstance(value, float): if (value is not None) and not isinstance(value, float):
raise NoteProcessError( raise NoteProcessError(
f"etudiant {etudid}: valeur de note invalide ({value})" f"etudiant {etudid}: valeur de note invalide ({value})"

View File

@ -60,16 +60,14 @@ from app.models.formsemestre import FormSemestre
from app import db, log from app import db, log
from app.models import Evaluation, ModuleImpl, UniteEns from app.models import Evaluation, Identite, ModuleImpl, UniteEns
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_edit_matiere from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue from app.scodoc import sco_edit_ue
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_moduleimpl from app.scodoc import sco_moduleimpl
from app.scodoc import sco_saisie_notes from app.scodoc import sco_saisie_notes
from app.scodoc import sco_etud
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
@ -83,10 +81,10 @@ def external_ue_create(
acronyme="", acronyme="",
ue_type=codes_cursus.UE_STANDARD, ue_type=codes_cursus.UE_STANDARD,
ects=0.0, ects=0.0,
) -> int: ) -> ModuleImpl:
"""Crée UE/matiere/module dans la formation du formsemestre """Crée UE/matiere/module dans la formation du formsemestre
puis un moduleimpl. puis un moduleimpl.
Return: moduleimpl_id Return: moduleimpl
""" """
formsemestre = FormSemestre.get_formsemestre(formsemestre_id) formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
log(f"creating external UE in {formsemestre}: {acronyme}") log(f"creating external UE in {formsemestre}: {acronyme}")
@ -139,28 +137,30 @@ def external_ue_create(
"module_id": module_id, "module_id": module_id,
"formsemestre_id": formsemestre_id, "formsemestre_id": formsemestre_id,
# affecte le 1er responsable du semestre comme resp. du module # affecte le 1er responsable du semestre comme resp. du module
"responsable_id": formsemestre.responsables[0].id "responsable_id": (
if len(formsemestre.responsables) formsemestre.responsables[0].id
else None, if len(formsemestre.responsables)
else None
),
}, },
) )
modimpl = ModuleImpl.query.get(moduleimpl_id)
return moduleimpl_id assert modimpl
return modimpl
def external_ue_inscrit_et_note( def external_ue_inscrit_et_note(
moduleimpl_id: int, formsemestre_id: int, notes_etuds: dict moduleimpl: ModuleImpl, formsemestre_id: int, notes_etuds: dict
): ):
"""Inscrit les étudiants au moduleimpl, crée au besoin une évaluation """Inscrit les étudiants au moduleimpl, crée au besoin une évaluation
et enregistre les notes. et enregistre les notes.
""" """
moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
log( log(
f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl_id}, notes_etuds={notes_etuds})" f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl.id}, notes_etuds={notes_etuds})"
) )
# Inscription des étudiants # Inscription des étudiants
sco_moduleimpl.do_moduleimpl_inscrit_etuds( sco_moduleimpl.do_moduleimpl_inscrit_etuds(
moduleimpl_id, moduleimpl.id,
formsemestre_id, formsemestre_id,
list(notes_etuds.keys()), list(notes_etuds.keys()),
) )
@ -188,12 +188,12 @@ def external_ue_inscrit_et_note(
) )
def get_existing_external_ue(formation_id: int) -> list[dict]: def get_existing_external_ue(formation_id: int) -> list[UniteEns]:
"Liste de toutes les UE externes définies dans cette formation" "Liste de toutes les UEs externes définies dans cette formation"
return sco_edit_ue.ue_list(args={"formation_id": formation_id, "is_external": True}) return UniteEns.query.filter_by(formation_id=formation_id, is_external=True).all()
def get_external_moduleimpl_id(formsemestre_id: int, ue_id: int) -> int: def get_external_moduleimpl(formsemestre_id: int, ue_id: int) -> ModuleImpl:
"moduleimpl correspondant à l'UE externe indiquée de ce formsemestre" "moduleimpl correspondant à l'UE externe indiquée de ce formsemestre"
r = ndb.SimpleDictFetch( r = ndb.SimpleDictFetch(
""" """
@ -205,7 +205,10 @@ def get_external_moduleimpl_id(formsemestre_id: int, ue_id: int) -> int:
{"ue_id": ue_id, "formsemestre_id": formsemestre_id}, {"ue_id": ue_id, "formsemestre_id": formsemestre_id},
) )
if r: if r:
return r[0]["moduleimpl_id"] modimpl_id = r[0]["moduleimpl_id"]
modimpl = ModuleImpl.query.get(modimpl_id)
assert modimpl
return modimpl
else: else:
raise ScoValueError( raise ScoValueError(
f"""Aucun module externe ne correspond f"""Aucun module externe ne correspond
@ -225,20 +228,20 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
En BUT, pas d'UEs externes. Voir https://scodoc.org/git/ScoDoc/ScoDoc/issues/542 En BUT, pas d'UEs externes. Voir https://scodoc.org/git/ScoDoc/ScoDoc/issues/542
""" """
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
# Contrôle d'accès: # Contrôle d'accès:
if not formsemestre.can_be_edited_by(current_user): if not formsemestre.can_be_edited_by(current_user):
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération") raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
if formsemestre.formation.is_apc(): if formsemestre.formation.is_apc():
raise ScoValueError("Impossible d'ajouter une UE externe en BUT") raise ScoValueError("Impossible d'ajouter une UE externe en BUT")
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0] etud = Identite.get_etud(etudid)
formation_id = formsemestre.formation.id formation_id = formsemestre.formation.id
existing_external_ue = get_existing_external_ue(formation_id) existing_external_ue = get_existing_external_ue(formation_id)
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
"Ajout d'une UE externe pour %(nomprenom)s" % etud, f"Ajout d'une UE externe pour {etud.nomprenom}",
javascripts=["js/sco_ue_external.js"], javascripts=["js/sco_ue_external.js"],
), ),
"""<p class="help">Cette page permet d'indiquer que l'étudiant a suivi une UE """<p class="help">Cette page permet d'indiquer que l'étudiant a suivi une UE
@ -275,10 +278,10 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
"input_type": "menu", "input_type": "menu",
"title": "UE externe existante:", "title": "UE externe existante:",
"allowed_values": [""] "allowed_values": [""]
+ [str(ue["ue_id"]) for ue in existing_external_ue], + [str(ue.id) for ue in existing_external_ue],
"labels": [default_label] "labels": [default_label]
+ [ + [
"%s (%s)" % (ue["titre"], ue["acronyme"]) f"{ue.titre or ''} ({ue.acronyme})"
for ue in existing_external_ue for ue in existing_external_ue
], ],
"attributes": ['onchange="update_external_ue_form();"'], "attributes": ['onchange="update_external_ue_form();"'],
@ -364,7 +367,7 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
) )
if tf[2]["existing_ue"]: if tf[2]["existing_ue"]:
ue_id = int(tf[2]["existing_ue"]) ue_id = int(tf[2]["existing_ue"])
moduleimpl_id = get_external_moduleimpl_id(formsemestre_id, ue_id) modimpl = get_external_moduleimpl(formsemestre_id, ue_id)
else: else:
acronyme = tf[2]["acronyme"].strip() acronyme = tf[2]["acronyme"].strip()
if not acronyme: if not acronyme:
@ -375,7 +378,7 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
+ tf[1] + tf[1]
+ html_footer + html_footer
) )
moduleimpl_id = external_ue_create( modimpl = external_ue_create(
formsemestre_id, formsemestre_id,
titre=tf[2]["titre"], titre=tf[2]["titre"],
acronyme=acronyme, acronyme=acronyme,
@ -384,7 +387,7 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
) )
external_ue_inscrit_et_note( external_ue_inscrit_et_note(
moduleimpl_id, modimpl,
formsemestre_id, formsemestre_id,
{etudid: note_value}, {etudid: note_value},
) )

View File

@ -23,7 +23,7 @@
<h2 class="insidebar">Scolarité</h2> <h2 class="insidebar">Scolarité</h2>
<a href="{{url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Semestres</a> <br> <a href="{{url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Semestres</a> <br>
<a href="{{url_for('notes.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Programmes</a> <br> <a href="{{url_for('notes.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Formations</a> <br>
{% if current_user.has_permission(sco.Permission.AbsChange)%} {% if current_user.has_permission(sco.Permission.AbsChange)%}
<a href="{{url_for('assiduites.bilan_dept', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Assiduité</a> <br> <a href="{{url_for('assiduites.bilan_dept', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Assiduité</a> <br>

View File

@ -30,6 +30,7 @@ Module notes: issu de ScoDoc7 / ZNotes.py
Emmanuel Viennet, 2021 Emmanuel Viennet, 2021
""" """
import html
from operator import itemgetter from operator import itemgetter
import time import time
@ -487,7 +488,6 @@ def get_ue_niveaux_options_html():
return apc_edit_ue.get_ue_niveaux_options_html(ue) return apc_edit_ue.get_ue_niveaux_options_html(ue)
@bp.route("/ue_list") # backward compat
@bp.route("/ue_table") @bp.route("/ue_table")
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
@ -682,21 +682,21 @@ def module_clone():
@bp.route("/index_html") @bp.route("/index_html")
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
@scodoc7func
def index_html(): def index_html():
"Page accueil formations" "Page accueil formations"
fmt = request.args.get("fmt", "html")
editable = current_user.has_permission(Permission.EditFormation) editable = current_user.has_permission(Permission.EditFormation)
table = sco_formations.formation_list_table()
if fmt != "html":
return table.make_page(fmt=fmt, filename=f"Formations-{g.scodoc_dept}")
H = [ H = [
html_sco_header.sco_header(page_title="Programmes formations"), html_sco_header.sco_header(page_title="Formations (programmes)"),
"""<h2>Programmes pédagogiques</h2> """<h2>Formations (programmes pédagogiques)</h2>
""", """,
table.html(),
] ]
T = sco_formations.formation_list_table()
H.append(T.html())
if editable: if editable:
H.append( H.append(
f""" f"""
@ -804,7 +804,7 @@ def formation_import_xml_form():
<h2>Import effectué !</h2> <h2>Import effectué !</h2>
<ul> <ul>
<li><a class="stdlink" href="{ <li><a class="stdlink" href="{
url_for("notes.ue_list", scodoc_dept=g.scodoc_dept, formation_id=formation_id url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id
)}">Voir la formation</a> )}">Voir la formation</a>
</li> </li>
<li><a class="stdlink" href="{ <li><a class="stdlink" href="{
@ -817,19 +817,6 @@ def formation_import_xml_form():
""" """
# sco_publish(
# "/formation_create_new_version",
# sco_formations.formation_create_new_version,
# Permission.EditFormation,
# )
# --- UE
sco_publish(
"/ue_list",
sco_edit_ue.ue_list,
Permission.ScoView,
)
sco_publish("/module_move", sco_edit_formation.module_move, Permission.EditFormation) sco_publish("/module_move", sco_edit_formation.module_move, Permission.EditFormation)
sco_publish("/ue_move", sco_edit_formation.ue_move, Permission.EditFormation) sco_publish("/ue_move", sco_edit_formation.ue_move, Permission.EditFormation)
@ -3284,11 +3271,12 @@ def check_sem_integrity(formsemestre_id, fix=False):
for modimpl in modimpls: for modimpl in modimpls:
mod = sco_edit_module.module_list({"module_id": modimpl["module_id"]})[0] mod = sco_edit_module.module_list({"module_id": modimpl["module_id"]})[0]
formations_set.add(mod["formation_id"]) formations_set.add(mod["formation_id"])
ue = sco_edit_ue.ue_list({"ue_id": mod["ue_id"]})[0] ue = UniteEns.query.get_or_404(mod["ue_id"])
formations_set.add(ue["formation_id"]) ue_dict = ue.to_dict()
if ue["formation_id"] != mod["formation_id"]: formations_set.add(ue_dict["formation_id"])
if ue_dict["formation_id"] != mod["formation_id"]:
modimpl["mod"] = mod modimpl["mod"] = mod
modimpl["ue"] = ue modimpl["ue"] = ue_dict
bad_ue.append(modimpl) bad_ue.append(modimpl)
if sem["formation_id"] != mod["formation_id"]: if sem["formation_id"] != mod["formation_id"]:
bad_sem.append(modimpl) bad_sem.append(modimpl)
@ -3341,30 +3329,28 @@ def check_sem_integrity(formsemestre_id, fix=False):
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
@scodoc7func @scodoc7func
def check_form_integrity(formation_id, fix=False): def check_form_integrity(formation_id, fix=False):
"debug" "debug (obsolete)"
log("check_form_integrity: formation_id=%s fix=%s" % (formation_id, fix)) log(f"check_form_integrity: formation_id={formation_id} fix={fix}")
ues = sco_edit_ue.ue_list(args={"formation_id": formation_id}) formation: Formation = Formation.query.filter_by(
dept_id=g.scodoc_dept_id, formation_id=formation_id
).first_or_404()
bad = [] bad = []
for ue in ues: for ue in formation.ues:
mats = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]}) for matiere in ue.matieres:
for mat in mats: for mod in matiere.modules:
mods = sco_edit_module.module_list({"matiere_id": mat["matiere_id"]}) if mod.ue_id != ue.id:
for mod in mods:
if mod["ue_id"] != ue["ue_id"]:
if fix: if fix:
# fix mod.ue_id # fix mod.ue_id
log( log(f"fix: mod.ue_id = {ue.id} (was {mod.ue_id})")
"fix: mod.ue_id = %s (was %s)" % (ue["ue_id"], mod["ue_id"]) mod.ue_id = ue.id
) db.session.add(mod)
mod["ue_id"] = ue["ue_id"]
sco_edit_module.do_module_edit(mod)
bad.append(mod) bad.append(mod)
if mod["formation_id"] != formation_id: if mod.formation_id != formation_id:
bad.append(mod) bad.append(mod)
if bad: if bad:
txth = "<br>".join([str(x) for x in bad]) txth = "<br>".join([html.escape(str(x)) for x in bad])
txt = "\n".join([str(x) for x in bad]) txt = "\n".join([str(x) for x in bad])
log("check_form_integrity: formation_id=%s\ninconsistencies:" % formation_id) log(f"check_form_integrity: formation_id={formation_id}\ninconsistencies:")
log(txt) log(txt)
# Notify by e-mail # Notify by e-mail
send_scodoc_alarm("Notes: formation incoherente !", txt) send_scodoc_alarm("Notes: formation incoherente !", txt)
@ -3380,39 +3366,31 @@ def check_form_integrity(formation_id, fix=False):
@scodoc7func @scodoc7func
def check_formsemestre_integrity(formsemestre_id): def check_formsemestre_integrity(formsemestre_id):
"debug" "debug"
log("check_formsemestre_integrity: formsemestre_id=%s" % (formsemestre_id)) log(f"check_formsemestre_integrity: formsemestre_id={formsemestre_id}")
# verifie que tous les moduleimpl d'un formsemestre # verifie que tous les moduleimpl d'un formsemestre
# se réfèrent à un module dont l'UE appartient a la même formation # se réfèrent à un module dont l'UE appartient a la même formation
# Ancien bug: les ue_id étaient mal copiés lors des création de versions # Ancien bug: les ue_id étaient mal copiés lors des création de versions
# de formations # de formations
diag = [] diag = []
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id) for modimpl in formsemestre.modimpls:
for mod in Mlist: if modimpl.module.ue_id != modimpl.module.matiere.ue_id:
if mod["module"]["ue_id"] != mod["matiere"]["ue_id"]:
diag.append( diag.append(
"moduleimpl %s: module.ue_id=%s != matiere.ue_id=%s" f"""moduleimpl {modimpl.id}: module.ue_id={modimpl.module.ue_id
% ( } != matiere.ue_id={modimpl.module.matiere.ue_id}"""
mod["moduleimpl_id"],
mod["module"]["ue_id"],
mod["matiere"]["ue_id"],
)
) )
if mod["ue"]["formation_id"] != mod["module"]["formation_id"]: if modimpl.module.ue.formation_id != modimpl.module.formation_id:
diag.append( diag.append(
"moduleimpl %s: ue.formation_id=%s != mod.formation_id=%s" f"""moduleimpl {modimpl.id}: ue.formation_id={
% ( modimpl.module.ue.formation_id} != mod.formation_id={
mod["moduleimpl_id"], modimpl.module.formation_id}"""
mod["ue"]["formation_id"],
mod["module"]["formation_id"],
)
) )
if diag: if diag:
send_scodoc_alarm( send_scodoc_alarm(
"Notes: formation incoherente dans semestre %s !" % formsemestre_id, f"Notes: formation incoherente dans semestre {formsemestre_id} !",
"\n".join(diag), "\n".join(diag),
) )
log("check_formsemestre_integrity: formsemestre_id=%s" % formsemestre_id) log(f"check_formsemestre_integrity: formsemestre_id={formsemestre_id}")
log("inconsistencies:\n" + "\n".join(diag)) log("inconsistencies:\n" + "\n".join(diag))
else: else:
diag = ["OK"] diag = ["OK"]

View File

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

View File

@ -249,37 +249,7 @@ def test_formations(test_client):
assert len(lim_modid) == 1 assert len(lim_modid) == 1
lim_modimpl_id = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id) lim_modimpl_id = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
# print(lim_modimpl_id) assert lim_modid == lim_modimpl_id
# ---- Test de moduleimpl_withmodule_list
assert lim_modid == lim_modimpl_id # doit etre le meme resultat
liimp_sem1 = sco_moduleimpl.moduleimpl_withmodule_list(
formsemestre_id=sem1["formsemestre_id"]
)
assert len(liimp_sem1) == 2
assert module_id in (
liimp_sem1[0]["module_id"],
liimp_sem1[1]["module_id"],
)
assert module_id2 in (
liimp_sem1[0]["module_id"],
liimp_sem1[1]["module_id"],
)
liimp_sem2 = sco_moduleimpl.moduleimpl_withmodule_list(
formsemestre_id=sem2["formsemestre_id"]
)
assert module_id_t == liimp_sem2[0]["module_id"]
liimp_modid = sco_moduleimpl.moduleimpl_withmodule_list(module_id=module_id)
assert len(liimp_modid) == 1
liimp_modimplid = sco_moduleimpl.moduleimpl_withmodule_list(
moduleimpl_id=moduleimpl_id
)
assert liimp_modid == liimp_modimplid
# --- Suppression du module, matiere et ue test du semestre 2 # --- Suppression du module, matiere et ue test du semestre 2

View File

@ -54,7 +54,7 @@ RELEASE=1
ARCH="amd64" ARCH="amd64"
FACTORY_DIR="/opt/factory" FACTORY_DIR="/opt/factory"
DEST_DIR="$PACKAGE_NAME"_"$VERSION"-"$RELEASE"_"$ARCH" DEST_DIR="$PACKAGE_NAME"_"$VERSION"-"$RELEASE"_"$ARCH"
GIT_RELEASE_URL="https://scodoc.org/git/viennet/ScoDoc/archive/${RELEASE_TAG}.tar.gz" GIT_RELEASE_URL="https://scodoc.org/git/ScoDoc/ScoDoc/archive/${RELEASE_TAG}.tar.gz"
UNIT_TESTS_DIR="/opt/scodoc" # on lance les tests dans le rep. de travail, pas idéal UNIT_TESTS_DIR="/opt/scodoc" # on lance les tests dans le rep. de travail, pas idéal
echo "Le paquet sera $DEST_DIR.deb" echo "Le paquet sera $DEST_DIR.deb"