Merge branch 'sco96' of https://scodoc.org/git/viennet/ScoDoc into evaluations

This commit is contained in:
Emmanuel Viennet 2023-08-23 16:37:54 +02:00
commit f62d2277a8
19 changed files with 260 additions and 120 deletions

3
.flake8 Normal file
View File

@ -0,0 +1,3 @@
[flake8]
max-line-length = 88
ignore = E203,W503

View File

@ -78,7 +78,11 @@ def compute_sem_moys_apc_using_ects(
else:
ects = ects_df.to_numpy()
# ects est maintenant un array nb_etuds x nb_ues
moy_gen = (etud_moy_ue_df * ects).sum(axis=1) / ects.sum(axis=1)
except ZeroDivisionError:
# peut arriver si aucun module... on ignore
moy_gen = pd.Series(np.NaN, index=etud_moy_ue_df.index)
except TypeError:
if None in ects:
formation = db.session.get(Formation, formation_id)

View File

@ -79,13 +79,15 @@ Adresses d'origine:
to : {orig_to}
cc : {orig_cc}
bcc: {orig_bcc}
---
---
\n\n"""
+ msg.body
)
current_app.logger.info(
f"""email sent to{' (mode test)' if email_test_mode_address else ''}: {msg.recipients}
f"""email sent to{
' (mode test)' if email_test_mode_address else ''
}: {msg.recipients}
from sender {msg.sender}
"""
)
@ -98,7 +100,8 @@ def get_from_addr(dept_acronym: str = None):
"""L'adresse "from" à utiliser pour envoyer un mail
Si le departement est spécifié, ou si l'attribut `g.scodoc_dept`existe,
prend le `email_from_addr` des préférences de ce département si ce champ est non vide.
prend le `email_from_addr` des préférences de ce département si ce champ
est non vide.
Sinon, utilise le paramètre global `email_from_addr`.
Sinon, la variable de config `SCODOC_MAIL_FROM`.
"""

View File

@ -1,19 +1,17 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
import datetime
import html
import traceback
from flask import g, current_app, abort
import psycopg2
import psycopg2.pool
import psycopg2.extras
from flask import g, current_app, abort
import app
import app.scodoc.sco_utils as scu
from app import log
from app.scodoc.sco_exceptions import ScoException, ScoValueError, NoteProcessError
import datetime
quote_html = html.escape

View File

