Edition préférences: sections dépliables. + Code cleaning.

This commit is contained in:
Emmanuel Viennet 2022-10-02 23:43:29 +02:00
parent ad46a190ab
commit 37a8b3bb0b
41 changed files with 178 additions and 127 deletions

View File

@ -47,7 +47,7 @@ def pvjury_table_but(formsemestre_id: int, format="html"):
title = "Procès-verbal de jury BUT annuel" title = "Procès-verbal de jury BUT annuel"
if format == "html": if format == "html":
line_sep = "<br/>" line_sep = "<br>"
else: else:
line_sep = "\n" line_sep = "\n"
# remplace pour le BUT la fonction sco_pvjury.pvjury_table # remplace pour le BUT la fonction sco_pvjury.pvjury_table

View File

@ -253,7 +253,7 @@ def get_annotation_PE(etudid, tag_annotation_pe):
) # Suppression du tag d'annotation PE ) # Suppression du tag d'annotation PE
annotationPE = annotationPE.replace("\r", "") # Suppression des \r annotationPE = annotationPE.replace("\r", "") # Suppression des \r
annotationPE = annotationPE.replace( annotationPE = annotationPE.replace(
"<br/>", "\n\n" "<br>", "\n\n"
) # Interprète les retours chariots html ) # Interprète les retours chariots html
return annotationPE return annotationPE
return "" # pas d'annotations return "" # pas d'annotations

View File

@ -55,7 +55,7 @@ def _pe_view_sem_recap_form(formsemestre_id):
<p class="help"> <p class="help">
Cette fonction génère un ensemble de fichiers permettant d'éditer des avis de Cette fonction génère un ensemble de fichiers permettant d'éditer des avis de
poursuites d'études. poursuites d'études.
<br/> <br>
De nombreux aspects sont paramétrables: De nombreux aspects sont paramétrables:
<a href="https://scodoc.org/AvisPoursuiteEtudes" target="_blank" rel="noopener"> <a href="https://scodoc.org/AvisPoursuiteEtudes" target="_blank" rel="noopener">
voir la documentation</a>. voir la documentation</a>.
@ -65,7 +65,7 @@ def _pe_view_sem_recap_form(formsemestre_id):
<div class="pe_template_up"> <div class="pe_template_up">
Les templates sont généralement installés sur le serveur ou dans le Les templates sont généralement installés sur le serveur ou dans le
paramétrage de ScoDoc. paramétrage de ScoDoc.
<br/> <br>
Au besoin, vous pouvez spécifier ici votre propre fichier de template Au besoin, vous pouvez spécifier ici votre propre fichier de template
(<tt>un_avis.tex</tt>): (<tt>un_avis.tex</tt>):
<div class="pe_template_upb">Template: <div class="pe_template_upb">Template:

View File

@ -38,6 +38,9 @@ def TrivialFormulator(
html_foot_markup="", html_foot_markup="",
readonly=False, readonly=False,
is_submitted=False, is_submitted=False,
title="",
after_table="",
before_table="{title}",
): ):
""" """
form_url : URL for this form form_url : URL for this form
@ -74,7 +77,8 @@ def TrivialFormulator(
HTML elements: HTML elements:
input_type : 'text', 'textarea', 'password', input_type : 'text', 'textarea', 'password',
'radio', 'menu', 'checkbox', 'radio', 'menu', 'checkbox',
'hidden', 'separator', 'file', 'date', 'datedmy' (avec validation), 'hidden', 'separator', 'table_separator',
'file', 'date', 'datedmy' (avec validation),
'boolcheckbox', 'text_suggest', 'boolcheckbox', 'text_suggest',
'color' 'color'
(default text) (default text)
@ -111,6 +115,9 @@ def TrivialFormulator(
html_foot_markup=html_foot_markup, html_foot_markup=html_foot_markup,
readonly=readonly, readonly=readonly,
is_submitted=is_submitted, is_submitted=is_submitted,
title=title,
after_table=after_table,
before_table=before_table,
) )
form = t.getform() form = t.getform()
if t.canceled(): if t.canceled():
@ -144,6 +151,9 @@ class TF(object):
html_foot_markup="", # html snippet put at the end, just after the table html_foot_markup="", # html snippet put at the end, just after the table
readonly=False, readonly=False,
is_submitted=False, is_submitted=False,
title="",
after_table="",
before_table="{title}",
): ):
self.form_url = form_url self.form_url = form_url
self.values = values.copy() self.values = values.copy()
@ -165,6 +175,9 @@ class TF(object):
self.top_buttons = top_buttons self.top_buttons = top_buttons
self.bottom_buttons = bottom_buttons self.bottom_buttons = bottom_buttons
self.html_foot_markup = html_foot_markup self.html_foot_markup = html_foot_markup
self.title = title
self.after_table = after_table
self.before_table = before_table
self.readonly = readonly self.readonly = readonly
self.result = None self.result = None
self.is_submitted = is_submitted self.is_submitted = is_submitted
@ -426,6 +439,7 @@ class TF(object):
R.append('<input type="hidden" name="%s_submitted" value="1">' % self.formid) R.append('<input type="hidden" name="%s_submitted" value="1">' % self.formid)
if self.top_buttons: if self.top_buttons:
R.append(buttons_markup + "<p></p>") R.append(buttons_markup + "<p></p>")
R.append(self.before_table.format(title=self.title))
R.append('<table class="tf">') R.append('<table class="tf">')
for field, descr in self.formdescription: for field, descr in self.formdescription:
if descr.get("readonly", False): if descr.get("readonly", False):
@ -453,6 +467,16 @@ class TF(object):
etempl = separatortemplate etempl = separatortemplate
R.append(etempl % {"label": title, "item_dom_attr": item_dom_attr}) R.append(etempl % {"label": title, "item_dom_attr": item_dom_attr})
continue continue
elif input_type == "table_separator":
etempl = ""
# Table ouverte ?
if len([p for p in R if "<table" in p]) > len(
[p for p in R if "</table" in p]
):
R.append(f"""</table>{self.after_table}""")
R.append(
f"""{self.before_table.format(title=descr.get("title", ""))}<table class="tf">"""
)
else: else:
etempl = itemtemplate etempl = itemtemplate
lab = [] lab = []
@ -610,7 +634,7 @@ class TF(object):
'<input type="hidden" name="%s" id="%s" value="%s" %s >' '<input type="hidden" name="%s" id="%s" value="%s" %s >'
% (field, wid, values[field], attribs) % (field, wid, values[field], attribs)
) )
elif input_type == "separator": elif (input_type == "separator") or (input_type == "table_separator"):
pass pass
elif input_type == "file": elif input_type == "file":
lem.append( lem.append(
@ -641,13 +665,15 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
) )
lem.append(('value="%(' + field + ')s" >') % values) lem.append(('value="%(' + field + ')s" >') % values)
else: else:
raise ValueError("unkown input_type for form (%s)!" % input_type) raise ValueError(f"unkown input_type for form ({input_type})!")
explanation = descr.get("explanation", "") explanation = descr.get("explanation", "")
if explanation: if explanation:
lem.append('<span class="tf-explanation">%s</span>' % explanation) lem.append(f"""<span class="tf-explanation">{explanation}</span>""")
comment = descr.get("comment", "") comment = descr.get("comment", "")
if comment: if comment:
lem.append('<br/><span class="tf-comment">%s</span>' % comment) if (input_type != "checkbox") and (input_type != "boolcheckbox"):
lem.append("<br>")
lem.append(f"""<span class="tf-comment">{comment}</span>""")
R.append( R.append(
etempl etempl
% { % {
@ -657,11 +683,11 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
} }
) )
R.append("</table>") R.append("</table>")
R.append(self.after_table)
R.append(self.html_foot_markup) R.append(self.html_foot_markup)
if self.bottom_buttons: if self.bottom_buttons:
R.append("<br/>" + buttons_markup) R.append("<br>" + buttons_markup)
if add_no_enter_js: if add_no_enter_js:
R.append( R.append(
@ -753,7 +779,7 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
if input_type == "separator": # separator if input_type == "separator": # separator
R.append('<td colspan="2">%s' % title) R.append('<td colspan="2">%s' % title)
else: elif input_type != "table_separator":
R.append('<td class="tf-ro-fieldlabel%s">' % klass) R.append('<td class="tf-ro-fieldlabel%s">' % klass)
R.append("%s</td>" % title) R.append("%s</td>" % title)
R.append('<td class="tf-ro-field%s">' % klass) R.append('<td class="tf-ro-field%s">' % klass)
@ -786,7 +812,11 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
R.append( R.append(
'<div class="tf-ro-textarea">%s</div>' % html.escape(self.values[field]) '<div class="tf-ro-textarea">%s</div>' % html.escape(self.values[field])
) )
elif input_type == "separator" or input_type == "hidden": elif (
input_type == "separator"
or input_type == "hidden"
or input_type == "table_separator"
):
pass pass
elif input_type == "file": elif input_type == "file":
R.append("'%s'" % self.values[field]) R.append("'%s'" % self.values[field])

View File

@ -284,8 +284,8 @@ def sco_header(
if current_user.passwd_temp: if current_user.passwd_temp:
H.append( H.append(
f"""<div class="passwd_warn"> f"""<div class="passwd_warn">
Attention !<br/> Attention !<br>
Vous avez reçu un mot de passe temporaire.<br/> Vous avez reçu un mot de passe temporaire.<br>
Vous devez le changer: <a href="{scu.UsersURL}/form_change_password?user_name={current_user.user_name}">cliquez ici</a> Vous devez le changer: <a href="{scu.UsersURL}/form_change_password?user_name={current_user.user_name}">cliquez ici</a>
</div>""" </div>"""
) )

View File

@ -48,26 +48,26 @@ def sidebar_common():
url_for("users.user_info_page", url_for("users.user_info_page",
scodoc_dept=g.scodoc_dept, user_name=current_user.user_name) scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
}">{current_user.user_name}</a> }">{current_user.user_name}</a>
<br/><a id="deconnectlink" href="{url_for("auth.logout")}">déconnexion</a> <br><a id="deconnectlink" href="{url_for("auth.logout")}">déconnexion</a>
</div> </div>
{sidebar_dept()} {sidebar_dept()}
<h2 class="insidebar">Scolarité</h2> <h2 class="insidebar">Scolarité</h2>
<a href="{scu.ScoURL()}" class="sidebar">Semestres</a> <br/> <a href="{scu.ScoURL()}" class="sidebar">Semestres</a> <br>
<a href="{scu.NotesURL()}" class="sidebar">Programmes</a> <br/> <a href="{scu.NotesURL()}" class="sidebar">Programmes</a> <br>
<a href="{scu.AbsencesURL()}" class="sidebar">Absences</a> <br/> <a href="{scu.AbsencesURL()}" class="sidebar">Absences</a> <br>
""" """
] ]
if current_user.has_permission( if current_user.has_permission(
Permission.ScoUsersAdmin Permission.ScoUsersAdmin
) or current_user.has_permission(Permission.ScoUsersView): ) or current_user.has_permission(Permission.ScoUsersView):
H.append( H.append(
f"""<a href="{scu.UsersURL()}" class="sidebar">Utilisateurs</a> <br/>""" f"""<a href="{scu.UsersURL()}" class="sidebar">Utilisateurs</a> <br>"""
) )
if current_user.has_permission(Permission.ScoChangePreferences): if current_user.has_permission(Permission.ScoChangePreferences):
H.append( H.append(
f"""<a href="{url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}" f"""<a href="{url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}"
class="sidebar">Paramétrage</a> <br/>""" class="sidebar">Paramétrage</a> <br>"""
) )
return "".join(H) return "".join(H)
@ -84,7 +84,7 @@ def sidebar(etudid: int = None):
H = [ H = [
f"""<div class="sidebar"> f"""<div class="sidebar">
{ sidebar_common() } { sidebar_common() }
<div class="box-chercheetud">Chercher étudiant:<br/> <div class="box-chercheetud">Chercher étudiant:<br>
<form method="get" id="form-chercheetud" <form method="get" id="form-chercheetud"
action="{url_for('scolar.search_etud_in_dept', scodoc_dept=g.scodoc_dept) }"> action="{url_for('scolar.search_etud_in_dept', scodoc_dept=g.scodoc_dept) }">
<div><input type="text" size="12" class="in-expnom" name="expnom" spellcheck="false"></input></div> <div><input type="text" size="12" class="in-expnom" name="expnom" spellcheck="false"></input></div>
@ -121,7 +121,7 @@ def sidebar(etudid: int = None):
nbabsnj = nbabs - nbabsjust nbabsnj = nbabs - nbabsjust
H.append( H.append(
f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">(1/2 j.) f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">(1/2 j.)
<br/>{ nbabsjust } J., { nbabsnj } N.J.</span>""" <br>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
) )
H.append("<ul>") H.append("<ul>")
if current_user.has_permission(Permission.ScoAbsChange): if current_user.has_permission(Permission.ScoAbsChange):
@ -150,7 +150,7 @@ def sidebar(etudid: int = None):
# Logo # Logo
H.append( H.append(
f"""<div class="logo-insidebar"> f"""<div class="logo-insidebar">
<div class="sidebar-bottom"><a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }" class="sidebar">À propos</a><br/> <div class="sidebar-bottom"><a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }" class="sidebar">À propos</a><br>
<a href="{ scu.SCO_USER_MANUAL }" target="_blank" class="sidebar">Aide</a> <a href="{ scu.SCO_USER_MANUAL }" target="_blank" class="sidebar">Aide</a>
</div></div> </div></div>
<div class="logo-logo"> <div class="logo-logo">

View File

@ -51,7 +51,7 @@ def convert_html_to_text(s):
def newline_to_br(text): def newline_to_br(text):
return text.replace("\n", "<br/>") return text.replace("\n", "<br>")
class HTMLSanitizer(HTMLParser): class HTMLSanitizer(HTMLParser):

View File

@ -251,7 +251,7 @@ def SignaleAbsenceEtud(): # etudid implied
<td><input type="text" name="datefin" size="10" class="datepicker"/> <em>j/m/a</em></td> <td><input type="text" name="datefin" size="10" class="datepicker"/> <em>j/m/a</em></td>
</tr> </tr>
</table> </table>
<br/> <br>
<input type="radio" name="demijournee" value="2" checked>Journé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="1">Matin(s)
&nbsp;<input type="radio" name="demijournee" value="0">Après-midi &nbsp;<input type="radio" name="demijournee" value="0">Après-midi
@ -260,7 +260,7 @@ def SignaleAbsenceEtud(): # etudid implied
<p> <p>
<input type="checkbox" name="estjust"/>Absence justifiée. <input type="checkbox" name="estjust"/>Absence justifiée.
<br/> <br>
Raison: <input type="text" name="description" size="42"/> (optionnel) Raison: <input type="text" name="description" size="42"/> (optionnel)
</p> </p>
@ -398,13 +398,13 @@ def JustifAbsenceEtud(): # etudid implied
<td><input type="text" name="datefin" size="10" class="datepicker"/></td> <td><input type="text" name="datefin" size="10" class="datepicker"/></td>
</tr> </tr>
</table> </table>
<br/> <br>
<input type="radio" name="demijournee" value="2" checked>Journé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="1">Matin(s)
&nbsp;<input type="radio" name="demijournee" value="0">Apr&egrave;s midi &nbsp;<input type="radio" name="demijournee" value="0">Apr&egrave;s midi
<br/><br/> <br><br>
Raison: <input type="text" name="description" size="42"/> (optionnel) Raison: <input type="text" name="description" size="42"/> (optionnel)
<p> <p>
@ -752,9 +752,9 @@ def CalAbs(etudid, sco_year=None):
), ),
"""<table><tr><td><h2>Absences de %(nomprenom)s (%(inscription)s)</h2><p>""" """<table><tr><td><h2>Absences de %(nomprenom)s (%(inscription)s)</h2><p>"""
% etud, % etud,
"""<b><font color="#EE0000">A : absence NON justifiée</font><br/> """<b><font color="#EE0000">A : absence NON justifiée</font><br>
<font color="#F8B7B0">a : absence justifiée</font><br/> <font color="#F8B7B0">a : absence justifiée</font><br>
<font color="#8EA2C6">X : justification sans absence</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> </p>
""" """

View File

@ -980,8 +980,8 @@ class ApoData(object):
log("Colonnes declarees: %s" % declared) log("Colonnes declarees: %s" % declared)
log("Colonnes presentes: %s" % present) log("Colonnes presentes: %s" % present)
raise ScoFormatError( raise ScoFormatError(
"""Fichier Apogee invalide<br/>Colonnes declarees: <tt>%s</tt> """Fichier Apogee invalide<br>Colonnes declarees: <tt>%s</tt>
<br/>Colonnes presentes: <tt>%s</tt>""" <br>Colonnes presentes: <tt>%s</tt>"""
% (declared, present) % (declared, present)
) )
# l'ensemble de tous les codes des elements apo des semestres: # l'ensemble de tous les codes des elements apo des semestres:

View File

@ -295,7 +295,7 @@ def etudarchive_import_files_form(group_id):
supprimer, via la fiche de chaque étudiant.</b> supprimer, via la fiche de chaque étudiant.</b>
</p> </p>
<p class="help">Cette page permet de charger en une seule fois les fichiers <p class="help">Cette page permet de charger en une seule fois les fichiers
de plusieurs étudiants.<br/> de plusieurs étudiants.<br>
Il faut d'abord remplir une feuille excel donnant les noms Il faut d'abord remplir une feuille excel donnant les noms
des fichiers (un fichier par étudiant). des fichiers (un fichier par étudiant).
</p> </p>

View File

@ -237,7 +237,7 @@ class BulletinGenerator:
# compris: reportlab is not thread safe ! # compris: reportlab is not thread safe !
# see http://two.pairlist.net/pipermail/reportlab-users/2006-June/005037.html # see http://two.pairlist.net/pipermail/reportlab-users/2006-June/005037.html
# (donc maintenant protégé dans ScoDoc par un Lock global) # (donc maintenant protégé dans ScoDoc par un Lock global)
self.diagnostic = "erreur lors de la génération du PDF<br/>" self.diagnostic = "erreur lors de la génération du PDF<br>"
self.diagnostic += "<pre>" + traceback.format_exc() + "</pre>" self.diagnostic += "<pre>" + traceback.format_exc() + "</pre>"
return [] return []
return Table(Pt, colWidths=colWidths, style=pdfTableStyle) return Table(Pt, colWidths=colWidths, style=pdfTableStyle)

View File

@ -132,9 +132,9 @@ def formsemestre_table_estim_cost(
], ],
html_caption="""<div class="help"> html_caption="""<div class="help">
Estimation du coût de formation basé sur le programme pédagogique Estimation du coût de formation basé sur le programme pédagogique
et les nombres de groupes.<br/> et les nombres de groupes.<br>
Coût théorique en heures équivalent TD.<br/> Coût théorique en heures équivalent TD.<br>
Attention: ne prend en compte que les modules utilisés dans ce semestre.<br/> Attention: ne prend en compte que les modules utilisés dans ce semestre.<br>
Attention: prend en compte <em>tous les modules</em> utilisés dans ce semestre, ce qui Attention: prend en compte <em>tous les modules</em> utilisés dans ce semestre, ce qui
peut conduire à une sur-estimation du coût s'il y a des modules optionnels peut conduire à une sur-estimation du coût s'il y a des modules optionnels
(dans ce cas, retoucher le tableau excel exporté). (dans ce cas, retoucher le tableau excel exporté).
@ -173,10 +173,10 @@ def formsemestre_estim_cost(
h = """ h = """
<form name="f" method="get" action="%s"> <form name="f" method="get" action="%s">
<input type="hidden" name="formsemestre_id" value="%s"></input> <input type="hidden" name="formsemestre_id" value="%s"></input>
Nombre de groupes de TD: <input type="text" name="n_group_td" value="%s" onchange="document.f.submit()"/><br/> Nombre de groupes de TD: <input type="text" name="n_group_td" value="%s" onchange="document.f.submit()"/><br>
Nombre de groupes de TP: <input type="text" name="n_group_tp" value="%s" onchange="document.f.submit()"/> Nombre de groupes de TP: <input type="text" name="n_group_tp" value="%s" onchange="document.f.submit()"/>
&nbsp;Coefficient heures TP: <input type="text" name="coef_tp" value="%s" onchange="document.f.submit()"/> &nbsp;Coefficient heures TP: <input type="text" name="coef_tp" value="%s" onchange="document.f.submit()"/>
<br/> <br>
</form> </form>
""" % ( """ % (
request.base_url, request.base_url,

View File

@ -98,7 +98,7 @@ def index_html(showcodes=0, showsemtable=0):
H.append( H.append(
"""<h2>Aucun utilisateur défini !</h2><p>Pour définir des utilisateurs """<h2>Aucun utilisateur défini !</h2><p>Pour définir des utilisateurs
<a href="Users">passez par la page Utilisateurs</a>. <a href="Users">passez par la page Utilisateurs</a>.
<br/> <br>
Définissez au moins un utilisateur avec le rôle AdminXXX (le responsable du département XXX). Définissez au moins un utilisateur avec le rôle AdminXXX (le responsable du département XXX).
</p> </p>
""" """

View File

@ -542,7 +542,7 @@ class EtapeBilan(object):
ind_col, ind_col,
comptage, comptage,
"", "",
json.dumps(self.titres[ind_col].replace("<br/>", " / "))[1:-1], json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1],
) )
elif ind_col == COL_CUMUL: elif ind_col == COL_CUMUL:
javascript = "doFiltrage(%s, %s, '.%s', '*', '%s', '%s', '%s');" % ( javascript = "doFiltrage(%s, %s, '.%s', '*', '%s', '%s', '%s');" % (
@ -561,7 +561,7 @@ class EtapeBilan(object):
ind_col, ind_col,
comptage, comptage,
json.dumps(self.titres[ind_row])[1:-1], json.dumps(self.titres[ind_row])[1:-1],
json.dumps(self.titres[ind_col].replace("<br/>", " / "))[1:-1], json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1],
) )
return '<a href="#synthese" onclick="%s">%d</a>' % (javascript, count) return '<a href="#synthese" onclick="%s">%d</a>' % (javascript, count)
@ -590,9 +590,9 @@ class EtapeBilan(object):
for key_etape in liste_etapes: for key_etape in liste_etapes:
col_id = self.indicatifs[key_etape] col_id = self.indicatifs[key_etape]
col_ids.append(col_id) col_ids.append(col_id)
self.titres[col_id] = "%s<br/>%s" % key_to_values(key_etape) self.titres[col_id] = "%s<br>%s" % key_to_values(key_etape)
col_ids.append(COL_CUMUL) col_ids.append(COL_CUMUL)
self.titres[COL_CUMUL] = "Total<br/>semestre" self.titres[COL_CUMUL] = "Total<br>semestre"
rows = [] rows = []
for semestre in liste_semestres: for semestre in liste_semestres:
@ -675,7 +675,7 @@ class EtapeBilan(object):
NIP_NON_UNIQUE, NIP_NON_UNIQUE,
) )
H.append( H.append(
'Code(s) nip) partagé(s) par <a href="#synthèse" onclick="%s">%d</a> étudiants<br/>' 'Code(s) nip) partagé(s) par <a href="#synthèse" onclick="%s">%d</a> étudiants<br>'
% (javascript, self.tag_count[NIP_NON_UNIQUE]) % (javascript, self.tag_count[NIP_NON_UNIQUE])
) )
return "\n".join(H) return "\n".join(H)
@ -728,11 +728,11 @@ class EtapeBilan(object):
prenom = data_etu.data_scodoc["prenom"] prenom = data_etu.data_scodoc["prenom"]
link = self.link_etu(etudid, nom) link = self.link_etu(etudid, nom)
tag = ", ".join([tag for tag in sorted(data_etu.tags)]) tag = ", ".join([tag for tag in sorted(data_etu.tags)])
semestre = "<br/>".join( semestre = "<br>".join(
[self.link_semestre(sem, True) for sem in data_etu.semestres] [self.link_semestre(sem, True) for sem in data_etu.semestres]
) )
annees = "<br/>".join([etape[0] for etape in data_etu.etapes]) annees = "<br>".join([etape[0] for etape in data_etu.etapes])
etapes = "<br/>".join([etape[1] for etape in data_etu.etapes]) etapes = "<br>".join([etape[1] for etape in data_etu.etapes])
classe = data_etu.ind_row + data_etu.ind_col classe = data_etu.ind_row + data_etu.ind_col
if NIP_NON_UNIQUE in data_etu.tags: if NIP_NON_UNIQUE in data_etu.tags:
classe += " " + NIP_NON_UNIQUE classe += " " + NIP_NON_UNIQUE

View File

@ -907,7 +907,7 @@ def fill_etuds_info(etuds: list[dict], add_admission=True):
etud["ilycee"] = "Lycée " + format_lycee(etud["nomlycee"]) etud["ilycee"] = "Lycée " + format_lycee(etud["nomlycee"])
if etud["villelycee"]: if etud["villelycee"]:
etud["ilycee"] += " (%s)" % etud.get("villelycee", "") etud["ilycee"] += " (%s)" % etud.get("villelycee", "")
etud["ilycee"] += "<br/>" etud["ilycee"] += "<br>"
else: else:
if etud.get("codelycee"): if etud.get("codelycee"):
etud["ilycee"] = format_lycee_from_code(etud["codelycee"]) etud["ilycee"] = format_lycee_from_code(etud["codelycee"])

View File

@ -224,7 +224,7 @@ def formsemestre_check_absences_html(formsemestre_id):
"Vérification absences aux évaluations de ce semestre", "Vérification absences aux évaluations de ce semestre",
), ),
"""<p class="help">Vérification de la cohérence entre les notes saisies et les absences signalées. """<p class="help">Vérification de la cohérence entre les notes saisies et les absences signalées.
Sont listés tous les modules avec des évaluations.<br/>Aucune action n'est effectuée: Sont listés tous les modules avec des évaluations.<br>Aucune action n'est effectuée:
il vous appartient de corriger les erreurs détectées si vous le jugez nécessaire. il vous appartient de corriger les erreurs détectées si vous le jugez nécessaire.
</p>""", </p>""",
] ]

View File

@ -58,7 +58,7 @@ def form_search_etud(
<b>{title}</b> <b>{title}</b>
<input type="text" name="expnom" class="in-expnom" width="12" spellcheck="false" value=""> <input type="text" name="expnom" class="in-expnom" width="12" spellcheck="false" value="">
<input type="submit" value="Chercher"> <input type="submit" value="Chercher">
<br/>(entrer une partie du nom) <br>(entrer une partie du nom)
""" """
) )
if dest_url: if dest_url:

View File

@ -1659,7 +1659,7 @@ def formsemestre_change_publication_bul(
"<h2>Confirmer la %s publication des bulletins ?</h2>" % msg, "<h2>Confirmer la %s publication des bulletins ?</h2>" % msg,
helpmsg="""Il est parfois utile de désactiver la diffusion des bulletins, helpmsg="""Il est parfois utile de désactiver la diffusion des bulletins,
par exemple pendant la tenue d'un jury ou avant harmonisation des notes. par exemple pendant la tenue d'un jury ou avant harmonisation des notes.
<br/> <br>
Ce réglage n'a d'effet que si votre établissement a interfacé ScoDoc et un portail étudiant. Ce réglage n'a d'effet que si votre établissement a interfacé ScoDoc et un portail étudiant.
""", """,
dest_url="", dest_url="",

View File

@ -893,7 +893,7 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
H.append("</ul>") H.append("</ul>")
H.append("<p>Total: %d étudiants concernés.</p>" % len(etudlist)) H.append("<p>Total: %d étudiants concernés.</p>" % len(etudlist))
H.append( H.append(
"""<p class="help">Ces étudiants sont inscrits dans le semestre sélectionné et aussi dans d'autres semestres qui se déroulent en même temps ! <br/>Sauf exception, cette situation est anormale:</p> """<p class="help">Ces étudiants sont inscrits dans le semestre sélectionné et aussi dans d'autres semestres qui se déroulent en même temps ! <br>Sauf exception, cette situation est anormale:</p>
<ul> <ul>
<li>vérifier que les dates des semestres se suivent sans se chevaucher</li> <li>vérifier que les dates des semestres se suivent sans se chevaucher</li>
<li>ou si besoin désinscrire le(s) étudiant(s) de l'un des semestres (via leurs fiches individuelles).</li> <li>ou si besoin désinscrire le(s) étudiant(s) de l'un des semestres (via leurs fiches individuelles).</li>

View File

@ -349,7 +349,7 @@ def formsemestre_validation_etud_form(
H.append("</table>") H.append("</table>")
H.append( H.append(
'<p><br/></p><input type="submit" value="Valider ce choix" disabled="1" id="subut"/>' '<p><br></p><input type="submit" value="Valider ce choix" disabled="1" id="subut"/>'
) )
H.append("</form>") H.append("</form>")
@ -1299,7 +1299,7 @@ def check_formation_ues(formation_id):
H = [ H = [
"""<div class="ue_warning"><span>Attention:</span> les UE suivantes de cette formation """<div class="ue_warning"><span>Attention:</span> les UE suivantes de cette formation
sont utilisées dans des sont utilisées dans des
semestres de rangs différents (eg S1 et S3). <br/>Cela peut engendrer des problèmes pour semestres de rangs différents (eg S1 et S3). <br>Cela peut engendrer des problèmes pour
la capitalisation des UE. Il serait préférable d'essayer de rectifier cette situation: 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 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 soit veiller à saisir le bon indice de semestre dans le menu lors de la validation d'une

View File

@ -302,7 +302,7 @@ def scolars_import_excel_file(
else: else:
unknown.append(f) unknown.append(f)
raise ScoValueError( raise ScoValueError(
"""Nombre de colonnes incorrect (devrait être %d, et non %d)<br/> """Nombre de colonnes incorrect (devrait être %d, et non %d)<br>
(colonnes manquantes: %s, colonnes invalides: %s)""" (colonnes manquantes: %s, colonnes invalides: %s)"""
% (len(titles), len(fs), list(missing.keys()), unknown) % (len(titles), len(fs), list(missing.keys()), unknown)
) )

View File

@ -122,7 +122,7 @@ def import_excel_file(datafile, force=""):
del cols[tit] del cols[tit]
if cols or unknown: if cols or unknown:
raise ScoValueError( raise ScoValueError(
"""colonnes incorrectes (on attend %d, et non %d) <br/> """colonnes incorrectes (on attend %d, et non %d) <br>
(colonnes manquantes: %s, colonnes invalides: %s)""" (colonnes manquantes: %s, colonnes invalides: %s)"""
% (len(TITLES), len(fs), list(cols.keys()), unknown) % (len(TITLES), len(fs), list(cols.keys()), unknown)
) )

View File

@ -517,18 +517,18 @@ def _make_table_notes(
hh += "s" hh += "s"
hh += ", %d en attente." % (nb_att) hh += ", %d en attente." % (nb_att)
pdf_title = "<br/> BORDEREAU DE SIGNATURES" pdf_title = "<br> BORDEREAU DE SIGNATURES"
pdf_title += "<br/><br/>%(titre)s" % sem pdf_title += "<br><br>%(titre)s" % sem
pdf_title += "<br/>(%(mois_debut)s - %(mois_fin)s)" % sem pdf_title += "<br>(%(mois_debut)s - %(mois_fin)s)" % sem
pdf_title += " semestre %s %s" % ( pdf_title += " semestre %s %s" % (
sem["semestre_id"], sem["semestre_id"],
sem.get("modalite", ""), sem.get("modalite", ""),
) )
pdf_title += f"<br/>Notes du module {module.code} - {module.titre}" pdf_title += f"<br>Notes du module {module.code} - {module.titre}"
pdf_title += "<br/>Evaluation : %(description)s " % e pdf_title += "<br>Evaluation : %(description)s " % e
if len(e["jour"]) > 0: if len(e["jour"]) > 0:
pdf_title += " (%(jour)s)" % e pdf_title += " (%(jour)s)" % e
pdf_title += "(noté sur %(note_max)s )<br/><br/>" % e pdf_title += "(noté sur %(note_max)s )<br><br>" % e
else: else:
hh = " %s, %s (%d étudiants)" % ( hh = " %s, %s (%d étudiants)" % (
E["description"], E["description"],
@ -623,11 +623,11 @@ def _make_table_notes(
commentkeys.sort(key=lambda x: int(x[1])) commentkeys.sort(key=lambda x: int(x[1]))
for (comment, key) in commentkeys: for (comment, key) in commentkeys:
C.append( C.append(
'<span class="colcomment">(%s)</span> <em>%s</em><br/>' % (key, comment) '<span class="colcomment">(%s)</span> <em>%s</em><br>' % (key, comment)
) )
if commentkeys: if commentkeys:
C.append( C.append(
'<span><a class=stdlink" href="evaluation_list_operations?evaluation_id=%s">Gérer les opérations</a></span><br/>' '<span><a class=stdlink" href="evaluation_list_operations?evaluation_id=%s">Gérer les opérations</a></span><br>'
% E["evaluation_id"] % E["evaluation_id"]
) )
eval_info = "xxx" eval_info = "xxx"

View File

@ -229,7 +229,7 @@ def js_coords_lycees(etuds_by_lycee):
lyc = etuds_by_lycee[codelycee][0] lyc = etuds_by_lycee[codelycee][0]
if not lyc.get("positionlycee", False): if not lyc.get("positionlycee", False):
continue continue
listeetuds = "<br/>%d étudiants: " % len( listeetuds = "<br>%d étudiants: " % len(
etuds_by_lycee[codelycee] etuds_by_lycee[codelycee]
) + ", ".join( ) + ", ".join(
[ [

View File

@ -152,7 +152,7 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
{ _make_menu(partitions, "Ajouter", "true") } { _make_menu(partitions, "Ajouter", "true") }
{ _make_menu(partitions, "Enlever", "false")} { _make_menu(partitions, "Enlever", "false")}
</tr></table> </tr></table>
<p><br/></p> <p><br></p>
<table class="sortable" id="mi_table"> <table class="sortable" id="mi_table">
<tr> <tr>
<th>Nom</th> <th>Nom</th>

View File

@ -189,7 +189,7 @@ def ficheEtud(etudid=None):
else: else:
info["paysdomicile"] = "" info["paysdomicile"] = ""
if info["telephone"] or info["telephonemobile"]: if info["telephone"] or info["telephonemobile"]:
info["telephones"] = "<br/>%s &nbsp;&nbsp; %s" % ( info["telephones"] = "<br>%s &nbsp;&nbsp; %s" % (
info["telephonestr"], info["telephonestr"],
info["telephonemobilestr"], info["telephonemobilestr"],
) )
@ -506,9 +506,9 @@ def ficheEtud(etudid=None):
<b>Ajouter une annotation sur %(nomprenom)s: </b> <b>Ajouter une annotation sur %(nomprenom)s: </b>
<table><tr> <table><tr>
<tr><td><textarea name="comment" rows="4" cols="50" value=""></textarea> <tr><td><textarea name="comment" rows="4" cols="50" value=""></textarea>
<br/><font size=-1> <br><font size=-1>
<i>Ces annotations sont lisibles par tous les enseignants et le secrétariat.</i> <i>Ces annotations sont lisibles par tous les enseignants et le secrétariat.</i>
<br/> <br>
<i>L'annotation commençant par "PE:" est un avis de poursuite d'études.</i> <i>L'annotation commençant par "PE:" est un avis de poursuite d'études.</i>
</font> </font>
</td></tr> </td></tr>

View File

@ -338,7 +338,7 @@ class PlacementRunner:
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE) return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
def _production_pdf(self): def _production_pdf(self):
pdf_title = "<br/>".join(self.desceval) pdf_title = "<br>".join(self.desceval)
pdf_title += ( pdf_title += (
"\nDate : %(jour)s - Horaire : %(heure_debut)s à %(heure_fin)s" "\nDate : %(jour)s - Horaire : %(heure_debut)s à %(heure_fin)s"
% self.eval_data % self.eval_data

View File

@ -43,7 +43,7 @@ Au niveau du code interface, on défini pour chaque préférence:
- initvalue : valeur initiale - initvalue : valeur initiale
- explanation: explication en français - explanation: explication en français
- size: longueur du chap texte - size: longueur du chap texte
- input_type: textarea,separator,... type de widget TrivialFormulator a utiliser - input_type: textarea, separator, ... type de widget TrivialFormulator a utiliser
- rows, rols: geometrie des textareas - rows, rols: geometrie des textareas
- category: misc ou bul ou page_bulletins ou abs ou general ou portal - category: misc ou bul ou page_bulletins ou abs ou general ou portal
ou pdf ou pvpdf ou ... ou pdf ou pvpdf ou ...
@ -202,7 +202,7 @@ _INSTALLED_FONTS = ", ".join(sco_pdf.get_available_font_names())
PREF_CATEGORIES = ( PREF_CATEGORIES = (
# sur page "Paramètres" # sur page "Paramètres"
("general", {}), ("general", {"title": ""}), # voir paramètre titlr de TrivialFormulator
("misc", {"title": "Divers"}), ("misc", {"title": "Divers"}),
("apc", {"title": "BUT et Approches par Compétences"}), ("apc", {"title": "BUT et Approches par Compétences"}),
("abs", {"title": "Suivi des absences", "related": ("bul",)}), ("abs", {"title": "Suivi des absences", "related": ("bul",)}),
@ -1008,7 +1008,7 @@ class BasePreferences(object):
( (
"PV_LETTER_DIPLOMA_SIGNATURE", "PV_LETTER_DIPLOMA_SIGNATURE",
{ {
"initvalue": """Le %(DirectorTitle)s, <br/>%(DirectorName)s""", "initvalue": """Le %(DirectorTitle)s, <br>%(DirectorName)s""",
"title": """Signature des lettres individuelles de diplôme""", "title": """Signature des lettres individuelles de diplôme""",
"explanation": """%(DirectorName)s et %(DirectorTitle)s remplacés""", "explanation": """%(DirectorName)s et %(DirectorTitle)s remplacés""",
"input_type": "textarea", "input_type": "textarea",
@ -1020,8 +1020,8 @@ class BasePreferences(object):
( (
"PV_LETTER_PASSAGE_SIGNATURE", "PV_LETTER_PASSAGE_SIGNATURE",
{ {
"initvalue": """Pour le Directeur de l'IUT<br/> "initvalue": """Pour le Directeur de l'IUT<br>
et par délégation<br/> et par délégation<br>
Le Chef du département""", Le Chef du département""",
"title": """Signature des lettres individuelles de passage d'un semestre à l'autre""", "title": """Signature des lettres individuelles de passage d'un semestre à l'autre""",
"explanation": """%(DirectorName)s et %(DirectorTitle)s remplacés""", "explanation": """%(DirectorName)s et %(DirectorTitle)s remplacés""",
@ -1056,7 +1056,7 @@ class BasePreferences(object):
<para leftindent="%(pv_htab1)s">%(codepostaldomicile)s %(villedomicile)s</para> <para leftindent="%(pv_htab1)s">%(codepostaldomicile)s %(villedomicile)s</para>
<para spaceBefore="25mm" fontSize="14" alignment="center"> <para spaceBefore="25mm" fontSize="14" alignment="center">
<b>Jury de %(type_jury)s <br/> %(titre_formation)s</b> <b>Jury de %(type_jury)s <br> %(titre_formation)s</b>
</para> </para>
<para spaceBefore="10mm" fontSize="14" leftindent="0"> <para spaceBefore="10mm" fontSize="14" leftindent="0">
@ -1499,7 +1499,7 @@ class BasePreferences(object):
"bul_pdf_sig_left", "bul_pdf_sig_left",
{ {
"initvalue": """<para>La direction des études "initvalue": """<para>La direction des études
<br/> <br>
%(responsable)s %(responsable)s
</para> </para>
""", """,
@ -1515,7 +1515,7 @@ class BasePreferences(object):
"bul_pdf_sig_right", "bul_pdf_sig_right",
{ {
"initvalue": """<para>Le chef de département "initvalue": """<para>Le chef de département
<br/> <br>
%(ChiefDeptName)s %(ChiefDeptName)s
</para> </para>
""", """,
@ -1891,7 +1891,7 @@ class BasePreferences(object):
"explanation": """si cette adresse est indiquée, TOUS les mails "explanation": """si cette adresse est indiquée, TOUS les mails
envoyés par ScoDoc de ce département vont aller vers elle envoyés par ScoDoc de ce département vont aller vers elle
AU LIEU DE LEUR DESTINATION NORMALE !""", AU LIEU DE LEUR DESTINATION NORMALE !""",
"size": 30, "size": 60,
"category": "debug", "category": "debug",
"only_global": True, "only_global": True,
}, },
@ -1935,7 +1935,7 @@ class BasePreferences(object):
value = _get_pref_default_value_from_config(name, pref[1]) value = _get_pref_default_value_from_config(name, pref[1])
self.default[name] = value self.default[name] = value
self.prefs[None][name] = value self.prefs[None][name] = value
log("creating missing preference for %s=%s" % (name, value)) log(f"creating missing preference for {name}={value}")
# add to db table # add to db table
self._editor.create( self._editor.create(
cnx, {"dept_id": self.dept_id, "name": name, "value": value} cnx, {"dept_id": self.dept_id, "name": name, "value": value}
@ -1999,7 +1999,7 @@ class BasePreferences(object):
if not pdb: if not pdb:
# crée préférence # crée préférence
log("create pref sem=%s %s=%s" % (formsemestre_id, name, value)) log(f"create pref sem={formsemestre_id} {name}={value}")
self._editor.create( self._editor.create(
cnx, cnx,
{ {
@ -2036,7 +2036,7 @@ class BasePreferences(object):
def set(self, formsemestre_id, name, value): def set(self, formsemestre_id, name, value):
if not name or name[0] == "_" or name not in self.prefs_name: if not name or name[0] == "_" or name not in self.prefs_name:
raise ValueError("invalid preference name: %s" % name) raise ValueError(f"invalid preference name: {name}")
if formsemestre_id and name in self.prefs_only_global: if formsemestre_id and name in self.prefs_only_global:
raise ValueError("pref %s is always defined globaly") raise ValueError("pref %s is always defined globaly")
if not formsemestre_id in self.prefs: if not formsemestre_id in self.prefs:
@ -2055,7 +2055,7 @@ class BasePreferences(object):
cnx, args={"formsemestre_id": formsemestre_id, "name": name} cnx, args={"formsemestre_id": formsemestre_id, "name": name}
) )
if pdb: if pdb:
log("deleting pref sem=%s %s" % (formsemestre_id, name)) log(f"deleting pref sem={formsemestre_id} {name}")
assert pdb[0]["dept_id"] == self.dept_id assert pdb[0]["dept_id"] == self.dept_id
self._editor.delete(cnx, pdb[0]["pref_id"]) self._editor.delete(cnx, pdb[0]["pref_id"])
sco_cache.invalidate_formsemestre() # > modif preferences sco_cache.invalidate_formsemestre() # > modif preferences
@ -2067,14 +2067,18 @@ class BasePreferences(object):
self.load() self.load()
H = [ H = [
html_sco_header.sco_header(page_title="Préférences"), html_sco_header.sco_header(page_title="Préférences"),
"<h2>Préférences globales pour %s</h2>" % scu.ScoURL(), f"<h2>Préférences globales pour {scu.ScoURL()}</h2>",
# f"""<p><a href="{url_for("scodoc.configure_logos", scodoc_dept=g.scodoc_dept) # f"""<p><a href="{url_for("scodoc.configure_logos", scodoc_dept=g.scodoc_dept)
# }">modification des logos du département (pour documents pdf)</a></p>""" # }">modification des logos du département (pour documents pdf)</a></p>"""
# if current_user.is_administrator() # if current_user.is_administrator()
# else "", # else "",
"""<p class="help">Ces paramètres s'appliquent par défaut à tous les semestres, sauf si ceux-ci définissent des valeurs spécifiques.</p> """<p class="help">Ces paramètres s'appliquent par défaut à tous les semestres,
<p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p> sauf si ceux-ci définissent des valeurs spécifiques.
""", </p>
<p class="msg">Attention: cliquez sur "Enregistrer les modifications"
en bas de page pour appliquer vos changements !
</p>
""",
] ]
form = self.build_tf_form() form = self.build_tf_form()
tf = TrivialFormulator( tf = TrivialFormulator(
@ -2083,6 +2087,9 @@ class BasePreferences(object):
form, form,
initvalues=self.prefs[None], initvalues=self.prefs[None],
submitlabel="Enregistrer les modifications", submitlabel="Enregistrer les modifications",
title="Département et institution",
before_table="<details><summary>{title}</summary>",
after_table="</details>",
) )
if tf[0] == 0: if tf[0] == 0:
return "\n".join(H) + tf[1] + html_sco_header.sco_footer() return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
@ -2094,7 +2101,7 @@ class BasePreferences(object):
self.save() self.save()
return flask.redirect(scu.ScoURL() + "?head_message=Préférences modifiées") return flask.redirect(scu.ScoURL() + "?head_message=Préférences modifiées")
def build_tf_form(self, categories=[], formsemestre_id=None): def build_tf_form(self, categories: list[str] = None, formsemestre_id: int = None):
"""Build list of elements for TrivialFormulator. """Build list of elements for TrivialFormulator.
If formsemestre_id is not specified, edit global prefs. If formsemestre_id is not specified, edit global prefs.
""" """
@ -2119,7 +2126,7 @@ class BasePreferences(object):
onclick="set_global_pref(this, '{pref_name}');" onclick="set_global_pref(this, '{pref_name}');"
>utiliser paramètre global</span>""" >utiliser paramètre global</span>"""
if formsemestre_id and self.is_global(formsemestre_id, pref_name): if formsemestre_id and self.is_global(formsemestre_id, pref_name):
# valeur actuelle globale (ou vient d'etre supprimee localement): # valeur actuelle globale (ou vient d'etre supprimée localement):
# montre la valeur et menus pour la rendre locale # montre la valeur et menus pour la rendre locale
descr["readonly"] = True descr["readonly"] = True
menu_global = f"""<select class="tf-selglobal" menu_global = f"""<select class="tf-selglobal"
@ -2138,8 +2145,11 @@ class BasePreferences(object):
if title: if title:
form.append( form.append(
( (
f"sep_{cat}", f"table_{cat}",
{"input_type": "separator", "title": f"<h3>{title}</h3>"}, {
"input_type": "table_separator",
"title": f"{title}",
},
) )
) )
subtitle = cat_descr.get("subtitle", None) subtitle = cat_descr.get("subtitle", None)
@ -2246,6 +2256,9 @@ function set_global_pref(el, pref_name) {
initvalues=self, initvalues=self,
cssclass="sco_pref", cssclass="sco_pref",
submitlabel="Enregistrer les modifications", submitlabel="Enregistrer les modifications",
title="Département et institution",
before_table="<details><summary>{title}</summary>",
after_table="</details>",
) )
dest_url = ( dest_url = (
scu.NotesURL() scu.NotesURL()

View File

@ -340,11 +340,11 @@ class PVTemplate(CourrierIndividuelTemplate):
def _simulate_br(paragraph_txt: str, para="<para>") -> str: def _simulate_br(paragraph_txt: str, para="<para>") -> str:
"""Reportlab bug turnaround (could be removed in a future version). """Reportlab bug turnaround (could be removed in a future version).
p is a string with Reportlab intra-paragraph XML tags. p is a string with Reportlab intra-paragraph XML tags.
Replaces <br/> (currently ignored by Reportlab) by </para><para> Replaces <br> (currently ignored by Reportlab) by </para><para>
Also replaces <br> by <br/> Also replaces <br> by <br>
""" """
return ("</para>" + para).join( return ("</para>" + para).join(
re.split(r"<.*?br.*?/>", paragraph_txt.replace("<br>", "<br/>")) re.split(r"<.*?br.*?/>", paragraph_txt.replace("<br>", "<br>"))
) )
@ -515,7 +515,7 @@ def pdf_lettre_individuelle(sem, decision, etud: Identite, params, signature=Non
params.update(decision["identite"]) params.update(decision["identite"])
# fix domicile # fix domicile
if params["domicile"]: if params["domicile"]:
params["domicile"] = params["domicile"].replace("\\n", "<br/>") params["domicile"] = params["domicile"].replace("\\n", "<br>")
# UE capitalisées: # UE capitalisées:
if decision["decisions_ue"] and decision["decisions_ue_descr"]: if decision["decisions_ue"] and decision["decisions_ue_descr"]:
@ -649,8 +649,8 @@ def add_apc_infos(formsemestre: FormSemestre, params: dict, decision: dict):
params["decision_sem_descr"] = decision_annee.get("code") or "" params["decision_sem_descr"] = decision_annee.get("code") or ""
params[ params[
"decision_ue_txt" "decision_ue_txt"
] = f"""{params["decision_ue_txt"]}<br/> ] = f"""{params["decision_ue_txt"]}<br>
<b>Niveaux de compétences:</b><br/> {decision.get("descr_decisions_rcue") or ""} <b>Niveaux de compétences:</b><br> {decision.get("descr_decisions_rcue") or ""}
""" """

View File

@ -384,7 +384,7 @@ def formsemestre_report_counts(
else: else:
checked = "" checked = ""
F.append( F.append(
'<br/><input type="checkbox" name="only_primo" onchange="document.f.submit()" %s>Restreindre aux primo-entrants</input>' '<br><input type="checkbox" name="only_primo" onchange="document.f.submit()" %s>Restreindre aux primo-entrants</input>'
% checked % checked
) )
F.append( F.append(
@ -928,7 +928,7 @@ def _gen_form_selectetuds(
else: else:
checked = "" checked = ""
F.append( F.append(
'<br/><input type="checkbox" name="only_primo" onchange="javascript: submit(this);" %s/>Restreindre aux primo-entrants' '<br><input type="checkbox" name="only_primo" onchange="javascript: submit(this);" %s/>Restreindre aux primo-entrants'
% checked % checked
) )
F.append( F.append(

View File

@ -237,7 +237,7 @@ def do_evaluation_upload_xls():
ni += 1 ni += 1
except: except:
diag.append( diag.append(
'Erreur: Ligne invalide ! (erreur ligne %d)<br/>"%s"' 'Erreur: Ligne invalide ! (erreur ligne %d)<br>"%s"'
% (ni, str(lines[ni])) % (ni, str(lines[ni]))
) )
raise InvalidNoteValue() raise InvalidNoteValue()

View File

@ -139,7 +139,7 @@ class SemSet(dict):
# Construction du ou des lien(s) vers le semestre # Construction du ou des lien(s) vers le semestre
pattern = '<a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titreannee)s</a>' pattern = '<a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titreannee)s</a>'
self["semlinks"] = [(pattern % sem) for sem in self.sems] self["semlinks"] = [(pattern % sem) for sem in self.sems]
self["semtitles_str"] = "<br/>".join(self["semlinks"]) self["semtitles_str"] = "<br>".join(self["semlinks"])
def fill_formsemestres(self): def fill_formsemestres(self):
for sem in self.sems: for sem in self.sems:
@ -290,16 +290,15 @@ class SemSet(dict):
% (self["semset_id"], sem["formsemestre_id"]) % (self["semset_id"], sem["formsemestre_id"])
) )
H.append( H.append(
"<br/>Etapes: <tt>%(etapes_apo_str)s</tt>, %(nbinscrits)s inscrits" "<br>Etapes: <tt>%(etapes_apo_str)s</tt>, %(nbinscrits)s inscrits" % sem
% sem
) )
H.append("<br/>Elément Apogée année: ") H.append("<br>Elément Apogée année: ")
if sem["elt_annee_apo"]: if sem["elt_annee_apo"]:
H.append("<tt>%(elt_annee_apo)s</tt>" % sem) H.append("<tt>%(elt_annee_apo)s</tt>" % sem)
else: else:
H.append('<span style="color: red;">manquant</span>') H.append('<span style="color: red;">manquant</span>')
H.append("<br/>Elément Apogée semestre: ") H.append("<br>Elément Apogée semestre: ")
if sem["elt_sem_apo"]: if sem["elt_sem_apo"]:
H.append("<tt>%(elt_sem_apo)s</tt>" % sem) H.append("<tt>%(elt_sem_apo)s</tt>" % sem)
else: else:

View File

@ -463,7 +463,7 @@ def list_synch(sem, anneeapogee=None):
"id": "etuds_noninscrits", "id": "etuds_noninscrits",
"title": "Étudiants non inscrits dans ce semestre", "title": "Étudiants non inscrits dans ce semestre",
"help": """Ces étudiants sont déjà connus par ScoDoc, sont inscrits dans cette étape Apogée mais ne sont pas inscrits à ce semestre ScoDoc. Cochez les étudiants à inscrire.""", "help": """Ces étudiants sont déjà connus par ScoDoc, sont inscrits dans cette étape Apogée mais ne sont pas inscrits à ce semestre ScoDoc. Cochez les étudiants à inscrire.""",
"comment": """ dans ScoDoc et Apogée, <br/>mais pas inscrits "comment": """ dans ScoDoc et Apogée, <br>mais pas inscrits
dans ce semestre""", dans ce semestre""",
"title_target": "", "title_target": "",
"with_checkbox": True, "with_checkbox": True,

View File

@ -506,7 +506,7 @@ def photos_import_files_form(group_ids=()):
de chaque étudiant (menu "Étudiant" / "Changer la photo").</b> de chaque étudiant (menu "Étudiant" / "Changer la photo").</b>
</p> </p>
<p class="help">Cette page permet de charger en une seule fois les photos <p class="help">Cette page permet de charger en une seule fois les photos
de plusieurs étudiants.<br/> de plusieurs étudiants.<br>
Il faut d'abord remplir une feuille excel donnant les noms Il faut d'abord remplir une feuille excel donnant les noms
des fichiers images (une image par étudiant). des fichiers images (une image par étudiant).
</p> </p>

View File

@ -238,7 +238,7 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
javascripts=["js/sco_ue_external.js"], javascripts=["js/sco_ue_external.js"],
), ),
"""<p class="help">Cette page permet d'indiquer que l'étudiant a suivi une UE """<p class="help">Cette page permet d'indiquer que l'étudiant a suivi une UE
dans un autre établissement et qu'elle doit être intégrée dans le semestre courant.<br/> dans un autre établissement et qu'elle doit être intégrée dans le semestre courant.<br>
La note (/20) obtenue par l'étudiant doit toujours être spécifiée.</br> La note (/20) obtenue par l'étudiant doit toujours être spécifiée.</br>
On peut choisir une UE externe existante (dans le menu), ou bien en créer une, qui sera On peut choisir une UE externe existante (dans le menu), ou bien en créer une, qui sera
alors ajoutée à la formation. alors ajoutée à la formation.

View File

@ -290,7 +290,7 @@ def check_modif_user(
(si ok est faux, l'utilisateur peut quand même forcer la creation) (si ok est faux, l'utilisateur peut quand même forcer la creation)
- msg: message warning à presenter à l'utilisateur - msg: message warning à presenter à l'utilisateur
""" """
MSG_OPT = """<br/>Attention: (vous pouvez forcer l'opération en cochant "<em>Ignorer les avertissements</em>" en bas de page)""" MSG_OPT = """<br>Attention: (vous pouvez forcer l'opération en cochant "<em>Ignorer les avertissements</em>" en bas de page)"""
# ce login existe ? # ce login existe ?
user = _user_list(user_name) user = _user_list(user_name)
if edit and not user: # safety net, le user_name ne devrait pas changer if edit and not user: # safety net, le user_name ne devrait pas changer

View File

@ -2863,10 +2863,13 @@ select.tf-selglobal {
} }
td.tf-fieldlabel { td.tf-fieldlabel {
/* font-weight: bold; */
vertical-align: top; vertical-align: top;
} }
td.tf-field {
max-width: 800px;
}
.tf-comment { .tf-comment {
font-size: 80%; font-size: 80%;
font-style: italic; font-style: italic;
@ -2876,6 +2879,12 @@ td.tf-fieldlabel {
font-style: italic; font-style: italic;
} }
#tf details summary {
font-size: 130%;
margin-top: 6px;
margin-bottom: 6px;
}
.radio_green { .radio_green {
background-color: green; background-color: green;
} }

View File

@ -699,7 +699,7 @@ def _gen_form_saisie_groupe(
} }
</script> </script>
<div id="AjaxDiv"></div> <div id="AjaxDiv"></div>
<br/> <br>
<table rules="cols" frame="box" class="abs_form_table"> <table rules="cols" frame="box" class="abs_form_table">
<tr><th class="formabs_contetud">%d étudiants</th> <tr><th class="formabs_contetud">%d étudiants</th>
""" """
@ -990,7 +990,7 @@ def EtatAbsencesGr(
), ),
html_title=html_sco_header.html_sem_header(title, with_page_header=False) html_title=html_sco_header.html_sem_header(title, with_page_header=False)
+ form_date, + form_date,
# "<p>Période du %s au %s (nombre de <b>demi-journées</b>)<br/>" % (debut, fin), # "<p>Période du %s au %s (nombre de <b>demi-journées</b>)<br>" % (debut, fin),
base_url="%s&formsemestre_id=%s&debut=%s&fin=%s" base_url="%s&formsemestre_id=%s&debut=%s&fin=%s"
% (groups_infos.base_url, formsemestre_id, debut, fin), % (groups_infos.base_url, formsemestre_id, debut, fin),
filename="etat_abs_" filename="etat_abs_"
@ -1003,7 +1003,7 @@ def EtatAbsencesGr(
Justifs non utilisés: nombre de demi-journées avec justificatif mais sans absences relevées. Justifs non utilisés: nombre de demi-journées avec justificatif mais sans absences relevées.
</p> </p>
<p class="help"> <p class="help">
Cliquez sur un nom pour afficher le calendrier des absences<br/> Cliquez sur un nom pour afficher le calendrier des absences<br>
ou entrez une date pour visualiser les absents un jour donné&nbsp;: ou entrez une date pour visualiser les absents un jour donné&nbsp;:
</p> </p>
<div style="margin-bottom: 10px;"> <div style="margin-bottom: 10px;">
@ -1422,7 +1422,7 @@ def process_billet_absence_form(billet_id):
H.append(tab.html()) H.append(tab.html())
if billet.justified: if billet.justified:
H.append( H.append(
"""<p>L'étudiant pense pouvoir justifier cette absence.<br/> """<p>L'étudiant pense pouvoir justifier cette absence.<br>
<em>Vérifiez le justificatif avant d'enregistrer.</em></p>""" <em>Vérifiez le justificatif avant d'enregistrer.</em></p>"""
) )
F = f"""<p><a class="stdlink" href="{ F = f"""<p><a class="stdlink" href="{
@ -1437,7 +1437,7 @@ def process_billet_absence_form(billet_id):
</p> </p>
""" """
return "\n".join(H) + "<br/>" + tf[1] + F + html_sco_header.sco_footer() return "\n".join(H) + "<br>" + tf[1] + F + html_sco_header.sco_footer()
elif tf[0] == -1: elif tf[0] == -1:
return flask.redirect(scu.ScoURL()) return flask.redirect(scu.ScoURL())
else: else:

View File

@ -852,7 +852,7 @@ def formsemestre_change_lock(formsemestre_id, dialog_confirmed=False):
helpmsg="""Les notes d'un semestre verrouillé ne peuvent plus être modifiées. helpmsg="""Les notes d'un semestre verrouillé ne peuvent plus être modifiées.
Un semestre verrouillé peut cependant être déverrouillé facilement à tout moment Un semestre verrouillé peut cependant être déverrouillé facilement à tout moment
(par son responsable ou un administrateur). (par son responsable ou un administrateur).
<br/> <br>
Le programme d'une formation qui a un semestre verrouillé ne peut plus être modifié. Le programme d'une formation qui a un semestre verrouillé ne peut plus être modifié.
""", """,
dest_url="", dest_url="",
@ -3045,12 +3045,12 @@ def check_sem_integrity(formsemestre_id, fix=False):
if bad_ue: if bad_ue:
H += [ H += [
"<h2>Modules d'une autre formation que leur UE:</h2>", "<h2>Modules d'une autre formation que leur UE:</h2>",
"<br/>".join([str(x) for x in bad_ue]), "<br>".join([str(x) for x in bad_ue]),
] ]
if bad_sem: if bad_sem:
H += [ H += [
"<h2>Module du semestre dans une autre formation:</h2>", "<h2>Module du semestre dans une autre formation:</h2>",
"<br/>".join([str(x) for x in bad_sem]), "<br>".join([str(x) for x in bad_sem]),
] ]
if not bad_ue and not bad_sem: if not bad_ue and not bad_sem:
H.append("<p>Aucun problème à signaler !</p>") H.append("<p>Aucun problème à signaler !</p>")
@ -3106,7 +3106,7 @@ def check_form_integrity(formation_id, fix=False):
if mod["formation_id"] != formation_id: if mod["formation_id"] != formation_id:
bad.append(mod) bad.append(mod)
if bad: if bad:
txth = "<br/>".join([str(x) for x in bad]) txth = "<br>".join([str(x) for x in bad])
txt = "\n".join([str(x) for x in bad]) txt = "\n".join([str(x) for x in bad])
log("check_form_integrity: formation_id=%s\ninconsistencies:" % formation_id) log("check_form_integrity: formation_id=%s\ninconsistencies:" % formation_id)
log(txt) log(txt)
@ -3162,7 +3162,7 @@ def check_formsemestre_integrity(formsemestre_id):
diag = ["OK"] diag = ["OK"]
log("ok") log("ok")
return ( return (
html_sco_header.sco_header() + "<br/>".join(diag) + html_sco_header.sco_footer() html_sco_header.sco_header() + "<br>".join(diag) + html_sco_header.sco_footer()
) )

View File

@ -2141,9 +2141,9 @@ def form_students_import_infos_admissions(formsemestre_id=None):
Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés, Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés,
les autres lignes de la feuille seront ignorées. Et seules les colonnes intéressant ScoDoc les autres lignes de la feuille seront ignorées. Et seules les colonnes intéressant ScoDoc
seront importées: il est inutile d'éliminer les autres. seront importées: il est inutile d'éliminer les autres.
<br/> <br>
<em>Seules les données "admission" seront modifiées (et pas l'identité de l'étudiant).</em> <em>Seules les données "admission" seront modifiées (et pas l'identité de l'étudiant).</em>
<br/> <br>
<em>Les colonnes "nom" et "prenom" sont requises, ou bien une colonne "etudid".</em> <em>Les colonnes "nom" et "prenom" sont requises, ou bien une colonne "etudid".</em>
</p> </p>
<p> <p>
@ -2240,7 +2240,7 @@ def formsemestre_import_etud_admission(formsemestre_id, import_email=True):
H.append("<h3>Adresses mails modifiées:</h3>") H.append("<h3>Adresses mails modifiées:</h3>")
for (info, new_mail) in changed_mails: for (info, new_mail) in changed_mails:
H.append( H.append(
"%s: <tt>%s</tt> devient <tt>%s</tt><br/>" "%s: <tt>%s</tt> devient <tt>%s</tt><br>"
% (info["nom"], info["email"], new_mail) % (info["nom"], info["email"], new_mail)
) )
return "\n".join(H) + html_sco_header.sco_footer() return "\n".join(H) + html_sco_header.sco_footer()

View File

@ -936,7 +936,7 @@ def change_password(user_name, password, password2):
if not is_valid_password(password): if not is_valid_password(password):
H.append( H.append(
f"""<p><b>ce mot de passe n'est pas assez compliqué !</b> f"""<p><b>ce mot de passe n'est pas assez compliqué !</b>
<br/>(oui, il faut un mot de passe vraiment compliqué !) <br>(oui, il faut un mot de passe vraiment compliqué !)
</p> </p>
<p><a href="{dest_url}" class="stdlink">Recommencer</a></p> <p><a href="{dest_url}" class="stdlink">Recommencer</a></p>
""" """