forked from ScoDoc/ScoDoc
Edition préférences: sections dépliables. + Code cleaning.
This commit is contained in:
parent
4fb296bbfa
commit
c28dcf677a
|
@ -1,139 +1,139 @@
|
|||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""Jury BUT: table synthèse résultats semestre / PV
|
||||
"""
|
||||
from flask import g, request, url_for
|
||||
|
||||
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
|
||||
|
||||
from app import log
|
||||
from app.but import jury_but
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
def _descr_cursus_but(etud: Identite) -> str:
|
||||
"description de la liste des semestres BUT suivis"
|
||||
# prend simplement tous les semestre de type APC, ce qui sera faux si
|
||||
# l'étudiant change de spécialité au sein du même département
|
||||
# (ce qui ne peut normalement pas se produire)
|
||||
indices = sorted(
|
||||
[
|
||||
ins.formsemestre.semestre_id
|
||||
if ins.formsemestre.semestre_id is not None
|
||||
else -1
|
||||
for ins in etud.formsemestre_inscriptions
|
||||
if ins.formsemestre.formation.is_apc()
|
||||
]
|
||||
)
|
||||
return ", ".join(f"S{indice}" for indice in indices)
|
||||
|
||||
|
||||
def pvjury_table_but(formsemestre_id: int, format="html"):
|
||||
"""Page récapitulant les décisions de jury BUT
|
||||
formsemestre peut être pair ou impair
|
||||
"""
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
assert formsemestre.formation.is_apc()
|
||||
title = "Procès-verbal de jury BUT annuel"
|
||||
|
||||
if format == "html":
|
||||
line_sep = "<br/>"
|
||||
else:
|
||||
line_sep = "\n"
|
||||
# remplace pour le BUT la fonction sco_pvjury.pvjury_table
|
||||
annee_but = (formsemestre.semestre_id + 1) // 2
|
||||
titles = {
|
||||
"nom": "Nom",
|
||||
"cursus": "Cursus",
|
||||
"ues": "UE validées",
|
||||
"niveaux": "Niveaux de compétences validés",
|
||||
"decision_but": f"Décision BUT{annee_but}",
|
||||
"diplome": "Résultat au diplôme",
|
||||
"devenir": "Devenir",
|
||||
"observations": "Observations",
|
||||
}
|
||||
rows = []
|
||||
for etudid in formsemestre.etuds_inscriptions:
|
||||
etud: Identite = Identite.query.get(etudid)
|
||||
try:
|
||||
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
||||
if deca.annee_but != annee_but: # wtf ?
|
||||
log(
|
||||
f"pvjury_table_but: inconsistent annee_but {deca.annee_but} != {annee_but}"
|
||||
)
|
||||
continue
|
||||
except ScoValueError:
|
||||
deca = None
|
||||
row = {
|
||||
"nom": etud.etat_civil_pv(line_sep=line_sep),
|
||||
"_nom_order": etud.sort_key,
|
||||
"_nom_target_attrs": f'class="etudinfo" id="{etud.id}"',
|
||||
"_nom_td_attrs": f'id="{etud.id}" class="etudinfo"',
|
||||
"_nom_target": url_for(
|
||||
"scolar.ficheEtud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud.id,
|
||||
),
|
||||
"cursus": _descr_cursus_but(etud),
|
||||
"ues": deca.descr_ues_validation(line_sep=line_sep) if deca else "-",
|
||||
"niveaux": deca.descr_niveaux_validation(line_sep=line_sep)
|
||||
if deca
|
||||
else "-",
|
||||
"decision_but": deca.code_valide if deca else "",
|
||||
"devenir": ", ".join([f"S{i}" for i in deca.get_autorisations_passage()])
|
||||
if deca
|
||||
else "",
|
||||
}
|
||||
|
||||
rows.append(row)
|
||||
|
||||
rows.sort(key=lambda x: x["_nom_order"])
|
||||
|
||||
# Style excel... passages à la ligne sur \n
|
||||
xls_style_base = sco_excel.excel_make_style()
|
||||
xls_style_base["alignment"] = Alignment(wrapText=True, vertical="top")
|
||||
|
||||
tab = GenTable(
|
||||
base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}",
|
||||
caption=title,
|
||||
columns_ids=titles.keys(),
|
||||
html_caption=title,
|
||||
html_class="pvjury_table_but table_leftalign",
|
||||
html_title=f"""<div style="margin-bottom: 8px;"><span style="font-size: 120%; font-weight: bold;">{title}</span>
|
||||
<span style="padding-left: 20px;">
|
||||
<a href="{url_for("notes.pvjury_table_but",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, format="xlsx")}"
|
||||
class="stdlink">version excel</a></span></div>
|
||||
|
||||
""",
|
||||
html_with_td_classes=True,
|
||||
origin=f"Généré par {scu.sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
||||
page_title=title,
|
||||
pdf_title=title,
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
rows=rows,
|
||||
table_id="formation_table_recap",
|
||||
titles=titles,
|
||||
xls_columns_width={
|
||||
"nom": 32,
|
||||
"cursus": 12,
|
||||
"ues": 32,
|
||||
"niveaux": 32,
|
||||
"decision_but": 14,
|
||||
"diplome": 17,
|
||||
"devenir": 8,
|
||||
"observations": 12,
|
||||
},
|
||||
xls_style_base=xls_style_base,
|
||||
)
|
||||
return tab.make_page(format=format, javascripts=["js/etud_info.js"], init_qtip=True)
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""Jury BUT: table synthèse résultats semestre / PV
|
||||
"""
|
||||
from flask import g, request, url_for
|
||||
|
||||
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
|
||||
|
||||
from app import log
|
||||
from app.but import jury_but
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
def _descr_cursus_but(etud: Identite) -> str:
|
||||
"description de la liste des semestres BUT suivis"
|
||||
# prend simplement tous les semestre de type APC, ce qui sera faux si
|
||||
# l'étudiant change de spécialité au sein du même département
|
||||
# (ce qui ne peut normalement pas se produire)
|
||||
indices = sorted(
|
||||
[
|
||||
ins.formsemestre.semestre_id
|
||||
if ins.formsemestre.semestre_id is not None
|
||||
else -1
|
||||
for ins in etud.formsemestre_inscriptions
|
||||
if ins.formsemestre.formation.is_apc()
|
||||
]
|
||||
)
|
||||
return ", ".join(f"S{indice}" for indice in indices)
|
||||
|
||||
|
||||
def pvjury_table_but(formsemestre_id: int, format="html"):
|
||||
"""Page récapitulant les décisions de jury BUT
|
||||
formsemestre peut être pair ou impair
|
||||
"""
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
assert formsemestre.formation.is_apc()
|
||||
title = "Procès-verbal de jury BUT annuel"
|
||||
|
||||
if format == "html":
|
||||
line_sep = "<br>"
|
||||
else:
|
||||
line_sep = "\n"
|
||||
# remplace pour le BUT la fonction sco_pvjury.pvjury_table
|
||||
annee_but = (formsemestre.semestre_id + 1) // 2
|
||||
titles = {
|
||||
"nom": "Nom",
|
||||
"cursus": "Cursus",
|
||||
"ues": "UE validées",
|
||||
"niveaux": "Niveaux de compétences validés",
|
||||
"decision_but": f"Décision BUT{annee_but}",
|
||||
"diplome": "Résultat au diplôme",
|
||||
"devenir": "Devenir",
|
||||
"observations": "Observations",
|
||||
}
|
||||
rows = []
|
||||
for etudid in formsemestre.etuds_inscriptions:
|
||||
etud: Identite = Identite.query.get(etudid)
|
||||
try:
|
||||
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
||||
if deca.annee_but != annee_but: # wtf ?
|
||||
log(
|
||||
f"pvjury_table_but: inconsistent annee_but {deca.annee_but} != {annee_but}"
|
||||
)
|
||||
continue
|
||||
except ScoValueError:
|
||||
deca = None
|
||||
row = {
|
||||
"nom": etud.etat_civil_pv(line_sep=line_sep),
|
||||
"_nom_order": etud.sort_key,
|
||||
"_nom_target_attrs": f'class="etudinfo" id="{etud.id}"',
|
||||
"_nom_td_attrs": f'id="{etud.id}" class="etudinfo"',
|
||||
"_nom_target": url_for(
|
||||
"scolar.ficheEtud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud.id,
|
||||
),
|
||||
"cursus": _descr_cursus_but(etud),
|
||||
"ues": deca.descr_ues_validation(line_sep=line_sep) if deca else "-",
|
||||
"niveaux": deca.descr_niveaux_validation(line_sep=line_sep)
|
||||
if deca
|
||||
else "-",
|
||||
"decision_but": deca.code_valide if deca else "",
|
||||
"devenir": ", ".join([f"S{i}" for i in deca.get_autorisations_passage()])
|
||||
if deca
|
||||
else "",
|
||||
}
|
||||
|
||||
rows.append(row)
|
||||
|
||||
rows.sort(key=lambda x: x["_nom_order"])
|
||||
|
||||
# Style excel... passages à la ligne sur \n
|
||||
xls_style_base = sco_excel.excel_make_style()
|
||||
xls_style_base["alignment"] = Alignment(wrapText=True, vertical="top")
|
||||
|
||||
tab = GenTable(
|
||||
base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}",
|
||||
caption=title,
|
||||
columns_ids=titles.keys(),
|
||||
html_caption=title,
|
||||
html_class="pvjury_table_but table_leftalign",
|
||||
html_title=f"""<div style="margin-bottom: 8px;"><span style="font-size: 120%; font-weight: bold;">{title}</span>
|
||||
<span style="padding-left: 20px;">
|
||||
<a href="{url_for("notes.pvjury_table_but",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, format="xlsx")}"
|
||||
class="stdlink">version excel</a></span></div>
|
||||
|
||||
""",
|
||||
html_with_td_classes=True,
|
||||
origin=f"Généré par {scu.sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
||||
page_title=title,
|
||||
pdf_title=title,
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
rows=rows,
|
||||
table_id="formation_table_recap",
|
||||
titles=titles,
|
||||
xls_columns_width={
|
||||
"nom": 32,
|
||||
"cursus": 12,
|
||||
"ues": 32,
|
||||
"niveaux": 32,
|
||||
"decision_but": 14,
|
||||
"diplome": 17,
|
||||
"devenir": 8,
|
||||
"observations": 12,
|
||||
},
|
||||
xls_style_base=xls_style_base,
|
||||
)
|
||||
return tab.make_page(format=format, javascripts=["js/etud_info.js"], init_qtip=True)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,180 +1,180 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
##############################################################################
|
||||
# Module "Avis de poursuite d'étude"
|
||||
# conçu et développé par Cléo Baras (IUT de Grenoble)
|
||||
##############################################################################
|
||||
|
||||
|
||||
"""ScoDoc : interface des fonctions de gestion des avis de poursuites d'étude
|
||||
|
||||
"""
|
||||
|
||||
from flask import send_file, request
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
from app.pe import pe_tools
|
||||
from app.pe import pe_jurype
|
||||
from app.pe import pe_avislatex
|
||||
|
||||
|
||||
def _pe_view_sem_recap_form(formsemestre_id):
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Avis de poursuite d'études"),
|
||||
f"""<h2 class="formsemestre">Génération des avis de poursuites d'études</h2>
|
||||
<p class="help">
|
||||
Cette fonction génère un ensemble de fichiers permettant d'éditer des avis de
|
||||
poursuites d'études.
|
||||
<br/>
|
||||
De nombreux aspects sont paramétrables:
|
||||
<a href="https://scodoc.org/AvisPoursuiteEtudes" target="_blank" rel="noopener">
|
||||
voir la documentation</a>.
|
||||
</p>
|
||||
<form method="post" action="pe_view_sem_recap" id="pe_view_sem_recap_form"
|
||||
enctype="multipart/form-data">
|
||||
<div class="pe_template_up">
|
||||
Les templates sont généralement installés sur le serveur ou dans le
|
||||
paramétrage de ScoDoc.
|
||||
<br/>
|
||||
Au besoin, vous pouvez spécifier ici votre propre fichier de template
|
||||
(<tt>un_avis.tex</tt>):
|
||||
<div class="pe_template_upb">Template:
|
||||
<input type="file" size="30" name="avis_tmpl_file"/>
|
||||
</div>
|
||||
<div class="pe_template_upb">Pied de page:
|
||||
<input type="file" size="30" name="footer_tmpl_file"/>
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" value="Générer les documents"/>
|
||||
<input type="hidden" name="formsemestre_id" value="{formsemestre_id}">
|
||||
</form>
|
||||
""",
|
||||
]
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
# called from the web, POST or GET
|
||||
def pe_view_sem_recap(
|
||||
formsemestre_id,
|
||||
avis_tmpl_file=None,
|
||||
footer_tmpl_file=None,
|
||||
):
|
||||
"""Génération des avis de poursuite d'étude"""
|
||||
if request.method == "GET":
|
||||
return _pe_view_sem_recap_form(formsemestre_id)
|
||||
prefs = sco_preferences.SemPreferences(formsemestre_id=formsemestre_id)
|
||||
|
||||
semBase = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
|
||||
jury = pe_jurype.JuryPE(semBase)
|
||||
# Ajout avis LaTeX au même zip:
|
||||
etudids = list(jury.syntheseJury.keys())
|
||||
|
||||
# Récupération du template latex, du footer latex et du tag identifiant les annotations relatives aux PE
|
||||
# (chaines unicodes, html non quoté)
|
||||
template_latex = ""
|
||||
# template fourni via le formulaire Web
|
||||
if avis_tmpl_file:
|
||||
try:
|
||||
template_latex = avis_tmpl_file.read().decode("utf-8")
|
||||
except UnicodeDecodeError as e:
|
||||
raise ScoValueError(
|
||||
"Données (template) invalides (caractères non UTF8 ?)"
|
||||
) from e
|
||||
else:
|
||||
# template indiqué dans préférences ScoDoc ?
|
||||
template_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
|
||||
formsemestre_id, champ="pe_avis_latex_tmpl"
|
||||
)
|
||||
|
||||
template_latex = template_latex.strip()
|
||||
if not template_latex:
|
||||
# pas de preference pour le template: utilise fichier du serveur
|
||||
template_latex = pe_avislatex.get_templates_from_distrib("avis")
|
||||
|
||||
# Footer:
|
||||
footer_latex = ""
|
||||
# template fourni via le formulaire Web
|
||||
if footer_tmpl_file:
|
||||
footer_latex = footer_tmpl_file.read().decode("utf-8")
|
||||
else:
|
||||
footer_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
|
||||
formsemestre_id, champ="pe_avis_latex_footer"
|
||||
)
|
||||
footer_latex = footer_latex.strip()
|
||||
if not footer_latex:
|
||||
# pas de preference pour le footer: utilise fichier du serveur
|
||||
footer_latex = pe_avislatex.get_templates_from_distrib(
|
||||
"footer"
|
||||
) # fallback: footer vides
|
||||
|
||||
tag_annotation_pe = pe_avislatex.get_code_latex_from_scodoc_preference(
|
||||
formsemestre_id, champ="pe_tag_annotation_avis_latex"
|
||||
)
|
||||
|
||||
# Ajout des annotations PE dans un fichier excel
|
||||
sT = pe_avislatex.table_syntheseAnnotationPE(jury.syntheseJury, tag_annotation_pe)
|
||||
if sT:
|
||||
jury.add_file_to_zip(
|
||||
jury.NOM_EXPORT_ZIP + "_annotationsPE" + scu.XLSX_SUFFIX, sT.excel()
|
||||
)
|
||||
|
||||
latex_pages = {} # Dictionnaire de la forme nom_fichier => contenu_latex
|
||||
for etudid in etudids:
|
||||
[nom_fichier, contenu_latex] = pe_avislatex.get_avis_poursuite_par_etudiant(
|
||||
jury,
|
||||
etudid,
|
||||
template_latex,
|
||||
tag_annotation_pe,
|
||||
footer_latex,
|
||||
prefs,
|
||||
)
|
||||
jury.add_file_to_zip("avis/" + nom_fichier + ".tex", contenu_latex)
|
||||
latex_pages[nom_fichier] = contenu_latex # Sauvegarde dans un dico
|
||||
|
||||
# Nouvelle version : 1 fichier par étudiant avec 1 fichier appelant créée ci-dessous
|
||||
doc_latex = "\n% -----\n".join(
|
||||
["\\include{" + nom + "}" for nom in sorted(latex_pages.keys())]
|
||||
)
|
||||
jury.add_file_to_zip("avis/avis_poursuite.tex", doc_latex)
|
||||
|
||||
# Ajoute image, LaTeX class file(s) and modeles
|
||||
pe_tools.add_pe_stuff_to_zip(jury.zipfile, jury.NOM_EXPORT_ZIP)
|
||||
data = jury.get_zipped_data()
|
||||
|
||||
return send_file(
|
||||
data,
|
||||
mimetype="application/zip",
|
||||
download_name=scu.sanitize_filename(jury.NOM_EXPORT_ZIP + ".zip"),
|
||||
as_attachment=True,
|
||||
)
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
##############################################################################
|
||||
# Module "Avis de poursuite d'étude"
|
||||
# conçu et développé par Cléo Baras (IUT de Grenoble)
|
||||
##############################################################################
|
||||
|
||||
|
||||
"""ScoDoc : interface des fonctions de gestion des avis de poursuites d'étude
|
||||
|
||||
"""
|
||||
|
||||
from flask import send_file, request
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
from app.pe import pe_tools
|
||||
from app.pe import pe_jurype
|
||||
from app.pe import pe_avislatex
|
||||
|
||||
|
||||
def _pe_view_sem_recap_form(formsemestre_id):
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Avis de poursuite d'études"),
|
||||
f"""<h2 class="formsemestre">Génération des avis de poursuites d'études</h2>
|
||||
<p class="help">
|
||||
Cette fonction génère un ensemble de fichiers permettant d'éditer des avis de
|
||||
poursuites d'études.
|
||||
<br>
|
||||
De nombreux aspects sont paramétrables:
|
||||
<a href="https://scodoc.org/AvisPoursuiteEtudes" target="_blank" rel="noopener">
|
||||
voir la documentation</a>.
|
||||
</p>
|
||||
<form method="post" action="pe_view_sem_recap" id="pe_view_sem_recap_form"
|
||||
enctype="multipart/form-data">
|
||||
<div class="pe_template_up">
|
||||
Les templates sont généralement installés sur le serveur ou dans le
|
||||
paramétrage de ScoDoc.
|
||||
<br>
|
||||
Au besoin, vous pouvez spécifier ici votre propre fichier de template
|
||||
(<tt>un_avis.tex</tt>):
|
||||
<div class="pe_template_upb">Template:
|
||||
<input type="file" size="30" name="avis_tmpl_file"/>
|
||||
</div>
|
||||
<div class="pe_template_upb">Pied de page:
|
||||
<input type="file" size="30" name="footer_tmpl_file"/>
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" value="Générer les documents"/>
|
||||
<input type="hidden" name="formsemestre_id" value="{formsemestre_id}">
|
||||
</form>
|
||||
""",
|
||||
]
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
# called from the web, POST or GET
|
||||
def pe_view_sem_recap(
|
||||
formsemestre_id,
|
||||
avis_tmpl_file=None,
|
||||
footer_tmpl_file=None,
|
||||
):
|
||||
"""Génération des avis de poursuite d'étude"""
|
||||
if request.method == "GET":
|
||||
return _pe_view_sem_recap_form(formsemestre_id)
|
||||
prefs = sco_preferences.SemPreferences(formsemestre_id=formsemestre_id)
|
||||
|
||||
semBase = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
|
||||
jury = pe_jurype.JuryPE(semBase)
|
||||
# Ajout avis LaTeX au même zip:
|
||||
etudids = list(jury.syntheseJury.keys())
|
||||
|
||||
# Récupération du template latex, du footer latex et du tag identifiant les annotations relatives aux PE
|
||||
# (chaines unicodes, html non quoté)
|
||||
template_latex = ""
|
||||
# template fourni via le formulaire Web
|
||||
if avis_tmpl_file:
|
||||
try:
|
||||
template_latex = avis_tmpl_file.read().decode("utf-8")
|
||||
except UnicodeDecodeError as e:
|
||||
raise ScoValueError(
|
||||
"Données (template) invalides (caractères non UTF8 ?)"
|
||||
) from e
|
||||
else:
|
||||
# template indiqué dans préférences ScoDoc ?
|
||||
template_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
|
||||
formsemestre_id, champ="pe_avis_latex_tmpl"
|
||||
)
|
||||
|
||||
template_latex = template_latex.strip()
|
||||
if not template_latex:
|
||||
# pas de preference pour le template: utilise fichier du serveur
|
||||
template_latex = pe_avislatex.get_templates_from_distrib("avis")
|
||||
|
||||
# Footer:
|
||||
footer_latex = ""
|
||||
# template fourni via le formulaire Web
|
||||
if footer_tmpl_file:
|
||||
footer_latex = footer_tmpl_file.read().decode("utf-8")
|
||||
else:
|
||||
footer_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
|
||||
formsemestre_id, champ="pe_avis_latex_footer"
|
||||
)
|
||||
footer_latex = footer_latex.strip()
|
||||
if not footer_latex:
|
||||
# pas de preference pour le footer: utilise fichier du serveur
|
||||
footer_latex = pe_avislatex.get_templates_from_distrib(
|
||||
"footer"
|
||||
) # fallback: footer vides
|
||||
|
||||
tag_annotation_pe = pe_avislatex.get_code_latex_from_scodoc_preference(
|
||||
formsemestre_id, champ="pe_tag_annotation_avis_latex"
|
||||
)
|
||||
|
||||
# Ajout des annotations PE dans un fichier excel
|
||||
sT = pe_avislatex.table_syntheseAnnotationPE(jury.syntheseJury, tag_annotation_pe)
|
||||
if sT:
|
||||
jury.add_file_to_zip(
|
||||
jury.NOM_EXPORT_ZIP + "_annotationsPE" + scu.XLSX_SUFFIX, sT.excel()
|
||||
)
|
||||
|
||||
latex_pages = {} # Dictionnaire de la forme nom_fichier => contenu_latex
|
||||
for etudid in etudids:
|
||||
[nom_fichier, contenu_latex] = pe_avislatex.get_avis_poursuite_par_etudiant(
|
||||
jury,
|
||||
etudid,
|
||||
template_latex,
|
||||
tag_annotation_pe,
|
||||
footer_latex,
|
||||
prefs,
|
||||
)
|
||||
jury.add_file_to_zip("avis/" + nom_fichier + ".tex", contenu_latex)
|
||||
latex_pages[nom_fichier] = contenu_latex # Sauvegarde dans un dico
|
||||
|
||||
# Nouvelle version : 1 fichier par étudiant avec 1 fichier appelant créée ci-dessous
|
||||
doc_latex = "\n% -----\n".join(
|
||||
["\\include{" + nom + "}" for nom in sorted(latex_pages.keys())]
|
||||
)
|
||||
jury.add_file_to_zip("avis/avis_poursuite.tex", doc_latex)
|
||||
|
||||
# Ajoute image, LaTeX class file(s) and modeles
|
||||
pe_tools.add_pe_stuff_to_zip(jury.zipfile, jury.NOM_EXPORT_ZIP)
|
||||
data = jury.get_zipped_data()
|
||||
|
||||
return send_file(
|
||||
data,
|
||||
mimetype="application/zip",
|
||||
download_name=scu.sanitize_filename(jury.NOM_EXPORT_ZIP + ".zip"),
|
||||
as_attachment=True,
|
||||
)
|
||||
|
|
|
@ -38,6 +38,9 @@ def TrivialFormulator(
|
|||
html_foot_markup="",
|
||||
readonly=False,
|
||||
is_submitted=False,
|
||||
title="",
|
||||
after_table="",
|
||||
before_table="{title}",
|
||||
):
|
||||
"""
|
||||
form_url : URL for this form
|
||||
|
@ -74,7 +77,8 @@ def TrivialFormulator(
|
|||
HTML elements:
|
||||
input_type : 'text', 'textarea', 'password',
|
||||
'radio', 'menu', 'checkbox',
|
||||
'hidden', 'separator', 'file', 'date', 'datedmy' (avec validation),
|
||||
'hidden', 'separator', 'table_separator',
|
||||
'file', 'date', 'datedmy' (avec validation),
|
||||
'boolcheckbox', 'text_suggest',
|
||||
'color'
|
||||
(default text)
|
||||
|
@ -111,6 +115,9 @@ def TrivialFormulator(
|
|||
html_foot_markup=html_foot_markup,
|
||||
readonly=readonly,
|
||||
is_submitted=is_submitted,
|
||||
title=title,
|
||||
after_table=after_table,
|
||||
before_table=before_table,
|
||||
)
|
||||
form = t.getform()
|
||||
if t.canceled():
|
||||
|
@ -144,6 +151,9 @@ class TF(object):
|
|||
html_foot_markup="", # html snippet put at the end, just after the table
|
||||
readonly=False,
|
||||
is_submitted=False,
|
||||
title="",
|
||||
after_table="",
|
||||
before_table="{title}",
|
||||
):
|
||||
self.form_url = form_url
|
||||
self.values = values.copy()
|
||||
|
@ -165,6 +175,9 @@ class TF(object):
|
|||
self.top_buttons = top_buttons
|
||||
self.bottom_buttons = bottom_buttons
|
||||
self.html_foot_markup = html_foot_markup
|
||||
self.title = title
|
||||
self.after_table = after_table
|
||||
self.before_table = before_table
|
||||
self.readonly = readonly
|
||||
self.result = None
|
||||
self.is_submitted = is_submitted
|
||||
|
@ -426,6 +439,7 @@ class TF(object):
|
|||
R.append('<input type="hidden" name="%s_submitted" value="1">' % self.formid)
|
||||
if self.top_buttons:
|
||||
R.append(buttons_markup + "<p></p>")
|
||||
R.append(self.before_table.format(title=self.title))
|
||||
R.append('<table class="tf">')
|
||||
for field, descr in self.formdescription:
|
||||
if descr.get("readonly", False):
|
||||
|
@ -453,6 +467,16 @@ class TF(object):
|
|||
etempl = separatortemplate
|
||||
R.append(etempl % {"label": title, "item_dom_attr": item_dom_attr})
|
||||
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:
|
||||
etempl = itemtemplate
|
||||
lab = []
|
||||
|
@ -610,7 +634,7 @@ class TF(object):
|
|||
'<input type="hidden" name="%s" id="%s" value="%s" %s >'
|
||||
% (field, wid, values[field], attribs)
|
||||
)
|
||||
elif input_type == "separator":
|
||||
elif (input_type == "separator") or (input_type == "table_separator"):
|
||||
pass
|
||||
elif input_type == "file":
|
||||
lem.append(
|
||||
|
@ -641,13 +665,15 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
|||
)
|
||||
lem.append(('value="%(' + field + ')s" >') % values)
|
||||
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", "")
|
||||
if explanation:
|
||||
lem.append('<span class="tf-explanation">%s</span>' % explanation)
|
||||
lem.append(f"""<span class="tf-explanation">{explanation}</span>""")
|
||||
comment = descr.get("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(
|
||||
etempl
|
||||
% {
|
||||
|
@ -657,11 +683,11 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
|||
}
|
||||
)
|
||||
R.append("</table>")
|
||||
|
||||
R.append(self.after_table)
|
||||
R.append(self.html_foot_markup)
|
||||
|
||||
if self.bottom_buttons:
|
||||
R.append("<br/>" + buttons_markup)
|
||||
R.append("<br>" + buttons_markup)
|
||||
|
||||
if add_no_enter_js:
|
||||
R.append(
|
||||
|
@ -753,7 +779,7 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
|||
|
||||
if input_type == "separator": # separator
|
||||
R.append('<td colspan="2">%s' % title)
|
||||
else:
|
||||
elif input_type != "table_separator":
|
||||
R.append('<td class="tf-ro-fieldlabel%s">' % klass)
|
||||
R.append("%s</td>" % title)
|
||||
R.append('<td class="tf-ro-field%s">' % klass)
|
||||
|
@ -786,7 +812,11 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
|||
R.append(
|
||||
'<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
|
||||
elif input_type == "file":
|
||||
R.append("'%s'" % self.values[field])
|
||||
|
|
|
@ -1,321 +1,321 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""HTML Header/Footer for ScoDoc pages
|
||||
"""
|
||||
|
||||
import html
|
||||
|
||||
from flask import render_template
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import scodoc_flash_status_messages
|
||||
from app.scodoc import html_sidebar
|
||||
import sco_version
|
||||
|
||||
|
||||
# Some constants:
|
||||
|
||||
# Multiselect menus are used on a few pages and not loaded by default
|
||||
BOOTSTRAP_MULTISELECT_JS = [
|
||||
"libjs/bootstrap-3.1.1-dist/js/bootstrap.min.js",
|
||||
"libjs/bootstrap-multiselect/bootstrap-multiselect.js",
|
||||
"libjs/purl.js",
|
||||
]
|
||||
|
||||
BOOTSTRAP_MULTISELECT_CSS = [
|
||||
"libjs/bootstrap-3.1.1-dist/css/bootstrap.min.css",
|
||||
"libjs/bootstrap-3.1.1-dist/css/bootstrap-theme.min.css",
|
||||
"libjs/bootstrap-multiselect/bootstrap-multiselect.css",
|
||||
]
|
||||
|
||||
|
||||
def standard_html_header():
|
||||
"""Standard HTML header for pages outside depts"""
|
||||
# not used in ZScolar, see sco_header
|
||||
return f"""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html><head>
|
||||
<title>ScoDoc: accueil</title>
|
||||
<META http-equiv="Content-Type" content="text/html; charset={scu.SCO_ENCODING}">
|
||||
<META http-equiv="Content-Style-Type" content="text/css">
|
||||
<META name="LANG" content="fr">
|
||||
<META name="DESCRIPTION" content="ScoDoc: gestion scolarite">
|
||||
|
||||
<link href="{scu.STATIC_DIR}/css/scodoc.css" rel="stylesheet" type="text/css"/>
|
||||
|
||||
</head><body>{scu.CUSTOM_HTML_HEADER_CNX}"""
|
||||
|
||||
|
||||
def standard_html_footer():
|
||||
"""Le pied de page HTML de la page d'accueil."""
|
||||
return f"""<p class="footer">
|
||||
Problème de connexion (identifiant, mot de passe): <em>contacter votre responsable ou chef de département</em>.</p>
|
||||
<p>Problèmes et suggestions sur le logiciel: <a href="mailto:{scu.SCO_USERS_LIST}">{scu.SCO_USERS_LIST}</a></p>
|
||||
<p><em>ScoDoc est un logiciel libre développé par Emmanuel Viennet.</em></p>
|
||||
</body></html>"""
|
||||
|
||||
|
||||
_HTML_BEGIN = f"""<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=%(encoding)s" />
|
||||
<meta http-equiv="Content-Style-Type" content="text/css" />
|
||||
<meta name="LANG" content="fr" />
|
||||
<meta name="DESCRIPTION" content="ScoDoc" />
|
||||
<title>%(page_title)s</title>
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/css/smoothness/jquery-ui-1.10.4.custom.min.css" />
|
||||
|
||||
<link href="{scu.STATIC_DIR}/css/scodoc.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{scu.STATIC_DIR}/css/menu.css" rel="stylesheet" type="text/css" />
|
||||
<script src="{scu.STATIC_DIR}/libjs/menu.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/libjs/bubble.js"></script>
|
||||
<script>
|
||||
window.onload=function(){{enableTooltips("gtrcontent")}};
|
||||
</script>
|
||||
|
||||
<script src="{scu.STATIC_DIR}/jQuery/jquery.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/libjs/jquery.field.min.js"></script>
|
||||
|
||||
<script src="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
|
||||
<script src="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.css" />
|
||||
|
||||
<script src="{scu.STATIC_DIR}/js/scodoc.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/js/etud_info.js"></script>
|
||||
"""
|
||||
|
||||
|
||||
def scodoc_top_html_header(page_title="ScoDoc: bienvenue"):
|
||||
H = [
|
||||
_HTML_BEGIN % {"page_title": page_title, "encoding": scu.SCO_ENCODING},
|
||||
"""</head><body id="gtrcontent">""",
|
||||
scu.CUSTOM_HTML_HEADER_CNX,
|
||||
]
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
# Header:
|
||||
def sco_header(
|
||||
# optional args
|
||||
page_title="", # page title
|
||||
no_side_bar=False, # hide sidebar
|
||||
cssstyles=(), # additionals CSS sheets
|
||||
javascripts=(), # additionals JS filenames to load
|
||||
scripts=(), # script to put in page header
|
||||
bodyOnLoad="", # JS
|
||||
init_qtip=False, # include qTip
|
||||
init_google_maps=False, # Google maps
|
||||
init_datatables=True,
|
||||
titrebandeau="", # titre dans bandeau superieur
|
||||
head_message="", # message action (petit cadre jaune en haut)
|
||||
user_check=True, # verifie passwords temporaires
|
||||
etudid=None,
|
||||
formsemestre_id=None,
|
||||
):
|
||||
"Main HTML page header for ScoDoc"
|
||||
from app.scodoc.sco_formsemestre_status import formsemestre_page_title
|
||||
|
||||
scodoc_flash_status_messages()
|
||||
|
||||
# Get head message from http request:
|
||||
if not head_message:
|
||||
if request.method == "POST":
|
||||
head_message = request.form.get("head_message", "")
|
||||
elif request.method == "GET":
|
||||
head_message = request.args.get("head_message", "")
|
||||
params = {
|
||||
"page_title": page_title or sco_version.SCONAME,
|
||||
"no_side_bar": no_side_bar,
|
||||
"ScoURL": scu.ScoURL(),
|
||||
"encoding": scu.SCO_ENCODING,
|
||||
"titrebandeau_mkup": "<td>" + titrebandeau + "</td>",
|
||||
"authuser": current_user.user_name,
|
||||
}
|
||||
if bodyOnLoad:
|
||||
params["bodyOnLoad_mkup"] = """onload="%s" """ % bodyOnLoad
|
||||
else:
|
||||
params["bodyOnLoad_mkup"] = ""
|
||||
if no_side_bar:
|
||||
params["margin_left"] = "1em"
|
||||
else:
|
||||
params["margin_left"] = "140px"
|
||||
|
||||
H = [
|
||||
"""<!DOCTYPE html><html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>%(page_title)s</title>
|
||||
<meta name="LANG" content="fr" />
|
||||
<meta name="DESCRIPTION" content="ScoDoc" />
|
||||
|
||||
"""
|
||||
% params
|
||||
]
|
||||
# jQuery UI
|
||||
# can modify loaded theme here
|
||||
H.append(
|
||||
f'<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/css/smoothness/jquery-ui-1.10.4.custom.min.css" />\n'
|
||||
)
|
||||
if init_google_maps:
|
||||
# It may be necessary to add an API key:
|
||||
H.append('<script src="https://maps.google.com/maps/api/js"></script>')
|
||||
|
||||
# Feuilles de style additionnelles:
|
||||
for cssstyle in cssstyles:
|
||||
H.append(
|
||||
f"""<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/{cssstyle}" />\n"""
|
||||
)
|
||||
|
||||
H.append(
|
||||
f"""
|
||||
<link href="{scu.STATIC_DIR}/css/scodoc.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{scu.STATIC_DIR}/css/menu.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{scu.STATIC_DIR}/css/gt_table.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<script src="{scu.STATIC_DIR}/libjs/menu.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/libjs/bubble.js"></script>
|
||||
<script>
|
||||
window.onload=function(){{enableTooltips("gtrcontent")}};
|
||||
|
||||
var SCO_URL="{scu.ScoURL()}";
|
||||
</script>"""
|
||||
)
|
||||
|
||||
# jQuery
|
||||
H.append(
|
||||
f"""<script src="{scu.STATIC_DIR}/jQuery/jquery.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/libjs/jquery.field.min.js"></script>"""
|
||||
)
|
||||
# qTip
|
||||
if init_qtip:
|
||||
H.append(
|
||||
f"""<script src="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.css" />"""
|
||||
)
|
||||
|
||||
H.append(
|
||||
f"""<script src="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/js/scodoc.js"></script>"""
|
||||
)
|
||||
if init_google_maps:
|
||||
H.append(
|
||||
f'<script src="{scu.STATIC_DIR}/libjs/jquery.ui.map.full.min.js"></script>'
|
||||
)
|
||||
if init_datatables:
|
||||
H.append(
|
||||
f"""<link rel="stylesheet" type="text/css" href="{scu.STATIC_DIR}/DataTables/datatables.min.css"/>
|
||||
<script src="{scu.STATIC_DIR}/DataTables/datatables.min.js"></script>"""
|
||||
)
|
||||
# H.append(
|
||||
# f'<link href="{scu.STATIC_DIR}/css/tooltip.css" rel="stylesheet" type="text/css" />'
|
||||
# )
|
||||
# JS additionels
|
||||
for js in javascripts:
|
||||
H.append(f"""<script src="{scu.STATIC_DIR}/{js}"></script>\n""")
|
||||
|
||||
H.append(
|
||||
f"""<style>
|
||||
#gtrcontent {{
|
||||
margin-left: {params["margin_left"]};
|
||||
height: 100%%;
|
||||
margin-bottom: 10px;
|
||||
}}
|
||||
</style>
|
||||
"""
|
||||
)
|
||||
# Scripts de la page:
|
||||
if scripts:
|
||||
H.append("""<script>""")
|
||||
for script in scripts:
|
||||
H.append(script)
|
||||
H.append("""</script>""")
|
||||
|
||||
H.append("</head>")
|
||||
|
||||
# Body et bandeau haut:
|
||||
H.append("""<body %(bodyOnLoad_mkup)s>""" % params)
|
||||
H.append(scu.CUSTOM_HTML_HEADER)
|
||||
#
|
||||
if not no_side_bar:
|
||||
H.append(html_sidebar.sidebar(etudid))
|
||||
H.append("""<div id="gtrcontent">""")
|
||||
# En attendant le replacement complet de cette fonction,
|
||||
# inclusion ici des messages flask
|
||||
H.append(render_template("flashed_messages.html"))
|
||||
#
|
||||
# Barre menu semestre:
|
||||
H.append(formsemestre_page_title(formsemestre_id))
|
||||
|
||||
# Avertissement si mot de passe à changer
|
||||
if user_check:
|
||||
if current_user.passwd_temp:
|
||||
H.append(
|
||||
f"""<div class="passwd_warn">
|
||||
Attention !<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>
|
||||
</div>"""
|
||||
)
|
||||
#
|
||||
if head_message:
|
||||
H.append('<div class="head_message">' + html.escape(head_message) + "</div>")
|
||||
#
|
||||
# div pour affichage messages temporaires
|
||||
H.append('<div id="sco_msg" class="head_message"></div>')
|
||||
#
|
||||
return "".join(H)
|
||||
|
||||
|
||||
def sco_footer():
|
||||
"""Main HTMl pages footer"""
|
||||
return (
|
||||
"""</div><!-- /gtrcontent -->""" + scu.CUSTOM_HTML_FOOTER + """</body></html>"""
|
||||
)
|
||||
|
||||
|
||||
def html_sem_header(
|
||||
title, with_page_header=True, with_h2=True, page_title=None, **args
|
||||
):
|
||||
"Titre d'une page semestre avec lien vers tableau de bord"
|
||||
# sem now unused and thus optional...
|
||||
if with_page_header:
|
||||
h = sco_header(page_title="%s" % (page_title or title), **args)
|
||||
else:
|
||||
h = ""
|
||||
if with_h2:
|
||||
return h + f"""<h2 class="formsemestre">{title}</h2>"""
|
||||
else:
|
||||
return h
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""HTML Header/Footer for ScoDoc pages
|
||||
"""
|
||||
|
||||
import html
|
||||
|
||||
from flask import render_template
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import scodoc_flash_status_messages
|
||||
from app.scodoc import html_sidebar
|
||||
import sco_version
|
||||
|
||||
|
||||
# Some constants:
|
||||
|
||||
# Multiselect menus are used on a few pages and not loaded by default
|
||||
BOOTSTRAP_MULTISELECT_JS = [
|
||||
"libjs/bootstrap-3.1.1-dist/js/bootstrap.min.js",
|
||||
"libjs/bootstrap-multiselect/bootstrap-multiselect.js",
|
||||
"libjs/purl.js",
|
||||
]
|
||||
|
||||
BOOTSTRAP_MULTISELECT_CSS = [
|
||||
"libjs/bootstrap-3.1.1-dist/css/bootstrap.min.css",
|
||||
"libjs/bootstrap-3.1.1-dist/css/bootstrap-theme.min.css",
|
||||
"libjs/bootstrap-multiselect/bootstrap-multiselect.css",
|
||||
]
|
||||
|
||||
|
||||
def standard_html_header():
|
||||
"""Standard HTML header for pages outside depts"""
|
||||
# not used in ZScolar, see sco_header
|
||||
return f"""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html><head>
|
||||
<title>ScoDoc: accueil</title>
|
||||
<META http-equiv="Content-Type" content="text/html; charset={scu.SCO_ENCODING}">
|
||||
<META http-equiv="Content-Style-Type" content="text/css">
|
||||
<META name="LANG" content="fr">
|
||||
<META name="DESCRIPTION" content="ScoDoc: gestion scolarite">
|
||||
|
||||
<link href="{scu.STATIC_DIR}/css/scodoc.css" rel="stylesheet" type="text/css"/>
|
||||
|
||||
</head><body>{scu.CUSTOM_HTML_HEADER_CNX}"""
|
||||
|
||||
|
||||
def standard_html_footer():
|
||||
"""Le pied de page HTML de la page d'accueil."""
|
||||
return f"""<p class="footer">
|
||||
Problème de connexion (identifiant, mot de passe): <em>contacter votre responsable ou chef de département</em>.</p>
|
||||
<p>Problèmes et suggestions sur le logiciel: <a href="mailto:{scu.SCO_USERS_LIST}">{scu.SCO_USERS_LIST}</a></p>
|
||||
<p><em>ScoDoc est un logiciel libre développé par Emmanuel Viennet.</em></p>
|
||||
</body></html>"""
|
||||
|
||||
|
||||
_HTML_BEGIN = f"""<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=%(encoding)s" />
|
||||
<meta http-equiv="Content-Style-Type" content="text/css" />
|
||||
<meta name="LANG" content="fr" />
|
||||
<meta name="DESCRIPTION" content="ScoDoc" />
|
||||
<title>%(page_title)s</title>
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/css/smoothness/jquery-ui-1.10.4.custom.min.css" />
|
||||
|
||||
<link href="{scu.STATIC_DIR}/css/scodoc.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{scu.STATIC_DIR}/css/menu.css" rel="stylesheet" type="text/css" />
|
||||
<script src="{scu.STATIC_DIR}/libjs/menu.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/libjs/bubble.js"></script>
|
||||
<script>
|
||||
window.onload=function(){{enableTooltips("gtrcontent")}};
|
||||
</script>
|
||||
|
||||
<script src="{scu.STATIC_DIR}/jQuery/jquery.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/libjs/jquery.field.min.js"></script>
|
||||
|
||||
<script src="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
|
||||
<script src="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.css" />
|
||||
|
||||
<script src="{scu.STATIC_DIR}/js/scodoc.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/js/etud_info.js"></script>
|
||||
"""
|
||||
|
||||
|
||||
def scodoc_top_html_header(page_title="ScoDoc: bienvenue"):
|
||||
H = [
|
||||
_HTML_BEGIN % {"page_title": page_title, "encoding": scu.SCO_ENCODING},
|
||||
"""</head><body id="gtrcontent">""",
|
||||
scu.CUSTOM_HTML_HEADER_CNX,
|
||||
]
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
# Header:
|
||||
def sco_header(
|
||||
# optional args
|
||||
page_title="", # page title
|
||||
no_side_bar=False, # hide sidebar
|
||||
cssstyles=(), # additionals CSS sheets
|
||||
javascripts=(), # additionals JS filenames to load
|
||||
scripts=(), # script to put in page header
|
||||
bodyOnLoad="", # JS
|
||||
init_qtip=False, # include qTip
|
||||
init_google_maps=False, # Google maps
|
||||
init_datatables=True,
|
||||
titrebandeau="", # titre dans bandeau superieur
|
||||
head_message="", # message action (petit cadre jaune en haut)
|
||||
user_check=True, # verifie passwords temporaires
|
||||
etudid=None,
|
||||
formsemestre_id=None,
|
||||
):
|
||||
"Main HTML page header for ScoDoc"
|
||||
from app.scodoc.sco_formsemestre_status import formsemestre_page_title
|
||||
|
||||
scodoc_flash_status_messages()
|
||||
|
||||
# Get head message from http request:
|
||||
if not head_message:
|
||||
if request.method == "POST":
|
||||
head_message = request.form.get("head_message", "")
|
||||
elif request.method == "GET":
|
||||
head_message = request.args.get("head_message", "")
|
||||
params = {
|
||||
"page_title": page_title or sco_version.SCONAME,
|
||||
"no_side_bar": no_side_bar,
|
||||
"ScoURL": scu.ScoURL(),
|
||||
"encoding": scu.SCO_ENCODING,
|
||||
"titrebandeau_mkup": "<td>" + titrebandeau + "</td>",
|
||||
"authuser": current_user.user_name,
|
||||
}
|
||||
if bodyOnLoad:
|
||||
params["bodyOnLoad_mkup"] = """onload="%s" """ % bodyOnLoad
|
||||
else:
|
||||
params["bodyOnLoad_mkup"] = ""
|
||||
if no_side_bar:
|
||||
params["margin_left"] = "1em"
|
||||
else:
|
||||
params["margin_left"] = "140px"
|
||||
|
||||
H = [
|
||||
"""<!DOCTYPE html><html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>%(page_title)s</title>
|
||||
<meta name="LANG" content="fr" />
|
||||
<meta name="DESCRIPTION" content="ScoDoc" />
|
||||
|
||||
"""
|
||||
% params
|
||||
]
|
||||
# jQuery UI
|
||||
# can modify loaded theme here
|
||||
H.append(
|
||||
f'<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/css/smoothness/jquery-ui-1.10.4.custom.min.css" />\n'
|
||||
)
|
||||
if init_google_maps:
|
||||
# It may be necessary to add an API key:
|
||||
H.append('<script src="https://maps.google.com/maps/api/js"></script>')
|
||||
|
||||
# Feuilles de style additionnelles:
|
||||
for cssstyle in cssstyles:
|
||||
H.append(
|
||||
f"""<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/{cssstyle}" />\n"""
|
||||
)
|
||||
|
||||
H.append(
|
||||
f"""
|
||||
<link href="{scu.STATIC_DIR}/css/scodoc.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{scu.STATIC_DIR}/css/menu.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{scu.STATIC_DIR}/css/gt_table.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<script src="{scu.STATIC_DIR}/libjs/menu.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/libjs/bubble.js"></script>
|
||||
<script>
|
||||
window.onload=function(){{enableTooltips("gtrcontent")}};
|
||||
|
||||
var SCO_URL="{scu.ScoURL()}";
|
||||
</script>"""
|
||||
)
|
||||
|
||||
# jQuery
|
||||
H.append(
|
||||
f"""<script src="{scu.STATIC_DIR}/jQuery/jquery.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/libjs/jquery.field.min.js"></script>"""
|
||||
)
|
||||
# qTip
|
||||
if init_qtip:
|
||||
H.append(
|
||||
f"""<script src="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.css" />"""
|
||||
)
|
||||
|
||||
H.append(
|
||||
f"""<script src="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/js/scodoc.js"></script>"""
|
||||
)
|
||||
if init_google_maps:
|
||||
H.append(
|
||||
f'<script src="{scu.STATIC_DIR}/libjs/jquery.ui.map.full.min.js"></script>'
|
||||
)
|
||||
if init_datatables:
|
||||
H.append(
|
||||
f"""<link rel="stylesheet" type="text/css" href="{scu.STATIC_DIR}/DataTables/datatables.min.css"/>
|
||||
<script src="{scu.STATIC_DIR}/DataTables/datatables.min.js"></script>"""
|
||||
)
|
||||
# H.append(
|
||||
# f'<link href="{scu.STATIC_DIR}/css/tooltip.css" rel="stylesheet" type="text/css" />'
|
||||
# )
|
||||
# JS additionels
|
||||
for js in javascripts:
|
||||
H.append(f"""<script src="{scu.STATIC_DIR}/{js}"></script>\n""")
|
||||
|
||||
H.append(
|
||||
f"""<style>
|
||||
#gtrcontent {{
|
||||
margin-left: {params["margin_left"]};
|
||||
height: 100%%;
|
||||
margin-bottom: 10px;
|
||||
}}
|
||||
</style>
|
||||
"""
|
||||
)
|
||||
# Scripts de la page:
|
||||
if scripts:
|
||||
H.append("""<script>""")
|
||||
for script in scripts:
|
||||
H.append(script)
|
||||
H.append("""</script>""")
|
||||
|
||||
H.append("</head>")
|
||||
|
||||
# Body et bandeau haut:
|
||||
H.append("""<body %(bodyOnLoad_mkup)s>""" % params)
|
||||
H.append(scu.CUSTOM_HTML_HEADER)
|
||||
#
|
||||
if not no_side_bar:
|
||||
H.append(html_sidebar.sidebar(etudid))
|
||||
H.append("""<div id="gtrcontent">""")
|
||||
# En attendant le replacement complet de cette fonction,
|
||||
# inclusion ici des messages flask
|
||||
H.append(render_template("flashed_messages.html"))
|
||||
#
|
||||
# Barre menu semestre:
|
||||
H.append(formsemestre_page_title(formsemestre_id))
|
||||
|
||||
# Avertissement si mot de passe à changer
|
||||
if user_check:
|
||||
if current_user.passwd_temp:
|
||||
H.append(
|
||||
f"""<div class="passwd_warn">
|
||||
Attention !<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>
|
||||
</div>"""
|
||||
)
|
||||
#
|
||||
if head_message:
|
||||
H.append('<div class="head_message">' + html.escape(head_message) + "</div>")
|
||||
#
|
||||
# div pour affichage messages temporaires
|
||||
H.append('<div id="sco_msg" class="head_message"></div>')
|
||||
#
|
||||
return "".join(H)
|
||||
|
||||
|
||||
def sco_footer():
|
||||
"""Main HTMl pages footer"""
|
||||
return (
|
||||
"""</div><!-- /gtrcontent -->""" + scu.CUSTOM_HTML_FOOTER + """</body></html>"""
|
||||
)
|
||||
|
||||
|
||||
def html_sem_header(
|
||||
title, with_page_header=True, with_h2=True, page_title=None, **args
|
||||
):
|
||||
"Titre d'une page semestre avec lien vers tableau de bord"
|
||||
# sem now unused and thus optional...
|
||||
if with_page_header:
|
||||
h = sco_header(page_title="%s" % (page_title or title), **args)
|
||||
else:
|
||||
h = ""
|
||||
if with_h2:
|
||||
return h + f"""<h2 class="formsemestre">{title}</h2>"""
|
||||
else:
|
||||
return h
|
||||
|
|
|
@ -1,172 +1,172 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
Génération de la "sidebar" (marge gauche des pages HTML)
|
||||
"""
|
||||
from flask import render_template, url_for
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from sco_version import SCOVERSION
|
||||
|
||||
|
||||
def sidebar_common():
|
||||
"partie commune à toutes les sidebar"
|
||||
home_link = url_for("scodoc.index", scodoc_dept=g.scodoc_dept)
|
||||
H = [
|
||||
f"""<a class="scodoc_title" href="{home_link}">ScoDoc {SCOVERSION}</a><br>
|
||||
<a href="{home_link}" class="sidebar">Accueil</a> <br>
|
||||
<div id="authuser"><a id="authuserlink" href="{
|
||||
url_for("users.user_info_page",
|
||||
scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
||||
}">{current_user.user_name}</a>
|
||||
<br/><a id="deconnectlink" href="{url_for("auth.logout")}">déconnexion</a>
|
||||
</div>
|
||||
{sidebar_dept()}
|
||||
<h2 class="insidebar">Scolarité</h2>
|
||||
<a href="{scu.ScoURL()}" class="sidebar">Semestres</a> <br/>
|
||||
<a href="{scu.NotesURL()}" class="sidebar">Programmes</a> <br/>
|
||||
<a href="{scu.AbsencesURL()}" class="sidebar">Absences</a> <br/>
|
||||
"""
|
||||
]
|
||||
if current_user.has_permission(
|
||||
Permission.ScoUsersAdmin
|
||||
) or current_user.has_permission(Permission.ScoUsersView):
|
||||
H.append(
|
||||
f"""<a href="{scu.UsersURL()}" class="sidebar">Utilisateurs</a> <br/>"""
|
||||
)
|
||||
|
||||
if current_user.has_permission(Permission.ScoChangePreferences):
|
||||
H.append(
|
||||
f"""<a href="{url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}"
|
||||
class="sidebar">Paramétrage</a> <br/>"""
|
||||
)
|
||||
|
||||
return "".join(H)
|
||||
|
||||
|
||||
def sidebar(etudid: int = None):
|
||||
"Main HTML page sidebar"
|
||||
# rewritten from legacy DTML code
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_etud
|
||||
|
||||
params = {}
|
||||
|
||||
H = [
|
||||
f"""<div class="sidebar">
|
||||
{ sidebar_common() }
|
||||
<div class="box-chercheetud">Chercher étudiant:<br/>
|
||||
<form method="get" id="form-chercheetud"
|
||||
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>
|
||||
</form></div>
|
||||
<div class="etud-insidebar">
|
||||
"""
|
||||
]
|
||||
# ---- Il y-a-t-il un etudiant selectionné ?
|
||||
etudid = etudid if etudid is not None else g.get("etudid", None)
|
||||
if etudid is None:
|
||||
if request.method == "GET":
|
||||
etudid = request.args.get("etudid", None)
|
||||
elif request.method == "POST":
|
||||
etudid = request.form.get("etudid", None)
|
||||
|
||||
if etudid is not None:
|
||||
etudi = int(etudid)
|
||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||
params.update(etud)
|
||||
params["fiche_url"] = url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||
)
|
||||
# compte les absences du semestre en cours
|
||||
H.append(
|
||||
"""<h2 id="insidebar-etud"><a href="%(fiche_url)s" class="sidebar">
|
||||
<font color="#FF0000">%(civilite_str)s %(nom_disp)s</font></a>
|
||||
</h2>
|
||||
<b>Absences</b>"""
|
||||
% params
|
||||
)
|
||||
if etud["cursem"]:
|
||||
cur_sem = etud["cursem"]
|
||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, cur_sem)
|
||||
nbabsnj = nbabs - nbabsjust
|
||||
H.append(
|
||||
f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">(1/2 j.)
|
||||
<br/>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
|
||||
)
|
||||
H.append("<ul>")
|
||||
if current_user.has_permission(Permission.ScoAbsChange):
|
||||
H.append(
|
||||
f"""
|
||||
<li><a href="{ url_for('absences.SignaleAbsenceEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Ajouter</a></li>
|
||||
<li><a href="{ url_for('absences.JustifAbsenceEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Justifier</a></li>
|
||||
<li><a href="{ url_for('absences.AnnuleAbsenceEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Supprimer</a></li>
|
||||
"""
|
||||
)
|
||||
if sco_preferences.get_preference("handle_billets_abs"):
|
||||
H.append(
|
||||
f"""<li><a href="{ url_for('absences.billets_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Billets</a></li>"""
|
||||
)
|
||||
H.append(
|
||||
f"""
|
||||
<li><a href="{ url_for('absences.CalAbs', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Calendrier</a></li>
|
||||
<li><a href="{ url_for('absences.ListeAbsEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Liste</a></li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
else:
|
||||
pass # H.append("(pas d'étudiant en cours)")
|
||||
# ---------
|
||||
H.append("</div>") # /etud-insidebar
|
||||
# Logo
|
||||
H.append(
|
||||
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/>
|
||||
<a href="{ scu.SCO_USER_MANUAL }" target="_blank" class="sidebar">Aide</a>
|
||||
</div></div>
|
||||
<div class="logo-logo">
|
||||
<a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }">
|
||||
{ scu.icontag("scologo_img", no_size=True) }</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of sidebar -->
|
||||
"""
|
||||
)
|
||||
return "".join(H)
|
||||
|
||||
|
||||
def sidebar_dept():
|
||||
"""Partie supérieure de la marge de gauche"""
|
||||
return render_template(
|
||||
"sidebar_dept.html",
|
||||
prefs=sco_preferences.SemPreferences(),
|
||||
)
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
Génération de la "sidebar" (marge gauche des pages HTML)
|
||||
"""
|
||||
from flask import render_template, url_for
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from sco_version import SCOVERSION
|
||||
|
||||
|
||||
def sidebar_common():
|
||||
"partie commune à toutes les sidebar"
|
||||
home_link = url_for("scodoc.index", scodoc_dept=g.scodoc_dept)
|
||||
H = [
|
||||
f"""<a class="scodoc_title" href="{home_link}">ScoDoc {SCOVERSION}</a><br>
|
||||
<a href="{home_link}" class="sidebar">Accueil</a> <br>
|
||||
<div id="authuser"><a id="authuserlink" href="{
|
||||
url_for("users.user_info_page",
|
||||
scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
||||
}">{current_user.user_name}</a>
|
||||
<br><a id="deconnectlink" href="{url_for("auth.logout")}">déconnexion</a>
|
||||
</div>
|
||||
{sidebar_dept()}
|
||||
<h2 class="insidebar">Scolarité</h2>
|
||||
<a href="{scu.ScoURL()}" class="sidebar">Semestres</a> <br>
|
||||
<a href="{scu.NotesURL()}" class="sidebar">Programmes</a> <br>
|
||||
<a href="{scu.AbsencesURL()}" class="sidebar">Absences</a> <br>
|
||||
"""
|
||||
]
|
||||
if current_user.has_permission(
|
||||
Permission.ScoUsersAdmin
|
||||
) or current_user.has_permission(Permission.ScoUsersView):
|
||||
H.append(
|
||||
f"""<a href="{scu.UsersURL()}" class="sidebar">Utilisateurs</a> <br>"""
|
||||
)
|
||||
|
||||
if current_user.has_permission(Permission.ScoChangePreferences):
|
||||
H.append(
|
||||
f"""<a href="{url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}"
|
||||
class="sidebar">Paramétrage</a> <br>"""
|
||||
)
|
||||
|
||||
return "".join(H)
|
||||
|
||||
|
||||
def sidebar(etudid: int = None):
|
||||
"Main HTML page sidebar"
|
||||
# rewritten from legacy DTML code
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_etud
|
||||
|
||||
params = {}
|
||||
|
||||
H = [
|
||||
f"""<div class="sidebar">
|
||||
{ sidebar_common() }
|
||||
<div class="box-chercheetud">Chercher étudiant:<br>
|
||||
<form method="get" id="form-chercheetud"
|
||||
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>
|
||||
</form></div>
|
||||
<div class="etud-insidebar">
|
||||
"""
|
||||
]
|
||||
# ---- Il y-a-t-il un etudiant selectionné ?
|
||||
etudid = etudid if etudid is not None else g.get("etudid", None)
|
||||
if etudid is None:
|
||||
if request.method == "GET":
|
||||
etudid = request.args.get("etudid", None)
|
||||
elif request.method == "POST":
|
||||
etudid = request.form.get("etudid", None)
|
||||
|
||||
if etudid is not None:
|
||||
etudi = int(etudid)
|
||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||
params.update(etud)
|
||||
params["fiche_url"] = url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||
)
|
||||
# compte les absences du semestre en cours
|
||||
H.append(
|
||||
"""<h2 id="insidebar-etud"><a href="%(fiche_url)s" class="sidebar">
|
||||
<font color="#FF0000">%(civilite_str)s %(nom_disp)s</font></a>
|
||||
</h2>
|
||||
<b>Absences</b>"""
|
||||
% params
|
||||
)
|
||||
if etud["cursem"]:
|
||||
cur_sem = etud["cursem"]
|
||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, cur_sem)
|
||||
nbabsnj = nbabs - nbabsjust
|
||||
H.append(
|
||||
f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">(1/2 j.)
|
||||
<br>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
|
||||
)
|
||||
H.append("<ul>")
|
||||
if current_user.has_permission(Permission.ScoAbsChange):
|
||||
H.append(
|
||||
f"""
|
||||
<li><a href="{ url_for('absences.SignaleAbsenceEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Ajouter</a></li>
|
||||
<li><a href="{ url_for('absences.JustifAbsenceEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Justifier</a></li>
|
||||
<li><a href="{ url_for('absences.AnnuleAbsenceEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Supprimer</a></li>
|
||||
"""
|
||||
)
|
||||
if sco_preferences.get_preference("handle_billets_abs"):
|
||||
H.append(
|
||||
f"""<li><a href="{ url_for('absences.billets_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Billets</a></li>"""
|
||||
)
|
||||
H.append(
|
||||
f"""
|
||||
<li><a href="{ url_for('absences.CalAbs', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Calendrier</a></li>
|
||||
<li><a href="{ url_for('absences.ListeAbsEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Liste</a></li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
else:
|
||||
pass # H.append("(pas d'étudiant en cours)")
|
||||
# ---------
|
||||
H.append("</div>") # /etud-insidebar
|
||||
# Logo
|
||||
H.append(
|
||||
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>
|
||||
<a href="{ scu.SCO_USER_MANUAL }" target="_blank" class="sidebar">Aide</a>
|
||||
</div></div>
|
||||
<div class="logo-logo">
|
||||
<a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }">
|
||||
{ scu.icontag("scologo_img", no_size=True) }</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of sidebar -->
|
||||
"""
|
||||
)
|
||||
return "".join(H)
|
||||
|
||||
|
||||
def sidebar_dept():
|
||||
"""Partie supérieure de la marge de gauche"""
|
||||
return render_template(
|
||||
"sidebar_dept.html",
|
||||
prefs=sco_preferences.SemPreferences(),
|
||||
)
|
||||
|
|
|
@ -1,80 +1,80 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from html.parser import HTMLParser
|
||||
|
||||
|
||||
"""HTML sanitizing function
|
||||
used to clean user submitted HTML
|
||||
(Python 3 only)
|
||||
"""
|
||||
|
||||
# permet de conserver les liens
|
||||
def html_to_safe_html(text, convert_br=True): # was HTML2SafeHTML
|
||||
# text = html_to_safe_html(text, valid_tags=("b", "a", "i", "br", "p"))
|
||||
# New version (jul 2021) with our own parser
|
||||
text = convert_html_to_text(text)
|
||||
if convert_br:
|
||||
return newline_to_br(text)
|
||||
else:
|
||||
return text
|
||||
|
||||
|
||||
def convert_html_to_text(s):
|
||||
parser = HTMLSanitizer()
|
||||
parser.feed(s)
|
||||
return parser.text
|
||||
|
||||
|
||||
def newline_to_br(text):
|
||||
return text.replace("\n", "<br/>")
|
||||
|
||||
|
||||
class HTMLSanitizer(HTMLParser):
|
||||
def __init__(self, allowed_tags=("i", "b", "em", "br", "p"), **kwargs):
|
||||
super(HTMLSanitizer, self).__init__(**kwargs)
|
||||
self.allowed_tags = set(allowed_tags)
|
||||
self.text = ""
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag in self.allowed_tags:
|
||||
self.text += "<{} {}>".format(
|
||||
tag, ", ".join(['{}="{}"'.format(k, v) for (k, v) in attrs])
|
||||
)
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag in self.allowed_tags:
|
||||
self.text += "</" + tag + ">"
|
||||
|
||||
def handle_data(self, data):
|
||||
self.text += data
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_parser = HTMLSanitizer()
|
||||
test_parser.feed("""<p>Hello world <b z="1" >gras</b> <i a="2">italique</i></p>""")
|
||||
print(test_parser.text)
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from html.parser import HTMLParser
|
||||
|
||||
|
||||
"""HTML sanitizing function
|
||||
used to clean user submitted HTML
|
||||
(Python 3 only)
|
||||
"""
|
||||
|
||||
# permet de conserver les liens
|
||||
def html_to_safe_html(text, convert_br=True): # was HTML2SafeHTML
|
||||
# text = html_to_safe_html(text, valid_tags=("b", "a", "i", "br", "p"))
|
||||
# New version (jul 2021) with our own parser
|
||||
text = convert_html_to_text(text)
|
||||
if convert_br:
|
||||
return newline_to_br(text)
|
||||
else:
|
||||
return text
|
||||
|
||||
|
||||
def convert_html_to_text(s):
|
||||
parser = HTMLSanitizer()
|
||||
parser.feed(s)
|
||||
return parser.text
|
||||
|
||||
|
||||
def newline_to_br(text):
|
||||
return text.replace("\n", "<br>")
|
||||
|
||||
|
||||
class HTMLSanitizer(HTMLParser):
|
||||
def __init__(self, allowed_tags=("i", "b", "em", "br", "p"), **kwargs):
|
||||
super(HTMLSanitizer, self).__init__(**kwargs)
|
||||
self.allowed_tags = set(allowed_tags)
|
||||
self.text = ""
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag in self.allowed_tags:
|
||||
self.text += "<{} {}>".format(
|
||||
tag, ", ".join(['{}="{}"'.format(k, v) for (k, v) in attrs])
|
||||
)
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag in self.allowed_tags:
|
||||
self.text += "</" + tag + ">"
|
||||
|
||||
def handle_data(self, data):
|
||||
self.text += data
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_parser = HTMLSanitizer()
|
||||
test_parser.feed("""<p>Hello world <b z="1" >gras</b> <i a="2">italique</i></p>""")
|
||||
print(test_parser.text)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,386 +1,386 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""ScoDoc : gestion des fichiers archivés associés aux étudiants
|
||||
Il s'agit de fichiers quelconques, généralement utilisés pour conserver
|
||||
les dossiers d'admission et autres pièces utiles.
|
||||
"""
|
||||
import flask
|
||||
from flask import url_for, render_template
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_import_etuds
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_trombino
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_archives
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_etud
|
||||
|
||||
|
||||
class EtudsArchiver(sco_archives.BaseArchiver):
|
||||
def __init__(self):
|
||||
sco_archives.BaseArchiver.__init__(self, archive_type="docetuds")
|
||||
|
||||
|
||||
EtudsArchive = EtudsArchiver()
|
||||
|
||||
|
||||
def can_edit_etud_archive(authuser):
|
||||
"""True si l'utilisateur peut modifier les archives etudiantes"""
|
||||
return authuser.has_permission(Permission.ScoEtudAddAnnotations)
|
||||
|
||||
|
||||
def etud_list_archives_html(etudid):
|
||||
"""HTML snippet listing archives"""
|
||||
can_edit = can_edit_etud_archive(current_user)
|
||||
etuds = sco_etud.get_etud_info(etudid=etudid)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
etud_archive_id = etudid
|
||||
L = []
|
||||
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
||||
a = {
|
||||
"archive_id": archive_id,
|
||||
"description": EtudsArchive.get_archive_description(archive_id),
|
||||
"date": EtudsArchive.get_archive_date(archive_id),
|
||||
"content": EtudsArchive.list_archive(archive_id),
|
||||
}
|
||||
L.append(a)
|
||||
delete_icon = scu.icontag(
|
||||
"delete_small_img", title="Supprimer fichier", alt="supprimer"
|
||||
)
|
||||
delete_disabled_icon = scu.icontag(
|
||||
"delete_small_dis_img", title="Suppression non autorisée"
|
||||
)
|
||||
H = ['<div class="etudarchive"><ul>']
|
||||
for a in L:
|
||||
archive_name = EtudsArchive.get_archive_name(a["archive_id"])
|
||||
H.append(
|
||||
"""<li><span class ="etudarchive_descr" title="%s">%s</span>"""
|
||||
% (a["date"].strftime("%d/%m/%Y %H:%M"), a["description"])
|
||||
)
|
||||
for filename in a["content"]:
|
||||
H.append(
|
||||
"""<a class="stdlink etudarchive_link" href="etud_get_archived_file?etudid=%s&archive_name=%s&filename=%s">%s</a>"""
|
||||
% (etudid, archive_name, filename, filename)
|
||||
)
|
||||
if not a["content"]:
|
||||
H.append("<em>aucun fichier !</em>")
|
||||
if can_edit:
|
||||
H.append(
|
||||
'<span class="deletudarchive"><a class="smallbutton" href="etud_delete_archive?etudid=%s&archive_name=%s">%s</a></span>'
|
||||
% (etudid, archive_name, delete_icon)
|
||||
)
|
||||
else:
|
||||
H.append('<span class="deletudarchive">' + delete_disabled_icon + "</span>")
|
||||
H.append("</li>")
|
||||
if can_edit:
|
||||
H.append(
|
||||
'<li class="addetudarchive"><a class="stdlink" href="etud_upload_file_form?etudid=%s">ajouter un fichier</a></li>'
|
||||
% etudid
|
||||
)
|
||||
H.append("</ul></div>")
|
||||
return "".join(H)
|
||||
|
||||
|
||||
def add_archives_info_to_etud_list(etuds):
|
||||
"""Add key 'etudarchive' describing archive of etuds
|
||||
(used to list all archives of a group)
|
||||
"""
|
||||
for etud in etuds:
|
||||
l = []
|
||||
etud_archive_id = etud["etudid"]
|
||||
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
||||
l.append(
|
||||
"%s (%s)"
|
||||
% (
|
||||
EtudsArchive.get_archive_description(archive_id),
|
||||
EtudsArchive.list_archive(archive_id)[0],
|
||||
)
|
||||
)
|
||||
etud["etudarchive"] = ", ".join(l)
|
||||
|
||||
|
||||
def etud_upload_file_form(etudid):
|
||||
"""Page with a form to choose and upload a file, with a description."""
|
||||
# check permission
|
||||
if not can_edit_etud_archive(current_user):
|
||||
raise AccessDenied("opération non autorisée pour %s" % current_user)
|
||||
etuds = sco_etud.get_etud_info(filled=True)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Chargement d'un document associé à %(nomprenom)s" % etud,
|
||||
),
|
||||
"""<h2>Chargement d'un document associé à %(nomprenom)s</h2>
|
||||
"""
|
||||
% etud,
|
||||
"""<p>Le fichier ne doit pas dépasser %sMo.</p>
|
||||
"""
|
||||
% (scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)),
|
||||
]
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
scu.get_request_args(),
|
||||
(
|
||||
("etudid", {"default": etudid, "input_type": "hidden"}),
|
||||
("datafile", {"input_type": "file", "title": "Fichier", "size": 30}),
|
||||
(
|
||||
"description",
|
||||
{
|
||||
"input_type": "textarea",
|
||||
"rows": 4,
|
||||
"cols": 77,
|
||||
"title": "Description",
|
||||
},
|
||||
),
|
||||
),
|
||||
submitlabel="Valider",
|
||||
cancelbutton="Annuler",
|
||||
)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
||||
elif tf[0] == -1:
|
||||
return flask.redirect(
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
)
|
||||
else:
|
||||
data = tf[2]["datafile"].read()
|
||||
descr = tf[2]["description"]
|
||||
filename = tf[2]["datafile"].filename
|
||||
etud_archive_id = etud["etudid"]
|
||||
_store_etud_file_to_new_archive(
|
||||
etud_archive_id, data, filename, description=descr
|
||||
)
|
||||
return flask.redirect(
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
)
|
||||
|
||||
|
||||
def _store_etud_file_to_new_archive(
|
||||
etud_archive_id, data, filename, description=""
|
||||
) -> tuple[bool, str]:
|
||||
"""Store data to new archive."""
|
||||
filesize = len(data)
|
||||
if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE:
|
||||
return False, f"Fichier archive '{filename}' de taille invalide ! ({filesize})"
|
||||
archive_id = EtudsArchive.create_obj_archive(etud_archive_id, description)
|
||||
EtudsArchive.store(archive_id, filename, data)
|
||||
return True, "ok"
|
||||
|
||||
|
||||
def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
||||
"""Delete an archive"""
|
||||
# check permission
|
||||
if not can_edit_etud_archive(current_user):
|
||||
raise AccessDenied(f"opération non autorisée pour {current_user}")
|
||||
etuds = sco_etud.get_etud_info(filled=True)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
etud_archive_id = etud["etudid"]
|
||||
archive_id = EtudsArchive.get_id_from_name(etud_archive_id, archive_name)
|
||||
if not dialog_confirmed:
|
||||
return scu.confirm_dialog(
|
||||
"""<h2>Confirmer la suppression des fichiers ?</h2>
|
||||
<p>Fichier associé le %s à l'étudiant %s</p>
|
||||
<p>La suppression sera définitive.</p>"""
|
||||
% (
|
||||
EtudsArchive.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M"),
|
||||
etud["nomprenom"],
|
||||
),
|
||||
dest_url="",
|
||||
cancel_url=url_for(
|
||||
"scolar.ficheEtud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etudid,
|
||||
head_message="annulation",
|
||||
),
|
||||
parameters={"etudid": etudid, "archive_name": archive_name},
|
||||
)
|
||||
|
||||
EtudsArchive.delete_archive(archive_id)
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"scolar.ficheEtud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etudid,
|
||||
head_message="Archive%20supprimée",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def etud_get_archived_file(etudid, archive_name, filename):
|
||||
"""Send file to client."""
|
||||
etuds = sco_etud.get_etud_info(etudid=etudid, filled=True)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
etud_archive_id = etud["etudid"]
|
||||
return EtudsArchive.get_archived_file(etud_archive_id, archive_name, filename)
|
||||
|
||||
|
||||
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
|
||||
def etudarchive_generate_excel_sample(group_id=None):
|
||||
"""Feuille excel pour import fichiers etudiants (utilisé pour admissions)"""
|
||||
fmt = sco_import_etuds.sco_import_format()
|
||||
data = sco_import_etuds.sco_import_generate_excel_sample(
|
||||
fmt,
|
||||
group_ids=[group_id],
|
||||
only_tables=["identite"],
|
||||
exclude_cols=[
|
||||
"date_naissance",
|
||||
"lieu_naissance",
|
||||
"nationalite",
|
||||
"statut",
|
||||
"photo_filename",
|
||||
],
|
||||
extra_cols=["fichier_a_charger"],
|
||||
)
|
||||
return scu.send_file(
|
||||
data,
|
||||
"ImportFichiersEtudiants",
|
||||
suffix=scu.XLSX_SUFFIX,
|
||||
mime=scu.XLSX_MIMETYPE,
|
||||
)
|
||||
|
||||
|
||||
def etudarchive_import_files_form(group_id):
|
||||
"""Formulaire pour importation fichiers d'un groupe"""
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Import de fichiers associés aux étudiants"
|
||||
),
|
||||
"""<h2 class="formsemestre">Téléchargement de fichier associés aux étudiants</h2>
|
||||
<p>Les fichiers associés (dossiers d'admission, certificats, ...), de
|
||||
types quelconques (pdf, doc, images) sont accessibles aux utilisateurs via
|
||||
la fiche individuelle de l'étudiant.
|
||||
</p>
|
||||
<p class="warning">Ne pas confondre avec les photos des étudiants, qui se
|
||||
chargent via l'onglet "Photos".</p>
|
||||
<p><b>Vous pouvez aussi charger à tout moment de nouveaux fichiers, ou en
|
||||
supprimer, via la fiche de chaque étudiant.</b>
|
||||
</p>
|
||||
<p class="help">Cette page permet de charger en une seule fois les fichiers
|
||||
de plusieurs étudiants.<br/>
|
||||
Il faut d'abord remplir une feuille excel donnant les noms
|
||||
des fichiers (un fichier par étudiant).
|
||||
</p>
|
||||
<p class="help">Ensuite, réunir vos fichiers dans un fichier zip, puis
|
||||
télécharger simultanément le fichier excel et le fichier zip.
|
||||
</p>
|
||||
<ol>
|
||||
<li><a class="stdlink" href="etudarchive_generate_excel_sample?group_id=%s">
|
||||
Obtenir la feuille excel à remplir</a>
|
||||
</li>
|
||||
<li style="padding-top: 2em;">
|
||||
"""
|
||||
% group_id,
|
||||
]
|
||||
F = html_sco_header.sco_footer()
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
scu.get_request_args(),
|
||||
(
|
||||
("xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}),
|
||||
("zipfile", {"title": "Fichier zip:", "input_type": "file", "size": 40}),
|
||||
(
|
||||
"description",
|
||||
{
|
||||
"input_type": "textarea",
|
||||
"rows": 4,
|
||||
"cols": 77,
|
||||
"title": "Description",
|
||||
},
|
||||
),
|
||||
("group_id", {"input_type": "hidden"}),
|
||||
),
|
||||
)
|
||||
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + tf[1] + "</li></ol>" + F
|
||||
# retrouve le semestre à partir du groupe:
|
||||
group = sco_groups.get_group(group_id)
|
||||
if tf[0] == -1:
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=group["formsemestre_id"],
|
||||
)
|
||||
)
|
||||
else:
|
||||
return etudarchive_import_files(
|
||||
formsemestre_id=group["formsemestre_id"],
|
||||
xlsfile=tf[2]["xlsfile"],
|
||||
zipfile=tf[2]["zipfile"],
|
||||
description=tf[2]["description"],
|
||||
)
|
||||
|
||||
|
||||
def etudarchive_import_files(
|
||||
formsemestre_id=None, xlsfile=None, zipfile=None, description=""
|
||||
):
|
||||
"Importe des fichiers"
|
||||
|
||||
def callback(etud, data, filename):
|
||||
return _store_etud_file_to_new_archive(
|
||||
etud["etudid"], data, filename, description
|
||||
)
|
||||
|
||||
# Utilise la fontion developpée au depart pour les photos
|
||||
(
|
||||
ignored_zipfiles,
|
||||
unmatched_files,
|
||||
stored_etud_filename,
|
||||
) = sco_trombino.zip_excel_import_files(
|
||||
xlsfile=xlsfile,
|
||||
zipfile=zipfile,
|
||||
callback=callback,
|
||||
filename_title="fichier_a_charger",
|
||||
)
|
||||
return render_template(
|
||||
"scolar/photos_import_files.html",
|
||||
page_title="Téléchargement de fichiers associés aux étudiants",
|
||||
ignored_zipfiles=ignored_zipfiles,
|
||||
unmatched_files=unmatched_files,
|
||||
stored_etud_filename=stored_etud_filename,
|
||||
next_page=url_for(
|
||||
"scolar.groups_view",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
),
|
||||
)
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""ScoDoc : gestion des fichiers archivés associés aux étudiants
|
||||
Il s'agit de fichiers quelconques, généralement utilisés pour conserver
|
||||
les dossiers d'admission et autres pièces utiles.
|
||||
"""
|
||||
import flask
|
||||
from flask import url_for, render_template
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_import_etuds
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_trombino
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_archives
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_etud
|
||||
|
||||
|
||||
class EtudsArchiver(sco_archives.BaseArchiver):
|
||||
def __init__(self):
|
||||
sco_archives.BaseArchiver.__init__(self, archive_type="docetuds")
|
||||
|
||||
|
||||
EtudsArchive = EtudsArchiver()
|
||||
|
||||
|
||||
def can_edit_etud_archive(authuser):
|
||||
"""True si l'utilisateur peut modifier les archives etudiantes"""
|
||||
return authuser.has_permission(Permission.ScoEtudAddAnnotations)
|
||||
|
||||
|
||||
def etud_list_archives_html(etudid):
|
||||
"""HTML snippet listing archives"""
|
||||
can_edit = can_edit_etud_archive(current_user)
|
||||
etuds = sco_etud.get_etud_info(etudid=etudid)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
etud_archive_id = etudid
|
||||
L = []
|
||||
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
||||
a = {
|
||||
"archive_id": archive_id,
|
||||
"description": EtudsArchive.get_archive_description(archive_id),
|
||||
"date": EtudsArchive.get_archive_date(archive_id),
|
||||
"content": EtudsArchive.list_archive(archive_id),
|
||||
}
|
||||
L.append(a)
|
||||
delete_icon = scu.icontag(
|
||||
"delete_small_img", title="Supprimer fichier", alt="supprimer"
|
||||
)
|
||||
delete_disabled_icon = scu.icontag(
|
||||
"delete_small_dis_img", title="Suppression non autorisée"
|
||||
)
|
||||
H = ['<div class="etudarchive"><ul>']
|
||||
for a in L:
|
||||
archive_name = EtudsArchive.get_archive_name(a["archive_id"])
|
||||
H.append(
|
||||
"""<li><span class ="etudarchive_descr" title="%s">%s</span>"""
|
||||
% (a["date"].strftime("%d/%m/%Y %H:%M"), a["description"])
|
||||
)
|
||||
for filename in a["content"]:
|
||||
H.append(
|
||||
"""<a class="stdlink etudarchive_link" href="etud_get_archived_file?etudid=%s&archive_name=%s&filename=%s">%s</a>"""
|
||||
% (etudid, archive_name, filename, filename)
|
||||
)
|
||||
if not a["content"]:
|
||||
H.append("<em>aucun fichier !</em>")
|
||||
if can_edit:
|
||||
H.append(
|
||||
'<span class="deletudarchive"><a class="smallbutton" href="etud_delete_archive?etudid=%s&archive_name=%s">%s</a></span>'
|
||||
% (etudid, archive_name, delete_icon)
|
||||
)
|
||||
else:
|
||||
H.append('<span class="deletudarchive">' + delete_disabled_icon + "</span>")
|
||||
H.append("</li>")
|
||||
if can_edit:
|
||||
H.append(
|
||||
'<li class="addetudarchive"><a class="stdlink" href="etud_upload_file_form?etudid=%s">ajouter un fichier</a></li>'
|
||||
% etudid
|
||||
)
|
||||
H.append("</ul></div>")
|
||||
return "".join(H)
|
||||
|
||||
|
||||
def add_archives_info_to_etud_list(etuds):
|
||||
"""Add key 'etudarchive' describing archive of etuds
|
||||
(used to list all archives of a group)
|
||||
"""
|
||||
for etud in etuds:
|
||||
l = []
|
||||
etud_archive_id = etud["etudid"]
|
||||
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
||||
l.append(
|
||||
"%s (%s)"
|
||||
% (
|
||||
EtudsArchive.get_archive_description(archive_id),
|
||||
EtudsArchive.list_archive(archive_id)[0],
|
||||
)
|
||||
)
|
||||
etud["etudarchive"] = ", ".join(l)
|
||||
|
||||
|
||||
def etud_upload_file_form(etudid):
|
||||
"""Page with a form to choose and upload a file, with a description."""
|
||||
# check permission
|
||||
if not can_edit_etud_archive(current_user):
|
||||
raise AccessDenied("opération non autorisée pour %s" % current_user)
|
||||
etuds = sco_etud.get_etud_info(filled=True)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Chargement d'un document associé à %(nomprenom)s" % etud,
|
||||
),
|
||||
"""<h2>Chargement d'un document associé à %(nomprenom)s</h2>
|
||||
"""
|
||||
% etud,
|
||||
"""<p>Le fichier ne doit pas dépasser %sMo.</p>
|
||||
"""
|
||||
% (scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)),
|
||||
]
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
scu.get_request_args(),
|
||||
(
|
||||
("etudid", {"default": etudid, "input_type": "hidden"}),
|
||||
("datafile", {"input_type": "file", "title": "Fichier", "size": 30}),
|
||||
(
|
||||
"description",
|
||||
{
|
||||
"input_type": "textarea",
|
||||
"rows": 4,
|
||||
"cols": 77,
|
||||
"title": "Description",
|
||||
},
|
||||
),
|
||||
),
|
||||
submitlabel="Valider",
|
||||
cancelbutton="Annuler",
|
||||
)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
||||
elif tf[0] == -1:
|
||||
return flask.redirect(
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
)
|
||||
else:
|
||||
data = tf[2]["datafile"].read()
|
||||
descr = tf[2]["description"]
|
||||
filename = tf[2]["datafile"].filename
|
||||
etud_archive_id = etud["etudid"]
|
||||
_store_etud_file_to_new_archive(
|
||||
etud_archive_id, data, filename, description=descr
|
||||
)
|
||||
return flask.redirect(
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
)
|
||||
|
||||
|
||||
def _store_etud_file_to_new_archive(
|
||||
etud_archive_id, data, filename, description=""
|
||||
) -> tuple[bool, str]:
|
||||
"""Store data to new archive."""
|
||||
filesize = len(data)
|
||||
if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE:
|
||||
return False, f"Fichier archive '{filename}' de taille invalide ! ({filesize})"
|
||||
archive_id = EtudsArchive.create_obj_archive(etud_archive_id, description)
|
||||
EtudsArchive.store(archive_id, filename, data)
|
||||
return True, "ok"
|
||||
|
||||
|
||||
def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
||||
"""Delete an archive"""
|
||||
# check permission
|
||||
if not can_edit_etud_archive(current_user):
|
||||
raise AccessDenied(f"opération non autorisée pour {current_user}")
|
||||
etuds = sco_etud.get_etud_info(filled=True)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
etud_archive_id = etud["etudid"]
|
||||
archive_id = EtudsArchive.get_id_from_name(etud_archive_id, archive_name)
|
||||
if not dialog_confirmed:
|
||||
return scu.confirm_dialog(
|
||||
"""<h2>Confirmer la suppression des fichiers ?</h2>
|
||||
<p>Fichier associé le %s à l'étudiant %s</p>
|
||||
<p>La suppression sera définitive.</p>"""
|
||||
% (
|
||||
EtudsArchive.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M"),
|
||||
etud["nomprenom"],
|
||||
),
|
||||
dest_url="",
|
||||
cancel_url=url_for(
|
||||
"scolar.ficheEtud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etudid,
|
||||
head_message="annulation",
|
||||
),
|
||||
parameters={"etudid": etudid, "archive_name": archive_name},
|
||||
)
|
||||
|
||||
EtudsArchive.delete_archive(archive_id)
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"scolar.ficheEtud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etudid,
|
||||
head_message="Archive%20supprimée",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def etud_get_archived_file(etudid, archive_name, filename):
|
||||
"""Send file to client."""
|
||||
etuds = sco_etud.get_etud_info(etudid=etudid, filled=True)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
etud_archive_id = etud["etudid"]
|
||||
return EtudsArchive.get_archived_file(etud_archive_id, archive_name, filename)
|
||||
|
||||
|
||||
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
|
||||
def etudarchive_generate_excel_sample(group_id=None):
|
||||
"""Feuille excel pour import fichiers etudiants (utilisé pour admissions)"""
|
||||
fmt = sco_import_etuds.sco_import_format()
|
||||
data = sco_import_etuds.sco_import_generate_excel_sample(
|
||||
fmt,
|
||||
group_ids=[group_id],
|
||||
only_tables=["identite"],
|
||||
exclude_cols=[
|
||||
"date_naissance",
|
||||
"lieu_naissance",
|
||||
"nationalite",
|
||||
"statut",
|
||||
"photo_filename",
|
||||
],
|
||||
extra_cols=["fichier_a_charger"],
|
||||
)
|
||||
return scu.send_file(
|
||||
data,
|
||||
"ImportFichiersEtudiants",
|
||||
suffix=scu.XLSX_SUFFIX,
|
||||
mime=scu.XLSX_MIMETYPE,
|
||||
)
|
||||
|
||||
|
||||
def etudarchive_import_files_form(group_id):
|
||||
"""Formulaire pour importation fichiers d'un groupe"""
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Import de fichiers associés aux étudiants"
|
||||
),
|
||||
"""<h2 class="formsemestre">Téléchargement de fichier associés aux étudiants</h2>
|
||||
<p>Les fichiers associés (dossiers d'admission, certificats, ...), de
|
||||
types quelconques (pdf, doc, images) sont accessibles aux utilisateurs via
|
||||
la fiche individuelle de l'étudiant.
|
||||
</p>
|
||||
<p class="warning">Ne pas confondre avec les photos des étudiants, qui se
|
||||
chargent via l'onglet "Photos".</p>
|
||||
<p><b>Vous pouvez aussi charger à tout moment de nouveaux fichiers, ou en
|
||||
supprimer, via la fiche de chaque étudiant.</b>
|
||||
</p>
|
||||
<p class="help">Cette page permet de charger en une seule fois les fichiers
|
||||
de plusieurs étudiants.<br>
|
||||
Il faut d'abord remplir une feuille excel donnant les noms
|
||||
des fichiers (un fichier par étudiant).
|
||||
</p>
|
||||
<p class="help">Ensuite, réunir vos fichiers dans un fichier zip, puis
|
||||
télécharger simultanément le fichier excel et le fichier zip.
|
||||
</p>
|
||||
<ol>
|
||||
<li><a class="stdlink" href="etudarchive_generate_excel_sample?group_id=%s">
|
||||
Obtenir la feuille excel à remplir</a>
|
||||
</li>
|
||||
<li style="padding-top: 2em;">
|
||||
"""
|
||||
% group_id,
|
||||
]
|
||||
F = html_sco_header.sco_footer()
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
scu.get_request_args(),
|
||||
(
|
||||
("xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}),
|
||||
("zipfile", {"title": "Fichier zip:", "input_type": "file", "size": 40}),
|
||||
(
|
||||
"description",
|
||||
{
|
||||
"input_type": "textarea",
|
||||
"rows": 4,
|
||||
"cols": 77,
|
||||
"title": "Description",
|
||||
},
|
||||
),
|
||||
("group_id", {"input_type": "hidden"}),
|
||||
),
|
||||
)
|
||||
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + tf[1] + "</li></ol>" + F
|
||||
# retrouve le semestre à partir du groupe:
|
||||
group = sco_groups.get_group(group_id)
|
||||
if tf[0] == -1:
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=group["formsemestre_id"],
|
||||
)
|
||||
)
|
||||
else:
|
||||
return etudarchive_import_files(
|
||||
formsemestre_id=group["formsemestre_id"],
|
||||
xlsfile=tf[2]["xlsfile"],
|
||||
zipfile=tf[2]["zipfile"],
|
||||
description=tf[2]["description"],
|
||||
)
|
||||
|
||||
|
||||
def etudarchive_import_files(
|
||||
formsemestre_id=None, xlsfile=None, zipfile=None, description=""
|
||||
):
|
||||
"Importe des fichiers"
|
||||
|
||||
def callback(etud, data, filename):
|
||||
return _store_etud_file_to_new_archive(
|
||||
etud["etudid"], data, filename, description
|
||||
)
|
||||
|
||||
# Utilise la fontion developpée au depart pour les photos
|
||||
(
|
||||
ignored_zipfiles,
|
||||
unmatched_files,
|
||||
stored_etud_filename,
|
||||
) = sco_trombino.zip_excel_import_files(
|
||||
xlsfile=xlsfile,
|
||||
zipfile=zipfile,
|
||||
callback=callback,
|
||||
filename_title="fichier_a_charger",
|
||||
)
|
||||
return render_template(
|
||||
"scolar/photos_import_files.html",
|
||||
page_title="Téléchargement de fichiers associés aux étudiants",
|
||||
ignored_zipfiles=ignored_zipfiles,
|
||||
unmatched_files=unmatched_files,
|
||||
stored_etud_filename=stored_etud_filename,
|
||||
next_page=url_for(
|
||||
"scolar.groups_view",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1,368 +1,368 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@gmail.com
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Génération des bulletins de note: super-classe pour les générateurs (HTML et PDF)
|
||||
|
||||
class BulletinGenerator:
|
||||
description
|
||||
supported_formats = [ 'pdf', 'html' ]
|
||||
.bul_title_pdf()
|
||||
.bul_table(format)
|
||||
.bul_part_below(format)
|
||||
.bul_signatures_pdf()
|
||||
|
||||
.__init__ et .generate(format) methodes appelees par le client (sco_bulletin)
|
||||
|
||||
La préférence 'bul_class_name' donne le nom de la classe generateur.
|
||||
La préférence 'bul_pdf_class_name' est obsolete (inutilisée).
|
||||
|
||||
|
||||
"""
|
||||
import collections
|
||||
import io
|
||||
import time
|
||||
import traceback
|
||||
|
||||
|
||||
import reportlab
|
||||
from reportlab.platypus import (
|
||||
SimpleDocTemplate,
|
||||
DocIf,
|
||||
Paragraph,
|
||||
Spacer,
|
||||
Frame,
|
||||
PageBreak,
|
||||
)
|
||||
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
|
||||
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import NoteProcessError
|
||||
from app import log
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_pdf
|
||||
from app.scodoc.sco_pdf import PDFLOCK
|
||||
import sco_version
|
||||
|
||||
|
||||
class BulletinGenerator:
|
||||
"Virtual superclass for PDF bulletin generators" ""
|
||||
# Here some helper methods
|
||||
# see sco_bulletins_standard.BulletinGeneratorStandard subclass for real methods
|
||||
supported_formats = [] # should list supported formats, eg [ 'html', 'pdf' ]
|
||||
description = "superclass for bulletins" # description for user interface
|
||||
list_in_menu = True # la classe doit-elle est montrée dans le menu de config ?
|
||||
scale_table_in_page = True # rescale la table sur 1 page
|
||||
multi_pages = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
infos,
|
||||
authuser=None,
|
||||
version="long",
|
||||
filigranne=None,
|
||||
server_name=None,
|
||||
):
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
if not version in scu.BULLETINS_VERSIONS:
|
||||
raise ValueError("invalid version code !")
|
||||
self.infos = infos
|
||||
self.authuser = authuser # nécessaire pour version HTML qui contient liens dépendant de l'utilisateur
|
||||
self.version = version
|
||||
self.filigranne = filigranne
|
||||
self.server_name = server_name
|
||||
# Store preferences for convenience:
|
||||
formsemestre_id = self.infos["formsemestre_id"]
|
||||
self.preferences = sco_preferences.SemPreferences(formsemestre_id)
|
||||
self.diagnostic = None # error message if any problem
|
||||
# Common PDF styles:
|
||||
# - Pour tous les champs du bulletin sauf les cellules de table:
|
||||
self.FieldStyle = reportlab.lib.styles.ParagraphStyle({})
|
||||
self.FieldStyle.fontName = self.preferences["SCOLAR_FONT_BUL_FIELDS"]
|
||||
self.FieldStyle.fontSize = self.preferences["SCOLAR_FONT_SIZE"]
|
||||
self.FieldStyle.firstLineIndent = 0
|
||||
# - Pour les cellules de table:
|
||||
self.CellStyle = reportlab.lib.styles.ParagraphStyle({})
|
||||
self.CellStyle.fontSize = self.preferences["SCOLAR_FONT_SIZE"]
|
||||
self.CellStyle.fontName = self.preferences["SCOLAR_FONT"]
|
||||
self.CellStyle.leading = (
|
||||
1.0 * self.preferences["SCOLAR_FONT_SIZE"]
|
||||
) # vertical space
|
||||
# Marges du document PDF
|
||||
self.margins = (
|
||||
self.preferences["left_margin"],
|
||||
self.preferences["top_margin"],
|
||||
self.preferences["right_margin"],
|
||||
self.preferences["bottom_margin"],
|
||||
)
|
||||
|
||||
def get_filename(self):
|
||||
"""Build a filename to be proposed to the web client"""
|
||||
sem = sco_formsemestre.get_formsemestre(self.infos["formsemestre_id"])
|
||||
return scu.bul_filename_old(sem, self.infos["etud"], "pdf")
|
||||
|
||||
def generate(self, format="", stand_alone=True):
|
||||
"""Return bulletin in specified format"""
|
||||
if not format in self.supported_formats:
|
||||
raise ValueError("unsupported bulletin format (%s)" % format)
|
||||
try:
|
||||
PDFLOCK.acquire() # this lock is necessary since reportlab is not re-entrant
|
||||
if format == "html":
|
||||
return self.generate_html()
|
||||
elif format == "pdf":
|
||||
return self.generate_pdf(stand_alone=stand_alone)
|
||||
else:
|
||||
raise ValueError("invalid bulletin format (%s)" % format)
|
||||
finally:
|
||||
PDFLOCK.release()
|
||||
|
||||
def generate_html(self):
|
||||
"""Return bulletin as an HTML string"""
|
||||
H = ['<div class="notes_bulletin">']
|
||||
# table des notes:
|
||||
H.append(self.bul_table(format="html")) # pylint: disable=no-member
|
||||
# infos sous la table:
|
||||
H.append(self.bul_part_below(format="html")) # pylint: disable=no-member
|
||||
H.append("</div>")
|
||||
return "\n".join(H)
|
||||
|
||||
def generate_pdf(self, stand_alone=True):
|
||||
"""Build PDF bulletin from distinct parts
|
||||
Si stand_alone, génère un doc PDF complet et renvoie une string
|
||||
Sinon, renvoie juste une liste d'objets PLATYPUS pour intégration
|
||||
dans un autre document.
|
||||
"""
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
formsemestre_id = self.infos["formsemestre_id"]
|
||||
marque_debut_bulletin = sco_pdf.DebutBulletin(
|
||||
self.infos["etud"]["nomprenom"],
|
||||
filigranne=self.infos["filigranne"],
|
||||
footer_content=f"""ScoDoc - Bulletin de {self.infos["etud"]["nomprenom"]} - {time.strftime("%d/%m/%Y %H:%M")}""",
|
||||
)
|
||||
story = []
|
||||
# partie haute du bulletin
|
||||
story += self.bul_title_pdf() # pylint: disable=no-member
|
||||
index_obj_debut = len(story)
|
||||
|
||||
# table des notes
|
||||
story += self.bul_table(format="pdf") # pylint: disable=no-member
|
||||
# infos sous la table
|
||||
story += self.bul_part_below(format="pdf") # pylint: disable=no-member
|
||||
# signatures
|
||||
story += self.bul_signatures_pdf() # pylint: disable=no-member
|
||||
if self.scale_table_in_page:
|
||||
# Réduit sur une page
|
||||
story = [marque_debut_bulletin, KeepInFrame(0, 0, story, mode="shrink")]
|
||||
else:
|
||||
# Insere notre marqueur qui permet de générer les bookmarks et filigrannes:
|
||||
story.insert(index_obj_debut, marque_debut_bulletin)
|
||||
#
|
||||
# objects.append(sco_pdf.FinBulletin())
|
||||
if not stand_alone:
|
||||
if self.multi_pages:
|
||||
# Bulletins sur plusieurs page, force début suivant sur page impaire
|
||||
story.append(
|
||||
DocIf("doc.page%2 == 1", [PageBreak(), PageBreak()], [PageBreak()])
|
||||
)
|
||||
else:
|
||||
story.append(PageBreak()) # insert page break at end
|
||||
|
||||
return story
|
||||
else:
|
||||
# Generation du document PDF
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
report = io.BytesIO() # in-memory document, no disk file
|
||||
document = sco_pdf.BaseDocTemplate(report)
|
||||
document.addPageTemplates(
|
||||
sco_pdf.ScoDocPageTemplate(
|
||||
document,
|
||||
author="%s %s (E. Viennet) [%s]"
|
||||
% (sco_version.SCONAME, sco_version.SCOVERSION, self.description),
|
||||
title="Bulletin %s de %s"
|
||||
% (sem["titremois"], self.infos["etud"]["nomprenom"]),
|
||||
subject="Bulletin de note",
|
||||
margins=self.margins,
|
||||
server_name=self.server_name,
|
||||
filigranne=self.filigranne,
|
||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||
)
|
||||
)
|
||||
document.build(story)
|
||||
data = report.getvalue()
|
||||
return data
|
||||
|
||||
def buildTableObject(self, P, pdfTableStyle, colWidths):
|
||||
"""Utility used by some old-style generators.
|
||||
Build a platypus Table instance from a nested list of cells, style and widths.
|
||||
P: table, as a list of lists
|
||||
PdfTableStyle: commandes de style pour la table (reportlab)
|
||||
"""
|
||||
try:
|
||||
# put each table cell in a Paragraph
|
||||
Pt = [
|
||||
[Paragraph(sco_pdf.SU(x), self.CellStyle) for x in line] for line in P
|
||||
]
|
||||
except:
|
||||
# enquête sur exception intermittente...
|
||||
log("*** bug in PDF buildTableObject:")
|
||||
log("P=%s" % P)
|
||||
# compris: reportlab is not thread safe !
|
||||
# see http://two.pairlist.net/pipermail/reportlab-users/2006-June/005037.html
|
||||
# (donc maintenant protégé dans ScoDoc par un Lock global)
|
||||
self.diagnostic = "erreur lors de la génération du PDF<br/>"
|
||||
self.diagnostic += "<pre>" + traceback.format_exc() + "</pre>"
|
||||
return []
|
||||
return Table(Pt, colWidths=colWidths, style=pdfTableStyle)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
def make_formsemestre_bulletinetud(
|
||||
infos,
|
||||
version=None, # short, long, selectedevals
|
||||
format="pdf", # html, pdf
|
||||
stand_alone=True,
|
||||
):
|
||||
"""Bulletin de notes
|
||||
|
||||
Appelle une fonction générant le bulletin au format spécifié à partir des informations infos,
|
||||
selon les préférences du semestre.
|
||||
|
||||
"""
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
version = version or "long"
|
||||
if not version in scu.BULLETINS_VERSIONS:
|
||||
raise ValueError("invalid version code !")
|
||||
|
||||
formsemestre_id = infos["formsemestre_id"]
|
||||
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
||||
|
||||
gen_class = None
|
||||
for bul_class_name in (
|
||||
sco_preferences.get_preference("bul_class_name", formsemestre_id),
|
||||
# si pas trouvé (modifs locales bizarres ,), ré-essaye avec la valeur par défaut
|
||||
bulletin_default_class_name(),
|
||||
):
|
||||
if infos.get("type") == "BUT" and format.startswith("pdf"):
|
||||
gen_class = bulletin_get_class(bul_class_name + "BUT")
|
||||
if gen_class is None:
|
||||
gen_class = bulletin_get_class(bul_class_name)
|
||||
|
||||
if gen_class is None:
|
||||
raise ValueError(
|
||||
"Type de bulletin PDF invalide (paramètre: %s)" % bul_class_name
|
||||
)
|
||||
|
||||
try:
|
||||
PDFLOCK.acquire()
|
||||
bul_generator = gen_class(
|
||||
infos,
|
||||
authuser=current_user,
|
||||
version=version,
|
||||
filigranne=infos["filigranne"],
|
||||
server_name=request.url_root,
|
||||
)
|
||||
if format not in bul_generator.supported_formats:
|
||||
# use standard generator
|
||||
log(
|
||||
"Bulletin format %s not supported by %s, using %s"
|
||||
% (format, bul_class_name, bulletin_default_class_name())
|
||||
)
|
||||
bul_class_name = bulletin_default_class_name()
|
||||
gen_class = bulletin_get_class(bul_class_name)
|
||||
bul_generator = gen_class(
|
||||
infos,
|
||||
authuser=current_user,
|
||||
version=version,
|
||||
filigranne=infos["filigranne"],
|
||||
server_name=request.url_root,
|
||||
)
|
||||
|
||||
data = bul_generator.generate(format=format, stand_alone=stand_alone)
|
||||
finally:
|
||||
PDFLOCK.release()
|
||||
|
||||
if bul_generator.diagnostic:
|
||||
log("bul_error: %s" % bul_generator.diagnostic)
|
||||
raise NoteProcessError(bul_generator.diagnostic)
|
||||
|
||||
filename = bul_generator.get_filename()
|
||||
|
||||
return data, filename
|
||||
|
||||
|
||||
####
|
||||
|
||||
# Liste des types des classes de générateurs de bulletins PDF:
|
||||
BULLETIN_CLASSES = collections.OrderedDict()
|
||||
|
||||
|
||||
def register_bulletin_class(klass):
|
||||
BULLETIN_CLASSES[klass.__name__] = klass
|
||||
|
||||
|
||||
def bulletin_class_descriptions():
|
||||
return [
|
||||
BULLETIN_CLASSES[class_name].description
|
||||
for class_name in BULLETIN_CLASSES
|
||||
if BULLETIN_CLASSES[class_name].list_in_menu
|
||||
]
|
||||
|
||||
|
||||
def bulletin_class_names() -> list[str]:
|
||||
"Liste les noms des classes de bulletins à présenter à l'utilisateur"
|
||||
return [
|
||||
class_name
|
||||
for class_name in BULLETIN_CLASSES
|
||||
if BULLETIN_CLASSES[class_name].list_in_menu
|
||||
]
|
||||
|
||||
|
||||
def bulletin_default_class_name():
|
||||
return bulletin_class_names()[0]
|
||||
|
||||
|
||||
def bulletin_get_class(class_name: str) -> BulletinGenerator:
|
||||
"""La class de génération de bulletin de ce nom,
|
||||
ou None si pas trouvée
|
||||
"""
|
||||
return BULLETIN_CLASSES.get(class_name)
|
||||
|
||||
|
||||
def bulletin_get_class_name_displayed(formsemestre_id):
|
||||
"""Le nom du générateur utilisé, en clair"""
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
||||
gen_class = bulletin_get_class(bul_class_name)
|
||||
if gen_class is None:
|
||||
return "invalide ! (voir paramètres)"
|
||||
return gen_class.description
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@gmail.com
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Génération des bulletins de note: super-classe pour les générateurs (HTML et PDF)
|
||||
|
||||
class BulletinGenerator:
|
||||
description
|
||||
supported_formats = [ 'pdf', 'html' ]
|
||||
.bul_title_pdf()
|
||||
.bul_table(format)
|
||||
.bul_part_below(format)
|
||||
.bul_signatures_pdf()
|
||||
|
||||
.__init__ et .generate(format) methodes appelees par le client (sco_bulletin)
|
||||
|
||||
La préférence 'bul_class_name' donne le nom de la classe generateur.
|
||||
La préférence 'bul_pdf_class_name' est obsolete (inutilisée).
|
||||
|
||||
|
||||
"""
|
||||
import collections
|
||||
import io
|
||||
import time
|
||||
import traceback
|
||||
|
||||
|
||||
import reportlab
|
||||
from reportlab.platypus import (
|
||||
SimpleDocTemplate,
|
||||
DocIf,
|
||||
Paragraph,
|
||||
Spacer,
|
||||
Frame,
|
||||
PageBreak,
|
||||
)
|
||||
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
|
||||
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import NoteProcessError
|
||||
from app import log
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_pdf
|
||||
from app.scodoc.sco_pdf import PDFLOCK
|
||||
import sco_version
|
||||
|
||||
|
||||
class BulletinGenerator:
|
||||
"Virtual superclass for PDF bulletin generators" ""
|
||||
# Here some helper methods
|
||||
# see sco_bulletins_standard.BulletinGeneratorStandard subclass for real methods
|
||||
supported_formats = [] # should list supported formats, eg [ 'html', 'pdf' ]
|
||||
description = "superclass for bulletins" # description for user interface
|
||||
list_in_menu = True # la classe doit-elle est montrée dans le menu de config ?
|
||||
scale_table_in_page = True # rescale la table sur 1 page
|
||||
multi_pages = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
infos,
|
||||
authuser=None,
|
||||
version="long",
|
||||
filigranne=None,
|
||||
server_name=None,
|
||||
):
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
if not version in scu.BULLETINS_VERSIONS:
|
||||
raise ValueError("invalid version code !")
|
||||
self.infos = infos
|
||||
self.authuser = authuser # nécessaire pour version HTML qui contient liens dépendant de l'utilisateur
|
||||
self.version = version
|
||||
self.filigranne = filigranne
|
||||
self.server_name = server_name
|
||||
# Store preferences for convenience:
|
||||
formsemestre_id = self.infos["formsemestre_id"]
|
||||
self.preferences = sco_preferences.SemPreferences(formsemestre_id)
|
||||
self.diagnostic = None # error message if any problem
|
||||
# Common PDF styles:
|
||||
# - Pour tous les champs du bulletin sauf les cellules de table:
|
||||
self.FieldStyle = reportlab.lib.styles.ParagraphStyle({})
|
||||
self.FieldStyle.fontName = self.preferences["SCOLAR_FONT_BUL_FIELDS"]
|
||||
self.FieldStyle.fontSize = self.preferences["SCOLAR_FONT_SIZE"]
|
||||
self.FieldStyle.firstLineIndent = 0
|
||||
# - Pour les cellules de table:
|
||||
self.CellStyle = reportlab.lib.styles.ParagraphStyle({})
|
||||
self.CellStyle.fontSize = self.preferences["SCOLAR_FONT_SIZE"]
|
||||
self.CellStyle.fontName = self.preferences["SCOLAR_FONT"]
|
||||
self.CellStyle.leading = (
|
||||
1.0 * self.preferences["SCOLAR_FONT_SIZE"]
|
||||
) # vertical space
|
||||
# Marges du document PDF
|
||||
self.margins = (
|
||||
self.preferences["left_margin"],
|
||||
self.preferences["top_margin"],
|
||||
self.preferences["right_margin"],
|
||||
self.preferences["bottom_margin"],
|
||||
)
|
||||
|
||||
def get_filename(self):
|
||||
"""Build a filename to be proposed to the web client"""
|
||||
sem = sco_formsemestre.get_formsemestre(self.infos["formsemestre_id"])
|
||||
return scu.bul_filename_old(sem, self.infos["etud"], "pdf")
|
||||
|
||||
def generate(self, format="", stand_alone=True):
|
||||
"""Return bulletin in specified format"""
|
||||
if not format in self.supported_formats:
|
||||
raise ValueError("unsupported bulletin format (%s)" % format)
|
||||
try:
|
||||
PDFLOCK.acquire() # this lock is necessary since reportlab is not re-entrant
|
||||
if format == "html":
|
||||
return self.generate_html()
|
||||
elif format == "pdf":
|
||||
return self.generate_pdf(stand_alone=stand_alone)
|
||||
else:
|
||||
raise ValueError("invalid bulletin format (%s)" % format)
|
||||
finally:
|
||||
PDFLOCK.release()
|
||||
|
||||
def generate_html(self):
|
||||
"""Return bulletin as an HTML string"""
|
||||
H = ['<div class="notes_bulletin">']
|
||||
# table des notes:
|
||||
H.append(self.bul_table(format="html")) # pylint: disable=no-member
|
||||
# infos sous la table:
|
||||
H.append(self.bul_part_below(format="html")) # pylint: disable=no-member
|
||||
H.append("</div>")
|
||||
return "\n".join(H)
|
||||
|
||||
def generate_pdf(self, stand_alone=True):
|
||||
"""Build PDF bulletin from distinct parts
|
||||
Si stand_alone, génère un doc PDF complet et renvoie une string
|
||||
Sinon, renvoie juste une liste d'objets PLATYPUS pour intégration
|
||||
dans un autre document.
|
||||
"""
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
formsemestre_id = self.infos["formsemestre_id"]
|
||||
marque_debut_bulletin = sco_pdf.DebutBulletin(
|
||||
self.infos["etud"]["nomprenom"],
|
||||
filigranne=self.infos["filigranne"],
|
||||
footer_content=f"""ScoDoc - Bulletin de {self.infos["etud"]["nomprenom"]} - {time.strftime("%d/%m/%Y %H:%M")}""",
|
||||
)
|
||||
story = []
|
||||
# partie haute du bulletin
|
||||
story += self.bul_title_pdf() # pylint: disable=no-member
|
||||
index_obj_debut = len(story)
|
||||
|
||||
# table des notes
|
||||
story += self.bul_table(format="pdf") # pylint: disable=no-member
|
||||
# infos sous la table
|
||||
story += self.bul_part_below(format="pdf") # pylint: disable=no-member
|
||||
# signatures
|
||||
story += self.bul_signatures_pdf() # pylint: disable=no-member
|
||||
if self.scale_table_in_page:
|
||||
# Réduit sur une page
|
||||
story = [marque_debut_bulletin, KeepInFrame(0, 0, story, mode="shrink")]
|
||||
else:
|
||||
# Insere notre marqueur qui permet de générer les bookmarks et filigrannes:
|
||||
story.insert(index_obj_debut, marque_debut_bulletin)
|
||||
#
|
||||
# objects.append(sco_pdf.FinBulletin())
|
||||
if not stand_alone:
|
||||
if self.multi_pages:
|
||||
# Bulletins sur plusieurs page, force début suivant sur page impaire
|
||||
story.append(
|
||||
DocIf("doc.page%2 == 1", [PageBreak(), PageBreak()], [PageBreak()])
|
||||
)
|
||||
else:
|
||||
story.append(PageBreak()) # insert page break at end
|
||||
|
||||
return story
|
||||
else:
|
||||
# Generation du document PDF
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
report = io.BytesIO() # in-memory document, no disk file
|
||||
document = sco_pdf.BaseDocTemplate(report)
|
||||
document.addPageTemplates(
|
||||
sco_pdf.ScoDocPageTemplate(
|
||||
document,
|
||||
author="%s %s (E. Viennet) [%s]"
|
||||
% (sco_version.SCONAME, sco_version.SCOVERSION, self.description),
|
||||
title="Bulletin %s de %s"
|
||||
% (sem["titremois"], self.infos["etud"]["nomprenom"]),
|
||||
subject="Bulletin de note",
|
||||
margins=self.margins,
|
||||
server_name=self.server_name,
|
||||
filigranne=self.filigranne,
|
||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||
)
|
||||
)
|
||||
document.build(story)
|
||||
data = report.getvalue()
|
||||
return data
|
||||
|
||||
def buildTableObject(self, P, pdfTableStyle, colWidths):
|
||||
"""Utility used by some old-style generators.
|
||||
Build a platypus Table instance from a nested list of cells, style and widths.
|
||||
P: table, as a list of lists
|
||||
PdfTableStyle: commandes de style pour la table (reportlab)
|
||||
"""
|
||||
try:
|
||||
# put each table cell in a Paragraph
|
||||
Pt = [
|
||||
[Paragraph(sco_pdf.SU(x), self.CellStyle) for x in line] for line in P
|
||||
]
|
||||
except:
|
||||
# enquête sur exception intermittente...
|
||||
log("*** bug in PDF buildTableObject:")
|
||||
log("P=%s" % P)
|
||||
# compris: reportlab is not thread safe !
|
||||
# see http://two.pairlist.net/pipermail/reportlab-users/2006-June/005037.html
|
||||
# (donc maintenant protégé dans ScoDoc par un Lock global)
|
||||
self.diagnostic = "erreur lors de la génération du PDF<br>"
|
||||
self.diagnostic += "<pre>" + traceback.format_exc() + "</pre>"
|
||||
return []
|
||||
return Table(Pt, colWidths=colWidths, style=pdfTableStyle)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
def make_formsemestre_bulletinetud(
|
||||
infos,
|
||||
version=None, # short, long, selectedevals
|
||||
format="pdf", # html, pdf
|
||||
stand_alone=True,
|
||||
):
|
||||
"""Bulletin de notes
|
||||
|
||||
Appelle une fonction générant le bulletin au format spécifié à partir des informations infos,
|
||||
selon les préférences du semestre.
|
||||
|
||||
"""
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
version = version or "long"
|
||||
if not version in scu.BULLETINS_VERSIONS:
|
||||
raise ValueError("invalid version code !")
|
||||
|
||||
formsemestre_id = infos["formsemestre_id"]
|
||||
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
||||
|
||||
gen_class = None
|
||||
for bul_class_name in (
|
||||
sco_preferences.get_preference("bul_class_name", formsemestre_id),
|
||||
# si pas trouvé (modifs locales bizarres ,), ré-essaye avec la valeur par défaut
|
||||
bulletin_default_class_name(),
|
||||
):
|
||||
if infos.get("type") == "BUT" and format.startswith("pdf"):
|
||||
gen_class = bulletin_get_class(bul_class_name + "BUT")
|
||||
if gen_class is None:
|
||||
gen_class = bulletin_get_class(bul_class_name)
|
||||
|
||||
if gen_class is None:
|
||||
raise ValueError(
|
||||
"Type de bulletin PDF invalide (paramètre: %s)" % bul_class_name
|
||||
)
|
||||
|
||||
try:
|
||||
PDFLOCK.acquire()
|
||||
bul_generator = gen_class(
|
||||
infos,
|
||||
authuser=current_user,
|
||||
version=version,
|
||||
filigranne=infos["filigranne"],
|
||||
server_name=request.url_root,
|
||||
)
|
||||
if format not in bul_generator.supported_formats:
|
||||
# use standard generator
|
||||
log(
|
||||
"Bulletin format %s not supported by %s, using %s"
|
||||
% (format, bul_class_name, bulletin_default_class_name())
|
||||
)
|
||||
bul_class_name = bulletin_default_class_name()
|
||||
gen_class = bulletin_get_class(bul_class_name)
|
||||
bul_generator = gen_class(
|
||||
infos,
|
||||
authuser=current_user,
|
||||
version=version,
|
||||
filigranne=infos["filigranne"],
|
||||
server_name=request.url_root,
|
||||
)
|
||||
|
||||
data = bul_generator.generate(format=format, stand_alone=stand_alone)
|
||||
finally:
|
||||
PDFLOCK.release()
|
||||
|
||||
if bul_generator.diagnostic:
|
||||
log("bul_error: %s" % bul_generator.diagnostic)
|
||||
raise NoteProcessError(bul_generator.diagnostic)
|
||||
|
||||
filename = bul_generator.get_filename()
|
||||
|
||||
return data, filename
|
||||
|
||||
|
||||
####
|
||||
|
||||
# Liste des types des classes de générateurs de bulletins PDF:
|
||||
BULLETIN_CLASSES = collections.OrderedDict()
|
||||
|
||||
|
||||
def register_bulletin_class(klass):
|
||||
BULLETIN_CLASSES[klass.__name__] = klass
|
||||
|
||||
|
||||
def bulletin_class_descriptions():
|
||||
return [
|
||||
BULLETIN_CLASSES[class_name].description
|
||||
for class_name in BULLETIN_CLASSES
|
||||
if BULLETIN_CLASSES[class_name].list_in_menu
|
||||
]
|
||||
|
||||
|
||||
def bulletin_class_names() -> list[str]:
|
||||
"Liste les noms des classes de bulletins à présenter à l'utilisateur"
|
||||
return [
|
||||
class_name
|
||||
for class_name in BULLETIN_CLASSES
|
||||
if BULLETIN_CLASSES[class_name].list_in_menu
|
||||
]
|
||||
|
||||
|
||||
def bulletin_default_class_name():
|
||||
return bulletin_class_names()[0]
|
||||
|
||||
|
||||
def bulletin_get_class(class_name: str) -> BulletinGenerator:
|
||||
"""La class de génération de bulletin de ce nom,
|
||||
ou None si pas trouvée
|
||||
"""
|
||||
return BULLETIN_CLASSES.get(class_name)
|
||||
|
||||
|
||||
def bulletin_get_class_name_displayed(formsemestre_id):
|
||||
"""Le nom du générateur utilisé, en clair"""
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
||||
gen_class = bulletin_get_class(bul_class_name)
|
||||
if gen_class is None:
|
||||
return "invalide ! (voir paramètres)"
|
||||
return gen_class.description
|
||||
|
|
|
@ -1,197 +1,197 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Rapports estimation coût de formation basé sur le programme pédagogique
|
||||
et les nombres de groupes.
|
||||
|
||||
(coût théorique en heures équivalent TD)
|
||||
"""
|
||||
from flask import request
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_formsemestre_status
|
||||
from app.scodoc import sco_preferences
|
||||
import sco_version
|
||||
|
||||
|
||||
def formsemestre_table_estim_cost(
|
||||
formsemestre_id,
|
||||
n_group_td=1,
|
||||
n_group_tp=1,
|
||||
coef_tp=1,
|
||||
coef_cours=1.5,
|
||||
):
|
||||
"""
|
||||
Rapports estimation coût de formation basé sur le programme pédagogique
|
||||
et les nombres de groupes.
|
||||
Coût théorique en heures équivalent TD.
|
||||
Attention: ne prend en compte que les modules utilisés dans ce semestre.
|
||||
Attention: prend en compte _tous_ les modules utilisés dans ce semestre, ce qui
|
||||
peut conduire à une sur-estimation du coût s'il y a des modules optionnels
|
||||
(dans ce cas, retoucher le tableau excel exporté).
|
||||
"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
sco_formsemestre_status.fill_formsemestre(sem)
|
||||
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
||||
T = []
|
||||
for M in Mlist:
|
||||
Mod = M["module"]
|
||||
T.append(
|
||||
{
|
||||
"code": Mod["code"] or "",
|
||||
"titre": Mod["titre"],
|
||||
"heures_cours": Mod["heures_cours"],
|
||||
"heures_td": Mod["heures_td"] * n_group_td,
|
||||
"heures_tp": Mod["heures_tp"] * n_group_tp,
|
||||
}
|
||||
)
|
||||
|
||||
# calcul des heures:
|
||||
for t in T:
|
||||
t["HeqTD"] = (
|
||||
t["heures_td"] + coef_cours * t["heures_cours"] + coef_tp * t["heures_tp"]
|
||||
)
|
||||
sum_cours = sum([t["heures_cours"] for t in T])
|
||||
sum_td = sum([t["heures_td"] for t in T])
|
||||
sum_tp = sum([t["heures_tp"] for t in T])
|
||||
sum_heqtd = sum_td + coef_cours * sum_cours + coef_tp * sum_tp
|
||||
assert abs(sum([t["HeqTD"] for t in T]) - sum_heqtd) < 0.01, "%s != %s" % (
|
||||
sum([t["HeqTD"] for t in T]),
|
||||
sum_heqtd,
|
||||
)
|
||||
|
||||
T.append(
|
||||
{
|
||||
"code": "TOTAL SEMESTRE",
|
||||
"heures_cours": sum_cours,
|
||||
"heures_td": sum_td,
|
||||
"heures_tp": sum_tp,
|
||||
"HeqTD": sum_heqtd,
|
||||
"_table_part": "foot",
|
||||
}
|
||||
)
|
||||
|
||||
titles = {
|
||||
"code": "Code",
|
||||
"titre": "Titre",
|
||||
"heures_cours": "Cours",
|
||||
"heures_td": "TD",
|
||||
"heures_tp": "TP",
|
||||
"HeqTD": "HeqTD",
|
||||
}
|
||||
|
||||
tab = GenTable(
|
||||
titles=titles,
|
||||
columns_ids=(
|
||||
"code",
|
||||
"titre",
|
||||
"heures_cours",
|
||||
"heures_td",
|
||||
"heures_tp",
|
||||
"HeqTD",
|
||||
),
|
||||
rows=T,
|
||||
html_sortable=True,
|
||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||
html_class="table_leftalign table_listegroupe",
|
||||
xls_before_table=[
|
||||
["%(titre)s %(num_sem)s %(modalitestr)s" % sem],
|
||||
["Formation %(titre)s version %(version)s" % sem["formation"]],
|
||||
[],
|
||||
["", "TD", "TP"],
|
||||
["Nombre de groupes", n_group_td, n_group_tp],
|
||||
[],
|
||||
[],
|
||||
],
|
||||
html_caption="""<div class="help">
|
||||
Estimation du coût de formation basé sur le programme pédagogique
|
||||
et les nombres de groupes.<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: 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
|
||||
(dans ce cas, retoucher le tableau excel exporté).
|
||||
</div>
|
||||
""",
|
||||
origin="Généré par %s le " % sco_version.SCONAME
|
||||
+ scu.timedate_human_repr()
|
||||
+ "",
|
||||
filename="EstimCout-S%s" % sem["semestre_id"],
|
||||
)
|
||||
return tab
|
||||
|
||||
|
||||
def formsemestre_estim_cost(
|
||||
formsemestre_id,
|
||||
n_group_td=1,
|
||||
n_group_tp=1,
|
||||
coef_tp=1,
|
||||
coef_cours=1.5,
|
||||
format="html",
|
||||
):
|
||||
"""Page (formulaire) estimation coûts"""
|
||||
|
||||
n_group_td = int(n_group_td)
|
||||
n_group_tp = int(n_group_tp)
|
||||
coef_tp = float(coef_tp)
|
||||
coef_cours = float(coef_cours)
|
||||
|
||||
tab = formsemestre_table_estim_cost(
|
||||
formsemestre_id,
|
||||
n_group_td=n_group_td,
|
||||
n_group_tp=n_group_tp,
|
||||
coef_tp=coef_tp,
|
||||
coef_cours=coef_cours,
|
||||
)
|
||||
h = """
|
||||
<form name="f" method="get" action="%s">
|
||||
<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 TP: <input type="text" name="n_group_tp" value="%s" onchange="document.f.submit()"/>
|
||||
Coefficient heures TP: <input type="text" name="coef_tp" value="%s" onchange="document.f.submit()"/>
|
||||
<br/>
|
||||
</form>
|
||||
""" % (
|
||||
request.base_url,
|
||||
formsemestre_id,
|
||||
n_group_td,
|
||||
n_group_tp,
|
||||
coef_tp,
|
||||
)
|
||||
tab.html_before_table = h
|
||||
tab.base_url = "%s?formsemestre_id=%s&n_group_td=%s&n_group_tp=%s&coef_tp=%s" % (
|
||||
request.base_url,
|
||||
formsemestre_id,
|
||||
n_group_td,
|
||||
n_group_tp,
|
||||
coef_tp,
|
||||
)
|
||||
|
||||
return tab.make_page(format=format)
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Rapports estimation coût de formation basé sur le programme pédagogique
|
||||
et les nombres de groupes.
|
||||
|
||||
(coût théorique en heures équivalent TD)
|
||||
"""
|
||||
from flask import request
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_formsemestre_status
|
||||
from app.scodoc import sco_preferences
|
||||
import sco_version
|
||||
|
||||
|
||||
def formsemestre_table_estim_cost(
|
||||
formsemestre_id,
|
||||
n_group_td=1,
|
||||
n_group_tp=1,
|
||||
coef_tp=1,
|
||||
coef_cours=1.5,
|
||||
):
|
||||
"""
|
||||
Rapports estimation coût de formation basé sur le programme pédagogique
|
||||
et les nombres de groupes.
|
||||
Coût théorique en heures équivalent TD.
|
||||
Attention: ne prend en compte que les modules utilisés dans ce semestre.
|
||||
Attention: prend en compte _tous_ les modules utilisés dans ce semestre, ce qui
|
||||
peut conduire à une sur-estimation du coût s'il y a des modules optionnels
|
||||
(dans ce cas, retoucher le tableau excel exporté).
|
||||
"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
sco_formsemestre_status.fill_formsemestre(sem)
|
||||
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
||||
T = []
|
||||
for M in Mlist:
|
||||
Mod = M["module"]
|
||||
T.append(
|
||||
{
|
||||
"code": Mod["code"] or "",
|
||||
"titre": Mod["titre"],
|
||||
"heures_cours": Mod["heures_cours"],
|
||||
"heures_td": Mod["heures_td"] * n_group_td,
|
||||
"heures_tp": Mod["heures_tp"] * n_group_tp,
|
||||
}
|
||||
)
|
||||
|
||||
# calcul des heures:
|
||||
for t in T:
|
||||
t["HeqTD"] = (
|
||||
t["heures_td"] + coef_cours * t["heures_cours"] + coef_tp * t["heures_tp"]
|
||||
)
|
||||
sum_cours = sum([t["heures_cours"] for t in T])
|
||||
sum_td = sum([t["heures_td"] for t in T])
|
||||
sum_tp = sum([t["heures_tp"] for t in T])
|
||||
sum_heqtd = sum_td + coef_cours * sum_cours + coef_tp * sum_tp
|
||||
assert abs(sum([t["HeqTD"] for t in T]) - sum_heqtd) < 0.01, "%s != %s" % (
|
||||
sum([t["HeqTD"] for t in T]),
|
||||
sum_heqtd,
|
||||
)
|
||||
|
||||
T.append(
|
||||
{
|
||||
"code": "TOTAL SEMESTRE",
|
||||
"heures_cours": sum_cours,
|
||||
"heures_td": sum_td,
|
||||
"heures_tp": sum_tp,
|
||||
"HeqTD": sum_heqtd,
|
||||
"_table_part": "foot",
|
||||
}
|
||||
)
|
||||
|
||||
titles = {
|
||||
"code": "Code",
|
||||
"titre": "Titre",
|
||||
"heures_cours": "Cours",
|
||||
"heures_td": "TD",
|
||||
"heures_tp": "TP",
|
||||
"HeqTD": "HeqTD",
|
||||
}
|
||||
|
||||
tab = GenTable(
|
||||
titles=titles,
|
||||
columns_ids=(
|
||||
"code",
|
||||
"titre",
|
||||
"heures_cours",
|
||||
"heures_td",
|
||||
"heures_tp",
|
||||
"HeqTD",
|
||||
),
|
||||
rows=T,
|
||||
html_sortable=True,
|
||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||
html_class="table_leftalign table_listegroupe",
|
||||
xls_before_table=[
|
||||
["%(titre)s %(num_sem)s %(modalitestr)s" % sem],
|
||||
["Formation %(titre)s version %(version)s" % sem["formation"]],
|
||||
[],
|
||||
["", "TD", "TP"],
|
||||
["Nombre de groupes", n_group_td, n_group_tp],
|
||||
[],
|
||||
[],
|
||||
],
|
||||
html_caption="""<div class="help">
|
||||
Estimation du coût de formation basé sur le programme pédagogique
|
||||
et les nombres de groupes.<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: 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
|
||||
(dans ce cas, retoucher le tableau excel exporté).
|
||||
</div>
|
||||
""",
|
||||
origin="Généré par %s le " % sco_version.SCONAME
|
||||
+ scu.timedate_human_repr()
|
||||
+ "",
|
||||
filename="EstimCout-S%s" % sem["semestre_id"],
|
||||
)
|
||||
return tab
|
||||
|
||||
|
||||
def formsemestre_estim_cost(
|
||||
formsemestre_id,
|
||||
n_group_td=1,
|
||||
n_group_tp=1,
|
||||
coef_tp=1,
|
||||
coef_cours=1.5,
|
||||
format="html",
|
||||
):
|
||||
"""Page (formulaire) estimation coûts"""
|
||||
|
||||
n_group_td = int(n_group_td)
|
||||
n_group_tp = int(n_group_tp)
|
||||
coef_tp = float(coef_tp)
|
||||
coef_cours = float(coef_cours)
|
||||
|
||||
tab = formsemestre_table_estim_cost(
|
||||
formsemestre_id,
|
||||
n_group_td=n_group_td,
|
||||
n_group_tp=n_group_tp,
|
||||
coef_tp=coef_tp,
|
||||
coef_cours=coef_cours,
|
||||
)
|
||||
h = """
|
||||
<form name="f" method="get" action="%s">
|
||||
<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 TP: <input type="text" name="n_group_tp" value="%s" onchange="document.f.submit()"/>
|
||||
Coefficient heures TP: <input type="text" name="coef_tp" value="%s" onchange="document.f.submit()"/>
|
||||
<br>
|
||||
</form>
|
||||
""" % (
|
||||
request.base_url,
|
||||
formsemestre_id,
|
||||
n_group_td,
|
||||
n_group_tp,
|
||||
coef_tp,
|
||||
)
|
||||
tab.html_before_table = h
|
||||
tab.base_url = "%s?formsemestre_id=%s&n_group_td=%s&n_group_tp=%s&coef_tp=%s" % (
|
||||
request.base_url,
|
||||
formsemestre_id,
|
||||
n_group_td,
|
||||
n_group_tp,
|
||||
coef_tp,
|
||||
)
|
||||
|
||||
return tab.make_page(format=format)
|
||||
|
|
|
@ -1,399 +1,399 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Page accueil département (liste des semestres, etc)
|
||||
"""
|
||||
|
||||
from flask import g, request
|
||||
from flask import url_for
|
||||
from flask_login import current_user
|
||||
|
||||
import app
|
||||
from app.models import ScolarNews
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import html_sco_header
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_modalites
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_users
|
||||
|
||||
|
||||
def index_html(showcodes=0, showsemtable=0):
|
||||
"Page accueil département (liste des semestres)"
|
||||
showcodes = int(showcodes)
|
||||
showsemtable = int(showsemtable)
|
||||
H = []
|
||||
|
||||
# News:
|
||||
H.append(ScolarNews.scolar_news_summary_html())
|
||||
|
||||
# Avertissement de mise à jour:
|
||||
H.append("""<div id="update_warning"></div>""")
|
||||
|
||||
# Liste de toutes les sessions:
|
||||
sems = sco_formsemestre.do_formsemestre_list()
|
||||
cursems = [] # semestres "courants"
|
||||
othersems = [] # autres (verrouillés)
|
||||
# icon image:
|
||||
groupicon = scu.icontag("groupicon_img", title="Inscrits", border="0")
|
||||
emptygroupicon = scu.icontag(
|
||||
"emptygroupicon_img", title="Pas d'inscrits", border="0"
|
||||
)
|
||||
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
|
||||
# Sélection sur l'etat du semestre
|
||||
for sem in sems:
|
||||
if sem["etat"] and sem["modalite"] != "EXT":
|
||||
sem["lockimg"] = ""
|
||||
cursems.append(sem)
|
||||
else:
|
||||
sem["lockimg"] = lockicon
|
||||
othersems.append(sem)
|
||||
# Responsable de formation:
|
||||
sco_formsemestre.sem_set_responsable_name(sem)
|
||||
|
||||
if showcodes:
|
||||
sem["tmpcode"] = f"<td><tt>{sem['formsemestre_id']}</tt></td>"
|
||||
else:
|
||||
sem["tmpcode"] = ""
|
||||
# Nombre d'inscrits:
|
||||
args = {"formsemestre_id": sem["formsemestre_id"]}
|
||||
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(args=args)
|
||||
nb = len(ins) # nb etudiants
|
||||
sem["nb_inscrits"] = nb
|
||||
if nb > 0:
|
||||
sem["groupicon"] = groupicon
|
||||
else:
|
||||
sem["groupicon"] = emptygroupicon
|
||||
|
||||
# S'il n'y a pas d'utilisateurs dans la base, affiche message
|
||||
if not sco_users.get_user_list(dept=g.scodoc_dept):
|
||||
H.append(
|
||||
"""<h2>Aucun utilisateur défini !</h2><p>Pour définir des utilisateurs
|
||||
<a href="Users">passez par la page Utilisateurs</a>.
|
||||
<br/>
|
||||
Définissez au moins un utilisateur avec le rôle AdminXXX (le responsable du département XXX).
|
||||
</p>
|
||||
"""
|
||||
)
|
||||
|
||||
# Liste des formsemestres "courants"
|
||||
if cursems:
|
||||
H.append('<h2 class="listesems">Sessions en cours</h2>')
|
||||
H.append(_sem_table(cursems))
|
||||
else:
|
||||
# aucun semestre courant: affiche aide
|
||||
H.append(
|
||||
"""<h2 class="listesems">Aucune session en cours !</h2>
|
||||
<p>Pour ajouter une session, aller dans <a href="Notes" id="link-programmes">Programmes</a>,
|
||||
choisissez une formation, puis suivez le lien "<em>UE, modules, semestres</em>".
|
||||
</p><p>
|
||||
Là, en bas de page, suivez le lien
|
||||
"<em>Mettre en place un nouveau semestre de formation...</em>"
|
||||
</p>"""
|
||||
)
|
||||
|
||||
if showsemtable:
|
||||
H.append(
|
||||
f"""<hr>
|
||||
<h2>Semestres de {sco_preferences.get_preference("DeptName")}</h2>
|
||||
"""
|
||||
)
|
||||
H.append(_sem_table_gt(sems, showcodes=showcodes).html())
|
||||
H.append("</table>")
|
||||
if not showsemtable:
|
||||
H.append(
|
||||
f"""<hr>
|
||||
<p><a class="stdlink" href="{url_for('scolar.index_html',
|
||||
scodoc_dept=g.scodoc_dept, showsemtable=1)
|
||||
}">Voir table des semestres (dont {len(othersems)}
|
||||
verrouillé{'s' if len(othersems) else ''})</a>
|
||||
</p>"""
|
||||
)
|
||||
|
||||
H.append(
|
||||
f"""<p>
|
||||
<form action="{url_for('notes.view_formsemestre_by_etape', scodoc_dept=g.scodoc_dept)}">
|
||||
Chercher étape courante:
|
||||
<input name="etape_apo" type="text" size="8" spellcheck="false"></input>
|
||||
</form>
|
||||
</p>"""
|
||||
)
|
||||
#
|
||||
if current_user.has_permission(Permission.ScoEtudInscrit):
|
||||
H.append(
|
||||
"""<hr>
|
||||
<h3>Gestion des étudiants</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="etudident_create_form">créer <em>un</em> nouvel étudiant</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="form_students_import_excel">importer de nouveaux étudiants</a>
|
||||
(ne pas utiliser sauf cas particulier, utilisez plutôt le lien dans
|
||||
le tableau de bord semestre si vous souhaitez inscrire les
|
||||
étudiants importés à un semestre)
|
||||
</li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
#
|
||||
if current_user.has_permission(Permission.ScoEditApo):
|
||||
H.append(
|
||||
f"""<hr>
|
||||
<h3>Exports Apogée</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="{url_for('notes.semset_page', scodoc_dept=g.scodoc_dept)
|
||||
}">Années scolaires / exports Apogée</a></li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
#
|
||||
H.append(
|
||||
"""<hr>
|
||||
<h3>Assistance</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="sco_dump_and_send_db">Envoyer données</a></li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
#
|
||||
return (
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"ScoDoc {g.scodoc_dept}", javascripts=["js/scolar_index.js"]
|
||||
)
|
||||
+ "\n".join(H)
|
||||
+ html_sco_header.sco_footer()
|
||||
)
|
||||
|
||||
|
||||
def _sem_table(sems):
|
||||
"""Affiche liste des semestres, utilisée pour semestres en cours"""
|
||||
tmpl = """<tr class="%(trclass)s">%(tmpcode)s
|
||||
<td class="semicon">%(lockimg)s <a href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
|
||||
<td class="datesem">%(mois_debut)s <a title="%(session_id)s">-</a> %(mois_fin)s</td>
|
||||
<td><a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
||||
<span class="respsem">(%(responsable_name)s)</span>
|
||||
</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
# Liste des semestres, groupés par modalités
|
||||
sems_by_mod, modalites = sco_modalites.group_sems_by_modalite(sems)
|
||||
|
||||
H = ['<table class="listesems">']
|
||||
for modalite in modalites:
|
||||
if len(modalites) > 1:
|
||||
H.append('<tr><th colspan="3">%s</th></tr>' % modalite["titre"])
|
||||
|
||||
if sems_by_mod[modalite["modalite"]]:
|
||||
cur_idx = sems_by_mod[modalite["modalite"]][0]["semestre_id"]
|
||||
for sem in sems_by_mod[modalite["modalite"]]:
|
||||
if cur_idx != sem["semestre_id"]:
|
||||
sem["trclass"] = "firstsem" # separe les groupes de semestres
|
||||
cur_idx = sem["semestre_id"]
|
||||
else:
|
||||
sem["trclass"] = ""
|
||||
sem["notes_url"] = scu.NotesURL()
|
||||
H.append(tmpl % sem)
|
||||
H.append("</table>")
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def _sem_table_gt(sems, showcodes=False):
|
||||
"""Nouvelle version de la table des semestres
|
||||
Utilise une datatables.
|
||||
"""
|
||||
_style_sems(sems)
|
||||
columns_ids = (
|
||||
"lockimg",
|
||||
"semestre_id_n",
|
||||
"modalite",
|
||||
#'mois_debut',
|
||||
"dash_mois_fin",
|
||||
"titre_resp",
|
||||
"nb_inscrits",
|
||||
"etapes_apo_str",
|
||||
"elt_annee_apo",
|
||||
"elt_sem_apo",
|
||||
)
|
||||
if showcodes:
|
||||
columns_ids = ("formsemestre_id",) + columns_ids
|
||||
|
||||
html_class = "stripe cell-border compact hover order-column table_leftalign semlist"
|
||||
if current_user.has_permission(Permission.ScoEditApo):
|
||||
html_class += " apo_editable"
|
||||
tab = GenTable(
|
||||
titles={
|
||||
"formsemestre_id": "id",
|
||||
"semestre_id_n": "S#",
|
||||
"modalite": "",
|
||||
"mois_debut": "Début",
|
||||
"dash_mois_fin": "Année",
|
||||
"titre_resp": "Semestre",
|
||||
"nb_inscrits": "N",
|
||||
"etapes_apo_str": "Étape Apo.",
|
||||
"elt_annee_apo": "Elt. année Apo.",
|
||||
"elt_sem_apo": "Elt. sem. Apo.",
|
||||
},
|
||||
columns_ids=columns_ids,
|
||||
rows=sems,
|
||||
table_id="semlist",
|
||||
html_class_ignore_default=True,
|
||||
html_class=html_class,
|
||||
html_sortable=True,
|
||||
html_table_attrs=f"""
|
||||
data-apo_save_url="{url_for('notes.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept)}"
|
||||
data-elt_annee_apo_save_url="{url_for('notes.formsemestre_set_elt_annee_apo', scodoc_dept=g.scodoc_dept)}"
|
||||
data-elt_sem_apo_save_url="{url_for('notes.formsemestre_set_elt_sem_apo', scodoc_dept=g.scodoc_dept)}"
|
||||
""",
|
||||
html_with_td_classes=True,
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
|
||||
return tab
|
||||
|
||||
|
||||
def _style_sems(sems):
|
||||
"""ajoute quelques attributs de présentation pour la table"""
|
||||
for sem in sems:
|
||||
sem["notes_url"] = scu.NotesURL()
|
||||
sem["_groupicon_target"] = (
|
||||
"%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s"
|
||||
% sem
|
||||
)
|
||||
sem["_formsemestre_id_class"] = "blacktt"
|
||||
sem["dash_mois_fin"] = '<a title="%(session_id)s"></a> %(anneescolaire)s' % sem
|
||||
sem["_dash_mois_fin_class"] = "datesem"
|
||||
sem["titre_resp"] = (
|
||||
"""<a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
||||
<span class="respsem">(%(responsable_name)s)</span>"""
|
||||
% sem
|
||||
)
|
||||
sem["_css_row_class"] = "css_S%d css_M%s" % (
|
||||
sem["semestre_id"],
|
||||
sem["modalite"],
|
||||
)
|
||||
sem["_semestre_id_class"] = "semestre_id"
|
||||
sem["_modalite_class"] = "modalite"
|
||||
if sem["semestre_id"] == -1:
|
||||
sem["semestre_id_n"] = ""
|
||||
else:
|
||||
sem["semestre_id_n"] = sem["semestre_id"]
|
||||
# pour édition codes Apogée:
|
||||
sem[
|
||||
"_etapes_apo_str_td_attrs"
|
||||
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['etapes_apo_str']}" """
|
||||
sem[
|
||||
"_elt_annee_apo_td_attrs"
|
||||
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_annee_apo']}" """
|
||||
sem[
|
||||
"_elt_sem_apo_td_attrs"
|
||||
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """
|
||||
|
||||
|
||||
def delete_dept(dept_id: int):
|
||||
"""Suppression irréversible d'un département et de tous les objets rattachés"""
|
||||
assert isinstance(dept_id, int)
|
||||
|
||||
# Un peu complexe, merci JMP :)
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor()
|
||||
try:
|
||||
# 1- Create temp tables to store ids
|
||||
reqs = [
|
||||
"create temp table etudids_temp as select id from identite where dept_id = %(dept_id)s",
|
||||
"create temp table formsemestres_temp as select id from notes_formsemestre where dept_id = %(dept_id)s",
|
||||
"create temp table moduleimpls_temp as select id from notes_moduleimpl where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"create temp table formations_temp as select id from notes_formations where dept_id = %(dept_id)s",
|
||||
"create temp table tags_temp as select id from notes_tags where dept_id = %(dept_id)s",
|
||||
]
|
||||
for r in reqs:
|
||||
cursor.execute(r, {"dept_id": dept_id})
|
||||
|
||||
# 2- Delete student-related informations
|
||||
# ordered list of tables
|
||||
etud_tables = [
|
||||
"notes_notes",
|
||||
"group_membership",
|
||||
"admissions",
|
||||
"billet_absence",
|
||||
"adresse",
|
||||
"absences",
|
||||
"notes_notes_log",
|
||||
"notes_moduleimpl_inscription",
|
||||
"itemsuivi",
|
||||
"notes_appreciations",
|
||||
"scolar_autorisation_inscription",
|
||||
"absences_notifications",
|
||||
"notes_formsemestre_inscription",
|
||||
"scolar_formsemestre_validation",
|
||||
"scolar_events",
|
||||
]
|
||||
for table in etud_tables:
|
||||
cursor.execute(
|
||||
f"delete from {table} where etudid in (select id from etudids_temp)"
|
||||
)
|
||||
|
||||
reqs = [
|
||||
"delete from identite where dept_id = %(dept_id)s",
|
||||
"delete from sco_prefs where dept_id = %(dept_id)s",
|
||||
"delete from notes_semset_formsemestre where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_evaluation where moduleimpl_id in (select id from moduleimpls_temp)",
|
||||
"delete from notes_modules_enseignants where moduleimpl_id in (select id from moduleimpls_temp)",
|
||||
"delete from notes_formsemestre_uecoef where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_formsemestre_ue_computation_expr where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_formsemestre_responsables where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_moduleimpl where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_modules_tags where tag_id in (select id from tags_temp)",
|
||||
"delete from notes_tags where dept_id = %(dept_id)s",
|
||||
"delete from notes_modules where formation_id in (select id from formations_temp)",
|
||||
"delete from notes_matieres where ue_id in (select id from notes_ue where formation_id in (select id from formations_temp))",
|
||||
"delete from notes_formsemestre_etapes where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from group_descr where partition_id in (select id from partition where formsemestre_id in (select id from formsemestres_temp))",
|
||||
"delete from partition where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_formsemestre_custommenu where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_ue where formation_id in (select id from formations_temp)",
|
||||
"delete from notes_formsemestre where dept_id = %(dept_id)s",
|
||||
"delete from scolar_news where dept_id = %(dept_id)s",
|
||||
"delete from notes_semset where dept_id = %(dept_id)s",
|
||||
"delete from notes_formations where dept_id = %(dept_id)s",
|
||||
"delete from departement where id = %(dept_id)s",
|
||||
"drop table tags_temp",
|
||||
"drop table formations_temp",
|
||||
"drop table moduleimpls_temp",
|
||||
"drop table etudids_temp",
|
||||
"drop table formsemestres_temp",
|
||||
]
|
||||
for r in reqs:
|
||||
cursor.execute(r, {"dept_id": dept_id})
|
||||
except:
|
||||
cnx.rollback()
|
||||
finally:
|
||||
cnx.commit()
|
||||
app.clear_scodoc_cache()
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Page accueil département (liste des semestres, etc)
|
||||
"""
|
||||
|
||||
from flask import g, request
|
||||
from flask import url_for
|
||||
from flask_login import current_user
|
||||
|
||||
import app
|
||||
from app.models import ScolarNews
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import html_sco_header
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_modalites
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_users
|
||||
|
||||
|
||||
def index_html(showcodes=0, showsemtable=0):
|
||||
"Page accueil département (liste des semestres)"
|
||||
showcodes = int(showcodes)
|
||||
showsemtable = int(showsemtable)
|
||||
H = []
|
||||
|
||||
# News:
|
||||
H.append(ScolarNews.scolar_news_summary_html())
|
||||
|
||||
# Avertissement de mise à jour:
|
||||
H.append("""<div id="update_warning"></div>""")
|
||||
|
||||
# Liste de toutes les sessions:
|
||||
sems = sco_formsemestre.do_formsemestre_list()
|
||||
cursems = [] # semestres "courants"
|
||||
othersems = [] # autres (verrouillés)
|
||||
# icon image:
|
||||
groupicon = scu.icontag("groupicon_img", title="Inscrits", border="0")
|
||||
emptygroupicon = scu.icontag(
|
||||
"emptygroupicon_img", title="Pas d'inscrits", border="0"
|
||||
)
|
||||
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
|
||||
# Sélection sur l'etat du semestre
|
||||
for sem in sems:
|
||||
if sem["etat"] and sem["modalite"] != "EXT":
|
||||
sem["lockimg"] = ""
|
||||
cursems.append(sem)
|
||||
else:
|
||||
sem["lockimg"] = lockicon
|
||||
othersems.append(sem)
|
||||
# Responsable de formation:
|
||||
sco_formsemestre.sem_set_responsable_name(sem)
|
||||
|
||||
if showcodes:
|
||||
sem["tmpcode"] = f"<td><tt>{sem['formsemestre_id']}</tt></td>"
|
||||
else:
|
||||
sem["tmpcode"] = ""
|
||||
# Nombre d'inscrits:
|
||||
args = {"formsemestre_id": sem["formsemestre_id"]}
|
||||
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(args=args)
|
||||
nb = len(ins) # nb etudiants
|
||||
sem["nb_inscrits"] = nb
|
||||
if nb > 0:
|
||||
sem["groupicon"] = groupicon
|
||||
else:
|
||||
sem["groupicon"] = emptygroupicon
|
||||
|
||||
# S'il n'y a pas d'utilisateurs dans la base, affiche message
|
||||
if not sco_users.get_user_list(dept=g.scodoc_dept):
|
||||
H.append(
|
||||
"""<h2>Aucun utilisateur défini !</h2><p>Pour définir des utilisateurs
|
||||
<a href="Users">passez par la page Utilisateurs</a>.
|
||||
<br>
|
||||
Définissez au moins un utilisateur avec le rôle AdminXXX (le responsable du département XXX).
|
||||
</p>
|
||||
"""
|
||||
)
|
||||
|
||||
# Liste des formsemestres "courants"
|
||||
if cursems:
|
||||
H.append('<h2 class="listesems">Sessions en cours</h2>')
|
||||
H.append(_sem_table(cursems))
|
||||
else:
|
||||
# aucun semestre courant: affiche aide
|
||||
H.append(
|
||||
"""<h2 class="listesems">Aucune session en cours !</h2>
|
||||
<p>Pour ajouter une session, aller dans <a href="Notes" id="link-programmes">Programmes</a>,
|
||||
choisissez une formation, puis suivez le lien "<em>UE, modules, semestres</em>".
|
||||
</p><p>
|
||||
Là, en bas de page, suivez le lien
|
||||
"<em>Mettre en place un nouveau semestre de formation...</em>"
|
||||
</p>"""
|
||||
)
|
||||
|
||||
if showsemtable:
|
||||
H.append(
|
||||
f"""<hr>
|
||||
<h2>Semestres de {sco_preferences.get_preference("DeptName")}</h2>
|
||||
"""
|
||||
)
|
||||
H.append(_sem_table_gt(sems, showcodes=showcodes).html())
|
||||
H.append("</table>")
|
||||
if not showsemtable:
|
||||
H.append(
|
||||
f"""<hr>
|
||||
<p><a class="stdlink" href="{url_for('scolar.index_html',
|
||||
scodoc_dept=g.scodoc_dept, showsemtable=1)
|
||||
}">Voir table des semestres (dont {len(othersems)}
|
||||
verrouillé{'s' if len(othersems) else ''})</a>
|
||||
</p>"""
|
||||
)
|
||||
|
||||
H.append(
|
||||
f"""<p>
|
||||
<form action="{url_for('notes.view_formsemestre_by_etape', scodoc_dept=g.scodoc_dept)}">
|
||||
Chercher étape courante:
|
||||
<input name="etape_apo" type="text" size="8" spellcheck="false"></input>
|
||||
</form>
|
||||
</p>"""
|
||||
)
|
||||
#
|
||||
if current_user.has_permission(Permission.ScoEtudInscrit):
|
||||
H.append(
|
||||
"""<hr>
|
||||
<h3>Gestion des étudiants</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="etudident_create_form">créer <em>un</em> nouvel étudiant</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="form_students_import_excel">importer de nouveaux étudiants</a>
|
||||
(ne pas utiliser sauf cas particulier, utilisez plutôt le lien dans
|
||||
le tableau de bord semestre si vous souhaitez inscrire les
|
||||
étudiants importés à un semestre)
|
||||
</li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
#
|
||||
if current_user.has_permission(Permission.ScoEditApo):
|
||||
H.append(
|
||||
f"""<hr>
|
||||
<h3>Exports Apogée</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="{url_for('notes.semset_page', scodoc_dept=g.scodoc_dept)
|
||||
}">Années scolaires / exports Apogée</a></li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
#
|
||||
H.append(
|
||||
"""<hr>
|
||||
<h3>Assistance</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="sco_dump_and_send_db">Envoyer données</a></li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
#
|
||||
return (
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"ScoDoc {g.scodoc_dept}", javascripts=["js/scolar_index.js"]
|
||||
)
|
||||
+ "\n".join(H)
|
||||
+ html_sco_header.sco_footer()
|
||||
)
|
||||
|
||||
|
||||
def _sem_table(sems):
|
||||
"""Affiche liste des semestres, utilisée pour semestres en cours"""
|
||||
tmpl = """<tr class="%(trclass)s">%(tmpcode)s
|
||||
<td class="semicon">%(lockimg)s <a href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
|
||||
<td class="datesem">%(mois_debut)s <a title="%(session_id)s">-</a> %(mois_fin)s</td>
|
||||
<td><a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
||||
<span class="respsem">(%(responsable_name)s)</span>
|
||||
</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
# Liste des semestres, groupés par modalités
|
||||
sems_by_mod, modalites = sco_modalites.group_sems_by_modalite(sems)
|
||||
|
||||
H = ['<table class="listesems">']
|
||||
for modalite in modalites:
|
||||
if len(modalites) > 1:
|
||||
H.append('<tr><th colspan="3">%s</th></tr>' % modalite["titre"])
|
||||
|
||||
if sems_by_mod[modalite["modalite"]]:
|
||||
cur_idx = sems_by_mod[modalite["modalite"]][0]["semestre_id"]
|
||||
for sem in sems_by_mod[modalite["modalite"]]:
|
||||
if cur_idx != sem["semestre_id"]:
|
||||
sem["trclass"] = "firstsem" # separe les groupes de semestres
|
||||
cur_idx = sem["semestre_id"]
|
||||
else:
|
||||
sem["trclass"] = ""
|
||||
sem["notes_url"] = scu.NotesURL()
|
||||
H.append(tmpl % sem)
|
||||
H.append("</table>")
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def _sem_table_gt(sems, showcodes=False):
|
||||
"""Nouvelle version de la table des semestres
|
||||
Utilise une datatables.
|
||||
"""
|
||||
_style_sems(sems)
|
||||
columns_ids = (
|
||||
"lockimg",
|
||||
"semestre_id_n",
|
||||
"modalite",
|
||||
#'mois_debut',
|
||||
"dash_mois_fin",
|
||||
"titre_resp",
|
||||
"nb_inscrits",
|
||||
"etapes_apo_str",
|
||||
"elt_annee_apo",
|
||||
"elt_sem_apo",
|
||||
)
|
||||
if showcodes:
|
||||
columns_ids = ("formsemestre_id",) + columns_ids
|
||||
|
||||
html_class = "stripe cell-border compact hover order-column table_leftalign semlist"
|
||||
if current_user.has_permission(Permission.ScoEditApo):
|
||||
html_class += " apo_editable"
|
||||
tab = GenTable(
|
||||
titles={
|
||||
"formsemestre_id": "id",
|
||||
"semestre_id_n": "S#",
|
||||
"modalite": "",
|
||||
"mois_debut": "Début",
|
||||
"dash_mois_fin": "Année",
|
||||
"titre_resp": "Semestre",
|
||||
"nb_inscrits": "N",
|
||||
"etapes_apo_str": "Étape Apo.",
|
||||
"elt_annee_apo": "Elt. année Apo.",
|
||||
"elt_sem_apo": "Elt. sem. Apo.",
|
||||
},
|
||||
columns_ids=columns_ids,
|
||||
rows=sems,
|
||||
table_id="semlist",
|
||||
html_class_ignore_default=True,
|
||||
html_class=html_class,
|
||||
html_sortable=True,
|
||||
html_table_attrs=f"""
|
||||
data-apo_save_url="{url_for('notes.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept)}"
|
||||
data-elt_annee_apo_save_url="{url_for('notes.formsemestre_set_elt_annee_apo', scodoc_dept=g.scodoc_dept)}"
|
||||
data-elt_sem_apo_save_url="{url_for('notes.formsemestre_set_elt_sem_apo', scodoc_dept=g.scodoc_dept)}"
|
||||
""",
|
||||
html_with_td_classes=True,
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
|
||||
return tab
|
||||
|
||||
|
||||
def _style_sems(sems):
|
||||
"""ajoute quelques attributs de présentation pour la table"""
|
||||
for sem in sems:
|
||||
sem["notes_url"] = scu.NotesURL()
|
||||
sem["_groupicon_target"] = (
|
||||
"%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s"
|
||||
% sem
|
||||
)
|
||||
sem["_formsemestre_id_class"] = "blacktt"
|
||||
sem["dash_mois_fin"] = '<a title="%(session_id)s"></a> %(anneescolaire)s' % sem
|
||||
sem["_dash_mois_fin_class"] = "datesem"
|
||||
sem["titre_resp"] = (
|
||||
"""<a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
||||
<span class="respsem">(%(responsable_name)s)</span>"""
|
||||
% sem
|
||||
)
|
||||
sem["_css_row_class"] = "css_S%d css_M%s" % (
|
||||
sem["semestre_id"],
|
||||
sem["modalite"],
|
||||
)
|
||||
sem["_semestre_id_class"] = "semestre_id"
|
||||
sem["_modalite_class"] = "modalite"
|
||||
if sem["semestre_id"] == -1:
|
||||
sem["semestre_id_n"] = ""
|
||||
else:
|
||||
sem["semestre_id_n"] = sem["semestre_id"]
|
||||
# pour édition codes Apogée:
|
||||
sem[
|
||||
"_etapes_apo_str_td_attrs"
|
||||
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['etapes_apo_str']}" """
|
||||
sem[
|
||||
"_elt_annee_apo_td_attrs"
|
||||
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_annee_apo']}" """
|
||||
sem[
|
||||
"_elt_sem_apo_td_attrs"
|
||||
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """
|
||||
|
||||
|
||||
def delete_dept(dept_id: int):
|
||||
"""Suppression irréversible d'un département et de tous les objets rattachés"""
|
||||
assert isinstance(dept_id, int)
|
||||
|
||||
# Un peu complexe, merci JMP :)
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor()
|
||||
try:
|
||||
# 1- Create temp tables to store ids
|
||||
reqs = [
|
||||
"create temp table etudids_temp as select id from identite where dept_id = %(dept_id)s",
|
||||
"create temp table formsemestres_temp as select id from notes_formsemestre where dept_id = %(dept_id)s",
|
||||
"create temp table moduleimpls_temp as select id from notes_moduleimpl where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"create temp table formations_temp as select id from notes_formations where dept_id = %(dept_id)s",
|
||||
"create temp table tags_temp as select id from notes_tags where dept_id = %(dept_id)s",
|
||||
]
|
||||
for r in reqs:
|
||||
cursor.execute(r, {"dept_id": dept_id})
|
||||
|
||||
# 2- Delete student-related informations
|
||||
# ordered list of tables
|
||||
etud_tables = [
|
||||
"notes_notes",
|
||||
"group_membership",
|
||||
"admissions",
|
||||
"billet_absence",
|
||||
"adresse",
|
||||
"absences",
|
||||
"notes_notes_log",
|
||||
"notes_moduleimpl_inscription",
|
||||
"itemsuivi",
|
||||
"notes_appreciations",
|
||||
"scolar_autorisation_inscription",
|
||||
"absences_notifications",
|
||||
"notes_formsemestre_inscription",
|
||||
"scolar_formsemestre_validation",
|
||||
"scolar_events",
|
||||
]
|
||||
for table in etud_tables:
|
||||
cursor.execute(
|
||||
f"delete from {table} where etudid in (select id from etudids_temp)"
|
||||
)
|
||||
|
||||
reqs = [
|
||||
"delete from identite where dept_id = %(dept_id)s",
|
||||
"delete from sco_prefs where dept_id = %(dept_id)s",
|
||||
"delete from notes_semset_formsemestre where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_evaluation where moduleimpl_id in (select id from moduleimpls_temp)",
|
||||
"delete from notes_modules_enseignants where moduleimpl_id in (select id from moduleimpls_temp)",
|
||||
"delete from notes_formsemestre_uecoef where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_formsemestre_ue_computation_expr where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_formsemestre_responsables where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_moduleimpl where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_modules_tags where tag_id in (select id from tags_temp)",
|
||||
"delete from notes_tags where dept_id = %(dept_id)s",
|
||||
"delete from notes_modules where formation_id in (select id from formations_temp)",
|
||||
"delete from notes_matieres where ue_id in (select id from notes_ue where formation_id in (select id from formations_temp))",
|
||||
"delete from notes_formsemestre_etapes where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from group_descr where partition_id in (select id from partition where formsemestre_id in (select id from formsemestres_temp))",
|
||||
"delete from partition where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_formsemestre_custommenu where formsemestre_id in (select id from formsemestres_temp)",
|
||||
"delete from notes_ue where formation_id in (select id from formations_temp)",
|
||||
"delete from notes_formsemestre where dept_id = %(dept_id)s",
|
||||
"delete from scolar_news where dept_id = %(dept_id)s",
|
||||
"delete from notes_semset where dept_id = %(dept_id)s",
|
||||
"delete from notes_formations where dept_id = %(dept_id)s",
|
||||
"delete from departement where id = %(dept_id)s",
|
||||
"drop table tags_temp",
|
||||
"drop table formations_temp",
|
||||
"drop table moduleimpls_temp",
|
||||
"drop table etudids_temp",
|
||||
"drop table formsemestres_temp",
|
||||
]
|
||||
for r in reqs:
|
||||
cursor.execute(r, {"dept_id": dept_id})
|
||||
except:
|
||||
cnx.rollback()
|
||||
finally:
|
||||
cnx.commit()
|
||||
app.clear_scodoc_cache()
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -907,7 +907,7 @@ def fill_etuds_info(etuds: list[dict], add_admission=True):
|
|||
etud["ilycee"] = "Lycée " + format_lycee(etud["nomlycee"])
|
||||
if etud["villelycee"]:
|
||||
etud["ilycee"] += " (%s)" % etud.get("villelycee", "")
|
||||
etud["ilycee"] += "<br/>"
|
||||
etud["ilycee"] += "<br>"
|
||||
else:
|
||||
if etud.get("codelycee"):
|
||||
etud["ilycee"] = format_lycee_from_code(etud["codelycee"])
|
||||
|
|
|
@ -1,257 +1,257 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Vérification des absences à une évaluation
|
||||
"""
|
||||
from flask import url_for, g
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_moduleimpl
|
||||
|
||||
# matin et/ou après-midi ?
|
||||
def _eval_demijournee(E):
|
||||
"1 si matin, 0 si apres midi, 2 si toute la journee"
|
||||
am, pm = False, False
|
||||
if E["heure_debut"] < "13:00":
|
||||
am = True
|
||||
if E["heure_fin"] > "13:00":
|
||||
pm = True
|
||||
if am and pm:
|
||||
demijournee = 2
|
||||
elif am:
|
||||
demijournee = 1
|
||||
else:
|
||||
demijournee = 0
|
||||
pm = True
|
||||
return am, pm, demijournee
|
||||
|
||||
|
||||
def evaluation_check_absences(evaluation_id):
|
||||
"""Vérifie les absences au moment de cette évaluation.
|
||||
Cas incohérents que l'on peut rencontrer pour chaque étudiant:
|
||||
note et absent
|
||||
ABS et pas noté absent
|
||||
ABS et absent justifié
|
||||
EXC et pas noté absent
|
||||
EXC et pas justifie
|
||||
Ramene 3 listes d'etudid
|
||||
"""
|
||||
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||||
if not E["jour"]:
|
||||
return [], [], [], [], [] # evaluation sans date
|
||||
|
||||
am, pm, demijournee = _eval_demijournee(E)
|
||||
|
||||
# Liste les absences à ce moment:
|
||||
A = sco_abs.list_abs_jour(ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm)
|
||||
As = set([x["etudid"] for x in A]) # ensemble des etudiants absents
|
||||
NJ = sco_abs.list_abs_non_just_jour(ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm)
|
||||
NJs = set([x["etudid"] for x in NJ]) # ensemble des etudiants absents non justifies
|
||||
Just = sco_abs.list_abs_jour(
|
||||
ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm, is_abs=None, is_just=True
|
||||
)
|
||||
Justs = set([x["etudid"] for x in Just]) # ensemble des etudiants avec justif
|
||||
|
||||
# Les notes:
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
ValButAbs = [] # une note mais noté absent
|
||||
AbsNonSignalee = [] # note ABS mais pas noté absent
|
||||
ExcNonSignalee = [] # note EXC mais pas noté absent
|
||||
ExcNonJust = [] # note EXC mais absent non justifie
|
||||
AbsButExc = [] # note ABS mais justifié
|
||||
for etudid, _ in sco_groups.do_evaluation_listeetuds_groups(
|
||||
evaluation_id, getallstudents=True
|
||||
):
|
||||
if etudid in notes_db:
|
||||
val = notes_db[etudid]["value"]
|
||||
if (
|
||||
val != None and val != scu.NOTES_NEUTRALISE and val != scu.NOTES_ATTENTE
|
||||
) and etudid in As:
|
||||
# note valide et absent
|
||||
ValButAbs.append(etudid)
|
||||
if val is None and not etudid in As:
|
||||
# absent mais pas signale comme tel
|
||||
AbsNonSignalee.append(etudid)
|
||||
if val == scu.NOTES_NEUTRALISE and not etudid in As:
|
||||
# Neutralisé mais pas signale absent
|
||||
ExcNonSignalee.append(etudid)
|
||||
if val == scu.NOTES_NEUTRALISE and etudid in NJs:
|
||||
# EXC mais pas justifié
|
||||
ExcNonJust.append(etudid)
|
||||
if val is None and etudid in Justs:
|
||||
# ABS mais justificatif
|
||||
AbsButExc.append(etudid)
|
||||
|
||||
return ValButAbs, AbsNonSignalee, ExcNonSignalee, ExcNonJust, AbsButExc
|
||||
|
||||
|
||||
def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True):
|
||||
"""Affiche état vérification absences d'une évaluation"""
|
||||
|
||||
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||||
am, pm, demijournee = _eval_demijournee(E)
|
||||
|
||||
(
|
||||
ValButAbs,
|
||||
AbsNonSignalee,
|
||||
ExcNonSignalee,
|
||||
ExcNonJust,
|
||||
AbsButExc,
|
||||
) = evaluation_check_absences(evaluation_id)
|
||||
|
||||
if with_header:
|
||||
H = [
|
||||
html_sco_header.html_sem_header("Vérification absences à l'évaluation"),
|
||||
sco_evaluations.evaluation_describe(evaluation_id=evaluation_id),
|
||||
"""<p class="help">Vérification de la cohérence entre les notes saisies et les absences signalées.</p>""",
|
||||
]
|
||||
else:
|
||||
# pas de header, mais un titre
|
||||
H = [
|
||||
"""<h2 class="eval_check_absences">%s du %s """
|
||||
% (E["description"], E["jour"])
|
||||
]
|
||||
if (
|
||||
not ValButAbs
|
||||
and not AbsNonSignalee
|
||||
and not ExcNonSignalee
|
||||
and not ExcNonJust
|
||||
):
|
||||
H.append(': <span class="eval_check_absences_ok">ok</span>')
|
||||
H.append("</h2>")
|
||||
|
||||
def etudlist(etudids, linkabs=False):
|
||||
H.append("<ul>")
|
||||
if not etudids and show_ok:
|
||||
H.append("<li>aucun</li>")
|
||||
for etudid in etudids:
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
H.append(
|
||||
'<li><a class="discretelink" href="%s">'
|
||||
% url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
||||
)
|
||||
+ "%(nomprenom)s</a>" % etud
|
||||
)
|
||||
if linkabs:
|
||||
H.append(
|
||||
f"""<a class="stdlink" href="{url_for(
|
||||
'absences.doSignaleAbsence',
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud["etudid"],
|
||||
datedebut=E["jour"],
|
||||
datefin=E["jour"],
|
||||
demijournee=demijournee,
|
||||
moduleimpl_id=E["moduleimpl_id"],
|
||||
)
|
||||
}">signaler cette absence</a>"""
|
||||
)
|
||||
H.append("</li>")
|
||||
H.append("</ul>")
|
||||
|
||||
if ValButAbs or show_ok:
|
||||
H.append(
|
||||
"<h3>Etudiants ayant une note alors qu'ils sont signalés absents:</h3>"
|
||||
)
|
||||
etudlist(ValButAbs)
|
||||
|
||||
if AbsNonSignalee or show_ok:
|
||||
H.append(
|
||||
"""<h3>Etudiants avec note "ABS" alors qu'ils ne sont <em>pas</em> signalés absents:</h3>"""
|
||||
)
|
||||
etudlist(AbsNonSignalee, linkabs=True)
|
||||
|
||||
if ExcNonSignalee or show_ok:
|
||||
H.append(
|
||||
"""<h3>Etudiants avec note "EXC" alors qu'ils ne sont <em>pas</em> signalés absents:</h3>"""
|
||||
)
|
||||
etudlist(ExcNonSignalee)
|
||||
|
||||
if ExcNonJust or show_ok:
|
||||
H.append(
|
||||
"""<h3>Etudiants avec note "EXC" alors qu'ils sont absents <em>non justifiés</em>:</h3>"""
|
||||
)
|
||||
etudlist(ExcNonJust)
|
||||
|
||||
if AbsButExc or show_ok:
|
||||
H.append(
|
||||
"""<h3>Etudiants avec note "ABS" alors qu'ils ont une <em>justification</em>:</h3>"""
|
||||
)
|
||||
etudlist(AbsButExc)
|
||||
|
||||
if with_header:
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def formsemestre_check_absences_html(formsemestre_id):
|
||||
"""Affiche etat verification absences pour toutes les evaluations du semestre !"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"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.
|
||||
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.
|
||||
</p>""",
|
||||
]
|
||||
# Modules, dans l'ordre
|
||||
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
||||
for M in Mlist:
|
||||
evals = sco_evaluation_db.do_evaluation_list(
|
||||
{"moduleimpl_id": M["moduleimpl_id"]}
|
||||
)
|
||||
if evals:
|
||||
H.append(
|
||||
'<div class="module_check_absences"><h2><a href="moduleimpl_status?moduleimpl_id=%s">%s: %s</a></h2>'
|
||||
% (
|
||||
M["moduleimpl_id"],
|
||||
M["module"]["code"] or "",
|
||||
M["module"]["abbrev"] or "",
|
||||
)
|
||||
)
|
||||
for E in evals:
|
||||
H.append(
|
||||
evaluation_check_absences_html(
|
||||
E["evaluation_id"],
|
||||
with_header=False,
|
||||
show_ok=False,
|
||||
)
|
||||
)
|
||||
if evals:
|
||||
H.append("</div>")
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return "\n".join(H)
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Vérification des absences à une évaluation
|
||||
"""
|
||||
from flask import url_for, g
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_moduleimpl
|
||||
|
||||
# matin et/ou après-midi ?
|
||||
def _eval_demijournee(E):
|
||||
"1 si matin, 0 si apres midi, 2 si toute la journee"
|
||||
am, pm = False, False
|
||||
if E["heure_debut"] < "13:00":
|
||||
am = True
|
||||
if E["heure_fin"] > "13:00":
|
||||
pm = True
|
||||
if am and pm:
|
||||
demijournee = 2
|
||||
elif am:
|
||||
demijournee = 1
|
||||
else:
|
||||
demijournee = 0
|
||||
pm = True
|
||||
return am, pm, demijournee
|
||||
|
||||
|
||||
def evaluation_check_absences(evaluation_id):
|
||||
"""Vérifie les absences au moment de cette évaluation.
|
||||
Cas incohérents que l'on peut rencontrer pour chaque étudiant:
|
||||
note et absent
|
||||
ABS et pas noté absent
|
||||
ABS et absent justifié
|
||||
EXC et pas noté absent
|
||||
EXC et pas justifie
|
||||
Ramene 3 listes d'etudid
|
||||
"""
|
||||
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||||
if not E["jour"]:
|
||||
return [], [], [], [], [] # evaluation sans date
|
||||
|
||||
am, pm, demijournee = _eval_demijournee(E)
|
||||
|
||||
# Liste les absences à ce moment:
|
||||
A = sco_abs.list_abs_jour(ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm)
|
||||
As = set([x["etudid"] for x in A]) # ensemble des etudiants absents
|
||||
NJ = sco_abs.list_abs_non_just_jour(ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm)
|
||||
NJs = set([x["etudid"] for x in NJ]) # ensemble des etudiants absents non justifies
|
||||
Just = sco_abs.list_abs_jour(
|
||||
ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm, is_abs=None, is_just=True
|
||||
)
|
||||
Justs = set([x["etudid"] for x in Just]) # ensemble des etudiants avec justif
|
||||
|
||||
# Les notes:
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
ValButAbs = [] # une note mais noté absent
|
||||
AbsNonSignalee = [] # note ABS mais pas noté absent
|
||||
ExcNonSignalee = [] # note EXC mais pas noté absent
|
||||
ExcNonJust = [] # note EXC mais absent non justifie
|
||||
AbsButExc = [] # note ABS mais justifié
|
||||
for etudid, _ in sco_groups.do_evaluation_listeetuds_groups(
|
||||
evaluation_id, getallstudents=True
|
||||
):
|
||||
if etudid in notes_db:
|
||||
val = notes_db[etudid]["value"]
|
||||
if (
|
||||
val != None and val != scu.NOTES_NEUTRALISE and val != scu.NOTES_ATTENTE
|
||||
) and etudid in As:
|
||||
# note valide et absent
|
||||
ValButAbs.append(etudid)
|
||||
if val is None and not etudid in As:
|
||||
# absent mais pas signale comme tel
|
||||
AbsNonSignalee.append(etudid)
|
||||
if val == scu.NOTES_NEUTRALISE and not etudid in As:
|
||||
# Neutralisé mais pas signale absent
|
||||
ExcNonSignalee.append(etudid)
|
||||
if val == scu.NOTES_NEUTRALISE and etudid in NJs:
|
||||
# EXC mais pas justifié
|
||||
ExcNonJust.append(etudid)
|
||||
if val is None and etudid in Justs:
|
||||
# ABS mais justificatif
|
||||
AbsButExc.append(etudid)
|
||||
|
||||
return ValButAbs, AbsNonSignalee, ExcNonSignalee, ExcNonJust, AbsButExc
|
||||
|
||||
|
||||
def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True):
|
||||
"""Affiche état vérification absences d'une évaluation"""
|
||||
|
||||
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||||
am, pm, demijournee = _eval_demijournee(E)
|
||||
|
||||
(
|
||||
ValButAbs,
|
||||
AbsNonSignalee,
|
||||
ExcNonSignalee,
|
||||
ExcNonJust,
|
||||
AbsButExc,
|
||||
) = evaluation_check_absences(evaluation_id)
|
||||
|
||||
if with_header:
|
||||
H = [
|
||||
html_sco_header.html_sem_header("Vérification absences à l'évaluation"),
|
||||
sco_evaluations.evaluation_describe(evaluation_id=evaluation_id),
|
||||
"""<p class="help">Vérification de la cohérence entre les notes saisies et les absences signalées.</p>""",
|
||||
]
|
||||
else:
|
||||
# pas de header, mais un titre
|
||||
H = [
|
||||
"""<h2 class="eval_check_absences">%s du %s """
|
||||
% (E["description"], E["jour"])
|
||||
]
|
||||
if (
|
||||
not ValButAbs
|
||||
and not AbsNonSignalee
|
||||
and not ExcNonSignalee
|
||||
and not ExcNonJust
|
||||
):
|
||||
H.append(': <span class="eval_check_absences_ok">ok</span>')
|
||||
H.append("</h2>")
|
||||
|
||||
def etudlist(etudids, linkabs=False):
|
||||
H.append("<ul>")
|
||||
if not etudids and show_ok:
|
||||
H.append("<li>aucun</li>")
|
||||
for etudid in etudids:
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
H.append(
|
||||
'<li><a class="discretelink" href="%s">'
|
||||
% url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
||||
)
|
||||
+ "%(nomprenom)s</a>" % etud
|
||||
)
|
||||
if linkabs:
|
||||
H.append(
|
||||
f"""<a class="stdlink" href="{url_for(
|
||||
'absences.doSignaleAbsence',
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud["etudid"],
|
||||
datedebut=E["jour"],
|
||||
datefin=E["jour"],
|
||||
demijournee=demijournee,
|
||||
moduleimpl_id=E["moduleimpl_id"],
|
||||
)
|
||||
}">signaler cette absence</a>"""
|
||||
)
|
||||
H.append("</li>")
|
||||
H.append("</ul>")
|
||||
|
||||
if ValButAbs or show_ok:
|
||||
H.append(
|
||||
"<h3>Etudiants ayant une note alors qu'ils sont signalés absents:</h3>"
|
||||
)
|
||||
etudlist(ValButAbs)
|
||||
|
||||
if AbsNonSignalee or show_ok:
|
||||
H.append(
|
||||
"""<h3>Etudiants avec note "ABS" alors qu'ils ne sont <em>pas</em> signalés absents:</h3>"""
|
||||
)
|
||||
etudlist(AbsNonSignalee, linkabs=True)
|
||||
|
||||
if ExcNonSignalee or show_ok:
|
||||
H.append(
|
||||
"""<h3>Etudiants avec note "EXC" alors qu'ils ne sont <em>pas</em> signalés absents:</h3>"""
|
||||
)
|
||||
etudlist(ExcNonSignalee)
|
||||
|
||||
if ExcNonJust or show_ok:
|
||||
H.append(
|
||||
"""<h3>Etudiants avec note "EXC" alors qu'ils sont absents <em>non justifiés</em>:</h3>"""
|
||||
)
|
||||
etudlist(ExcNonJust)
|
||||
|
||||
if AbsButExc or show_ok:
|
||||
H.append(
|
||||
"""<h3>Etudiants avec note "ABS" alors qu'ils ont une <em>justification</em>:</h3>"""
|
||||
)
|
||||
etudlist(AbsButExc)
|
||||
|
||||
if with_header:
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def formsemestre_check_absences_html(formsemestre_id):
|
||||
"""Affiche etat verification absences pour toutes les evaluations du semestre !"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"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.
|
||||
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.
|
||||
</p>""",
|
||||
]
|
||||
# Modules, dans l'ordre
|
||||
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
||||
for M in Mlist:
|
||||
evals = sco_evaluation_db.do_evaluation_list(
|
||||
{"moduleimpl_id": M["moduleimpl_id"]}
|
||||
)
|
||||
if evals:
|
||||
H.append(
|
||||
'<div class="module_check_absences"><h2><a href="moduleimpl_status?moduleimpl_id=%s">%s: %s</a></h2>'
|
||||
% (
|
||||
M["moduleimpl_id"],
|
||||
M["module"]["code"] or "",
|
||||
M["module"]["abbrev"] or "",
|
||||
)
|
||||
)
|
||||
for E in evals:
|
||||
H.append(
|
||||
evaluation_check_absences_html(
|
||||
E["evaluation_id"],
|
||||
with_header=False,
|
||||
show_ok=False,
|
||||
)
|
||||
)
|
||||
if evals:
|
||||
H.append("</div>")
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return "\n".join(H)
|
||||
|
|
|
@ -1,411 +1,411 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Recherche d'étudiants
|
||||
"""
|
||||
import flask
|
||||
from flask import url_for, g, request
|
||||
from flask_login import current_user
|
||||
|
||||
import app
|
||||
from app.models import Departement
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc.sco_exceptions import ScoException
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
|
||||
def form_search_etud(
|
||||
dest_url=None,
|
||||
parameters=None,
|
||||
parameters_keys=None,
|
||||
title="Rechercher un étudiant par nom : ",
|
||||
add_headers=False, # complete page
|
||||
):
|
||||
"form recherche par nom"
|
||||
H = []
|
||||
H.append(
|
||||
f"""<form action="{ url_for("scolar.search_etud_in_dept", scodoc_dept=g.scodoc_dept) }" method="POST">
|
||||
<b>{title}</b>
|
||||
<input type="text" name="expnom" class="in-expnom" width="12" spellcheck="false" value="">
|
||||
<input type="submit" value="Chercher">
|
||||
<br/>(entrer une partie du nom)
|
||||
"""
|
||||
)
|
||||
if dest_url:
|
||||
H.append('<input type="hidden" name="dest_url" value="%s"/>' % dest_url)
|
||||
if parameters:
|
||||
for param in parameters.keys():
|
||||
H.append(
|
||||
'<input type="hidden" name="%s" value="%s"/>'
|
||||
% (param, parameters[param])
|
||||
)
|
||||
H.append(
|
||||
'<input type="hidden" name="parameters_keys" value="%s"/>'
|
||||
% (",".join(parameters.keys()))
|
||||
)
|
||||
elif parameters_keys:
|
||||
if request.method == "POST":
|
||||
vals = request.form
|
||||
elif request.method == "GET":
|
||||
vals = request.args
|
||||
else:
|
||||
vals = {}
|
||||
for key in parameters_keys.split(","):
|
||||
v = vals.get(key, False)
|
||||
if v:
|
||||
H.append('<input type="hidden" name="%s" value="%s"/>' % (key, v))
|
||||
H.append(
|
||||
'<input type="hidden" name="parameters_keys" value="%s"/>' % parameters_keys
|
||||
)
|
||||
H.append("</form>")
|
||||
|
||||
if add_headers:
|
||||
return (
|
||||
html_sco_header.sco_header(page_title="Choix d'un étudiant")
|
||||
+ "\n".join(H)
|
||||
+ html_sco_header.sco_footer()
|
||||
)
|
||||
else:
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def search_etud_in_dept(expnom=""):
|
||||
"""Page recherche d'un etudiant.
|
||||
|
||||
Affiche la fiche de l'étudiant, ou, si la recherche donne plusieurs résultats,
|
||||
la liste des étudiants correspondants.
|
||||
Appelée par:
|
||||
- boite de recherche barre latérale gauche.
|
||||
- choix d'un étudiant à inscrire (en POST avec dest_url et parameters_keys)
|
||||
|
||||
Args:
|
||||
expnom: string, regexp sur le nom ou un code_nip ou un etudid
|
||||
"""
|
||||
if isinstance(expnom, int) or len(expnom) > 1:
|
||||
try:
|
||||
etudid = int(expnom)
|
||||
except ValueError:
|
||||
etudid = None
|
||||
if etudid is not None:
|
||||
etuds = sco_etud.get_etud_info(filled=True, etudid=expnom)
|
||||
if (etudid is None) or len(etuds) != 1:
|
||||
expnom_str = str(expnom)
|
||||
if scu.is_valid_code_nip(expnom_str):
|
||||
etuds = search_etuds_infos(code_nip=expnom_str)
|
||||
else:
|
||||
etuds = search_etuds_infos(expnom=expnom_str)
|
||||
else:
|
||||
etuds = [] # si expnom est trop court, n'affiche rien
|
||||
|
||||
if request.method == "POST":
|
||||
vals = request.form
|
||||
elif request.method == "GET":
|
||||
vals = request.args
|
||||
else:
|
||||
vals = {}
|
||||
|
||||
url_args = {"scodoc_dept": g.scodoc_dept}
|
||||
if "dest_url" in vals:
|
||||
endpoint = vals["dest_url"]
|
||||
else:
|
||||
endpoint = "scolar.ficheEtud"
|
||||
if "parameters_keys" in vals:
|
||||
for key in vals["parameters_keys"].split(","):
|
||||
url_args[key] = vals[key]
|
||||
|
||||
if len(etuds) == 1:
|
||||
# va directement a la fiche
|
||||
url_args["etudid"] = etuds[0]["etudid"]
|
||||
return flask.redirect(url_for(endpoint, **url_args))
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Recherche d'un étudiant",
|
||||
no_side_bar=False,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
)
|
||||
]
|
||||
if len(etuds) == 0 and len(etuds) <= 1:
|
||||
H.append("""<h2>chercher un étudiant:</h2>""")
|
||||
else:
|
||||
H.append(
|
||||
f"""<h2>{len(etuds)} résultats pour "<tt>{expnom}</tt>": choisissez un étudiant:</h2>"""
|
||||
)
|
||||
H.append(
|
||||
form_search_etud(
|
||||
dest_url=endpoint,
|
||||
parameters=vals.get("parameters"),
|
||||
parameters_keys=vals.get("parameters_keys"),
|
||||
title="Autre recherche",
|
||||
)
|
||||
)
|
||||
if len(etuds) > 0:
|
||||
# Choix dans la liste des résultats:
|
||||
for e in etuds:
|
||||
url_args["etudid"] = e["etudid"]
|
||||
target = url_for(endpoint, **url_args)
|
||||
e["_nomprenom_target"] = target
|
||||
e["inscription_target"] = target
|
||||
e["_nomprenom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"])
|
||||
sco_groups.etud_add_group_infos(
|
||||
e, e["cursem"]["formsemestre_id"] if e["cursem"] else None
|
||||
)
|
||||
|
||||
tab = GenTable(
|
||||
columns_ids=("nomprenom", "code_nip", "inscription", "groupes"),
|
||||
titles={
|
||||
"nomprenom": "Étudiant",
|
||||
"code_nip": "NIP",
|
||||
"inscription": "Inscription",
|
||||
"groupes": "Groupes",
|
||||
},
|
||||
rows=etuds,
|
||||
html_sortable=True,
|
||||
html_class="table_leftalign",
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
H.append(tab.html())
|
||||
if len(etuds) > 20: # si la page est grande
|
||||
H.append(
|
||||
form_search_etud(
|
||||
dest_url=endpoint,
|
||||
parameters=vals.get("parameters"),
|
||||
parameters_keys=vals.get("parameters_keys"),
|
||||
title="Autre recherche",
|
||||
)
|
||||
)
|
||||
else:
|
||||
H.append('<h2 style="color: red;">Aucun résultat pour "%s".</h2>' % expnom)
|
||||
H.append(
|
||||
"""<p class="help">La recherche porte sur tout ou partie du NOM ou du NIP de l'étudiant. Saisir au moins deux caractères.</p>"""
|
||||
)
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
# Was chercheEtudsInfo()
|
||||
def search_etuds_infos(expnom=None, code_nip=None):
|
||||
"""recherche les étudiants correspondants à expnom ou au code_nip
|
||||
et ramene liste de mappings utilisables en DTML.
|
||||
"""
|
||||
may_be_nip = scu.is_valid_code_nip(expnom)
|
||||
cnx = ndb.GetDBConnexion()
|
||||
if expnom and not may_be_nip:
|
||||
expnom = expnom.upper() # les noms dans la BD sont en uppercase
|
||||
try:
|
||||
etuds = sco_etud.etudident_list(cnx, args={"nom": expnom}, test="~")
|
||||
except ScoException:
|
||||
etuds = []
|
||||
else:
|
||||
code_nip = code_nip or expnom
|
||||
if code_nip:
|
||||
etuds = sco_etud.etudident_list(cnx, args={"code_nip": str(code_nip)})
|
||||
else:
|
||||
etuds = []
|
||||
sco_etud.fill_etuds_info(etuds)
|
||||
return etuds
|
||||
|
||||
|
||||
def search_etud_by_name(term: str) -> list:
|
||||
"""Recherche noms étudiants par début du nom, pour autocomplete
|
||||
Accepte aussi un début de code NIP (au moins 6 caractères)
|
||||
Renvoie une liste de dicts
|
||||
{ "label" : "<nip> <nom> <prenom>", "value" : etudid }
|
||||
"""
|
||||
may_be_nip = scu.is_valid_code_nip(term)
|
||||
# term = term.upper() # conserve les accents
|
||||
term = term.upper()
|
||||
if (
|
||||
not scu.ALPHANUM_EXP.match(term) # n'autorise pas les caractères spéciaux
|
||||
and not may_be_nip
|
||||
):
|
||||
data = []
|
||||
else:
|
||||
if may_be_nip:
|
||||
r = ndb.SimpleDictFetch(
|
||||
"""SELECT nom, prenom, code_nip
|
||||
FROM identite
|
||||
WHERE
|
||||
dept_id = %(dept_id)s
|
||||
AND code_nip LIKE %(beginning)s
|
||||
ORDER BY nom
|
||||
""",
|
||||
{"beginning": term + "%", "dept_id": g.scodoc_dept_id},
|
||||
)
|
||||
data = [
|
||||
{
|
||||
"label": "%s %s %s"
|
||||
% (x["code_nip"], x["nom"], sco_etud.format_prenom(x["prenom"])),
|
||||
"value": x["code_nip"],
|
||||
}
|
||||
for x in r
|
||||
]
|
||||
else:
|
||||
r = ndb.SimpleDictFetch(
|
||||
"""SELECT id AS etudid, nom, prenom
|
||||
FROM identite
|
||||
WHERE
|
||||
dept_id = %(dept_id)s
|
||||
AND nom LIKE %(beginning)s
|
||||
ORDER BY nom
|
||||
""",
|
||||
{"beginning": term + "%", "dept_id": g.scodoc_dept_id},
|
||||
)
|
||||
|
||||
data = [
|
||||
{
|
||||
"label": "%s %s" % (x["nom"], sco_etud.format_prenom(x["prenom"])),
|
||||
"value": x["etudid"],
|
||||
}
|
||||
for x in r
|
||||
]
|
||||
return data
|
||||
|
||||
|
||||
# ---------- Recherche sur plusieurs département
|
||||
|
||||
|
||||
def search_etud_in_accessible_depts(expnom=None, code_nip=None):
|
||||
"""
|
||||
result is a list of (sorted) etuds, one list per dept.
|
||||
"""
|
||||
result = []
|
||||
accessible_depts = []
|
||||
depts = Departement.query.filter_by(visible=True).all()
|
||||
for dept in depts:
|
||||
if current_user.has_permission(Permission.ScoView, dept=dept.acronym):
|
||||
if expnom or code_nip:
|
||||
accessible_depts.append(dept.acronym)
|
||||
app.set_sco_dept(dept.acronym)
|
||||
etuds = search_etuds_infos(expnom=expnom, code_nip=code_nip)
|
||||
else:
|
||||
etuds = []
|
||||
result.append(etuds)
|
||||
return result, accessible_depts
|
||||
|
||||
|
||||
def table_etud_in_accessible_depts(expnom=None):
|
||||
"""
|
||||
Page avec table étudiants trouvés, dans tous les departements.
|
||||
Attention: nous sommes ici au niveau de ScoDoc, pas dans un département
|
||||
"""
|
||||
result, accessible_depts = search_etud_in_accessible_depts(expnom=expnom)
|
||||
H = [
|
||||
"""<div class="table_etud_in_accessible_depts">""",
|
||||
"""<h3>Recherche multi-département de "<tt>%s</tt>"</h3>""" % expnom,
|
||||
]
|
||||
for etuds in result:
|
||||
if etuds:
|
||||
dept_id = etuds[0]["dept"]
|
||||
# H.append('<h3>Département %s</h3>' % DeptId)
|
||||
for e in etuds:
|
||||
e["_nomprenom_target"] = url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=dept_id, etudid=e["etudid"]
|
||||
)
|
||||
e["_nomprenom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"])
|
||||
|
||||
tab = GenTable(
|
||||
titles={"nomprenom": "Étudiants en " + dept_id},
|
||||
columns_ids=("nomprenom",),
|
||||
rows=etuds,
|
||||
html_sortable=True,
|
||||
html_class="table_leftalign",
|
||||
)
|
||||
|
||||
H.append('<div class="table_etud_in_dept">')
|
||||
H.append(tab.html())
|
||||
H.append("</div>")
|
||||
if len(accessible_depts) > 1:
|
||||
ss = "s"
|
||||
else:
|
||||
ss = ""
|
||||
H.append(
|
||||
f"""<p>(recherche menée dans le{ss} département{ss}:
|
||||
{", ".join(accessible_depts)})
|
||||
</p>
|
||||
<p>
|
||||
<a href="{url_for("scodoc.index")}" class="stdlink">Retour à l'accueil</a>
|
||||
</p>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
return (
|
||||
html_sco_header.scodoc_top_html_header(page_title="Choix d'un étudiant")
|
||||
+ "\n".join(H)
|
||||
+ html_sco_header.standard_html_footer()
|
||||
)
|
||||
|
||||
|
||||
def search_inscr_etud_by_nip(code_nip, format="json"):
|
||||
"""Recherche multi-departement d'un étudiant par son code NIP
|
||||
Seuls les départements accessibles par l'utilisateur sont cherchés.
|
||||
|
||||
Renvoie une liste des inscriptions de l'étudiants dans tout ScoDoc:
|
||||
code_nip, nom, prenom, civilite_str, dept, formsemestre_id, date_debut_sem, date_fin_sem
|
||||
"""
|
||||
result, _ = search_etud_in_accessible_depts(code_nip=code_nip)
|
||||
|
||||
T = []
|
||||
for etuds in result:
|
||||
if etuds:
|
||||
dept_id = etuds[0]["dept"]
|
||||
for e in etuds:
|
||||
for sem in e["sems"]:
|
||||
T.append(
|
||||
{
|
||||
"dept": dept_id,
|
||||
"etudid": e["etudid"],
|
||||
"code_nip": e["code_nip"],
|
||||
"civilite_str": e["civilite_str"],
|
||||
"nom": e["nom"],
|
||||
"prenom": e["prenom"],
|
||||
"formsemestre_id": sem["formsemestre_id"],
|
||||
"date_debut_iso": sem["date_debut_iso"],
|
||||
"date_fin_iso": sem["date_fin_iso"],
|
||||
}
|
||||
)
|
||||
|
||||
columns_ids = (
|
||||
"dept",
|
||||
"etudid",
|
||||
"code_nip",
|
||||
"civilite_str",
|
||||
"nom",
|
||||
"prenom",
|
||||
"formsemestre_id",
|
||||
"date_debut_iso",
|
||||
"date_fin_iso",
|
||||
)
|
||||
tab = GenTable(columns_ids=columns_ids, rows=T)
|
||||
|
||||
return tab.make_page(format=format, with_html_headers=False, publish=True)
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Recherche d'étudiants
|
||||
"""
|
||||
import flask
|
||||
from flask import url_for, g, request
|
||||
from flask_login import current_user
|
||||
|
||||
import app
|
||||
from app.models import Departement
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc.sco_exceptions import ScoException
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
|
||||
def form_search_etud(
|
||||
dest_url=None,
|
||||
parameters=None,
|
||||
parameters_keys=None,
|
||||
title="Rechercher un étudiant par nom : ",
|
||||
add_headers=False, # complete page
|
||||
):
|
||||
"form recherche par nom"
|
||||
H = []
|
||||
H.append(
|
||||
f"""<form action="{ url_for("scolar.search_etud_in_dept", scodoc_dept=g.scodoc_dept) }" method="POST">
|
||||
<b>{title}</b>
|
||||
<input type="text" name="expnom" class="in-expnom" width="12" spellcheck="false" value="">
|
||||
<input type="submit" value="Chercher">
|
||||
<br>(entrer une partie du nom)
|
||||
"""
|
||||
)
|
||||
if dest_url:
|
||||
H.append('<input type="hidden" name="dest_url" value="%s"/>' % dest_url)
|
||||
if parameters:
|
||||
for param in parameters.keys():
|
||||
H.append(
|
||||
'<input type="hidden" name="%s" value="%s"/>'
|
||||
% (param, parameters[param])
|
||||
)
|
||||
H.append(
|
||||
'<input type="hidden" name="parameters_keys" value="%s"/>'
|
||||
% (",".join(parameters.keys()))
|
||||
)
|
||||
elif parameters_keys:
|
||||
if request.method == "POST":
|
||||
vals = request.form
|
||||
elif request.method == "GET":
|
||||
vals = request.args
|
||||
else:
|
||||
vals = {}
|
||||
for key in parameters_keys.split(","):
|
||||
v = vals.get(key, False)
|
||||
if v:
|
||||
H.append('<input type="hidden" name="%s" value="%s"/>' % (key, v))
|
||||
H.append(
|
||||
'<input type="hidden" name="parameters_keys" value="%s"/>' % parameters_keys
|
||||
)
|
||||
H.append("</form>")
|
||||
|
||||
if add_headers:
|
||||
return (
|
||||
html_sco_header.sco_header(page_title="Choix d'un étudiant")
|
||||
+ "\n".join(H)
|
||||
+ html_sco_header.sco_footer()
|
||||
)
|
||||
else:
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def search_etud_in_dept(expnom=""):
|
||||
"""Page recherche d'un etudiant.
|
||||
|
||||
Affiche la fiche de l'étudiant, ou, si la recherche donne plusieurs résultats,
|
||||
la liste des étudiants correspondants.
|
||||
Appelée par:
|
||||
- boite de recherche barre latérale gauche.
|
||||
- choix d'un étudiant à inscrire (en POST avec dest_url et parameters_keys)
|
||||
|
||||
Args:
|
||||
expnom: string, regexp sur le nom ou un code_nip ou un etudid
|
||||
"""
|
||||
if isinstance(expnom, int) or len(expnom) > 1:
|
||||
try:
|
||||
etudid = int(expnom)
|
||||
except ValueError:
|
||||
etudid = None
|
||||
if etudid is not None:
|
||||
etuds = sco_etud.get_etud_info(filled=True, etudid=expnom)
|
||||
if (etudid is None) or len(etuds) != 1:
|
||||
expnom_str = str(expnom)
|
||||
if scu.is_valid_code_nip(expnom_str):
|
||||
etuds = search_etuds_infos(code_nip=expnom_str)
|
||||
else:
|
||||
etuds = search_etuds_infos(expnom=expnom_str)
|
||||
else:
|
||||
etuds = [] # si expnom est trop court, n'affiche rien
|
||||
|
||||
if request.method == "POST":
|
||||
vals = request.form
|
||||
elif request.method == "GET":
|
||||
vals = request.args
|
||||
else:
|
||||
vals = {}
|
||||
|
||||
url_args = {"scodoc_dept": g.scodoc_dept}
|
||||
if "dest_url" in vals:
|
||||
endpoint = vals["dest_url"]
|
||||
else:
|
||||
endpoint = "scolar.ficheEtud"
|
||||
if "parameters_keys" in vals:
|
||||
for key in vals["parameters_keys"].split(","):
|
||||
url_args[key] = vals[key]
|
||||
|
||||
if len(etuds) == 1:
|
||||
# va directement a la fiche
|
||||
url_args["etudid"] = etuds[0]["etudid"]
|
||||
return flask.redirect(url_for(endpoint, **url_args))
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Recherche d'un étudiant",
|
||||
no_side_bar=False,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
)
|
||||
]
|
||||
if len(etuds) == 0 and len(etuds) <= 1:
|
||||
H.append("""<h2>chercher un étudiant:</h2>""")
|
||||
else:
|
||||
H.append(
|
||||
f"""<h2>{len(etuds)} résultats pour "<tt>{expnom}</tt>": choisissez un étudiant:</h2>"""
|
||||
)
|
||||
H.append(
|
||||
form_search_etud(
|
||||
dest_url=endpoint,
|
||||
parameters=vals.get("parameters"),
|
||||
parameters_keys=vals.get("parameters_keys"),
|
||||
title="Autre recherche",
|
||||
)
|
||||
)
|
||||
if len(etuds) > 0:
|
||||
# Choix dans la liste des résultats:
|
||||
for e in etuds:
|
||||
url_args["etudid"] = e["etudid"]
|
||||
target = url_for(endpoint, **url_args)
|
||||
e["_nomprenom_target"] = target
|
||||
e["inscription_target"] = target
|
||||
e["_nomprenom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"])
|
||||
sco_groups.etud_add_group_infos(
|
||||
e, e["cursem"]["formsemestre_id"] if e["cursem"] else None
|
||||
)
|
||||
|
||||
tab = GenTable(
|
||||
columns_ids=("nomprenom", "code_nip", "inscription", "groupes"),
|
||||
titles={
|
||||
"nomprenom": "Étudiant",
|
||||
"code_nip": "NIP",
|
||||
"inscription": "Inscription",
|
||||
"groupes": "Groupes",
|
||||
},
|
||||
rows=etuds,
|
||||
html_sortable=True,
|
||||
html_class="table_leftalign",
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
H.append(tab.html())
|
||||
if len(etuds) > 20: # si la page est grande
|
||||
H.append(
|
||||
form_search_etud(
|
||||
dest_url=endpoint,
|
||||
parameters=vals.get("parameters"),
|
||||
parameters_keys=vals.get("parameters_keys"),
|
||||
title="Autre recherche",
|
||||
)
|
||||
)
|
||||
else:
|
||||
H.append('<h2 style="color: red;">Aucun résultat pour "%s".</h2>' % expnom)
|
||||
H.append(
|
||||
"""<p class="help">La recherche porte sur tout ou partie du NOM ou du NIP de l'étudiant. Saisir au moins deux caractères.</p>"""
|
||||
)
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
# Was chercheEtudsInfo()
|
||||
def search_etuds_infos(expnom=None, code_nip=None):
|
||||
"""recherche les étudiants correspondants à expnom ou au code_nip
|
||||
et ramene liste de mappings utilisables en DTML.
|
||||
"""
|
||||
may_be_nip = scu.is_valid_code_nip(expnom)
|
||||
cnx = ndb.GetDBConnexion()
|
||||
if expnom and not may_be_nip:
|
||||
expnom = expnom.upper() # les noms dans la BD sont en uppercase
|
||||
try:
|
||||
etuds = sco_etud.etudident_list(cnx, args={"nom": expnom}, test="~")
|
||||
except ScoException:
|
||||
etuds = []
|
||||
else:
|
||||
code_nip = code_nip or expnom
|
||||
if code_nip:
|
||||
etuds = sco_etud.etudident_list(cnx, args={"code_nip": str(code_nip)})
|
||||
else:
|
||||
etuds = []
|
||||
sco_etud.fill_etuds_info(etuds)
|
||||
return etuds
|
||||
|
||||
|
||||
def search_etud_by_name(term: str) -> list:
|
||||
"""Recherche noms étudiants par début du nom, pour autocomplete
|
||||
Accepte aussi un début de code NIP (au moins 6 caractères)
|
||||
Renvoie une liste de dicts
|
||||
{ "label" : "<nip> <nom> <prenom>", "value" : etudid }
|
||||
"""
|
||||
may_be_nip = scu.is_valid_code_nip(term)
|
||||
# term = term.upper() # conserve les accents
|
||||
term = term.upper()
|
||||
if (
|
||||
not scu.ALPHANUM_EXP.match(term) # n'autorise pas les caractères spéciaux
|
||||
and not may_be_nip
|
||||
):
|
||||
data = []
|
||||
else:
|
||||
if may_be_nip:
|
||||
r = ndb.SimpleDictFetch(
|
||||
"""SELECT nom, prenom, code_nip
|
||||
FROM identite
|
||||
WHERE
|
||||
dept_id = %(dept_id)s
|
||||
AND code_nip LIKE %(beginning)s
|
||||
ORDER BY nom
|
||||
""",
|
||||
{"beginning": term + "%", "dept_id": g.scodoc_dept_id},
|
||||
)
|
||||
data = [
|
||||
{
|
||||
"label": "%s %s %s"
|
||||
% (x["code_nip"], x["nom"], sco_etud.format_prenom(x["prenom"])),
|
||||
"value": x["code_nip"],
|
||||
}
|
||||
for x in r
|
||||
]
|
||||
else:
|
||||
r = ndb.SimpleDictFetch(
|
||||
"""SELECT id AS etudid, nom, prenom
|
||||
FROM identite
|
||||
WHERE
|
||||
dept_id = %(dept_id)s
|
||||
AND nom LIKE %(beginning)s
|
||||
ORDER BY nom
|
||||
""",
|
||||
{"beginning": term + "%", "dept_id": g.scodoc_dept_id},
|
||||
)
|
||||
|
||||
data = [
|
||||
{
|
||||
"label": "%s %s" % (x["nom"], sco_etud.format_prenom(x["prenom"])),
|
||||
"value": x["etudid"],
|
||||
}
|
||||
for x in r
|
||||
]
|
||||
return data
|
||||
|
||||
|
||||
# ---------- Recherche sur plusieurs département
|
||||
|
||||
|
||||
def search_etud_in_accessible_depts(expnom=None, code_nip=None):
|
||||
"""
|
||||
result is a list of (sorted) etuds, one list per dept.
|
||||
"""
|
||||
result = []
|
||||
accessible_depts = []
|
||||
depts = Departement.query.filter_by(visible=True).all()
|
||||
for dept in depts:
|
||||
if current_user.has_permission(Permission.ScoView, dept=dept.acronym):
|
||||
if expnom or code_nip:
|
||||
accessible_depts.append(dept.acronym)
|
||||
app.set_sco_dept(dept.acronym)
|
||||
etuds = search_etuds_infos(expnom=expnom, code_nip=code_nip)
|
||||
else:
|
||||
etuds = []
|
||||
result.append(etuds)
|
||||
return result, accessible_depts
|
||||
|
||||
|
||||
def table_etud_in_accessible_depts(expnom=None):
|
||||
"""
|
||||
Page avec table étudiants trouvés, dans tous les departements.
|
||||
Attention: nous sommes ici au niveau de ScoDoc, pas dans un département
|
||||
"""
|
||||
result, accessible_depts = search_etud_in_accessible_depts(expnom=expnom)
|
||||
H = [
|
||||
"""<div class="table_etud_in_accessible_depts">""",
|
||||
"""<h3>Recherche multi-département de "<tt>%s</tt>"</h3>""" % expnom,
|
||||
]
|
||||
for etuds in result:
|
||||
if etuds:
|
||||
dept_id = etuds[0]["dept"]
|
||||
# H.append('<h3>Département %s</h3>' % DeptId)
|
||||
for e in etuds:
|
||||
e["_nomprenom_target"] = url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=dept_id, etudid=e["etudid"]
|
||||
)
|
||||
e["_nomprenom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"])
|
||||
|
||||
tab = GenTable(
|
||||
titles={"nomprenom": "Étudiants en " + dept_id},
|
||||
columns_ids=("nomprenom",),
|
||||
rows=etuds,
|
||||
html_sortable=True,
|
||||
html_class="table_leftalign",
|
||||
)
|
||||
|
||||
H.append('<div class="table_etud_in_dept">')
|
||||
H.append(tab.html())
|
||||
H.append("</div>")
|
||||
if len(accessible_depts) > 1:
|
||||
ss = "s"
|
||||
else:
|
||||
ss = ""
|
||||
H.append(
|
||||
f"""<p>(recherche menée dans le{ss} département{ss}:
|
||||
{", ".join(accessible_depts)})
|
||||
</p>
|
||||
<p>
|
||||
<a href="{url_for("scodoc.index")}" class="stdlink">Retour à l'accueil</a>
|
||||
</p>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
return (
|
||||
html_sco_header.scodoc_top_html_header(page_title="Choix d'un étudiant")
|
||||
+ "\n".join(H)
|
||||
+ html_sco_header.standard_html_footer()
|
||||
)
|
||||
|
||||
|
||||
def search_inscr_etud_by_nip(code_nip, format="json"):
|
||||
"""Recherche multi-departement d'un étudiant par son code NIP
|
||||
Seuls les départements accessibles par l'utilisateur sont cherchés.
|
||||
|
||||
Renvoie une liste des inscriptions de l'étudiants dans tout ScoDoc:
|
||||
code_nip, nom, prenom, civilite_str, dept, formsemestre_id, date_debut_sem, date_fin_sem
|
||||
"""
|
||||
result, _ = search_etud_in_accessible_depts(code_nip=code_nip)
|
||||
|
||||
T = []
|
||||
for etuds in result:
|
||||
if etuds:
|
||||
dept_id = etuds[0]["dept"]
|
||||
for e in etuds:
|
||||
for sem in e["sems"]:
|
||||
T.append(
|
||||
{
|
||||
"dept": dept_id,
|
||||
"etudid": e["etudid"],
|
||||
"code_nip": e["code_nip"],
|
||||
"civilite_str": e["civilite_str"],
|
||||
"nom": e["nom"],
|
||||
"prenom": e["prenom"],
|
||||
"formsemestre_id": sem["formsemestre_id"],
|
||||
"date_debut_iso": sem["date_debut_iso"],
|
||||
"date_fin_iso": sem["date_fin_iso"],
|
||||
}
|
||||
)
|
||||
|
||||
columns_ids = (
|
||||
"dept",
|
||||
"etudid",
|
||||
"code_nip",
|
||||
"civilite_str",
|
||||
"nom",
|
||||
"prenom",
|
||||
"formsemestre_id",
|
||||
"date_debut_iso",
|
||||
"date_fin_iso",
|
||||
)
|
||||
tab = GenTable(columns_ids=columns_ids, rows=T)
|
||||
|
||||
return tab.make_page(format=format, with_html_headers=False, publish=True)
|
||||
|
|
|
@ -1659,7 +1659,7 @@ def formsemestre_change_publication_bul(
|
|||
"<h2>Confirmer la %s publication des bulletins ?</h2>" % msg,
|
||||
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.
|
||||
<br/>
|
||||
<br>
|
||||
Ce réglage n'a d'effet que si votre établissement a interfacé ScoDoc et un portail étudiant.
|
||||
""",
|
||||
dest_url="",
|
||||
|
|
|
@ -893,7 +893,7 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
|
|||
H.append("</ul>")
|
||||
H.append("<p>Total: %d étudiants concernés.</p>" % len(etudlist))
|
||||
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>
|
||||
<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>
|
||||
|
|
|
@ -349,7 +349,7 @@ def formsemestre_validation_etud_form(
|
|||
|
||||
H.append("</table>")
|
||||
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>")
|
||||
|
||||
|
@ -1299,7 +1299,7 @@ def check_formation_ues(formation_id):
|
|||
H = [
|
||||
"""<div class="ue_warning"><span>Attention:</span> les UE suivantes de cette formation
|
||||
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:
|
||||
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
|
||||
|
|
|
@ -302,7 +302,7 @@ def scolars_import_excel_file(
|
|||
else:
|
||||
unknown.append(f)
|
||||
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)"""
|
||||
% (len(titles), len(fs), list(missing.keys()), unknown)
|
||||
)
|
||||
|
|
|
@ -1,312 +1,312 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Import d'utilisateurs via fichier Excel
|
||||
"""
|
||||
import random
|
||||
import time
|
||||
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from flask import g, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
from app import db
|
||||
from app import email
|
||||
from app.auth.models import User, UserRole
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_users
|
||||
|
||||
|
||||
TITLES = ("user_name", "nom", "prenom", "email", "roles", "dept")
|
||||
COMMENTS = (
|
||||
"""user_name:
|
||||
Composé de lettres (minuscules ou majuscules), de chiffres ou du caractère _
|
||||
""",
|
||||
"""nom:
|
||||
Maximum 64 caractères""",
|
||||
"""prenom:
|
||||
Maximum 64 caractères""",
|
||||
"""email:
|
||||
Maximum 120 caractères""",
|
||||
"""roles:
|
||||
un plusieurs rôles séparés par ','
|
||||
chaque role est fait de 2 composantes séparées par _:
|
||||
1. Le role (Ens, Secr ou Admin)
|
||||
2. Le département (en majuscule)
|
||||
Exemple: "Ens_RT,Admin_INFO"
|
||||
""",
|
||||
"""dept:
|
||||
Le département d'appartenance du l'utillsateur. Laisser vide si l'utilisateur intervient dans plusieurs dépatements
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
def generate_excel_sample():
|
||||
"""generates an excel document suitable to import users"""
|
||||
style = sco_excel.excel_make_style(bold=True)
|
||||
titles = TITLES
|
||||
titles_styles = [style] * len(titles)
|
||||
return sco_excel.excel_simple_table(
|
||||
titles=titles,
|
||||
titles_styles=titles_styles,
|
||||
sheet_name="Utilisateurs ScoDoc",
|
||||
comments=COMMENTS,
|
||||
)
|
||||
|
||||
|
||||
def import_excel_file(datafile, force=""):
|
||||
"""
|
||||
Import scodoc users from Excel file.
|
||||
This method:
|
||||
* checks that the current_user has the ability to do so (at the moment only a SuperAdmin). He may thereoff import users with any well formed role into any department (or all)
|
||||
* Once the check is done ans successfull, build the list of users (does not check the data)
|
||||
* call :func:`import_users` to actually do the job
|
||||
history: scodoc7 with no SuperAdmin every Admin_XXX could import users.
|
||||
:param datafile: the stream from to the to be imported
|
||||
:return: same as import users
|
||||
"""
|
||||
# Check current user privilege
|
||||
auth_name = str(current_user)
|
||||
if not current_user.is_administrator():
|
||||
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
|
||||
# Récupération des informations sur l'utilisateur courant
|
||||
log("sco_import_users.import_excel_file by %s" % auth_name)
|
||||
# Read the data from the stream
|
||||
exceldata = datafile.read()
|
||||
if not exceldata:
|
||||
raise ScoValueError("Ficher excel vide ou invalide")
|
||||
_, data = sco_excel.excel_bytes_to_list(exceldata)
|
||||
if not data:
|
||||
raise ScoValueError(
|
||||
"""Le fichier xlsx attendu semble vide !
|
||||
"""
|
||||
)
|
||||
# 1- --- check title line
|
||||
fs = [scu.stripquotes(s).lower() for s in data[0]]
|
||||
log("excel: fs='%s'\ndata=%s" % (str(fs), str(data)))
|
||||
# check cols
|
||||
cols = {}.fromkeys(TITLES)
|
||||
unknown = []
|
||||
for tit in fs:
|
||||
if tit not in cols:
|
||||
unknown.append(tit)
|
||||
else:
|
||||
del cols[tit]
|
||||
if cols or unknown:
|
||||
raise ScoValueError(
|
||||
"""colonnes incorrectes (on attend %d, et non %d) <br/>
|
||||
(colonnes manquantes: %s, colonnes invalides: %s)"""
|
||||
% (len(TITLES), len(fs), list(cols.keys()), unknown)
|
||||
)
|
||||
# ok, same titles... : build the list of dictionaries
|
||||
users = []
|
||||
for line in data[1:]:
|
||||
d = {}
|
||||
for i in range(len(fs)):
|
||||
d[fs[i]] = line[i]
|
||||
users.append(d)
|
||||
|
||||
return import_users(users=users, force=force)
|
||||
|
||||
|
||||
def import_users(users, force=""):
|
||||
"""
|
||||
Import users from a list of users_descriptors.
|
||||
|
||||
descriptors are dictionaries hosting users's data.
|
||||
The operation is atomic (all the users are imported or none)
|
||||
|
||||
:param users: list of descriptors to be imported
|
||||
|
||||
:return: a tuple that describe the result of the import:
|
||||
* ok: import ok or aborted
|
||||
* messages: the list of messages
|
||||
* the # of users created
|
||||
|
||||
Implémentation:
|
||||
Pour chaque utilisateur à créer:
|
||||
* vérifier données (y compris que le même nom d'utilisateur n'est pas utilisé plusieurs fois)
|
||||
* générer mot de passe aléatoire
|
||||
* créer utilisateur et mettre le mot de passe
|
||||
* envoyer mot de passe par mail
|
||||
Les utilisateurs à créer sont stockés dans un dictionnaire.
|
||||
L'ajout effectif ne se fait qu'en fin de fonction si aucune erreur n'a été détectée
|
||||
"""
|
||||
|
||||
created = {} # uid créés
|
||||
if len(users) == 0:
|
||||
import_ok = False
|
||||
msg_list = ["Feuille vide ou illisible"]
|
||||
else:
|
||||
msg_list = []
|
||||
line = 1 # start from excel line #2
|
||||
import_ok = True
|
||||
|
||||
def append_msg(msg):
|
||||
msg_list.append("Ligne %s : %s" % (line, msg))
|
||||
|
||||
try:
|
||||
for u in users:
|
||||
line = line + 1
|
||||
user_ok, msg = sco_users.check_modif_user(
|
||||
0,
|
||||
enforce_optionals=not force,
|
||||
user_name=u["user_name"],
|
||||
nom=u["nom"],
|
||||
prenom=u["prenom"],
|
||||
email=u["email"],
|
||||
roles=[r for r in u["roles"].split(",") if r],
|
||||
dept=u["dept"],
|
||||
)
|
||||
if not user_ok:
|
||||
append_msg("identifiant '%s' %s" % (u["user_name"], msg))
|
||||
|
||||
u["passwd"] = generate_password()
|
||||
#
|
||||
# check identifiant
|
||||
if u["user_name"] in created.keys():
|
||||
user_ok = False
|
||||
append_msg(
|
||||
"l'utilisateur '%s' a déjà été décrit ligne %s"
|
||||
% (u["user_name"], created[u["user_name"]]["line"])
|
||||
)
|
||||
# check roles / ignore whitespaces around roles / build roles_string
|
||||
# roles_string (expected by User) appears as column 'roles' in excel file
|
||||
roles_list = []
|
||||
for role in u["roles"].split(","):
|
||||
try:
|
||||
role = role.strip()
|
||||
if role:
|
||||
_, _ = UserRole.role_dept_from_string(role)
|
||||
roles_list.append(role)
|
||||
except ScoValueError as value_error:
|
||||
user_ok = False
|
||||
append_msg("role %s : %s" % (role, value_error))
|
||||
u["roles_string"] = ",".join(roles_list)
|
||||
if user_ok:
|
||||
u["line"] = line
|
||||
created[u["user_name"]] = u
|
||||
else:
|
||||
import_ok = False
|
||||
except ScoValueError as value_error:
|
||||
log(f"import_users: exception: abort create {str(created.keys())}")
|
||||
raise ScoValueError(msg) from value_error
|
||||
if import_ok:
|
||||
for u in created.values():
|
||||
# Création de l'utilisateur (via SQLAlchemy)
|
||||
user = User()
|
||||
user.from_dict(u, new_user=True)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
mail_password(u)
|
||||
else:
|
||||
created = {} # reset # of created users to 0
|
||||
return import_ok, msg_list, len(created)
|
||||
|
||||
|
||||
# --------- Génération du mot de passe initial -----------
|
||||
# Adapté de http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440564
|
||||
# Alphabet tres simple pour des mots de passe simples...
|
||||
|
||||
|
||||
ALPHABET = r"""ABCDEFGHIJKLMNPQRSTUVWXYZ123456789123456789AEIOU"""
|
||||
PASSLEN = 8
|
||||
RNG = random.Random(time.time())
|
||||
|
||||
|
||||
def generate_password():
|
||||
"""This function creates a pseudo random number generator object, seeded with
|
||||
the cryptographic hash of the passString. The contents of the character set
|
||||
is then shuffled and a selection of passLength words is made from this list.
|
||||
This selection is returned as the generated password."""
|
||||
l = list(ALPHABET) # make this mutable so that we can shuffle the characters
|
||||
RNG.shuffle(l) # shuffle the character set
|
||||
# pick up only a subset from the available characters:
|
||||
return "".join(RNG.sample(l, PASSLEN))
|
||||
|
||||
|
||||
def mail_password(user: dict, reset=False) -> None:
|
||||
"Send password by email"
|
||||
if not user["email"]:
|
||||
return
|
||||
|
||||
user["url"] = url_for("scodoc.index", _external=True)
|
||||
txt = (
|
||||
"""
|
||||
Bonjour %(prenom)s %(nom)s,
|
||||
|
||||
"""
|
||||
% user
|
||||
)
|
||||
if reset:
|
||||
txt += (
|
||||
"""
|
||||
votre mot de passe ScoDoc a été ré-initialisé.
|
||||
|
||||
Le nouveau mot de passe est: %(passwd)s
|
||||
Votre nom d'utilisateur est %(user_name)s
|
||||
|
||||
Vous devrez changer ce mot de passe lors de votre première connexion
|
||||
sur %(url)s
|
||||
"""
|
||||
% user
|
||||
)
|
||||
else:
|
||||
txt += (
|
||||
"""
|
||||
vous avez été déclaré comme utilisateur du logiciel de gestion de scolarité ScoDoc.
|
||||
|
||||
Votre nom d'utilisateur est %(user_name)s
|
||||
Votre mot de passe est: %(passwd)s
|
||||
|
||||
Le logiciel est accessible sur: %(url)s
|
||||
|
||||
Vous êtes invité à changer ce mot de passe au plus vite (cliquez sur votre nom en haut à gauche de la page d'accueil).
|
||||
"""
|
||||
% user
|
||||
)
|
||||
|
||||
txt += (
|
||||
"""
|
||||
_______
|
||||
ScoDoc est un logiciel libre développé par Emmanuel Viennet et l'association ScoDoc.
|
||||
Pour plus d'informations sur ce logiciel, voir %s
|
||||
|
||||
"""
|
||||
% scu.SCO_WEBSITE
|
||||
)
|
||||
msg = MIMEMultipart()
|
||||
if reset:
|
||||
subject = "Mot de passe ScoDoc"
|
||||
else:
|
||||
subject = "Votre accès ScoDoc"
|
||||
sender = sco_preferences.get_preference("email_from_addr")
|
||||
email.send_email(subject, sender, [user["email"]], txt)
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Import d'utilisateurs via fichier Excel
|
||||
"""
|
||||
import random
|
||||
import time
|
||||
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from flask import g, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
from app import db
|
||||
from app import email
|
||||
from app.auth.models import User, UserRole
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_users
|
||||
|
||||
|
||||
TITLES = ("user_name", "nom", "prenom", "email", "roles", "dept")
|
||||
COMMENTS = (
|
||||
"""user_name:
|
||||
Composé de lettres (minuscules ou majuscules), de chiffres ou du caractère _
|
||||
""",
|
||||
"""nom:
|
||||
Maximum 64 caractères""",
|
||||
"""prenom:
|
||||
Maximum 64 caractères""",
|
||||
"""email:
|
||||
Maximum 120 caractères""",
|
||||
"""roles:
|
||||
un plusieurs rôles séparés par ','
|
||||
chaque role est fait de 2 composantes séparées par _:
|
||||
1. Le role (Ens, Secr ou Admin)
|
||||
2. Le département (en majuscule)
|
||||
Exemple: "Ens_RT,Admin_INFO"
|
||||
""",
|
||||
"""dept:
|
||||
Le département d'appartenance du l'utillsateur. Laisser vide si l'utilisateur intervient dans plusieurs dépatements
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
def generate_excel_sample():
|
||||
"""generates an excel document suitable to import users"""
|
||||
style = sco_excel.excel_make_style(bold=True)
|
||||
titles = TITLES
|
||||
titles_styles = [style] * len(titles)
|
||||
return sco_excel.excel_simple_table(
|
||||
titles=titles,
|
||||
titles_styles=titles_styles,
|
||||
sheet_name="Utilisateurs ScoDoc",
|
||||
comments=COMMENTS,
|
||||
)
|
||||
|
||||
|
||||
def import_excel_file(datafile, force=""):
|
||||
"""
|
||||
Import scodoc users from Excel file.
|
||||
This method:
|
||||
* checks that the current_user has the ability to do so (at the moment only a SuperAdmin). He may thereoff import users with any well formed role into any department (or all)
|
||||
* Once the check is done ans successfull, build the list of users (does not check the data)
|
||||
* call :func:`import_users` to actually do the job
|
||||
history: scodoc7 with no SuperAdmin every Admin_XXX could import users.
|
||||
:param datafile: the stream from to the to be imported
|
||||
:return: same as import users
|
||||
"""
|
||||
# Check current user privilege
|
||||
auth_name = str(current_user)
|
||||
if not current_user.is_administrator():
|
||||
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
|
||||
# Récupération des informations sur l'utilisateur courant
|
||||
log("sco_import_users.import_excel_file by %s" % auth_name)
|
||||
# Read the data from the stream
|
||||
exceldata = datafile.read()
|
||||
if not exceldata:
|
||||
raise ScoValueError("Ficher excel vide ou invalide")
|
||||
_, data = sco_excel.excel_bytes_to_list(exceldata)
|
||||
if not data:
|
||||
raise ScoValueError(
|
||||
"""Le fichier xlsx attendu semble vide !
|
||||
"""
|
||||
)
|
||||
# 1- --- check title line
|
||||
fs = [scu.stripquotes(s).lower() for s in data[0]]
|
||||
log("excel: fs='%s'\ndata=%s" % (str(fs), str(data)))
|
||||
# check cols
|
||||
cols = {}.fromkeys(TITLES)
|
||||
unknown = []
|
||||
for tit in fs:
|
||||
if tit not in cols:
|
||||
unknown.append(tit)
|
||||
else:
|
||||
del cols[tit]
|
||||
if cols or unknown:
|
||||
raise ScoValueError(
|
||||
"""colonnes incorrectes (on attend %d, et non %d) <br>
|
||||
(colonnes manquantes: %s, colonnes invalides: %s)"""
|
||||
% (len(TITLES), len(fs), list(cols.keys()), unknown)
|
||||
)
|
||||
# ok, same titles... : build the list of dictionaries
|
||||
users = []
|
||||
for line in data[1:]:
|
||||
d = {}
|
||||
for i in range(len(fs)):
|
||||
d[fs[i]] = line[i]
|
||||
users.append(d)
|
||||
|
||||
return import_users(users=users, force=force)
|
||||
|
||||
|
||||
def import_users(users, force=""):
|
||||
"""
|
||||
Import users from a list of users_descriptors.
|
||||
|
||||
descriptors are dictionaries hosting users's data.
|
||||
The operation is atomic (all the users are imported or none)
|
||||
|
||||
:param users: list of descriptors to be imported
|
||||
|
||||
:return: a tuple that describe the result of the import:
|
||||
* ok: import ok or aborted
|
||||
* messages: the list of messages
|
||||
* the # of users created
|
||||
|
||||
Implémentation:
|
||||
Pour chaque utilisateur à créer:
|
||||
* vérifier données (y compris que le même nom d'utilisateur n'est pas utilisé plusieurs fois)
|
||||
* générer mot de passe aléatoire
|
||||
* créer utilisateur et mettre le mot de passe
|
||||
* envoyer mot de passe par mail
|
||||
Les utilisateurs à créer sont stockés dans un dictionnaire.
|
||||
L'ajout effectif ne se fait qu'en fin de fonction si aucune erreur n'a été détectée
|
||||
"""
|
||||
|
||||
created = {} # uid créés
|
||||
if len(users) == 0:
|
||||
import_ok = False
|
||||
msg_list = ["Feuille vide ou illisible"]
|
||||
else:
|
||||
msg_list = []
|
||||
line = 1 # start from excel line #2
|
||||
import_ok = True
|
||||
|
||||
def append_msg(msg):
|
||||
msg_list.append("Ligne %s : %s" % (line, msg))
|
||||
|
||||
try:
|
||||
for u in users:
|
||||
line = line + 1
|
||||
user_ok, msg = sco_users.check_modif_user(
|
||||
0,
|
||||
enforce_optionals=not force,
|
||||
user_name=u["user_name"],
|
||||
nom=u["nom"],
|
||||
prenom=u["prenom"],
|
||||
email=u["email"],
|
||||
roles=[r for r in u["roles"].split(",") if r],
|
||||
dept=u["dept"],
|
||||
)
|
||||
if not user_ok:
|
||||
append_msg("identifiant '%s' %s" % (u["user_name"], msg))
|
||||
|
||||
u["passwd"] = generate_password()
|
||||
#
|
||||
# check identifiant
|
||||
if u["user_name"] in created.keys():
|
||||
user_ok = False
|
||||
append_msg(
|
||||
"l'utilisateur '%s' a déjà été décrit ligne %s"
|
||||
% (u["user_name"], created[u["user_name"]]["line"])
|
||||
)
|
||||
# check roles / ignore whitespaces around roles / build roles_string
|
||||
# roles_string (expected by User) appears as column 'roles' in excel file
|
||||
roles_list = []
|
||||
for role in u["roles"].split(","):
|
||||
try:
|
||||
role = role.strip()
|
||||
if role:
|
||||
_, _ = UserRole.role_dept_from_string(role)
|
||||
roles_list.append(role)
|
||||
except ScoValueError as value_error:
|
||||
user_ok = False
|
||||
append_msg("role %s : %s" % (role, value_error))
|
||||
u["roles_string"] = ",".join(roles_list)
|
||||
if user_ok:
|
||||
u["line"] = line
|
||||
created[u["user_name"]] = u
|
||||
else:
|
||||
import_ok = False
|
||||
except ScoValueError as value_error:
|
||||
log(f"import_users: exception: abort create {str(created.keys())}")
|
||||
raise ScoValueError(msg) from value_error
|
||||
if import_ok:
|
||||
for u in created.values():
|
||||
# Création de l'utilisateur (via SQLAlchemy)
|
||||
user = User()
|
||||
user.from_dict(u, new_user=True)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
mail_password(u)
|
||||
else:
|
||||
created = {} # reset # of created users to 0
|
||||
return import_ok, msg_list, len(created)
|
||||
|
||||
|
||||
# --------- Génération du mot de passe initial -----------
|
||||
# Adapté de http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440564
|
||||
# Alphabet tres simple pour des mots de passe simples...
|
||||
|
||||
|
||||
ALPHABET = r"""ABCDEFGHIJKLMNPQRSTUVWXYZ123456789123456789AEIOU"""
|
||||
PASSLEN = 8
|
||||
RNG = random.Random(time.time())
|
||||
|
||||
|
||||
def generate_password():
|
||||
"""This function creates a pseudo random number generator object, seeded with
|
||||
the cryptographic hash of the passString. The contents of the character set
|
||||
is then shuffled and a selection of passLength words is made from this list.
|
||||
This selection is returned as the generated password."""
|
||||
l = list(ALPHABET) # make this mutable so that we can shuffle the characters
|
||||
RNG.shuffle(l) # shuffle the character set
|
||||
# pick up only a subset from the available characters:
|
||||
return "".join(RNG.sample(l, PASSLEN))
|
||||
|
||||
|
||||
def mail_password(user: dict, reset=False) -> None:
|
||||
"Send password by email"
|
||||
if not user["email"]:
|
||||
return
|
||||
|
||||
user["url"] = url_for("scodoc.index", _external=True)
|
||||
txt = (
|
||||
"""
|
||||
Bonjour %(prenom)s %(nom)s,
|
||||
|
||||
"""
|
||||
% user
|
||||
)
|
||||
if reset:
|
||||
txt += (
|
||||
"""
|
||||
votre mot de passe ScoDoc a été ré-initialisé.
|
||||
|
||||
Le nouveau mot de passe est: %(passwd)s
|
||||
Votre nom d'utilisateur est %(user_name)s
|
||||
|
||||
Vous devrez changer ce mot de passe lors de votre première connexion
|
||||
sur %(url)s
|
||||
"""
|
||||
% user
|
||||
)
|
||||
else:
|
||||
txt += (
|
||||
"""
|
||||
vous avez été déclaré comme utilisateur du logiciel de gestion de scolarité ScoDoc.
|
||||
|
||||
Votre nom d'utilisateur est %(user_name)s
|
||||
Votre mot de passe est: %(passwd)s
|
||||
|
||||
Le logiciel est accessible sur: %(url)s
|
||||
|
||||
Vous êtes invité à changer ce mot de passe au plus vite (cliquez sur votre nom en haut à gauche de la page d'accueil).
|
||||
"""
|
||||
% user
|
||||
)
|
||||
|
||||
txt += (
|
||||
"""
|
||||
_______
|
||||
ScoDoc est un logiciel libre développé par Emmanuel Viennet et l'association ScoDoc.
|
||||
Pour plus d'informations sur ce logiciel, voir %s
|
||||
|
||||
"""
|
||||
% scu.SCO_WEBSITE
|
||||
)
|
||||
msg = MIMEMultipart()
|
||||
if reset:
|
||||
subject = "Mot de passe ScoDoc"
|
||||
else:
|
||||
subject = "Votre accès ScoDoc"
|
||||
sender = sco_preferences.get_preference("email_from_addr")
|
||||
email.send_email(subject, sender, [user["email"]], txt)
|
||||
|
|
|
@ -517,18 +517,18 @@ def _make_table_notes(
|
|||
hh += "s"
|
||||
hh += ", %d en attente." % (nb_att)
|
||||
|
||||
pdf_title = "<br/> BORDEREAU DE SIGNATURES"
|
||||
pdf_title += "<br/><br/>%(titre)s" % sem
|
||||
pdf_title += "<br/>(%(mois_debut)s - %(mois_fin)s)" % sem
|
||||
pdf_title = "<br> BORDEREAU DE SIGNATURES"
|
||||
pdf_title += "<br><br>%(titre)s" % sem
|
||||
pdf_title += "<br>(%(mois_debut)s - %(mois_fin)s)" % sem
|
||||
pdf_title += " semestre %s %s" % (
|
||||
sem["semestre_id"],
|
||||
sem.get("modalite", ""),
|
||||
)
|
||||
pdf_title += f"<br/>Notes du module {module.code} - {module.titre}"
|
||||
pdf_title += "<br/>Evaluation : %(description)s " % e
|
||||
pdf_title += f"<br>Notes du module {module.code} - {module.titre}"
|
||||
pdf_title += "<br>Evaluation : %(description)s " % e
|
||||
if len(e["jour"]) > 0:
|
||||
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:
|
||||
hh = " %s, %s (%d étudiants)" % (
|
||||
E["description"],
|
||||
|
@ -623,11 +623,11 @@ def _make_table_notes(
|
|||
commentkeys.sort(key=lambda x: int(x[1]))
|
||||
for (comment, key) in commentkeys:
|
||||
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:
|
||||
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"]
|
||||
)
|
||||
eval_info = "xxx"
|
||||
|
|
|
@ -1,259 +1,259 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Rapports sur lycées d'origine des étudiants d'un semestre.
|
||||
- statistiques decisions
|
||||
- suivi cohortes
|
||||
"""
|
||||
from operator import itemgetter
|
||||
|
||||
from flask import url_for, g, request
|
||||
|
||||
import app
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_report
|
||||
from app.scodoc import sco_etud
|
||||
import sco_version
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
|
||||
|
||||
def formsemestre_table_etuds_lycees(
|
||||
formsemestre_id, group_lycees=True, only_primo=False
|
||||
):
|
||||
"""Récupère liste d'etudiants avec etat et decision."""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
etuds = sco_report.tsp_etud_list(formsemestre_id, only_primo=only_primo)[0]
|
||||
if only_primo:
|
||||
primostr = "primo-entrants du "
|
||||
else:
|
||||
primostr = "du "
|
||||
title = "Lycées des étudiants %ssemestre " % primostr + sem["titreannee"]
|
||||
return _table_etuds_lycees(
|
||||
etuds,
|
||||
group_lycees,
|
||||
title,
|
||||
sco_preferences.SemPreferences(formsemestre_id),
|
||||
)
|
||||
|
||||
|
||||
def scodoc_table_etuds_lycees(format="html"):
|
||||
"""Table avec _tous_ les étudiants des semestres non verrouillés
|
||||
de _tous_ les départements.
|
||||
"""
|
||||
cur_dept = g.scodoc_dept
|
||||
semdepts = sco_formsemestre.scodoc_get_all_unlocked_sems()
|
||||
etuds = []
|
||||
try:
|
||||
for (sem, dept) in semdepts:
|
||||
app.set_sco_dept(dept.acronym)
|
||||
etuds += sco_report.tsp_etud_list(sem["formsemestre_id"])[0]
|
||||
finally:
|
||||
app.set_sco_dept(cur_dept)
|
||||
|
||||
tab, etuds_by_lycee = _table_etuds_lycees(
|
||||
etuds,
|
||||
False,
|
||||
"Lycées de TOUS les étudiants",
|
||||
sco_preferences.SemPreferences(),
|
||||
no_links=True,
|
||||
)
|
||||
tab.base_url = request.base_url
|
||||
t = tab.make_page(format=format, with_html_headers=False)
|
||||
if format != "html":
|
||||
return t
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=tab.page_title,
|
||||
init_google_maps=True,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js", "js/map_lycees.js"],
|
||||
),
|
||||
"""<h2 class="formsemestre">Lycées d'origine des %d étudiants (%d semestres)</h2>"""
|
||||
% (len(etuds), len(semdepts)),
|
||||
t,
|
||||
"""<div id="lyc_map_canvas"></div>
|
||||
""",
|
||||
js_coords_lycees(etuds_by_lycee),
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def _table_etuds_lycees(etuds, group_lycees, title, preferences, no_links=False):
|
||||
etuds = [sco_etud.etud_add_lycee_infos(e) for e in etuds]
|
||||
etuds_by_lycee = scu.group_by_key(etuds, "codelycee")
|
||||
#
|
||||
if group_lycees:
|
||||
L = [etuds_by_lycee[codelycee][0] for codelycee in etuds_by_lycee]
|
||||
for l in L:
|
||||
l["nbetuds"] = len(etuds_by_lycee[l["codelycee"]])
|
||||
# L.sort( key=operator.itemgetter('codepostallycee', 'nomlycee') ) argh, only python 2.5+ !!!
|
||||
L.sort(key=itemgetter("codepostallycee", "nomlycee"))
|
||||
columns_ids = (
|
||||
"nbetuds",
|
||||
"codelycee",
|
||||
"codepostallycee",
|
||||
"villelycee",
|
||||
"nomlycee",
|
||||
)
|
||||
bottom_titles = {
|
||||
"nbetuds": len(etuds),
|
||||
"nomlycee": "%d lycées"
|
||||
% len([x for x in etuds_by_lycee if etuds_by_lycee[x][0]["codelycee"]]),
|
||||
}
|
||||
else:
|
||||
L = etuds
|
||||
columns_ids = (
|
||||
"civilite_str",
|
||||
"nom",
|
||||
"prenom",
|
||||
"codelycee",
|
||||
"codepostallycee",
|
||||
"villelycee",
|
||||
"nomlycee",
|
||||
)
|
||||
bottom_titles = None
|
||||
if not no_links:
|
||||
for etud in etuds:
|
||||
fiche_url = url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
||||
)
|
||||
etud["_nom_target"] = fiche_url
|
||||
etud["_prenom_target"] = fiche_url
|
||||
etud["_nom_td_attrs"] = 'id="%s" class="etudinfo"' % (etud["etudid"])
|
||||
|
||||
tab = GenTable(
|
||||
columns_ids=columns_ids,
|
||||
rows=L,
|
||||
titles={
|
||||
"nbetuds": "Nb d'étudiants",
|
||||
"civilite_str": "",
|
||||
"nom": "Nom",
|
||||
"prenom": "Prénom",
|
||||
"etudid": "etudid",
|
||||
"codelycee": "Code Lycée",
|
||||
"codepostallycee": "Code postal",
|
||||
"nomlycee": "Lycée",
|
||||
"villelycee": "Commune",
|
||||
},
|
||||
origin="Généré par %s le " % sco_version.SCONAME
|
||||
+ scu.timedate_human_repr()
|
||||
+ "",
|
||||
caption=title,
|
||||
page_title="Carte lycées d'origine",
|
||||
html_sortable=True,
|
||||
html_class="table_leftalign table_listegroupe",
|
||||
bottom_titles=bottom_titles,
|
||||
preferences=preferences,
|
||||
)
|
||||
return tab, etuds_by_lycee
|
||||
|
||||
|
||||
def formsemestre_etuds_lycees(
|
||||
formsemestre_id,
|
||||
format="html",
|
||||
only_primo=False,
|
||||
no_grouping=False,
|
||||
):
|
||||
"""Table des lycées d'origine"""
|
||||
tab, etuds_by_lycee = formsemestre_table_etuds_lycees(
|
||||
formsemestre_id, only_primo=only_primo, group_lycees=not no_grouping
|
||||
)
|
||||
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
|
||||
if only_primo:
|
||||
tab.base_url += "&only_primo=1"
|
||||
if no_grouping:
|
||||
tab.base_url += "&no_grouping=1"
|
||||
t = tab.make_page(format=format, with_html_headers=False)
|
||||
if format != "html":
|
||||
return t
|
||||
F = [
|
||||
sco_report.tsp_form_primo_group(
|
||||
only_primo, no_grouping, formsemestre_id, format
|
||||
)
|
||||
]
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=tab.page_title,
|
||||
init_google_maps=True,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js", "js/map_lycees.js"],
|
||||
),
|
||||
"""<h2 class="formsemestre">Lycées d'origine des étudiants</h2>""",
|
||||
"\n".join(F),
|
||||
t,
|
||||
"""<div id="lyc_map_canvas"></div>
|
||||
""",
|
||||
js_coords_lycees(etuds_by_lycee),
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def qjs(txt): # quote for JS
|
||||
return txt.replace("'", r"\'").replace('"', r"\"")
|
||||
|
||||
|
||||
def js_coords_lycees(etuds_by_lycee):
|
||||
"""Formatte liste des lycees en JSON pour Google Map"""
|
||||
L = []
|
||||
for codelycee in etuds_by_lycee:
|
||||
if codelycee:
|
||||
lyc = etuds_by_lycee[codelycee][0]
|
||||
if not lyc.get("positionlycee", False):
|
||||
continue
|
||||
listeetuds = "<br/>%d étudiants: " % len(
|
||||
etuds_by_lycee[codelycee]
|
||||
) + ", ".join(
|
||||
[
|
||||
'<a class="discretelink" href="%s" title="">%s</a>'
|
||||
% (
|
||||
url_for(
|
||||
"scolar.ficheEtud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=e["etudid"],
|
||||
),
|
||||
qjs(e["nomprenom"]),
|
||||
)
|
||||
for e in etuds_by_lycee[codelycee]
|
||||
]
|
||||
)
|
||||
pos = qjs(lyc["positionlycee"])
|
||||
legend = "%s %s" % (qjs("%(nomlycee)s (%(villelycee)s)" % lyc), listeetuds)
|
||||
L.append(
|
||||
"{'position' : '%s', 'name' : '%s', 'number' : %d }"
|
||||
% (pos, legend, len(etuds_by_lycee[codelycee]))
|
||||
)
|
||||
|
||||
return """<script type="text/javascript">
|
||||
var lycees_coords = [%s];
|
||||
</script>""" % ",".join(
|
||||
L
|
||||
)
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Rapports sur lycées d'origine des étudiants d'un semestre.
|
||||
- statistiques decisions
|
||||
- suivi cohortes
|
||||
"""
|
||||
from operator import itemgetter
|
||||
|
||||
from flask import url_for, g, request
|
||||
|
||||
import app
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_report
|
||||
from app.scodoc import sco_etud
|
||||
import sco_version
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
|
||||
|
||||
def formsemestre_table_etuds_lycees(
|
||||
formsemestre_id, group_lycees=True, only_primo=False
|
||||
):
|
||||
"""Récupère liste d'etudiants avec etat et decision."""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
etuds = sco_report.tsp_etud_list(formsemestre_id, only_primo=only_primo)[0]
|
||||
if only_primo:
|
||||
primostr = "primo-entrants du "
|
||||
else:
|
||||
primostr = "du "
|
||||
title = "Lycées des étudiants %ssemestre " % primostr + sem["titreannee"]
|
||||
return _table_etuds_lycees(
|
||||
etuds,
|
||||
group_lycees,
|
||||
title,
|
||||
sco_preferences.SemPreferences(formsemestre_id),
|
||||
)
|
||||
|
||||
|
||||
def scodoc_table_etuds_lycees(format="html"):
|
||||
"""Table avec _tous_ les étudiants des semestres non verrouillés
|
||||
de _tous_ les départements.
|
||||
"""
|
||||
cur_dept = g.scodoc_dept
|
||||
semdepts = sco_formsemestre.scodoc_get_all_unlocked_sems()
|
||||
etuds = []
|
||||
try:
|
||||
for (sem, dept) in semdepts:
|
||||
app.set_sco_dept(dept.acronym)
|
||||
etuds += sco_report.tsp_etud_list(sem["formsemestre_id"])[0]
|
||||
finally:
|
||||
app.set_sco_dept(cur_dept)
|
||||
|
||||
tab, etuds_by_lycee = _table_etuds_lycees(
|
||||
etuds,
|
||||
False,
|
||||
"Lycées de TOUS les étudiants",
|
||||
sco_preferences.SemPreferences(),
|
||||
no_links=True,
|
||||
)
|
||||
tab.base_url = request.base_url
|
||||
t = tab.make_page(format=format, with_html_headers=False)
|
||||
if format != "html":
|
||||
return t
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=tab.page_title,
|
||||
init_google_maps=True,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js", "js/map_lycees.js"],
|
||||
),
|
||||
"""<h2 class="formsemestre">Lycées d'origine des %d étudiants (%d semestres)</h2>"""
|
||||
% (len(etuds), len(semdepts)),
|
||||
t,
|
||||
"""<div id="lyc_map_canvas"></div>
|
||||
""",
|
||||
js_coords_lycees(etuds_by_lycee),
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def _table_etuds_lycees(etuds, group_lycees, title, preferences, no_links=False):
|
||||
etuds = [sco_etud.etud_add_lycee_infos(e) for e in etuds]
|
||||
etuds_by_lycee = scu.group_by_key(etuds, "codelycee")
|
||||
#
|
||||
if group_lycees:
|
||||
L = [etuds_by_lycee[codelycee][0] for codelycee in etuds_by_lycee]
|
||||
for l in L:
|
||||
l["nbetuds"] = len(etuds_by_lycee[l["codelycee"]])
|
||||
# L.sort( key=operator.itemgetter('codepostallycee', 'nomlycee') ) argh, only python 2.5+ !!!
|
||||
L.sort(key=itemgetter("codepostallycee", "nomlycee"))
|
||||
columns_ids = (
|
||||
"nbetuds",
|
||||
"codelycee",
|
||||
"codepostallycee",
|
||||
"villelycee",
|
||||
"nomlycee",
|
||||
)
|
||||
bottom_titles = {
|
||||
"nbetuds": len(etuds),
|
||||
"nomlycee": "%d lycées"
|
||||
% len([x for x in etuds_by_lycee if etuds_by_lycee[x][0]["codelycee"]]),
|
||||
}
|
||||
else:
|
||||
L = etuds
|
||||
columns_ids = (
|
||||
"civilite_str",
|
||||
"nom",
|
||||
"prenom",
|
||||
"codelycee",
|
||||
"codepostallycee",
|
||||
"villelycee",
|
||||
"nomlycee",
|
||||
)
|
||||
bottom_titles = None
|
||||
if not no_links:
|
||||
for etud in etuds:
|
||||
fiche_url = url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
||||
)
|
||||
etud["_nom_target"] = fiche_url
|
||||
etud["_prenom_target"] = fiche_url
|
||||
etud["_nom_td_attrs"] = 'id="%s" class="etudinfo"' % (etud["etudid"])
|
||||
|
||||
tab = GenTable(
|
||||
columns_ids=columns_ids,
|
||||
rows=L,
|
||||
titles={
|
||||
"nbetuds": "Nb d'étudiants",
|
||||
"civilite_str": "",
|
||||
"nom": "Nom",
|
||||
"prenom": "Prénom",
|
||||
"etudid": "etudid",
|
||||
"codelycee": "Code Lycée",
|
||||
"codepostallycee": "Code postal",
|
||||
"nomlycee": "Lycée",
|
||||
"villelycee": "Commune",
|
||||
},
|
||||
origin="Généré par %s le " % sco_version.SCONAME
|
||||
+ scu.timedate_human_repr()
|
||||
+ "",
|
||||
caption=title,
|
||||
page_title="Carte lycées d'origine",
|
||||
html_sortable=True,
|
||||
html_class="table_leftalign table_listegroupe",
|
||||
bottom_titles=bottom_titles,
|
||||
preferences=preferences,
|
||||
)
|
||||
return tab, etuds_by_lycee
|
||||
|
||||
|
||||
def formsemestre_etuds_lycees(
|
||||
formsemestre_id,
|
||||
format="html",
|
||||
only_primo=False,
|
||||
no_grouping=False,
|
||||
):
|
||||
"""Table des lycées d'origine"""
|
||||
tab, etuds_by_lycee = formsemestre_table_etuds_lycees(
|
||||
formsemestre_id, only_primo=only_primo, group_lycees=not no_grouping
|
||||
)
|
||||
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
|
||||
if only_primo:
|
||||
tab.base_url += "&only_primo=1"
|
||||
if no_grouping:
|
||||
tab.base_url += "&no_grouping=1"
|
||||
t = tab.make_page(format=format, with_html_headers=False)
|
||||
if format != "html":
|
||||
return t
|
||||
F = [
|
||||
sco_report.tsp_form_primo_group(
|
||||
only_primo, no_grouping, formsemestre_id, format
|
||||
)
|
||||
]
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=tab.page_title,
|
||||
init_google_maps=True,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js", "js/map_lycees.js"],
|
||||
),
|
||||
"""<h2 class="formsemestre">Lycées d'origine des étudiants</h2>""",
|
||||
"\n".join(F),
|
||||
t,
|
||||
"""<div id="lyc_map_canvas"></div>
|
||||
""",
|
||||
js_coords_lycees(etuds_by_lycee),
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def qjs(txt): # quote for JS
|
||||
return txt.replace("'", r"\'").replace('"', r"\"")
|
||||
|
||||
|
||||
def js_coords_lycees(etuds_by_lycee):
|
||||
"""Formatte liste des lycees en JSON pour Google Map"""
|
||||
L = []
|
||||
for codelycee in etuds_by_lycee:
|
||||
if codelycee:
|
||||
lyc = etuds_by_lycee[codelycee][0]
|
||||
if not lyc.get("positionlycee", False):
|
||||
continue
|
||||
listeetuds = "<br>%d étudiants: " % len(
|
||||
etuds_by_lycee[codelycee]
|
||||
) + ", ".join(
|
||||
[
|
||||
'<a class="discretelink" href="%s" title="">%s</a>'
|
||||
% (
|
||||
url_for(
|
||||
"scolar.ficheEtud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=e["etudid"],
|
||||
),
|
||||
qjs(e["nomprenom"]),
|
||||
)
|
||||
for e in etuds_by_lycee[codelycee]
|
||||
]
|
||||
)
|
||||
pos = qjs(lyc["positionlycee"])
|
||||
legend = "%s %s" % (qjs("%(nomlycee)s (%(villelycee)s)" % lyc), listeetuds)
|
||||
L.append(
|
||||
"{'position' : '%s', 'name' : '%s', 'number' : %d }"
|
||||
% (pos, legend, len(etuds_by_lycee[codelycee]))
|
||||
)
|
||||
|
||||
return """<script type="text/javascript">
|
||||
var lycees_coords = [%s];
|
||||
</script>""" % ",".join(
|
||||
L
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -189,7 +189,7 @@ def ficheEtud(etudid=None):
|
|||
else:
|
||||
info["paysdomicile"] = ""
|
||||
if info["telephone"] or info["telephonemobile"]:
|
||||
info["telephones"] = "<br/>%s %s" % (
|
||||
info["telephones"] = "<br>%s %s" % (
|
||||
info["telephonestr"],
|
||||
info["telephonemobilestr"],
|
||||
)
|
||||
|
@ -506,9 +506,9 @@ def ficheEtud(etudid=None):
|
|||
<b>Ajouter une annotation sur %(nomprenom)s: </b>
|
||||
<table><tr>
|
||||
<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>
|
||||
<br/>
|
||||
<br>
|
||||
<i>L'annotation commençant par "PE:" est un avis de poursuite d'études.</i>
|
||||
</font>
|
||||
</td></tr>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -43,7 +43,7 @@ Au niveau du code interface, on défini pour chaque préférence:
|
|||
- initvalue : valeur initiale
|
||||
- explanation: explication en français
|
||||
- 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
|
||||
- category: misc ou bul ou page_bulletins ou abs ou general ou portal
|
||||
ou pdf ou pvpdf ou ...
|
||||
|
@ -202,7 +202,7 @@ _INSTALLED_FONTS = ", ".join(sco_pdf.get_available_font_names())
|
|||
|
||||
PREF_CATEGORIES = (
|
||||
# sur page "Paramètres"
|
||||
("general", {}),
|
||||
("general", {"title": ""}), # voir paramètre titlr de TrivialFormulator
|
||||
("misc", {"title": "Divers"}),
|
||||
("apc", {"title": "BUT et Approches par Compétences"}),
|
||||
("abs", {"title": "Suivi des absences", "related": ("bul",)}),
|
||||
|
@ -1008,7 +1008,7 @@ class BasePreferences(object):
|
|||
(
|
||||
"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""",
|
||||
"explanation": """%(DirectorName)s et %(DirectorTitle)s remplacés""",
|
||||
"input_type": "textarea",
|
||||
|
@ -1020,8 +1020,8 @@ class BasePreferences(object):
|
|||
(
|
||||
"PV_LETTER_PASSAGE_SIGNATURE",
|
||||
{
|
||||
"initvalue": """Pour le Directeur de l'IUT<br/>
|
||||
et par délégation<br/>
|
||||
"initvalue": """Pour le Directeur de l'IUT<br>
|
||||
et par délégation<br>
|
||||
Le Chef du département""",
|
||||
"title": """Signature des lettres individuelles de passage d'un semestre à l'autre""",
|
||||
"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 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 spaceBefore="10mm" fontSize="14" leftindent="0">
|
||||
|
@ -1499,7 +1499,7 @@ class BasePreferences(object):
|
|||
"bul_pdf_sig_left",
|
||||
{
|
||||
"initvalue": """<para>La direction des études
|
||||
<br/>
|
||||
<br>
|
||||
%(responsable)s
|
||||
</para>
|
||||
""",
|
||||
|
@ -1515,7 +1515,7 @@ class BasePreferences(object):
|
|||
"bul_pdf_sig_right",
|
||||
{
|
||||
"initvalue": """<para>Le chef de département
|
||||
<br/>
|
||||
<br>
|
||||
%(ChiefDeptName)s
|
||||
</para>
|
||||
""",
|
||||
|
@ -1891,7 +1891,7 @@ class BasePreferences(object):
|
|||
"explanation": """si cette adresse est indiquée, TOUS les mails
|
||||
envoyés par ScoDoc de ce département vont aller vers elle
|
||||
AU LIEU DE LEUR DESTINATION NORMALE !""",
|
||||
"size": 30,
|
||||
"size": 60,
|
||||
"category": "debug",
|
||||
"only_global": True,
|
||||
},
|
||||
|
@ -1935,7 +1935,7 @@ class BasePreferences(object):
|
|||
value = _get_pref_default_value_from_config(name, pref[1])
|
||||
self.default[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
|
||||
self._editor.create(
|
||||
cnx, {"dept_id": self.dept_id, "name": name, "value": value}
|
||||
|
@ -1999,7 +1999,7 @@ class BasePreferences(object):
|
|||
|
||||
if not pdb:
|
||||
# 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(
|
||||
cnx,
|
||||
{
|
||||
|
@ -2036,7 +2036,7 @@ class BasePreferences(object):
|
|||
|
||||
def set(self, formsemestre_id, name, value):
|
||||
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:
|
||||
raise ValueError("pref %s is always defined globaly")
|
||||
if not formsemestre_id in self.prefs:
|
||||
|
@ -2055,7 +2055,7 @@ class BasePreferences(object):
|
|||
cnx, args={"formsemestre_id": formsemestre_id, "name": name}
|
||||
)
|
||||
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
|
||||
self._editor.delete(cnx, pdb[0]["pref_id"])
|
||||
sco_cache.invalidate_formsemestre() # > modif preferences
|
||||
|
@ -2067,14 +2067,18 @@ class BasePreferences(object):
|
|||
self.load()
|
||||
H = [
|
||||
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)
|
||||
# }">modification des logos du département (pour documents pdf)</a></p>"""
|
||||
# if current_user.is_administrator()
|
||||
# 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="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
|
||||
""",
|
||||
"""<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="msg">Attention: cliquez sur "Enregistrer les modifications"
|
||||
en bas de page pour appliquer vos changements !
|
||||
</p>
|
||||
""",
|
||||
]
|
||||
form = self.build_tf_form()
|
||||
tf = TrivialFormulator(
|
||||
|
@ -2083,6 +2087,9 @@ class BasePreferences(object):
|
|||
form,
|
||||
initvalues=self.prefs[None],
|
||||
submitlabel="Enregistrer les modifications",
|
||||
title="Département et institution",
|
||||
before_table="<details><summary>{title}</summary>",
|
||||
after_table="</details>",
|
||||
)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
||||
|
@ -2094,7 +2101,7 @@ class BasePreferences(object):
|
|||
self.save()
|
||||
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.
|
||||
If formsemestre_id is not specified, edit global prefs.
|
||||
"""
|
||||
|
@ -2119,7 +2126,7 @@ class BasePreferences(object):
|
|||
onclick="set_global_pref(this, '{pref_name}');"
|
||||
>utiliser paramètre global</span>"""
|
||||
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
|
||||
descr["readonly"] = True
|
||||
menu_global = f"""<select class="tf-selglobal"
|
||||
|
@ -2138,8 +2145,11 @@ class BasePreferences(object):
|
|||
if title:
|
||||
form.append(
|
||||
(
|
||||
f"sep_{cat}",
|
||||
{"input_type": "separator", "title": f"<h3>{title}</h3>"},
|
||||
f"table_{cat}",
|
||||
{
|
||||
"input_type": "table_separator",
|
||||
"title": f"{title}",
|
||||
},
|
||||
)
|
||||
)
|
||||
subtitle = cat_descr.get("subtitle", None)
|
||||
|
@ -2246,6 +2256,9 @@ function set_global_pref(el, pref_name) {
|
|||
initvalues=self,
|
||||
cssclass="sco_pref",
|
||||
submitlabel="Enregistrer les modifications",
|
||||
title="Département et institution",
|
||||
before_table="<details><summary>{title}</summary>",
|
||||
after_table="</details>",
|
||||
)
|
||||
dest_url = (
|
||||
scu.NotesURL()
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -384,7 +384,7 @@ def formsemestre_report_counts(
|
|||
else:
|
||||
checked = ""
|
||||
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
|
||||
)
|
||||
F.append(
|
||||
|
@ -928,7 +928,7 @@ def _gen_form_selectetuds(
|
|||
else:
|
||||
checked = ""
|
||||
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
|
||||
)
|
||||
F.append(
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -463,7 +463,7 @@ def list_synch(sem, anneeapogee=None):
|
|||
"id": "etuds_noninscrits",
|
||||
"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.""",
|
||||
"comment": """ dans ScoDoc et Apogée, <br/>mais pas inscrits
|
||||
"comment": """ dans ScoDoc et Apogée, <br>mais pas inscrits
|
||||
dans ce semestre""",
|
||||
"title_target": "",
|
||||
"with_checkbox": True,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,384 +1,384 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Fonction de gestion des UE "externes" (effectuees dans un cursus exterieur)
|
||||
|
||||
On rapatrie (saisie) les notes (et crédits ECTS).
|
||||
|
||||
Cas d'usage: les étudiants d'une formation gérée par ScoDoc peuvent
|
||||
suivre un certain nombre d'UE à l'extérieur. L'établissement a reconnu
|
||||
au préalable une forme d'équivalence entre ces UE et celles du
|
||||
programme. Les UE effectuées à l'extérieur sont par nature variable
|
||||
d'un étudiant à l'autre et d'une année à l'autre, et ne peuvent pas
|
||||
être introduites dans le programme pédagogique ScoDoc sans alourdir
|
||||
considérablement les opérations (saisie, affichage du programme,
|
||||
gestion des inscriptions).
|
||||
En outre, un suivi détaillé de ces UE n'est pas nécessaire: il suffit
|
||||
de pouvoir y associer une note et une quantité de crédits ECTS.
|
||||
|
||||
Solution proposée (nov 2014):
|
||||
- un nouveau type d'UE qui
|
||||
|
||||
- s'affichera à part dans le programme pédagogique
|
||||
et les bulletins
|
||||
- pas présentées lors de la mise en place de semestres
|
||||
- affichage sur bulletin des étudiants qui y sont inscrit
|
||||
- création en même temps que la saisie de la note
|
||||
(chaine creation: UE/matière/module, inscription étudiant, entrée valeur note)
|
||||
avec auto-suggestion du nom pour limiter la création de doublons
|
||||
- seront aussi présentées (à part) sur la page "Voir les inscriptions aux modules"
|
||||
|
||||
"""
|
||||
import flask
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_edit_matiere
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_edit_ue
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_saisie_notes
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
||||
|
||||
|
||||
def external_ue_create(
|
||||
formsemestre_id,
|
||||
titre="",
|
||||
acronyme="",
|
||||
ue_type=sco_codes_parcours.UE_STANDARD,
|
||||
ects=0.0,
|
||||
):
|
||||
"""Crée UE/matiere/module/evaluation puis saisie les notes"""
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
log(f"creating external UE in {formsemestre}: {acronyme}")
|
||||
|
||||
# Contrôle d'accès:
|
||||
if not current_user.has_permission(Permission.ScoImplement):
|
||||
if (not formsemestre.resp_can_edit) or (
|
||||
current_user.id not in [u.id for u in formsemestre.responsables]
|
||||
):
|
||||
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
|
||||
#
|
||||
formation_id = formsemestre.formation.id
|
||||
|
||||
numero = sco_edit_ue.next_ue_numero(
|
||||
formation_id, semestre_id=formsemestre.semestre_id
|
||||
)
|
||||
ue_id = sco_edit_ue.do_ue_create(
|
||||
{
|
||||
"formation_id": formation_id,
|
||||
"semestre_idx": formsemestre.semestre_id,
|
||||
"titre": titre,
|
||||
"acronyme": acronyme,
|
||||
"numero": numero,
|
||||
"type": ue_type,
|
||||
"ects": ects,
|
||||
"is_external": True,
|
||||
},
|
||||
)
|
||||
|
||||
matiere_id = sco_edit_matiere.do_matiere_create(
|
||||
{"ue_id": ue_id, "titre": titre or acronyme, "numero": 1}
|
||||
)
|
||||
|
||||
module_id = sco_edit_module.do_module_create(
|
||||
{
|
||||
"titre": "UE extérieure",
|
||||
"code": acronyme,
|
||||
"coefficient": ects, # tous le coef. module est egal à la quantite d'ECTS
|
||||
"ue_id": ue_id,
|
||||
"matiere_id": matiere_id,
|
||||
"formation_id": formation_id,
|
||||
"semestre_id": formsemestre.semestre_id,
|
||||
"module_type": scu.ModuleType.STANDARD,
|
||||
},
|
||||
)
|
||||
|
||||
moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(
|
||||
{
|
||||
"module_id": module_id,
|
||||
"formsemestre_id": formsemestre_id,
|
||||
# affecte le 1er responsable du semestre comme resp. du module
|
||||
"responsable_id": formsemestre.responsables[0].id
|
||||
if len(formsemestre.responsables)
|
||||
else None,
|
||||
},
|
||||
)
|
||||
|
||||
return moduleimpl_id
|
||||
|
||||
|
||||
def external_ue_inscrit_et_note(
|
||||
moduleimpl_id: int, formsemestre_id: int, notes_etuds: dict
|
||||
):
|
||||
"""Inscrit les étudiants au moduleimpl, crée au besoin une évaluation
|
||||
et enregistre les notes.
|
||||
"""
|
||||
log(
|
||||
f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl_id}, notes_etuds={notes_etuds})"
|
||||
)
|
||||
# Inscription des étudiants
|
||||
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
|
||||
moduleimpl_id,
|
||||
formsemestre_id,
|
||||
list(notes_etuds.keys()),
|
||||
)
|
||||
|
||||
# Création d'une évaluation si il n'y en a pas déjà:
|
||||
mod_evals = sco_evaluation_db.do_evaluation_list(
|
||||
args={"moduleimpl_id": moduleimpl_id}
|
||||
)
|
||||
if len(mod_evals):
|
||||
# met la note dans le première évaluation existante:
|
||||
evaluation_id = mod_evals[0]["evaluation_id"]
|
||||
else:
|
||||
# crée une évaluation:
|
||||
evaluation_id = sco_evaluation_db.do_evaluation_create(
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
note_max=20.0,
|
||||
coefficient=1.0,
|
||||
publish_incomplete=True,
|
||||
evaluation_type=scu.EVALUATION_NORMALE,
|
||||
visibulletin=False,
|
||||
description="note externe",
|
||||
)
|
||||
# Saisie des notes
|
||||
_, _, _ = sco_saisie_notes.notes_add(
|
||||
current_user,
|
||||
evaluation_id,
|
||||
list(notes_etuds.items()),
|
||||
do_it=True,
|
||||
)
|
||||
|
||||
|
||||
def get_existing_external_ue(formation_id: int) -> list[dict]:
|
||||
"Liste de toutes les UE externes définies dans cette formation"
|
||||
return sco_edit_ue.ue_list(args={"formation_id": formation_id, "is_external": True})
|
||||
|
||||
|
||||
def get_external_moduleimpl_id(formsemestre_id: int, ue_id: int) -> int:
|
||||
"moduleimpl correspondant à l'UE externe indiquée de ce formsemestre"
|
||||
r = ndb.SimpleDictFetch(
|
||||
"""
|
||||
SELECT mi.id AS moduleimpl_id FROM notes_moduleimpl mi, notes_modules mo
|
||||
WHERE mi.formsemestre_id = %(formsemestre_id)s
|
||||
AND mi.module_id = mo.id
|
||||
AND mo.ue_id = %(ue_id)s
|
||||
""",
|
||||
{"ue_id": ue_id, "formsemestre_id": formsemestre_id},
|
||||
)
|
||||
if r:
|
||||
return r[0]["moduleimpl_id"]
|
||||
else:
|
||||
raise ScoValueError(
|
||||
f"""Aucun module externe ne correspond
|
||||
(formsemestre_id={formsemestre_id}, ue_id={ue_id})"""
|
||||
)
|
||||
|
||||
|
||||
# Web function
|
||||
def external_ue_create_form(formsemestre_id: int, etudid: int):
|
||||
"""Formulaire création UE externe + inscription étudiant et saisie note
|
||||
- Demande UE: peut-être existante (liste les UE externes de cette formation),
|
||||
ou sinon spécifier titre, acronyme, type, ECTS
|
||||
- Demande note à enregistrer.
|
||||
|
||||
Note: pour l'édition éventuelle de ces informations, on utilisera les
|
||||
fonctions standards sur les UE/modules/notes
|
||||
"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
# Contrôle d'accès:
|
||||
if not current_user.has_permission(Permission.ScoImplement):
|
||||
if not sem["resp_can_edit"] or (current_user.id not in sem["responsables"]):
|
||||
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
|
||||
|
||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||
formation_id = sem["formation_id"]
|
||||
existing_external_ue = get_existing_external_ue(formation_id)
|
||||
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Ajout d'une UE externe pour %(nomprenom)s" % etud,
|
||||
javascripts=["js/sco_ue_external.js"],
|
||||
),
|
||||
"""<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/>
|
||||
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
|
||||
alors ajoutée à la formation.
|
||||
</p>
|
||||
""",
|
||||
]
|
||||
html_footer = html_sco_header.sco_footer()
|
||||
Fo = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
||||
parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"])
|
||||
ue_types = [
|
||||
typ for typ in parcours.ALLOWED_UE_TYPES if typ != sco_codes_parcours.UE_SPORT
|
||||
]
|
||||
ue_types.sort()
|
||||
ue_types_names = [sco_codes_parcours.UE_TYPE_NAME[k] for k in ue_types]
|
||||
ue_types = [str(x) for x in ue_types]
|
||||
|
||||
if existing_external_ue:
|
||||
default_label = "Nouvelle UE"
|
||||
else:
|
||||
default_label = "Aucune UE externe existante"
|
||||
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
scu.get_request_args(),
|
||||
(
|
||||
("formsemestre_id", {"input_type": "hidden"}),
|
||||
("etudid", {"input_type": "hidden"}),
|
||||
(
|
||||
"existing_ue",
|
||||
{
|
||||
"input_type": "menu",
|
||||
"title": "UE externe existante:",
|
||||
"allowed_values": [""]
|
||||
+ [str(ue["ue_id"]) for ue in existing_external_ue],
|
||||
"labels": [default_label]
|
||||
+ [
|
||||
"%s (%s)" % (ue["titre"], ue["acronyme"])
|
||||
for ue in existing_external_ue
|
||||
],
|
||||
"attributes": ['onchange="update_external_ue_form();"'],
|
||||
"explanation": "inscrire cet étudiant dans cette UE",
|
||||
},
|
||||
),
|
||||
(
|
||||
"sep",
|
||||
{
|
||||
"input_type": "separator",
|
||||
"title": "Ou bien déclarer une nouvelle UE externe:",
|
||||
"dom_id": "tf_extue_decl",
|
||||
},
|
||||
),
|
||||
# champs a desactiver si une UE existante est choisie
|
||||
(
|
||||
"titre",
|
||||
{"size": 30, "explanation": "nom de l'UE", "dom_id": "tf_extue_titre"},
|
||||
),
|
||||
(
|
||||
"acronyme",
|
||||
{
|
||||
"size": 8,
|
||||
"explanation": "abbréviation",
|
||||
"allow_null": True, # attention: verifier
|
||||
"dom_id": "tf_extue_acronyme",
|
||||
},
|
||||
),
|
||||
(
|
||||
"type",
|
||||
{
|
||||
"explanation": "type d'UE",
|
||||
"input_type": "menu",
|
||||
"allowed_values": ue_types,
|
||||
"labels": ue_types_names,
|
||||
"dom_id": "tf_extue_type",
|
||||
},
|
||||
),
|
||||
(
|
||||
"ects",
|
||||
{
|
||||
"size": 4,
|
||||
"type": "float",
|
||||
"min_value": 0,
|
||||
"max_value": 1000,
|
||||
"title": "ECTS",
|
||||
"explanation": "nombre de crédits ECTS",
|
||||
"dom_id": "tf_extue_ects",
|
||||
},
|
||||
),
|
||||
#
|
||||
(
|
||||
"note",
|
||||
{"size": 4, "explanation": "note sur 20", "dom_id": "tf_extue_note"},
|
||||
),
|
||||
),
|
||||
submitlabel="Enregistrer",
|
||||
cancelbutton="Annuler",
|
||||
)
|
||||
|
||||
bull_url = "formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" % (
|
||||
formsemestre_id,
|
||||
etudid,
|
||||
)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + "\n" + tf[1] + html_footer
|
||||
elif tf[0] == -1:
|
||||
return flask.redirect(bull_url)
|
||||
else:
|
||||
note = tf[2]["note"].strip().upper()
|
||||
note_value, invalid = sco_saisie_notes.convert_note_from_string(note, 20.0)
|
||||
if invalid:
|
||||
return (
|
||||
"\n".join(H)
|
||||
+ "\n"
|
||||
+ tf_error_message("valeur note invalide")
|
||||
+ tf[1]
|
||||
+ html_footer
|
||||
)
|
||||
if tf[2]["existing_ue"]:
|
||||
ue_id = int(tf[2]["existing_ue"])
|
||||
moduleimpl_id = get_external_moduleimpl_id(formsemestre_id, ue_id)
|
||||
else:
|
||||
acronyme = tf[2]["acronyme"].strip()
|
||||
if not acronyme:
|
||||
return (
|
||||
"\n".join(H)
|
||||
+ "\n"
|
||||
+ tf_error_message("spécifier acronyme d'UE")
|
||||
+ tf[1]
|
||||
+ html_footer
|
||||
)
|
||||
moduleimpl_id = external_ue_create(
|
||||
formsemestre_id,
|
||||
titre=tf[2]["titre"],
|
||||
acronyme=acronyme,
|
||||
ue_type=tf[2]["type"], # type de l'UE
|
||||
ects=tf[2]["ects"],
|
||||
)
|
||||
|
||||
external_ue_inscrit_et_note(
|
||||
moduleimpl_id,
|
||||
formsemestre_id,
|
||||
{etudid: note_value},
|
||||
)
|
||||
return flask.redirect(bull_url + "&head_message=Ajout%20effectué")
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Fonction de gestion des UE "externes" (effectuees dans un cursus exterieur)
|
||||
|
||||
On rapatrie (saisie) les notes (et crédits ECTS).
|
||||
|
||||
Cas d'usage: les étudiants d'une formation gérée par ScoDoc peuvent
|
||||
suivre un certain nombre d'UE à l'extérieur. L'établissement a reconnu
|
||||
au préalable une forme d'équivalence entre ces UE et celles du
|
||||
programme. Les UE effectuées à l'extérieur sont par nature variable
|
||||
d'un étudiant à l'autre et d'une année à l'autre, et ne peuvent pas
|
||||
être introduites dans le programme pédagogique ScoDoc sans alourdir
|
||||
considérablement les opérations (saisie, affichage du programme,
|
||||
gestion des inscriptions).
|
||||
En outre, un suivi détaillé de ces UE n'est pas nécessaire: il suffit
|
||||
de pouvoir y associer une note et une quantité de crédits ECTS.
|
||||
|
||||
Solution proposée (nov 2014):
|
||||
- un nouveau type d'UE qui
|
||||
|
||||
- s'affichera à part dans le programme pédagogique
|
||||
et les bulletins
|
||||
- pas présentées lors de la mise en place de semestres
|
||||
- affichage sur bulletin des étudiants qui y sont inscrit
|
||||
- création en même temps que la saisie de la note
|
||||
(chaine creation: UE/matière/module, inscription étudiant, entrée valeur note)
|
||||
avec auto-suggestion du nom pour limiter la création de doublons
|
||||
- seront aussi présentées (à part) sur la page "Voir les inscriptions aux modules"
|
||||
|
||||
"""
|
||||
import flask
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_edit_matiere
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_edit_ue
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_saisie_notes
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
||||
|
||||
|
||||
def external_ue_create(
|
||||
formsemestre_id,
|
||||
titre="",
|
||||
acronyme="",
|
||||
ue_type=sco_codes_parcours.UE_STANDARD,
|
||||
ects=0.0,
|
||||
):
|
||||
"""Crée UE/matiere/module/evaluation puis saisie les notes"""
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
log(f"creating external UE in {formsemestre}: {acronyme}")
|
||||
|
||||
# Contrôle d'accès:
|
||||
if not current_user.has_permission(Permission.ScoImplement):
|
||||
if (not formsemestre.resp_can_edit) or (
|
||||
current_user.id not in [u.id for u in formsemestre.responsables]
|
||||
):
|
||||
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
|
||||
#
|
||||
formation_id = formsemestre.formation.id
|
||||
|
||||
numero = sco_edit_ue.next_ue_numero(
|
||||
formation_id, semestre_id=formsemestre.semestre_id
|
||||
)
|
||||
ue_id = sco_edit_ue.do_ue_create(
|
||||
{
|
||||
"formation_id": formation_id,
|
||||
"semestre_idx": formsemestre.semestre_id,
|
||||
"titre": titre,
|
||||
"acronyme": acronyme,
|
||||
"numero": numero,
|
||||
"type": ue_type,
|
||||
"ects": ects,
|
||||
"is_external": True,
|
||||
},
|
||||
)
|
||||
|
||||
matiere_id = sco_edit_matiere.do_matiere_create(
|
||||
{"ue_id": ue_id, "titre": titre or acronyme, "numero": 1}
|
||||
)
|
||||
|
||||
module_id = sco_edit_module.do_module_create(
|
||||
{
|
||||
"titre": "UE extérieure",
|
||||
"code": acronyme,
|
||||
"coefficient": ects, # tous le coef. module est egal à la quantite d'ECTS
|
||||
"ue_id": ue_id,
|
||||
"matiere_id": matiere_id,
|
||||
"formation_id": formation_id,
|
||||
"semestre_id": formsemestre.semestre_id,
|
||||
"module_type": scu.ModuleType.STANDARD,
|
||||
},
|
||||
)
|
||||
|
||||
moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(
|
||||
{
|
||||
"module_id": module_id,
|
||||
"formsemestre_id": formsemestre_id,
|
||||
# affecte le 1er responsable du semestre comme resp. du module
|
||||
"responsable_id": formsemestre.responsables[0].id
|
||||
if len(formsemestre.responsables)
|
||||
else None,
|
||||
},
|
||||
)
|
||||
|
||||
return moduleimpl_id
|
||||
|
||||
|
||||
def external_ue_inscrit_et_note(
|
||||
moduleimpl_id: int, formsemestre_id: int, notes_etuds: dict
|
||||
):
|
||||
"""Inscrit les étudiants au moduleimpl, crée au besoin une évaluation
|
||||
et enregistre les notes.
|
||||
"""
|
||||
log(
|
||||
f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl_id}, notes_etuds={notes_etuds})"
|
||||
)
|
||||
# Inscription des étudiants
|
||||
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
|
||||
moduleimpl_id,
|
||||
formsemestre_id,
|
||||
list(notes_etuds.keys()),
|
||||
)
|
||||
|
||||
# Création d'une évaluation si il n'y en a pas déjà:
|
||||
mod_evals = sco_evaluation_db.do_evaluation_list(
|
||||
args={"moduleimpl_id": moduleimpl_id}
|
||||
)
|
||||
if len(mod_evals):
|
||||
# met la note dans le première évaluation existante:
|
||||
evaluation_id = mod_evals[0]["evaluation_id"]
|
||||
else:
|
||||
# crée une évaluation:
|
||||
evaluation_id = sco_evaluation_db.do_evaluation_create(
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
note_max=20.0,
|
||||
coefficient=1.0,
|
||||
publish_incomplete=True,
|
||||
evaluation_type=scu.EVALUATION_NORMALE,
|
||||
visibulletin=False,
|
||||
description="note externe",
|
||||
)
|
||||
# Saisie des notes
|
||||
_, _, _ = sco_saisie_notes.notes_add(
|
||||
current_user,
|
||||
evaluation_id,
|
||||
list(notes_etuds.items()),
|
||||
do_it=True,
|
||||
)
|
||||
|
||||
|
||||
def get_existing_external_ue(formation_id: int) -> list[dict]:
|
||||
"Liste de toutes les UE externes définies dans cette formation"
|
||||
return sco_edit_ue.ue_list(args={"formation_id": formation_id, "is_external": True})
|
||||
|
||||
|
||||
def get_external_moduleimpl_id(formsemestre_id: int, ue_id: int) -> int:
|
||||
"moduleimpl correspondant à l'UE externe indiquée de ce formsemestre"
|
||||
r = ndb.SimpleDictFetch(
|
||||
"""
|
||||
SELECT mi.id AS moduleimpl_id FROM notes_moduleimpl mi, notes_modules mo
|
||||
WHERE mi.formsemestre_id = %(formsemestre_id)s
|
||||
AND mi.module_id = mo.id
|
||||
AND mo.ue_id = %(ue_id)s
|
||||
""",
|
||||
{"ue_id": ue_id, "formsemestre_id": formsemestre_id},
|
||||
)
|
||||
if r:
|
||||
return r[0]["moduleimpl_id"]
|
||||
else:
|
||||
raise ScoValueError(
|
||||
f"""Aucun module externe ne correspond
|
||||
(formsemestre_id={formsemestre_id}, ue_id={ue_id})"""
|
||||
)
|
||||
|
||||
|
||||
# Web function
|
||||
def external_ue_create_form(formsemestre_id: int, etudid: int):
|
||||
"""Formulaire création UE externe + inscription étudiant et saisie note
|
||||
- Demande UE: peut-être existante (liste les UE externes de cette formation),
|
||||
ou sinon spécifier titre, acronyme, type, ECTS
|
||||
- Demande note à enregistrer.
|
||||
|
||||
Note: pour l'édition éventuelle de ces informations, on utilisera les
|
||||
fonctions standards sur les UE/modules/notes
|
||||
"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
# Contrôle d'accès:
|
||||
if not current_user.has_permission(Permission.ScoImplement):
|
||||
if not sem["resp_can_edit"] or (current_user.id not in sem["responsables"]):
|
||||
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
|
||||
|
||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||
formation_id = sem["formation_id"]
|
||||
existing_external_ue = get_existing_external_ue(formation_id)
|
||||
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Ajout d'une UE externe pour %(nomprenom)s" % etud,
|
||||
javascripts=["js/sco_ue_external.js"],
|
||||
),
|
||||
"""<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>
|
||||
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
|
||||
alors ajoutée à la formation.
|
||||
</p>
|
||||
""",
|
||||
]
|
||||
html_footer = html_sco_header.sco_footer()
|
||||
Fo = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
||||
parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"])
|
||||
ue_types = [
|
||||
typ for typ in parcours.ALLOWED_UE_TYPES if typ != sco_codes_parcours.UE_SPORT
|
||||
]
|
||||
ue_types.sort()
|
||||
ue_types_names = [sco_codes_parcours.UE_TYPE_NAME[k] for k in ue_types]
|
||||
ue_types = [str(x) for x in ue_types]
|
||||
|
||||
if existing_external_ue:
|
||||
default_label = "Nouvelle UE"
|
||||
else:
|
||||
default_label = "Aucune UE externe existante"
|
||||
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
scu.get_request_args(),
|
||||
(
|
||||
("formsemestre_id", {"input_type": "hidden"}),
|
||||
("etudid", {"input_type": "hidden"}),
|
||||
(
|
||||
"existing_ue",
|
||||
{
|
||||
"input_type": "menu",
|
||||
"title": "UE externe existante:",
|
||||
"allowed_values": [""]
|
||||
+ [str(ue["ue_id"]) for ue in existing_external_ue],
|
||||
"labels": [default_label]
|
||||
+ [
|
||||
"%s (%s)" % (ue["titre"], ue["acronyme"])
|
||||
for ue in existing_external_ue
|
||||
],
|
||||
"attributes": ['onchange="update_external_ue_form();"'],
|
||||
"explanation": "inscrire cet étudiant dans cette UE",
|
||||
},
|
||||
),
|
||||
(
|
||||
"sep",
|
||||
{
|
||||
"input_type": "separator",
|
||||
"title": "Ou bien déclarer une nouvelle UE externe:",
|
||||
"dom_id": "tf_extue_decl",
|
||||
},
|
||||
),
|
||||
# champs a desactiver si une UE existante est choisie
|
||||
(
|
||||
"titre",
|
||||
{"size": 30, "explanation": "nom de l'UE", "dom_id": "tf_extue_titre"},
|
||||
),
|
||||
(
|
||||
"acronyme",
|
||||
{
|
||||
"size": 8,
|
||||
"explanation": "abbréviation",
|
||||
"allow_null": True, # attention: verifier
|
||||
"dom_id": "tf_extue_acronyme",
|
||||
},
|
||||
),
|
||||
(
|
||||
"type",
|
||||
{
|
||||
"explanation": "type d'UE",
|
||||
"input_type": "menu",
|
||||
"allowed_values": ue_types,
|
||||
"labels": ue_types_names,
|
||||
"dom_id": "tf_extue_type",
|
||||
},
|
||||
),
|
||||
(
|
||||
"ects",
|
||||
{
|
||||
"size": 4,
|
||||
"type": "float",
|
||||
"min_value": 0,
|
||||
"max_value": 1000,
|
||||
"title": "ECTS",
|
||||
"explanation": "nombre de crédits ECTS",
|
||||
"dom_id": "tf_extue_ects",
|
||||
},
|
||||
),
|
||||
#
|
||||
(
|
||||
"note",
|
||||
{"size": 4, "explanation": "note sur 20", "dom_id": "tf_extue_note"},
|
||||
),
|
||||
),
|
||||
submitlabel="Enregistrer",
|
||||
cancelbutton="Annuler",
|
||||
)
|
||||
|
||||
bull_url = "formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" % (
|
||||
formsemestre_id,
|
||||
etudid,
|
||||
)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + "\n" + tf[1] + html_footer
|
||||
elif tf[0] == -1:
|
||||
return flask.redirect(bull_url)
|
||||
else:
|
||||
note = tf[2]["note"].strip().upper()
|
||||
note_value, invalid = sco_saisie_notes.convert_note_from_string(note, 20.0)
|
||||
if invalid:
|
||||
return (
|
||||
"\n".join(H)
|
||||
+ "\n"
|
||||
+ tf_error_message("valeur note invalide")
|
||||
+ tf[1]
|
||||
+ html_footer
|
||||
)
|
||||
if tf[2]["existing_ue"]:
|
||||
ue_id = int(tf[2]["existing_ue"])
|
||||
moduleimpl_id = get_external_moduleimpl_id(formsemestre_id, ue_id)
|
||||
else:
|
||||
acronyme = tf[2]["acronyme"].strip()
|
||||
if not acronyme:
|
||||
return (
|
||||
"\n".join(H)
|
||||
+ "\n"
|
||||
+ tf_error_message("spécifier acronyme d'UE")
|
||||
+ tf[1]
|
||||
+ html_footer
|
||||
)
|
||||
moduleimpl_id = external_ue_create(
|
||||
formsemestre_id,
|
||||
titre=tf[2]["titre"],
|
||||
acronyme=acronyme,
|
||||
ue_type=tf[2]["type"], # type de l'UE
|
||||
ects=tf[2]["ects"],
|
||||
)
|
||||
|
||||
external_ue_inscrit_et_note(
|
||||
moduleimpl_id,
|
||||
formsemestre_id,
|
||||
{etudid: note_value},
|
||||
)
|
||||
return flask.redirect(bull_url + "&head_message=Ajout%20effectué")
|
||||
|
|
|
@ -1,375 +1,375 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Fonctions sur les utilisateurs
|
||||
"""
|
||||
|
||||
# Anciennement ZScoUsers.py, fonctions de gestion des données réécrite avec flask/SQLAlchemy
|
||||
import re
|
||||
|
||||
from flask import url_for, g, request
|
||||
from flask.templating import render_template
|
||||
from flask_login import current_user
|
||||
|
||||
|
||||
from app import db, Departement
|
||||
|
||||
from app.auth.models import Permission
|
||||
from app.auth.models import User
|
||||
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app import log, cache
|
||||
from app.scodoc.scolog import logdb
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
from app.scodoc.sco_exceptions import (
|
||||
AccessDenied,
|
||||
ScoValueError,
|
||||
)
|
||||
|
||||
|
||||
# ---------------
|
||||
|
||||
# ---------------
|
||||
|
||||
|
||||
def index_html(all_depts=False, with_inactives=False, format="html"):
|
||||
"gestion utilisateurs..."
|
||||
all_depts = int(all_depts)
|
||||
with_inactives = int(with_inactives)
|
||||
|
||||
H = [html_sco_header.html_sem_header("Gestion des utilisateurs")]
|
||||
|
||||
if current_user.has_permission(Permission.ScoUsersAdmin, g.scodoc_dept):
|
||||
H.append(
|
||||
'<p><a href="{}" class="stdlink">Ajouter un utilisateur</a>'.format(
|
||||
url_for("users.create_user_form", scodoc_dept=g.scodoc_dept)
|
||||
)
|
||||
)
|
||||
if current_user.is_administrator():
|
||||
H.append(
|
||||
' <a href="{}" class="stdlink">Importer des utilisateurs</a></p>'.format(
|
||||
url_for("users.import_users_form", scodoc_dept=g.scodoc_dept)
|
||||
)
|
||||
)
|
||||
else:
|
||||
H.append(
|
||||
" Pour importer des utilisateurs en masse (via xlsx file) contactez votre administrateur scodoc."
|
||||
)
|
||||
if all_depts:
|
||||
checked = "checked"
|
||||
else:
|
||||
checked = ""
|
||||
if with_inactives:
|
||||
olds_checked = "checked"
|
||||
else:
|
||||
olds_checked = ""
|
||||
H.append(
|
||||
"""<p><form name="f" action="%s" method="get">
|
||||
<input type="checkbox" name="all_depts" value="1" onchange="document.f.submit();" %s>Tous les départements</input>
|
||||
<input type="checkbox" name="with_inactives" value="1" onchange="document.f.submit();" %s>Avec anciens utilisateurs</input>
|
||||
</form></p>"""
|
||||
% (request.base_url, checked, olds_checked)
|
||||
)
|
||||
|
||||
L = list_users(
|
||||
g.scodoc_dept,
|
||||
all_depts=all_depts,
|
||||
with_inactives=with_inactives,
|
||||
format=format,
|
||||
with_links=current_user.has_permission(Permission.ScoUsersAdmin, g.scodoc_dept),
|
||||
)
|
||||
if format != "html":
|
||||
return L
|
||||
H.append(L)
|
||||
|
||||
F = html_sco_header.sco_footer()
|
||||
return "\n".join(H) + F
|
||||
|
||||
|
||||
def list_users(
|
||||
dept,
|
||||
all_depts=False, # tous les departements
|
||||
with_inactives=False, # inclut les anciens utilisateurs (status "old")
|
||||
format="html",
|
||||
with_links=True,
|
||||
):
|
||||
"List users, returns a table in the specified format"
|
||||
from app.scodoc.sco_permissions_check import can_handle_passwd
|
||||
|
||||
if dept and not all_depts:
|
||||
users = get_user_list(dept=dept, with_inactives=with_inactives)
|
||||
comm = "dept. %s" % dept
|
||||
else:
|
||||
users = get_user_list(with_inactives=with_inactives)
|
||||
comm = "tous"
|
||||
if with_inactives:
|
||||
comm += ", avec anciens"
|
||||
comm = "(" + comm + ")"
|
||||
# -- Add some information and links:
|
||||
r = []
|
||||
for u in users:
|
||||
# Can current user modify this user ?
|
||||
can_modify = can_handle_passwd(u, allow_admindepts=True)
|
||||
|
||||
d = u.to_dict()
|
||||
r.append(d)
|
||||
# Add links
|
||||
if with_links and can_modify:
|
||||
target = url_for(
|
||||
"users.user_info_page", scodoc_dept=dept, user_name=u.user_name
|
||||
)
|
||||
d["_user_name_target"] = target
|
||||
d["_nom_target"] = target
|
||||
d["_prenom_target"] = target
|
||||
|
||||
# Hide passwd modification date (depending on visitor's permission)
|
||||
if not can_modify:
|
||||
d["date_modif_passwd"] = "(non visible)"
|
||||
|
||||
columns_ids = [
|
||||
"user_name",
|
||||
"nom_fmt",
|
||||
"prenom_fmt",
|
||||
"email",
|
||||
"dept",
|
||||
"roles_string",
|
||||
"date_expiration",
|
||||
"date_modif_passwd",
|
||||
"passwd_temp",
|
||||
"status_txt",
|
||||
]
|
||||
# Seul l'admin peut voir les dates de dernière connexion
|
||||
if current_user.is_administrator():
|
||||
columns_ids.append("last_seen")
|
||||
title = "Utilisateurs définis dans ScoDoc"
|
||||
tab = GenTable(
|
||||
rows=r,
|
||||
columns_ids=columns_ids,
|
||||
titles={
|
||||
"user_name": "Login",
|
||||
"nom_fmt": "Nom",
|
||||
"prenom_fmt": "Prénom",
|
||||
"email": "Mail",
|
||||
"dept": "Dept.",
|
||||
"roles_string": "Rôles",
|
||||
"date_expiration": "Expiration",
|
||||
"date_modif_passwd": "Modif. mot de passe",
|
||||
"last_seen": "Dernière cnx.",
|
||||
"passwd_temp": "Temp.",
|
||||
"status_txt": "Etat",
|
||||
},
|
||||
caption=title,
|
||||
page_title="title",
|
||||
html_title="""<h2>%d utilisateurs %s</h2>
|
||||
<p class="help">Cliquer sur un nom pour changer son mot de passe</p>"""
|
||||
% (len(r), comm),
|
||||
html_class="table_leftalign list_users",
|
||||
html_with_td_classes=True,
|
||||
html_sortable=True,
|
||||
base_url="%s?all_depts=%s" % (request.base_url, 1 if all_depts else 0),
|
||||
pdf_link=False, # table is too wide to fit in a paper page => disable pdf
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
|
||||
return tab.make_page(format=format, with_html_headers=False)
|
||||
|
||||
|
||||
def get_user_list(dept=None, with_inactives=False):
|
||||
"""Returns list of users.
|
||||
If dept, select users from this dept,
|
||||
else return all users.
|
||||
"""
|
||||
# was get_userlist
|
||||
q = User.query
|
||||
if dept is not None:
|
||||
q = q.filter_by(dept=dept)
|
||||
if not with_inactives:
|
||||
q = q.filter_by(active=True)
|
||||
return q.order_by(User.nom, User.user_name).all()
|
||||
|
||||
|
||||
def _user_list(user_name):
|
||||
"return user as a dict"
|
||||
u = User.query.filter_by(user_name=user_name).first()
|
||||
if u:
|
||||
return u.to_dict()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@cache.memoize(timeout=50) # seconds
|
||||
def user_info(user_name_or_id=None, user: User = None):
|
||||
"""Dict avec infos sur l'utilisateur (qui peut ne pas etre dans notre base).
|
||||
Si user_name est specifie (string ou id), interroge la BD. Sinon, user doit etre une instance
|
||||
de User.
|
||||
"""
|
||||
if user_name_or_id is not None:
|
||||
if isinstance(user_name_or_id, int):
|
||||
u = User.query.filter_by(id=user_name_or_id).first()
|
||||
else:
|
||||
u = User.query.filter_by(user_name=user_name_or_id).first()
|
||||
if u:
|
||||
user_name = u.user_name
|
||||
info = u.to_dict()
|
||||
else:
|
||||
info = None
|
||||
user_name = "inconnu"
|
||||
else:
|
||||
info = user.to_dict()
|
||||
user_name = user.user_name
|
||||
|
||||
if not info:
|
||||
# special case: user is not in our database
|
||||
return {
|
||||
"user_name": user_name,
|
||||
"nom": user_name,
|
||||
"prenom": "",
|
||||
"email": "",
|
||||
"dept": "",
|
||||
"nomprenom": user_name,
|
||||
"prenomnom": user_name,
|
||||
"prenom_fmt": "",
|
||||
"nom_fmt": user_name,
|
||||
"nomcomplet": user_name,
|
||||
"nomplogin": user_name,
|
||||
# "nomnoacc": scu.suppress_accents(user_name),
|
||||
"passwd_temp": 0,
|
||||
"status": "",
|
||||
"date_expiration": None,
|
||||
}
|
||||
else:
|
||||
# Ensure we never publish password hash
|
||||
if "password_hash" in info:
|
||||
del info["password_hash"]
|
||||
return info
|
||||
|
||||
|
||||
def check_modif_user(
|
||||
edit,
|
||||
enforce_optionals=False,
|
||||
user_name="",
|
||||
nom="",
|
||||
prenom="",
|
||||
email="",
|
||||
dept="",
|
||||
roles=[],
|
||||
):
|
||||
"""Vérifie que cet utilisateur peut être créé (edit=0) ou modifié (edit=1)
|
||||
Cherche homonymes.
|
||||
returns (ok, msg)
|
||||
- ok : si vrai, peut continuer avec ces parametres
|
||||
(si ok est faux, l'utilisateur peut quand même forcer la creation)
|
||||
- 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)"""
|
||||
# ce login existe ?
|
||||
user = _user_list(user_name)
|
||||
if edit and not user: # safety net, le user_name ne devrait pas changer
|
||||
return False, "identifiant %s inexistant" % user_name
|
||||
if not edit and user:
|
||||
return False, "identifiant %s déjà utilisé" % user_name
|
||||
if not user_name or not nom or not prenom:
|
||||
return False, "champ requis vide"
|
||||
if not re.match(r"^[a-zA-Z0-9@\\\-_\\\.]*$", user_name):
|
||||
return (
|
||||
False,
|
||||
"identifiant '%s' invalide (pas d'accents ni de caractères spéciaux)"
|
||||
% user_name,
|
||||
)
|
||||
if enforce_optionals and len(user_name) > 64:
|
||||
return False, "identifiant '%s' trop long (64 caractères)" % user_name
|
||||
if enforce_optionals and len(nom) > 64:
|
||||
return False, "nom '%s' trop long (64 caractères)" % nom + MSG_OPT
|
||||
if enforce_optionals and len(prenom) > 64:
|
||||
return False, "prenom '%s' trop long (64 caractères)" % prenom + MSG_OPT
|
||||
# check that tha same user_name has not already been described in this import
|
||||
if not email:
|
||||
return False, "vous devriez indiquer le mail de l'utilisateur créé !"
|
||||
if len(email) > 120:
|
||||
return False, "email '%s' trop long (120 caractères)" % email
|
||||
if not re.fullmatch(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", email):
|
||||
return False, "l'adresse mail semble incorrecte"
|
||||
# check département
|
||||
if (
|
||||
enforce_optionals
|
||||
and dept
|
||||
and Departement.query.filter_by(acronym=dept).first() is None
|
||||
):
|
||||
return False, "département '%s' inexistant" % dept + MSG_OPT
|
||||
if enforce_optionals and not roles:
|
||||
return False, "aucun rôle sélectionné, êtes vous sûr ?" + MSG_OPT
|
||||
# Unicité du mail
|
||||
users_with_this_mail = User.query.filter_by(email=email).all()
|
||||
if edit: # modification
|
||||
if email != user["email"] and len(users_with_this_mail) > 0:
|
||||
return False, "un autre utilisateur existe déjà avec cette adresse mail"
|
||||
else: # création utilisateur
|
||||
if len(users_with_this_mail) > 0:
|
||||
return False, "un autre utilisateur existe déjà avec cette adresse mail"
|
||||
|
||||
# ok
|
||||
# Des noms/prénoms semblables existent ?
|
||||
nom = nom.lower().strip()
|
||||
prenom = prenom.lower().strip()
|
||||
similar_users = User.query.filter(
|
||||
User.nom.ilike(nom), User.prenom.ilike(prenom)
|
||||
).all()
|
||||
if edit:
|
||||
minmatch = 1
|
||||
else:
|
||||
minmatch = 0
|
||||
if enforce_optionals and len(similar_users) > minmatch:
|
||||
return (
|
||||
False,
|
||||
"des utilisateurs proches existent: "
|
||||
+ ", ".join(
|
||||
[
|
||||
"%s %s (pseudo=%s)" % (x.prenom, x.nom, x.user_name)
|
||||
for x in similar_users
|
||||
]
|
||||
)
|
||||
+ MSG_OPT,
|
||||
)
|
||||
# Roles ?
|
||||
return True, ""
|
||||
|
||||
|
||||
def user_edit(user_name, vals):
|
||||
"""Edit the user specified by user_name
|
||||
(ported from Zope to SQLAlchemy, hence strange !)
|
||||
"""
|
||||
u = User.query.filter_by(user_name=user_name).first()
|
||||
if not u:
|
||||
raise ScoValueError("Invalid user_name")
|
||||
u.from_dict(vals)
|
||||
db.session.add(u)
|
||||
db.session.commit()
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Fonctions sur les utilisateurs
|
||||
"""
|
||||
|
||||
# Anciennement ZScoUsers.py, fonctions de gestion des données réécrite avec flask/SQLAlchemy
|
||||
import re
|
||||
|
||||
from flask import url_for, g, request
|
||||
from flask.templating import render_template
|
||||
from flask_login import current_user
|
||||
|
||||
|
||||
from app import db, Departement
|
||||
|
||||
from app.auth.models import Permission
|
||||
from app.auth.models import User
|
||||
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app import log, cache
|
||||
from app.scodoc.scolog import logdb
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
from app.scodoc.sco_exceptions import (
|
||||
AccessDenied,
|
||||
ScoValueError,
|
||||
)
|
||||
|
||||
|
||||
# ---------------
|
||||
|
||||
# ---------------
|
||||
|
||||
|
||||
def index_html(all_depts=False, with_inactives=False, format="html"):
|
||||
"gestion utilisateurs..."
|
||||
all_depts = int(all_depts)
|
||||
with_inactives = int(with_inactives)
|
||||
|
||||
H = [html_sco_header.html_sem_header("Gestion des utilisateurs")]
|
||||
|
||||
if current_user.has_permission(Permission.ScoUsersAdmin, g.scodoc_dept):
|
||||
H.append(
|
||||
'<p><a href="{}" class="stdlink">Ajouter un utilisateur</a>'.format(
|
||||
url_for("users.create_user_form", scodoc_dept=g.scodoc_dept)
|
||||
)
|
||||
)
|
||||
if current_user.is_administrator():
|
||||
H.append(
|
||||
' <a href="{}" class="stdlink">Importer des utilisateurs</a></p>'.format(
|
||||
url_for("users.import_users_form", scodoc_dept=g.scodoc_dept)
|
||||
)
|
||||
)
|
||||
else:
|
||||
H.append(
|
||||
" Pour importer des utilisateurs en masse (via xlsx file) contactez votre administrateur scodoc."
|
||||
)
|
||||
if all_depts:
|
||||
checked = "checked"
|
||||
else:
|
||||
checked = ""
|
||||
if with_inactives:
|
||||
olds_checked = "checked"
|
||||
else:
|
||||
olds_checked = ""
|
||||
H.append(
|
||||
"""<p><form name="f" action="%s" method="get">
|
||||
<input type="checkbox" name="all_depts" value="1" onchange="document.f.submit();" %s>Tous les départements</input>
|
||||
<input type="checkbox" name="with_inactives" value="1" onchange="document.f.submit();" %s>Avec anciens utilisateurs</input>
|
||||
</form></p>"""
|
||||
% (request.base_url, checked, olds_checked)
|
||||
)
|
||||
|
||||
L = list_users(
|
||||
g.scodoc_dept,
|
||||
all_depts=all_depts,
|
||||
with_inactives=with_inactives,
|
||||
format=format,
|
||||
with_links=current_user.has_permission(Permission.ScoUsersAdmin, g.scodoc_dept),
|
||||
)
|
||||
if format != "html":
|
||||
return L
|
||||
H.append(L)
|
||||
|
||||
F = html_sco_header.sco_footer()
|
||||
return "\n".join(H) + F
|
||||
|
||||
|
||||
def list_users(
|
||||
dept,
|
||||
all_depts=False, # tous les departements
|
||||
with_inactives=False, # inclut les anciens utilisateurs (status "old")
|
||||
format="html",
|
||||
with_links=True,
|
||||
):
|
||||
"List users, returns a table in the specified format"
|
||||
from app.scodoc.sco_permissions_check import can_handle_passwd
|
||||
|
||||
if dept and not all_depts:
|
||||
users = get_user_list(dept=dept, with_inactives=with_inactives)
|
||||
comm = "dept. %s" % dept
|
||||
else:
|
||||
users = get_user_list(with_inactives=with_inactives)
|
||||
comm = "tous"
|
||||
if with_inactives:
|
||||
comm += ", avec anciens"
|
||||
comm = "(" + comm + ")"
|
||||
# -- Add some information and links:
|
||||
r = []
|
||||
for u in users:
|
||||
# Can current user modify this user ?
|
||||
can_modify = can_handle_passwd(u, allow_admindepts=True)
|
||||
|
||||
d = u.to_dict()
|
||||
r.append(d)
|
||||
# Add links
|
||||
if with_links and can_modify:
|
||||
target = url_for(
|
||||
"users.user_info_page", scodoc_dept=dept, user_name=u.user_name
|
||||
)
|
||||
d["_user_name_target"] = target
|
||||
d["_nom_target"] = target
|
||||
d["_prenom_target"] = target
|
||||
|
||||
# Hide passwd modification date (depending on visitor's permission)
|
||||
if not can_modify:
|
||||
d["date_modif_passwd"] = "(non visible)"
|
||||
|
||||
columns_ids = [
|
||||
"user_name",
|
||||
"nom_fmt",
|
||||
"prenom_fmt",
|
||||
"email",
|
||||
"dept",
|
||||
"roles_string",
|
||||
"date_expiration",
|
||||
"date_modif_passwd",
|
||||
"passwd_temp",
|
||||
"status_txt",
|
||||
]
|
||||
# Seul l'admin peut voir les dates de dernière connexion
|
||||
if current_user.is_administrator():
|
||||
columns_ids.append("last_seen")
|
||||
title = "Utilisateurs définis dans ScoDoc"
|
||||
tab = GenTable(
|
||||
rows=r,
|
||||
columns_ids=columns_ids,
|
||||
titles={
|
||||
"user_name": "Login",
|
||||
"nom_fmt": "Nom",
|
||||
"prenom_fmt": "Prénom",
|
||||
"email": "Mail",
|
||||
"dept": "Dept.",
|
||||
"roles_string": "Rôles",
|
||||
"date_expiration": "Expiration",
|
||||
"date_modif_passwd": "Modif. mot de passe",
|
||||
"last_seen": "Dernière cnx.",
|
||||
"passwd_temp": "Temp.",
|
||||
"status_txt": "Etat",
|
||||
},
|
||||
caption=title,
|
||||
page_title="title",
|
||||
html_title="""<h2>%d utilisateurs %s</h2>
|
||||
<p class="help">Cliquer sur un nom pour changer son mot de passe</p>"""
|
||||
% (len(r), comm),
|
||||
html_class="table_leftalign list_users",
|
||||
html_with_td_classes=True,
|
||||
html_sortable=True,
|
||||
base_url="%s?all_depts=%s" % (request.base_url, 1 if all_depts else 0),
|
||||
pdf_link=False, # table is too wide to fit in a paper page => disable pdf
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
|
||||
return tab.make_page(format=format, with_html_headers=False)
|
||||
|
||||
|
||||
def get_user_list(dept=None, with_inactives=False):
|
||||
"""Returns list of users.
|
||||
If dept, select users from this dept,
|
||||
else return all users.
|
||||
"""
|
||||
# was get_userlist
|
||||
q = User.query
|
||||
if dept is not None:
|
||||
q = q.filter_by(dept=dept)
|
||||
if not with_inactives:
|
||||
q = q.filter_by(active=True)
|
||||
return q.order_by(User.nom, User.user_name).all()
|
||||
|
||||
|
||||
def _user_list(user_name):
|
||||
"return user as a dict"
|
||||
u = User.query.filter_by(user_name=user_name).first()
|
||||
if u:
|
||||
return u.to_dict()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@cache.memoize(timeout=50) # seconds
|
||||
def user_info(user_name_or_id=None, user: User = None):
|
||||
"""Dict avec infos sur l'utilisateur (qui peut ne pas etre dans notre base).
|
||||
Si user_name est specifie (string ou id), interroge la BD. Sinon, user doit etre une instance
|
||||
de User.
|
||||
"""
|
||||
if user_name_or_id is not None:
|
||||
if isinstance(user_name_or_id, int):
|
||||
u = User.query.filter_by(id=user_name_or_id).first()
|
||||
else:
|
||||
u = User.query.filter_by(user_name=user_name_or_id).first()
|
||||
if u:
|
||||
user_name = u.user_name
|
||||
info = u.to_dict()
|
||||
else:
|
||||
info = None
|
||||
user_name = "inconnu"
|
||||
else:
|
||||
info = user.to_dict()
|
||||
user_name = user.user_name
|
||||
|
||||
if not info:
|
||||
# special case: user is not in our database
|
||||
return {
|
||||
"user_name": user_name,
|
||||
"nom": user_name,
|
||||
"prenom": "",
|
||||
"email": "",
|
||||
"dept": "",
|
||||
"nomprenom": user_name,
|
||||
"prenomnom": user_name,
|
||||
"prenom_fmt": "",
|
||||
"nom_fmt": user_name,
|
||||
"nomcomplet": user_name,
|
||||
"nomplogin": user_name,
|
||||
# "nomnoacc": scu.suppress_accents(user_name),
|
||||
"passwd_temp": 0,
|
||||
"status": "",
|
||||
"date_expiration": None,
|
||||
}
|
||||
else:
|
||||
# Ensure we never publish password hash
|
||||
if "password_hash" in info:
|
||||
del info["password_hash"]
|
||||
return info
|
||||
|
||||
|
||||
def check_modif_user(
|
||||
edit,
|
||||
enforce_optionals=False,
|
||||
user_name="",
|
||||
nom="",
|
||||
prenom="",
|
||||
email="",
|
||||
dept="",
|
||||
roles=[],
|
||||
):
|
||||
"""Vérifie que cet utilisateur peut être créé (edit=0) ou modifié (edit=1)
|
||||
Cherche homonymes.
|
||||
returns (ok, msg)
|
||||
- ok : si vrai, peut continuer avec ces parametres
|
||||
(si ok est faux, l'utilisateur peut quand même forcer la creation)
|
||||
- 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)"""
|
||||
# ce login existe ?
|
||||
user = _user_list(user_name)
|
||||
if edit and not user: # safety net, le user_name ne devrait pas changer
|
||||
return False, "identifiant %s inexistant" % user_name
|
||||
if not edit and user:
|
||||
return False, "identifiant %s déjà utilisé" % user_name
|
||||
if not user_name or not nom or not prenom:
|
||||
return False, "champ requis vide"
|
||||
if not re.match(r"^[a-zA-Z0-9@\\\-_\\\.]*$", user_name):
|
||||
return (
|
||||
False,
|
||||
"identifiant '%s' invalide (pas d'accents ni de caractères spéciaux)"
|
||||
% user_name,
|
||||
)
|
||||
if enforce_optionals and len(user_name) > 64:
|
||||
return False, "identifiant '%s' trop long (64 caractères)" % user_name
|
||||
if enforce_optionals and len(nom) > 64:
|
||||
return False, "nom '%s' trop long (64 caractères)" % nom + MSG_OPT
|
||||
if enforce_optionals and len(prenom) > 64:
|
||||
return False, "prenom '%s' trop long (64 caractères)" % prenom + MSG_OPT
|
||||
# check that tha same user_name has not already been described in this import
|
||||
if not email:
|
||||
return False, "vous devriez indiquer le mail de l'utilisateur créé !"
|
||||
if len(email) > 120:
|
||||
return False, "email '%s' trop long (120 caractères)" % email
|
||||
if not re.fullmatch(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", email):
|
||||
return False, "l'adresse mail semble incorrecte"
|
||||
# check département
|
||||
if (
|
||||
enforce_optionals
|
||||
and dept
|
||||
and Departement.query.filter_by(acronym=dept).first() is None
|
||||
):
|
||||
return False, "département '%s' inexistant" % dept + MSG_OPT
|
||||
if enforce_optionals and not roles:
|
||||
return False, "aucun rôle sélectionné, êtes vous sûr ?" + MSG_OPT
|
||||
# Unicité du mail
|
||||
users_with_this_mail = User.query.filter_by(email=email).all()
|
||||
if edit: # modification
|
||||
if email != user["email"] and len(users_with_this_mail) > 0:
|
||||
return False, "un autre utilisateur existe déjà avec cette adresse mail"
|
||||
else: # création utilisateur
|
||||
if len(users_with_this_mail) > 0:
|
||||
return False, "un autre utilisateur existe déjà avec cette adresse mail"
|
||||
|
||||
# ok
|
||||
# Des noms/prénoms semblables existent ?
|
||||
nom = nom.lower().strip()
|
||||
prenom = prenom.lower().strip()
|
||||
similar_users = User.query.filter(
|
||||
User.nom.ilike(nom), User.prenom.ilike(prenom)
|
||||
).all()
|
||||
if edit:
|
||||
minmatch = 1
|
||||
else:
|
||||
minmatch = 0
|
||||
if enforce_optionals and len(similar_users) > minmatch:
|
||||
return (
|
||||
False,
|
||||
"des utilisateurs proches existent: "
|
||||
+ ", ".join(
|
||||
[
|
||||
"%s %s (pseudo=%s)" % (x.prenom, x.nom, x.user_name)
|
||||
for x in similar_users
|
||||
]
|
||||
)
|
||||
+ MSG_OPT,
|
||||
)
|
||||
# Roles ?
|
||||
return True, ""
|
||||
|
||||
|
||||
def user_edit(user_name, vals):
|
||||
"""Edit the user specified by user_name
|
||||
(ported from Zope to SQLAlchemy, hence strange !)
|
||||
"""
|
||||
u = User.query.filter_by(user_name=user_name).first()
|
||||
if not u:
|
||||
raise ScoValueError("Invalid user_name")
|
||||
u.from_dict(vals)
|
||||
db.session.add(u)
|
||||
db.session.commit()
|
||||
|
|
|
@ -2863,10 +2863,13 @@ select.tf-selglobal {
|
|||
}
|
||||
|
||||
td.tf-fieldlabel {
|
||||
/* font-weight: bold; */
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
td.tf-field {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.tf-comment {
|
||||
font-size: 80%;
|
||||
font-style: italic;
|
||||
|
@ -2876,6 +2879,12 @@ td.tf-fieldlabel {
|
|||
font-style: italic;
|
||||
}
|
||||
|
||||
#tf details summary {
|
||||
font-size: 130%;
|
||||
margin-top: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.radio_green {
|
||||
background-color: green;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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.
|
||||
Un semestre verrouillé peut cependant être déverrouillé facilement à tout moment
|
||||
(par son responsable ou un administrateur).
|
||||
<br/>
|
||||
<br>
|
||||
Le programme d'une formation qui a un semestre verrouillé ne peut plus être modifié.
|
||||
""",
|
||||
dest_url="",
|
||||
|
@ -3045,12 +3045,12 @@ def check_sem_integrity(formsemestre_id, fix=False):
|
|||
if bad_ue:
|
||||
H += [
|
||||
"<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:
|
||||
H += [
|
||||
"<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:
|
||||
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:
|
||||
bad.append(mod)
|
||||
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])
|
||||
log("check_form_integrity: formation_id=%s\ninconsistencies:" % formation_id)
|
||||
log(txt)
|
||||
|
@ -3162,7 +3162,7 @@ def check_formsemestre_integrity(formsemestre_id):
|
|||
diag = ["OK"]
|
||||
log("ok")
|
||||
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()
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
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.
|
||||
<br/>
|
||||
<br>
|
||||
<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>
|
||||
</p>
|
||||
<p>
|
||||
|
@ -2240,7 +2240,7 @@ def formsemestre_import_etud_admission(formsemestre_id, import_email=True):
|
|||
H.append("<h3>Adresses mails modifiées:</h3>")
|
||||
for (info, new_mail) in changed_mails:
|
||||
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)
|
||||
)
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
|
2006
app/views/users.py
2006
app/views/users.py
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue