+
\ No newline at end of file
diff --git a/app/templates/entreprises/contacts.html b/app/templates/entreprises/contacts.html
index 22fb1be92..0d0df6797 100644
--- a/app/templates/entreprises/contacts.html
+++ b/app/templates/entreprises/contacts.html
@@ -42,11 +42,13 @@
Aucun contact présent dans la base
{% endif %}
+ {% if current_user.has_permission(current_user.Permission.RelationsEntreprisesExport, None) %}
+ {% endif %}
{% endblock %}
\ No newline at end of file
From 2d9e428ebfac54d77dee7f3bd13b4abfbfdb47f6 Mon Sep 17 00:00:00 2001
From: Arthur ZHU
Date: Mon, 31 Jan 2022 18:22:54 +0100
Subject: [PATCH 008/287] ajout pagination + nettoyage
---
app/entreprises/__init__.py | 2 +-
app/entreprises/routes.py | 159 ++++--------------
app/static/css/entreprises.css | 11 ++
app/templates/base.html | 4 +
.../entreprises/ajout_entreprise.html | 3 +-
.../entreprises/ajout_historique.html | 1 +
app/templates/entreprises/contacts.html | 30 +++-
app/templates/entreprises/entreprises.html | 32 +++-
.../entreprises/entreprises_validation.html | 1 +
.../entreprises/envoi_offre_form.html | 1 +
.../entreprises/fiche_entreprise.html | 136 +++++++--------
app/templates/entreprises/logs.html | 37 ++++
.../entreprises/offres_expirees.html | 22 +--
app/templates/entreprises/offres_recues.html | 8 +-
14 files changed, 235 insertions(+), 212 deletions(-)
create mode 100644 app/static/css/entreprises.css
create mode 100644 app/templates/entreprises/logs.html
diff --git a/app/entreprises/__init__.py b/app/entreprises/__init__.py
index 44968ffb1..813fd73b6 100644
--- a/app/entreprises/__init__.py
+++ b/app/entreprises/__init__.py
@@ -7,7 +7,7 @@ from app.auth.models import User
bp = Blueprint("entreprises", __name__)
-LOGS_LEN = 10
+LOGS_LEN = 5
@bp.app_template_filter()
diff --git a/app/entreprises/routes.py b/app/entreprises/routes.py
index 651184920..a6f91813b 100644
--- a/app/entreprises/routes.py
+++ b/app/entreprises/routes.py
@@ -48,17 +48,11 @@ from werkzeug.utils import secure_filename
def index():
"""
Permet d'afficher une page avec la liste des entreprises et une liste des dernières opérations
-
- Retourne: template de la page (entreprises.html)
- Arguments du template:
- title:
- titre de la page
- entreprises:
- liste des entreprises
- logs:
- liste des logs
"""
- entreprises = Entreprise.query.filter_by(visible=True).all()
+ page = request.args.get("page", 1, type=int)
+ entreprises = Entreprise.query.filter_by(visible=True).paginate(
+ page=page, per_page=10
+ )
logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).limit(LOGS_LEN).all()
return render_template(
"entreprises/entreprises.html",
@@ -68,9 +62,27 @@ def index():
)
+@bp.route("/logs", methods=["GET"])
+@permission_required(Permission.RelationsEntreprisesView)
+def logs():
+ """
+ Permet d'afficher les logs (toutes les entreprises)
+ """
+ page = request.args.get("page", 1, type=int)
+ logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).paginate(
+ page=page, per_page=20
+ )
+ if logs is None:
+ abort(404)
+ return render_template("entreprises/logs.html", title=("Logs"), logs=logs)
+
+
@bp.route("/validation", methods=["GET"])
@permission_required(Permission.RelationsEntreprisesValidate)
def validation_entreprise():
+ """
+ Permet d'afficher une page avec la liste des entreprises a valider
+ """
entreprises = Entreprise.query.filter_by(visible=False).all()
return render_template(
"entreprises/entreprises_validation.html",
@@ -84,21 +96,13 @@ def validation_entreprise():
def contacts():
"""
Permet d'afficher une page la liste des contacts et une liste des dernières opérations
-
- Retourne: template de la page (contacts.html)
- Arguments du template:
- title:
- titre de la page
- contacts:
- liste des contacts
- logs:
- liste des logs
"""
+ page = request.args.get("page", 1, type=int)
contacts = (
db.session.query(EntrepriseContact, Entreprise)
.join(Entreprise, EntrepriseContact.entreprise_id == Entreprise.id)
.filter_by(visible=True)
- .all()
+ .paginate(page=page, per_page=10)
)
logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).limit(LOGS_LEN).all()
return render_template(
@@ -114,25 +118,6 @@ def fiche_entreprise(id):
l'historique des étudiants ayant réaliser un stage ou une alternance dans cette entreprise.
La fiche entreprise comporte les informations de l'entreprise, les contacts de l'entreprise et
les offres de l'entreprise.
-
- Arguments:
- id:
- l'id de l'entreprise
-
- Retourne: template de la page (fiche_entreprise.html)
- Arguments du template:
- title:
- titre de la page
- entreprise:
- un objet entreprise
- contacts:
- liste des contacts de l'entreprise
- offres:
- liste des offres de l'entreprise avec leurs fichiers
- logs:
- liste des logs
- historique:
- liste des étudiants ayant réaliser un stage ou une alternance dans l'entreprise
"""
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404()
offres = entreprise.offres
@@ -183,6 +168,9 @@ def fiche_entreprise(id):
@bp.route("/fiche_entreprise_validation/", methods=["GET"])
@permission_required(Permission.RelationsEntreprisesValidate)
def fiche_entreprise_validation(id):
+ """
+ Permet d'afficher la fiche entreprise d'une entreprise a valider
+ """
entreprise = Entreprise.query.filter_by(id=id, visible=False).first_or_404()
contacts = entreprise.contacts
return render_template(
@@ -198,13 +186,6 @@ def fiche_entreprise_validation(id):
def offres_recues():
"""
Permet d'afficher la page où l'on recoit les offres
-
- Retourne: template de la page (offres.html)
- Arguments du template:
- title:
- titre de la page
- offres_recus:
- liste des offres reçues
"""
offres_recues = (
db.session.query(EntrepriseEnvoiOffre, EntrepriseOffre)
@@ -222,6 +203,9 @@ def offres_recues():
@bp.route("/fiche_entreprise//offres_expirees")
@permission_required(Permission.RelationsEntreprisesView)
def offres_expirees(id):
+ """
+ Permet d'afficher la liste des offres expirés d'une entreprise
+ """
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404()
offres = entreprise.offres
offres_expirees_with_files = []
@@ -290,7 +274,7 @@ def add_entreprise():
return redirect(url_for("entreprises.index"))
return render_template(
"entreprises/ajout_entreprise.html",
- title=("Ajout entreprise + contact"),
+ title=("Ajout entreprise avec contact"),
form=form,
)
@@ -300,10 +284,6 @@ def add_entreprise():
def edit_entreprise(id):
"""
Permet de modifier une entreprise de la base avec un formulaire
-
- Arguments:
- id:
- l'id de l'entreprise
"""
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404()
form = EntrepriseModificationForm()
@@ -369,10 +349,6 @@ def edit_entreprise(id):
def delete_entreprise(id):
"""
Permet de supprimer une entreprise de la base avec un formulaire de confirmation
-
- Arguments:
- id:
- l'id de l'entreprise
"""
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404()
form = SuppressionConfirmationForm()
@@ -397,6 +373,9 @@ def delete_entreprise(id):
@bp.route("/validate_entreprise/", methods=["GET", "POST"])
@permission_required(Permission.RelationsEntreprisesValidate)
def validate_entreprise(id):
+ """
+ Permet de valider une entreprise
+ """
entreprise = Entreprise.query.filter_by(id=id, visible=False).first_or_404()
entreprise.visible = True
db.session.commit()
@@ -408,10 +387,6 @@ def validate_entreprise(id):
def add_offre(id):
"""
Permet d'ajouter une offre a une entreprise
-
- Arguments:
- id:
- l'id de l'entreprise
"""
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404()
form = OffreCreationForm()
@@ -443,10 +418,6 @@ def add_offre(id):
def edit_offre(id):
"""
Permet de modifier une offre
-
- Arguments:
- id:
- l'id de l'offre
"""
offre = EntrepriseOffre.query.filter_by(id=id).first_or_404()
form = OffreModificationForm()
@@ -483,10 +454,6 @@ def edit_offre(id):
def delete_offre(id):
"""
Permet de supprimer une offre
-
- Arguments:
- id:
- l'id de l'offre
"""
offre = EntrepriseOffre.query.filter_by(id=id).first_or_404()
entreprise_id = offre.entreprise.id
@@ -512,10 +479,6 @@ def delete_offre(id):
def add_contact(id):
"""
Permet d'ajouter un contact a une entreprise
-
- Arguments:
- id:
- l'id de l'entreprise
"""
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404()
form = ContactCreationForm(hidden_entreprise_id=entreprise.id)
@@ -547,10 +510,6 @@ def add_contact(id):
def edit_contact(id):
"""
Permet de modifier un contact
-
- Arguments:
- id:
- l'id du contact
"""
contact = EntrepriseContact.query.filter_by(id=id).first_or_404()
form = ContactModificationForm()
@@ -589,10 +548,6 @@ def edit_contact(id):
def delete_contact(id):
"""
Permet de supprimer un contact
-
- Arguments:
- id:
- l'id du contact
"""
contact = EntrepriseContact.query.filter_by(id=id).first_or_404()
entreprise_id = contact.entreprise.id
@@ -627,10 +582,6 @@ def delete_contact(id):
def add_historique(id):
"""
Permet d'ajouter un étudiant ayant réalisé un stage ou une alternance sur la fiche entreprise de l'entreprise
-
- Arguments:
- id:
- l'id de l'entreprise
"""
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404()
form = HistoriqueCreationForm()
@@ -672,10 +623,6 @@ def add_historique(id):
def envoyer_offre(id):
"""
Permet d'envoyer une offre à un utilisateur
-
- Arguments:
- id:
- l'id de l'offre
"""
offre = EntrepriseOffre.query.filter_by(id=id).first_or_404()
form = EnvoiOffreForm()
@@ -706,13 +653,6 @@ def envoyer_offre(id):
def json_etudiants():
"""
Permet de récuperer un JSON avec tous les étudiants
-
- Arguments:
- term:
- le terme utilisé pour le filtre de l'autosuggest
-
- Retourne:
- le JSON de tous les étudiants (nom, prenom, formation actuelle?) correspondant au terme
"""
if request.args.get("term") == None:
abort(400)
@@ -740,13 +680,6 @@ def json_etudiants():
def json_responsables():
"""
Permet de récuperer un JSON avec tous les étudiants
-
- Arguments:
- term:
- le terme utilisé pour le filtre de l'autosuggest
-
- Retourne:
- le JSON de tous les utilisateurs (nom, prenom, login) correspondant au terme
"""
if request.args.get("term") == None:
abort(400)
@@ -792,7 +725,7 @@ def export_contacts():
"""
Permet d'exporter la liste des contacts sous format excel (.xlsx)
"""
- contacts = EntrepriseContact.query.filter_by(visible=True).all()
+ contacts = EntrepriseContact.query.all()
if contacts:
keys = ["nom", "prenom", "telephone", "mail", "poste", "service"]
titles = keys[:]
@@ -811,7 +744,7 @@ def export_contacts_bis():
"""
Permet d'exporter la liste des contacts avec leur entreprise sous format excel (.xlsx)
"""
- contacts = EntrepriseContact.query.filter_by(visible=True).all()
+ contacts = EntrepriseContact.query.all()
if contacts:
keys = [
"nom",
@@ -846,16 +779,6 @@ def export_contacts_bis():
def get_offre_file(entreprise_id, offre_id, filedir, filename):
"""
Permet de télécharger un fichier d'une offre
-
- Arguments:
- entreprise_id:
- l'id de l'entreprise
- offre_id:
- l'id de l'offre
- filedir:
- le répertoire du fichier
- filename:
- le nom du fichier
"""
if os.path.isfile(
os.path.join(
@@ -887,10 +810,6 @@ def get_offre_file(entreprise_id, offre_id, filedir, filename):
def add_offre_file(offre_id):
"""
Permet d'ajouter un fichier à une offre
-
- Arguments:
- offre_id:
- l'id de l'offre
"""
offre = EntrepriseOffre.query.filter_by(id=offre_id).first_or_404()
form = AjoutFichierForm()
@@ -919,12 +838,6 @@ def add_offre_file(offre_id):
def delete_offre_file(offre_id, filedir):
"""
Permet de supprimer un fichier d'une offre
-
- Arguments:
- offre_id:
- l'id de l'offre
- filedir:
- le répertoire du fichier
"""
offre = EntrepriseOffre.query.filter_by(id=offre_id).first_or_404()
form = SuppressionConfirmationForm()
diff --git a/app/static/css/entreprises.css b/app/static/css/entreprises.css
new file mode 100644
index 000000000..6dded2fa4
--- /dev/null
+++ b/app/static/css/entreprises.css
@@ -0,0 +1,11 @@
+.btn-inverse {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgb(0 0 0 / 25%);
+ background-color: #363636;
+}
+
+.btn-inverse:hover {
+ color: #ffffff;
+ background-color: #222222;
+ *background-color: #151515;
+}
\ No newline at end of file
diff --git a/app/templates/base.html b/app/templates/base.html
index adf70171b..176e4b993 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -4,6 +4,7 @@
{% block styles %}
{{super()}}
+
{% endblock %}
{% block title %}
@@ -35,6 +36,9 @@
url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)
}}">Dept. {{ g.scodoc_dept }}
{% endif %}
+ {% if not current_user.is_anonymous and current_user.has_permission(current_user.Permission.RelationsEntreprisesView, None) %}
+
{% endblock %}
\ No newline at end of file
diff --git a/app/templates/entreprises/offres_recues.html b/app/templates/entreprises/offres_recues.html
index 5f31fb74b..63bbdb65d 100644
--- a/app/templates/entreprises/offres_recues.html
+++ b/app/templates/entreprises/offres_recues.html
@@ -5,31 +5,26 @@
{% include 'entreprises/nav.html' %}
-
{{ title }}
+
Offres reçues
{% if offres_recues %}
-
-
{% for offre in offres_recues %}
-
-
- Envoyé le {{ offre[0].date_envoi.strftime('%d %B %Y à %H:%M') }} par {{ offre[0].sender_id|get_nomcomplet_by_id }}
- Intitulé : {{ offre[1].intitule }}
- Description : {{ offre[1].description }}
- Type de l'offre : {{ offre[1].type_offre }}
- Missions : {{ offre[1].missions }}
- Durée : {{ offre[1].duree }}
+
+
+ Envoyé le {{ offre[0].date_envoi.strftime('%d %B %Y à %H:%M') }} par {{ offre[0].sender_id|get_nomcomplet_by_id }}
+ Intitulé : {{ offre[1].intitule }}
+ Description : {{ offre[1].description }}
+ Type de l'offre : {{ offre[1].type_offre }}
+ Missions : {{ offre[1].missions }}
+ Durée : {{ offre[1].duree }}
- {% for fichier in offre[2] %}
- {{ fichier[1] }}
- {% endfor %}
-
+ {% for fichier in offre[2] %}
+ {{ fichier[1] }}
+ {% endfor %}
- {% endfor %}
-
- {% else %}
+ {% endfor %}
+ {% else %}
Aucune offre reçue
-
{% endif %}
{% endblock %}
\ No newline at end of file
diff --git a/app/templates/entreprises/validate_confirmation.html b/app/templates/entreprises/validate_confirmation.html
index 04ab1eac1..277bf237c 100644
--- a/app/templates/entreprises/validate_confirmation.html
+++ b/app/templates/entreprises/validate_confirmation.html
@@ -3,7 +3,7 @@
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
-
{{ title }}
+
Validation entreprise
Cliquez sur le bouton Valider pour confirmer votre validation
From 42e74174bad057917bae908da589e95957e5a481 Mon Sep 17 00:00:00 2001
From: Arthur ZHU
Date: Tue, 8 Feb 2022 19:13:45 +0100
Subject: [PATCH 021/287] suite import entreprises par excel
---
app/entreprises/forms.py | 2 +-
app/entreprises/routes.py | 50 +++++++++++++++----
.../entreprises/import_entreprises.html | 18 +++++++
3 files changed, 60 insertions(+), 10 deletions(-)
diff --git a/app/entreprises/forms.py b/app/entreprises/forms.py
index 006902be0..19c562ab3 100644
--- a/app/entreprises/forms.py
+++ b/app/entreprises/forms.py
@@ -110,7 +110,7 @@ class EntrepriseCreationForm(FlaskForm):
def validate_siret(self, siret):
siret = siret.data.strip()
- if re.match("^\d{14}$", siret) == None:
+ if re.match("^\d{14}$", siret) is None:
raise ValidationError("Format incorrect")
req = requests.get(
f"https://entreprise.data.gouv.fr/api/sirene/v1/siret/{siret}"
diff --git a/app/entreprises/routes.py b/app/entreprises/routes.py
index 1b01f9634..4611336d5 100644
--- a/app/entreprises/routes.py
+++ b/app/entreprises/routes.py
@@ -1,9 +1,10 @@
import os
-from queue import Empty
from config import Config
from datetime import datetime, date
import glob
import shutil
+import re
+import requests
from flask import render_template, redirect, url_for, request, flash, send_file, abort
from flask.json import jsonify
@@ -869,12 +870,6 @@ def get_import_entreprises_file_sample():
"ville",
"codepostal",
"pays",
- "nom_contact",
- "prenom_contact",
- "telephone",
- "mail",
- "poste",
- "service",
]
titles = keys[:]
title = "ImportEntreprises"
@@ -883,6 +878,23 @@ def get_import_entreprises_file_sample():
return scu.send_file(xlsx, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
+def verif_entreprise_data(entreprise_data):
+ print(entreprise_data)
+ for data in entreprise_data:
+ if data is "":
+ return False
+ siret = entreprise_data[0].strip()
+ if re.match("^\d{14}$", siret) is None:
+ return False
+ req = requests.get(f"https://entreprise.data.gouv.fr/api/sirene/v1/siret/{siret}")
+ if req.status_code != 200:
+ return False
+ entreprise = Entreprise.query.filter_by(siret=siret).first()
+ if entreprise is not None:
+ return False
+ return True
+
+
@bp.route("/import_entreprises", methods=["GET", "POST"])
@permission_required(Permission.RelationsEntreprisesExport)
def import_entreprises():
@@ -894,8 +906,28 @@ def import_entreprises():
path = os.path.join(Config.SCODOC_VAR_DIR, "tmp")
file = form.fichier.data
filename = secure_filename(file.filename)
- file.save(os.path.join(path, filename))
- # print(sco_excel.excel_file_to_list(filename))
+ file_path = os.path.join(path, filename)
+ file.save(file_path)
+ data = sco_excel.excel_file_to_list(file_path)
+ os.remove(file_path)
+ entreprises_import = []
+ for entreprise_data in data[1][1:]:
+ if verif_entreprise_data(entreprise_data):
+ entreprise = Entreprise(
+ siret=entreprise_data[0],
+ nom=entreprise_data[1],
+ adresse=entreprise_data[2],
+ ville=entreprise_data[3],
+ codepostal=entreprise_data[4],
+ pays=entreprise_data[5],
+ )
+ entreprises_import.append(entreprise)
+ return render_template(
+ "entreprises/import_entreprises.html",
+ title=("Importation entreprises"),
+ form=form,
+ entreprises_import=entreprises_import,
+ )
return render_template(
"entreprises/import_entreprises.html",
title=("Importation entreprises"),
diff --git a/app/templates/entreprises/import_entreprises.html b/app/templates/entreprises/import_entreprises.html
index 5e1a50d77..dfa844874 100644
--- a/app/templates/entreprises/import_entreprises.html
+++ b/app/templates/entreprises/import_entreprises.html
@@ -18,4 +18,22 @@
{{ wtf.quick_form(form, novalidate=True) }}
+
+ {% if entreprises_import %}
+ {% for entreprise in entreprises_import %}
+
+
+ SIRET : {{ entreprise.siret }}
+ Nom : {{ entreprise.nom }}
+ Adresse : {{ entreprise.adresse }}
+ Code postal : {{ entreprise.codepostal }}
+ Ville : {{ entreprise.ville }}
+ Pays : {{ entreprise.pays }}
+
")
@@ -878,9 +877,8 @@ def do_formsemestre_validation_auto(formsemestre_id):
"Saisie automatisee des decisions d'un semestre"
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
next_semestre_id = sem["semestre_id"] + 1
- nt = sco_cache.NotesTableCache.get(
- formsemestre_id
- ) # > get_etudids, get_etud_decision_sem,
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
etudids = nt.get_etudids()
nb_valid = 0
conflicts = [] # liste des etudiants avec decision differente déjà saisie
@@ -899,7 +897,7 @@ def do_formsemestre_validation_auto(formsemestre_id):
)
and Se.barre_moy_ok
and Se.barres_ue_ok
- and not nt.etud_has_notes_attente(etudid)
+ and not etud_has_notes_attente(etudid, formsemestre_id)
):
# check: s'il existe une decision ou autorisation et qu'elles sont differentes,
# warning (et ne fait rien)
@@ -1133,9 +1131,11 @@ def do_formsemestre_validate_previous_ue(
Si le coefficient est spécifié, modifie le coefficient de
cette UE (utile seulement pour les semestres extérieurs).
"""
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
+
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
cnx = ndb.GetDBConnexion()
- nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etud_ue_status
if ue_coefficient != None:
sco_formsemestre.do_formsemestre_uecoef_edit_or_create(
cnx, formsemestre_id, ue_id, ue_coefficient
diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py
index 0778a0069..983805556 100644
--- a/app/scodoc/sco_moduleimpl_inscriptions.py
+++ b/app/scodoc/sco_moduleimpl_inscriptions.py
@@ -479,21 +479,19 @@ def get_etuds_with_capitalized_ue(formsemestre_id):
returns { ue_id : [ { infos } ] }
"""
UECaps = scu.DictDefault(defaultvalue=[])
- nt = sco_cache.NotesTableCache.get(
- formsemestre_id
- ) # > get_ues_stat_dict, get_etud_ue_status
+ nt = sco_cache.NotesTableCache.get(formsemestre_id)
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id}
)
ues = nt.get_ues_stat_dict()
for ue in ues:
for etud in inscrits:
- status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
- if status["was_capitalized"]:
+ ue_status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
+ if ue_status and ue_status["was_capitalized"]:
UECaps[ue["ue_id"]].append(
{
"etudid": etud["etudid"],
- "ue_status": status,
+ "ue_status": ue_status,
"is_ins": is_inscrit_ue(
etud["etudid"], formsemestre_id, ue["ue_id"]
),
diff --git a/app/scodoc/sco_parcours_dut.py b/app/scodoc/sco_parcours_dut.py
index 4214b70bc..1969dbbcb 100644
--- a/app/scodoc/sco_parcours_dut.py
+++ b/app/scodoc/sco_parcours_dut.py
@@ -111,9 +111,6 @@ class DecisionSem(object):
def SituationEtudParcours(etud, formsemestre_id):
"""renvoie une instance de SituationEtudParcours (ou sous-classe spécialisée)"""
- # nt = sco_cache.NotesTableCache.get(
- # formsemestre_id
- # ) # > get_etud_decision_sem, get_etud_moy_gen, get_ues_stat_dict, get_etud_ue_status, etud_check_conditions_ues
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
parcours = nt.parcours
@@ -924,9 +921,6 @@ def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite)
"""
valid_semestre = CODES_SEM_VALIDES.get(code_etat_sem, False)
cnx = ndb.GetDBConnexion()
- # nt = sco_cache.NotesTableCache.get(
- # formsemestre_id
- # ) # > get_ues_stat_dict, get_etud_ue_status
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
ue_ids = [x["ue_id"] for x in nt.get_ues_stat_dict(filter_sport=True)]
@@ -1005,7 +999,8 @@ def do_formsemestre_validate_ue(
if code == ADM:
if moy_ue is None:
# stocke la moyenne d'UE capitalisée:
- moy_ue = nt.get_etud_ue_status(etudid, ue_id)["moy"]
+ ue_status = nt.get_etud_ue_status(etudid, ue_id)
+ moy_ue = ue_status["moy"] if ue_status else ""
args["moy_ue"] = moy_ue
log("formsemestre_validate_ue: create %s" % args)
if code != None:
diff --git a/app/scodoc/sco_poursuite_dut.py b/app/scodoc/sco_poursuite_dut.py
index 60dfa2ab1..a98014cd1 100644
--- a/app/scodoc/sco_poursuite_dut.py
+++ b/app/scodoc/sco_poursuite_dut.py
@@ -62,13 +62,20 @@ def etud_get_poursuite_info(sem, etud):
dec = nt.get_etud_decision_sem(etudid)
# Moyennes et rangs des UE
ues = nt.get_ues_stat_dict(filter_sport=True)
- moy_ues = [
- (
- ue["acronyme"],
- scu.fmt_note(nt.get_etud_ue_status(etudid, ue["ue_id"])["moy"]),
- )
- for ue in ues
- ]
+ moy_ues = []
+ for ue in ues:
+ ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
+ if ue_status:
+ moy_ues.append(
+ (
+ ue["acronyme"],
+ scu.fmt_note(
+ nt.get_etud_ue_status(etudid, ue["ue_id"])["moy"]
+ ),
+ )
+ )
+ else:
+ moy_ues.append((ue["acronyme"], ""))
rg_ues = [
("rang_" + ue["acronyme"], nt.ue_rangs[ue["ue_id"]][0][etudid])
for ue in ues
diff --git a/app/scodoc/sco_prepajury.py b/app/scodoc/sco_prepajury.py
index 2032841b0..1a6837379 100644
--- a/app/scodoc/sco_prepajury.py
+++ b/app/scodoc/sco_prepajury.py
@@ -50,9 +50,7 @@ from app.scodoc.sco_excel import ScoExcelSheet
def feuille_preparation_jury(formsemestre_id):
"Feuille excel pour preparation des jurys"
- nt = sco_cache.NotesTableCache.get(
- formsemestre_id
- ) # > get_etudids, get_etud_moy_gen, get_ues_stat_dict, get_etud_ue_status, get_etud_decision_sem, identdict,
+ nt = sco_cache.NotesTableCache.get(formsemestre_id)
etudids = nt.get_etudids(sorted=True) # tri par moy gen
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
@@ -83,16 +81,13 @@ def feuille_preparation_jury(formsemestre_id):
etud = info[0]
Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id)
if Se.prev:
- ntp = sco_cache.NotesTableCache.get(
- Se.prev["formsemestre_id"]
- ) # > get_ues_stat_dict, get_etud_ue_status, get_etud_moy_gen, get_etud_decision_sem
+ ntp = sco_cache.NotesTableCache.get(Se.prev["formsemestre_id"])
for ue in ntp.get_ues_stat_dict(filter_sport=True):
ue_status = ntp.get_etud_ue_status(etudid, ue["ue_id"])
ue_code_s = (
ue["ue_code"] + "_%s" % ntp.sem["semestre_id"]
) # code indentifiant l'UE
- prev_moy_ue[ue_code_s][etudid] = ue_status["moy"]
- # prev_ue_acro[ue_code_s] = (ue['numero'], ue['acronyme'])
+ prev_moy_ue[ue_code_s][etudid] = ue_status["moy"] if ue_status else ""
prev_ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
prev_moy[etudid] = ntp.get_etud_moy_gen(etudid)
prev_decision = ntp.get_etud_decision_sem(etudid)
@@ -105,8 +100,7 @@ def feuille_preparation_jury(formsemestre_id):
for ue in nt.get_ues_stat_dict(filter_sport=True):
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
ue_code_s = ue["ue_code"] + "_%s" % nt.sem["semestre_id"]
- moy_ue[ue_code_s][etudid] = ue_status["moy"]
- # ue_acro[ue_code_s] = (ue['numero'], ue['acronyme'])
+ moy_ue[ue_code_s][etudid] = ue_status["moy"] if ue_status else ""
ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
if Se.prev:
diff --git a/app/scodoc/sco_pvjury.py b/app/scodoc/sco_pvjury.py
index 78ec14394..0ed6ebd6e 100644
--- a/app/scodoc/sco_pvjury.py
+++ b/app/scodoc/sco_pvjury.py
@@ -105,7 +105,7 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem):
for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]:
try:
uelist.append(nt.get_etud_ue_status(etudid, ue_id)["ue"])
- except KeyError:
+ except (KeyError, TypeError):
pass
uelist.sort(key=itemgetter("numero"))
@@ -162,13 +162,13 @@ def _comp_ects_by_ue_code(nt, decision_ues):
return ects_by_ue_code
-def _comp_ects_capitalises_by_ue_code(nt, etudid):
+def _comp_ects_capitalises_by_ue_code(nt: NotesTableCompat, etudid: int):
"""Calcul somme des ECTS des UE capitalisees"""
ues = nt.get_ues_stat_dict()
ects_by_ue_code = {}
for ue in ues:
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
- if ue_status["is_capitalized"]:
+ if ue_status and ue_status["is_capitalized"]:
ects_val = float(ue_status["ue"]["ects"] or 0.0)
ects_by_ue_code[ue["ue_code"]] = ects_val
diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py
index 90ca89683..ee7006d98 100644
--- a/app/scodoc/sco_recapcomplet.py
+++ b/app/scodoc/sco_recapcomplet.py
@@ -50,7 +50,6 @@ from app.scodoc import sco_bulletins_json
from app.scodoc import sco_bulletins_xml
from app.scodoc import sco_bulletins, sco_excel
from app.scodoc import sco_codes_parcours
-from app.scodoc import sco_cache
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formations
@@ -307,8 +306,6 @@ def make_formsemestre_recapcomplet(
)[0]
parcours = formsemestre.formation.get_parcours()
- # nt = sco_cache.NotesTableCache.get(formsemestre_id) # sco91
- # sco92 :
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
modimpls = formsemestre.modimpls_sorted
ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport
@@ -891,8 +888,8 @@ def _formsemestre_recapcomplet_xml(
force_publishing=True,
):
"XML export: liste tous les bulletins XML."
-
- nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_table_moyennes_triees
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
T = nt.get_table_moyennes_triees()
if not T:
return "", "", "xml"
@@ -962,7 +959,8 @@ def _formsemestre_recapcomplet_json(
"bulletins": [],
}
bulletins = J["bulletins"]
- nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_table_moyennes_triees
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
T = nt.get_table_moyennes_triees()
for t in T:
etudid = t[-1]
diff --git a/app/views/absences.py b/app/views/absences.py
index 07f4bfdba..4122d5bdb 100644
--- a/app/views/absences.py
+++ b/app/views/absences.py
@@ -729,12 +729,10 @@ def _gen_form_saisie_groupe(
# UE capitalisee dans semestre courant ?
cap = []
if etud["cursem"]:
- nt = sco_cache.NotesTableCache.get(
- etud["cursem"]["formsemestre_id"]
- ) # > get_ues_stat_dict, get_etud_ue_status
+ nt = sco_cache.NotesTableCache.get(etud["cursem"]["formsemestre_id"])
for ue in nt.get_ues_stat_dict():
- status = nt.get_etud_ue_status(etudid, ue["ue_id"])
- if status["is_capitalized"]:
+ ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
+ if ue_status and ue_status["is_capitalized"]:
cap.append(ue["acronyme"])
if cap:
capstr = ' (%s cap.)' % ", ".join(cap)
From b074b24b56d40505632bca44ce2683ab51f642fd Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sun, 13 Feb 2022 09:57:37 +0100
Subject: [PATCH 037/287] Fix: xml compat Bdx
---
app/but/bulletin_but_xml_compat.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/app/but/bulletin_but_xml_compat.py b/app/but/bulletin_but_xml_compat.py
index 279f92847..1369eb672 100644
--- a/app/but/bulletin_but_xml_compat.py
+++ b/app/but/bulletin_but_xml_compat.py
@@ -182,7 +182,10 @@ def bulletin_but_xml_compat(
# Liste ici uniquement les modules rattachés à cette UE
if modimpl.module.ue.id == ue.id:
# mod_moy = scu.fmt_note(results.etud_moy_ue[ue.id][etud.id])
- coef = results.modimpl_coefs_df[modimpl.id][ue.id]
+ try:
+ coef = results.modimpl_coefs_df[modimpl.id][ue.id]
+ except KeyError:
+ coef = 0.0
x_mod = Element(
"module",
id=str(modimpl.id),
From af7ff124fcc092105fb4ba9882ac883dfd7e57bb Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sun, 13 Feb 2022 13:58:09 +0100
Subject: [PATCH 038/287] =?UTF-8?q?feuille=20pr=C3=A9paration=20jury=20(DU?=
=?UTF-8?q?T/BUT)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/comp/moy_sem.py | 14 ++--
app/comp/res_common.py | 41 +++++++---
app/models/etudiants.py | 1 +
app/models/formsemestre.py | 6 +-
app/scodoc/sco_bulletins_pdf.py | 2 +-
app/scodoc/sco_etud.py | 102 ++++++++++++------------
app/scodoc/sco_parcours_dut.py | 12 ++-
app/scodoc/sco_prepajury.py | 132 +++++++++++++++++---------------
8 files changed, 172 insertions(+), 138 deletions(-)
diff --git a/app/comp/moy_sem.py b/app/comp/moy_sem.py
index ae167d4e7..5caa3d393 100644
--- a/app/comp/moy_sem.py
+++ b/app/comp/moy_sem.py
@@ -48,14 +48,15 @@ def compute_sem_moys_apc(
return moy_gen
-def comp_ranks_series(notes: pd.Series) -> dict[int, str]:
+def comp_ranks_series(notes: pd.Series) -> (pd.Series, pd.Series):
"""Calcul rangs à partir d'une séries ("vecteur") de notes (index etudid, valeur
numérique) en tenant compte des ex-aequos.
- Result: { etudid : rang:str } où rang est une chaine decrivant le rang.
+ Result: Series { etudid : rang:str } où rang est une chaine decrivant le rang.
"""
notes = notes.sort_values(ascending=False) # Serie, tri par ordre décroissant
- rangs = pd.Series(index=notes.index, dtype=str) # le rang est une chaîne
+ rangs_str = pd.Series(index=notes.index, dtype=str) # le rang est une chaîne
+ rangs_int = pd.Series(index=notes.index, dtype=int) # le rang numérique pour tris
N = len(notes)
nb_ex = 0 # nb d'ex-aequo consécutifs en cours
notes_i = notes.iat
@@ -67,6 +68,7 @@ def comp_ranks_series(notes: pd.Series) -> dict[int, str]:
next = None
val = notes_i[i]
if nb_ex:
+ rangs_int[etudid] = i + 1 - nb_ex
srang = "%d ex" % (i + 1 - nb_ex)
if val == next:
nb_ex += 1
@@ -74,9 +76,11 @@ def comp_ranks_series(notes: pd.Series) -> dict[int, str]:
nb_ex = 0
else:
if val == next:
+ rangs_int[etudid] = i + 1 - nb_ex
srang = "%d ex" % (i + 1 - nb_ex)
nb_ex = 1
else:
+ rangs_int[etudid] = i + 1
srang = "%d" % (i + 1)
- rangs[etudid] = srang
- return rangs
+ rangs_str[etudid] = srang
+ return rangs_str, rangs_int
diff --git a/app/comp/res_common.py b/app/comp/res_common.py
index 006cbe6b9..2e6429cb1 100644
--- a/app/comp/res_common.py
+++ b/app/comp/res_common.py
@@ -51,6 +51,7 @@ class ResultatsSemestre(ResultatsCache):
"etud_moy_ue: DataFrame columns UE, rows etudid"
self.etud_moy_gen = {}
self.etud_moy_gen_ranks = {}
+ self.etud_moy_gen_ranks_int = {}
self.modimpls_results: ModuleImplResults = None
"Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }"
self.etud_coef_ue_df = None
@@ -302,6 +303,7 @@ class NotesTableCompat(ResultatsSemestre):
"bonus_ues",
"malus",
"etud_moy_gen_ranks",
+ "etud_moy_gen_ranks_int",
"ue_rangs",
)
@@ -319,17 +321,33 @@ class NotesTableCompat(ResultatsSemestre):
self.expr_diagnostics = ""
self.parcours = self.formsemestre.formation.get_parcours()
- def get_etudids(self, sorted=False) -> list[int]:
- """Liste des etudids inscrits, incluant les démissionnaires.
- Si sorted, triée par moy. générale décroissante
- Sinon, triée par ordre alphabetique de NOM
+ def get_inscrits(self, include_demdef=True, order_by=False) -> list[Identite]:
+ """Liste des étudiants inscrits
+ order_by = False|'nom'|'moy' tri sur nom ou sur moyenne générale (indicative)
+
+ Note: pour récupérer les etudids des inscrits, non triés, il est plus efficace
+ d'utiliser `[ ins.etudid for ins in nt.formsemestre.inscriptions ]`
+ """
+ etuds = self.formsemestre.get_inscrits(
+ include_demdef=include_demdef, order=(order_by == "nom")
+ )
+ if order_by == "moy":
+ etuds.sort(
+ key=lambda e: (
+ self.etud_moy_gen_ranks_int.get(e.id, 100000),
+ e.sort_key,
+ )
+ )
+ return etuds
+
+ def get_etudids(self) -> list[int]:
+ """(deprecated)
+ Liste des etudids inscrits, incluant les démissionnaires.
+ triée par ordre alphabetique de NOM
+ (à éviter: renvoie les etudids, mais est moins efficace que get_inscrits)
"""
# Note: pour avoir les inscrits non triés,
# utiliser [ ins.etudid for ins in self.formsemestre.inscriptions ]
- if sorted:
- # Tri par moy. generale décroissante
- return [x[-1] for x in self.T]
-
return [x["etudid"] for x in self.inscrlist]
@cached_property
@@ -385,11 +403,14 @@ class NotesTableCompat(ResultatsSemestre):
Moyenne générale: etud_moy_gen_ranks
Par UE (sauf ue bonus)
"""
- self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
+ (
+ self.etud_moy_gen_ranks,
+ self.etud_moy_gen_ranks_int,
+ ) = moy_sem.comp_ranks_series(self.etud_moy_gen)
for ue in self.formsemestre.query_ues():
moy_ue = self.etud_moy_ue[ue.id]
self.ue_rangs[ue.id] = (
- moy_sem.comp_ranks_series(moy_ue),
+ moy_sem.comp_ranks_series(moy_ue)[0], # juste en chaine
int(moy_ue.count()),
)
# .count() -> nb of non NaN values
diff --git a/app/models/etudiants.py b/app/models/etudiants.py
index d919ee97c..18f13380d 100644
--- a/app/models/etudiants.py
+++ b/app/models/etudiants.py
@@ -134,6 +134,7 @@ class Identite(db.Model):
# ScoDoc7 output_formators: (backward compat)
e["etudid"] = self.id
e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"])
+ e["ne"] = {"M": "", "F": "ne"}.get(self.civilite, "(e)")
return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty
def to_dict_bul(self, include_urls=True):
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index 2d6006c20..c2850c2dc 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -363,17 +363,17 @@ class FormSemestre(db.Model):
etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
)
- def get_inscrits(self, include_demdef=False, sorted=False) -> list[Identite]:
+ def get_inscrits(self, include_demdef=False, order=False) -> list[Identite]:
"""Liste des étudiants inscrits à ce semestre
Si include_demdef, tous les étudiants, avec les démissionnaires
et défaillants.
- Si sorted, tri par clé sort_key
+ Si order, tri par clé sort_key
"""
if include_demdef:
etuds = [ins.etud for ins in self.inscriptions]
else:
etuds = [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT]
- if sorted:
+ if order:
etuds.sort(key=lambda e: e.sort_key)
return etuds
diff --git a/app/scodoc/sco_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py
index fa599fa56..94cfcf6bc 100644
--- a/app/scodoc/sco_bulletins_pdf.py
+++ b/app/scodoc/sco_bulletins_pdf.py
@@ -188,7 +188,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
bookmarks = {}
filigrannes = {}
i = 1
- for etud in formsemestre.get_inscrits(include_demdef=True, sorted=True):
+ for etud in formsemestre.get_inscrits(include_demdef=True, order=True):
frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
formsemestre_id,
etud.id,
diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py
index b4af85fda..d019d2df2 100644
--- a/app/scodoc/sco_etud.py
+++ b/app/scodoc/sco_etud.py
@@ -867,11 +867,7 @@ def fill_etuds_info(etuds, add_admission=True):
Si add_admission: ajoute au dict le schamps "admission" s'il n'y sont pas déjà.
"""
- from app.scodoc import sco_formsemestre
- from app.scodoc import sco_formsemestre_inscriptions
-
cnx = ndb.GetDBConnexion()
- # open('/tmp/t','w').write( str(etuds) )
for etud in etuds:
etudid = etud["etudid"]
etud["dept"] = g.scodoc_dept
@@ -894,49 +890,7 @@ def fill_etuds_info(etuds, add_admission=True):
etud.update(adr)
format_etud_ident(etud)
- # Semestres dans lesquel il est inscrit
- ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
- {"etudid": etudid}
- )
- etud["ins"] = ins
- sems = []
- cursem = None # semestre "courant" ou il est inscrit
- for i in ins:
- sem = sco_formsemestre.get_formsemestre(i["formsemestre_id"])
- if sco_formsemestre.sem_est_courant(sem):
- cursem = sem
- curi = i
- sem["ins"] = i
- sems.append(sem)
- # trie les semestres par date de debut, le plus recent d'abord
- # (important, ne pas changer (suivi cohortes))
- sems.sort(key=itemgetter("dateord"), reverse=True)
- etud["sems"] = sems
- etud["cursem"] = cursem
- if cursem:
- etud["inscription"] = cursem["titremois"]
- etud["inscriptionstr"] = "Inscrit en " + cursem["titremois"]
- etud["inscription_formsemestre_id"] = cursem["formsemestre_id"]
- etud["etatincursem"] = curi["etat"]
- etud["situation"] = descr_situation_etud(etudid, etud["ne"])
- # XXX est-ce utile ? sco_groups.etud_add_group_infos( etud, cursem)
- else:
- if etud["sems"]:
- if etud["sems"][0]["dateord"] > time.strftime(
- "%Y-%m-%d", time.localtime()
- ):
- etud["inscription"] = "futur"
- etud["situation"] = "futur élève"
- else:
- etud["inscription"] = "ancien"
- etud["situation"] = "ancien élève"
- else:
- etud["inscription"] = "non inscrit"
- etud["situation"] = etud["inscription"]
- etud["inscriptionstr"] = etud["inscription"]
- etud["inscription_formsemestre_id"] = None
- # XXXetud['partitions'] = {} # ne va pas chercher les groupes des anciens semestres
- etud["etatincursem"] = "?"
+ etud.update(etud_inscriptions_infos(etudid, etud["ne"]))
# nettoyage champs souvent vides
if etud.get("nomlycee"):
@@ -974,8 +928,56 @@ def fill_etuds_info(etuds, add_admission=True):
etud["telephonemobilestr"] = ""
-def descr_situation_etud(etudid, ne=""):
- """chaine decrivant la situation actuelle de l'etudiant"""
+def etud_inscriptions_infos(etudid: int, ne="") -> dict:
+ """Dict avec les informations sur les semestres passés et courant"""
+ from app.scodoc import sco_formsemestre
+ from app.scodoc import sco_formsemestre_inscriptions
+
+ etud = {}
+ # Semestres dans lesquel il est inscrit
+ ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
+ {"etudid": etudid}
+ )
+ etud["ins"] = ins
+ sems = []
+ cursem = None # semestre "courant" ou il est inscrit
+ for i in ins:
+ sem = sco_formsemestre.get_formsemestre(i["formsemestre_id"])
+ if sco_formsemestre.sem_est_courant(sem):
+ cursem = sem
+ curi = i
+ sem["ins"] = i
+ sems.append(sem)
+ # trie les semestres par date de debut, le plus recent d'abord
+ # (important, ne pas changer (suivi cohortes))
+ sems.sort(key=itemgetter("dateord"), reverse=True)
+ etud["sems"] = sems
+ etud["cursem"] = cursem
+ if cursem:
+ etud["inscription"] = cursem["titremois"]
+ etud["inscriptionstr"] = "Inscrit en " + cursem["titremois"]
+ etud["inscription_formsemestre_id"] = cursem["formsemestre_id"]
+ etud["etatincursem"] = curi["etat"]
+ etud["situation"] = descr_situation_etud(etudid, ne)
+ else:
+ if etud["sems"]:
+ if etud["sems"][0]["dateord"] > time.strftime("%Y-%m-%d", time.localtime()):
+ etud["inscription"] = "futur"
+ etud["situation"] = "futur élève"
+ else:
+ etud["inscription"] = "ancien"
+ etud["situation"] = "ancien élève"
+ else:
+ etud["inscription"] = "non inscrit"
+ etud["situation"] = etud["inscription"]
+ etud["inscriptionstr"] = etud["inscription"]
+ etud["inscription_formsemestre_id"] = None
+ etud["etatincursem"] = "?"
+ return etud
+
+
+def descr_situation_etud(etudid: int, ne="") -> str:
+ """chaîne décrivant la situation actuelle de l'étudiant"""
from app.scodoc import sco_formsemestre
cnx = ndb.GetDBConnexion()
@@ -992,7 +994,7 @@ def descr_situation_etud(etudid, ne=""):
)
r = cursor.dictfetchone()
if not r:
- situation = "non inscrit"
+ situation = "non inscrit" + ne
else:
sem = sco_formsemestre.get_formsemestre(r["formsemestre_id"])
if r["etat"] == "I":
diff --git a/app/scodoc/sco_parcours_dut.py b/app/scodoc/sco_parcours_dut.py
index 1969dbbcb..32c1cb3ac 100644
--- a/app/scodoc/sco_parcours_dut.py
+++ b/app/scodoc/sco_parcours_dut.py
@@ -36,7 +36,7 @@ import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
from app.scodoc.scolog import logdb
-from app.scodoc import sco_cache
+from app.scodoc import sco_cache, sco_etud
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formations
from app.scodoc.sco_codes_parcours import (
@@ -319,9 +319,6 @@ class SituationEtudParcoursGeneric(object):
sont validés. En sortie, sem_idx_set contient ceux qui n'ont pas été validés."""
for sem in self.get_semestres():
if sem["formation_code"] == self.formation.formation_code:
- # nt = sco_cache.NotesTableCache.get(
- # sem["formsemestre_id"]
- # ) # > get_etud_decision_sem
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
decision = nt.get_etud_decision_sem(self.etudid)
@@ -333,15 +330,16 @@ class SituationEtudParcoursGeneric(object):
def _comp_semestres(self):
# etud['sems'] est trie par date decroissante (voir fill_etuds_info)
+ if not "sems" in self.etud:
+ self.etud["sems"] = sco_etud.etud_inscriptions_infos(
+ self.etud["etudid"], self.etud["ne"]
+ )["sems"]
sems = self.etud["sems"][:] # copy
sems.reverse()
# Nb max d'UE et acronymes
ue_acros = {} # acronyme ue : 1
nb_max_ue = 0
for sem in sems:
- # nt = sco_cache.NotesTableCache.get(
- # sem["formsemestre_id"]
- # ) # > get_ues_stat_dict
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
ues = nt.get_ues_stat_dict(filter_sport=True)
diff --git a/app/scodoc/sco_prepajury.py b/app/scodoc/sco_prepajury.py
index 1a6837379..a15b9b0db 100644
--- a/app/scodoc/sco_prepajury.py
+++ b/app/scodoc/sco_prepajury.py
@@ -31,27 +31,31 @@ import time
from openpyxl.styles.numbers import FORMAT_NUMBER_00
+from flask import flash
from flask import request
from flask_login import current_user
-import app.scodoc.sco_utils as scu
+from app.comp import res_sem
+from app.comp.res_common import NotesTableCompat
+from app.models import FormSemestre, Identite
from app.scodoc import sco_abs
+from app.scodoc import sco_codes_parcours
from app.scodoc import sco_groups
-from app.scodoc import sco_cache
+from app.scodoc import sco_etud
from app.scodoc import sco_excel
from app.scodoc import sco_formsemestre
from app.scodoc import sco_parcours_dut
-from app.scodoc import sco_codes_parcours
-import sco_version
-from app.scodoc import sco_etud
from app.scodoc import sco_preferences
from app.scodoc.sco_excel import ScoExcelSheet
+import app.scodoc.sco_utils as scu
+import sco_version
def feuille_preparation_jury(formsemestre_id):
"Feuille excel pour preparation des jurys"
- nt = sco_cache.NotesTableCache.get(formsemestre_id)
- etudids = nt.get_etudids(sorted=True) # tri par moy gen
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
+ etuds: Identite = nt.get_inscrits(order_by="moy") # tri par moy gen
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etud_groups = sco_groups.formsemestre_get_etud_groupnames(formsemestre_id)
@@ -74,64 +78,65 @@ def feuille_preparation_jury(formsemestre_id):
groupestd = {} # etudid : nom groupe principal
nbabs = {}
nbabsjust = {}
- for etudid in etudids:
- info = sco_etud.get_etud_info(etudid=etudid, filled=True)
- if not info:
- continue # should not occur...
- etud = info[0]
- Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id)
+ for etud in etuds:
+ Se = sco_parcours_dut.SituationEtudParcours(
+ etud.to_dict_scodoc7(), formsemestre_id
+ )
if Se.prev:
- ntp = sco_cache.NotesTableCache.get(Se.prev["formsemestre_id"])
+ formsemestre_prev = FormSemestre.query.get_or_404(
+ Se.prev["formsemestre_id"]
+ )
+ ntp: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre_prev)
for ue in ntp.get_ues_stat_dict(filter_sport=True):
- ue_status = ntp.get_etud_ue_status(etudid, ue["ue_id"])
+ ue_status = ntp.get_etud_ue_status(etud.id, ue["ue_id"])
ue_code_s = (
ue["ue_code"] + "_%s" % ntp.sem["semestre_id"]
) # code indentifiant l'UE
- prev_moy_ue[ue_code_s][etudid] = ue_status["moy"] if ue_status else ""
+ prev_moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else ""
prev_ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
- prev_moy[etudid] = ntp.get_etud_moy_gen(etudid)
- prev_decision = ntp.get_etud_decision_sem(etudid)
+ prev_moy[etud.id] = ntp.get_etud_moy_gen(etud.id)
+ prev_decision = ntp.get_etud_decision_sem(etud.id)
if prev_decision:
- prev_code[etudid] = prev_decision["code"]
+ prev_code[etud.id] = prev_decision["code"]
if prev_decision["compense_formsemestre_id"]:
- prev_code[etudid] += "+" # indique qu'il a servi a compenser
+ prev_code[etud.id] += "+" # indique qu'il a servi a compenser
- moy[etudid] = nt.get_etud_moy_gen(etudid)
+ moy[etud.id] = nt.get_etud_moy_gen(etud.id)
for ue in nt.get_ues_stat_dict(filter_sport=True):
- ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
+ ue_status = nt.get_etud_ue_status(etud.id, ue["ue_id"])
ue_code_s = ue["ue_code"] + "_%s" % nt.sem["semestre_id"]
- moy_ue[ue_code_s][etudid] = ue_status["moy"] if ue_status else ""
+ moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else ""
ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
if Se.prev:
try:
- moy_inter[etudid] = (moy[etudid] + prev_moy[etudid]) / 2.0
+ moy_inter[etud.id] = (moy[etud.id] + prev_moy[etud.id]) / 2.0
except:
pass
- decision = nt.get_etud_decision_sem(etudid)
+ decision = nt.get_etud_decision_sem(etud.id)
if decision:
- code[etudid] = decision["code"]
+ code[etud.id] = decision["code"]
if decision["compense_formsemestre_id"]:
- code[etudid] += "+" # indique qu'il a servi a compenser
- assidu[etudid] = {False: "Non", True: "Oui"}.get(decision["assidu"], "")
+ code[etud.id] += "+" # indique qu'il a servi a compenser
+ assidu[etud.id] = {False: "Non", True: "Oui"}.get(decision["assidu"], "")
aut_list = sco_parcours_dut.formsemestre_get_autorisation_inscription(
- etudid, formsemestre_id
+ etud.id, formsemestre_id
)
- autorisations[etudid] = ", ".join(["S%s" % x["semestre_id"] for x in aut_list])
+ autorisations[etud.id] = ", ".join(["S%s" % x["semestre_id"] for x in aut_list])
# parcours:
- parcours[etudid] = Se.get_parcours_descr()
+ parcours[etud.id] = Se.get_parcours_descr()
# groupe principal (td)
- groupestd[etudid] = ""
- for s in etud["sems"]:
+ groupestd[etud.id] = ""
+ for s in Se.etud["sems"]:
if s["formsemestre_id"] == formsemestre_id:
- groupestd[etudid] = etud_groups.get(etudid, {}).get(
+ groupestd[etud.id] = etud_groups.get(etud.id, {}).get(
main_partition_id, ""
)
# absences:
- e_nbabs, e_nbabsjust = sco_abs.get_abs_count(etudid, sem)
- nbabs[etudid] = e_nbabs
- nbabsjust[etudid] = e_nbabs - e_nbabsjust
+ e_nbabs, e_nbabsjust = sco_abs.get_abs_count(etud.id, sem)
+ nbabs[etud.id] = e_nbabs
+ nbabsjust[etud.id] = e_nbabs - e_nbabsjust
# Codes des UE "semestre précédent":
ue_prev_codes = list(prev_moy_ue.keys())
@@ -223,26 +228,26 @@ def feuille_preparation_jury(formsemestre_id):
return x
i = 1 # numero etudiant
- for etudid in etudids:
+ for etud in etuds:
cells = []
- etud = nt.identdict[etudid]
cells.append(ws.make_cell(str(i)))
if sco_preferences.get_preference("prepa_jury_nip"):
- cells.append(ws.make_cell(etud["code_nip"]))
+ cells.append(ws.make_cell(etud.code_nip))
if sco_preferences.get_preference("prepa_jury_ine"):
- cells.append(ws.make_cell(etud["code_ine"]))
+ cells.append(ws.make_cell(etud.code_ine))
+ admission = etud.admission.first()
cells += ws.make_row(
[
- etudid,
- etud["civilite_str"],
- sco_etud.format_nom(etud["nom"]),
- sco_etud.format_prenom(etud["prenom"]),
- etud["date_naissance"],
- etud["bac"],
- etud["specialite"],
- etud["classement"],
- parcours[etudid],
- groupestd[etudid],
+ etud.id,
+ etud.civilite_str,
+ sco_etud.format_nom(etud.nom),
+ sco_etud.format_prenom(etud.prenom),
+ etud.date_naissance,
+ admission.bac,
+ admission.specialite,
+ admission.classement,
+ parcours[etud.id],
+ groupestd[etud.id],
]
)
co = len(cells)
@@ -250,33 +255,35 @@ def feuille_preparation_jury(formsemestre_id):
for ue_acro in ue_prev_codes:
cells.append(
ws.make_cell(
- fmt(prev_moy_ue.get(ue_acro, {}).get(etudid, "")), style_note
+ fmt(prev_moy_ue.get(ue_acro, {}).get(etud.id, "")), style_note
)
)
co += 1
cells.append(
- ws.make_cell(fmt(prev_moy.get(etudid, "")), style_bold)
+ ws.make_cell(fmt(prev_moy.get(etud.id, "")), style_bold)
) # moy gen prev
cells.append(
- ws.make_cell(fmt(prev_code.get(etudid, "")), style_moy)
+ ws.make_cell(fmt(prev_code.get(etud.id, "")), style_moy)
) # decision prev
co += 2
for ue_acro in ue_codes:
cells.append(
- ws.make_cell(fmt(moy_ue.get(ue_acro, {}).get(etudid, "")), style_note)
+ ws.make_cell(fmt(moy_ue.get(ue_acro, {}).get(etud.id, "")), style_note)
)
co += 1
- cells.append(ws.make_cell(fmt(moy.get(etudid, "")), style_note_bold)) # moy gen
+ cells.append(
+ ws.make_cell(fmt(moy.get(etud.id, "")), style_note_bold)
+ ) # moy gen
co += 1
if moy_inter:
- cells.append(ws.make_cell(fmt(moy_inter.get(etudid, "")), style_note))
- cells.append(ws.make_cell(str(nbabs.get(etudid, "")), style_center))
- cells.append(ws.make_cell(str(nbabsjust.get(etudid, "")), style_center))
+ cells.append(ws.make_cell(fmt(moy_inter.get(etud.id, "")), style_note))
+ cells.append(ws.make_cell(str(nbabs.get(etud.id, "")), style_center))
+ cells.append(ws.make_cell(str(nbabsjust.get(etud.id, "")), style_center))
if code:
- cells.append(ws.make_cell(code.get(etudid, ""), style_moy))
- cells.append(ws.make_cell(autorisations.get(etudid, ""), style_moy))
- # l.append(assidu.get(etudid, ''))
+ cells.append(ws.make_cell(code.get(etud.id, ""), style_moy))
+ cells.append(ws.make_cell(autorisations.get(etud.id, ""), style_moy))
+ # l.append(assidu.get(etud.id, ''))
ws.append_row(cells)
i += 1
#
@@ -320,6 +327,7 @@ def feuille_preparation_jury(formsemestre_id):
)
)
xls = ws.generate()
+ flash("Feuille préparation jury générée")
return scu.send_file(
xls,
f"PrepaJury{sn}",
From 42511ba04c9c9f66584ccdd917cd70fb17ef4648 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sun, 13 Feb 2022 14:11:15 +0100
Subject: [PATCH 039/287] Fix #301
---
app/scodoc/sco_moduleimpl_inscriptions.py | 16 ++++++++--------
app/scodoc/sco_prepajury.py | 1 -
2 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py
index 983805556..cbd9a3b0d 100644
--- a/app/scodoc/sco_moduleimpl_inscriptions.py
+++ b/app/scodoc/sco_moduleimpl_inscriptions.py
@@ -402,14 +402,14 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
H.append("")
H.append("")
- H.append(
- """
Cette page décrit les inscriptions actuelles.
- Vous pouvez changer (si vous en avez le droit) les inscrits dans chaque module en
- cliquant sur la ligne du module.
-
Note: la déinscription d'un module ne perd pas les notes. Ainsi, si
- l'étudiant est ensuite réinscrit au même module, il retrouvera ses notes.
- """
- )
+ H.append(
+ """
Cette page décrit les inscriptions actuelles.
+ Vous pouvez changer (si vous en avez le droit) les inscrits dans chaque module en
+ cliquant sur la ligne du module.
+
Note: la déinscription d'un module ne perd pas les notes. Ainsi, si
+ l'étudiant est ensuite réinscrit au même module, il retrouvera ses notes.
+ """
+ )
H.append(html_sco_header.sco_footer())
return "\n".join(H)
diff --git a/app/scodoc/sco_prepajury.py b/app/scodoc/sco_prepajury.py
index 1a6837379..12a1f2525 100644
--- a/app/scodoc/sco_prepajury.py
+++ b/app/scodoc/sco_prepajury.py
@@ -45,7 +45,6 @@ from app.scodoc import sco_codes_parcours
import sco_version
from app.scodoc import sco_etud
from app.scodoc import sco_preferences
-from app.scodoc.sco_excel import ScoExcelSheet
def feuille_preparation_jury(formsemestre_id):
From 3cafbf5988662d218fae0bbe03427acfc93e0f43 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sun, 13 Feb 2022 15:19:39 +0100
Subject: [PATCH 040/287] Fixes #293 : suppression utilisateur
---
app/auth/models.py | 6 ++++--
scodoc.py | 30 ++++++++++++++++++++++++------
2 files changed, 28 insertions(+), 8 deletions(-)
diff --git a/app/auth/models.py b/app/auth/models.py
index 32768df03..329bc3868 100644
--- a/app/auth/models.py
+++ b/app/auth/models.py
@@ -76,7 +76,9 @@ class User(UserMixin, db.Model):
"Departement",
foreign_keys=[Departement.acronym],
primaryjoin=(dept == Departement.acronym),
- lazy="dynamic",
+ lazy="select",
+ passive_deletes="all",
+ uselist=False,
)
def __init__(self, **kwargs):
@@ -236,7 +238,7 @@ class User(UserMixin, db.Model):
def get_dept_id(self) -> int:
"returns user's department id, or None"
if self.dept:
- return self._departement.first().id
+ return self._departement.id
return None
# Permissions management:
diff --git a/scodoc.py b/scodoc.py
index 28dfe7cfe..5b76ccb0e 100755
--- a/scodoc.py
+++ b/scodoc.py
@@ -14,6 +14,8 @@ import click
import flask
from flask.cli import with_appcontext
from flask.templating import render_template
+import psycopg2
+import sqlalchemy
from app import create_app, cli, db
from app import initialize_scodoc_database
@@ -133,11 +135,11 @@ def user_create(username, role, dept, nom=None, prenom=None): # user-create
"Create a new user"
r = Role.get_named_role(role)
if not r:
- sys.stderr.write("user_create: role {r} does not exist\n".format(r=role))
+ sys.stderr.write(f"user_create: role {role} does not exist\n")
return 1
u = User.query.filter_by(user_name=username).first()
if u:
- sys.stderr.write("user_create: user {u} already exists\n".format(u=u))
+ sys.stderr.write(f"user_create: user {u} already exists\n")
return 2
if dept == "@all":
dept = None
@@ -145,11 +147,26 @@ def user_create(username, role, dept, nom=None, prenom=None): # user-create
u.add_role(r, dept)
db.session.add(u)
db.session.commit()
- click.echo(
- "created user, login: {u.user_name}, with role {r} in dept. {dept}".format(
- u=u, r=r, dept=dept
+ click.echo(f"created user, login: {u.user_name}, with role {r} in dept. {dept}")
+
+
+@app.cli.command()
+@click.argument("username")
+def user_delete(username): # user-delete
+ "Try to delete this user. Fails if it's associated to some scodoc objects."
+ u = User.query.filter_by(user_name=username).first()
+ if not u:
+ sys.stderr.write(f"user_delete: user {username} not found\n")
+ return 2
+ db.session.delete(u)
+ try:
+ db.session.commit()
+ except (sqlalchemy.exc.IntegrityError, psycopg2.errors.ForeignKeyViolation):
+ sys.stderr.write(
+ f"""\nuser_delete: ne peux pas supprimer l'utilisateur {username}\ncar il est associé à des objets dans ScoDoc (modules, notes, ...).\n"""
)
- )
+ return 1
+ click.echo(f"deleted user, login: {username}")
@app.cli.command()
@@ -485,6 +502,7 @@ def recursive_help(cmd, parent=None):
@app.cli.command()
def dumphelp():
+ """Génère la page d'aide complète pour la doc."""
recursive_help(app.cli)
From 45c845d23bb92c1b3311a5b21feb63ba06b61fe8 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sun, 13 Feb 2022 15:50:16 +0100
Subject: [PATCH 041/287] was_capitalized / optim. -nt
---
app/comp/res_common.py | 12 +++++-
app/scodoc/sco_formsemestre_inscriptions.py | 17 ++++++---
app/scodoc/sco_moduleimpl_inscriptions.py | 8 +++-
app/scodoc/sco_report.py | 42 ++++++++++-----------
scodoc.py | 1 +
5 files changed, 52 insertions(+), 28 deletions(-)
diff --git a/app/comp/res_common.py b/app/comp/res_common.py
index 006cbe6b9..5b9ef625f 100644
--- a/app/comp/res_common.py
+++ b/app/comp/res_common.py
@@ -219,6 +219,7 @@ class ResultatsSemestre(ResultatsCache):
if ue.type == UE_SPORT:
return {
"is_capitalized": False,
+ "was_capitalized": False,
"is_external": False,
"coef_ue": 0.0,
"cur_moy_ue": 0.0,
@@ -235,7 +236,10 @@ class ResultatsSemestre(ResultatsCache):
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
cur_moy_ue = self.etud_moy_ue[ue_id][etudid]
moy_ue = cur_moy_ue
- is_capitalized = False
+ is_capitalized = False # si l'UE prise en compte est une UE capitalisée
+ was_capitalized = (
+ False # s'il y a precedemment une UE capitalisée (pas forcement meilleure)
+ )
if etudid in self.validations.ue_capitalisees.index:
ue_cap = self._get_etud_ue_cap(etudid, ue)
if (
@@ -243,6 +247,7 @@ class ResultatsSemestre(ResultatsCache):
and not ue_cap.empty
and not np.isnan(ue_cap["moy_ue"])
):
+ was_capitalized = True
if ue_cap["moy_ue"] > cur_moy_ue or np.isnan(cur_moy_ue):
moy_ue = ue_cap["moy_ue"]
is_capitalized = True
@@ -251,6 +256,7 @@ class ResultatsSemestre(ResultatsCache):
return {
"is_capitalized": is_capitalized,
+ "was_capitalized": was_capitalized,
"is_external": ue_cap["is_external"] if is_capitalized else ue.is_external,
"coef_ue": coef_ue,
"ects_pot": ue.ects or 0.0,
@@ -425,6 +431,10 @@ class NotesTableCompat(ResultatsSemestre):
ue_status_list.append(ue_status)
return self.parcours.check_barre_ues(ue_status_list)
+ def etud_has_decision(self, etudid):
+ """True s'il y a une décision de jury pour cet étudiant"""
+ return self.get_etud_decision_ues(etudid) or self.get_etud_decision_sem(etudid)
+
def get_etud_decision_ues(self, etudid: int) -> dict:
"""Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu.
Ne tient pas compte des UE capitalisées.
diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py
index 6ecfa32f2..3e398c837 100644
--- a/app/scodoc/sco_formsemestre_inscriptions.py
+++ b/app/scodoc/sco_formsemestre_inscriptions.py
@@ -32,14 +32,16 @@ import time
import flask
from flask import url_for, g, request
+from app.comp import res_sem
+from app.comp.res_common import NotesTableCompat
+from app.models import FormSemestre
import app.scodoc.sco_utils as scu
from app import log
from app.scodoc.scolog import logdb
from app.scodoc.sco_exceptions import ScoException, ScoValueError
-from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_codes_parcours import UE_STANDARD, UE_SPORT, UE_TYPE_NAME
import app.scodoc.notesdb as ndb
-from app.scodoc.TrivialFormulator import TrivialFormulator, TF
+from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc import sco_find_etud
from app.scodoc import sco_formsemestre
from app.scodoc import sco_moduleimpl
@@ -186,7 +188,9 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
raise ScoValueError("desinscription impossible: semestre verrouille")
# -- Si decisions de jury, desinscription interdite
- nt = sco_cache.NotesTableCache.get(formsemestre_id)
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
+
if nt.etud_has_decision(etudid):
raise ScoValueError(
"desinscription impossible: l'étudiant a une décision de jury (la supprimer avant si nécessaire)"
@@ -475,7 +479,8 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
raise ScoValueError("Modification impossible: semestre verrouille")
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
- nt = sco_cache.NotesTableCache.get(formsemestre_id)
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
F = html_sco_header.sco_footer()
H = [
@@ -785,7 +790,9 @@ def list_inscrits_ailleurs(formsemestre_id):
Pour chacun, donne la liste des semestres.
{ etudid : [ liste de sems ] }
"""
- nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
+
etudids = nt.get_etudids()
d = {}
for etudid in etudids:
diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py
index cbd9a3b0d..a0266219f 100644
--- a/app/scodoc/sco_moduleimpl_inscriptions.py
+++ b/app/scodoc/sco_moduleimpl_inscriptions.py
@@ -33,6 +33,10 @@ import flask
from flask import url_for, g, request
from flask_login import current_user
+from app.comp import res_sem
+from app.comp.res_common import NotesTableCompat
+from app.models import FormSemestre
+
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app import log
@@ -479,7 +483,9 @@ def get_etuds_with_capitalized_ue(formsemestre_id):
returns { ue_id : [ { infos } ] }
"""
UECaps = scu.DictDefault(defaultvalue=[])
- nt = sco_cache.NotesTableCache.get(formsemestre_id)
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
+
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id}
)
diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py
index 6c2582ef1..fff026471 100644
--- a/app/scodoc/sco_report.py
+++ b/app/scodoc/sco_report.py
@@ -39,14 +39,16 @@ from operator import itemgetter
from flask import url_for, g, request
import pydot
+from app.comp import res_sem
+from app.comp.res_common import NotesTableCompat
+from app.models import FormSemestre
+
import app.scodoc.sco_utils as scu
from app.models import FormationModalite
from app.scodoc import notesdb as ndb
from app.scodoc import html_sco_header
from app.scodoc import sco_codes_parcours
-from app.scodoc import sco_cache
from app.scodoc import sco_etud
-from app.scodoc import sco_excel
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_parcours_dut
@@ -61,9 +63,9 @@ MAX_ETUD_IN_DESCR = 20
def formsemestre_etuds_stats(sem, only_primo=False):
"""Récupère liste d'etudiants avec etat et decision."""
- nt = sco_cache.NotesTableCache.get(
- sem["formsemestre_id"]
- ) # > get_table_moyennes_triees, identdict, get_etud_decision_sem, get_etud_etat,
+ formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
+
T = nt.get_table_moyennes_triees()
# Construit liste d'étudiants du semestre avec leur decision
etuds = []
@@ -400,9 +402,8 @@ def table_suivi_cohorte(
logt("table_suivi_cohorte: start")
# 1-- Liste des semestres posterieurs dans lesquels ont été les etudiants de sem
- nt = sco_cache.NotesTableCache.get(
- formsemestre_id
- ) # > get_etudids, get_etud_decision_sem
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
etudids = nt.get_etudids()
logt("A: orig etuds set")
@@ -456,9 +457,8 @@ def table_suivi_cohorte(
s["members"] = orig_set.intersection(inset)
nb_dipl = 0 # combien de diplomes dans ce semestre ?
if s["semestre_id"] == nt.parcours.NB_SEM:
- nt = sco_cache.NotesTableCache.get(
- s["formsemestre_id"]
- ) # > get_etud_decision_sem
+ s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre)
for etudid in s["members"]:
dec = nt.get_etud_decision_sem(etudid)
if dec and code_semestre_validant(dec["code"]):
@@ -905,9 +905,9 @@ def _descr_etud_set(etudids):
def _count_dem_reo(formsemestre_id, etudids):
"count nb of demissions and reorientation in this etud set"
- nt = sco_cache.NotesTableCache.get(
- formsemestre_id
- ) # > get_etud_etat, get_etud_decision_sem
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
+
dems = set()
reos = set()
for etudid in etudids:
@@ -971,9 +971,9 @@ def get_codeparcoursetud(etud, prefix="", separator=""):
i = len(sems) - 1
while i >= 0:
s = sems[i] # 'sems' est a l'envers, du plus recent au plus ancien
- nt = sco_cache.NotesTableCache.get(
- s["formsemestre_id"]
- ) # > get_etud_etat, get_etud_decision_sem
+ s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre)
+
p.append(_codesem(s, prefix=prefix))
# code decisions jury de chaque semestre:
if nt.get_etud_etat(etud["etudid"]) == "D":
@@ -1017,7 +1017,8 @@ def tsp_etud_list(
"""
# log('tsp_etud_list(%s, bac="%s")' % (formsemestre_id,bac))
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
- nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids,
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
etudids = nt.get_etudids()
etuds = []
bacs = set()
@@ -1260,9 +1261,8 @@ def graph_parcours(
nxt = {}
etudid = etud["etudid"]
for s in etud["sems"]: # du plus recent au plus ancien
- nt = sco_cache.NotesTableCache.get(
- s["formsemestre_id"]
- ) # > get_etud_decision_sem, get_etud_etat
+ s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre)
dec = nt.get_etud_decision_sem(etudid)
if nxt:
if (
diff --git a/scodoc.py b/scodoc.py
index 5b76ccb0e..4dc5165f7 100755
--- a/scodoc.py
+++ b/scodoc.py
@@ -434,6 +434,7 @@ def localize_logo(logo: str = None, dept: str = None): # migrate-scodoc7-dept-l
@click.argument("xlsfile", type=click.File("rb"))
@click.argument("zipfile", type=click.File("rb"))
def photos_import_files(formsemestre_id: int, xlsfile: str, zipfile: str):
+ """Import des photos d'étudiants à partir d'une liste excel et d'un zip avec les images."""
import app as mapp
from app.scodoc import sco_trombino, sco_photos
from app.scodoc import notesdb as ndb
From e7a97919b4aa299ec9832d1c92dcf9e4c845d794 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sun, 13 Feb 2022 22:01:39 +0100
Subject: [PATCH 042/287] Fix: bac None
---
app/scodoc/sco_bac.py | 26 ++++++++++++++++++--------
1 file changed, 18 insertions(+), 8 deletions(-)
diff --git a/app/scodoc/sco_bac.py b/app/scodoc/sco_bac.py
index ce00d96c7..6f316a10c 100644
--- a/app/scodoc/sco_bac.py
+++ b/app/scodoc/sco_bac.py
@@ -132,19 +132,29 @@ BACS_S = {t[0]: t[2:] for t in _BACS}
class Baccalaureat:
def __init__(self, bac, specialite=""):
- self.bac = bac
- self.specialite = specialite
- self._abbrev, self._type = BACS_SSP.get((bac, specialite), (None, None))
+ self.bac = bac or ""
+ self.specialite = specialite or ""
+ self._abbrev, self._type = BACS_SSP.get(
+ (self.bac, self.specialite), (None, None)
+ )
# Parfois, la specialite commence par la serie: essaye
- if self._type is None and specialite and specialite.startswith(bac):
- specialite = specialite[len(bac) :].strip(" -")
- self._abbrev, self._type = BACS_SSP.get((bac, specialite), (None, None))
+ if (
+ self._type is None
+ and self.specialite
+ and self.specialite.startswith(self.bac)
+ ):
+ specialite = self.specialite[len(self.bac) :].strip(" -")
+ self._abbrev, self._type = BACS_SSP.get(
+ (self.bac, specialite), (None, None)
+ )
# Cherche la forme serie specialite
if self._type is None and specialite:
- self._abbrev, self._type = BACS_S.get(bac + " " + specialite, (None, None))
+ self._abbrev, self._type = BACS_S.get(
+ self.bac + " " + specialite, (None, None)
+ )
# Cherche avec juste le bac, sans specialite
if self._type is None:
- self._abbrev, self._type = BACS_S.get(bac, (None, None))
+ self._abbrev, self._type = BACS_S.get(self.bac, (None, None))
def abbrev(self):
"abbreviation for this bac"
From b28d22b3ed95aab5ce46efb17b2621fc6d8a7dfe Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sun, 13 Feb 2022 22:03:46 +0100
Subject: [PATCH 043/287] Modif css ue color var name
---
app/views/scodoc.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/views/scodoc.py b/app/views/scodoc.py
index e5cdc7719..e65c4e666 100644
--- a/app/views/scodoc.py
+++ b/app/views/scodoc.py
@@ -374,7 +374,7 @@ def ue_colors_css(formation_id: int, semestre_idx: int):
":root{\n"
+ "\n".join(
[
- f"--color-UE{semestre_idx}.{ue_idx+1}: {ue.color};"
+ f"--color-UE{semestre_idx}-{ue_idx+1}: {ue.color};"
for ue_idx, ue in enumerate(ues)
if ue.color
]
From f3d2420117ddf212bc5520b0cef2dbfef4c51629 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sun, 13 Feb 2022 22:08:16 +0100
Subject: [PATCH 044/287] Fix: bug St Malo
---
app/comp/moy_ue.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/comp/moy_ue.py b/app/comp/moy_ue.py
index bb341baa0..b0a534e51 100644
--- a/app/comp/moy_ue.py
+++ b/app/comp/moy_ue.py
@@ -234,12 +234,12 @@ def compute_ue_moys_apc(
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
modimpl_coefs_df: matrice coefficients (UE x modimpl), sans UEs bonus sport
- Résultat: DataFrame columns UE (sans sport), rows etudid
+ Résultat: DataFrame columns UE (sans bonus), rows etudid
"""
nb_etuds, nb_modules, nb_ues_no_bonus = sem_cube.shape
nb_ues_tot = len(ues)
assert len(modimpls) == nb_modules
- if nb_modules == 0 or nb_etuds == 0:
+ if nb_modules == 0 or nb_etuds == 0 or nb_ues_no_bonus == 0:
return pd.DataFrame(
index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index
)
From 129a39f7f0d0f67ffefa1c06482c3439c4db4e06 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sun, 13 Feb 2022 22:25:51 +0100
Subject: [PATCH 045/287] Fix: robustifie get_evals_in_mod
---
app/comp/res_common.py | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/app/comp/res_common.py b/app/comp/res_common.py
index 0adf82da5..09a06e796 100644
--- a/app/comp/res_common.py
+++ b/app/comp/res_common.py
@@ -9,6 +9,7 @@ from functools import cached_property
import numpy as np
import pandas as pd
+from app import log
from app.comp.aux_stats import StatsMoyenne
from app.comp import moy_sem
from app.comp.res_cache import ResultatsCache
@@ -19,8 +20,7 @@ from app.models import FormSemestreUECoef
from app.models.ues import UniteEns
from app.scodoc import sco_utils as scu
from app.scodoc.sco_cache import ResultatsSemestreCache
-from app.scodoc.sco_codes_parcours import UE_SPORT, ATT, DEF
-from app.scodoc.sco_exceptions import ScoValueError
+from app.scodoc.sco_codes_parcours import UE_SPORT, DEF
# Il faut bien distinguer
# - ce qui est caché de façon persistente (via redis):
@@ -554,25 +554,32 @@ class NotesTableCompat(ResultatsSemestre):
Évaluation "complete" ssi toutes notes saisies ou en attente.
"""
modimpl = ModuleImpl.query.get(moduleimpl_id)
+ modimpl_results = self.modimpls_results.get(moduleimpl_id)
+ if not modimpl_results:
+ return [] # safeguard
evals_results = []
for e in modimpl.evaluations:
- if self.modimpls_results[moduleimpl_id].evaluations_completes_dict[e.id]:
+ if modimpl_results.evaluations_completes_dict.get(e.id, False):
d = e.to_dict()
- moduleimpl_results = self.modimpls_results[e.moduleimpl_id]
d["heure_debut"] = e.heure_debut # datetime.time
d["heure_fin"] = e.heure_fin
d["jour"] = e.jour # datetime
d["notes"] = {
etud.id: {
"etudid": etud.id,
- "value": moduleimpl_results.evals_notes[e.id][etud.id],
+ "value": modimpl_results.evals_notes[e.id][etud.id],
}
for etud in self.etuds
}
d["etat"] = {
- "evalattente": moduleimpl_results.evaluations_etat[e.id].nb_attente,
+ "evalattente": modimpl_results.evaluations_etat[e.id].nb_attente,
}
evals_results.append(d)
+ elif e.id not in modimpl_results.evaluations_completes_dict:
+ # ne devrait pas arriver ? XXX
+ log(
+ f"Warning: 220213 get_evals_in_mod {e.id} not in mod {moduleimpl_id} ?"
+ )
return evals_results
def get_evaluations_etats(self):
From 67fc12053ed2519d5cb3c7fa6001b9482ba5a21e Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sun, 13 Feb 2022 23:53:11 +0100
Subject: [PATCH 046/287] Elimination des derniers NotesTableCache
---
app/comp/res_common.py | 6 ++---
app/models/formsemestre.py | 1 +
app/pe/pe_jurype.py | 4 +--
app/scodoc/notes_table.py | 2 +-
app/scodoc/sco_abs_views.py | 12 ++++++---
app/scodoc/sco_apogee_csv.py | 26 +++++++++++++-----
app/scodoc/sco_bulletins.py | 6 +----
app/scodoc/sco_debouche.py | 11 +++++---
app/scodoc/sco_etape_apogee.py | 4 +--
app/scodoc/sco_evaluations.py | 10 ++++---
app/scodoc/sco_export_results.py | 6 ++++-
app/scodoc/sco_formsemestre_exterieurs.py | 6 ++++-
app/scodoc/sco_formsemestre_status.py | 7 +++--
app/scodoc/sco_liste_notes.py | 15 ++++++-----
app/scodoc/sco_moduleimpl_status.py | 1 -
app/scodoc/sco_parcours_dut.py | 9 -------
app/scodoc/sco_poursuite_dut.py | 6 ++++-
app/scodoc/sco_pvjury.py | 3 ---
app/scodoc/sco_saisie_notes.py | 11 ++++----
app/scodoc/sco_semset.py | 6 ++++-
app/scodoc/sco_tag_module.py | 6 ++++-
app/views/absences.py | 33 ++++++++++++-----------
app/views/notes.py | 20 +++++++++-----
tests/unit/test_caches.py | 8 ++++--
tests/unit/test_notes_modules.py | 15 ++++++++---
tests/unit/test_sco_basic.py | 8 ++++--
26 files changed, 145 insertions(+), 97 deletions(-)
diff --git a/app/comp/res_common.py b/app/comp/res_common.py
index 09a06e796..948c0e95c 100644
--- a/app/comp/res_common.py
+++ b/app/comp/res_common.py
@@ -4,7 +4,7 @@
# See LICENSE
##############################################################################
-from collections import defaultdict, Counter
+from collections import Counter
from functools import cached_property
import numpy as np
import pandas as pd
@@ -367,7 +367,7 @@ class NotesTableCompat(ResultatsSemestre):
sous forme de dict etud,
classée dans l'ordre alphabétique de noms.
"""
- etuds = self.formsemestre.get_inscrits(include_demdef=True, sorted=True)
+ etuds = self.formsemestre.get_inscrits(include_demdef=True, order=True)
return [e.to_dict_scodoc7() for e in etuds]
@cached_property
@@ -401,7 +401,7 @@ class NotesTableCompat(ResultatsSemestre):
d = modimpl.to_dict()
# compat ScoDoc < 9.2: ajoute matières
d["mat"] = modimpl.module.matiere.to_dict()
- modimpls_dict.append(d)
+ modimpls_dict.append(d)
return modimpls_dict
def compute_rangs(self):
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index c2850c2dc..245142484 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -121,6 +121,7 @@ class FormSemestre(db.Model):
d.pop("_sa_instance_state", None)
# ScoDoc7 output_formators: (backward compat)
d["formsemestre_id"] = self.id
+ d["titre_num"] = self.titre_num()
if self.date_debut:
d["date_debut"] = self.date_debut.strftime("%d/%m/%Y")
d["date_debut_iso"] = self.date_debut.isoformat()
diff --git a/app/pe/pe_jurype.py b/app/pe/pe_jurype.py
index d29d040ff..2720ad435 100644
--- a/app/pe/pe_jurype.py
+++ b/app/pe/pe_jurype.py
@@ -322,12 +322,10 @@ class JuryPE(object):
etudiants = []
for sem in semsListe: # pour chacun des semestres de la liste
- # nt = self.get_notes_d_un_semestre( sem['formsemestre_id'] )
nt = self.get_cache_notes_d_un_semestre(sem["formsemestre_id"])
- # sco_cache.NotesTableCache.get( sem['formsemestre_id'])
etudiantsDuSemestre = (
nt.get_etudids()
- ) # nt.identdict.keys() # identification des etudiants du semestre
+ ) # identification des etudiants du semestre
if pe_tools.PE_DEBUG:
pe_tools.pe_print(
diff --git a/app/scodoc/notes_table.py b/app/scodoc/notes_table.py
index 7fc03ce89..b1ac97b85 100644
--- a/app/scodoc/notes_table.py
+++ b/app/scodoc/notes_table.py
@@ -171,7 +171,7 @@ class NotesTable:
def __init__(self, formsemestre_id):
# log(f"NotesTable( formsemestre_id={formsemestre_id} )")
- # raise NotImplementedError() # XXX
+ raise NotImplementedError() # XXX
if not formsemestre_id:
raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id)
self.formsemestre_id = formsemestre_id
diff --git a/app/scodoc/sco_abs_views.py b/app/scodoc/sco_abs_views.py
index 2f108c243..d92ee8559 100644
--- a/app/scodoc/sco_abs_views.py
+++ b/app/scodoc/sco_abs_views.py
@@ -33,7 +33,9 @@ import datetime
from flask import url_for, g, request, abort
from app import log
-from app.models import Identite
+from app.comp import res_sem
+from app.comp.res_common import NotesTableCompat
+from app.models import Identite, FormSemestre
import app.scodoc.sco_utils as scu
from app.scodoc import notesdb as ndb
from app.scodoc.scolog import logdb
@@ -118,7 +120,8 @@ def doSignaleAbsence(
if moduleimpl_id and moduleimpl_id != "NULL":
mod = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
formsemestre_id = mod["formsemestre_id"]
- nt = sco_cache.NotesTableCache.get(formsemestre_id)
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
ues = nt.get_ues_stat_dict()
for ue in ues:
modimpls = nt.get_modimpls_dict(ue_id=ue["ue_id"])
@@ -179,11 +182,12 @@ def SignaleAbsenceEtud(): # etudid implied
menu_module = ""
else:
formsemestre_id = etud["cursem"]["formsemestre_id"]
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
+ ues = nt.get_ues_stat_dict()
require_module = sco_preferences.get_preference(
"abs_require_module", formsemestre_id
)
- nt = sco_cache.NotesTableCache.get(formsemestre_id)
- ues = nt.get_ues_stat_dict()
if require_module:
menu_module = """
+
+
+{% endblock %}
+
{% block app_content %}
{% include 'entreprises/nav.html' %}
@@ -19,63 +26,77 @@
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesExport, None) %}
Importer des contacts
{% endif %}
- {% if current_user.has_permission(current_user.Permission.RelationsEntreprisesExport, None) and contacts.items %}
+ {% if current_user.has_permission(current_user.Permission.RelationsEntreprisesExport, None) and contacts %}
Exporter la liste des contacts
{% endif %}
{{current_user.user_name}}
diff --git a/app/views/__init__.py b/app/views/__init__.py
index 3e9855c7a..3d8af0777 100644
--- a/app/views/__init__.py
+++ b/app/views/__init__.py
@@ -16,6 +16,7 @@ from app.scodoc import sco_formsemestre_status
from app.scodoc import sco_preferences
from app.scodoc.sco_permissions import Permission
from app.scodoc import sco_utils as scu
+import sco_version
scodoc_bp = Blueprint("scodoc", __name__)
scolar_bp = Blueprint("scolar", __name__)
@@ -53,6 +54,7 @@ class ScoData:
# Champs utilisés par toutes les pages ScoDoc (sidebar, en-tête)
self.Permission = Permission
self.scu = scu
+ self.SCOVERSION = sco_version.SCOVERSION
# -- Informations étudiant courant, si sélectionné:
etudid = g.get("etudid", None)
if not etudid:
From f8630b3cdb2ce6e9fffdfa0e1bcf9fba2b5a5103 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Thu, 17 Feb 2022 23:13:55 +0100
Subject: [PATCH 074/287] Modification calcul bonus sport BUT
---
app/comp/bonus_spo.py | 2 +-
app/comp/moy_ue.py | 11 ++++++++---
app/comp/res_but.py | 12 +++++-------
3 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py
index 098bd1286..30754bcfc 100644
--- a/app/comp/bonus_spo.py
+++ b/app/comp/bonus_spo.py
@@ -387,7 +387,7 @@ class BonusBordeaux1(BonusSportMultiplicatif):
"""
name = "bonus_iutBordeaux1"
- displayed_name = "IUT de Bordeaux 1"
+ displayed_name = "IUT de Bordeaux"
classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP
seuil_moy_gen = 10.0
amplitude = 0.005
diff --git a/app/comp/moy_ue.py b/app/comp/moy_ue.py
index 84a7e2bf1..8b98d2ef4 100644
--- a/app/comp/moy_ue.py
+++ b/app/comp/moy_ue.py
@@ -218,21 +218,25 @@ def compute_ue_moys_apc(
ues: list,
modimpl_inscr_df: pd.DataFrame,
modimpl_coefs_df: pd.DataFrame,
+ modimpl_mask: np.array,
) -> pd.DataFrame:
"""Calcul de la moyenne d'UE en mode APC (BUT).
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
NI non inscrit à (au moins un) module de cette UE
NA pas de notes disponibles
- ERR erreur dans une formule utilisateur. [XXX pas encore gérées ici]
+ ERR erreur dans une formule utilisateurs (pas gérées ici).
sem_cube: notes moyennes aux modules
ndarray (etuds x modimpls x UEs)
(floats avec des NaN)
etuds : liste des étudiants (dim. 0 du cube)
- modimpls : liste des modules à considérer (dim. 1 du cube)
+ modimpls : liste des module_impl (dim. 1 du cube)
ues : liste des UE (dim. 2 du cube)
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
modimpl_coefs_df: matrice coefficients (UE x modimpl), sans UEs bonus sport
+ modimpl_mask: liste de booléens, indiquants le module doit être pris ou pas.
+ (utilisé pour éliminer les bonus, et pourra servir à cacluler
+ sur des sous-ensembles de modules)
Résultat: DataFrame columns UE (sans bonus), rows etudid
"""
@@ -249,7 +253,8 @@ def compute_ue_moys_apc(
assert modimpl_coefs_df.shape[0] == nb_ues_no_bonus
assert modimpl_coefs_df.shape[1] == nb_modules
modimpl_inscr = modimpl_inscr_df.values
- modimpl_coefs = modimpl_coefs_df.values
+ # Met à zéro tous les coefs des modules non sélectionnés dans le masque:
+ modimpl_coefs = np.where(modimpl_mask, modimpl_coefs_df.values, 0.0)
# Duplique les inscriptions sur les UEs non bonus:
modimpl_inscr_stacked = np.stack([modimpl_inscr] * nb_ues_no_bonus, axis=2)
diff --git a/app/comp/res_but.py b/app/comp/res_but.py
index b74efb105..1d01f4f42 100644
--- a/app/comp/res_but.py
+++ b/app/comp/res_but.py
@@ -56,14 +56,11 @@ class ResultatsSemestreBUT(NotesTableCompat):
# modimpl_coefs_df.columns.get_loc(modimpl.id)
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
- # Elimine les coefs des modimpl bonus sports:
- modimpls_sport = [
- modimpl
+ # Masque de tous les modules _sauf_ les bonus (sport)
+ modimpls_mask = [
+ modimpl.module.ue.type != UE_SPORT
for modimpl in self.formsemestre.modimpls_sorted
- if modimpl.module.ue.type == UE_SPORT
]
- for modimpl in modimpls_sport:
- self.modimpl_coefs_df[modimpl.id] = 0
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
self.sem_cube,
@@ -72,6 +69,7 @@ class ResultatsSemestreBUT(NotesTableCompat):
self.ues,
self.modimpl_inscr_df,
self.modimpl_coefs_df,
+ modimpls_mask,
)
# Les coefficients d'UE ne sont pas utilisés en APC
self.etud_coef_ue_df = pd.DataFrame(
@@ -85,7 +83,7 @@ class ResultatsSemestreBUT(NotesTableCompat):
self.etud_moy_ue -= self.malus
# --- Bonus Sport & Culture
- if len(modimpls_sport) > 0:
+ if not all(modimpls_mask): # au moins un module bonus
bonus_class = ScoDocSiteConfig.get_bonus_sport_class()
if bonus_class is not None:
bonus: BonusSport = bonus_class(
From bfd0a4b311f5e52746821f260c5f94efa30e0908 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Thu, 17 Feb 2022 23:15:15 +0100
Subject: [PATCH 075/287] phrase sur bul. BUT
---
app/static/js/releve-but.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/static/js/releve-but.js b/app/static/js/releve-but.js
index 5dcd9e5bc..dcc4520bd 100644
--- a/app/static/js/releve-but.js
+++ b/app/static/js/releve-but.js
@@ -91,7 +91,7 @@ class releveBUT extends HTMLElement {
Inscrit le
- Les moyennes servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de compétences ou d'UE.
+ Les moyennes ci-dessus servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de compétences ou d'UE.
From 716a6bf41f0a230b2cc71fe871e8f68bddd5c8d1 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 00:08:19 +0100
Subject: [PATCH 076/287] Fix migrations redondantes
---
.../versions/bd2c1c3d866e_refcomp_orebut.py | 30 -------------------
sco_version.py | 2 +-
2 files changed, 1 insertion(+), 31 deletions(-)
diff --git a/migrations/versions/bd2c1c3d866e_refcomp_orebut.py b/migrations/versions/bd2c1c3d866e_refcomp_orebut.py
index 8475cd95e..1c54847ff 100644
--- a/migrations/versions/bd2c1c3d866e_refcomp_orebut.py
+++ b/migrations/versions/bd2c1c3d866e_refcomp_orebut.py
@@ -19,31 +19,6 @@ depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
- op.add_column("apc_competence", sa.Column("id_orebut", sa.Text(), nullable=True))
- op.drop_constraint(
- "apc_competence_referentiel_id_titre_key", "apc_competence", type_="unique"
- )
- op.create_index(
- op.f("ix_apc_competence_id_orebut"),
- "apc_competence",
- ["id_orebut"],
- )
- op.add_column(
- "apc_referentiel_competences", sa.Column("annexe", sa.Text(), nullable=True)
- )
- op.add_column(
- "apc_referentiel_competences",
- sa.Column("type_structure", sa.Text(), nullable=True),
- )
- op.add_column(
- "apc_referentiel_competences",
- sa.Column("type_departement", sa.Text(), nullable=True),
- )
- op.add_column(
- "apc_referentiel_competences",
- sa.Column("version_orebut", sa.Text(), nullable=True),
- )
-
op.create_index(
op.f("ix_notes_formsemestre_uecoef_formsemestre_id"),
"notes_formsemestre_uecoef",
@@ -80,15 +55,10 @@ def downgrade():
table_name="notes_formsemestre_uecoef",
)
- op.drop_column("apc_referentiel_competences", "version_orebut")
- op.drop_column("apc_referentiel_competences", "type_departement")
- op.drop_column("apc_referentiel_competences", "type_structure")
- op.drop_column("apc_referentiel_competences", "annexe")
op.drop_index(op.f("ix_apc_competence_id_orebut"), table_name="apc_competence")
op.create_unique_constraint(
"apc_competence_referentiel_id_titre_key",
"apc_competence",
["referentiel_id", "titre"],
)
- op.drop_column("apc_competence", "id_orebut")
# ### end Alembic commands ###
diff --git a/sco_version.py b/sco_version.py
index 1e9561b08..e7456ecab 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.1.57"
+SCOVERSION = "9.1.58"
SCONAME = "ScoDoc"
From 9e4c19a2920456db10e4dfdf18f8b8d9ee843633 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 12:12:16 +0100
Subject: [PATCH 077/287] Fix: coefs absents en formations classiques
plantaient bonus sport nouvelle formule
---
app/comp/res_classic.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/comp/res_classic.py b/app/comp/res_classic.py
index 2caf515c5..543772d3e 100644
--- a/app/comp/res_classic.py
+++ b/app/comp/res_classic.py
@@ -60,7 +60,7 @@ class ResultatsSemestreClassic(NotesTableCompat):
)
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
self.modimpl_coefs = np.array(
- [m.module.coefficient for m in self.formsemestre.modimpls_sorted]
+ [m.module.coefficient or 0.0 for m in self.formsemestre.modimpls_sorted]
)
self.modimpl_idx = {
m.id: i for i, m in enumerate(self.formsemestre.modimpls_sorted)
From 89db5cf85894cad7b3780aafb2535a777b28eed4 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 13:59:39 +0100
Subject: [PATCH 078/287] Pour tests: modif calcul bonus DUT : UE vers moy.
gen.
---
app/comp/res_classic.py | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/app/comp/res_classic.py b/app/comp/res_classic.py
index 543772d3e..007654d21 100644
--- a/app/comp/res_classic.py
+++ b/app/comp/res_classic.py
@@ -113,11 +113,19 @@ class ResultatsSemestreClassic(NotesTableCompat):
self.etud_moy_ue += self.bonus_ues # somme les dataframes
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
bonus_mg = bonus.get_bonus_moy_gen()
- if bonus_mg is not None:
+ if bonus_mg is None and self.bonus_ues is not None:
+ # pas de bonus explicite sur la moyenne générale
+ # on l'ajuste pour refléter les modifs d'UE, à l'aide des coefs d'UE.
+ self.etud_moy_gen -= (self.etud_coef_ue_df * self.bonus_ues).sum(
+ axis=1
+ ) / self.etud_coef_ue_df.sum(axis=1)
+ elif bonus_mg:
+ # Applique le bonus moyenne générale renvoyé
self.etud_moy_gen += bonus_mg
- self.etud_moy_gen.clip(lower=0.0, upper=20.0, inplace=True)
- # compat nt, utilisé pour l'afficher sur les bulletins:
- self.bonus = bonus_mg
+
+ self.etud_moy_gen.clip(lower=0.0, upper=20.0, inplace=True)
+ # compat nt, utilisé pour l'afficher sur les bulletins:
+ self.bonus = bonus_mg
# --- UE capitalisées
self.apply_capitalisation()
From fffba011ea931a9e22d479837d271e3defc4a3d4 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 14:05:48 +0100
Subject: [PATCH 079/287] Pour tests: modif calcul bonus DUT (corrige erreur de
signe)
---
app/comp/res_classic.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/comp/res_classic.py b/app/comp/res_classic.py
index 007654d21..28f3952b9 100644
--- a/app/comp/res_classic.py
+++ b/app/comp/res_classic.py
@@ -116,7 +116,7 @@ class ResultatsSemestreClassic(NotesTableCompat):
if bonus_mg is None and self.bonus_ues is not None:
# pas de bonus explicite sur la moyenne générale
# on l'ajuste pour refléter les modifs d'UE, à l'aide des coefs d'UE.
- self.etud_moy_gen -= (self.etud_coef_ue_df * self.bonus_ues).sum(
+ self.etud_moy_gen += (self.etud_coef_ue_df * self.bonus_ues).sum(
axis=1
) / self.etud_coef_ue_df.sum(axis=1)
elif bonus_mg:
From 175c66c83481ba58374382d964a616e97f3b4427 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 14:15:29 +0100
Subject: [PATCH 080/287] Affichage plus clair du bonus sur bulletins
classiques
---
app/comp/bonus_spo.py | 2 +-
app/comp/res_classic.py | 3 ++-
app/scodoc/sco_bulletins.py | 2 ++
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py
index 30754bcfc..be2a87306 100644
--- a/app/comp/bonus_spo.py
+++ b/app/comp/bonus_spo.py
@@ -53,7 +53,7 @@ class BonusSport:
etud_moy_gen et etud_moy_ue ne sont PAS modifiés (mais utilisés par certains bonus non additifs).
"""
- # En classique, active un bonus sur les UEs: (dans ce cas bonus_moy_gen reste None)
+ # En classique, active un bonus sur les UEs: (dans ce cas bonus_moy_gen est ajusté pour le prendre en compte)
classic_use_bonus_ues = False
# Attributs virtuels:
diff --git a/app/comp/res_classic.py b/app/comp/res_classic.py
index 28f3952b9..a4b352ed8 100644
--- a/app/comp/res_classic.py
+++ b/app/comp/res_classic.py
@@ -116,9 +116,10 @@ class ResultatsSemestreClassic(NotesTableCompat):
if bonus_mg is None and self.bonus_ues is not None:
# pas de bonus explicite sur la moyenne générale
# on l'ajuste pour refléter les modifs d'UE, à l'aide des coefs d'UE.
- self.etud_moy_gen += (self.etud_coef_ue_df * self.bonus_ues).sum(
+ bonus_mg = (self.etud_coef_ue_df * self.bonus_ues).sum(
axis=1
) / self.etud_coef_ue_df.sum(axis=1)
+ self.etud_moy_gen += bonus_mg
elif bonus_mg:
# Applique le bonus moyenne générale renvoyé
self.etud_moy_gen += bonus_mg
diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py
index 93a926b64..de7f28c9d 100644
--- a/app/scodoc/sco_bulletins.py
+++ b/app/scodoc/sco_bulletins.py
@@ -315,6 +315,8 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
u["cur_moy_ue_txt"] = "bonus appliqué sur les UEs"
else:
u["cur_moy_ue_txt"] = "bonus de %.3g points" % x
+ if nt.bonus_ues is not None:
+ u["cur_moy_ue_txt"] += " (+ues)"
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
if ue_status["coef_ue"] != None:
u["coef_ue_txt"] = scu.fmt_coef(ue_status["coef_ue"])
From 39e31983ee36fe993e5d2506e20e683af4204157 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 14:24:35 +0100
Subject: [PATCH 081/287] Fix (pour suite tests)
---
app/comp/res_classic.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/comp/res_classic.py b/app/comp/res_classic.py
index a4b352ed8..1dfcfb4da 100644
--- a/app/comp/res_classic.py
+++ b/app/comp/res_classic.py
@@ -120,7 +120,7 @@ class ResultatsSemestreClassic(NotesTableCompat):
axis=1
) / self.etud_coef_ue_df.sum(axis=1)
self.etud_moy_gen += bonus_mg
- elif bonus_mg:
+ elif bonus_mg is not None:
# Applique le bonus moyenne générale renvoyé
self.etud_moy_gen += bonus_mg
From a67515d560aa910484d8c6978fc82e61698a3deb Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 19:34:40 +0100
Subject: [PATCH 082/287] =?UTF-8?q?Ignore=20les=20poids=20en=20BD=20des=20?=
=?UTF-8?q?=C3=A9vals=20des=20modules=20non=20APC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/comp/moy_mod.py | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py
index f30f04912..2ec6c35f2 100644
--- a/app/comp/moy_mod.py
+++ b/app/comp/moy_mod.py
@@ -359,6 +359,10 @@ def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
Les valeurs manquantes (évaluations sans coef vers des UE) sont
remplies: 1 si le coef de ce module dans l'UE est non nul, zéro sinon
(sauf pour module bonus, defaut à 1)
+
+ Si le module n'est pas une ressource ou une SAE, ne charge pas de poids
+ et renvoie toujours les poids par défaut.
+
Résultat: (evals_poids, liste de UEs du semestre sauf le sport)
"""
modimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
@@ -367,13 +371,17 @@ def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
ue_ids = [ue.id for ue in ues]
evaluation_ids = [evaluation.id for evaluation in evaluations]
evals_poids = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
- for ue_poids in EvaluationUEPoids.query.join(
- EvaluationUEPoids.evaluation
- ).filter_by(moduleimpl_id=moduleimpl_id):
- try:
- evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
- except KeyError as exc:
- pass # poids vers des UE qui n'existent plus ou sont dans un autre semestre...
+ if (
+ modimpl.module.module_type == ModuleType.RESSOURCE
+ or modimpl.module.module_type == ModuleType.SAE
+ ):
+ for ue_poids in EvaluationUEPoids.query.join(
+ EvaluationUEPoids.evaluation
+ ).filter_by(moduleimpl_id=moduleimpl_id):
+ try:
+ evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
+ except KeyError as exc:
+ pass # poids vers des UE qui n'existent plus ou sont dans un autre semestre...
# Initialise poids non enregistrés:
default_poids = (
From ab212a5b2b5e1d4777dab5fcda33d4aa550483dd Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 19:35:57 +0100
Subject: [PATCH 083/287] Edition programme: avertissements
---
app/templates/pn/form_mods.html | 7 +++++++
app/templates/pn/form_ues.html | 3 +++
2 files changed, 10 insertions(+)
diff --git a/app/templates/pn/form_mods.html b/app/templates/pn/form_mods.html
index 2be3cfe24..10927bc88 100644
--- a/app/templates/pn/form_mods.html
+++ b/app/templates/pn/form_mods.html
@@ -65,6 +65,13 @@
{% endfor %}
+ {% if mod.ue.type != 0 and mod.module_type != 0 %}
+
+ type incompatible avec son UE de rattachement !
+
+ {% endif %}
+
diff --git a/app/templates/pn/form_ues.html b/app/templates/pn/form_ues.html
index 350d12d82..0cd3e333b 100644
--- a/app/templates/pn/form_ues.html
+++ b/app/templates/pn/form_ues.html
@@ -48,6 +48,9 @@
}}">modifier
{% endif %}
+ {% if ue.type == 1 and ue.modules.count() == 0 %}
+ aucun module rattaché !
+ {% endif %}
{% endfor %}
From 091a49cb0df07eb9949fe3112ecbcf2f466ae109 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 19:36:51 +0100
Subject: [PATCH 084/287] Edition module: simplifie liste rattachement
---
app/scodoc/sco_edit_module.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py
index 41a25d090..2bedc0c11 100644
--- a/app/scodoc/sco_edit_module.py
+++ b/app/scodoc/sco_edit_module.py
@@ -500,6 +500,13 @@ def module_edit(module_id=None):
matieres = matieres.filter(UniteEns.semestre_idx == a_module.ue.semestre_idx)
if is_apc:
+ # ne conserve que la 1ere matière de chaque UE,
+ # et celle à laquelle ce module est rattaché
+ matieres = [
+ mat
+ for mat in matieres
+ if a_module.matiere.id == mat.id or mat.id == mat.ue.matieres.first().id
+ ]
mat_names = [
"S%s / %s" % (mat.ue.semestre_idx, mat.ue.acronyme) for mat in matieres
]
From 9b21157fb74f4c49134f0b683fb70847db45f3cd Mon Sep 17 00:00:00 2001
From: Arthur ZHU
Date: Fri, 18 Feb 2022 19:57:19 +0100
Subject: [PATCH 085/287] cadre info base sirene, gestion erreur (a tester)
---
app/entreprises/app_relations_entreprises.py | 7 +-
app/entreprises/forms.py | 9 ++-
app/entreprises/routes.py | 2 +-
app/static/css/entreprises.css | 6 ++
.../entreprises/ajout_entreprise.html | 3 -
.../form_modification_entreprise.html | 65 +++++++++++++++++++
6 files changed, 84 insertions(+), 8 deletions(-)
create mode 100644 app/templates/entreprises/form_modification_entreprise.html
diff --git a/app/entreprises/app_relations_entreprises.py b/app/entreprises/app_relations_entreprises.py
index c47000ce3..6f060616d 100644
--- a/app/entreprises/app_relations_entreprises.py
+++ b/app/entreprises/app_relations_entreprises.py
@@ -144,7 +144,12 @@ def verif_entreprise_data(entreprise_data):
siret = entreprise_data[0].strip() # vérification sur le siret
if re.match("^\d{14}$", siret) is None:
return False
- req = requests.get(f"https://entreprise.data.gouv.fr/api/sirene/v1/siret/{siret}")
+ try:
+ req = requests.get(
+ f"https://entreprise.data.gouv.fr/api/sirene/v1/siret/{siret}"
+ )
+ except requests.ConnectionError:
+ print("no internet")
if req.status_code != 200:
return False
entreprise = Entreprise.query.filter_by(siret=siret).first()
diff --git a/app/entreprises/forms.py b/app/entreprises/forms.py
index 7bc1a1021..ca74da261 100644
--- a/app/entreprises/forms.py
+++ b/app/entreprises/forms.py
@@ -102,9 +102,12 @@ class EntrepriseCreationForm(FlaskForm):
siret = siret.data.strip()
if re.match("^\d{14}$", siret) is None:
raise ValidationError("Format incorrect")
- req = requests.get(
- f"https://entreprise.data.gouv.fr/api/sirene/v1/siret/{siret}"
- )
+ try:
+ req = requests.get(
+ f"https://entreprise.data.gouv.fr/api/sirene/v1/siret/{siret}"
+ )
+ except requests.ConnectionError:
+ print("no internet")
if req.status_code != 200:
raise ValidationError("SIRET inexistant")
entreprise = Entreprise.query.filter_by(siret=siret).first()
diff --git a/app/entreprises/routes.py b/app/entreprises/routes.py
index 547d20511..43cef4a31 100644
--- a/app/entreprises/routes.py
+++ b/app/entreprises/routes.py
@@ -364,7 +364,7 @@ def edit_entreprise(id):
form.ville.data = entreprise.ville
form.pays.data = entreprise.pays
return render_template(
- "entreprises/form.html",
+ "entreprises/form_modification_entreprise.html",
title="Modification entreprise",
form=form,
)
diff --git a/app/static/css/entreprises.css b/app/static/css/entreprises.css
index d2dc5cf5d..fac9d11b8 100644
--- a/app/static/css/entreprises.css
+++ b/app/static/css/entreprises.css
@@ -85,4 +85,10 @@
.offre-recue {
display: flex;
justify-content: space-between;
+}
+
+#sirene-data {
+ border: solid 2px;
+ border-radius: 10px;
+ padding: 10px;
}
\ No newline at end of file
diff --git a/app/templates/entreprises/ajout_entreprise.html b/app/templates/entreprises/ajout_entreprise.html
index c593335b8..fe597c550 100644
--- a/app/templates/entreprises/ajout_entreprise.html
+++ b/app/templates/entreprises/ajout_entreprise.html
@@ -20,7 +20,6 @@
function autocomplete() {
var input = document.getElementById("siret").value;
- data = null
if(input.length == 14) {
fetch("https://entreprise.data.gouv.fr/api/sirene/v1/siret/" + input)
.then(response => {
@@ -40,7 +39,6 @@
document.getElementById("adresse").value = response.etablissement.l4_normalisee
document.getElementById("codepostal").value = response.etablissement.code_postal
document.getElementById("ville").value = response.etablissement.libelle_commune
- document.getElementById("pays").value = 'FRANCE'
}
function emptyForm() {
@@ -48,7 +46,6 @@
document.getElementById("adresse").value = ''
document.getElementById("codepostal").value = ''
document.getElementById("ville").value = ''
- document.getElementById("pays").value = ''
}
}
diff --git a/app/templates/entreprises/form_modification_entreprise.html b/app/templates/entreprises/form_modification_entreprise.html
new file mode 100644
index 000000000..c77cc12f8
--- /dev/null
+++ b/app/templates/entreprises/form_modification_entreprise.html
@@ -0,0 +1,65 @@
+{# -*- mode: jinja-html -*- #}
+{% extends 'base.html' %}
+{% import 'bootstrap/wtf.html' as wtf %}
+
+{% block styles %}
+{{super()}}
+{% endblock %}
+
+{% block app_content %}
+
+
+
+{% endblock %}
\ No newline at end of file
From 7a85ec7466419db036418dc3869870877d6c70cb Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 21:18:08 +0100
Subject: [PATCH 086/287] Ajout bonus Cachan1, et suppression bonus Annecy
---
app/comp/bonus_spo.py | 231 ++++++++++++++++++++++++++++--------------
1 file changed, 154 insertions(+), 77 deletions(-)
diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py
index be2a87306..ed4bf5db3 100644
--- a/app/comp/bonus_spo.py
+++ b/app/comp/bonus_spo.py
@@ -284,6 +284,7 @@ class BonusSportMultiplicatif(BonusSport):
class BonusDirect(BonusSportAdditif):
"""Bonus direct: les points sont directement ajoutés à la moyenne générale.
+
Les coefficients sont ignorés: tous les points de bonus sont sommés.
(rappel: la note est ramenée sur 20 avant application).
"""
@@ -294,47 +295,49 @@ class BonusDirect(BonusSportAdditif):
proportion_point = 1.0
-class BonusAnnecy(BonusSport):
- """Calcul bonus modules optionnels (sport), règle IUT d'Annecy.
- Il peut y avoir plusieurs modules de bonus.
- Prend pour chaque étudiant la meilleure de ses notes bonus et
- ajoute à chaque UE :
- 0.05 point si >=10,
- 0.1 point si >=12,
- 0.15 point si >=14,
- 0.2 point si >=16,
- 0.25 point si >=18.
- """
+# Finalement ils n'en veulent pas.
+# class BonusAnnecy(BonusSport):
+# """Calcul bonus modules optionnels (sport), règle IUT d'Annecy.
- name = "bonus_iut_annecy"
- displayed_name = "IUT d'Annecy"
+# Il peut y avoir plusieurs modules de bonus.
+# Prend pour chaque étudiant la meilleure de ses notes bonus et
+# ajoute à chaque UE :
+# 0.05 point si >=10,
+# 0.1 point si >=12,
+# 0.15 point si >=14,
+# 0.2 point si >=16,
+# 0.25 point si >=18.
+# """
- def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
- """calcul du bonus"""
- # if math.prod(sem_modimpl_moys_inscrits.shape) == 0:
- # return # no etuds or no mod sport
- # Prend la note de chaque modimpl, sans considération d'UE
- if len(sem_modimpl_moys_inscrits.shape) > 2: # apc
- sem_modimpl_moys_inscrits = sem_modimpl_moys_inscrits[:, :, 0]
- # ici sem_modimpl_moys_inscrits est nb_etuds x nb_mods_bonus, en APC et en classic
- note_bonus_max = np.max(sem_modimpl_moys_inscrits, axis=1) # 1d, nb_etuds
- bonus = np.zeros(note_bonus_max.shape)
- bonus[note_bonus_max >= 18.0] = 0.25
- bonus[note_bonus_max >= 16.0] = 0.20
- bonus[note_bonus_max >= 14.0] = 0.15
- bonus[note_bonus_max >= 12.0] = 0.10
- bonus[note_bonus_max >= 10.0] = 0.05
+# name = "bonus_iut_annecy"
+# displayed_name = "IUT d'Annecy"
- # Bonus moyenne générale et sur les UE
- self.bonus_moy_gen = pd.Series(bonus, index=self.etuds_idx, dtype=float)
- ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
- nb_ues_no_bonus = len(ues_idx)
- self.bonus_ues = pd.DataFrame(
- np.stack([bonus] * nb_ues_no_bonus, axis=1),
- columns=ues_idx,
- index=self.etuds_idx,
- dtype=float,
- )
+# def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
+# """calcul du bonus"""
+# # if math.prod(sem_modimpl_moys_inscrits.shape) == 0:
+# # return # no etuds or no mod sport
+# # Prend la note de chaque modimpl, sans considération d'UE
+# if len(sem_modimpl_moys_inscrits.shape) > 2: # apc
+# sem_modimpl_moys_inscrits = sem_modimpl_moys_inscrits[:, :, 0]
+# # ici sem_modimpl_moys_inscrits est nb_etuds x nb_mods_bonus, en APC et en classic
+# note_bonus_max = np.max(sem_modimpl_moys_inscrits, axis=1) # 1d, nb_etuds
+# bonus = np.zeros(note_bonus_max.shape)
+# bonus[note_bonus_max >= 10.0] = 0.05
+# bonus[note_bonus_max >= 12.0] = 0.10
+# bonus[note_bonus_max >= 14.0] = 0.15
+# bonus[note_bonus_max >= 16.0] = 0.20
+# bonus[note_bonus_max >= 18.0] = 0.25
+
+# # Bonus moyenne générale et sur les UE
+# self.bonus_moy_gen = pd.Series(bonus, index=self.etuds_idx, dtype=float)
+# ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
+# nb_ues_no_bonus = len(ues_idx)
+# self.bonus_ues = pd.DataFrame(
+# np.stack([bonus] * nb_ues_no_bonus, axis=1),
+# columns=ues_idx,
+# index=self.etuds_idx,
+# dtype=float,
+# )
class BonusBethune(BonusSportMultiplicatif):
@@ -375,15 +378,16 @@ class BonusBezier(BonusSportAdditif):
class BonusBordeaux1(BonusSportMultiplicatif):
"""Calcul bonus modules optionnels (sport, culture), règle IUT Bordeaux 1, sur moyenne générale
et UE.
-
+
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
de l'Université Bordeaux 1 (sport, théâtre) non rattachés à une unité d'enseignement.
-
+
Chaque point au-dessus de 10 sur 20 obtenus dans cet enseignement correspond à un %
- qui augmente la moyenne de chaque UE et la moyenne générale.
- Formule : le % = points>moyenne / 2
+ qui augmente la moyenne de chaque UE et la moyenne générale.
+ Formule : pourcentage = (points au dessus de 10) / 2
+
Par exemple : sport 13/20 : chaque UE sera multipliée par 1+0,015, ainsi que la moyenne générale.
-
+
"""
name = "bonus_iutBordeaux1"
@@ -393,6 +397,68 @@ class BonusBordeaux1(BonusSportMultiplicatif):
amplitude = 0.005
+class BonusCachan1(BonusSportAdditif):
+ """Calcul bonus optionnels (sport, culture), règle IUT de Cachan 1.
+
+
+
DUT/LP : la meilleure note d'option, si elle est supérieure à 10,
+ bonifie les moyennes d'UE (sauf l'UE41 dont le code est UE41_E) à raison
+ de bonus = (option - 10)/10.
+
+
+
BUT : la meilleure note d'option, si elle est supérieure à 10, bonifie
+ les moyennes d'UE à raison de bonus = (option - 10)*5%.
+
+ """
+
+ name = "bonus_cachan1"
+ displayed_name = "IUT de Cachan 1"
+ seuil_moy_gen = 10.0 # tous les points sont comptés
+ proportion_point = 0.05
+ classic_use_bonus_ues = True
+
+ def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
+ """calcul du bonus, avec réglage différent suivant le type de formation"""
+ # Prend la note de chaque modimpl, sans considération d'UE
+ if len(sem_modimpl_moys_inscrits.shape) > 2: # apc
+ sem_modimpl_moys_inscrits = sem_modimpl_moys_inscrits[:, :, 0]
+ # ici sem_modimpl_moys_inscrits est nb_etuds x nb_mods_bonus, en APC et en classic
+ note_bonus_max = np.max(sem_modimpl_moys_inscrits, axis=1) # 1d, nb_etuds
+ ues = self.formsemestre.query_ues(with_sport=False).all()
+ ues_idx = [ue.id for ue in ues]
+
+ if self.formsemestre.formation.is_apc(): # --- BUT
+ bonus_moy_arr = np.where(
+ note_bonus_max > self.seuil_moy_gen,
+ (note_bonus_max - self.seuil_moy_gen) * self.proportion_point,
+ 0.0,
+ )
+ self.bonus_ues = pd.DataFrame(
+ np.stack([bonus_moy_arr] * len(ues)).T,
+ index=self.etuds_idx,
+ columns=ues_idx,
+ dtype=float,
+ )
+ else: # --- DUT
+ # pareil mais proportion différente et exclusion d'une UE
+ proportion_point = 0.1
+ bonus_moy_arr = np.where(
+ note_bonus_max > self.seuil_moy_gen,
+ (note_bonus_max - self.seuil_moy_gen) * proportion_point,
+ 0.0,
+ )
+ self.bonus_ues = pd.DataFrame(
+ np.stack([bonus_moy_arr] * len(ues)).T,
+ index=self.etuds_idx,
+ columns=ues_idx,
+ dtype=float,
+ )
+ # Pas de bonus sur la ou les ue de code "UE41_E"
+ ue_exclues = [ue for ue in ues if ue.ue_code == "UE41_E"]
+ for ue in ue_exclues:
+ self.bonus_ues[ue.id] = 0.0
+
+
class BonusColmar(BonusSportAdditif):
"""Calcul bonus modules optionnels (sport, culture), règle IUT Colmar.
@@ -417,19 +483,21 @@ class BonusColmar(BonusSportAdditif):
class BonusGrenobleIUT1(BonusSportMultiplicatif):
"""Bonus IUT1 de Grenoble
+
À compter de sept. 2021:
La note de sport est sur 20, et on calcule une bonification (en %)
qui va s'appliquer à la moyenne de chaque UE du semestre en appliquant
la formule : bonification (en %) = (note-10)*0,5.
-
- Bonification qui ne s'applique que si la note est >10.
-
- (Une note de 10 donne donc 0% de bonif ; note de 20 : 5% de bonif)
-
+
+ La bonification ne s'applique que si la note est supérieure à 10.
+
+ (Une note de 10 donne donc 0% de bonif, et une note de 20 : 5% de bonif)
+
Avant sept. 2021, la note de sport allait de 0 à 5 points (sur 20).
Chaque point correspondait à 0.25% d'augmentation de la moyenne
générale.
Par exemple : note de sport 2/5 : la moyenne générale était augmentée de 0.5%.
+
"""
name = "bonus_iut1grenoble_2017"
@@ -456,9 +524,11 @@ class BonusGrenobleIUT1(BonusSportMultiplicatif):
class BonusLaRochelle(BonusSportAdditif):
"""Calcul bonus modules optionnels (sport, culture), règle IUT de La Rochelle.
- Si la note de sport est comprise entre 0 et 10 : pas d'ajout de point.
- Si la note de sport est comprise entre 10 et 20 : ajout de 1% de cette
- note sur la moyenne générale du semestre (ou sur les UE en BUT).
+
+
Si la note de sport est comprise entre 0 et 10 : pas d'ajout de point.
+
Si la note de sport est comprise entre 10 et 20 : ajout de 1% de cette
+ note sur la moyenne générale du semestre (ou sur les UE en BUT).
+
"""
name = "bonus_iutlr"
@@ -483,16 +553,17 @@ class BonusLeHavre(BonusSportMultiplicatif):
class BonusLeMans(BonusSportAdditif):
"""Calcul bonus modules optionnels (sport, culture), règle IUT Le Mans.
- Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
+
Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
optionnelles sont cumulés.
+
+
+
En BUT: la moyenne de chacune des UE du semestre est augmentée de
+ 2% du cumul des points de bonus;
-
- En BUT: la moyenne de chacune des UE du semestre est augmentée de
- 2% du cumul des points de bonus,
-
- En DUT/LP: la moyenne générale est augmentée de 5% du cumul des points bonus.
-
- Dans tous les cas, le bonus est dans la limite de 0,5 point.
+
En DUT/LP: la moyenne générale est augmentée de 5% du cumul des points bonus.
+
+
+
Dans tous les cas, le bonus est dans la limite de 0,5 point.
"""
name = "bonus_iutlemans"
@@ -516,12 +587,13 @@ class BonusLeMans(BonusSportAdditif):
class BonusLille(BonusSportAdditif):
"""Calcul bonus modules optionnels (sport, culture), règle IUT Villeneuve d'Ascq
- Les étudiants de l'IUT peuvent suivre des enseignements optionnels
+
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
de l'Université Lille (sports, etc) non rattachés à une unité d'enseignement.
-
+
Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
optionnelles sont cumulés et 4% (2% avant août 2010) de ces points cumulés
s'ajoutent à la moyenne générale du semestre déjà obtenue par l'étudiant.
+
"""
name = "bonus_lille"
@@ -573,17 +645,19 @@ class BonusMulhouse(BonusSportAdditif):
class BonusNantes(BonusSportAdditif):
"""IUT de Nantes (Septembre 2018)
- Nous avons différents types de bonification
+
Nous avons différents types de bonification
(sport, culture, engagement citoyen).
-
+
Nous ajoutons aux moyennes une bonification de 0,2 pour chaque item
la bonification totale ne doit pas excéder les 0,5 point.
Sur le bulletin nous ne mettons pas une note sur 20 mais directement les bonifications.
-
- Dans ScoDoc: on a déclarera une UE "sport&culture" dans laquelle on aura des modules
- pour chaque activité (Sport, Associations, ...)
- avec à chaque fois une note (ScoDoc l'affichera comme une note sur 20, mais en fait ce sera la
- valeur de la bonification: entrer 0,1/20 signifiera un bonus de 0,1 point la moyenne générale)
+
+ Dans ScoDoc: on a déclarera une UE "sport&culture" dans laquelle on aura
+ des modules pour chaque activité (Sport, Associations, ...)
+ avec à chaque fois une note (ScoDoc l'affichera comme une note sur 20,
+ mais en fait ce sera la valeur de la bonification: entrer 0,1/20 signifiera
+ un bonus de 0,1 point la moyenne générale).
+
"""
name = "bonus_nantes"
@@ -627,13 +701,14 @@ class BonusStDenis(BonusSportAdditif):
class BonusTours(BonusDirect):
"""Calcul bonus sport & culture IUT Tours.
- Les notes des UE bonus (ramenées sur 20) sont sommées
+
Les notes des UE bonus (ramenées sur 20) sont sommées
et 1/40 (2,5%) est ajouté aux moyennes: soit à la moyenne générale,
soit pour le BUT à chaque moyenne d'UE.
-
- Attention: en GEII, facteur 1/40, ailleurs facteur 1.
-
+
+ Attention: en GEII, facteur 1/40, ailleurs facteur 1.
+
Le bonus total est limité à 1 point.
+
"""
name = "bonus_tours"
@@ -658,11 +733,13 @@ class BonusVilleAvray(BonusSport):
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
de l'Université Paris 10 (C2I) non rattachés à une unité d'enseignement.
- Si la note est >= 10 et < 12, bonus de 0.1 point
- Si la note est >= 12 et < 16, bonus de 0.2 point
- Si la note est >= 16, bonus de 0.3 point
- Ce bonus s'ajoute à la moyenne générale du semestre déjà obtenue par
- l'étudiant.
+
+
Si la note est >= 10 et < 12, bonus de 0.1 point
+
Si la note est >= 12 et < 16, bonus de 0.2 point
+
Si la note est >= 16, bonus de 0.3 point
+
+
Ce bonus s'ajoute à la moyenne générale du semestre déjà obtenue par
+ l'étudiant.
"""
name = "bonus_iutva"
From fae11d82ced18f65e91c82a2716b5e014a15498e Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 21:54:30 +0100
Subject: [PATCH 087/287] =?UTF-8?q?Fix:=20prise=20en=20compte=20=C3=A9val?=
=?UTF-8?q?=20de=20rattrapage=20dans=20les=20modules=20BUT?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/comp/moy_mod.py | 12 +++++++-----
sco_version.py | 2 +-
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py
index 2ec6c35f2..eea357e8f 100644
--- a/app/comp/moy_mod.py
+++ b/app/comp/moy_mod.py
@@ -335,15 +335,17 @@ class ModuleImplResultsAPC(ModuleImplResults):
notes_rat / (eval_rat.note_max / 20.0),
np.nan,
)
+ # "Étend" le rattrapage sur les UE: la note de rattrapage est la même
+ # pour toutes les UE mais ne remplace que là où elle est supérieure
+ notes_rat_ues = np.stack([notes_rat] * nb_ues, axis=1)
# prend le max
- etuds_use_rattrapage = notes_rat > etuds_moy_module
+ etuds_use_rattrapage = notes_rat_ues > etuds_moy_module
etuds_moy_module = np.where(
- etuds_use_rattrapage[:, np.newaxis],
- np.tile(notes_rat[:, np.newaxis], nb_ues),
- etuds_moy_module,
+ etuds_use_rattrapage, notes_rat_ues, etuds_moy_module
)
+ # Serie indiquant que l'étudiant utilise une note de rattarage sur l'une des UE:
self.etuds_use_rattrapage = pd.Series(
- etuds_use_rattrapage, index=self.evals_notes.index
+ etuds_use_rattrapage.any(axis=1), index=self.evals_notes.index
)
self.etuds_moy_module = pd.DataFrame(
etuds_moy_module,
diff --git a/sco_version.py b/sco_version.py
index e7456ecab..04070622d 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.1.58"
+SCOVERSION = "9.1.59"
SCONAME = "ScoDoc"
From 58a8bcb83da72c122825fbaec2f5fd586da8ca7e Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 22:14:12 +0100
Subject: [PATCH 088/287] =?UTF-8?q?Ajoute=20lien=20'inscrire=20des=20?=
=?UTF-8?q?=C3=A9tudiants'.=20Closes=20#319.=20Auteur:=20PB.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/scodoc/sco_moduleimpl_inscriptions.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py
index e3c232305..453f63f1f 100644
--- a/app/scodoc/sco_moduleimpl_inscriptions.py
+++ b/app/scodoc/sco_moduleimpl_inscriptions.py
@@ -305,7 +305,10 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
if can_change:
c_link = (
'%s'
- % (mod["moduleimpl_id"], mod["descri"])
+ % (
+ mod["moduleimpl_id"],
+ mod["descri"] or "(inscrire des étudiants)",
+ )
)
else:
c_link = mod["descri"]
From d8776485465574009876ea5022f09c75324eceb2 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 22:19:23 +0100
Subject: [PATCH 089/287] =?UTF-8?q?empeche=20cr=C3=A9ation=20de=20d=C3=A9p?=
=?UTF-8?q?artements=20avec=20m=C3=AAme=20acronyme?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/models/departements.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/app/models/departements.py b/app/models/departements.py
index ebe5cc145..44a963ca0 100644
--- a/app/models/departements.py
+++ b/app/models/departements.py
@@ -55,6 +55,9 @@ def create_dept(acronym: str, visible=True) -> Departement:
"Create new departement"
from app.models import ScoPreference
+ existing = Departement.query.filter_by(acronym=acronym).count()
+ if existing:
+ raise ValueError(f"acronyme {acronym} déjà existant")
departement = Departement(acronym=acronym, visible=visible)
p1 = ScoPreference(name="DeptName", value=acronym, departement=departement)
db.session.add(p1)
From c81c4efb406f2f0059daaab7aa03183769f28c59 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 22:59:05 +0100
Subject: [PATCH 090/287] Fix: formsemestre_evaluations_cal
---
app/scodoc/sco_etape_apogee_view.py | 4 ++--
app/scodoc/sco_evaluations.py | 27 +++++++++++----------------
2 files changed, 13 insertions(+), 18 deletions(-)
diff --git a/app/scodoc/sco_etape_apogee_view.py b/app/scodoc/sco_etape_apogee_view.py
index b784e5f0f..21fc2410f 100644
--- a/app/scodoc/sco_etape_apogee_view.py
+++ b/app/scodoc/sco_etape_apogee_view.py
@@ -53,7 +53,7 @@ from app.scodoc.sco_exceptions import ScoValueError
def apo_semset_maq_status(
- semset_id="",
+ semset_id: int,
allow_missing_apo=False,
allow_missing_decisions=False,
allow_missing_csv=False,
@@ -65,7 +65,7 @@ def apo_semset_maq_status(
):
"""Page statut / tableau de bord"""
if not semset_id:
- raise ValueError("invalid null semset_id")
+ raise ScoValueError("invalid null semset_id")
semset = sco_semset.SemSet(semset_id=semset_id)
semset.fill_formsemestres()
# autorise export meme si etudiants Apo manquants:
diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py
index 09f2179b7..d895a8a37 100644
--- a/app/scodoc/sco_evaluations.py
+++ b/app/scodoc/sco_evaluations.py
@@ -405,7 +405,6 @@ def formsemestre_evaluations_cal(formsemestre_id):
"""Page avec calendrier de toutes les evaluations de ce semestre"""
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
- sem = formsemestre.to_dict()
evals = nt.get_evaluations_etats()
nb_evals = len(evals)
@@ -416,8 +415,8 @@ def formsemestre_evaluations_cal(formsemestre_id):
today = time.strftime("%Y-%m-%d")
- year = int(sem["annee_debut"])
- if sem["mois_debut_ord"] < 8:
+ year = formsemestre.date_debut.year
+ if formsemestre.date_debut.month < 8:
year -= 1 # calendrier septembre a septembre
events = {} # (day, halfday) : event
for e in evals:
@@ -537,11 +536,10 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
"""Experimental: un tableau indiquant pour chaque évaluation
le nombre de jours avant la publication des notes.
- N'indique pas les évaluations de ratrapage ni celles des modules de bonus/malus.
+ N'indique pas les évaluations de rattrapage ni celles des modules de bonus/malus.
"""
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
- sem = formsemestre.to_dict()
evals = nt.get_evaluations_etats()
T = []
@@ -607,7 +605,7 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
origin="Généré par %s le " % sco_version.SCONAME
+ scu.timedate_human_repr()
+ "",
- filename=scu.make_filename("evaluations_delais_" + sem["titreannee"]),
+ filename=scu.make_filename("evaluations_delais_" + formsemestre.titre_annee()),
)
return tab.make_page(format=format)
@@ -635,16 +633,13 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
'voir toutes les notes du module'
% moduleimpl_id
)
- mod_descr = (
- '%s %s(resp. %s) %s'
- % (
- moduleimpl_id,
- Mod["code"] or "",
- Mod["titre"] or "?",
- nomcomplet,
- resp,
- link,
- )
+ mod_descr = '%s %s(resp. %s) %s' % (
+ moduleimpl_id,
+ Mod["code"] or "",
+ Mod["titre"] or "?",
+ nomcomplet,
+ resp,
+ link,
)
etit = E["description"] or ""
From 202ce4e73e8a2ae05df7bb5f02065135110a4926 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 23:30:45 +0100
Subject: [PATCH 091/287] nginx frontal: augmentation du timeout proxy
---
tools/build_release.sh | 2 ++
tools/etc/scodoc9-nginx-timeout.conf | 5 +++++
2 files changed, 7 insertions(+)
create mode 100644 tools/etc/scodoc9-nginx-timeout.conf
diff --git a/tools/build_release.sh b/tools/build_release.sh
index 69a333bbd..5afefaca6 100644
--- a/tools/build_release.sh
+++ b/tools/build_release.sh
@@ -93,6 +93,8 @@ fi
# nginx:
mkdir -p "$slash"/etc/nginx/sites-available || die "can't mkdir nginx config"
cp -p "$SCODOC_DIR"/tools/etc/scodoc9.nginx "$slash"/etc/nginx/sites-available/scodoc9.nginx.distrib || die "can't copy nginx config"
+mkdir -p "$slash"/etc/nginx/conf.d || die "can't mkdir nginx conf.d"
+cp -p "$SCODOC_DIR"/tools/etc/scodoc9-nginx-timeout.conf "$slash"/etc/nginx/conf.d/ || die "can't copy nginx timeout config"
# systemd
mkdir -p "$slash"/etc/systemd/system/ || die "can't mkdir systemd config"
diff --git a/tools/etc/scodoc9-nginx-timeout.conf b/tools/etc/scodoc9-nginx-timeout.conf
new file mode 100644
index 000000000..98f367161
--- /dev/null
+++ b/tools/etc/scodoc9-nginx-timeout.conf
@@ -0,0 +1,5 @@
+# Reglage des timeout du frontal nginx pour ScoDoc 9 (>= 9.1.59)
+
+proxy_read_timeout 400;
+proxy_connect_timeout 400;
+proxy_send_timeout 400;
From 5c951d58e790f37fed0e9993b57af460accc1480 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 18 Feb 2022 23:43:13 +0100
Subject: [PATCH 092/287] =?UTF-8?q?Fix=20lien=20chargement=20ref.=20comp?=
=?UTF-8?q?=C3=A9tences?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/templates/but/refcomp_load.html | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/templates/but/refcomp_load.html b/app/templates/but/refcomp_load.html
index 290de937b..50d73d80c 100644
--- a/app/templates/but/refcomp_load.html
+++ b/app/templates/but/refcomp_load.html
@@ -18,10 +18,12 @@
Liste des référentiels de compétences chargés
+ {% if formation is not none %}
From cca72dfed27089f9308cf3d6ce13df994110a4cc Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sat, 19 Feb 2022 00:28:24 +0100
Subject: [PATCH 093/287] Bonus IUT Amiens
---
app/comp/bonus_spo.py | 15 +++++++++++++++
sco_version.py | 2 +-
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py
index ed4bf5db3..65f6c03b9 100644
--- a/app/comp/bonus_spo.py
+++ b/app/comp/bonus_spo.py
@@ -295,6 +295,21 @@ class BonusDirect(BonusSportAdditif):
proportion_point = 1.0
+class BonusAmiens(BonusSportAdditif):
+ """Bonus IUT Amiens pour les modules optionnels (sport, culture, ...).
+
+ Toute note non nulle, peu importe sa valeur, entraine un bonus de 0,1 point
+ sur toutes les moyennes d'UE.
+ """
+
+ name = "bonus_amiens"
+ displayed_name = "IUT d'Amiens"
+ seuil_moy_gen = 0.0 # tous les points sont comptés
+ proportion_point = 1e10
+ bonus_max = 0.1
+ classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP
+
+
# Finalement ils n'en veulent pas.
# class BonusAnnecy(BonusSport):
# """Calcul bonus modules optionnels (sport), règle IUT d'Annecy.
diff --git a/sco_version.py b/sco_version.py
index 04070622d..96ef7d1da 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.1.59"
+SCOVERSION = "9.1.60"
SCONAME = "ScoDoc"
From 63784e341ad1d33f1bdb7060b88e25e0b3ad4d9e Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sat, 19 Feb 2022 01:00:40 +0100
Subject: [PATCH 094/287] Correction pour Amiens, Roanne
---
app/comp/bonus_spo.py | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py
index 65f6c03b9..0a95621e8 100644
--- a/app/comp/bonus_spo.py
+++ b/app/comp/bonus_spo.py
@@ -205,7 +205,8 @@ class BonusSportAdditif(BonusSport):
"""calcul du bonus
sem_modimpl_moys_inscrits: les notes de sport
En APC: ndarray (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
- modimpl_coefs_etuds_no_nan:
+ En classic: ndarray (nb_etuds, nb_mod_sport)
+ modimpl_coefs_etuds_no_nan: même shape, les coefs.
"""
if 0 in sem_modimpl_moys_inscrits.shape:
# pas d'étudiants ou pas d'UE ou pas de module...
@@ -228,12 +229,22 @@ class BonusSportAdditif(BonusSport):
bonus_moy_arr = np.clip(bonus_moy_arr, 0.0, 20.0, out=bonus_moy_arr)
# en APC, bonus_moy_arr est (nb_etuds, nb_ues_non_bonus)
- if self.formsemestre.formation.is_apc() or self.classic_use_bonus_ues:
+ if self.formsemestre.formation.is_apc():
# Bonus sur les UE et None sur moyenne générale
ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
self.bonus_ues = pd.DataFrame(
bonus_moy_arr, index=self.etuds_idx, columns=ues_idx, dtype=float
)
+ elif self.classic_use_bonus_ues:
+ # Formations classiques apppliquant le bonus sur les UEs
+ # ici bonus_moy_arr = ndarray 1d nb_etuds
+ ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
+ self.bonus_ues = pd.DataFrame(
+ np.stack([bonus_moy_arr] * len(ues_idx)).T,
+ index=self.etuds_idx,
+ columns=ues_idx,
+ dtype=float,
+ )
else:
# Bonus sur la moyenne générale seulement
self.bonus_moy_gen = pd.Series(
@@ -693,7 +704,7 @@ class BonusRoanne(BonusSportAdditif):
displayed_name = "IUT de Roanne"
seuil_moy_gen = 0.0
bonus_max = 0.6 # plafonnement à 0.6 points
- apply_bonus_mg_to_ues = True # sur les UE, même en DUT et LP
+ classic_use_bonus_ues = True # sur les UE, même en DUT et LP
class BonusStDenis(BonusSportAdditif):
@@ -792,7 +803,7 @@ class BonusIUTV(BonusSportAdditif):
name = "bonus_iutv"
displayed_name = "IUT de Villetaneuse"
- pass # oui, c'ets le bonus par défaut
+ pass # oui, c'est le bonus par défaut
def get_bonus_class_dict(start=BonusSport, d=None):
From 44123c022eac799449173f8d92734bcebb75f8c1 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sat, 19 Feb 2022 16:16:52 +0100
Subject: [PATCH 095/287] =?UTF-8?q?Am=C3=A9liore=20=C3=A9dition=20programm?=
=?UTF-8?q?es=20classiques?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/scodoc/sco_edit_ue.py | 34 +++++++++++++++++------
app/scodoc/sco_formsemestre_validation.py | 16 +++++++----
app/static/css/scodoc.css | 4 +++
sco_version.py | 2 +-
4 files changed, 41 insertions(+), 15 deletions(-)
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index 17cdc8c07..061bf43c6 100644
--- a/app/scodoc/sco_edit_ue.py
+++ b/app/scodoc/sco_edit_ue.py
@@ -601,7 +601,12 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
_add_ue_semestre_id(ues_externes, is_apc)
ues.sort(key=lambda u: (u["semestre_id"], u["numero"]))
ues_externes.sort(key=lambda u: (u["semestre_id"], u["numero"]))
- has_duplicate_ue_codes = len(set([ue["ue_code"] for ue in ues])) != len(ues)
+ # Codes dupliqués (pour aider l'utilisateur)
+ seen = set()
+ duplicated_codes = {
+ ue["ue_code"] for ue in ues if ue["ue_code"] in seen or seen.add(ue["ue_code"])
+ }
+ ues_with_duplicated_code = [ue for ue in ues if ue["ue_code"] in duplicated_codes]
has_perm_change = current_user.has_permission(Permission.ScoChangeFormation)
# editable = (not locked) and has_perm_change
@@ -664,11 +669,17 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
if msg:
H.append('
' + msg + "
")
- if has_duplicate_ue_codes:
+ if ues_with_duplicated_code:
H.append(
- """
Attention: plusieurs UE de cette
- formation ont le même code. Il faut corriger cela ci-dessous,
- sinon les calculs d'ECTS seront erronés !
"""
+ f"""
Attention: plusieurs UE de cette
+ formation ont le même code : {
+ ', '.join([
+ '' + ue["acronyme"] + " (code " + ue["ue_code"] + ")"
+ for ue in ues_with_duplicated_code ])
+ }.
+ Il faut corriger cela, sinon les capitalisations et ECTS seront
+ erronés !
"""
)
# Description de la formation
@@ -930,8 +941,8 @@ def _ue_table_ues(
if cur_ue_semestre_id != ue["semestre_id"]:
cur_ue_semestre_id = ue["semestre_id"]
- if iue > 0:
- H.append("")
+ # if iue > 0:
+ # H.append("")
if ue["semestre_id"] == sco_codes_parcours.UE_SEM_DEFAULT:
lab = "Pas d'indication de semestre:"
else:
@@ -953,7 +964,6 @@ def _ue_table_ues(
)
else:
H.append(arrow_none)
- iue += 1
ue["acro_titre"] = str(ue["acronyme"])
if ue["titre"] != ue["acronyme"]:
ue["acro_titre"] += " " + str(ue["titre"])
@@ -1001,6 +1011,14 @@ def _ue_table_ues(
delete_disabled_icon,
)
)
+ if (iue >= len(ues) - 1) or ue["semestre_id"] != ues[iue + 1]["semestre_id"]:
+ H.append(
+ f"""
"""
+ )
+ iue += 1
+
return "\n".join(H)
diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py
index f755bb118..987dc962f 100644
--- a/app/scodoc/sco_formsemestre_validation.py
+++ b/app/scodoc/sco_formsemestre_validation.py
@@ -1250,7 +1250,7 @@ def check_formation_ues(formation_id):
for ue in ues:
# formsemestres utilisant cette ue ?
sems = ndb.SimpleDictFetch(
- """SELECT DISTINCT sem.id AS formsemestre_id, sem.*
+ """SELECT DISTINCT sem.id AS formsemestre_id, sem.*
FROM notes_formsemestre sem, notes_modules mod, notes_moduleimpl mi
WHERE sem.formation_id = %(formation_id)s
AND mod.id = mi.module_id
@@ -1269,11 +1269,11 @@ def check_formation_ues(formation_id):
return "", {}
# Genere message HTML:
H = [
- """
Attention: les UE suivantes de cette formation
+ """
Attention: les UE suivantes de cette formation
sont utilisées dans des
- semestres de rangs différents (eg S1 et S3). Cela peut engendrer des problèmes pour
- la capitalisation des UE. Il serait préférable d'essayer de rectifier cette situation:
- soit modifier le programme de la formation (définir des UE dans chaque semestre),
+ semestres de rangs différents (eg S1 et S3). Cela peut engendrer des problèmes pour
+ la capitalisation des UE. Il serait préférable d'essayer de rectifier cette situation:
+ soit modifier le programme de la formation (définir des UE dans chaque semestre),
soit veiller à saisir le bon indice de semestre dans le menu lors de la validation d'une
UE extérieure.
@@ -1286,7 +1286,11 @@ def check_formation_ues(formation_id):
for x in ue_multiples[ue["ue_id"]]
]
slist = ", ".join(
- ["%(titreannee)s (semestre %(semestre_id)s)" % s for s in sems]
+ [
+ """%(titreannee)s (semestre %(semestre_id)s)"""
+ % s
+ for s in sems
+ ]
)
H.append("