forked from ScoDoc/ScoDoc
Merge branch 'master' into feuille_jury
This commit is contained in:
commit
d31d24388f
@ -8,16 +8,17 @@
|
||||
API : accès aux étudiants
|
||||
"""
|
||||
from datetime import datetime
|
||||
from operator import attrgetter
|
||||
|
||||
from flask import g, request
|
||||
from flask_json import as_json
|
||||
from flask_login import current_user
|
||||
from flask_login import login_required
|
||||
from sqlalchemy import desc, or_
|
||||
from sqlalchemy import desc, func, or_
|
||||
from sqlalchemy.dialects.postgresql import VARCHAR
|
||||
|
||||
import app
|
||||
from app.api import api_bp as bp, api_web_bp
|
||||
from app.scodoc.sco_utils import json_error
|
||||
from app.api import tools
|
||||
from app.decorators import scodoc, permission_required
|
||||
from app.models import (
|
||||
@ -31,6 +32,8 @@ from app.scodoc import sco_bulletins
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc.sco_bulletins import do_formsemestre_bulletinetud
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_utils import json_error, suppress_accents
|
||||
|
||||
|
||||
# Un exemple:
|
||||
# @bp.route("/api_function/<int:arg>")
|
||||
@ -164,12 +167,39 @@ def etudiants(etudid: int = None, nip: str = None, ine: str = None):
|
||||
)
|
||||
if not None in allowed_depts:
|
||||
# restreint aux départements autorisés:
|
||||
etuds = etuds.join(Departement).filter(
|
||||
query = query.join(Departement).filter(
|
||||
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
||||
)
|
||||
return [etud.to_dict_api() for etud in query]
|
||||
|
||||
|
||||
@bp.route("/etudiants/name/<string:start>")
|
||||
@api_web_bp.route("/etudiants/name/<string:start>")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def etudiants_by_name(start: str = "", min_len=3, limit=32):
|
||||
"""Liste des étudiants dont le nom débute par start.
|
||||
Si start fait moins de min_len=3 caractères, liste vide.
|
||||
La casse et les accents sont ignorés.
|
||||
"""
|
||||
if len(start) < min_len:
|
||||
return []
|
||||
start = suppress_accents(start).lower()
|
||||
query = Identite.query.filter(
|
||||
func.lower(func.unaccent(Identite.nom, type_=VARCHAR)).ilike(start + "%")
|
||||
)
|
||||
allowed_depts = current_user.get_depts_with_permission(Permission.ScoView)
|
||||
if not None in allowed_depts:
|
||||
# restreint aux départements autorisés:
|
||||
query = query.join(Departement).filter(
|
||||
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
||||
)
|
||||
etuds = query.order_by(Identite.nom, Identite.prenom).limit(limit)
|
||||
# Note: on raffine le tri pour les caractères spéciaux et nom usuel ici:
|
||||
return [etud.to_dict_api() for etud in sorted(etuds, key=attrgetter("sort_key"))]
|
||||
|
||||
|
||||
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
|
||||
@bp.route("/etudiant/nip/<string:nip>/formsemestres")
|
||||
@bp.route("/etudiant/ine/<string:ine>/formsemestres")
|
||||
|
@ -8,7 +8,7 @@
|
||||
ScoDoc 9 API : accès aux évaluations
|
||||
"""
|
||||
|
||||
from flask import g
|
||||
from flask import g, request
|
||||
from flask_json import as_json
|
||||
from flask_login import login_required
|
||||
|
||||
@ -17,7 +17,7 @@ import app
|
||||
from app.api import api_bp as bp, api_web_bp
|
||||
from app.decorators import scodoc, permission_required
|
||||
from app.models import Evaluation, ModuleImpl, FormSemestre
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_evaluation_db, sco_saisie_notes
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
@ -28,7 +28,7 @@ import app.scodoc.sco_utils as scu
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def the_eval(evaluation_id: int):
|
||||
def evaluation(evaluation_id: int):
|
||||
"""Description d'une évaluation.
|
||||
|
||||
{
|
||||
@ -93,24 +93,22 @@ def evaluations(moduleimpl_id: int):
|
||||
@as_json
|
||||
def evaluation_notes(evaluation_id: int):
|
||||
"""
|
||||
Retourne la liste des notes à partir de l'id d'une évaluation donnée
|
||||
Retourne la liste des notes de l'évaluation
|
||||
|
||||
evaluation_id : l'id d'une évaluation
|
||||
evaluation_id : l'id de l'évaluation
|
||||
|
||||
Exemple de résultat :
|
||||
{
|
||||
"1": {
|
||||
"id": 1,
|
||||
"etudid": 10,
|
||||
"11": {
|
||||
"etudid": 11,
|
||||
"evaluation_id": 1,
|
||||
"value": 15.0,
|
||||
"comment": "",
|
||||
"date": "Wed, 20 Apr 2022 06:49:05 GMT",
|
||||
"uid": 2
|
||||
},
|
||||
"2": {
|
||||
"id": 2,
|
||||
"etudid": 1,
|
||||
"12": {
|
||||
"etudid": 12,
|
||||
"evaluation_id": 1,
|
||||
"value": 12.0,
|
||||
"comment": "",
|
||||
@ -128,8 +126,8 @@ def evaluation_notes(evaluation_id: int):
|
||||
.filter_by(dept_id=g.scodoc_dept_id)
|
||||
)
|
||||
|
||||
the_eval = query.first_or_404()
|
||||
dept = the_eval.moduleimpl.formsemestre.departement
|
||||
evaluation = query.first_or_404()
|
||||
dept = evaluation.moduleimpl.formsemestre.departement
|
||||
app.set_sco_dept(dept.acronym)
|
||||
|
||||
notes = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
@ -137,7 +135,49 @@ def evaluation_notes(evaluation_id: int):
|
||||
# "ABS", "EXC", etc mais laisse les notes sur le barème de l'éval.
|
||||
note = notes[etudid]
|
||||
note["value"] = scu.fmt_note(note["value"], keep_numeric=True)
|
||||
note["note_max"] = the_eval.note_max
|
||||
note["note_max"] = evaluation.note_max
|
||||
del note["id"]
|
||||
|
||||
return notes
|
||||
# in JS, keys must be string, not integers
|
||||
return {str(etudid): note for etudid, note in notes.items()}
|
||||
|
||||
|
||||
@bp.route("/evaluation/<int:evaluation_id>/notes/set", methods=["POST"])
|
||||
@api_web_bp.route("/evaluation/<int:evaluation_id>/notes/set", methods=["POST"])
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoEnsView)
|
||||
@as_json
|
||||
def evaluation_set_notes(evaluation_id: int):
|
||||
"""Écriture de notes dans une évaluation.
|
||||
The request content type should be "application/json",
|
||||
and contains:
|
||||
{
|
||||
'notes' : [ [etudid, value], ... ],
|
||||
'comment' : optional string
|
||||
}
|
||||
Result:
|
||||
- nb_changed: nombre de notes changées
|
||||
- nb_suppress: nombre de notes effacées
|
||||
- etudids_with_decision: liste des etudiants dont la note a changé
|
||||
alors qu'ils ont une décision de jury enregistrée.
|
||||
"""
|
||||
query = Evaluation.query.filter_by(id=evaluation_id)
|
||||
if g.scodoc_dept:
|
||||
query = (
|
||||
query.join(ModuleImpl)
|
||||
.join(FormSemestre)
|
||||
.filter_by(dept_id=g.scodoc_dept_id)
|
||||
)
|
||||
evaluation = query.first_or_404()
|
||||
dept = evaluation.moduleimpl.formsemestre.departement
|
||||
app.set_sco_dept(dept.acronym)
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
notes = data.get("notes")
|
||||
if notes is None:
|
||||
return scu.json_error(404, "no notes")
|
||||
if not isinstance(notes, list):
|
||||
return scu.json_error(404, "invalid notes argument (must be a list)")
|
||||
return sco_saisie_notes.save_notes(
|
||||
evaluation, notes, comment=data.get("comment", "")
|
||||
)
|
||||
|
@ -18,7 +18,7 @@ import pandas as pd
|
||||
|
||||
from flask import g
|
||||
|
||||
from app.scodoc.codes_cursus import UE_SPORT
|
||||
from app.scodoc.codes_cursus import UE_SPORT, UE_STANDARD
|
||||
from app.scodoc.codes_cursus import CursusDUT, CursusDUTMono
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
|
||||
@ -740,6 +740,7 @@ class BonusGrenobleIUT1(BonusSportMultiplicatif):
|
||||
|
||||
name = "bonus_iut1grenoble_2017"
|
||||
displayed_name = "IUT de Grenoble 1"
|
||||
|
||||
# C'est un bonus "multiplicatif": on l'exprime en additif,
|
||||
# sur chaque moyenne d'UE m_0
|
||||
# Augmenter de 5% correspond à multiplier par a=1.05
|
||||
@ -782,6 +783,7 @@ class BonusIUTRennes1(BonusSportAdditif):
|
||||
seuil_moy_gen = 10.0
|
||||
proportion_point = 1 / 20.0
|
||||
classic_use_bonus_ues = False
|
||||
|
||||
# S'applique aussi en classic, sur la moy. gen.
|
||||
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||
"""calcul du bonus"""
|
||||
@ -1336,6 +1338,7 @@ class BonusStNazaire(BonusSport):
|
||||
classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP
|
||||
amplitude = 0.01 / 4 # 4pt => 1%
|
||||
factor_max = 0.1 # 10% max
|
||||
|
||||
# Modifié 2022-11-29: calculer chaque bonus
|
||||
# (de 1 à 3 modules) séparément.
|
||||
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||
@ -1533,6 +1536,63 @@ class BonusIUTV(BonusSportAdditif):
|
||||
# c'est le bonus par défaut: aucune méthode à surcharger
|
||||
|
||||
|
||||
# Finalement inutile: un bonus direct est mieux adapté à leurs besoins.
|
||||
# # class BonusMastersUSPNIG(BonusSportAdditif):
|
||||
# """Calcul bonus modules optionnels (sport, culture), règle Masters de l'Institut Galilée (USPN)
|
||||
|
||||
# Les étudiants peuvent suivre des enseignements optionnels
|
||||
# de l'USPN (sports, musique, deuxième langue, culture, etc) dans une
|
||||
# UE libre. Les points au-dessus de 10 sur 20 obtenus dans cette UE
|
||||
# libre sont ajoutés au total des points obtenus pour les UE obligatoires
|
||||
# du semestre concerné.
|
||||
# """
|
||||
|
||||
# name = "bonus_masters__uspn_ig"
|
||||
# displayed_name = "Masters de l'Institut Galilée (USPN)"
|
||||
# proportion_point = 1.0
|
||||
# seuil_moy_gen = 10.0
|
||||
|
||||
# def __init__(
|
||||
# self,
|
||||
# formsemestre: "FormSemestre",
|
||||
# sem_modimpl_moys: np.array,
|
||||
# ues: list,
|
||||
# modimpl_inscr_df: pd.DataFrame,
|
||||
# modimpl_coefs: np.array,
|
||||
# etud_moy_gen,
|
||||
# etud_moy_ue,
|
||||
# ):
|
||||
# # Pour ce bonus, il nous faut la somme des coefs des modules non bonus
|
||||
# # du formsemestre (et non auxquels les étudiants sont inscrits !)
|
||||
# self.sum_coefs = sum(
|
||||
# [
|
||||
# m.module.coefficient
|
||||
# for m in formsemestre.modimpls_sorted
|
||||
# if (m.module.module_type == ModuleType.STANDARD)
|
||||
# and (m.module.ue.type == UE_STANDARD)
|
||||
# ]
|
||||
# )
|
||||
# super().__init__(
|
||||
# formsemestre,
|
||||
# sem_modimpl_moys,
|
||||
# ues,
|
||||
# modimpl_inscr_df,
|
||||
# modimpl_coefs,
|
||||
# etud_moy_gen,
|
||||
# etud_moy_ue,
|
||||
# )
|
||||
# # Bonus sur la moyenne générale seulement
|
||||
# # On a dans bonus_moy_arr le bonus additif classique
|
||||
# # Sa valeur sera appliquée comme moy_gen += bonus_moy_gen
|
||||
# # or ici on veut
|
||||
# # moy_gen = (somme des notes + bonus_moy_arr) / somme des coefs
|
||||
# # moy_gen += bonus_moy_arr / somme des coefs
|
||||
|
||||
# self.bonus_moy_gen = (
|
||||
# None if self.bonus_moy_gen is None else self.bonus_moy_gen / self.sum_coefs
|
||||
# )
|
||||
|
||||
|
||||
def get_bonus_class_dict(start=BonusSport, d=None):
|
||||
"""Dictionnaire des classes de bonus
|
||||
(liste les sous-classes de BonusSport ayant un nom)
|
||||
|
@ -288,7 +288,7 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
||||
if ref_comp is None:
|
||||
return set()
|
||||
if parcour_id is None:
|
||||
ues_ids = {ue.id for ue in self.ues}
|
||||
ues_ids = {ue.id for ue in self.ues if ue.type != UE_SPORT}
|
||||
else:
|
||||
parcour: ApcParcours = ApcParcours.query.get(parcour_id)
|
||||
annee = (self.formsemestre.semestre_id + 1) // 2
|
||||
@ -306,12 +306,12 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
||||
|
||||
return ues_ids
|
||||
|
||||
def etud_has_decision(self, etudid):
|
||||
def etud_has_decision(self, etudid) -> bool:
|
||||
"""True s'il y a une décision de jury pour cet étudiant émanant de ce formsemestre.
|
||||
prend aussi en compte les autorisations de passage.
|
||||
Sous-classée en BUT pour les RCUEs et années.
|
||||
"""
|
||||
return (
|
||||
return bool(
|
||||
super().etud_has_decision(etudid)
|
||||
or ApcValidationAnnee.query.filter_by(
|
||||
formsemestre_id=self.formsemestre.id, etudid=etudid
|
||||
|
@ -283,12 +283,12 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
]
|
||||
return etudids
|
||||
|
||||
def etud_has_decision(self, etudid):
|
||||
def etud_has_decision(self, etudid) -> bool:
|
||||
"""True s'il y a une décision de jury pour cet étudiant émanant de ce formsemestre.
|
||||
prend aussi en compte les autorisations de passage.
|
||||
Sous-classée en BUT pour les RCUEs et années.
|
||||
"""
|
||||
return (
|
||||
return bool(
|
||||
self.get_etud_decisions_ue(etudid)
|
||||
or self.get_etud_decision_sem(etudid)
|
||||
or ScolarAutorisationInscription.query.filter_by(
|
||||
|
@ -36,6 +36,7 @@ from sqlalchemy import text
|
||||
from wtforms import (
|
||||
BooleanField,
|
||||
DateField,
|
||||
DecimalField,
|
||||
FieldList,
|
||||
FormField,
|
||||
HiddenField,
|
||||
@ -122,13 +123,13 @@ class EntrepriseCreationForm(FlaskForm):
|
||||
origine = _build_string_field("Origine du correspondant", required=False)
|
||||
notes = _build_string_field("Notes sur le correspondant", required=False)
|
||||
|
||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
||||
submit = SubmitField("Enregistrer", render_kw=SUBMIT_MARGE)
|
||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def validate(self):
|
||||
def validate(self, extra_validators=None):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
if not super().validate(extra_validators):
|
||||
return False
|
||||
|
||||
if EntreprisePreferences.get_check_siret() and self.siret.data != "":
|
||||
siret_data = self.siret.data.strip().replace(" ", "")
|
||||
@ -248,13 +249,13 @@ class SiteCreationForm(FlaskForm):
|
||||
codepostal = _build_string_field("Code postal (*)")
|
||||
ville = _build_string_field("Ville (*)")
|
||||
pays = _build_string_field("Pays", required=False)
|
||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
||||
submit = SubmitField("Enregistrer", render_kw=SUBMIT_MARGE)
|
||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def validate(self):
|
||||
def validate(self, extra_validators=None):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
if not super().validate(extra_validators):
|
||||
return False
|
||||
|
||||
site = EntrepriseSite.query.filter_by(
|
||||
entreprise_id=self.hidden_entreprise_id.data, nom=self.nom.data
|
||||
@ -278,10 +279,10 @@ class SiteModificationForm(FlaskForm):
|
||||
submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE)
|
||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def validate(self):
|
||||
def validate(self, extra_validators=None):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
if not super().validate(extra_validators):
|
||||
return False
|
||||
|
||||
site = EntrepriseSite.query.filter(
|
||||
EntrepriseSite.entreprise_id == self.hidden_entreprise_id.data,
|
||||
@ -326,7 +327,7 @@ class OffreCreationForm(FlaskForm):
|
||||
FileAllowed(["pdf", "docx"], "Fichier .pdf ou .docx uniquement"),
|
||||
],
|
||||
)
|
||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
||||
submit = SubmitField("Enregistrer", render_kw=SUBMIT_MARGE)
|
||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -344,10 +345,10 @@ class OffreCreationForm(FlaskForm):
|
||||
(dept.id, dept.acronym) for dept in Departement.query.all()
|
||||
]
|
||||
|
||||
def validate(self):
|
||||
def validate(self, extra_validators=None):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
if not super().validate(extra_validators):
|
||||
return False
|
||||
|
||||
if len(self.depts.data) < 1:
|
||||
self.depts.errors.append("Choisir au moins un département")
|
||||
@ -392,10 +393,10 @@ class OffreModificationForm(FlaskForm):
|
||||
(dept.id, dept.acronym) for dept in Departement.query.all()
|
||||
]
|
||||
|
||||
def validate(self):
|
||||
def validate(self, extra_validators=None):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
if not super().validate(extra_validators):
|
||||
return False
|
||||
|
||||
if len(self.depts.data) < 1:
|
||||
self.depts.errors.append("Choisir au moins un département")
|
||||
@ -442,10 +443,10 @@ class CorrespondantCreationForm(FlaskForm):
|
||||
"Notes", required=False, render_kw={"class": "form-control"}
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
def validate(self, extra_validators=None):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
if not super().validate(extra_validators):
|
||||
return False
|
||||
|
||||
if not self.telephone.data and not self.mail.data:
|
||||
msg = "Saisir un moyen de contact (mail ou téléphone)"
|
||||
@ -458,13 +459,13 @@ class CorrespondantCreationForm(FlaskForm):
|
||||
class CorrespondantsCreationForm(FlaskForm):
|
||||
hidden_site_id = HiddenField()
|
||||
correspondants = FieldList(FormField(CorrespondantCreationForm), min_entries=1)
|
||||
submit = SubmitField("Envoyer")
|
||||
submit = SubmitField("Enregistrer")
|
||||
cancel = SubmitField("Annuler")
|
||||
|
||||
def validate(self):
|
||||
def validate(self, extra_validators=None):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
if not super().validate(extra_validators):
|
||||
return False
|
||||
|
||||
correspondant_list = []
|
||||
for entry in self.correspondants.entries:
|
||||
@ -531,10 +532,10 @@ class CorrespondantModificationForm(FlaskForm):
|
||||
.all()
|
||||
]
|
||||
|
||||
def validate(self):
|
||||
def validate(self, extra_validators=None):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
if not super().validate(extra_validators):
|
||||
return False
|
||||
|
||||
correspondant = EntrepriseCorrespondant.query.filter(
|
||||
EntrepriseCorrespondant.id != self.hidden_correspondant_id.data,
|
||||
@ -566,7 +567,7 @@ class ContactCreationForm(FlaskForm):
|
||||
render_kw={"placeholder": "Tapez le nom de l'utilisateur"},
|
||||
)
|
||||
notes = TextAreaField("Notes (*)", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
||||
submit = SubmitField("Enregistrer", render_kw=SUBMIT_MARGE)
|
||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def validate_utilisateur(self, utilisateur):
|
||||
@ -613,8 +614,9 @@ class ContactModificationForm(FlaskForm):
|
||||
class StageApprentissageCreationForm(FlaskForm):
|
||||
etudiant = _build_string_field(
|
||||
"Étudiant (*)",
|
||||
render_kw={"placeholder": "Tapez le nom de l'étudiant"},
|
||||
render_kw={"placeholder": "Tapez le nom de l'étudiant", "autocomplete": "off"},
|
||||
)
|
||||
etudid = HiddenField()
|
||||
type_offre = SelectField(
|
||||
"Type de l'offre (*)",
|
||||
choices=[("Stage"), ("Alternance")],
|
||||
@ -627,12 +629,12 @@ class StageApprentissageCreationForm(FlaskForm):
|
||||
"Date fin (*)", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||
)
|
||||
notes = TextAreaField("Notes")
|
||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
||||
submit = SubmitField("Enregistrer", render_kw=SUBMIT_MARGE)
|
||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def validate(self):
|
||||
def validate(self, extra_validators=None):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
if not super().validate(extra_validators):
|
||||
validate = False
|
||||
|
||||
if (
|
||||
@ -646,64 +648,27 @@ class StageApprentissageCreationForm(FlaskForm):
|
||||
|
||||
return validate
|
||||
|
||||
def validate_etudiant(self, etudiant):
|
||||
etudiant_data = etudiant.data.upper().strip()
|
||||
stm = text(
|
||||
"SELECT id, CONCAT(nom, ' ', prenom) as nom_prenom FROM Identite WHERE CONCAT(nom, ' ', prenom)=:nom_prenom"
|
||||
)
|
||||
etudiant = (
|
||||
Identite.query.from_statement(stm).params(nom_prenom=etudiant_data).first()
|
||||
)
|
||||
def validate_etudid(self, field):
|
||||
"L'etudid doit avoit été placé par le JS"
|
||||
etudid = int(field.data) if field.data else None
|
||||
etudiant = Identite.query.get(etudid) if etudid is not None else None
|
||||
if etudiant is None:
|
||||
raise ValidationError("Champ incorrect (selectionnez dans la liste)")
|
||||
raise ValidationError("Étudiant introuvable (sélectionnez dans la liste)")
|
||||
|
||||
|
||||
class StageApprentissageModificationForm(FlaskForm):
|
||||
etudiant = _build_string_field(
|
||||
"Étudiant (*)",
|
||||
render_kw={"placeholder": "Tapez le nom de l'étudiant"},
|
||||
)
|
||||
type_offre = SelectField(
|
||||
"Type de l'offre (*)",
|
||||
choices=[("Stage"), ("Alternance")],
|
||||
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||
)
|
||||
date_debut = DateField(
|
||||
"Date début (*)", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||
)
|
||||
date_fin = DateField(
|
||||
"Date fin (*)", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||
)
|
||||
notes = TextAreaField("Notes")
|
||||
submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE)
|
||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||
class FrenchFloatField(StringField):
|
||||
"A field allowing to enter . or ,"
|
||||
|
||||
def validate(self):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
|
||||
if (
|
||||
self.date_debut.data
|
||||
and self.date_fin.data
|
||||
and self.date_debut.data > self.date_fin.data
|
||||
):
|
||||
self.date_debut.errors.append("Les dates sont incompatibles")
|
||||
self.date_fin.errors.append("Les dates sont incompatibles")
|
||||
validate = False
|
||||
|
||||
return validate
|
||||
|
||||
def validate_etudiant(self, etudiant):
|
||||
etudiant_data = etudiant.data.upper().strip()
|
||||
stm = text(
|
||||
"SELECT id, CONCAT(nom, ' ', prenom) as nom_prenom FROM Identite WHERE CONCAT(nom, ' ', prenom)=:nom_prenom"
|
||||
)
|
||||
etudiant = (
|
||||
Identite.query.from_statement(stm).params(nom_prenom=etudiant_data).first()
|
||||
)
|
||||
if etudiant is None:
|
||||
raise ValidationError("Champ incorrect (selectionnez dans la liste)")
|
||||
def process_formdata(self, valuelist):
|
||||
"catch incoming data"
|
||||
if not valuelist:
|
||||
return
|
||||
try:
|
||||
value = valuelist[0].replace(",", ".")
|
||||
self.data = float(value)
|
||||
except ValueError as exc:
|
||||
self.data = None
|
||||
raise ValueError(self.gettext("Not a valid decimal value.")) from exc
|
||||
|
||||
|
||||
class TaxeApprentissageForm(FlaskForm):
|
||||
@ -720,25 +685,26 @@ class TaxeApprentissageForm(FlaskForm):
|
||||
],
|
||||
default=int(datetime.now().strftime("%Y")),
|
||||
)
|
||||
montant = IntegerField(
|
||||
montant = FrenchFloatField(
|
||||
"Montant (*)",
|
||||
validators=[
|
||||
DataRequired(message=CHAMP_REQUIS),
|
||||
NumberRange(
|
||||
min=1,
|
||||
message="Le montant doit être supérieur à 0",
|
||||
),
|
||||
# NumberRange(
|
||||
# min=0.1,
|
||||
# max=1e8,
|
||||
# message="Le montant doit être supérieur à 0",
|
||||
# ),
|
||||
],
|
||||
default=1,
|
||||
)
|
||||
notes = TextAreaField("Notes")
|
||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
||||
submit = SubmitField("Enregistrer", render_kw=SUBMIT_MARGE)
|
||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def validate(self):
|
||||
def validate(self, extra_validators=None):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
if not super().validate(extra_validators):
|
||||
return False
|
||||
|
||||
taxe = EntrepriseTaxeApprentissage.query.filter_by(
|
||||
entreprise_id=self.hidden_entreprise_id.data, annee=self.annee.data
|
||||
@ -788,12 +754,12 @@ class EnvoiOffreForm(FlaskForm):
|
||||
submit = SubmitField("Envoyer")
|
||||
cancel = SubmitField("Annuler")
|
||||
|
||||
def validate(self):
|
||||
def validate(self, extra_validators=None):
|
||||
validate = True
|
||||
list_select = True
|
||||
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
if not super().validate(extra_validators):
|
||||
return False
|
||||
|
||||
for entry in self.responsables.entries:
|
||||
if entry.data:
|
||||
|
@ -164,7 +164,10 @@ class EntrepriseStageApprentissage(db.Model):
|
||||
entreprise_id = db.Column(
|
||||
db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade")
|
||||
)
|
||||
etudid = db.Column(db.Integer)
|
||||
etudid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("identite.id", ondelete="CASCADE"),
|
||||
)
|
||||
type_offre = db.Column(db.Text)
|
||||
date_debut = db.Column(db.Date)
|
||||
date_fin = db.Column(db.Date)
|
||||
@ -180,7 +183,7 @@ class EntrepriseTaxeApprentissage(db.Model):
|
||||
db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade")
|
||||
)
|
||||
annee = db.Column(db.Integer)
|
||||
montant = db.Column(db.Integer)
|
||||
montant = db.Column(db.Float)
|
||||
notes = db.Column(db.Text)
|
||||
|
||||
|
||||
|
@ -28,7 +28,6 @@ from app.entreprises.forms import (
|
||||
ContactCreationForm,
|
||||
ContactModificationForm,
|
||||
StageApprentissageCreationForm,
|
||||
StageApprentissageModificationForm,
|
||||
EnvoiOffreForm,
|
||||
AjoutFichierForm,
|
||||
TaxeApprentissageForm,
|
||||
@ -239,7 +238,7 @@ def delete_validation_entreprise(entreprise_id):
|
||||
text=f"Non validation de la fiche entreprise ({entreprise.nom})",
|
||||
)
|
||||
db.session.add(log)
|
||||
flash("L'entreprise a été supprimé de la liste des entreprise à valider.")
|
||||
flash("L'entreprise a été supprimée de la liste des entreprises à valider.")
|
||||
return redirect(url_for("entreprises.validation"))
|
||||
return render_template(
|
||||
"entreprises/form_confirmation.j2",
|
||||
@ -770,7 +769,7 @@ def delete_taxe_apprentissage(entreprise_id, taxe_id):
|
||||
)
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
flash("La taxe d'apprentissage a été supprimé de la liste.")
|
||||
flash("La taxe d'apprentissage a été supprimée de la liste.")
|
||||
return redirect(
|
||||
url_for("entreprises.fiche_entreprise", entreprise_id=taxe.entreprise_id)
|
||||
)
|
||||
@ -966,7 +965,7 @@ def delete_offre(entreprise_id, offre_id):
|
||||
)
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
flash("L'offre a été supprimé de la fiche entreprise.")
|
||||
flash("L'offre a été supprimée de la fiche entreprise.")
|
||||
return redirect(
|
||||
url_for("entreprises.fiche_entreprise", entreprise_id=offre.entreprise_id)
|
||||
)
|
||||
@ -1473,7 +1472,8 @@ def delete_contact(entreprise_id, contact_id):
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def add_stage_apprentissage(entreprise_id):
|
||||
"""
|
||||
Permet d'ajouter un étudiant ayant réalisé un stage ou une alternance sur la fiche entreprise de l'entreprise
|
||||
Permet d'ajouter un étudiant ayant réalisé un stage ou alternance
|
||||
sur la fiche de l'entreprise
|
||||
"""
|
||||
entreprise = Entreprise.query.filter_by(
|
||||
id=entreprise_id, visible=True
|
||||
@ -1484,15 +1484,8 @@ def add_stage_apprentissage(entreprise_id):
|
||||
url_for("entreprises.fiche_entreprise", entreprise_id=entreprise_id)
|
||||
)
|
||||
if form.validate_on_submit():
|
||||
etudiant_nomcomplet = form.etudiant.data.upper().strip()
|
||||
stm = text(
|
||||
"SELECT id, CONCAT(nom, ' ', prenom) as nom_prenom FROM Identite WHERE CONCAT(nom, ' ', prenom)=:nom_prenom"
|
||||
)
|
||||
etudiant = (
|
||||
Identite.query.from_statement(stm)
|
||||
.params(nom_prenom=etudiant_nomcomplet)
|
||||
.first()
|
||||
)
|
||||
etudid = form.etudid.data
|
||||
etudiant = Identite.query.get_or_404(etudid)
|
||||
formation = etudiant.inscription_courante_date(
|
||||
form.date_debut.data, form.date_fin.data
|
||||
)
|
||||
@ -1538,7 +1531,7 @@ def add_stage_apprentissage(entreprise_id):
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def edit_stage_apprentissage(entreprise_id, stage_apprentissage_id):
|
||||
"""
|
||||
Permet de modifier un étudiant ayant réalisé un stage ou une alternance sur la fiche entreprise de l'entreprise
|
||||
Permet de modifier un étudiant ayant réalisé un stage ou alternance sur la fiche de l'entreprise
|
||||
"""
|
||||
stage_apprentissage = EntrepriseStageApprentissage.query.filter_by(
|
||||
id=stage_apprentissage_id, entreprise_id=entreprise_id
|
||||
@ -1548,21 +1541,14 @@ def edit_stage_apprentissage(entreprise_id, stage_apprentissage_id):
|
||||
etudiant = Identite.query.filter_by(id=stage_apprentissage.etudid).first_or_404(
|
||||
description=f"etudiant {stage_apprentissage.etudid} inconnue"
|
||||
)
|
||||
form = StageApprentissageModificationForm()
|
||||
form = StageApprentissageCreationForm()
|
||||
if request.method == "POST" and form.cancel.data:
|
||||
return redirect(
|
||||
url_for("entreprises.fiche_entreprise", entreprise_id=entreprise_id)
|
||||
)
|
||||
if form.validate_on_submit():
|
||||
etudiant_nomcomplet = form.etudiant.data.upper().strip()
|
||||
stm = text(
|
||||
"SELECT id, CONCAT(nom, ' ', prenom) as nom_prenom FROM Identite WHERE CONCAT(nom, ' ', prenom)=:nom_prenom"
|
||||
)
|
||||
etudiant = (
|
||||
Identite.query.from_statement(stm)
|
||||
.params(nom_prenom=etudiant_nomcomplet)
|
||||
.first()
|
||||
)
|
||||
etudid = form.etudid.data
|
||||
etudiant = Identite.query.get_or_404(etudid)
|
||||
formation = etudiant.inscription_courante_date(
|
||||
form.date_debut.data, form.date_fin.data
|
||||
)
|
||||
@ -1577,6 +1563,7 @@ def edit_stage_apprentissage(entreprise_id, stage_apprentissage_id):
|
||||
formation.formsemestre.formsemestre_id if formation else None,
|
||||
)
|
||||
stage_apprentissage.notes = form.notes.data.strip()
|
||||
db.session.add(stage_apprentissage)
|
||||
log = EntrepriseHistorique(
|
||||
authenticated_user=current_user.user_name,
|
||||
entreprise_id=stage_apprentissage.entreprise_id,
|
||||
@ -1593,7 +1580,9 @@ def edit_stage_apprentissage(entreprise_id, stage_apprentissage_id):
|
||||
)
|
||||
)
|
||||
elif request.method == "GET":
|
||||
form.etudiant.data = f"{sco_etud.format_nom(etudiant.nom)} {sco_etud.format_prenom(etudiant.prenom)}"
|
||||
form.etudiant.data = f"""{sco_etud.format_nom(etudiant.nom)} {
|
||||
sco_etud.format_prenom(etudiant.prenom)}"""
|
||||
form.etudid.data = etudiant.id
|
||||
form.type_offre.data = stage_apprentissage.type_offre
|
||||
form.date_debut.data = stage_apprentissage.date_debut
|
||||
form.date_fin.data = stage_apprentissage.date_fin
|
||||
|
@ -43,8 +43,8 @@ class Identite(db.Model):
|
||||
"optionnel (si present, affiché à la place du nom)"
|
||||
civilite = db.Column(db.String(1), nullable=False)
|
||||
|
||||
# données d'état-civil. Si présent remplace les données d'usage dans les documents officiels (bulletins, PV)
|
||||
# cf nomprenom_etat_civil()
|
||||
# données d'état-civil. Si présent remplace les données d'usage dans les documents
|
||||
# officiels (bulletins, PV): voir nomprenom_etat_civil()
|
||||
civilite_etat_civil = db.Column(db.String(1), nullable=False, server_default="X")
|
||||
prenom_etat_civil = db.Column(db.Text(), nullable=False, server_default="")
|
||||
|
||||
@ -220,7 +220,7 @@ class Identite(db.Model):
|
||||
}
|
||||
args_dict = {}
|
||||
for key, value in args.items():
|
||||
if hasattr(cls, key):
|
||||
if hasattr(cls, key) and not isinstance(getattr(cls, key, None), property):
|
||||
# compat scodoc7 (mauvaise idée de l'époque)
|
||||
if key in fs_empty_stored_as_nulls and value == "":
|
||||
value = None
|
||||
|
@ -145,6 +145,18 @@ class Evaluation(db.Model):
|
||||
db.session.add(copy)
|
||||
return copy
|
||||
|
||||
def is_matin(self) -> bool:
|
||||
"Evaluation ayant lieu le matin (faux si pas de date)"
|
||||
heure_debut_dt = self.heure_debut or datetime.time(8, 00)
|
||||
# 8:00 au cas ou pas d'heure (note externe?)
|
||||
return bool(self.jour) and heure_debut_dt < datetime.time(12, 00)
|
||||
|
||||
def is_apresmidi(self) -> bool:
|
||||
"Evaluation ayant lieu l'après midi (faux si pas de date)"
|
||||
heure_debut_dt = self.heure_debut or datetime.time(8, 00)
|
||||
# 8:00 au cas ou pas d'heure (note externe?)
|
||||
return bool(self.jour) and heure_debut_dt >= datetime.time(12, 00)
|
||||
|
||||
def set_default_poids(self) -> bool:
|
||||
"""Initialize les poids bvers les UE à leurs valeurs par défaut
|
||||
C'est à dire à 1 si le coef. module/UE est non nul, 0 sinon.
|
||||
|
@ -782,6 +782,8 @@ class FormSemestre(db.Model):
|
||||
Les groupes de parcours sont ceux de la partition scu.PARTITION_PARCOURS
|
||||
et leur nom est le code du parcours (eg "Cyber").
|
||||
"""
|
||||
if self.formation.referentiel_competence_id is None:
|
||||
return # safety net
|
||||
partition = Partition.query.filter_by(
|
||||
formsemestre_id=self.id, partition_name=scu.PARTITION_PARCOURS
|
||||
).first()
|
||||
@ -805,7 +807,10 @@ class FormSemestre(db.Model):
|
||||
query = (
|
||||
ApcParcours.query.filter_by(code=group.group_name)
|
||||
.join(ApcReferentielCompetences)
|
||||
.filter_by(dept_id=g.scodoc_dept_id)
|
||||
.filter_by(
|
||||
dept_id=g.scodoc_dept_id,
|
||||
id=self.formation.referentiel_competence_id,
|
||||
)
|
||||
)
|
||||
if query.count() != 1:
|
||||
log(
|
||||
|
@ -55,7 +55,7 @@ class Module(db.Model):
|
||||
secondary=parcours_modules,
|
||||
lazy="subquery",
|
||||
backref=db.backref("modules", lazy=True),
|
||||
order_by="ApcParcours.numero",
|
||||
order_by="ApcParcours.numero, ApcParcours.code",
|
||||
)
|
||||
|
||||
app_critiques = db.relationship(
|
||||
|
@ -56,7 +56,7 @@ class NotesNotes(db.Model):
|
||||
"pour debug"
|
||||
from app.models.evaluations import Evaluation
|
||||
|
||||
return f"""<{self.__class__.__name__} {self.id} v={self.value} {self.date.isoformat()
|
||||
return f"""<{self.__class__.__name__} {self.id} etudid={self.etudid} v={self.value} {self.date.isoformat()
|
||||
} {Evaluation.query.get(self.evaluation_id) if self.evaluation_id else "X" }>"""
|
||||
|
||||
|
||||
|
@ -58,7 +58,10 @@ class UniteEns(db.Model):
|
||||
|
||||
# Une UE appartient soit à tous les parcours (tronc commun), soit à un sous-ensemble
|
||||
parcours = db.relationship(
|
||||
ApcParcours, secondary="ue_parcours", backref=db.backref("ues", lazy=True)
|
||||
ApcParcours,
|
||||
secondary="ue_parcours",
|
||||
backref=db.backref("ues", lazy=True),
|
||||
order_by="ApcParcours.numero, ApcParcours.code",
|
||||
)
|
||||
|
||||
# relations
|
||||
|
@ -70,7 +70,7 @@ from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import Departement, FormSemestre
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc.sco_exceptions import ScoPermissionDenied
|
||||
from app.scodoc.sco_exceptions import ScoException, ScoPermissionDenied
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_groups
|
||||
@ -125,6 +125,12 @@ class BaseArchiver(object):
|
||||
if not os.path.isdir(obj_dir):
|
||||
log(f"creating directory {obj_dir}")
|
||||
os.mkdir(obj_dir)
|
||||
except FileExistsError as exc:
|
||||
raise ScoException(
|
||||
f"""BaseArchiver error: obj_dir={obj_dir} exists={
|
||||
os.path.exists(obj_dir)
|
||||
} isdir={os.path.isdir(obj_dir)}"""
|
||||
) from exc
|
||||
finally:
|
||||
scu.GSL.release()
|
||||
return obj_dir
|
||||
|
@ -62,7 +62,9 @@ def format_etud_ident(etud):
|
||||
else:
|
||||
etud["prenom_etat_civil"] = ""
|
||||
etud["civilite_str"] = format_civilite(etud["civilite"])
|
||||
etud["civilite_etat_civil_str"] = format_civilite(etud["civilite_etat_civil"])
|
||||
etud["civilite_etat_civil_str"] = format_civilite(
|
||||
etud.get("civilite_etat_civil", "X")
|
||||
)
|
||||
# Nom à afficher:
|
||||
if etud["nom_usuel"]:
|
||||
etud["nom_disp"] = etud["nom_usuel"]
|
||||
@ -145,7 +147,7 @@ def format_civilite(civilite):
|
||||
|
||||
def format_etat_civil(etud: dict):
|
||||
if etud["prenom_etat_civil"]:
|
||||
civ = {"M": "M.", "F": "Mme", "X": ""}[etud["civilite_etat_civil"]]
|
||||
civ = {"M": "M.", "F": "Mme", "X": ""}[etud.get("civilite_etat_civil", "X")]
|
||||
return f'{civ} {etud["prenom_etat_civil"]} {etud["nom"]}'
|
||||
else:
|
||||
return etud["nomprenom"]
|
||||
|
@ -252,12 +252,11 @@ def do_evaluation_delete(evaluation_id):
|
||||
def do_evaluation_get_all_notes(
|
||||
evaluation_id, table="notes_notes", filter_suppressed=True, by_uid=None
|
||||
):
|
||||
"""Toutes les notes pour une evaluation: { etudid : { 'value' : value, 'date' : date ... }}
|
||||
"""Toutes les notes pour une évaluation: { etudid : { 'value' : value, 'date' : date ... }}
|
||||
Attention: inclut aussi les notes des étudiants qui ne sont plus inscrits au module.
|
||||
"""
|
||||
do_cache = (
|
||||
filter_suppressed and table == "notes_notes" and (by_uid is None)
|
||||
) # pas de cache pour (rares) appels via undo_notes ou specifiant un enseignant
|
||||
# pas de cache pour (rares) appels via undo_notes ou specifiant un enseignant
|
||||
do_cache = filter_suppressed and table == "notes_notes" and (by_uid is None)
|
||||
if do_cache:
|
||||
r = sco_cache.EvaluationCache.get(evaluation_id)
|
||||
if r is not None:
|
||||
|
@ -433,7 +433,7 @@ def excel_simple_table(
|
||||
return ws.generate()
|
||||
|
||||
|
||||
def excel_feuille_saisie(e, titreannee, description, lines):
|
||||
def excel_feuille_saisie(evaluation: "Evaluation", titreannee, description, lines):
|
||||
"""Genere feuille excel pour saisie des notes.
|
||||
E: evaluation (dict)
|
||||
lines: liste de tuples
|
||||
@ -512,18 +512,20 @@ def excel_feuille_saisie(e, titreannee, description, lines):
|
||||
# description evaluation
|
||||
ws.append_single_cell_row(scu.unescape_html(description), style_titres)
|
||||
ws.append_single_cell_row(
|
||||
"Evaluation du %s (coef. %g)" % (e["jour"], e["coefficient"]), style
|
||||
"Evaluation du %s (coef. %g)"
|
||||
% (evaluation.jour or "sans date", evaluation.coefficient or 0.0),
|
||||
style,
|
||||
)
|
||||
# ligne blanche
|
||||
ws.append_blank_row()
|
||||
# code et titres colonnes
|
||||
ws.append_row(
|
||||
[
|
||||
ws.make_cell("!%s" % e["evaluation_id"], style_ro),
|
||||
ws.make_cell("!%s" % evaluation.id, style_ro),
|
||||
ws.make_cell("Nom", style_titres),
|
||||
ws.make_cell("Prénom", style_titres),
|
||||
ws.make_cell("Groupe", style_titres),
|
||||
ws.make_cell("Note sur %g" % e["note_max"], style_titres),
|
||||
ws.make_cell("Note sur %g" % (evaluation.note_max or 0.0), style_titres),
|
||||
ws.make_cell("Remarque", style_titres),
|
||||
]
|
||||
)
|
||||
|
@ -1333,11 +1333,18 @@ Ceci n'est possible que si :
|
||||
cancelbutton="Annuler",
|
||||
)
|
||||
if tf[0] == 0:
|
||||
if formsemestre_has_decisions_or_compensations(formsemestre):
|
||||
has_decisions, message = formsemestre_has_decisions_or_compensations(
|
||||
formsemestre
|
||||
)
|
||||
if has_decisions:
|
||||
H.append(
|
||||
"""<p><b>Ce semestre ne peut pas être supprimé !
|
||||
(il y a des décisions de jury ou des compensations par d'autres semestres)</b>
|
||||
</p>"""
|
||||
f"""<p><b>Ce semestre ne peut pas être supprimé !</b></p>
|
||||
<p>il y a des décisions de jury ou des compensations par d'autres semestres:
|
||||
</p>
|
||||
<ul>
|
||||
<li>{message}</li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
else:
|
||||
H.append(tf[1])
|
||||
@ -1372,32 +1379,46 @@ def formsemestre_delete2(formsemestre_id, dialog_confirmed=False):
|
||||
return flask.redirect(scu.ScoURL())
|
||||
|
||||
|
||||
def formsemestre_has_decisions_or_compensations(formsemestre: FormSemestre):
|
||||
def formsemestre_has_decisions_or_compensations(
|
||||
formsemestre: FormSemestre,
|
||||
) -> tuple[bool, str]:
|
||||
"""True if decision de jury (sem. UE, RCUE, année) émanant de ce semestre
|
||||
ou compensation de ce semestre par d'autres semestres
|
||||
ou autorisations de passage.
|
||||
"""
|
||||
# Validations de semestre ou d'UEs
|
||||
if ScolarFormSemestreValidation.query.filter_by(
|
||||
nb_validations = ScolarFormSemestreValidation.query.filter_by(
|
||||
formsemestre_id=formsemestre.id
|
||||
).count():
|
||||
return True
|
||||
if ScolarFormSemestreValidation.query.filter_by(
|
||||
).count()
|
||||
if nb_validations:
|
||||
return True, f"{nb_validations} validations de semestre ou d'UE"
|
||||
nb_validations = ScolarFormSemestreValidation.query.filter_by(
|
||||
compense_formsemestre_id=formsemestre.id
|
||||
).count():
|
||||
return True
|
||||
).count()
|
||||
if nb_validations:
|
||||
return True, f"{nb_validations} compensations utilisées dans d'autres semestres"
|
||||
# Autorisations d'inscription:
|
||||
if ScolarAutorisationInscription.query.filter_by(
|
||||
nb_validations = ScolarAutorisationInscription.query.filter_by(
|
||||
origin_formsemestre_id=formsemestre.id
|
||||
).count():
|
||||
return True
|
||||
).count()
|
||||
if nb_validations:
|
||||
return (
|
||||
True,
|
||||
f"{nb_validations} autorisations d'inscriptions émanant de ce semestre",
|
||||
)
|
||||
# Validations d'années BUT
|
||||
if ApcValidationAnnee.query.filter_by(formsemestre_id=formsemestre.id).count():
|
||||
return True
|
||||
nb_validations = ApcValidationAnnee.query.filter_by(
|
||||
formsemestre_id=formsemestre.id
|
||||
).count()
|
||||
if nb_validations:
|
||||
return True, f"{nb_validations} validations d'année BUT utilisant ce semestre"
|
||||
# Validations de RCUEs
|
||||
if ApcValidationRCUE.query.filter_by(formsemestre_id=formsemestre.id).count():
|
||||
return True
|
||||
return False
|
||||
nb_validations = ApcValidationRCUE.query.filter_by(
|
||||
formsemestre_id=formsemestre.id
|
||||
).count()
|
||||
if nb_validations:
|
||||
return True, f"{nb_validations} validations de RCUE utilisant ce semestre"
|
||||
return False, ""
|
||||
|
||||
|
||||
def do_formsemestre_delete(formsemestre_id):
|
||||
|
@ -175,9 +175,7 @@ def do_formsemestre_demission(
|
||||
)
|
||||
db.session.add(event)
|
||||
db.session.commit()
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=formsemestre_id
|
||||
) # > démission ou défaillance
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
||||
if etat_new == scu.DEMISSION:
|
||||
flash("Démission enregistrée")
|
||||
elif etat_new == scu.DEF:
|
||||
@ -210,7 +208,7 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
|
||||
|
||||
if nt.etud_has_decision(etudid):
|
||||
raise ScoValueError(
|
||||
"""désinscription impossible: l'étudiant {etud.nomprenom} a
|
||||
f"""désinscription impossible: l'étudiant {etud.nomprenom} a
|
||||
une décision de jury (la supprimer avant si nécessaire)"""
|
||||
)
|
||||
|
||||
|
@ -600,6 +600,7 @@ def formsemestre_description_table(
|
||||
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
||||
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
||||
).first_or_404()
|
||||
is_apc = formsemestre.formation.is_apc()
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
|
||||
parcours = codes_cursus.get_cursus_from_code(formsemestre.formation.type_parcours)
|
||||
@ -613,7 +614,7 @@ def formsemestre_description_table(
|
||||
else:
|
||||
ues = formsemestre.get_ues()
|
||||
columns_ids += [f"ue_{ue.id}" for ue in ues]
|
||||
if sco_preferences.get_preference("bul_show_ects", formsemestre_id):
|
||||
if sco_preferences.get_preference("bul_show_ects", formsemestre_id) and not is_apc:
|
||||
columns_ids += ["ects"]
|
||||
columns_ids += ["Inscrits", "Responsable", "Enseignants"]
|
||||
if with_evals:
|
||||
@ -640,6 +641,7 @@ def formsemestre_description_table(
|
||||
sum_coef = 0
|
||||
sum_ects = 0
|
||||
last_ue_id = None
|
||||
formsemestre_parcours_ids = {p.id for p in formsemestre.parcours}
|
||||
for modimpl in formsemestre.modimpls_sorted:
|
||||
# Ligne UE avec ECTS:
|
||||
ue = modimpl.module.ue
|
||||
@ -666,7 +668,7 @@ def formsemestre_description_table(
|
||||
ue_info[
|
||||
f"_{k}_td_attrs"
|
||||
] = f'style="background-color: {ue.color} !important;"'
|
||||
if not formsemestre.formation.is_apc():
|
||||
if not is_apc:
|
||||
# n'affiche la ligne UE qu'en formation classique
|
||||
# car l'UE de rattachement n'a pas d'intérêt en BUT
|
||||
rows.append(ue_info)
|
||||
@ -707,8 +709,17 @@ def formsemestre_description_table(
|
||||
for ue in ues:
|
||||
row[f"ue_{ue.id}"] = coef_dict.get(ue.id, 0.0) or ""
|
||||
if with_parcours:
|
||||
# Intersection des parcours du module avec ceux du formsemestre
|
||||
row["parcours"] = ", ".join(
|
||||
sorted([pa.code for pa in modimpl.module.parcours])
|
||||
[
|
||||
pa.code
|
||||
for pa in (
|
||||
modimpl.module.parcours
|
||||
if modimpl.module.parcours
|
||||
else modimpl.formsemestre.parcours
|
||||
)
|
||||
if pa.id in formsemestre_parcours_ids
|
||||
]
|
||||
)
|
||||
|
||||
rows.append(row)
|
||||
|
@ -28,7 +28,7 @@
|
||||
"""Opérations d'inscriptions aux modules (interface pour gérer options ou parcours)
|
||||
"""
|
||||
import collections
|
||||
from operator import itemgetter
|
||||
from operator import attrgetter
|
||||
|
||||
import flask
|
||||
from flask import url_for, g, request
|
||||
@ -553,8 +553,11 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
|
||||
>{etud.nomprenom}</a></td>"""
|
||||
)
|
||||
# Parcours:
|
||||
group = partition_parcours.get_etud_group(etud.id)
|
||||
parcours_name = group.group_name if group else ""
|
||||
if partition_parcours:
|
||||
group = partition_parcours.get_etud_group(etud.id)
|
||||
parcours_name = group.group_name if group else ""
|
||||
else:
|
||||
parcours_name = ""
|
||||
H.append(f"""<td class="parcours">{parcours_name}</td>""")
|
||||
# UEs:
|
||||
for ue in ues:
|
||||
@ -668,7 +671,7 @@ def descr_inscrs_module(moduleimpl_id, set_all, partitions):
|
||||
gr.append((partition["partition_name"], grp))
|
||||
#
|
||||
d = []
|
||||
for (partition_name, grp) in gr:
|
||||
for partition_name, grp in gr:
|
||||
if grp:
|
||||
d.append("groupes de %s: %s" % (partition_name, ", ".join(grp)))
|
||||
r = []
|
||||
@ -680,25 +683,25 @@ def descr_inscrs_module(moduleimpl_id, set_all, partitions):
|
||||
return False, len(ins), " et ".join(r)
|
||||
|
||||
|
||||
def _fmt_etud_set(ins, max_list_size=7):
|
||||
def _fmt_etud_set(etudids, max_list_size=7) -> str:
|
||||
# max_list_size est le nombre max de noms d'etudiants listés
|
||||
# au delà, on indique juste le nombre, sans les noms.
|
||||
if len(ins) > max_list_size:
|
||||
return "%d étudiants" % len(ins)
|
||||
if len(etudids) > max_list_size:
|
||||
return f"{len(etudids)} étudiants"
|
||||
etuds = []
|
||||
for etudid in ins:
|
||||
etuds.append(sco_etud.get_etud_info(etudid=etudid, filled=True)[0])
|
||||
etuds.sort(key=itemgetter("nom"))
|
||||
for etudid in etudids:
|
||||
etud = Identite.query.get(etudid)
|
||||
if etud:
|
||||
etuds.append(etud)
|
||||
|
||||
return ", ".join(
|
||||
[
|
||||
'<a class="discretelink" href="%s">%s</a>'
|
||||
% (
|
||||
f"""<a class="discretelink" href="{
|
||||
url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
||||
),
|
||||
etud["nomprenom"],
|
||||
)
|
||||
for etud in etuds
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||
)
|
||||
}">{etud.nomprenom}</a>"""
|
||||
for etud in sorted(etuds, key=attrgetter("sort_key"))
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -337,17 +337,18 @@ def ficheEtud(etudid=None):
|
||||
if not sco_permissions_check.can_suppress_annotation(a["id"]):
|
||||
a["dellink"] = ""
|
||||
else:
|
||||
a[
|
||||
"dellink"
|
||||
] = '<td class="annodel"><a href="doSuppressAnnotation?etudid=%s&annotation_id=%s">%s</a></td>' % (
|
||||
etudid,
|
||||
a["id"],
|
||||
scu.icontag(
|
||||
"delete_img",
|
||||
border="0",
|
||||
alt="suppress",
|
||||
title="Supprimer cette annotation",
|
||||
),
|
||||
a["dellink"] = (
|
||||
'<td class="annodel"><a href="doSuppressAnnotation?etudid=%s&annotation_id=%s">%s</a></td>'
|
||||
% (
|
||||
etudid,
|
||||
a["id"],
|
||||
scu.icontag(
|
||||
"delete_img",
|
||||
border="0",
|
||||
alt="suppress",
|
||||
title="Supprimer cette annotation",
|
||||
),
|
||||
)
|
||||
)
|
||||
author = sco_users.user_info(a["author"])
|
||||
alist.append(
|
||||
@ -446,7 +447,7 @@ def ficheEtud(etudid=None):
|
||||
info[
|
||||
"inscriptions_mkup"
|
||||
] = f"""<div class="ficheinscriptions" id="ficheinscriptions">
|
||||
<div class="fichetitre">Parcours</div>{info["liste_inscriptions"]}
|
||||
<div class="fichetitre">Cursus</div>{info["liste_inscriptions"]}
|
||||
{info["link_bul_pdf"]} {info["link_inscrire_ailleurs"]}
|
||||
</div>"""
|
||||
|
||||
|
@ -489,6 +489,7 @@ def _normalize_apo_fields(infolist):
|
||||
recode les champs: paiementinscription (-> booleen), datefinalisationinscription (date)
|
||||
ajoute le champs 'paiementinscription_str' : 'ok', 'Non' ou '?'
|
||||
ajoute les champs 'etape' (= None) et 'prenom' ('') s'ils ne sont pas présents.
|
||||
ajoute le champ 'civilite_etat_civil' (='X'), et 'prenom_etat_civil' (='') si non présent.
|
||||
"""
|
||||
for infos in infolist:
|
||||
if "paiementinscription" in infos:
|
||||
@ -520,6 +521,15 @@ def _normalize_apo_fields(infolist):
|
||||
if "prenom" not in infos:
|
||||
infos["prenom"] = ""
|
||||
|
||||
if "civilite_etat_civil" not in infos:
|
||||
infos["civilite_etat_civil"] = "X"
|
||||
|
||||
if "civilite_etat_civil" not in infos:
|
||||
infos["civilite_etat_civil"] = "X"
|
||||
|
||||
if "prenom_etat_civil" not in infos:
|
||||
infos["prenom_etat_civil"] = ""
|
||||
|
||||
return infolist
|
||||
|
||||
|
||||
|
@ -36,41 +36,49 @@ import flask
|
||||
from flask import g, url_for, request
|
||||
from flask_login import current_user
|
||||
|
||||
from app import log
|
||||
from app.auth.models import User
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import Evaluation, FormSemestre
|
||||
from app.models import ModuleImpl, ScolarNews
|
||||
from app.models import (
|
||||
Evaluation,
|
||||
FormSemestre,
|
||||
Module,
|
||||
ModuleImpl,
|
||||
NotesNotes,
|
||||
ScolarNews,
|
||||
)
|
||||
from app.models.etudiants import Identite
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log
|
||||
|
||||
from app.scodoc.sco_exceptions import (
|
||||
AccessDenied,
|
||||
InvalidNoteValue,
|
||||
NoteProcessError,
|
||||
ScoGenError,
|
||||
ScoBugCatcher,
|
||||
ScoException,
|
||||
ScoInvalidParamError,
|
||||
ScoValueError,
|
||||
)
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator, TF
|
||||
from app.scodoc import html_sco_header, sco_users
|
||||
from app.scodoc import htmlutils
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_groups_view
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_permissions_check
|
||||
from app.scodoc import sco_undo_notes
|
||||
from app.scodoc import sco_etud
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator, TF
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import json_error
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
|
||||
|
||||
def convert_note_from_string(
|
||||
@ -128,29 +136,30 @@ def _displayNote(val):
|
||||
return val
|
||||
|
||||
|
||||
def _check_notes(notes: list[(int, float)], evaluation: dict, mod: dict):
|
||||
def _check_notes(notes: list[(int, float)], evaluation: Evaluation):
|
||||
# XXX typehint : float or str
|
||||
"""notes is a list of tuples (etudid, value)
|
||||
mod is the module (used to ckeck type, for malus)
|
||||
returns list of valid notes (etudid, float value)
|
||||
and 4 lists of etudid: invalids, withoutnotes, absents, tosuppress, existingjury
|
||||
and 4 lists of etudid: etudids_invalids, etudids_without_notes, etudids_absents, etudid_to_suppress
|
||||
"""
|
||||
note_max = evaluation["note_max"]
|
||||
if mod["module_type"] in (
|
||||
note_max = evaluation.note_max or 0.0
|
||||
module: Module = evaluation.moduleimpl.module
|
||||
if module.module_type in (
|
||||
scu.ModuleType.STANDARD,
|
||||
scu.ModuleType.RESSOURCE,
|
||||
scu.ModuleType.SAE,
|
||||
):
|
||||
note_min = scu.NOTES_MIN
|
||||
elif mod["module_type"] == ModuleType.MALUS:
|
||||
elif module.module_type == ModuleType.MALUS:
|
||||
note_min = -20.0
|
||||
else:
|
||||
raise ValueError("Invalid module type") # bug
|
||||
L = [] # liste (etudid, note) des notes ok (ou absent)
|
||||
invalids = [] # etudid avec notes invalides
|
||||
withoutnotes = [] # etudid sans notes (champs vides)
|
||||
absents = [] # etudid absents
|
||||
tosuppress = [] # etudids avec ancienne note à supprimer
|
||||
valid_notes = [] # liste (etudid, note) des notes ok (ou absent)
|
||||
etudids_invalids = [] # etudid avec notes invalides
|
||||
etudids_without_notes = [] # etudid sans notes (champs vides)
|
||||
etudids_absents = [] # etudid absents
|
||||
etudid_to_suppress = [] # etudids avec ancienne note à supprimer
|
||||
|
||||
for etudid, note in notes:
|
||||
note = str(note).strip().upper()
|
||||
@ -166,31 +175,34 @@ def _check_notes(notes: list[(int, float)], evaluation: dict, mod: dict):
|
||||
note_max,
|
||||
note_min=note_min,
|
||||
etudid=etudid,
|
||||
absents=absents,
|
||||
tosuppress=tosuppress,
|
||||
invalids=invalids,
|
||||
absents=etudids_absents,
|
||||
tosuppress=etudid_to_suppress,
|
||||
invalids=etudids_invalids,
|
||||
)
|
||||
if not invalid:
|
||||
L.append((etudid, value))
|
||||
valid_notes.append((etudid, value))
|
||||
else:
|
||||
withoutnotes.append(etudid)
|
||||
return L, invalids, withoutnotes, absents, tosuppress
|
||||
etudids_without_notes.append(etudid)
|
||||
return (
|
||||
valid_notes,
|
||||
etudids_invalids,
|
||||
etudids_without_notes,
|
||||
etudids_absents,
|
||||
etudid_to_suppress,
|
||||
)
|
||||
|
||||
|
||||
def do_evaluation_upload_xls():
|
||||
"""
|
||||
Soumission d'un fichier XLS (evaluation_id, notefile)
|
||||
"""
|
||||
authuser = current_user
|
||||
vals = scu.get_request_args()
|
||||
evaluation_id = int(vals["evaluation_id"])
|
||||
comment = vals["comment"]
|
||||
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||||
M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||
# Check access
|
||||
# (admin, respformation, and responsable_id)
|
||||
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
|
||||
raise AccessDenied("Modification des notes impossible pour %s" % authuser)
|
||||
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||
# Check access (admin, respformation, and responsable_id)
|
||||
if not sco_permissions_check.can_edit_notes(current_user, evaluation.moduleimpl_id):
|
||||
raise AccessDenied(f"Modification des notes impossible pour {current_user}")
|
||||
#
|
||||
diag, lines = sco_excel.excel_file_to_list(vals["notefile"])
|
||||
try:
|
||||
@ -239,14 +251,16 @@ def do_evaluation_upload_xls():
|
||||
if etudid:
|
||||
notes.append((etudid, val))
|
||||
ni += 1
|
||||
except:
|
||||
except Exception as exc:
|
||||
diag.append(
|
||||
f"""Erreur: Ligne invalide ! (erreur ligne {ni})<br>{lines[ni]}"""
|
||||
)
|
||||
raise InvalidNoteValue()
|
||||
raise InvalidNoteValue() from exc
|
||||
# -- check values
|
||||
L, invalids, withoutnotes, absents, _ = _check_notes(notes, E, M["module"])
|
||||
if len(invalids):
|
||||
valid_notes, invalids, withoutnotes, absents, _ = _check_notes(
|
||||
notes, evaluation
|
||||
)
|
||||
if invalids:
|
||||
diag.append(
|
||||
f"Erreur: la feuille contient {len(invalids)} notes invalides</p>"
|
||||
)
|
||||
@ -258,37 +272,33 @@ def do_evaluation_upload_xls():
|
||||
diag.append("Notes invalides pour: " + ", ".join(etudsnames))
|
||||
raise InvalidNoteValue()
|
||||
else:
|
||||
nb_changed, nb_suppress, existing_decisions = notes_add(
|
||||
authuser, evaluation_id, L, comment
|
||||
etudids_changed, nb_suppress, etudids_with_decisions = notes_add(
|
||||
current_user, evaluation_id, valid_notes, comment
|
||||
)
|
||||
# news
|
||||
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[
|
||||
0
|
||||
]
|
||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||
mod["moduleimpl_id"] = M["moduleimpl_id"]
|
||||
mod["url"] = url_for(
|
||||
module: Module = evaluation.moduleimpl.module
|
||||
status_url = url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=mod["moduleimpl_id"],
|
||||
moduleimpl_id=evaluation.moduleimpl_id,
|
||||
_external=True,
|
||||
)
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_NOTE,
|
||||
obj=M["moduleimpl_id"],
|
||||
text='Chargement notes dans <a href="%(url)s">%(titre)s</a>' % mod,
|
||||
url=mod["url"],
|
||||
obj=evaluation.moduleimpl_id,
|
||||
text=f"""Chargement notes dans <a href="{status_url}">{
|
||||
module.titre or module.code}</a>""",
|
||||
url=status_url,
|
||||
max_frequency=30 * 60, # 30 minutes
|
||||
)
|
||||
|
||||
msg = (
|
||||
"<p>%d notes changées (%d sans notes, %d absents, %d note supprimées)</p>"
|
||||
% (nb_changed, len(withoutnotes), len(absents), nb_suppress)
|
||||
)
|
||||
if existing_decisions:
|
||||
msg += """<p class="warning">Important: il y avait déjà des décisions de jury enregistrées, qui sont potentiellement à revoir suite à cette modification !</p>"""
|
||||
# msg += '<p>' + str(notes) # debug
|
||||
msg = f"""<p>{len(etudids_changed)} notes changées ({len(withoutnotes)} sans notes, {
|
||||
len(absents)} absents, {nb_suppress} note supprimées)
|
||||
</p>"""
|
||||
if etudids_with_decisions:
|
||||
msg += """<p class="warning">Important: il y avait déjà des décisions de jury
|
||||
enregistrées, qui sont peut-être à revoir suite à cette modification !</p>
|
||||
"""
|
||||
return 1, msg
|
||||
|
||||
except InvalidNoteValue:
|
||||
@ -310,14 +320,12 @@ def do_evaluation_set_etud_note(evaluation: Evaluation, etud: Identite, value) -
|
||||
if not sco_permissions_check.can_edit_notes(current_user, evaluation.moduleimpl.id):
|
||||
raise AccessDenied(f"Modification des notes impossible pour {current_user}")
|
||||
# Convert and check value
|
||||
L, invalids, _, _, _ = _check_notes(
|
||||
[(etud.id, value)], evaluation.to_dict(), evaluation.moduleimpl.module.to_dict()
|
||||
)
|
||||
L, invalids, _, _, _ = _check_notes([(etud.id, value)], evaluation)
|
||||
if len(invalids) == 0:
|
||||
nb_changed, _, _ = notes_add(
|
||||
etudids_changed, _, _ = notes_add(
|
||||
current_user, evaluation.id, L, "Initialisation notes"
|
||||
)
|
||||
if nb_changed == 1:
|
||||
if len(etudids_changed) == 1:
|
||||
return True
|
||||
return False # error
|
||||
|
||||
@ -352,9 +360,7 @@ def do_evaluation_set_missing(
|
||||
if etudid not in notes_db: # pas de note
|
||||
notes.append((etudid, value))
|
||||
# Convert and check values
|
||||
L, invalids, _, _, _ = _check_notes(
|
||||
notes, evaluation.to_dict(), modimpl.module.to_dict()
|
||||
)
|
||||
valid_notes, invalids, _, _, _ = _check_notes(notes, evaluation)
|
||||
dest_url = url_for(
|
||||
"notes.saisie_notes", scodoc_dept=g.scodoc_dept, evaluation_id=evaluation_id
|
||||
)
|
||||
@ -372,13 +378,13 @@ def do_evaluation_set_missing(
|
||||
"""
|
||||
# Confirm action
|
||||
if not dialog_confirmed:
|
||||
plural = len(L) > 1
|
||||
plural = len(valid_notes) > 1
|
||||
return scu.confirm_dialog(
|
||||
f"""<h2>Mettre toutes les notes manquantes de l'évaluation
|
||||
à la valeur {value} ?</h2>
|
||||
<p>Seuls les étudiants pour lesquels aucune note (ni valeur, ni ABS, ni EXC)
|
||||
n'a été rentrée seront affectés.</p>
|
||||
<p><b>{len(L)} étudiant{"s" if plural else ""} concerné{"s" if plural else ""}
|
||||
<p><b>{len(valid_notes)} étudiant{"s" if plural else ""} concerné{"s" if plural else ""}
|
||||
par ce changement de note.</b>
|
||||
</p>
|
||||
""",
|
||||
@ -392,7 +398,7 @@ def do_evaluation_set_missing(
|
||||
)
|
||||
# ok
|
||||
comment = "Initialisation notes manquantes"
|
||||
nb_changed, _, _ = notes_add(current_user, evaluation_id, L, comment)
|
||||
etudids_changed, _, _ = notes_add(current_user, evaluation_id, valid_notes, comment)
|
||||
# news
|
||||
url = url_for(
|
||||
"notes.moduleimpl_status",
|
||||
@ -408,7 +414,7 @@ def do_evaluation_set_missing(
|
||||
)
|
||||
return f"""
|
||||
{ html_sco_header.sco_header() }
|
||||
<h2>{nb_changed} notes changées</h2>
|
||||
<h2>{len(etudids_changed)} notes changées</h2>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="{dest_url}">
|
||||
Revenir au formulaire de saisie des notes</a>
|
||||
@ -454,7 +460,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
||||
)
|
||||
|
||||
if not dialog_confirmed:
|
||||
nb_changed, nb_suppress, existing_decisions = notes_add(
|
||||
etudids_changed, nb_suppress, existing_decisions = notes_add(
|
||||
current_user, evaluation_id, notes, do_it=False, check_inscription=False
|
||||
)
|
||||
msg = f"""<p>Confirmer la suppression des {nb_suppress} notes ?
|
||||
@ -475,14 +481,14 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
||||
)
|
||||
|
||||
# modif
|
||||
nb_changed, nb_suppress, existing_decisions = notes_add(
|
||||
etudids_changed, nb_suppress, existing_decisions = notes_add(
|
||||
current_user,
|
||||
evaluation_id,
|
||||
notes,
|
||||
comment="effacer tout",
|
||||
check_inscription=False,
|
||||
)
|
||||
assert nb_changed == nb_suppress
|
||||
assert len(etudids_changed) == nb_suppress
|
||||
H = [f"""<p>{nb_suppress} notes supprimées</p>"""]
|
||||
if existing_decisions:
|
||||
H.append(
|
||||
@ -516,7 +522,7 @@ def notes_add(
|
||||
comment=None,
|
||||
do_it=True,
|
||||
check_inscription=True,
|
||||
) -> tuple:
|
||||
) -> tuple[list[int], int, list[int]]:
|
||||
"""
|
||||
Insert or update notes
|
||||
notes is a list of tuples (etudid,value)
|
||||
@ -524,12 +530,12 @@ def notes_add(
|
||||
WOULD be changed or suppressed.
|
||||
Nota:
|
||||
- si la note existe deja avec valeur distincte, ajoute une entree au log (notes_notes_log)
|
||||
Return tuple (nb_changed, nb_suppress, existing_decisions)
|
||||
|
||||
Return: tuple (etudids_changed, nb_suppress, etudids_with_decision)
|
||||
"""
|
||||
now = psycopg2.Timestamp(
|
||||
*time.localtime()[:6]
|
||||
) # datetime.datetime.now().isoformat()
|
||||
# Verifie inscription et valeur note
|
||||
now = psycopg2.Timestamp(*time.localtime()[:6])
|
||||
|
||||
# Vérifie inscription et valeur note
|
||||
inscrits = {
|
||||
x[0]
|
||||
for x in sco_groups.do_evaluation_listeetuds_groups(
|
||||
@ -548,13 +554,13 @@ def notes_add(
|
||||
# Met a jour la base
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
nb_changed = 0
|
||||
etudids_changed = []
|
||||
nb_suppress = 0
|
||||
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||
existing_decisions = (
|
||||
[]
|
||||
) # etudids pour lesquels il y a une decision de jury et que la note change
|
||||
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||
formsemestre: FormSemestre = evaluation.moduleimpl.formsemestre
|
||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
# etudids pour lesquels il y a une decision de jury et que la note change:
|
||||
etudids_with_decision = []
|
||||
try:
|
||||
for etudid, value in notes:
|
||||
changed = False
|
||||
@ -562,7 +568,7 @@ def notes_add(
|
||||
# nouvelle note
|
||||
if value != scu.NOTES_SUPPRESS:
|
||||
if do_it:
|
||||
aa = {
|
||||
args = {
|
||||
"etudid": etudid,
|
||||
"evaluation_id": evaluation_id,
|
||||
"value": value,
|
||||
@ -570,13 +576,20 @@ def notes_add(
|
||||
"uid": user.id,
|
||||
"date": now,
|
||||
}
|
||||
ndb.quote_dict(aa)
|
||||
ndb.quote_dict(args)
|
||||
# Note: le conflit ci-dessous peut arriver si un autre thread
|
||||
# a modifié la base après qu'on ait lu notes_db
|
||||
cursor.execute(
|
||||
"""INSERT INTO notes_notes
|
||||
(etudid, evaluation_id, value, comment, date, uid)
|
||||
VALUES (%(etudid)s,%(evaluation_id)s,%(value)s,%(comment)s,%(date)s,%(uid)s)
|
||||
VALUES
|
||||
(%(etudid)s,%(evaluation_id)s,%(value)s,
|
||||
%(comment)s,%(date)s,%(uid)s)
|
||||
ON CONFLICT ON CONSTRAINT notes_notes_etudid_evaluation_id_key
|
||||
DO UPDATE SET etudid=%(etudid)s, evaluation_id=%(evaluation_id)s,
|
||||
value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
|
||||
""",
|
||||
aa,
|
||||
args,
|
||||
)
|
||||
changed = True
|
||||
else:
|
||||
@ -584,7 +597,7 @@ def notes_add(
|
||||
oldval = notes_db[etudid]["value"]
|
||||
if type(value) != type(oldval):
|
||||
changed = True
|
||||
elif type(value) == float and (
|
||||
elif isinstance(value, float) and (
|
||||
abs(value - oldval) > scu.NOTES_PRECISION
|
||||
):
|
||||
changed = True
|
||||
@ -603,7 +616,7 @@ def notes_add(
|
||||
""",
|
||||
{"etudid": etudid, "evaluation_id": evaluation_id},
|
||||
)
|
||||
aa = {
|
||||
args = {
|
||||
"etudid": etudid,
|
||||
"evaluation_id": evaluation_id,
|
||||
"value": value,
|
||||
@ -611,7 +624,7 @@ def notes_add(
|
||||
"comment": comment,
|
||||
"uid": user.id,
|
||||
}
|
||||
ndb.quote_dict(aa)
|
||||
ndb.quote_dict(args)
|
||||
if value != scu.NOTES_SUPPRESS:
|
||||
if do_it:
|
||||
cursor.execute(
|
||||
@ -620,52 +633,49 @@ def notes_add(
|
||||
WHERE etudid = %(etudid)s
|
||||
and evaluation_id = %(evaluation_id)s
|
||||
""",
|
||||
aa,
|
||||
args,
|
||||
)
|
||||
else: # suppression ancienne note
|
||||
if do_it:
|
||||
log(
|
||||
"notes_add, suppress, evaluation_id=%s, etudid=%s, oldval=%s"
|
||||
% (evaluation_id, etudid, oldval)
|
||||
f"""notes_add, suppress, evaluation_id={evaluation_id}, etudid={
|
||||
etudid}, oldval={oldval}"""
|
||||
)
|
||||
cursor.execute(
|
||||
"""DELETE FROM notes_notes
|
||||
WHERE etudid = %(etudid)s
|
||||
AND evaluation_id = %(evaluation_id)s
|
||||
""",
|
||||
aa,
|
||||
args,
|
||||
)
|
||||
# garde trace de la suppression dans l'historique:
|
||||
aa["value"] = scu.NOTES_SUPPRESS
|
||||
args["value"] = scu.NOTES_SUPPRESS
|
||||
cursor.execute(
|
||||
"""INSERT INTO notes_notes_log (etudid,evaluation_id,value,comment,date,uid)
|
||||
VALUES (%(etudid)s, %(evaluation_id)s, %(value)s, %(comment)s, %(date)s, %(uid)s)
|
||||
"""INSERT INTO notes_notes_log
|
||||
(etudid,evaluation_id,value,comment,date,uid)
|
||||
VALUES
|
||||
(%(etudid)s, %(evaluation_id)s, %(value)s, %(comment)s, %(date)s, %(uid)s)
|
||||
""",
|
||||
aa,
|
||||
args,
|
||||
)
|
||||
nb_suppress += 1
|
||||
if changed:
|
||||
nb_changed += 1
|
||||
if has_existing_decision(M, E, etudid):
|
||||
existing_decisions.append(etudid)
|
||||
etudids_changed.append(etudid)
|
||||
if res.etud_has_decision(etudid):
|
||||
etudids_with_decision.append(etudid)
|
||||
except Exception as exc:
|
||||
log("*** exception in notes_add")
|
||||
if do_it:
|
||||
cnx.rollback() # abort
|
||||
# inval cache
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=M["formsemestre_id"]
|
||||
) # > modif notes (exception)
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
||||
sco_cache.EvaluationCache.delete(evaluation_id)
|
||||
raise # XXX
|
||||
raise ScoGenError("Erreur enregistrement note: merci de ré-essayer") from exc
|
||||
raise ScoException from exc
|
||||
if do_it:
|
||||
cnx.commit()
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=M["formsemestre_id"]
|
||||
) # > modif notes
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
||||
sco_cache.EvaluationCache.delete(evaluation_id)
|
||||
return nb_changed, nb_suppress, existing_decisions
|
||||
return etudids_changed, nb_suppress, etudids_with_decision
|
||||
|
||||
|
||||
def saisie_notes_tableur(evaluation_id, group_ids=()):
|
||||
@ -868,44 +878,39 @@ def saisie_notes_tableur(evaluation_id, group_ids=()):
|
||||
|
||||
def feuille_saisie_notes(evaluation_id, group_ids=[]):
|
||||
"""Document Excel pour saisie notes dans l'évaluation et les groupes indiqués"""
|
||||
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
|
||||
if not evals:
|
||||
evaluation: Evaluation = Evaluation.query.get(evaluation_id)
|
||||
if not evaluation:
|
||||
raise ScoValueError("invalid evaluation_id")
|
||||
eval_dict = evals[0]
|
||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=eval_dict["moduleimpl_id"])[0]
|
||||
formsemestre_id = M["formsemestre_id"]
|
||||
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
||||
mod_responsable = sco_users.user_info(M["responsable_id"])
|
||||
if eval_dict["jour"]:
|
||||
indication_date = ndb.DateDMYtoISO(eval_dict["jour"])
|
||||
modimpl = evaluation.moduleimpl
|
||||
formsemestre = modimpl.formsemestre
|
||||
mod_responsable = sco_users.user_info(modimpl.responsable_id)
|
||||
if evaluation.jour:
|
||||
indication_date = evaluation.jour.isoformat()
|
||||
else:
|
||||
indication_date = scu.sanitize_filename(eval_dict["description"])[:12]
|
||||
eval_name = "%s-%s" % (Mod["code"], indication_date)
|
||||
indication_date = scu.sanitize_filename(evaluation.description or "")[:12]
|
||||
eval_name = f"{evaluation.moduleimpl.module.code}-{indication_date}"
|
||||
|
||||
if eval_dict["description"]:
|
||||
evaltitre = "%s du %s" % (eval_dict["description"], eval_dict["jour"])
|
||||
else:
|
||||
evaltitre = "évaluation du %s" % eval_dict["jour"]
|
||||
description = "%s en %s (%s) resp. %s" % (
|
||||
evaltitre,
|
||||
Mod["abbrev"] or "",
|
||||
Mod["code"] or "",
|
||||
mod_responsable["prenomnom"],
|
||||
date_str = (
|
||||
f"""du {evaluation.jour.strftime("%d/%m/%Y")}"""
|
||||
if evaluation.jour
|
||||
else "(sans date)"
|
||||
)
|
||||
eval_titre = f"""{evaluation.description if evaluation.description else "évaluation"} {date_str}"""
|
||||
|
||||
description = f"""{eval_titre} en {evaluation.moduleimpl.module.abbrev or ""} ({
|
||||
evaluation.moduleimpl.module.code
|
||||
}) resp. {mod_responsable["prenomnom"]}"""
|
||||
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
group_ids=group_ids,
|
||||
formsemestre_id=formsemestre_id,
|
||||
formsemestre_id=formsemestre.id,
|
||||
select_all_when_unspecified=True,
|
||||
etat=None,
|
||||
)
|
||||
groups = sco_groups.listgroups(groups_infos.group_ids)
|
||||
gr_title_filename = sco_groups.listgroups_filename(groups)
|
||||
# gr_title = sco_groups.listgroups_abbrev(groups)
|
||||
if None in [g["group_name"] for g in groups]: # tous les etudiants
|
||||
getallstudents = True
|
||||
# gr_title = "tous"
|
||||
gr_title_filename = "tous"
|
||||
else:
|
||||
getallstudents = False
|
||||
@ -917,17 +922,17 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
|
||||
]
|
||||
|
||||
# une liste de liste de chaines: lignes de la feuille de calcul
|
||||
L = []
|
||||
rows = []
|
||||
|
||||
etuds = _get_sorted_etuds(eval_dict, etudids, formsemestre_id)
|
||||
etuds = _get_sorted_etuds(evaluation, etudids, formsemestre.id)
|
||||
for e in etuds:
|
||||
etudid = e["etudid"]
|
||||
groups = sco_groups.get_etud_groups(etudid, formsemestre_id)
|
||||
groups = sco_groups.get_etud_groups(etudid, formsemestre.id)
|
||||
grc = sco_groups.listgroups_abbrev(groups)
|
||||
|
||||
L.append(
|
||||
rows.append(
|
||||
[
|
||||
"%s" % etudid,
|
||||
str(etudid),
|
||||
e["nom"].upper(),
|
||||
e["prenom"].lower().capitalize(),
|
||||
e["inscr"]["etat"],
|
||||
@ -937,31 +942,11 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
|
||||
]
|
||||
)
|
||||
|
||||
filename = "notes_%s_%s" % (eval_name, gr_title_filename)
|
||||
filename = f"notes_{eval_name}_{gr_title_filename}"
|
||||
xls = sco_excel.excel_feuille_saisie(
|
||||
eval_dict, sem["titreannee"], description, lines=L
|
||||
evaluation, formsemestre.titre_annee(), description, lines=rows
|
||||
)
|
||||
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
|
||||
# return sco_excel.send_excel_file(xls, filename)
|
||||
|
||||
|
||||
def has_existing_decision(M, E, etudid):
|
||||
"""Verifie s'il y a une validation pour cet etudiant dans ce semestre ou UE
|
||||
Si oui, return True
|
||||
"""
|
||||
formsemestre_id = M["formsemestre_id"]
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
if nt.get_etud_decision_sem(etudid):
|
||||
return True
|
||||
dec_ues = nt.get_etud_decisions_ue(etudid)
|
||||
if dec_ues:
|
||||
mod = sco_edit_module.module_list({"module_id": M["module_id"]})[0]
|
||||
ue_id = mod["ue_id"]
|
||||
if ue_id in dec_ues:
|
||||
return True # decision pour l'UE a laquelle appartient cette evaluation
|
||||
|
||||
return False # pas de decision de jury affectee par cette note
|
||||
|
||||
|
||||
# -----------------------------
|
||||
@ -973,20 +958,18 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
|
||||
if not isinstance(evaluation_id, int):
|
||||
raise ScoInvalidParamError()
|
||||
group_ids = [int(group_id) for group_id in (group_ids or [])]
|
||||
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
|
||||
if not evals:
|
||||
evaluation: Evaluation = Evaluation.query.get(evaluation_id)
|
||||
if evaluation is None:
|
||||
raise ScoValueError("évaluation inexistante")
|
||||
E = evals[0]
|
||||
M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||
formsemestre_id = M["formsemestre_id"]
|
||||
modimpl = evaluation.moduleimpl
|
||||
moduleimpl_status_url = url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=E["moduleimpl_id"],
|
||||
moduleimpl_id=evaluation.moduleimpl_id,
|
||||
)
|
||||
# Check access
|
||||
# (admin, respformation, and responsable_id)
|
||||
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
|
||||
if not sco_permissions_check.can_edit_notes(current_user, evaluation.moduleimpl_id):
|
||||
return f"""
|
||||
{html_sco_header.sco_header()}
|
||||
<h2>Modification des notes impossible pour {current_user.user_name}</h2>
|
||||
@ -1001,16 +984,16 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
|
||||
# Informations sur les groupes à afficher:
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
group_ids=group_ids,
|
||||
formsemestre_id=formsemestre_id,
|
||||
formsemestre_id=modimpl.formsemestre_id,
|
||||
select_all_when_unspecified=True,
|
||||
etat=None,
|
||||
)
|
||||
|
||||
if E["description"]:
|
||||
page_title = 'Saisie "%s"' % E["description"]
|
||||
else:
|
||||
page_title = "Saisie des notes"
|
||||
|
||||
page_title = (
|
||||
f'Saisie "{evaluation.description}"'
|
||||
if evaluation.description
|
||||
else "Saisie des notes"
|
||||
)
|
||||
# HTML page:
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
@ -1036,19 +1019,19 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
|
||||
"id": "menu_saisie_tableur",
|
||||
"endpoint": "notes.saisie_notes_tableur",
|
||||
"args": {
|
||||
"evaluation_id": E["evaluation_id"],
|
||||
"evaluation_id": evaluation.id,
|
||||
"group_ids": groups_infos.group_ids,
|
||||
},
|
||||
},
|
||||
{
|
||||
"title": "Voir toutes les notes du module",
|
||||
"endpoint": "notes.evaluation_listenotes",
|
||||
"args": {"moduleimpl_id": E["moduleimpl_id"]},
|
||||
"args": {"moduleimpl_id": evaluation.moduleimpl_id},
|
||||
},
|
||||
{
|
||||
"title": "Effacer toutes les notes de cette évaluation",
|
||||
"endpoint": "notes.evaluation_suppress_alln",
|
||||
"args": {"evaluation_id": E["evaluation_id"]},
|
||||
"args": {"evaluation_id": evaluation.id},
|
||||
},
|
||||
],
|
||||
alone=True,
|
||||
@ -1077,7 +1060,9 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
|
||||
)
|
||||
|
||||
# Le formulaire de saisie des notes:
|
||||
form = _form_saisie_notes(E, M, groups_infos, destination=moduleimpl_status_url)
|
||||
form = _form_saisie_notes(
|
||||
evaluation, modimpl, groups_infos, destination=moduleimpl_status_url
|
||||
)
|
||||
if form is None:
|
||||
return flask.redirect(moduleimpl_status_url)
|
||||
H.append(form)
|
||||
@ -1101,10 +1086,9 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def _get_sorted_etuds(eval_dict: dict, etudids: list, formsemestre_id: int):
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(
|
||||
eval_dict["evaluation_id"]
|
||||
) # Notes existantes
|
||||
def _get_sorted_etuds(evaluation: Evaluation, etudids: list, formsemestre_id: int):
|
||||
# Notes existantes
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
|
||||
cnx = ndb.GetDBConnexion()
|
||||
etuds = []
|
||||
for etudid in etudids:
|
||||
@ -1123,17 +1107,17 @@ def _get_sorted_etuds(eval_dict: dict, etudids: list, formsemestre_id: int):
|
||||
e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id)
|
||||
|
||||
# Information sur absence (tenant compte de la demi-journée)
|
||||
jour_iso = ndb.DateDMYtoISO(eval_dict["jour"])
|
||||
jour_iso = evaluation.jour.isoformat() if evaluation.jour else ""
|
||||
warn_abs_lst = []
|
||||
if eval_dict["matin"]:
|
||||
nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=1)
|
||||
nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=1)
|
||||
if evaluation.is_matin():
|
||||
nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=True)
|
||||
nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=True)
|
||||
if nbabs:
|
||||
if nbabsjust:
|
||||
warn_abs_lst.append("absent justifié le matin !")
|
||||
else:
|
||||
warn_abs_lst.append("absent le matin !")
|
||||
if eval_dict["apresmidi"]:
|
||||
if evaluation.is_apresmidi():
|
||||
nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=0)
|
||||
nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=0)
|
||||
if nbabs:
|
||||
@ -1169,35 +1153,38 @@ def _get_sorted_etuds(eval_dict: dict, etudids: list, formsemestre_id: int):
|
||||
return etuds
|
||||
|
||||
|
||||
def _form_saisie_notes(E, M, groups_infos, destination=""):
|
||||
def _form_saisie_notes(
|
||||
evaluation: Evaluation, modimpl: ModuleImpl, groups_infos, destination=""
|
||||
):
|
||||
"""Formulaire HTML saisie des notes dans l'évaluation E du moduleimpl M
|
||||
pour les groupes indiqués.
|
||||
|
||||
On charge tous les étudiants, ne seront montrés que ceux
|
||||
des groupes sélectionnés grace a un filtre en javascript.
|
||||
"""
|
||||
evaluation_id = E["evaluation_id"]
|
||||
formsemestre_id = M["formsemestre_id"]
|
||||
|
||||
formsemestre_id = modimpl.formsemestre_id
|
||||
formsemestre: FormSemestre = evaluation.moduleimpl.formsemestre
|
||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
etudids = [
|
||||
x[0]
|
||||
for x in sco_groups.do_evaluation_listeetuds_groups(
|
||||
evaluation_id, getallstudents=True, include_demdef=True
|
||||
evaluation.id, getallstudents=True, include_demdef=True
|
||||
)
|
||||
]
|
||||
if not etudids:
|
||||
return '<div class="ue_warning"><span>Aucun étudiant sélectionné !</span></div>'
|
||||
|
||||
# Decisions de jury existantes ?
|
||||
decisions_jury = {etudid: has_existing_decision(M, E, etudid) for etudid in etudids}
|
||||
# Nb de decisions de jury (pour les inscrits à l'évaluation):
|
||||
# Décisions de jury existantes ?
|
||||
decisions_jury = {etudid: res.etud_has_decision(etudid) for etudid in etudids}
|
||||
|
||||
# Nb de décisions de jury (pour les inscrits à l'évaluation):
|
||||
nb_decisions = sum(decisions_jury.values())
|
||||
|
||||
etuds = _get_sorted_etuds(E, etudids, formsemestre_id)
|
||||
etuds = _get_sorted_etuds(evaluation, etudids, formsemestre_id)
|
||||
|
||||
# Build form:
|
||||
descr = [
|
||||
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
|
||||
("evaluation_id", {"default": evaluation.id, "input_type": "hidden"}),
|
||||
("formsemestre_id", {"default": formsemestre_id, "input_type": "hidden"}),
|
||||
(
|
||||
"group_ids",
|
||||
@ -1207,7 +1194,7 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
|
||||
("comment", {"size": 44, "title": "Commentaire", "return_focus_next": True}),
|
||||
("changed", {"default": "0", "input_type": "hidden"}), # changed in JS
|
||||
]
|
||||
if M["module"]["module_type"] in (
|
||||
if modimpl.module.module_type in (
|
||||
ModuleType.STANDARD,
|
||||
ModuleType.RESSOURCE,
|
||||
ModuleType.SAE,
|
||||
@ -1220,11 +1207,11 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
|
||||
"title": "Notes ",
|
||||
"cssclass": "formnote_bareme",
|
||||
"readonly": True,
|
||||
"default": " / %g" % E["note_max"],
|
||||
"default": " / %g" % evaluation.note_max,
|
||||
},
|
||||
)
|
||||
)
|
||||
elif M["module"]["module_type"] == ModuleType.MALUS:
|
||||
elif modimpl.module.module_type == ModuleType.MALUS:
|
||||
descr.append(
|
||||
(
|
||||
"s3",
|
||||
@ -1238,7 +1225,7 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise ValueError("invalid module type (%s)" % M["module"]["module_type"]) # bug
|
||||
raise ValueError(f"invalid module type ({modimpl.module.module_type})") # bug
|
||||
|
||||
initvalues = {}
|
||||
for e in etuds:
|
||||
@ -1248,7 +1235,7 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
|
||||
if disabled:
|
||||
classdem = " etud_dem"
|
||||
etud_classes.append("etud_dem")
|
||||
disabled_attr = 'disabled="%d"' % disabled
|
||||
disabled_attr = f'disabled="{disabled}"'
|
||||
else:
|
||||
classdem = ""
|
||||
disabled_attr = ""
|
||||
@ -1265,18 +1252,17 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
|
||||
)
|
||||
|
||||
# Historique des saisies de notes:
|
||||
if not disabled:
|
||||
explanation = (
|
||||
'<span id="hist_%s">' % etudid
|
||||
+ get_note_history_menu(evaluation_id, etudid)
|
||||
+ "</span>"
|
||||
)
|
||||
else:
|
||||
explanation = ""
|
||||
explanation = (
|
||||
""
|
||||
if disabled
|
||||
else f"""<span id="hist_{etudid}">{
|
||||
get_note_history_menu(evaluation.id, etudid)
|
||||
}</span>"""
|
||||
)
|
||||
explanation = e["absinfo"] + explanation
|
||||
|
||||
# Lien modif decision de jury:
|
||||
explanation += '<span id="jurylink_%s" class="jurylink"></span>' % etudid
|
||||
explanation += f'<span id="jurylink_{etudid}" class="jurylink"></span>'
|
||||
|
||||
# Valeur actuelle du champ:
|
||||
initvalues["note_" + str(etudid)] = e["val"]
|
||||
@ -1330,7 +1316,7 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
|
||||
H.append(tf.getform()) # check and init
|
||||
H.append(
|
||||
f"""<a href="{url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=M["moduleimpl_id"])
|
||||
moduleimpl_id=modimpl.id)
|
||||
}" class="btn btn-primary">Terminer</a>
|
||||
"""
|
||||
)
|
||||
@ -1345,7 +1331,7 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
|
||||
Mettre les notes manquantes à
|
||||
<input type="text" size="5" name="value"/>
|
||||
<input type="submit" value="OK"/>
|
||||
<input type="hidden" name="evaluation_id" value="{evaluation_id}"/>
|
||||
<input type="hidden" name="evaluation_id" value="{evaluation.id}"/>
|
||||
<input class="group_ids_str" type="hidden" name="group_ids_str" value="{
|
||||
",".join([str(x) for x in groups_infos.group_ids])
|
||||
}"/>
|
||||
@ -1362,50 +1348,56 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
|
||||
return None
|
||||
|
||||
|
||||
def save_note(etudid=None, evaluation_id=None, value=None, comment=""):
|
||||
"""Enregistre une note (ajax)"""
|
||||
authuser = current_user
|
||||
log(
|
||||
"save_note: evaluation_id=%s etudid=%s uid=%s value=%s"
|
||||
% (evaluation_id, etudid, authuser, value)
|
||||
)
|
||||
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||
Mod["url"] = url_for(
|
||||
def save_notes(
|
||||
evaluation: Evaluation, notes: list[tuple[(int, float)]], comment: str = ""
|
||||
) -> dict:
|
||||
"""Enregistre une liste de notes.
|
||||
Vérifie que les étudiants sont bien inscrits à ce module, et que l'on a le droit.
|
||||
Result: dict avec
|
||||
"""
|
||||
log(f"save_note: evaluation_id={evaluation.id} uid={current_user} notes={notes}")
|
||||
status_url = url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=M["moduleimpl_id"],
|
||||
moduleimpl_id=evaluation.moduleimpl_id,
|
||||
_external=True,
|
||||
)
|
||||
result = {"nbchanged": 0} # JSON
|
||||
# Check access: admin, respformation, or responsable_id
|
||||
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
|
||||
result["status"] = "unauthorized"
|
||||
if not sco_permissions_check.can_edit_notes(current_user, evaluation.moduleimpl_id):
|
||||
return json_error(403, "modification notes non autorisee pour cet utilisateur")
|
||||
#
|
||||
valid_notes, _, _, _, _ = _check_notes(notes, evaluation)
|
||||
if valid_notes:
|
||||
etudids_changed, _, etudids_with_decision = notes_add(
|
||||
current_user, evaluation.id, valid_notes, comment=comment, do_it=True
|
||||
)
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_NOTE,
|
||||
obj=evaluation.moduleimpl_id,
|
||||
text=f"""Chargement notes dans <a href="{status_url}">{
|
||||
evaluation.moduleimpl.module.titre or evaluation.moduleimpl.module.code}</a>""",
|
||||
url=status_url,
|
||||
max_frequency=30 * 60, # 30 minutes
|
||||
)
|
||||
result = {
|
||||
"etudids_with_decision": etudids_with_decision,
|
||||
"etudids_changed": etudids_changed,
|
||||
"history_menu": {
|
||||
etudid: get_note_history_menu(evaluation.id, etudid)
|
||||
for etudid in etudids_changed
|
||||
},
|
||||
}
|
||||
else:
|
||||
L, _, _, _, _ = _check_notes([(etudid, value)], E, Mod)
|
||||
if L:
|
||||
nbchanged, _, existing_decisions = notes_add(
|
||||
authuser, evaluation_id, L, comment=comment, do_it=True
|
||||
)
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_NOTE,
|
||||
obj=M["moduleimpl_id"],
|
||||
text='Chargement notes dans <a href="%(url)s">%(titre)s</a>' % Mod,
|
||||
url=Mod["url"],
|
||||
max_frequency=30 * 60, # 30 minutes
|
||||
)
|
||||
result["nbchanged"] = nbchanged
|
||||
result["existing_decisions"] = existing_decisions
|
||||
if nbchanged > 0:
|
||||
result["history_menu"] = get_note_history_menu(evaluation_id, etudid)
|
||||
else:
|
||||
result["history_menu"] = "" # no update needed
|
||||
result["status"] = "ok"
|
||||
return scu.sendJSON(result)
|
||||
result = {
|
||||
"etudids_changed": [],
|
||||
"etudids_with_decision": [],
|
||||
"history_menu": [],
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_note_history_menu(evaluation_id, etudid):
|
||||
def get_note_history_menu(evaluation_id: int, etudid: int) -> str:
|
||||
"""Menu HTML historique de la note"""
|
||||
history = sco_undo_notes.get_note_history(evaluation_id, etudid)
|
||||
if not history:
|
||||
|
@ -354,6 +354,10 @@ body.editionActivated .filtres .nonEditable .move {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.groupe:has(.etudiants:empty) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* .filtres .unselect {
|
||||
background: rgba(0, 153, 204, 0.5) !important;
|
||||
} */
|
||||
|
@ -3173,6 +3173,19 @@ li.tf-msg {
|
||||
/* EMO_WARNING, "⚠️" */
|
||||
}
|
||||
|
||||
p.error {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
p.error::before {
|
||||
content: "\2049 \fe0f";
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.infop {
|
||||
font-weight: normal;
|
||||
|
65
app/static/js/etud_autocomplete.js
Normal file
65
app/static/js/etud_autocomplete.js
Normal file
@ -0,0 +1,65 @@
|
||||
|
||||
// Mécanisme d'auto-complétion (choix) d'un étudiant
|
||||
// Il faut un champs #etudiant (text input) et à coté un champ hidden etudid qui sera rempli.
|
||||
// utilise autoComplete.js, source https://tarekraafat.github.io/autoComplete.js
|
||||
// EV 2023-06-01
|
||||
|
||||
function etud_autocomplete_config(with_dept = false) {
|
||||
return {
|
||||
selector: "#etudiant",
|
||||
placeHolder: "Nom...",
|
||||
threshold: 3,
|
||||
data: {
|
||||
src: async (query) => {
|
||||
try {
|
||||
// Fetch Data from external Source
|
||||
const source = await fetch(`/ScoDoc/api/etudiants/name/${query}`);
|
||||
// Data should be an array of `Objects` or `Strings`
|
||||
const data = await source.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
},
|
||||
// Data source 'Object' key to be searched
|
||||
keys: ["nom"]
|
||||
},
|
||||
events: {
|
||||
input: {
|
||||
selection: (event) => {
|
||||
const prenom = sco_capitalize(event.detail.selection.value.prenom);
|
||||
const selection = with_dept ? `${event.detail.selection.value.nom} ${prenom} (${event.detail.selection.value.dept_acronym})` : `${event.detail.selection.value.nom} ${prenom}`;
|
||||
// store etudid
|
||||
const etudidField = document.getElementById('etudid');
|
||||
etudidField.value = event.detail.selection.value.id;
|
||||
autoCompleteJS.input.value = selection;
|
||||
}
|
||||
}
|
||||
},
|
||||
resultsList: {
|
||||
element: (list, data) => {
|
||||
if (!data.results.length) {
|
||||
// Create "No Results" message element
|
||||
const message = document.createElement("div");
|
||||
// Add class to the created element
|
||||
message.setAttribute("class", "no_result");
|
||||
// Add message text content
|
||||
message.innerHTML = `<span>Pas de résultat pour "${data.query}"</span>`;
|
||||
// Append message element to the results list
|
||||
list.prepend(message);
|
||||
// Efface l'etudid
|
||||
const etudidField = document.getElementById('etudid');
|
||||
etudidField.value = "";
|
||||
}
|
||||
},
|
||||
noResults: true,
|
||||
},
|
||||
resultItem: {
|
||||
highlight: true,
|
||||
element: (item, data) => {
|
||||
const prenom = sco_capitalize(data.value.prenom);
|
||||
item.innerHTML += with_dept ? ` ${prenom} (${data.value.dept_acronym})` : ` ${prenom}`;
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -1,132 +1,142 @@
|
||||
// Formulaire saisie des notes
|
||||
|
||||
$().ready(function () {
|
||||
$("#formnotes .note").bind("blur", valid_note);
|
||||
|
||||
$("#formnotes .note").bind("blur", valid_note);
|
||||
|
||||
$("#formnotes input").bind("paste", paste_text);
|
||||
$(".btn_masquer_DEM").bind("click", masquer_DEM);
|
||||
|
||||
$("#formnotes input").bind("paste", paste_text);
|
||||
$(".btn_masquer_DEM").bind("click", masquer_DEM);
|
||||
});
|
||||
|
||||
function is_valid_note(v) {
|
||||
if (!v)
|
||||
return true;
|
||||
if (!v) return true;
|
||||
|
||||
var note_min = parseFloat($("#eval_note_min").text());
|
||||
var note_max = parseFloat($("#eval_note_max").text());
|
||||
var note_min = parseFloat($("#eval_note_min").text());
|
||||
var note_max = parseFloat($("#eval_note_max").text());
|
||||
|
||||
if (!v.match("^-?[0-9]*.?[0-9]*$")) {
|
||||
return (v == "ABS") || (v == "EXC") || (v == "SUPR") || (v == "ATT") || (v == "DEM");
|
||||
} else {
|
||||
var x = parseFloat(v);
|
||||
return (x >= note_min) && (x <= note_max);
|
||||
}
|
||||
if (!v.match("^-?[0-9]*.?[0-9]*$")) {
|
||||
return v == "ABS" || v == "EXC" || v == "SUPR" || v == "ATT" || v == "DEM";
|
||||
} else {
|
||||
var x = parseFloat(v);
|
||||
return x >= note_min && x <= note_max;
|
||||
}
|
||||
}
|
||||
|
||||
function valid_note(e) {
|
||||
var v = this.value.trim().toUpperCase().replace(",", ".");
|
||||
if (is_valid_note(v)) {
|
||||
if (v && (v != $(this).attr('data-last-saved-value'))) {
|
||||
this.className = "note_valid_new";
|
||||
var etudid = $(this).attr('data-etudid');
|
||||
save_note(this, v, etudid);
|
||||
}
|
||||
} else {
|
||||
/* Saisie invalide */
|
||||
this.className = "note_invalid";
|
||||
sco_message("valeur invalide ou hors barème");
|
||||
var v = this.value.trim().toUpperCase().replace(",", ".");
|
||||
if (is_valid_note(v)) {
|
||||
if (v && v != $(this).attr("data-last-saved-value")) {
|
||||
this.className = "note_valid_new";
|
||||
const etudid = parseInt($(this).attr("data-etudid"));
|
||||
save_note(this, v, etudid);
|
||||
}
|
||||
} else {
|
||||
/* Saisie invalide */
|
||||
this.className = "note_invalid";
|
||||
sco_message("valeur invalide ou hors barème");
|
||||
}
|
||||
}
|
||||
|
||||
function save_note(elem, v, etudid) {
|
||||
var evaluation_id = $("#formnotes_evaluation_id").attr("value");
|
||||
var formsemestre_id = $("#formnotes_formsemestre_id").attr("value");
|
||||
$('#sco_msg').html("en cours...").show();
|
||||
$.post(SCO_URL + '/Notes/save_note',
|
||||
{
|
||||
'etudid': etudid,
|
||||
'evaluation_id': evaluation_id,
|
||||
'value': v,
|
||||
'comment': document.getElementById('formnotes_comment').value
|
||||
async function save_note(elem, v, etudid) {
|
||||
let evaluation_id = $("#formnotes_evaluation_id").attr("value");
|
||||
let formsemestre_id = $("#formnotes_formsemestre_id").attr("value");
|
||||
$("#sco_msg").html("en cours...").show();
|
||||
try {
|
||||
const response = await fetch(
|
||||
SCO_URL + "/../api/evaluation/" + evaluation_id + "/notes/set",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
function (result) {
|
||||
if (result['nbchanged'] > 0) {
|
||||
sco_message("enregistré");
|
||||
elem.className = "note_saved";
|
||||
// il y avait une decision de jury ?
|
||||
if (result.existing_decisions[0] == etudid) {
|
||||
if (v != $(elem).attr('data-orig-value')) {
|
||||
$("#jurylink_" + etudid).html('<a href="formsemestre_validation_etud_form?formsemestre_id=' + formsemestre_id + '&etudid=' + etudid + '">mettre à jour décision de jury</a>');
|
||||
} else {
|
||||
$("#jurylink_" + etudid).html('');
|
||||
}
|
||||
}
|
||||
// mise a jour menu historique
|
||||
if (result['history_menu']) {
|
||||
$("#hist_" + etudid).html(result['history_menu']);
|
||||
}
|
||||
$(elem).attr('data-last-saved-value', v);
|
||||
} else {
|
||||
$('#sco_msg').html("").show();
|
||||
sco_message("valeur non enregistrée");
|
||||
}
|
||||
|
||||
}
|
||||
body: JSON.stringify({
|
||||
notes: [[etudid, v]],
|
||||
comment: document.getElementById("formnotes_comment").value,
|
||||
}),
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
sco_message("Erreur: valeur non enregistrée");
|
||||
} else {
|
||||
const data = await response.json();
|
||||
$("#sco_msg").hide();
|
||||
if (data.etudids_changed.length > 0) {
|
||||
sco_message("enregistré");
|
||||
elem.className = "note_saved";
|
||||
// Il y avait une decision de jury ?
|
||||
if (data.etudids_with_decision.includes(etudid)) {
|
||||
if (v != $(elem).attr("data-orig-value")) {
|
||||
$("#jurylink_" + etudid).html(
|
||||
'<a href="formsemestre_validation_etud_form?formsemestre_id=' +
|
||||
formsemestre_id +
|
||||
"&etudid=" +
|
||||
etudid +
|
||||
'">mettre à jour décision de jury</a>'
|
||||
);
|
||||
} else {
|
||||
$("#jurylink_" + etudid).html("");
|
||||
}
|
||||
}
|
||||
// Mise à jour menu historique
|
||||
if (data.history_menu[etudid]) {
|
||||
$("#hist_" + etudid).html(data.history_menu[etudid]);
|
||||
}
|
||||
$(elem).attr("data-last-saved-value", v);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Fetch error:", error);
|
||||
sco_message("Erreur réseau: valeur non enregistrée");
|
||||
}
|
||||
}
|
||||
|
||||
function change_history(e) {
|
||||
var opt = e.selectedOptions[0];
|
||||
var val = $(opt).attr("data-note");
|
||||
var etudid = $(e).attr('data-etudid');
|
||||
// le input associé a ce menu:
|
||||
var input_elem = e.parentElement.parentElement.parentElement.childNodes[0];
|
||||
input_elem.value = val;
|
||||
save_note(input_elem, val, etudid);
|
||||
let opt = e.selectedOptions[0];
|
||||
let val = $(opt).attr("data-note");
|
||||
const etudid = parseInt($(e).attr("data-etudid"));
|
||||
// le input associé a ce menu:
|
||||
let input_elem = e.parentElement.parentElement.parentElement.childNodes[0];
|
||||
input_elem.value = val;
|
||||
save_note(input_elem, val, etudid);
|
||||
}
|
||||
|
||||
// Contribution S.L.: copier/coller des notes
|
||||
|
||||
|
||||
function paste_text(e) {
|
||||
var event = e.originalEvent;
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
var clipb = e.originalEvent.clipboardData;
|
||||
var data = clipb.getData('Text');
|
||||
var list = data.split(/\r\n|\r|\n|\t| /g);
|
||||
var currentInput = event.currentTarget;
|
||||
var masquerDEM = document.querySelector("body").classList.contains("masquer_DEM");
|
||||
var event = e.originalEvent;
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
var clipb = e.originalEvent.clipboardData;
|
||||
var data = clipb.getData("Text");
|
||||
var list = data.split(/\r\n|\r|\n|\t| /g);
|
||||
var currentInput = event.currentTarget;
|
||||
var masquerDEM = document
|
||||
.querySelector("body")
|
||||
.classList.contains("masquer_DEM");
|
||||
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
currentInput.value = list[i];
|
||||
var evt = document.createEvent("HTMLEvents");
|
||||
evt.initEvent("blur", false, true);
|
||||
currentInput.dispatchEvent(evt);
|
||||
var sibbling = currentInput.parentElement.parentElement.nextElementSibling;
|
||||
while (
|
||||
sibbling &&
|
||||
(
|
||||
sibbling.style.display == "none" ||
|
||||
(
|
||||
masquerDEM && sibbling.classList.contains("etud_dem")
|
||||
)
|
||||
)
|
||||
) {
|
||||
sibbling = sibbling.nextElementSibling;
|
||||
}
|
||||
if (sibbling) {
|
||||
currentInput = sibbling.querySelector("input");
|
||||
if (!currentInput) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
currentInput.value = list[i];
|
||||
var evt = document.createEvent("HTMLEvents");
|
||||
evt.initEvent("blur", false, true);
|
||||
currentInput.dispatchEvent(evt);
|
||||
var sibbling = currentInput.parentElement.parentElement.nextElementSibling;
|
||||
while (
|
||||
sibbling &&
|
||||
(sibbling.style.display == "none" ||
|
||||
(masquerDEM && sibbling.classList.contains("etud_dem")))
|
||||
) {
|
||||
sibbling = sibbling.nextElementSibling;
|
||||
}
|
||||
if (sibbling) {
|
||||
currentInput = sibbling.querySelector("input");
|
||||
if (!currentInput) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function masquer_DEM() {
|
||||
document.querySelector("body").classList.toggle("masquer_DEM");
|
||||
document.querySelector("body").classList.toggle("masquer_DEM");
|
||||
}
|
||||
|
@ -67,6 +67,10 @@ $(function () {
|
||||
}
|
||||
});
|
||||
|
||||
function sco_capitalize(string) {
|
||||
return string[0].toUpperCase() + string.slice(1).toLowerCase();
|
||||
}
|
||||
|
||||
// Affiche un message transitoire (duration milliseconds, 0 means infinity)
|
||||
function sco_message(msg, className = "message_custom", duration = 0) {
|
||||
var div = document.createElement("div");
|
||||
|
1
app/static/libjs/autoComplete.js-10.2.7/dist/autoComplete.min.js
vendored
Normal file
1
app/static/libjs/autoComplete.js-10.2.7/dist/autoComplete.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
92
app/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.01.css
vendored
Normal file
92
app/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.01.css
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
.autoComplete_wrapper {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > input {
|
||||
width: 370px;
|
||||
height: 40px;
|
||||
padding-left: 20px;
|
||||
font-size: 1rem;
|
||||
color: rgba(123, 123, 123, 1);
|
||||
border-radius: 8px;
|
||||
border: 0;
|
||||
outline: none;
|
||||
background-color: #f1f3f4;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > input::placeholder {
|
||||
color: rgba(123, 123, 123, 0.5);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul {
|
||||
position: absolute;
|
||||
max-height: 226px;
|
||||
overflow-y: scroll;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0;
|
||||
margin: 0.5rem 0 0 0;
|
||||
border-radius: 0.6rem;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 3px 6px rgba(149, 157, 165, 0.15);
|
||||
border: 1px solid rgba(33, 33, 33, 0.07);
|
||||
z-index: 1000;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul[hidden],
|
||||
.autoComplete_wrapper > ul:empty {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li {
|
||||
margin: 0.3rem;
|
||||
padding: 0.3rem 0.5rem;
|
||||
list-style: none;
|
||||
text-align: left;
|
||||
font-size: 1rem;
|
||||
color: #212121;
|
||||
transition: all 0.1s ease-in-out;
|
||||
border-radius: 0.35rem;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li::selection {
|
||||
color: rgba(#ffffff, 0);
|
||||
background-color: rgba(#ffffff, 0);
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li:hover {
|
||||
cursor: pointer;
|
||||
background-color: rgba(123, 123, 123, 0.1);
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li mark {
|
||||
background-color: transparent;
|
||||
color: rgba(255, 122, 122, 1);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li mark::selection {
|
||||
color: rgba(#ffffff, 0);
|
||||
background-color: rgba(#ffffff, 0);
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li[aria-selected="true"] {
|
||||
background-color: rgba(123, 123, 123, 0.1);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.autoComplete_wrapper > input {
|
||||
width: 18rem;
|
||||
}
|
||||
}
|
82
app/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.02.css
vendored
Normal file
82
app/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.02.css
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
.autoComplete_wrapper {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > input {
|
||||
width: 370px;
|
||||
height: 40px;
|
||||
padding-left: 10px;
|
||||
font-size: 1rem;
|
||||
color: rgb(116, 116, 116);
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(33, 33, 33, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > input::placeholder {
|
||||
color: rgba(123, 123, 123, 0.5);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul {
|
||||
position: absolute;
|
||||
max-height: 226px;
|
||||
overflow-y: scroll;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0;
|
||||
margin: 0.5rem 0 0 0;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(33, 33, 33, 0.1);
|
||||
z-index: 1000;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li {
|
||||
padding: 10px 20px;
|
||||
list-style: none;
|
||||
text-align: left;
|
||||
font-size: 16px;
|
||||
color: #212121;
|
||||
transition: all 0.1s ease-in-out;
|
||||
border-radius: 3px;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li::selection {
|
||||
color: rgba(#ffffff, 0);
|
||||
background-color: rgba(#ffffff, 0);
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li:hover {
|
||||
cursor: pointer;
|
||||
background-color: rgba(123, 123, 123, 0.1);
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li mark {
|
||||
background-color: transparent;
|
||||
color: rgba(255, 122, 122, 1);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li mark::selection {
|
||||
color: rgba(#ffffff, 0);
|
||||
background-color: rgba(#ffffff, 0);
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li[aria-selected="true"] {
|
||||
background-color: rgba(123, 123, 123, 0.1);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.autoComplete_wrapper > input {
|
||||
width: 18rem;
|
||||
}
|
||||
}
|
128
app/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.css
vendored
Normal file
128
app/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.css
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
.autoComplete_wrapper {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > input {
|
||||
height: 3rem;
|
||||
width: 370px;
|
||||
margin: 0;
|
||||
padding: 0 2rem 0 3.2rem;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
font-size: 1rem;
|
||||
text-overflow: ellipsis;
|
||||
color: rgba(255, 122, 122, 0.3);
|
||||
outline: none;
|
||||
border-radius: 10rem;
|
||||
border: 0.05rem solid rgba(255, 122, 122, 0.5);
|
||||
background-image: url(./images/search.svg);
|
||||
background-size: 1.4rem;
|
||||
background-position: left 1.05rem top 0.8rem;
|
||||
background-repeat: no-repeat;
|
||||
background-origin: border-box;
|
||||
background-color: #fff;
|
||||
transition: all 0.4s ease;
|
||||
-webkit-transition: all -webkit-transform 0.4s ease;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > input::placeholder {
|
||||
color: rgba(255, 122, 122, 0.5);
|
||||
transition: all 0.3s ease;
|
||||
-webkit-transition: all -webkit-transform 0.3s ease;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > input:hover::placeholder {
|
||||
color: rgba(255, 122, 122, 0.6);
|
||||
transition: all 0.3s ease;
|
||||
-webkit-transition: all -webkit-transform 0.3s ease;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > input:focus::placeholder {
|
||||
padding: 0.1rem 0.6rem;
|
||||
font-size: 0.95rem;
|
||||
color: rgba(255, 122, 122, 0.4);
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > input:focus::selection {
|
||||
background-color: rgba(255, 122, 122, 0.15);
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > input::selection {
|
||||
background-color: rgba(255, 122, 122, 0.15);
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > input:hover {
|
||||
color: rgba(255, 122, 122, 0.8);
|
||||
transition: all 0.3s ease;
|
||||
-webkit-transition: all -webkit-transform 0.3s ease;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > input:focus {
|
||||
color: rgba(255, 122, 122, 1);
|
||||
border: 0.06rem solid rgba(255, 122, 122, 0.8);
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul {
|
||||
position: absolute;
|
||||
max-height: 226px;
|
||||
overflow-y: scroll;
|
||||
box-sizing: border-box;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0.5rem 0 0 0;
|
||||
padding: 0;
|
||||
z-index: 1;
|
||||
list-style: none;
|
||||
border-radius: 0.6rem;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(33, 33, 33, 0.07);
|
||||
box-shadow: 0 3px 6px rgba(149, 157, 165, 0.15);
|
||||
outline: none;
|
||||
transition: opacity 0.15s ease-in-out;
|
||||
-moz-transition: opacity 0.15s ease-in-out;
|
||||
-webkit-transition: opacity 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul[hidden],
|
||||
.autoComplete_wrapper > ul:empty {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li {
|
||||
margin: 0.3rem;
|
||||
padding: 0.3rem 0.5rem;
|
||||
text-align: left;
|
||||
font-size: 1rem;
|
||||
color: #212121;
|
||||
border-radius: 0.35rem;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li mark {
|
||||
background-color: transparent;
|
||||
color: rgba(255, 122, 122, 1);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li:hover {
|
||||
cursor: pointer;
|
||||
background-color: rgba(255, 122, 122, 0.15);
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li[aria-selected="true"] {
|
||||
background-color: rgba(255, 122, 122, 0.15);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.autoComplete_wrapper > input {
|
||||
width: 18rem;
|
||||
}
|
||||
}
|
8
app/static/libjs/autoComplete.js-10.2.7/dist/css/images/search.svg
vendored
Normal file
8
app/static/libjs/autoComplete.js-10.2.7/dist/css/images/search.svg
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" focusable="false" x="0px" y="0px" width="30" height="30" viewBox="0 0 171 171" style=" fill:#000000;">
|
||||
<g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal">
|
||||
<path d="M0,171.99609v-171.99609h171.99609v171.99609z" fill="none"></path>
|
||||
<g fill="#ff7a7a">
|
||||
<path d="M74.1,17.1c-31.41272,0 -57,25.58728 -57,57c0,31.41272 25.58728,57 57,57c13.6601,0 26.20509,-4.85078 36.03692,-12.90293l34.03301,34.03301c1.42965,1.48907 3.55262,2.08891 5.55014,1.56818c1.99752,-0.52073 3.55746,-2.08067 4.07819,-4.07819c0.52073,-1.99752 -0.0791,-4.12049 -1.56818,-5.55014l-34.03301,-34.03301c8.05215,-9.83182 12.90293,-22.37682 12.90293,-36.03692c0,-31.41272 -25.58728,-57 -57,-57zM74.1,28.5c25.2517,0 45.6,20.3483 45.6,45.6c0,25.2517 -20.3483,45.6 -45.6,45.6c-25.2517,0 -45.6,-20.3483 -45.6,-45.6c0,-25.2517 20.3483,-45.6 45.6,-45.6z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -479,17 +479,18 @@ class TableRecap(tb.Table):
|
||||
for row in self.rows:
|
||||
etud = row.etud
|
||||
admission = etud.admission.first()
|
||||
first = True
|
||||
for cid, title in fields.items():
|
||||
cell = row.add_cell(
|
||||
cid,
|
||||
title,
|
||||
getattr(admission, cid) or "",
|
||||
"admission",
|
||||
)
|
||||
if first:
|
||||
cell.classes.append("admission_first")
|
||||
first = False
|
||||
if admission:
|
||||
first = True
|
||||
for cid, title in fields.items():
|
||||
cell = row.add_cell(
|
||||
cid,
|
||||
title,
|
||||
getattr(admission, cid) or "",
|
||||
"admission",
|
||||
)
|
||||
if first:
|
||||
cell.classes.append("admission_first")
|
||||
first = False
|
||||
|
||||
def add_cursus(self):
|
||||
"""Ajoute colonne avec code cursus, eg 'S1 S2 S1'
|
||||
|
@ -85,7 +85,15 @@
|
||||
{{ super() }}
|
||||
{{ moment.include_moment() }}
|
||||
{{ moment.lang(g.locale) }}
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/menu.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/bubble.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/jQuery/jquery.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/jquery.field.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/js/scodoc.js"></script>
|
||||
<script>
|
||||
|
||||
var SCO_URL = "{{ url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)[:-11] }}";
|
||||
</script>
|
||||
{% endblock %}
|
@ -4,33 +4,27 @@
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/css/autosuggest_inquisitor.css" />
|
||||
<script src="/ScoDoc/static/libjs/AutoSuggest.js"></script>
|
||||
<link rel="stylesheet" href="/ScoDoc/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.02.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>{{ title }}</h1>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<p>
|
||||
(*) champs requis
|
||||
</p>
|
||||
{{ wtf.quick_form(form, novalidate=True) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{super()}}
|
||||
<script src="/ScoDoc/static/libjs/autoComplete.js-10.2.7/dist/autoComplete.min.js"></script>
|
||||
<script src="/ScoDoc/static/js/etud_autocomplete.js"></script>
|
||||
<script>
|
||||
window.onload = function (e) {
|
||||
var etudiants_options = {
|
||||
script: "/ScoDoc/entreprises/etudiants?",
|
||||
varname: "term",
|
||||
json: true,
|
||||
noresults: "Valeur invalide !",
|
||||
minchars: 2,
|
||||
timeout: 60000
|
||||
};
|
||||
var as_etudiants = new bsn.AutoSuggest('etudiant', etudiants_options);
|
||||
}
|
||||
const autoCompleteJS = new autoComplete(etud_autocomplete_config(with_dept = true));
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
<h2>Erreur !</h2>
|
||||
|
||||
{{ exc }}
|
||||
{{ exc | safe }}
|
||||
|
||||
<p>
|
||||
{% if g.scodoc_dept %}
|
||||
|
@ -496,7 +496,7 @@ sco_publish(
|
||||
@permission_required(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def formation_table_recap(formation_id, format="html"):
|
||||
return sco_formation_recap.formation_table_recap(formation_id, format="html")
|
||||
return sco_formation_recap.formation_table_recap(formation_id, format=format)
|
||||
|
||||
|
||||
sco_publish(
|
||||
@ -1874,12 +1874,6 @@ sco_publish(
|
||||
Permission.ScoEnsView,
|
||||
)
|
||||
sco_publish("/saisie_notes", sco_saisie_notes.saisie_notes, Permission.ScoEnsView)
|
||||
sco_publish(
|
||||
"/save_note",
|
||||
sco_saisie_notes.save_note,
|
||||
Permission.ScoEnsView,
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
sco_publish(
|
||||
"/do_evaluation_set_missing",
|
||||
sco_saisie_notes.do_evaluation_set_missing,
|
||||
|
@ -1306,6 +1306,8 @@ def _do_cancel_dem_or_def(
|
||||
db.session.delete(event)
|
||||
|
||||
db.session.commit()
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
||||
|
||||
flash(f"{operation_name} annulée.")
|
||||
return flask.redirect(
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
@ -1755,9 +1757,7 @@ def _etudident_create_or_edit_form(edit):
|
||||
# Inval semesters with this student:
|
||||
to_inval = [s["formsemestre_id"] for s in etud["sems"]]
|
||||
for formsemestre_id in to_inval:
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=formsemestre_id
|
||||
) # > etudident_create_or_edit
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
||||
#
|
||||
return flask.redirect(
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
@ -1833,7 +1833,7 @@ def etudident_delete(etudid, dialog_confirmed=False):
|
||||
# Inval semestres où il était inscrit:
|
||||
to_inval = [s["formsemestre_id"] for s in etud["sems"]]
|
||||
for formsemestre_id in to_inval:
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id) # >
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
||||
flash("Étudiant supprimé !")
|
||||
return flask.redirect(scu.ScoURL())
|
||||
|
||||
|
77
migrations/versions/d84bc592584e_extension_unaccent.py
Normal file
77
migrations/versions/d84bc592584e_extension_unaccent.py
Normal file
@ -0,0 +1,77 @@
|
||||
"""Extension unaccent
|
||||
|
||||
Revision ID: d84bc592584e
|
||||
Revises: b8df1b913c79
|
||||
Create Date: 2023-06-01 13:46:52.927951
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm import sessionmaker # added by ev
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "d84bc592584e"
|
||||
down_revision = "b8df1b913c79"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
Session = sessionmaker()
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind)
|
||||
# Ajout extension pour recherches sans accents:
|
||||
session.execute(sa.text("""CREATE EXTENSION IF NOT EXISTS "unaccent";"""))
|
||||
|
||||
# Clé étrangère sur identite
|
||||
session.execute(
|
||||
sa.text(
|
||||
"""UPDATE are_stages_apprentissages
|
||||
SET etudid = NULL
|
||||
WHERE are_stages_apprentissages.etudid NOT IN (
|
||||
SELECT id
|
||||
FROM Identite
|
||||
);
|
||||
"""
|
||||
)
|
||||
)
|
||||
with op.batch_alter_table("are_stages_apprentissages", schema=None) as batch_op:
|
||||
batch_op.create_foreign_key(
|
||||
"are_stages_apprentissages_etudid_fkey",
|
||||
"identite",
|
||||
["etudid"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
# Les montants de taxe en float:
|
||||
with op.batch_alter_table("are_taxe_apprentissage", schema=None) as batch_op:
|
||||
batch_op.alter_column(
|
||||
"montant",
|
||||
existing_type=sa.INTEGER(),
|
||||
type_=sa.Float(),
|
||||
existing_nullable=True,
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("are_taxe_apprentissage", schema=None) as batch_op:
|
||||
batch_op.alter_column(
|
||||
"montant",
|
||||
existing_type=sa.Float(),
|
||||
type_=sa.INTEGER(),
|
||||
existing_nullable=True,
|
||||
)
|
||||
|
||||
with op.batch_alter_table("are_stages_apprentissages", schema=None) as batch_op:
|
||||
batch_op.drop_constraint(
|
||||
"are_stages_apprentissages_etudid_fkey", type_="foreignkey"
|
||||
)
|
||||
|
||||
# ### end Alembic commands ###
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.4.77"
|
||||
SCOVERSION = "9.4.82"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
@ -224,6 +224,32 @@ def test_etudiants(api_headers):
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
def test_etudiants_by_name(api_headers):
|
||||
"""
|
||||
Route: /etudiants/name/<string:start>
|
||||
"""
|
||||
r = requests.get(
|
||||
API_URL + "/etudiants/name/A",
|
||||
headers=api_headers,
|
||||
verify=CHECK_CERTIFICATE,
|
||||
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||
)
|
||||
assert r.status_code == 200
|
||||
etuds = r.json()
|
||||
assert etuds == []
|
||||
#
|
||||
r = requests.get(
|
||||
API_URL + "/etudiants/name/REG",
|
||||
headers=api_headers,
|
||||
verify=CHECK_CERTIFICATE,
|
||||
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||
)
|
||||
assert r.status_code == 200
|
||||
etuds = r.json()
|
||||
assert len(etuds) == 1
|
||||
assert etuds[0]["nom"] == "RÉGNIER"
|
||||
|
||||
|
||||
def test_etudiant_formsemestres(api_headers):
|
||||
"""
|
||||
Route: /etudiant/etudid/<etudid:int>/formsemestres
|
||||
|
@ -24,7 +24,7 @@ from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
|
||||
from tests.api.tools_test_api import (
|
||||
verify_fields,
|
||||
EVALUATIONS_FIELDS,
|
||||
EVALUATION_FIELDS,
|
||||
NOTES_FIELDS,
|
||||
)
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ def test_evaluations(api_headers):
|
||||
Route :
|
||||
- /moduleimpl/<int:moduleimpl_id>/evaluations
|
||||
"""
|
||||
moduleimpl_id = 1
|
||||
moduleimpl_id = 20
|
||||
r = requests.get(
|
||||
f"{API_URL}/moduleimpl/{moduleimpl_id}/evaluations",
|
||||
headers=api_headers,
|
||||
@ -44,6 +44,7 @@ def test_evaluations(api_headers):
|
||||
)
|
||||
assert r.status_code == 200
|
||||
list_eval = r.json()
|
||||
assert list_eval
|
||||
assert isinstance(list_eval, list)
|
||||
for eval in list_eval:
|
||||
assert verify_fields(eval, EVALUATIONS_FIELDS) is True
|
||||
@ -63,16 +64,14 @@ def test_evaluations(api_headers):
|
||||
assert eval["moduleimpl_id"] == moduleimpl_id
|
||||
|
||||
|
||||
def test_evaluation_notes(
|
||||
api_headers,
|
||||
): # XXX TODO changer la boucle pour parcourir le dict sans les indices
|
||||
def test_evaluation_notes(api_headers):
|
||||
"""
|
||||
Test 'evaluation_notes'
|
||||
|
||||
Route :
|
||||
- /evaluation/<int:evaluation_id>/notes
|
||||
"""
|
||||
eval_id = 1
|
||||
eval_id = 20
|
||||
r = requests.get(
|
||||
f"{API_URL}/evaluation/{eval_id}/notes",
|
||||
headers=api_headers,
|
||||
@ -81,14 +80,15 @@ def test_evaluation_notes(
|
||||
)
|
||||
assert r.status_code == 200
|
||||
eval_notes = r.json()
|
||||
for i in range(1, len(eval_notes)):
|
||||
assert verify_fields(eval_notes[f"{i}"], EVALUATION_FIELDS)
|
||||
assert isinstance(eval_notes[f"{i}"]["id"], int)
|
||||
assert isinstance(eval_notes[f"{i}"]["etudid"], int)
|
||||
assert isinstance(eval_notes[f"{i}"]["evaluation_id"], int)
|
||||
assert isinstance(eval_notes[f"{i}"]["value"], float)
|
||||
assert isinstance(eval_notes[f"{i}"]["comment"], str)
|
||||
assert isinstance(eval_notes[f"{i}"]["date"], str)
|
||||
assert isinstance(eval_notes[f"{i}"]["uid"], int)
|
||||
assert eval_notes
|
||||
for etudid, note in eval_notes.items():
|
||||
assert int(etudid) == note["etudid"]
|
||||
assert verify_fields(note, NOTES_FIELDS)
|
||||
assert isinstance(note["etudid"], int)
|
||||
assert isinstance(note["evaluation_id"], int)
|
||||
assert isinstance(note["value"], float)
|
||||
assert isinstance(note["comment"], str)
|
||||
assert isinstance(note["date"], str)
|
||||
assert isinstance(note["uid"], int)
|
||||
|
||||
assert eval_id == eval_notes[f"{i}"]["evaluation_id"]
|
||||
assert eval_id == note["evaluation_id"]
|
||||
|
@ -58,6 +58,7 @@ def test_permissions(api_headers):
|
||||
"nip": 1,
|
||||
"partition_id": 1,
|
||||
"role_name": "Ens",
|
||||
"start": "abc",
|
||||
"uid": 1,
|
||||
"version": "long",
|
||||
}
|
||||
|
@ -568,8 +568,7 @@ EVALUATIONS_FIELDS = {
|
||||
"visi_bulletin",
|
||||
}
|
||||
|
||||
EVALUATION_FIELDS = {
|
||||
"id",
|
||||
NOTES_FIELDS = {
|
||||
"etudid",
|
||||
"evaluation_id",
|
||||
"value",
|
||||
|
554
tests/ressources/formations/scodoc_formation_BUT_INFO_v0514.xml
Normal file
554
tests/ressources/formations/scodoc_formation_BUT_INFO_v0514.xml
Normal file
@ -0,0 +1,554 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<formation acronyme="BUT INFO" titre_officiel="Bachelor Universitaire de Technologie" commentaire="Exemple incomplet. À partir du S4, seuls les parcours A et B ont des UEs." type_parcours="700" referentiel_competence_id="1" titre="BUT INFORMATIQUE" version="1" formation_code="FCOD49" code_specialite="" refcomp_version_orebut="2021-12-11 00:00:00" refcomp_specialite="INFO" refcomp_type_titre="B.U.T.">
|
||||
<ue numero="0" code_apogee="V1INFU11" titre="Compétence 1 : Réaliser un développement d'application" coefficient="0.0" semestre_idx="1" coef_rcue="1.0" type="0" color="#b80004" ue_code="UCOD62" ects="5.0" is_external="0" acronyme="UE11" apc_niveau_libelle="Développer des applications informatiques simples " apc_niveau_annee="BUT1" apc_niveau_ordre="1" reference="1">
|
||||
<matiere titre="Portfolio" numero="0">
|
||||
<module titre="Initiation au développement" abbrev="Initiation au dev." code="R1.01-A" heures_cours="0.0" heures_td="24.0" heures_tp="30.0" coefficient="66.0" ects="" semestre_id="1" numero="10" code_apogee="VINFR101" module_type="2">
|
||||
<coefficients ue_reference="2" coef="12.0" />
|
||||
<coefficients ue_reference="1" coef="21.0" />
|
||||
</module>
|
||||
<module titre="Implémentation d'un besoin client" abbrev="Implémentation" code="S1.01" heures_cours="0.0" heures_td="2.0" heures_tp="2.0" coefficient="40.0" ects="" semestre_id="1" numero="10" code_apogee="VINFS101" module_type="3">
|
||||
<coefficients ue_reference="1" coef="40.0" />
|
||||
</module>
|
||||
<module titre="Portfolio" abbrev="" code="P1" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="1" numero="700" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="3" coef="1.0" />
|
||||
<coefficients ue_reference="1" coef="1.0" />
|
||||
<coefficients ue_reference="6" coef="1.0" />
|
||||
<coefficients ue_reference="5" coef="1.0" />
|
||||
<coefficients ue_reference="2" coef="1.0" />
|
||||
<coefficients ue_reference="4" coef="1.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="1" code_apogee="V1INFU12" titre="Compétence 2 : Optimiser des applications informatiques" coefficient="0.0" semestre_idx="1" coef_rcue="1.0" type="0" color="#f97b3d" ue_code="UCOD61" ects="5.0" is_external="0" acronyme="UE12" apc_niveau_libelle="Appréhender et construire des algorithmes " apc_niveau_annee="BUT1" apc_niveau_ordre="1" reference="2">
|
||||
<matiere titre="Ressource" numero="2">
|
||||
<module titre="Initiation au développement" abbrev="Initiation au dev." code="R1.01-B" heures_cours="0.0" heures_td="16.0" heures_tp="24.0" coefficient="66.0" ects="" semestre_id="1" numero="20" code_apogee="VINFR101" module_type="2">
|
||||
<coefficients ue_reference="2" coef="12.0" />
|
||||
<coefficients ue_reference="1" coef="21.0" />
|
||||
</module>
|
||||
<module titre="Comparaison d'approches algorithmiques" abbrev="Comparaison d'algo." code="S1.02" heures_cours="0.0" heures_td="2.0" heures_tp="2.0" coefficient="40.0" ects="" semestre_id="1" numero="20" code_apogee="VINFS102" module_type="3">
|
||||
<coefficients ue_reference="2" coef="40.0" />
|
||||
</module>
|
||||
<module titre="Développement d'interfaces web" abbrev="Dev. interfaces web" code="R1.02" heures_cours="0.0" heures_td="5.0" heures_tp="14.0" coefficient="35.0" ects="" semestre_id="1" numero="30" code_apogee="VINFR102" module_type="2">
|
||||
<coefficients ue_reference="1" coef="12.0" />
|
||||
<coefficients ue_reference="5" coef="18.0" />
|
||||
<coefficients ue_reference="6" coef="5.0" />
|
||||
</module>
|
||||
<module titre="Mathématiques discrètes" abbrev="Maths discrètes" code="R1.06" heures_cours="0.0" heures_td="30.0" heures_tp="10.0" coefficient="33.0" ects="" semestre_id="1" numero="70" code_apogee="VINFR106" module_type="2">
|
||||
<coefficients ue_reference="2" coef="15.0" />
|
||||
<coefficients ue_reference="4" coef="18.0" />
|
||||
</module>
|
||||
<module titre="Outils mathématiques fondamentaux" abbrev="Outils fondamentaux" code="R1.07" heures_cours="0.0" heures_td="14.0" heures_tp="10.0" coefficient="15.0" ects="" semestre_id="1" numero="80" code_apogee="VINFR107" module_type="2">
|
||||
<coefficients ue_reference="2" coef="15.0" />
|
||||
</module>
|
||||
<module titre="Gestion de projet & des organisations" abbrev="Gestion proj. orga." code="R1.08" heures_cours="0.0" heures_td="21.0" heures_tp="10.0" coefficient="38.0" ects="" semestre_id="1" numero="90" code_apogee="VINFR108" module_type="2">
|
||||
<coefficients ue_reference="5" coef="27.0" />
|
||||
<coefficients ue_reference="6" coef="11.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="2" code_apogee="V1INFU13" titre="Compétence 3 : Administrer des systèmes informatiques communicants" coefficient="0.0" semestre_idx="1" coef_rcue="1.0" type="0" color="#feb40b" ue_code="UCOD50" ects="5.0" is_external="0" acronyme="UE13" apc_niveau_libelle="Installer et configurer un poste de travail " apc_niveau_annee="BUT1" apc_niveau_ordre="1" reference="3">
|
||||
<matiere titre="SAE" numero="1">
|
||||
<module titre="Installation d'un poste pour le développement" abbrev="Installation poste" code="S1.03" heures_cours="0.0" heures_td="5.0" heures_tp="4.0" coefficient="40.0" ects="" semestre_id="1" numero="30" code_apogee="VINFS103" module_type="3">
|
||||
<coefficients ue_reference="3" coef="40.0" />
|
||||
</module>
|
||||
<module titre="Introduction à l'architecture des ordinateurs" abbrev="Intro. archi." code="R1.03" heures_cours="0.0" heures_td="12.0" heures_tp="8.0" coefficient="24.0" ects="" semestre_id="1" numero="40" code_apogee="VINFR103" module_type="2">
|
||||
<coefficients ue_reference="3" coef="21.0" />
|
||||
<coefficients ue_reference="2" coef="6.0" />
|
||||
</module>
|
||||
<module titre="Introduction aux systèmes d’exploitation et à leur fonctionnement" abbrev="Intro. systèmes" code="R1.04" heures_cours="0.0" heures_td="5.0" heures_tp="20.0" coefficient="24.0" ects="" semestre_id="1" numero="50" code_apogee="VINFR104" module_type="2">
|
||||
<coefficients ue_reference="3" coef="21.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="3" code_apogee="V1INFU14" titre="Compétence 4 : Gérer des données de l'information" coefficient="0.0" semestre_idx="1" coef_rcue="1.0" type="0" color="#80cb3f" ue_code="UCOD10" ects="5.0" is_external="0" acronyme="UE14" apc_niveau_libelle="Concevoir et mettre en place une base de données à partir d’un cahier des charges client " apc_niveau_annee="BUT1" apc_niveau_ordre="1" reference="4">
|
||||
<matiere titre="Ressources" numero="0">
|
||||
<module titre="Création d'une base de données" abbrev="Création BD" code="S1.04" heures_cours="0.0" heures_td="4.0" heures_tp="2.0" coefficient="40.0" ects="" semestre_id="1" numero="40" code_apogee="VINFS104" module_type="3">
|
||||
<coefficients ue_reference="4" coef="40.0" />
|
||||
</module>
|
||||
<module titre="Introduction aux bases de données et SQL" abbrev="Introduction BD" code="R1.05" heures_cours="12.0" heures_td="9.0" heures_tp="24.0" coefficient="36.0" ects="" semestre_id="1" numero="60" code_apogee="VINFR105" module_type="2">
|
||||
<coefficients ue_reference="4" coef="36.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="4" code_apogee="V1INFU15" titre="Compétence 5 : Conduire un projet" coefficient="0.0" semestre_idx="1" coef_rcue="1.0" type="0" color="#05162e" ue_code="UCOD15" ects="5.0" is_external="0" acronyme="UE15" apc_niveau_libelle="Identifier les besoins métiers des clients et des utilisateurs " apc_niveau_annee="BUT1" apc_niveau_ordre="1" reference="5">
|
||||
<matiere titre="Compétence 5 : Conduire un projet" numero="1">
|
||||
<module titre="Recueil de besoins" abbrev="Recueil de besoins" code="S1.05" heures_cours="0.0" heures_td="4.0" heures_tp="2.0" coefficient="40.0" ects="" semestre_id="1" numero="50" code_apogee="VINFS105" module_type="3">
|
||||
<coefficients ue_reference="5" coef="40.0" />
|
||||
</module>
|
||||
<module titre="Économie durable et numérique" abbrev="Économie" code="R1.09" heures_cours="0.0" heures_td="18.0" heures_tp="5.0" coefficient="17.0" ects="" semestre_id="1" numero="100" code_apogee="VINFR109" module_type="2">
|
||||
<coefficients ue_reference="4" coef="6.0" />
|
||||
<coefficients ue_reference="6" coef="11.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="5" code_apogee="V1INFU16" titre="Compétence 6 : Travailler dans une équipe informatique" coefficient="0.0" semestre_idx="1" coef_rcue="1.0" type="0" color="#548687" ue_code="UCOD16" ects="5.0" is_external="0" acronyme="UE16" apc_niveau_libelle="Identifier ses aptitudes pour travailler dans une équipe " apc_niveau_annee="BUT1" apc_niveau_ordre="1" reference="6">
|
||||
<matiere titre="Compétence 6 : Travailler dans une équipe informatique" numero="1">
|
||||
<module titre="Découverte de l'environnement économique et écologique" abbrev="Environnement éco." code="S1.06" heures_cours="0.0" heures_td="0.0" heures_tp="6.0" coefficient="40.0" ects="" semestre_id="1" numero="60" code_apogee="VINFS106" module_type="3">
|
||||
<coefficients ue_reference="6" coef="40.0" />
|
||||
</module>
|
||||
<module titre="Anglais technique" abbrev="Anglais technique" code="R1.10" heures_cours="0.0" heures_td="15.0" heures_tp="9.0" coefficient="29.0" ects="" semestre_id="1" numero="110" code_apogee="VINFR110" module_type="2">
|
||||
<coefficients ue_reference="3" coef="12.0" />
|
||||
<coefficients ue_reference="1" coef="6.0" />
|
||||
<coefficients ue_reference="6" coef="11.0" />
|
||||
</module>
|
||||
<module titre="Bases de la communication" abbrev="Bases de la comm" code="R1.11" heures_cours="0.0" heures_td="15.0" heures_tp="9.0" coefficient="32.0" ects="" semestre_id="1" numero="120" code_apogee="VINFR111" module_type="2">
|
||||
<coefficients ue_reference="3" coef="6.0" />
|
||||
<coefficients ue_reference="5" coef="15.0" />
|
||||
<coefficients ue_reference="6" coef="11.0" />
|
||||
</module>
|
||||
<module titre="Projet professionnel et personnel" abbrev="PPP" code="R1.12" heures_cours="0.0" heures_td="8.0" heures_tp="2.0" coefficient="11.0" ects="" semestre_id="1" numero="130" code_apogee="VINFR112" module_type="2">
|
||||
<coefficients ue_reference="6" coef="11.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="6" code_apogee="" titre="Sport/Culture" coefficient="0.0" semestre_idx="1" coef_rcue="1.0" type="1" color="#ff4500" ue_code="X7.2" ects="0.0" is_external="0" acronyme="UE17" reference="7">
|
||||
<matiere titre="Sport/Culture" numero="1">
|
||||
<module titre="Sport / Culture" abbrev="Sport" code="X1.01" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="1" numero="520" code_apogee="" module_type="0" />
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="7" code_apogee="V1INFU21" titre="Compétence 1: Réaliser un développement d'application" coefficient="0.0" semestre_idx="2" coef_rcue="1.0" type="0" color="#b80004" ue_code="UCOD17" ects="5.0" is_external="0" acronyme="UE21" apc_niveau_libelle="Développer des applications informatiques simples " apc_niveau_annee="BUT1" apc_niveau_ordre="1" reference="8">
|
||||
<matiere titre="Compétence 1: Réaliser un développement d'application" numero="1">
|
||||
<module titre="Développement orienté objets" abbrev="Développement orienté objets" code="R2.01" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="140" code_apogee="VINFR201" module_type="2">
|
||||
<coefficients ue_reference="8" coef="21.0" />
|
||||
<coefficients ue_reference="9" coef="15.0" />
|
||||
</module>
|
||||
<module titre="Développement d'applications avec IHM" abbrev="Développement d'applications avec IHM" code="R2.02" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="150" code_apogee="VINFR202" module_type="2">
|
||||
<coefficients ue_reference="8" coef="21.0" />
|
||||
<coefficients ue_reference="12" coef="3.0" />
|
||||
<coefficients ue_reference="13" coef="4.0" />
|
||||
</module>
|
||||
<module titre="Qualité de développement" abbrev="Qualité de développement" code="R2.03" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="160" code_apogee="VINFR203" module_type="2">
|
||||
<coefficients ue_reference="8" coef="12.0" />
|
||||
<coefficients ue_reference="12" coef="6.0" />
|
||||
</module>
|
||||
<module titre="Développement d'une application" abbrev="Développement d'une application" code="S2.01" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="280" code_apogee="VINFS201" module_type="3">
|
||||
<coefficients ue_reference="8" coef="40.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="8" code_apogee="V1INFU22" titre="Compétence 2 : Optimiser des applications informatiques" coefficient="0.0" semestre_idx="2" coef_rcue="1.0" type="0" color="#f97b3d" ue_code="UCOD18" ects="5.0" is_external="0" acronyme="UE22" apc_niveau_libelle="Appréhender et construire des algorithmes " apc_niveau_annee="BUT1" apc_niveau_ordre="1" reference="9">
|
||||
<matiere titre="Compétence 2 : Optimiser des applications informatiques" numero="1">
|
||||
<module titre="Graphes" abbrev="Graphes" code="R2.07" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="210" code_apogee="VINFR207" module_type="2">
|
||||
<coefficients ue_reference="9" coef="21.0" />
|
||||
<coefficients ue_reference="12" coef="6.0" />
|
||||
</module>
|
||||
<module titre="Méthodes numériques" abbrev="Méthodes numériques" code="R2.09" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="230" code_apogee="VINFR209" module_type="2">
|
||||
<coefficients ue_reference="9" coef="12.0" />
|
||||
</module>
|
||||
<module titre="Exploration algorithmique d'un problème" abbrev="Exploration algorithmique d'un problème" code="S2.02" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="290" code_apogee="VINFS202" module_type="3">
|
||||
<coefficients ue_reference="9" coef="40.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="9" code_apogee="V1INFU23" titre="Compétence 3 : Administrer des systèmes informatiques communicants" coefficient="0.0" semestre_idx="2" coef_rcue="1.0" type="0" color="#feb40b" ue_code="UCOD19" ects="5.0" is_external="0" acronyme="UE23" apc_niveau_libelle="Installer et configurer un poste de travail " apc_niveau_annee="BUT1" apc_niveau_ordre="1" reference="10">
|
||||
<matiere titre="Compétence 3 : Administrer des systèmes informatiques communicants" numero="1">
|
||||
<module titre="Communication et fonctionnement bas niveau" abbrev="Communication et fonctionnement bas niveau" code="R2.04-A" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="170" code_apogee="VINFR204" module_type="2">
|
||||
<coefficients ue_reference="9" coef="6.0" />
|
||||
<coefficients ue_reference="10" coef="18.0" />
|
||||
</module>
|
||||
<module titre="Communication et fonctionnement bas niveau" abbrev="Communication et fonctionnement bas niveau" code="R2.04-B" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="180" code_apogee="VINFR204" module_type="2">
|
||||
<coefficients ue_reference="9" coef="6.0" />
|
||||
<coefficients ue_reference="10" coef="18.0" />
|
||||
</module>
|
||||
<module titre="Introduction aux services réseaux" abbrev="Introduction aux services réseaux" code="R2.05" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="190" code_apogee="VINFR205" module_type="2">
|
||||
<coefficients ue_reference="10" coef="15.0" />
|
||||
</module>
|
||||
<module titre="Installation de services réseau" abbrev="Installation de services réseau" code="S2.03" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="300" code_apogee="VINFS203" module_type="3">
|
||||
<coefficients ue_reference="10" coef="40.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="10" code_apogee="V1INFU24" titre="Compétence 4 : Gérer des données de l'information" coefficient="0.0" semestre_idx="2" coef_rcue="1.0" type="0" color="#80cb3f" ue_code="UCOD20" ects="5.0" is_external="0" acronyme="UE24" apc_niveau_libelle="Concevoir et mettre en place une base de données à partir d’un cahier des charges client " apc_niveau_annee="BUT1" apc_niveau_ordre="1" reference="11">
|
||||
<matiere titre="Compétence 4 : Gérer des données de l'information" numero="1">
|
||||
<module titre="Exploitation d'une base de données" abbrev="Exploitation d'une base de données" code="R2.06" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="200" code_apogee="VINFR206" module_type="2">
|
||||
<coefficients ue_reference="11" coef="30.0" />
|
||||
</module>
|
||||
<module titre="Outils numériques pour les statistiques descriptives" abbrev="Outils numériques pour les statistiques descriptives" code="R2.08" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="220" code_apogee="VINFR208" module_type="2">
|
||||
<coefficients ue_reference="11" coef="12.0" />
|
||||
</module>
|
||||
<module titre="Exploitation d'une base de données" abbrev="Exploitation d'une base de données" code="S2.04" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="310" code_apogee="VINFS204" module_type="3">
|
||||
<coefficients ue_reference="11" coef="40.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="11" code_apogee="V1INFU25" titre="Compétence 5 : Conduire un projet" coefficient="0.0" semestre_idx="2" coef_rcue="1.0" type="0" color="#05162e" ue_code="UCOD21" ects="5.0" is_external="0" acronyme="UE25" apc_niveau_libelle="Identifier les besoins métiers des clients et des utilisateurs " apc_niveau_annee="BUT1" apc_niveau_ordre="1" reference="12">
|
||||
<matiere titre="Compétence 5 : Conduire un projet" numero="1">
|
||||
<module titre="Gestion de projet & des organisations" abbrev="Gestion de projet & des organisations" code="R2.10" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="240" code_apogee="VINFR210" module_type="2">
|
||||
<coefficients ue_reference="11" coef="12.0" />
|
||||
<coefficients ue_reference="12" coef="30.0" />
|
||||
</module>
|
||||
<module titre="Anglais d'entreprise" abbrev="Anglais d'entreprise" code="R2.12" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="260" code_apogee="VINFR212" module_type="2">
|
||||
<coefficients ue_reference="10" coef="6.0" />
|
||||
<coefficients ue_reference="11" coef="6.0" />
|
||||
<coefficients ue_reference="12" coef="6.0" />
|
||||
<coefficients ue_reference="13" coef="17.0" />
|
||||
</module>
|
||||
<module titre="Gestion d'un projet" abbrev="Gestion d'un projet" code="S2.05" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="320" code_apogee="VINFS205" module_type="3">
|
||||
<coefficients ue_reference="12" coef="40.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="12" code_apogee="V1INFU26" titre="Compétence 6 : Travailler dans une équipe informatique" coefficient="0.0" semestre_idx="2" coef_rcue="1.0" type="0" color="#548687" ue_code="UCOD22" ects="5.0" is_external="0" acronyme="UE26" apc_niveau_libelle="Identifier ses aptitudes pour travailler dans une équipe " apc_niveau_annee="BUT1" apc_niveau_ordre="1" reference="13">
|
||||
<matiere titre="Compétence 6 : Travailler dans une équipe informatique" numero="1">
|
||||
<module titre="Droit des contrats et du numérique" abbrev="Droit des contrats et du numérique" code="R2.11" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="250" code_apogee="VINFR211" module_type="2">
|
||||
<coefficients ue_reference="13" coef="17.0" />
|
||||
</module>
|
||||
<module titre="Communication avec le milieu professionnel" abbrev="Communication avec le milieu professionnel" code="R2.13" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="270" code_apogee="VINFR213" module_type="2">
|
||||
<coefficients ue_reference="8" coef="6.0" />
|
||||
<coefficients ue_reference="10" coef="3.0" />
|
||||
<coefficients ue_reference="12" coef="9.0" />
|
||||
<coefficients ue_reference="13" coef="11.0" />
|
||||
</module>
|
||||
<module titre="Organisation d'un travail d'équipe" abbrev="Organisation d'un travail d'équipe" code="S2.06" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="330" code_apogee="VINFS206" module_type="3">
|
||||
<coefficients ue_reference="13" coef="40.0" />
|
||||
</module>
|
||||
<module titre="Portfolio" abbrev="Portfolio" code="P2" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="340" code_apogee="VINFPOR2" module_type="3">
|
||||
<coefficients ue_reference="8" coef="1.0" />
|
||||
<coefficients ue_reference="9" coef="1.0" />
|
||||
<coefficients ue_reference="10" coef="1.0" />
|
||||
<coefficients ue_reference="11" coef="1.0" />
|
||||
<coefficients ue_reference="12" coef="1.0" />
|
||||
<coefficients ue_reference="13" coef="1.0" />
|
||||
</module>
|
||||
<module titre="Projet professionnel et personnel : métiers de l'informatique" abbrev="PPP" code="R2.14" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="2" numero="350" code_apogee="VINFR214" module_type="2">
|
||||
<coefficients ue_reference="13" coef="11.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="13" code_apogee="" titre="Compétence 1 : Réaliser un développement d'application" coefficient="0.0" semestre_idx="3" coef_rcue="1.0" type="0" color="#b80004" ue_code="UCOD71" ects="5.0" is_external="0" acronyme="UE31" apc_niveau_libelle="Partir des exigences et aller jusqu’à une application complète " apc_niveau_annee="BUT2" apc_niveau_ordre="2" reference="14">
|
||||
<matiere titre="Compétence 1 : Réaliser un développement d'application" numero="1" />
|
||||
</ue>
|
||||
<ue numero="14" code_apogee="" titre="Compétence 2 : Optimiser des applications informatiques" coefficient="0.0" semestre_idx="3" coef_rcue="1.0" type="0" color="#f97b3d" ue_code="UCOD79" ects="5.0" is_external="0" acronyme="UE32" apc_niveau_libelle="Sélectionner les algorithmes adéquats pour répondre à un problème donné " apc_niveau_annee="BUT2" apc_niveau_ordre="2" reference="15">
|
||||
<matiere titre="Compétence 2 : Optimiser des applications informatiques" numero="1" />
|
||||
</ue>
|
||||
<ue numero="15" code_apogee="" titre="Compétence 3 : Administrer des systèmes informatiques communicants" coefficient="0.0" semestre_idx="3" coef_rcue="1.0" type="0" color="#feb40b" ue_code="UCOD102" ects="5.0" is_external="0" acronyme="UE33" apc_niveau_libelle="Déployer des services dans une architecture réseau" apc_niveau_annee="BUT2" apc_niveau_ordre="2" reference="16">
|
||||
<matiere titre="Compétence 3 : Administrer des systèmes informatiques communicants" numero="1" />
|
||||
</ue>
|
||||
<ue numero="16" code_apogee="" titre="Compétence 4 : Gérer des données de l'information" coefficient="0.0" semestre_idx="3" coef_rcue="1.0" type="0" color="#80cb3f" ue_code="UCOD103" ects="5.0" is_external="0" acronyme="UE34" apc_niveau_libelle="Optimiser une base de données, interagir avec une application et mettre en œuvre la sécurité " apc_niveau_annee="BUT2" apc_niveau_ordre="2" reference="17">
|
||||
<matiere titre="Compétence 4 : Gérer des données de l'information" numero="1" />
|
||||
</ue>
|
||||
<ue numero="17" code_apogee="" titre="Compétence 5 : Conduire un projet" coefficient="0.0" semestre_idx="3" coef_rcue="1.0" type="0" color="#05162e" ue_code="UCOD104" ects="5.0" is_external="0" acronyme="UE35" apc_niveau_libelle="Appliquer une démarche de suivi de projet en fonction des besoins métiers des clients et des utilisateurs " apc_niveau_annee="BUT2" apc_niveau_ordre="2" reference="18">
|
||||
<matiere titre="Compétence 5 : Conduire un projet" numero="1" />
|
||||
</ue>
|
||||
<ue numero="18" code_apogee="" titre="Compétence 6 : Travailler dans une équipe informatique" coefficient="0.0" semestre_idx="3" coef_rcue="1.0" type="0" color="#548687" ue_code="UCOD105" ects="5.0" is_external="0" acronyme="UE36" apc_niveau_libelle="Situer son rôle et ses missions au sein d’une équipe informatique " apc_niveau_annee="BUT2" apc_niveau_ordre="2" reference="19">
|
||||
<matiere titre="Compétence 6 : Travailler dans une équipe informatique" numero="1">
|
||||
<module titre="Développement web" abbrev="Développement web" code="R3.01" heures_cours="0.0" heures_td="8.0" heures_tp="25.0" coefficient="0.0" ects="" semestre_id="3" numero="360" code_apogee="VINFR301" module_type="2">
|
||||
<coefficients ue_reference="14" coef="15.0" />
|
||||
<coefficients ue_reference="15" coef="5.0" />
|
||||
<coefficients ue_reference="16" coef="5.0" />
|
||||
<coefficients ue_reference="17" coef="10.0" />
|
||||
</module>
|
||||
<module titre="Développement efficace" abbrev="Développement efficace" code="R3.02" heures_cours="0.0" heures_td="8.0" heures_tp="8.0" coefficient="0.0" ects="" semestre_id="3" numero="370" code_apogee="VINFR302" module_type="2">
|
||||
<coefficients ue_reference="14" coef="10.0" />
|
||||
<coefficients ue_reference="15" coef="13.0" />
|
||||
</module>
|
||||
<module titre="Analyse" abbrev="Analyse" code="R3.03" heures_cours="0.0" heures_td="8.0" heures_tp="8.0" coefficient="0.0" ects="" semestre_id="3" numero="380" code_apogee="VINFR303" module_type="2">
|
||||
<coefficients ue_reference="14" coef="12.0" />
|
||||
<coefficients ue_reference="15" coef="5.0" />
|
||||
<coefficients ue_reference="18" coef="10.0" />
|
||||
</module>
|
||||
<module titre="Qualité de développement" abbrev="Qualité de développement" code="R3.04" heures_cours="0.0" heures_td="16.0" heures_tp="24.0" coefficient="0.0" ects="" semestre_id="3" numero="390" code_apogee="VINFR304" module_type="2">
|
||||
<coefficients ue_reference="14" coef="15.0" />
|
||||
<coefficients ue_reference="18" coef="8.0" />
|
||||
<coefficients ue_reference="19" coef="5.0" />
|
||||
</module>
|
||||
<module titre="Programmation système" abbrev="Programmation système" code="R3.05" heures_cours="0.0" heures_td="12.0" heures_tp="12.0" coefficient="0.0" ects="" semestre_id="3" numero="400" code_apogee="VINFR305" module_type="2">
|
||||
<coefficients ue_reference="16" coef="22.0" />
|
||||
</module>
|
||||
<module titre="Architecture des réseaux" abbrev="Architecture des réseaux" code="R3.06" heures_cours="0.0" heures_td="8.0" heures_tp="8.0" coefficient="0.0" ects="" semestre_id="3" numero="410" code_apogee="VINFR306" module_type="2">
|
||||
<coefficients ue_reference="15" coef="5.0" />
|
||||
<coefficients ue_reference="16" coef="18.0" />
|
||||
</module>
|
||||
<module titre="SQL dans un langage de programmation" abbrev="SQL dans un langage de programmation" code="R3.07" heures_cours="0.0" heures_td="12.0" heures_tp="16.0" coefficient="0.0" ects="" semestre_id="3" numero="420" code_apogee="VINFR307" module_type="2">
|
||||
<coefficients ue_reference="17" coef="25.0" />
|
||||
</module>
|
||||
<module titre="Probabilités" abbrev="Probabilités" code="R3.08" heures_cours="0.0" heures_td="22.0" heures_tp="7.0" coefficient="0.0" ects="" semestre_id="3" numero="430" code_apogee="VINFR308" module_type="2">
|
||||
<coefficients ue_reference="15" coef="17.0" />
|
||||
<coefficients ue_reference="17" coef="5.0" />
|
||||
</module>
|
||||
<module titre="Cryptographie et sécurité" abbrev="Cryptographie et sécurité" code="R3.09" heures_cours="0.0" heures_td="8.0" heures_tp="12.0" coefficient="0.0" ects="" semestre_id="3" numero="440" code_apogee="VINFR309" module_type="2">
|
||||
<coefficients ue_reference="15" coef="10.0" />
|
||||
<coefficients ue_reference="16" coef="10.0" />
|
||||
<coefficients ue_reference="17" coef="5.0" />
|
||||
</module>
|
||||
<module titre="Management des systèmes d'information" abbrev="Management des systèmes d'information" code="R3.10" heures_cours="0.0" heures_td="24.0" heures_tp="8.0" coefficient="0.0" ects="" semestre_id="3" numero="450" code_apogee="VINFR310" module_type="2">
|
||||
<coefficients ue_reference="17" coef="10.0" />
|
||||
<coefficients ue_reference="18" coef="18.0" />
|
||||
<coefficients ue_reference="19" coef="16.0" />
|
||||
</module>
|
||||
<module titre="Droit des contrats et du numérique" abbrev="Droit des contrats et du numérique" code="R3.11" heures_cours="0.0" heures_td="28.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="3" numero="460" code_apogee="VINFR311" module_type="2">
|
||||
<coefficients ue_reference="14" coef="8.0" />
|
||||
<coefficients ue_reference="17" coef="5.0" />
|
||||
<coefficients ue_reference="18" coef="10.0" />
|
||||
</module>
|
||||
<module titre="Anglais professionnel" abbrev="Anglais professionnel" code="R3.12" heures_cours="0.0" heures_td="16.0" heures_tp="8.0" coefficient="0.0" ects="" semestre_id="3" numero="470" code_apogee="VINFR312" module_type="2">
|
||||
<coefficients ue_reference="15" coef="5.0" />
|
||||
<coefficients ue_reference="16" coef="5.0" />
|
||||
<coefficients ue_reference="18" coef="7.0" />
|
||||
<coefficients ue_reference="19" coef="8.0" />
|
||||
</module>
|
||||
<module titre="Communication professionnelle" abbrev="Communication professionnelle" code="R3.13" heures_cours="0.0" heures_td="16.0" heures_tp="8.0" coefficient="0.0" ects="" semestre_id="3" numero="480" code_apogee="VINFR313" module_type="2">
|
||||
<coefficients ue_reference="18" coef="7.0" />
|
||||
<coefficients ue_reference="19" coef="16.0" />
|
||||
</module>
|
||||
<module titre="Développement d'une application" abbrev="Développement d'une application" code="S3.01" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="3" numero="490" code_apogee="VINFS301" module_type="3">
|
||||
<coefficients ue_reference="14" coef="40.0" />
|
||||
<coefficients ue_reference="15" coef="40.0" />
|
||||
<coefficients ue_reference="16" coef="40.0" />
|
||||
<coefficients ue_reference="17" coef="40.0" />
|
||||
<coefficients ue_reference="18" coef="40.0" />
|
||||
<coefficients ue_reference="19" coef="40.0" />
|
||||
</module>
|
||||
<module titre="Portfolio" abbrev="Portfolio" code="P3" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="3" numero="500" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="14" coef="1.0" />
|
||||
<coefficients ue_reference="19" coef="1.0" />
|
||||
<coefficients ue_reference="15" coef="1.0" />
|
||||
<coefficients ue_reference="16" coef="1.0" />
|
||||
<coefficients ue_reference="17" coef="1.0" />
|
||||
<coefficients ue_reference="18" coef="1.0" />
|
||||
</module>
|
||||
<module titre="Projet personnel et professionnel" abbrev="Projet personnel et professionnel" code="R3.14" heures_cours="0.0" heures_td="8.0" heures_tp="5.0" coefficient="0.0" ects="" semestre_id="3" numero="510" code_apogee="VINFR314" module_type="2">
|
||||
<coefficients ue_reference="19" coef="15.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="19" code_apogee="" titre="Sport / Culture" coefficient="0.0" semestre_idx="3" coef_rcue="1.0" type="1" color="#444054" ue_code="X7.1" ects="0.0" is_external="0" acronyme="UE37" reference="20">
|
||||
<matiere titre="UE7" numero="1">
|
||||
<module titre="Sport / Culture" abbrev="Sport" code="X3.01" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="3" numero="680" code_apogee="" module_type="0" />
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="20" code_apogee="" titre="Réaliser un développement d'application" coefficient="0.0" semestre_idx="4" coef_rcue="1.0" type="0" color="#b80004" ue_code="UCOD170" ects="5.0" is_external="0" acronyme="UE41-A" apc_niveau_libelle="Partir des exigences et aller jusqu’à une application complète " apc_niveau_annee="BUT2" apc_niveau_ordre="2" reference="21">
|
||||
<parcours code="A" numero="0" libelle="A : Réalisation d’applications : conception, développement, validation" />
|
||||
<matiere titre="UE41 Compétence 1 : Réaliser un développement d'application" numero="1">
|
||||
<module titre="Développement d'une application complexe" abbrev="Développement d'une application complexe" code="S4.A.01" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="0" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="22" coef="15.0" />
|
||||
<coefficients ue_reference="23" coef="15.0" />
|
||||
<coefficients ue_reference="24" coef="15.0" />
|
||||
<coefficients ue_reference="25" coef="15.0" />
|
||||
<coefficients ue_reference="26" coef="15.0" />
|
||||
<coefficients ue_reference="27" coef="15.0" />
|
||||
<coefficients ue_reference="21" coef="15.0" />
|
||||
</module>
|
||||
<module titre="Stage" abbrev="Stage" code="S4.St" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="2" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="22" coef="40.0" />
|
||||
<coefficients ue_reference="23" coef="40.0" />
|
||||
<coefficients ue_reference="24" coef="40.0" />
|
||||
<coefficients ue_reference="25" coef="40.0" />
|
||||
<coefficients ue_reference="26" coef="40.0" />
|
||||
<coefficients ue_reference="27" coef="40.0" />
|
||||
<coefficients ue_reference="21" coef="40.0" />
|
||||
</module>
|
||||
<module titre="Portfolio" abbrev="Portfolio" code="P4-A" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="4" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="23" coef="5.0" />
|
||||
<coefficients ue_reference="24" coef="5.0" />
|
||||
<coefficients ue_reference="25" coef="5.0" />
|
||||
<coefficients ue_reference="26" coef="5.0" />
|
||||
<coefficients ue_reference="27" coef="5.0" />
|
||||
<coefficients ue_reference="21" coef="5.0" />
|
||||
<parcours code="A" numero="0" libelle="A : Réalisation d’applications : conception, développement, validation" />
|
||||
</module>
|
||||
<module titre="Architecture logicielle" abbrev="Architecture logicielle" code="R4.01" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="530" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="22" coef="23.0" />
|
||||
<coefficients ue_reference="24" coef="12.0" />
|
||||
<coefficients ue_reference="27" coef="4.0" />
|
||||
<coefficients ue_reference="21" coef="14.0" />
|
||||
</module>
|
||||
<module titre="Projet Personnel et Professionnel" abbrev="Projet Personnel et Professionnel" code="R4.07" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="590" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="27" coef="10.0" />
|
||||
</module>
|
||||
<module titre="Complétement web" abbrev="Complétement web" code="R4.A.10" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="620" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="22" coef="8.0" />
|
||||
<coefficients ue_reference="23" coef="4.0" />
|
||||
<coefficients ue_reference="25" coef="8.0" />
|
||||
<coefficients ue_reference="26" coef="4.0" />
|
||||
<coefficients ue_reference="21" coef="10.0" />
|
||||
</module>
|
||||
<module titre="Développement pour applications mobiles" abbrev="Développement pour applications mobiles" code="R4.A.11" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="630" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="22" coef="8.0" />
|
||||
<coefficients ue_reference="23" coef="4.0" />
|
||||
<coefficients ue_reference="25" coef="8.0" />
|
||||
<coefficients ue_reference="26" coef="4.0" />
|
||||
<coefficients ue_reference="21" coef="10.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="21" code_apogee="" titre="Réaliser un développement d'application" coefficient="0.0" semestre_idx="4" coef_rcue="1.0" type="0" color="#b80004" ue_code="UCOD171" ects="5.0" is_external="0" acronyme="UE41-B" apc_niveau_libelle="Partir des exigences et aller jusqu’à une application complète " apc_niveau_annee="BUT2" apc_niveau_ordre="2" reference="22">
|
||||
<parcours code="B" numero="0" libelle="B : Déploiement d’applications communicantes et sécurisées" />
|
||||
<matiere titre="UE41 Compétence 1 : Réaliser un développement d'application" numero="1">
|
||||
<module titre="Développement d'une application complexe" abbrev="Développement d'une application complexe" code="S4.A.01" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="1" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="22" coef="15.0" />
|
||||
<coefficients ue_reference="23" coef="15.0" />
|
||||
<coefficients ue_reference="24" coef="15.0" />
|
||||
<coefficients ue_reference="25" coef="15.0" />
|
||||
<coefficients ue_reference="26" coef="15.0" />
|
||||
<coefficients ue_reference="27" coef="15.0" />
|
||||
<coefficients ue_reference="21" coef="15.0" />
|
||||
</module>
|
||||
<module titre="Stage" abbrev="Stage" code="S4.St" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="3" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="22" coef="40.0" />
|
||||
<coefficients ue_reference="23" coef="40.0" />
|
||||
<coefficients ue_reference="24" coef="40.0" />
|
||||
<coefficients ue_reference="25" coef="40.0" />
|
||||
<coefficients ue_reference="26" coef="40.0" />
|
||||
<coefficients ue_reference="27" coef="40.0" />
|
||||
<coefficients ue_reference="21" coef="40.0" />
|
||||
</module>
|
||||
<module titre="Portfolio" abbrev="Portfolio" code="P4-B" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="5" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="22" coef="5.0" />
|
||||
<coefficients ue_reference="23" coef="5.0" />
|
||||
<coefficients ue_reference="24" coef="5.0" />
|
||||
<coefficients ue_reference="25" coef="5.0" />
|
||||
<coefficients ue_reference="26" coef="5.0" />
|
||||
<coefficients ue_reference="27" coef="5.0" />
|
||||
<parcours code="B" numero="0" libelle="B : Déploiement d’applications communicantes et sécurisées" />
|
||||
</module>
|
||||
<module titre="Architecture logicielle" abbrev="Architecture logicielle" code="R4.01" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="530" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="22" coef="27.0" />
|
||||
<coefficients ue_reference="24" coef="1.0" />
|
||||
<coefficients ue_reference="27" coef="4.0" />
|
||||
<coefficients ue_reference="21" coef="27.0" />
|
||||
</module>
|
||||
<module titre="Projet Personnel et Professionnel" abbrev="Projet Personnel et Professionnel" code="R4.07" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="590" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="27" coef="10.0" />
|
||||
</module>
|
||||
<module titre="Complétement web" abbrev="Complétement web" code="R4.A.10" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="620" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="22" coef="8.0" />
|
||||
<coefficients ue_reference="23" coef="4.0" />
|
||||
<coefficients ue_reference="25" coef="8.0" />
|
||||
<coefficients ue_reference="26" coef="4.0" />
|
||||
<coefficients ue_reference="21" coef="10.0" />
|
||||
</module>
|
||||
<module titre="Développement pour applications mobiles" abbrev="Développement pour applications mobiles" code="R4.A.11" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="630" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="22" coef="8.0" />
|
||||
<coefficients ue_reference="23" coef="4.0" />
|
||||
<coefficients ue_reference="25" coef="8.0" />
|
||||
<coefficients ue_reference="26" coef="4.0" />
|
||||
<coefficients ue_reference="21" coef="11.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="22" code_apogee="" titre="Optimiser des applications informatiques" coefficient="0.0" semestre_idx="4" coef_rcue="1.0" type="0" color="#f97b3d" ue_code="UCOD172" ects="5.0" is_external="0" acronyme="UE42" apc_niveau_libelle="Sélectionner les algorithmes adéquats pour répondre à un problème donné " apc_niveau_annee="BUT2" apc_niveau_ordre="2" reference="23">
|
||||
<matiere titre="UE42 Compétence 2 : Optimiser des applications informatiques" numero="1">
|
||||
<module titre="Méthodes d'optimisation" abbrev="Méthodes d'optimisation" code="R4.04" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="560" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="23" coef="12.0" />
|
||||
</module>
|
||||
<module titre="Automates et langages" abbrev="Automates et langages" code="R4.A.12" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="640" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="23" coef="12.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="23" code_apogee="" titre="Administrer des systèmes informatiques communicants" coefficient="0.0" semestre_idx="4" coef_rcue="1.0" type="0" color="#feb40b" ue_code="UCOD173" ects="5.0" is_external="0" acronyme="UE43" apc_niveau_libelle="Déployer des services dans une architecture réseau" apc_niveau_annee="BUT2" apc_niveau_ordre="2" reference="24">
|
||||
<matiere titre="UE43 Compétence 3 : Administrer des systèmes informatiques communicants" numero="1">
|
||||
<module titre="Virtualisation" abbrev="Virtualisation" code="R4.A.08" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="600" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="24" coef="28.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="24" code_apogee="" titre="Gérer des données de l'information" coefficient="0.0" semestre_idx="4" coef_rcue="1.0" type="0" color="#80cb3f" ue_code="UCOD174" ects="5.0" is_external="0" acronyme="UE44" apc_niveau_libelle="Optimiser une base de données, interagir avec une application et mettre en œuvre la sécurité " apc_niveau_annee="BUT2" apc_niveau_ordre="2" reference="25">
|
||||
<matiere titre="UE44 Compétence 4 : Gérer des données de l'information" numero="1">
|
||||
<module titre="Qualité & Non-relationnel" abbrev="Qualité & Non-relationnel" code="R4.03" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="550" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="25" coef="18.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="25" code_apogee="" titre="Conduire un projet" coefficient="0.0" semestre_idx="4" coef_rcue="1.0" type="0" color="#05162e" ue_code="UCOD175" ects="5.0" is_external="0" acronyme="UE45" apc_niveau_libelle="Appliquer une démarche de suivi de projet en fonction des besoins métiers des clients et des utilisateurs " apc_niveau_annee="BUT2" apc_niveau_ordre="2" reference="26">
|
||||
<matiere titre="UE45 Compétence 5 : Conduire un projet" numero="1">
|
||||
<module titre="Qualité de développement" abbrev="Qualité de développement" code="R4.02" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="540" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="22" coef="8.0" />
|
||||
<coefficients ue_reference="26" coef="10.0" />
|
||||
<coefficients ue_reference="21" coef="8.0" />
|
||||
</module>
|
||||
<module titre="Management avancé des systèmes d'information" abbrev="Management avancé des systèmes d'information" code="R4.A.09" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="610" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="23" coef="4.0" />
|
||||
<coefficients ue_reference="26" coef="22.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="26" code_apogee="" titre="Travailler dans une équipe informatique" coefficient="0.0" semestre_idx="4" coef_rcue="1.0" type="0" color="#0c0c0c" ue_code="UCOD176" ects="5.0" is_external="0" acronyme="UE46" apc_niveau_libelle="Situer son rôle et ses missions au sein d’une équipe informatique " apc_niveau_annee="BUT2" apc_niveau_ordre="2" reference="27">
|
||||
<parcours code="C" numero="0" libelle="C : Administration, gestion et exploitation des données" />
|
||||
<parcours code="B" numero="0" libelle="B : Déploiement d’applications communicantes et sécurisées" ects="4.0" />
|
||||
<parcours code="D" numero="0" libelle="D : Intégration d’applications et management du système d’information" ects="6.0" />
|
||||
<parcours code="A" numero="0" libelle="A : Réalisation d’applications : conception, développement, validation" ects="3.0" />
|
||||
<matiere titre="UE46 Compétence 6 : Travailler dans une équipe informatique" numero="1">
|
||||
<module titre="Anglais" abbrev="Anglais" code="R4.05" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="570" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="23" coef="4.0" />
|
||||
<coefficients ue_reference="27" coef="13.0" />
|
||||
</module>
|
||||
<module titre="Communication interne" abbrev="Communication interne" code="R4.06" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="580" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="25" coef="6.0" />
|
||||
<coefficients ue_reference="27" coef="13.0" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="27" code_apogee="" titre="Sport, culture, engagement" coefficient="0.0" semestre_idx="4" coef_rcue="1.0" type="1" color="#444054" ue_code="OPT4" ects="1.0" is_external="0" acronyme="Sport, culture, engagement" reference="28">
|
||||
<matiere titre="Sport, culture, engagement" numero="1">
|
||||
<module titre="Bonus" abbrev="Bonus" code="Bonus" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="4" numero="690" code_apogee="" module_type="0" />
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="28" code_apogee="" titre="Réaliser S5" coefficient="0.0" semestre_idx="5" coef_rcue="1.0" type="0" color="#b80004" ue_code="UCOD177" ects="5.0" is_external="0" acronyme="UE51" apc_niveau_libelle="Adapter des applications sur un ensemble de supports (embarqué, web, mobile, IoT…) " apc_niveau_annee="BUT3" apc_niveau_ordre="3" reference="29">
|
||||
<matiere titre="Réaliser S5" numero="1">
|
||||
<module titre="Portfolio" abbrev="" code="P5-A" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="5" numero="710" code_apogee="" module_type="3">
|
||||
<parcours code="A" numero="0" libelle="A : Réalisation d’applications : conception, développement, validation" />
|
||||
</module>
|
||||
<module titre="Portfolio" abbrev="" code="P5-B" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="5" numero="720" code_apogee="" module_type="3">
|
||||
<parcours code="B" numero="0" libelle="B : Déploiement d’applications communicantes et sécurisées" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="29" code_apogee="" titre="Optimiser" coefficient="0.0" semestre_idx="5" coef_rcue="1.0" type="0" color="#f97b3d" ue_code="UCOD178" ects="5.0" is_external="0" acronyme="UE52" apc_niveau_libelle="Analyser et optimiser des applications" apc_niveau_annee="BUT3" apc_niveau_ordre="3" reference="30">
|
||||
<parcours code="A" numero="0" libelle="A : Réalisation d’applications : conception, développement, validation" />
|
||||
<matiere titre="Optimiser S5" numero="1" />
|
||||
</ue>
|
||||
<ue numero="31" code_apogee="" titre="Administrer" coefficient="0.0" semestre_idx="5" coef_rcue="1.0" type="0" color="#dfb03f" ue_code="UCOD180" ects="5.0" is_external="0" acronyme="UE53" apc_niveau_libelle="Faire évoluer et maintenir un système informatique communicant en conditions opérationnelles " apc_niveau_annee="BUT3" apc_niveau_ordre="3" reference="31">
|
||||
<parcours code="B" numero="0" libelle="B : Déploiement d’applications communicantes et sécurisées" />
|
||||
<matiere titre="Administrer S5" numero="1" />
|
||||
</ue>
|
||||
<ue numero="32" code_apogee="" titre="Gérer" coefficient="0.0" semestre_idx="5" coef_rcue="1.0" type="0" color="#8abd53" ue_code="UCOD181" ects="5.0" is_external="0" acronyme="UE54" apc_niveau_libelle="Administrer une base de données, concevoir et réaliser des systèmes d’informations décisionnels " apc_niveau_annee="BUT3" apc_niveau_ordre="3" reference="32">
|
||||
<parcours code="C" numero="0" libelle="C : Administration, gestion et exploitation des données" />
|
||||
<matiere titre="Gérer S5 - TC" numero="1" />
|
||||
</ue>
|
||||
<ue numero="34" code_apogee="" titre="Conduire" coefficient="0.0" semestre_idx="5" coef_rcue="1.0" type="0" color="#0c1548" ue_code="UCOD185" ects="5.0" is_external="0" acronyme="UE55" apc_niveau_libelle="Participer à la conception et à la mise en œuvre d’un projet système d’information " apc_niveau_annee="BUT3" apc_niveau_ordre="3" reference="33">
|
||||
<parcours code="D" numero="0" libelle="D : Intégration d’applications et management du système d’information" ects="6.0" />
|
||||
<parcours code="C" numero="0" libelle="C : Administration, gestion et exploitation des données" ects="6.666" />
|
||||
<matiere titre="Conduire S5 - TC" numero="1" />
|
||||
</ue>
|
||||
<ue numero="35" code_apogee="" titre="Collaborer S5" coefficient="0.0" semestre_idx="5" coef_rcue="1.0" type="0" color="#0c0c0c" ue_code="UCOD186" ects="5.0" is_external="0" acronyme="UE56" apc_niveau_libelle="Manager une équipe informatique" apc_niveau_annee="BUT3" apc_niveau_ordre="3" reference="34">
|
||||
<matiere titre="Collaborer S5" numero="1" />
|
||||
</ue>
|
||||
<ue numero="36" code_apogee="" titre="Réaliser" coefficient="0.0" semestre_idx="6" coef_rcue="1.0" type="0" color="#b80004" ue_code="UCOD600" ects="5.0" is_external="0" acronyme="UE61" apc_niveau_libelle="Adapter des applications sur un ensemble de supports (embarqué, web, mobile, IoT…) " apc_niveau_annee="BUT3" apc_niveau_ordre="3" reference="35">
|
||||
<matiere titre="Réaliser S6" numero="1">
|
||||
<module titre="Portfolio" abbrev="" code="P6-A" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="6" numero="730" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="35" coef="1.0" />
|
||||
<coefficients ue_reference="36" coef="1.0" />
|
||||
<coefficients ue_reference="40" coef="1.0" />
|
||||
<parcours code="A" numero="0" libelle="A : Réalisation d’applications : conception, développement, validation" />
|
||||
</module>
|
||||
<module titre="Portfolio" abbrev="" code="P6-B" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="0.0" ects="" semestre_id="6" numero="740" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="35" coef="1.0" />
|
||||
<coefficients ue_reference="37" coef="1.0" />
|
||||
<coefficients ue_reference="40" coef="1.0" />
|
||||
<parcours code="B" numero="0" libelle="B : Déploiement d’applications communicantes et sécurisées" />
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue numero="37" code_apogee="" titre="Optimiser" coefficient="0.0" semestre_idx="6" coef_rcue="1.0" type="0" color="#f97b3d" ue_code="UCOD601" ects="5.0" is_external="0" acronyme="UE62" apc_niveau_libelle="Analyser et optimiser des applications" apc_niveau_annee="BUT3" apc_niveau_ordre="3" reference="36">
|
||||
<parcours code="A" numero="0" libelle="A : Réalisation d’applications : conception, développement, validation" />
|
||||
<matiere titre="Optimiser S6" numero="1" />
|
||||
</ue>
|
||||
<ue numero="39" code_apogee="" titre="Administrer" coefficient="0.0" semestre_idx="6" coef_rcue="1.0" type="0" color="#dfb03f" ue_code="UCOD603" ects="5.0" is_external="0" acronyme="UE63" apc_niveau_libelle="Faire évoluer et maintenir un système informatique communicant en conditions opérationnelles " apc_niveau_annee="BUT3" apc_niveau_ordre="3" reference="37">
|
||||
<parcours code="B" numero="0" libelle="B : Déploiement d’applications communicantes et sécurisées" />
|
||||
<matiere titre="Administrer S6" numero="1" />
|
||||
</ue>
|
||||
<ue numero="40" code_apogee="" titre="Gérer" coefficient="0.0" semestre_idx="6" coef_rcue="1.0" type="0" color="#8abd53" ue_code="UCOD604" ects="5.0" is_external="0" acronyme="UE64" apc_niveau_libelle="Administrer une base de données, concevoir et réaliser des systèmes d’informations décisionnels " apc_niveau_annee="BUT3" apc_niveau_ordre="3" reference="38">
|
||||
<parcours code="C" numero="0" libelle="C : Administration, gestion et exploitation des données" />
|
||||
<matiere titre="Gérer S6 - TC" numero="1" />
|
||||
</ue>
|
||||
<ue numero="41" code_apogee="" titre="Conduire" coefficient="0.0" semestre_idx="6" coef_rcue="1.0" type="0" color="#0c1548" ue_code="UCOD605" ects="5.0" is_external="0" acronyme="UE65" apc_niveau_libelle="Participer à la conception et à la mise en œuvre d’un projet système d’information " apc_niveau_annee="BUT3" apc_niveau_ordre="3" reference="39">
|
||||
<parcours code="C" numero="0" libelle="C : Administration, gestion et exploitation des données" />
|
||||
<parcours code="D" numero="0" libelle="D : Intégration d’applications et management du système d’information" />
|
||||
<matiere titre="Gérer Semestre 5 - C" numero="1" />
|
||||
</ue>
|
||||
<ue numero="43" code_apogee="" titre="Collaborer" coefficient="0.0" semestre_idx="6" coef_rcue="1.0" type="0" color="#0c0c0c" ue_code="UCOD607" ects="5.0" is_external="0" acronyme="UE66" apc_niveau_libelle="Manager une équipe informatique" apc_niveau_annee="BUT3" apc_niveau_ordre="3" reference="40">
|
||||
<matiere titre="Collaborer S6" numero="1" />
|
||||
</ue>
|
||||
</formation>
|
@ -103,14 +103,14 @@ def run_sco_basic(verbose=False) -> FormSemestre:
|
||||
|
||||
# --- Saisie toutes les notes de l'évaluation
|
||||
for idx, etud in enumerate(etuds):
|
||||
nb_changed, nb_suppress, existing_decisions = G.create_note(
|
||||
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
||||
evaluation_id=e["id"],
|
||||
etudid=etud["etudid"],
|
||||
note=NOTES_T[idx % len(NOTES_T)],
|
||||
)
|
||||
assert not existing_decisions
|
||||
assert nb_suppress == 0
|
||||
assert nb_changed == 1
|
||||
assert len(etudids_changed) == 1
|
||||
|
||||
# --- Vérifie que les notes sont prises en compte:
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(formsemestre_id, etud["etudid"])
|
||||
@ -136,7 +136,7 @@ def run_sco_basic(verbose=False) -> FormSemestre:
|
||||
)
|
||||
# Saisie les notes des 5 premiers étudiants:
|
||||
for idx, etud in enumerate(etuds[:5]):
|
||||
nb_changed, nb_suppress, existing_decisions = G.create_note(
|
||||
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
||||
evaluation_id=e2["id"],
|
||||
etudid=etud["etudid"],
|
||||
note=NOTES_T[idx % len(NOTES_T)],
|
||||
@ -158,7 +158,7 @@ def run_sco_basic(verbose=False) -> FormSemestre:
|
||||
|
||||
# Saisie des notes qui manquent:
|
||||
for idx, etud in enumerate(etuds[5:]):
|
||||
nb_changed, nb_suppress, existing_decisions = G.create_note(
|
||||
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
||||
evaluation_id=e2["id"],
|
||||
etudid=etud["etudid"],
|
||||
note=NOTES_T[idx % len(NOTES_T)],
|
||||
|
@ -239,7 +239,7 @@ def create_evaluations(formsemestre: FormSemestre):
|
||||
"jour": datetime.date(2022, 3, 1) + datetime.timedelta(days=modimpl.id),
|
||||
"heure_debut": "8h00",
|
||||
"heure_fin": "9h00",
|
||||
"description": None,
|
||||
"description": f"Evaluation-{modimpl.module.code}",
|
||||
"note_max": 20,
|
||||
"coefficient": 1.0,
|
||||
"visibulletin": True,
|
||||
|
Loading…
x
Reference in New Issue
Block a user