@ -318,7 +318,7 @@ def list_abs_in_range(
Returns:
List of absences
"""
if matin != None:
if matin is not None:
matin = _toboolean(matin)
ismatin = " AND A.MATIN = %(matin)s "
else:
@ -387,7 +387,7 @@ def count_abs_just(etudid, debut, fin, matin=None, moduleimpl_id=None) -> int:
Returns:
An integer.
"""
if matin != None:
if matin is not None:
matin = _toboolean(matin)
ismatin = " AND A.MATIN = %(matin)s "
else:
@ -482,7 +482,9 @@ def _get_abs_description(a, cursor=None):
else:
a["matin"] = False
cursor.execute(
"""select * from absences where etudid=%(etudid)s and jour=%(jour)s and matin=%(matin)s order by entry_date desc""",
"""SELECT * FROM absences
WHERE etudid=%(etudid)s AND jour=%(jour)s AND matin=%(matin)s
ORDER BY entry_date desc""",
a,
)
A = cursor.dictfetchall()
@ -517,9 +519,9 @@ def list_abs_jour(date, am=True, pm=True, is_abs=True, is_just=None):
req = """SELECT DISTINCT etudid, jour, matin FROM ABSENCES A
WHERE A.jour = %(date)s
"""
if is_abs != None:
if is_abs is not None:
req += " AND A.estabs = %(is_abs)s"
if is_just != None:
if is_just is not None:
req += " AND A.estjust = %(is_just)s"
if not am:
req += " AND NOT matin "
@ -883,7 +885,7 @@ def MonthTableBody(
descr = ev[4]
#
cc = []
if color != None:
if color is not None:
cc.append('<td bgcolor="%s" class="calcell">' % color)
else:
cc.append('<td class="calcell">')
@ -896,7 +898,7 @@ def MonthTableBody(
cc.append("<a %s %s>" % (href, descr))
if legend or d == 1:
if pad_width != None:
if pad_width is not None:
n = pad_width - len(legend) # pad to 8 cars
if n > 0:
legend = (
@ -959,7 +961,7 @@ def MonthTableBody(
ev_year = int(ev[0][:4])
ev_month = int(ev[0][5:7])
ev_day = int(ev[0][8:10])
if ev[4] != None:
if ev[4] is not None:
ev_half = int(ev[4])
else:
ev_half = 0
@ -978,7 +980,7 @@ def MonthTableBody(
if len(ev) > 5 and ev[5]:
descr = ev[5]
#
if color != None:
if color is not None:
cc.append('<td bgcolor="%s" class="calcell">' % (color))
else:
cc.append('<td class="calcell">')
@ -1072,7 +1074,8 @@ def invalidate_abs_count_sem(sem):
def invalidate_abs_etud_date(etudid, date): # was invalidateAbsEtudDate
"""Doit etre appelé à chaque modification des absences pour cet étudiant et cette date.
"""Doit etre appelé à chaque modification des absences
pour cet étudiant et cette date.
Invalide cache absence et caches semestre
date: date au format ISO
"""

View File

@ -137,14 +137,14 @@ def doSignaleAbsence(
]
if dates:
H.append(
f"""<p>Ajout de {nbadded} absences <b>{just_str}justifiées</b>
f"""<p>Ajout de {nbadded} absences <b>{just_str}justifiées</b>
du {datedebut} au {datefin} {indication_module}
</p>
"""
)
else:
H.append(
f"""<p class="warning">Aucune date ouvrable
f"""<p class="warning">Aucune date ouvrable
entre le {datedebut} et le {datefin} !
</p>
"""
@ -152,11 +152,11 @@ def doSignaleAbsence(
H.append(
f"""<ul>
<li><a class="stdlink" href="{url_for("absences.SignaleAbsenceEtud",
<li><a class="stdlink" href="{url_for("absences.SignaleAbsenceEtud",
scodoc_dept=g.scodoc_dept, etudid=etud.id
)}">Autre absence pour <b>{etud.nomprenom}</b></a>
</li>
<li><a class="stdlink" href="{url_for("absences.CalAbs",
<li><a class="stdlink" href="{url_for("absences.CalAbs",
scodoc_dept=g.scodoc_dept, etudid=etud.id
)}">Calendrier de ses absences</a>
</li>
@ -180,8 +180,12 @@ def SignaleAbsenceEtud(): # etudid implied
"abs_require_module"
) # on utilise la pref globale car pas de sem courant
if require_module:
menu_module = """<div class="ue_warning">Pas inscrit dans un semestre courant,
et l'indication du module est requise. Donc pas de saisie d'absence possible !</div>"""
menu_module = """<div class="ue_warning">Pas
inscrit dans un semestre courant,
et l'indication du module est requise.
Donc pas de saisie d'absence possible !
</div>
"""
disabled = True
else:
menu_module = ""
@ -197,17 +201,17 @@ def SignaleAbsenceEtud(): # etudid implied
menu_module = """
<script type="text/javascript">
function form_enable_disable() {
if ( $("select#sel_moduleimpl_id").val() == "" ) {
$("#butsubmit").prop("disabled", true);
} else {
$("#butsubmit").prop("disabled", false);
if ( $("select#sel_moduleimpl_id").val() == "" ) {
$("#butsubmit").prop("disabled", true);
} else {
$("#butsubmit").prop("disabled", false);
};
}
$(document).ready(function() {
form_enable_disable();
});
</script>
<p>Module:
<p>Module:
<select id="sel_moduleimpl_id" name="moduleimpl_id"
onChange="form_enable_disable();">"""
else:
@ -250,7 +254,10 @@ def SignaleAbsenceEtud(): # etudid implied
<p>
<table><tr>
<td>Date début : </td>
<td><input type="text" name="datedebut" size="10" class="datepicker"/> <em>j/m/a</em></td>
<td>
<input type="text" name="datedebut" size="10" class="datepicker"/>
<em>j/m/a</em>
</td>
<td>&nbsp;&nbsp;&nbsp;Date fin (optionnelle):</td>
<td><input type="text" name="datefin" size="10" class="datepicker"/> <em>j/m/a</em></td>
</tr>
@ -269,14 +276,14 @@ Raison: <input type="text" name="description" size="42"/> (optionnel)
</p>
<p>
<input id="butsubmit" type="submit" value="Envoyer" disable="%(disabled)s"/>
<input id="butsubmit" type="submit" value="Envoyer" disable="%(disabled)s"/>
<em>
<p>Seuls les modules du semestre en cours apparaissent.</p>
<p>Évitez de saisir une absence pour un module qui n'est pas en place à cette date.</p>
<p>Toutes les dates sont au format jour/mois/annee.</p>
</em>
</form>
</form>
"""
% {
"etudid": etud["etudid"],
@ -354,7 +361,10 @@ def doJustifAbsence(
)
H.append(
"""<ul><li><a href="JustifAbsenceEtud?etudid=%(etudid)s">Autre justification pour <b>%(nomprenom)s</b></a></li>
"""<ul>
<li><a href="JustifAbsenceEtud?etudid=%(etudid)s">Autre justification
pour <b>%(nomprenom)s</b>
</a></li>
<li><a href="SignaleAbsenceEtud?etudid=%(etudid)s">Signaler une absence</a></li>
<li><a href="CalAbs?etudid=%(etudid)s">Calendrier de ses absences</a></li>
<li><a href="ListeAbsEtud?etudid=%(etudid)s">Liste de ses absences</a></li>
@ -389,12 +399,12 @@ def JustifAbsenceEtud(): # etudid implied
),
"""</a></td></tr></table>""",
"""
<form action="doJustifAbsence" method="get">
<form action="doJustifAbsence" method="get">
<input type="hidden" name="etudid" value="%(etudid)s">
<p>
<table><tr>
<td>Date d&eacute;but : </td>
<td>Date début : </td>
<td>
<input type="text" name="datedebut" size="10" class="datepicker"/>
</td>
@ -412,7 +422,7 @@ def JustifAbsenceEtud(): # etudid implied
Raison: <input type="text" name="description" size="42"/> (optionnel)
<p>
<input type="submit" value="Envoyer">
<input type="submit" value="Envoyer">
</form> """
% etud,
@ -458,8 +468,10 @@ def doAnnuleAbsence(datedebut, datefin, demijournee, etudid=False): # etudid im
H.append(
"""<ul><li><a href="AnnuleAbsenceEtud?etudid=%(etudid)s">Annulation d'une
autre absence pour <b>%(nomprenom)s</b></a></li>
<li><a href="SignaleAbsenceEtud?etudid=%(etudid)s">Ajout d'une absence</a></li>
<li><a href="CalAbs?etudid=%(etudid)s">Calendrier de ses absences</a></li>
<li><a href="SignaleAbsenceEtud?etudid=%(etudid)s">Ajout d'une
absence</a></li>
<li><a href="CalAbs?etudid=%(etudid)s">Calendrier de ses
absences</a></li>
</ul>
<hr>"""
% etud
@ -480,10 +492,11 @@ def AnnuleAbsenceEtud(): # etudid implied
page_title="Annulation d'une absence pour %(nomprenom)s" % etud,
),
"""<table><tr><td>
<h2><font color="#FF0000">Annulation</font> d'une absence pour %(nomprenom)s</h2>
<h2><font color="#FF0000">Annulation</font> d'une absence
pour %(nomprenom)s</h2>
</td><td>
"""
% etud, # "
% etud,
"""<a href="%s">"""
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
sco_photos.etud_photo_html(
@ -491,16 +504,19 @@ def AnnuleAbsenceEtud(): # etudid implied
title="fiche de " + etud["nomprenom"],
),
"""</a></td></tr></table>""",
"""<p>A n'utiliser que suite à une erreur de saisie ou lorsqu'il s'avère que l'étudiant était en fait présent. </p>
<p><font color="#FF0000">Si plusieurs modules sont affectés, les absences seront toutes effacées. </font></p>
"""
"""<p>A n'utiliser que suite à une erreur de saisie ou lorsqu'il s'avère que
l'étudiant était en fait présent.
</p>
<p><font color="#FF0000">Si plusieurs modules sont affectés,
les absences seront toutes effacées. </font></p>
"""
% etud,
"""<table frame="border" border="1"><tr><td>
<form action="doAnnuleAbsence" method="get">
<form action="doAnnuleAbsence" method="get">
<input type="hidden" name="etudid" value="%(etudid)s">
<p>
<table><tr>
<td>Date d&eacute;but : </td>
<td>Date début : </td>
<td>
<input type="text" name="datedebut" size="10" class="datepicker"/> <em>j/m/a</em>
</td>
@ -511,22 +527,22 @@ def AnnuleAbsenceEtud(): # etudid implied
</tr>
</table>
<input type="radio" name="demijournee" value="2" checked>journ&eacute;e(s)
<input type="radio" name="demijournee" value="2" checked>journée(s)
&nbsp;<input type="radio" name="demijournee" value="1">Matin(s)
&nbsp;<input type="radio" name="demijournee" value="0">Apr&egrave;s midi
<p>
<input type="submit" value="Supprimer les absences">
</form>
<input type="submit" value="Supprimer les absences">
</form>
</td></tr>
<tr><td>
<form action="doAnnuleJustif" method="get">
<form action="doAnnuleJustif" method="get">
<input type="hidden" name="etudid" value="%(etudid)s">
<p>
<table><tr>
<td>Date d&eacute;but : </td>
<td>Date début : </td>
<td>
<input type="text" name="datedebut0" size="10" class="datepicker"/> <em>j/m/a</em>
</td>
@ -538,15 +554,16 @@ def AnnuleAbsenceEtud(): # etudid implied
</table>
<p>
<input type="radio" name="demijournee" value="2" checked>journ&eacute;e(s)
<input type="radio" name="demijournee" value="2" checked>journée(s)
&nbsp;<input type="radio" name="demijournee" value="1">Matin(s)
&nbsp;<input type="radio" name="demijournee" value="0">Apr&egrave;s midi
<p>
<input type="submit" value="Supprimer les justificatifs">
<i>(utiliser ceci en cas de justificatif erron&eacute; saisi ind&eacute;pendemment d'une absence)</i>
</form>
<input type="submit" value="Supprimer les justificatifs">
<i>(utiliser ceci en cas de justificatif erroné saisi indépendemment
d'une absence)</i>
</form>
</td></tr></table>"""
% etud,
html_sco_header.sco_footer(),
@ -591,8 +608,10 @@ def doAnnuleJustif(datedebut0, datefin0, demijournee): # etudid implied
H.append(
"""<ul><li><a href="AnnuleAbsenceEtud?etudid=%(etudid)s">Annulation d'une
autre absence pour <b>%(nomprenom)s</b></a></li>
<li><a href="SignaleAbsenceEtud?etudid=%(etudid)s">Ajout d'une absence</a></li>
<li><a href="CalAbs?etudid=%(etudid)s">Calendrier de ses absences</a></li>
<li><a href="SignaleAbsenceEtud?etudid=%(etudid)s">Ajout d'une
absence</a></li>
<li><a href="CalAbs?etudid=%(etudid)s">Calendrier de ses
absences</a></li>
</ul>
<hr>"""
% etud
@ -634,8 +653,11 @@ def AnnuleAbsencesDatesNoJust(etudid, dates, moduleimpl_id=None):
# supr les absences non justifiees
for date in dates:
cursor.execute(
"""DELETE FROM absences
WHERE etudid=%(etudid)s and (not estjust) and jour=%(date)s and moduleimpl_id=%(moduleimpl_id)s
"""DELETE FROM absences
WHERE etudid=%(etudid)s
AND (not estjust)
AND jour=%(date)s
AND moduleimpl_id=%(moduleimpl_id)s
""",
vars(),
)
@ -643,8 +665,11 @@ def AnnuleAbsencesDatesNoJust(etudid, dates, moduleimpl_id=None):
# s'assure que les justificatifs ne sont pas "absents"
for date in dates:
cursor.execute(
"""UPDATE absences SET estabs=FALSE
WHERE etudid=%(etudid)s AND jour=%(date)s AND moduleimpl_id=%(moduleimpl_id)s
"""UPDATE absences
SET estabs=FALSE
WHERE etudid=%(etudid)s
AND jour=%(date)s
AND moduleimpl_id=%(moduleimpl_id)s
""",
vars(),
)
@ -724,7 +749,7 @@ def _convert_sco_year(year) -> int:
year = int(year)
if year > 1900 and year < 2999:
return year
except:
except ValueError:
raise ScoValueError("année scolaire invalide")
@ -771,7 +796,8 @@ def CalAbs(etudid, sco_year=None):
"""<b><font color="#EE0000">A : absence NON justifiée</font><br>
<font color="#F8B7B0">a : absence justifiée</font><br>
<font color="#8EA2C6">X : justification sans absence</font><br>
%d absences sur l'année, dont %d justifiées (soit %d non justifiées)</b> <em>(%d justificatifs inutilisés)</em>
%d absences sur l'année, dont %d justifiées (soit %d non justifiées)</b>
<em>(%d justificatifs inutilisés)</em>
</p>
"""
% (nbabs, nbabsjust, nbabs - nbabsjust, len(justifs_noabs)),
@ -790,7 +816,8 @@ def CalAbs(etudid, sco_year=None):
"""<form method="GET" action="CalAbs" name="f">""",
"""<input type="hidden" name="etudid" value="%s"/>""" % etudid,
"""Année scolaire %s-%s""" % (annee_scolaire, annee_scolaire + 1),
"""&nbsp;&nbsp;Changer année: <select name="sco_year" onchange="document.f.submit()">""",
"""&nbsp;&nbsp;Changer année:
<select name="sco_year" onchange="document.f.submit()">""",
]
for y in range(annee_courante, min(annee_courante - 6, annee_scolaire - 6), -1):
H.append("""<option value="%s" """ % y)
@ -819,7 +846,8 @@ def ListeAbsEtud(
etudid:
with_evals: indique les evaluations aux dates d'absences
absjust_only: si vrai, renvoie table absences justifiées
sco_year: année scolaire à utiliser. Si non spécifier, utilie l'année en cours. e.g. "2005"
sco_year: année scolaire à utiliser.
Si non spécifier, utilie l'année en cours. e.g. "2005"
"""
# si absjust_only, table absjust seule (export xls ou pdf)
absjust_only = scu.to_bool(absjust_only)
@ -941,10 +969,12 @@ def _tables_abs_etud(
for a in absnonjust + absjust:
cursor.execute(
"""SELECT eval.*
FROM notes_evaluation eval, notes_moduleimpl_inscription mi, notes_moduleimpl m
WHERE eval.jour = %(jour)s
FROM notes_evaluation eval,
notes_moduleimpl_inscription mi,
notes_moduleimpl m
WHERE eval.jour = %(jour)s
and eval.moduleimpl_id = m.id
and mi.moduleimpl_id = m.id
and mi.moduleimpl_id = m.id
and mi.etudid = %(etudid)s
""",
{"jour": a["jour"].strftime("%Y-%m-%d"), "etudid": etudid},
@ -984,9 +1014,10 @@ def _tables_abs_etud(
)[0]
if format == "html":
ex.append(
f"""<a title="{mod['module']['titre']}" href="{url_for('notes.moduleimpl_status',
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
">{mod["module"]["code"] or "(module sans code)"}</a>"""
f"""<a title="{mod['module']['titre']}" href="{
url_for('notes.moduleimpl_status',
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])
}">{mod["module"]["code"] or "(module sans code)"}</a>"""
)
else:
ex.append(mod["module"]["code"] or "(module sans code)")
@ -1003,7 +1034,7 @@ def _tables_abs_etud(
if format == "html":
ex.append(
f"""<a title="{mod['module']['titre']}"
href="{url_for('notes.moduleimpl_status',
href="{url_for('notes.moduleimpl_status',
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
">{mod["module"]["code"] or '(module sans code)'}</a>"""
)

View File

@ -321,9 +321,10 @@ def filter_by_formsemestre(
def justifies(justi: Justificatif, obj: bool = False) -> list[int] or Query:
"""
Retourne la liste des assiduite_id qui sont justifié par la justification
Une assiduité est justifiée si elle est COMPLETEMENT ou PARTIELLEMENT comprise dans la plage du justificatif
et que l'état du justificatif est "valide"
renvoie des id si obj == False, sinon les Assiduités
Une assiduité est justifiée si elle est COMPLETEMENT ou PARTIELLEMENT
comprise dans la plage du justificatif
et que l'état du justificatif est "valide".
Renvoie des id si obj == False, sinon les Assiduités
"""
if justi.etat != scu.EtatJustificatif.VALIDE:
@ -427,7 +428,7 @@ def invalidate_assiduites_count(etudid, sem):
"""Invalidate (clear) cached counts"""
date_debut = sem["date_debut_iso"]
date_fin = sem["date_fin_iso"]
for met in sco_preferences.ASSIDUITES_METRIC_LABEL.values():
for met in scu.AssiduitesMetrics.TAG:
key = str(etudid) + "_" + date_debut + "_" + date_fin + f"{met}_assiduites"
sco_cache.AbsSemEtudCache.delete(key)
@ -444,9 +445,9 @@ def invalidate_assiduites_count_sem(sem):
def invalidate_assiduites_etud_date(etudid, date: datetime):
"""Doit etre appelé à chaque modification des assiduites pour cet étudiant et cette date.
"""Doit etre appelé à chaque modification des assiduites
pour cet étudiant et cette date.
Invalide cache absence et caches semestre
date: date au format ISO
"""
from app.scodoc import sco_compute_moy

View File

@ -112,7 +112,7 @@ def formsemestre_bulletinetud_published_dict(
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if not etudid in nt.identdict:
if etudid not in nt.identdict:
abort(404, "etudiant non inscrit dans ce semestre")
d = {"type": "classic", "version": "0"}
if (not sem["bul_hide_xml"]) or force_publishing:

View File

@ -198,13 +198,6 @@ def _get_pref_default_value_from_config(name, pref_spec):
_INSTALLED_FONTS = ", ".join(sco_pdf.get_available_font_names())
ASSIDUITES_METRIC_LABEL = {
# l'ordre est important, c'est celui-du menu. Le defaut en 1er donc.
"1/2 J.": "demi",
"J.": "journee",
"H.": "heure",
}
PREF_CATEGORIES = (
# sur page "Paramètres"
("general", {"title": ""}), # voir paramètre titlr de TrivialFormulator
@ -666,8 +659,8 @@ class BasePreferences(object):
{
"initvalue": "1/2 J.",
"input_type": "menu",
"labels": list(ASSIDUITES_METRIC_LABEL.keys()),
"allowed_values": list(ASSIDUITES_METRIC_LABEL.keys()),
"labels": scu.AssiduitesMetrics.LONG,
"allowed_values": scu.AssiduitesMetrics.SHORT,
"title": "Métrique de l'assiduité",
"explanation": "Unité utilisée dans la fiche étudiante, les bilans et les calculs",
"category": "assi",

View File

@ -166,6 +166,11 @@ class BiDirectionalEnum(Enum):
"""Vérifie sur un attribut existe dans l'enum"""
return attr.upper() in cls._member_names_
@classmethod
def all(cls, keys=True):
"""Retourne toutes les clés de l'enum"""
return cls._member_names_ if keys else list(cls._value2member_map_.keys())
@classmethod
def get(cls, attr: str, default: any = None):
"""Récupère une valeur à partir de son attribut"""
@ -254,15 +259,54 @@ def is_period_overlapping(
return p_deb < i_fin and p_fin > i_deb
def translate_assiduites_metric(hr_metric) -> str:
if hr_metric == "1/2 J.":
return "demi"
if hr_metric == "J.":
return "journee"
if hr_metric == "N.":
return "compte"
if hr_metric == "H.":
return "heure"
class AssiduitesMetrics:
"""Labels associés au métrique de l'assiduité"""
SHORT: list[str] = ["1/2 J.", "J.", "H."]
LONG: list[str] = ["Demi-journée", "Journée", "Heure"]
TAG: list[str] = ["demi", "journee", "heure"]
def translate_assiduites_metric(metric, inverse=True, short=True) -> str:
"""
translate_assiduites_metric
SHORT[true] : "J." "H." "N." "1/2 J."
SHORT[false] : "Journée" "Heure" "Nombre" "Demi-Journée"
inverse[false] : "demi" -> "1/2 J."
inverse[true] : "1/2 J." -> "demi"
Args:
metric (str): la métrique à traduire
inverse (bool, optional). Defaults to True.
short (bool, optional). Defaults to True.
Returns:
str: la métrique traduite
"""
index: int = None
if not inverse:
try:
index = AssiduitesMetrics.TAG.index(metric)
return (
AssiduitesMetrics.SHORT[index]
if short
else AssiduitesMetrics.LONG[index]
)
except ValueError:
return None
try:
index = (
AssiduitesMetrics.SHORT.index(metric)
if short
else AssiduitesMetrics.LONG.index(metric)
)
return AssiduitesMetrics.TAG[index]
except ValueError:
return None
# Types de modules

View File

@ -125,8 +125,8 @@ class RowAssi(tb.Row):
"absent": ["Absences", 0.0, 0.0],
}
assi_metric = sco_preferences.ASSIDUITES_METRIC_LABEL.get(
sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id)
assi_metric = scu.translate_assiduites_metric(
sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id),
)
for etat, valeur in retour.items():

View File

@ -336,19 +336,21 @@
}
const defAnnee = {{ annee }}
let annees = {{ annees | safe }}
annees = annees.filter((x, i) => annees.indexOf(x) === i)
const etudid = {{ sco.etud.id }};
const nonwork = [{{ nonworkdays | safe }}];
window.onload = () => {
const select = document.querySelector('#annee');
for (let i = defAnnee + 1; i > defAnnee - 6; i--) {
annees.forEach((a) => {
const opt = document.createElement("option");
opt.value = i + "",
opt.textContent = i + "";
if (i === defAnnee) {
opt.value = a + "",
opt.textContent = `${a} - ${a + 1}`;
if (a === defAnnee) {
opt.selected = true;
}
select.appendChild(opt)
}
})
setterAnnee(defAnnee)
};

View File

@ -162,7 +162,11 @@
userIdDiv.textContent = `saisi le ${formatDateModal(
assiduite.entry_date,
"à"
)} \npar ${assiduite.user_id}`;
)}`;
if (assiduite.user_id != null) {
userIdDiv.textContent += `\npar ${assiduite.user_id}`
}
bubble.appendChild(userIdDiv);
bubble.style.left = `${event.clientX - bubble.offsetWidth / 2}px`;

View File

@ -327,8 +327,8 @@ def bilan_etud():
date_debut: str = f"{scu.annee_scolaire()}-09-01"
date_fin: str = f"{scu.annee_scolaire()+1}-06-30"
assi_metric = sco_preferences.ASSIDUITES_METRIC_LABEL.get(
sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id)
assi_metric = scu.translate_assiduites_metric(
sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id),
)
return HTMLBuilder(
@ -417,6 +417,16 @@ def calendrier_etud():
],
)
annees: list[int] = sorted(
[ins.formsemestre.date_debut.year for ins in etud.formsemestre_inscriptions],
reverse=True,
)
annees_str: str = "["
for ann in annees:
annees_str += f"{ann},"
annees_str += "]"
return HTMLBuilder(
header,
render_template(
@ -425,6 +435,7 @@ def calendrier_etud():
annee=scu.annee_scolaire(),
nonworkdays=_non_work_days(),
minitimeline=_mini_timeline(),
annees=annees_str,
),
).build()
@ -840,8 +851,14 @@ def visu_assi_group():
return render_template(
"assiduites/pages/visu_assi.j2",
assi_metric=sco_preferences.ASSIDUITES_METRIC_LABEL.get(
sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id)
assi_metric=scu.translate_assiduites_metric(
scu.translate_assiduites_metric(
sco_preferences.get_preference(
"assi_metrique", dept_id=g.scodoc_dept_id
),
),
inverse=False,
short=False,
),
date_debut=dates["debut"],
date_fin=dates["fin"],

View File

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

View File

@ -661,7 +661,12 @@ def profile(host, port, length, profile_dir):
@click.option(
"-n",
"--noon",
help="Spécifie l'heure de fin du matin (et donc début de l'après-midi) format `hh:mm`",
help="Spécifie l'heure de fin du matin format `hh:mm`",
)
@click.option(
"-a",
"--afternoon",
help="Spécifie l'heure de début de l'après-midi format `hh:mm` valeur identique à --noon si non spécifié",
)
@click.option(
"-e",
@ -670,10 +675,14 @@ def profile(host, port, length, profile_dir):
)
@with_appcontext
def migrate_abs_to_assiduites(
dept: str = None, morning: str = None, noon: str = None, evening: str = None
dept: str = None,
morning: str = None,
noon: str = None,
afternoon: str = None,
evening: str = None,
): # migrate-abs-to-assiduites
"""Permet de migrer les absences vers le nouveau module d'assiduités"""
tools.migrate_abs_to_assiduites(dept, morning, noon, evening)
tools.migrate_abs_to_assiduites(dept, morning, noon, afternoon, evening)
# import cProfile
# cProfile.runctx(
# f"tools.migrate_abs_to_assiduites({dept})",

View File

@ -4,6 +4,22 @@
# Prend la version dans le code source local et cherche une release gitea de même tag.
# Lance ensuite les tests unitaires locaux.
SKIP_TESTS=0
while getopts "s" opt; do
case "$opt" in
s)
SKIP_TESTS=1
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
# Le répertoire de ce script: .../scodoc/tools
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
@ -57,13 +73,17 @@ SCODOC_USER=scodoc
[ -z "$FACTORY_DIR" ] && die "empty FACTORY_DIR"
[ "$(id -nu)" != "$SCODOC_USER" ] && die "Erreur: le script $0 doit être lancé par l'utilisateur $SCODOC_USER"
# Tests unitaires lancés dans le répertoire de travail
echo "TESTS UNITAIRES"
(cd "$UNIT_TESTS_DIR"; pytest tests/unit) || terminate "Erreur dans tests unitaires"
# Tests API
(cd "$UNIT_TESTS_DIR"; tools/test_api.sh) || terminate "Erreur dans tests unitaires API"
if [ "$SKIP_TESTS" = 1 ]
then
echo "SKIPPING UNIT TESTS !"
else
# Tests unitaires lancés dans le répertoire de travail
echo "TESTS UNITAIRES"
(cd "$UNIT_TESTS_DIR"; pytest tests/unit) || terminate "Erreur dans tests unitaires"
# Tests API
(cd "$UNIT_TESTS_DIR"; tools/test_api.sh) || terminate "Erreur dans tests unitaires API"
fi
# Création répertoire du paquet, et de opt
slash="$FACTORY_DIR"/"$DEST_DIR"

View File

@ -45,7 +45,7 @@ then
PSQL=/usr/lib/postgresql/15/bin/psql
#export POSTGRES_SERVICE="postgresql@11-main.service"
else
die "unsupported Debian version"
die "unsupported Debian version (${debian_version}, expected 12)"
fi
export PSQL

View File

@ -47,6 +47,7 @@ class _glob:
MORNING: time = None
NOON: time = None
AFTERNOON: time = None
EVENING: time = None
@ -93,7 +94,7 @@ class _Merger:
time_ = _glob.NOON if end else _glob.MORNING
date_ = datetime.combine(couple[0], time_)
else:
time_ = _glob.EVENING if end else _glob.NOON
time_ = _glob.EVENING if end else _glob.AFTERNOON
date_ = datetime.combine(couple[0], time_)
d = localize_datetime(date_)
return d
@ -229,6 +230,7 @@ def migrate_abs_to_assiduites(
dept: str = None,
morning: str = None,
noon: str = None,
afternoon: str = None,
evening: str = None,
debug: bool = False,
):
@ -266,6 +268,12 @@ def migrate_abs_to_assiduites(
noon: list[str] = str(noon).split(":")
_glob.NOON = time(int(noon[0]), int(noon[1]))
if afternoon is None:
afternoon = ScoDocSiteConfig.get("assi_lunch_time", time(13, 0))
afternoon: list[str] = str(afternoon).split(":")
_glob.AFTERNOON = time(int(afternoon[0]), int(afternoon[1]))
if evening is None:
evening = ScoDocSiteConfig.get("assi_afternoon_time", time(18, 0))