forked from ScoDoc/ScoDoc
2614 lines
84 KiB
Python
2614 lines
84 KiB
Python
# -*- coding: utf-8 -*-
|
|
##############################################################################
|
|
#
|
|
# ScoDoc
|
|
#
|
|
# Copyright (c) 1999 - 2024 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 scolar: vues de .../ScoDoc/<dept>/Scolarite
|
|
|
|
issu de ScoDoc7 / ZScolar.py
|
|
|
|
Emmanuel Viennet, 2021
|
|
"""
|
|
import datetime
|
|
import time
|
|
|
|
import requests
|
|
|
|
import flask
|
|
from flask import abort, flash, make_response, render_template, url_for
|
|
from flask import g, request
|
|
from flask_json import as_json
|
|
from flask_login import current_user
|
|
from flask_wtf import FlaskForm
|
|
from flask_wtf.file import FileField, FileAllowed
|
|
import sqlalchemy as sa
|
|
from wtforms import SubmitField
|
|
|
|
import app
|
|
from app import db
|
|
from app import log
|
|
from app.decorators import (
|
|
scodoc,
|
|
scodoc7func,
|
|
permission_required,
|
|
permission_required_compat_scodoc7,
|
|
)
|
|
from app.models import (
|
|
Admission,
|
|
Departement,
|
|
FormSemestre,
|
|
Identite,
|
|
Partition,
|
|
ScolarEvent,
|
|
ScolarNews,
|
|
Scolog,
|
|
)
|
|
from app.models.etudiants import make_etud_args
|
|
from app.views import scolar_bp as bp
|
|
from app.views import ScoData
|
|
|
|
import app.scodoc.sco_utils as scu
|
|
import app.scodoc.notesdb as ndb
|
|
from app.scodoc.sco_permissions import Permission
|
|
from app.scodoc.sco_exceptions import (
|
|
AccessDenied,
|
|
ScoPermissionDenied,
|
|
ScoValueError,
|
|
)
|
|
|
|
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
|
from app.scodoc.gen_tables import GenTable
|
|
from app.scodoc import (
|
|
codes_cursus,
|
|
html_sco_header,
|
|
sco_import_etuds,
|
|
sco_archives_etud,
|
|
sco_bug_report,
|
|
sco_cache,
|
|
sco_debouche,
|
|
sco_dept,
|
|
sco_dump_db,
|
|
sco_etud,
|
|
sco_edt_cal,
|
|
sco_find_etud,
|
|
sco_formsemestre,
|
|
sco_formsemestre_inscriptions,
|
|
sco_groups,
|
|
sco_groups_edit,
|
|
sco_groups_exports,
|
|
sco_groups_view,
|
|
sco_page_etud,
|
|
sco_permissions_check,
|
|
sco_photos,
|
|
sco_portal_apogee,
|
|
sco_preferences,
|
|
sco_synchro_etuds,
|
|
sco_trombino,
|
|
sco_trombino_tours,
|
|
sco_up_to_date,
|
|
)
|
|
from app.tables import list_etuds
|
|
from app.forms.main.create_bug_report import CreateBugReport
|
|
|
|
|
|
def sco_publish(route, function, permission, methods=["GET"]):
|
|
"""Declare a route for a python function,
|
|
protected by permission and called following ScoDoc 7 Zope standards.
|
|
"""
|
|
return bp.route(route, methods=methods)(
|
|
scodoc(permission_required(permission)(scodoc7func(function)))
|
|
)
|
|
|
|
|
|
# --------------------------------------------------------------------
|
|
#
|
|
# SCOLARITE (/ScoDoc/<dept>/Scolarite/...)
|
|
#
|
|
# --------------------------------------------------------------------
|
|
|
|
|
|
# --------------------------------------------------------------------
|
|
#
|
|
# PREFERENCES
|
|
#
|
|
# --------------------------------------------------------------------
|
|
|
|
|
|
@bp.route("/edit_preferences", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EditPreferences)
|
|
@scodoc7func
|
|
def edit_preferences():
|
|
"""Edit global preferences (lien "Paramétrage" département)"""
|
|
return sco_preferences.get_base_preferences().edit()
|
|
|
|
|
|
@bp.route("/formsemestre_edit_preferences", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_edit_preferences(formsemestre_id):
|
|
"""Edit preferences for a semestre"""
|
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
ok = (
|
|
current_user.has_permission(Permission.EditFormSemestre)
|
|
or ((current_user.id in sem["responsables"]) and sem["resp_can_edit"])
|
|
) and (sem["etat"])
|
|
if ok:
|
|
return sco_preferences.SemPreferences(formsemestre_id=formsemestre_id).edit()
|
|
else:
|
|
raise AccessDenied(
|
|
"Modification impossible pour %s" % current_user.get_nomplogin()
|
|
)
|
|
|
|
|
|
@bp.route("/doc_preferences")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def doc_preferences():
|
|
"""List preferences for wiki documentation"""
|
|
response = make_response(sco_preferences.doc_preferences())
|
|
response.headers["Content-Type"] = "text/plain"
|
|
return response
|
|
|
|
|
|
class DeptLogosConfigurationForm(FlaskForm):
|
|
"Panneau de configuration logos dept"
|
|
|
|
logo_header = FileField(
|
|
label="Modifier l'image:",
|
|
description="logo placé en haut des documents PDF",
|
|
validators=[
|
|
FileAllowed(
|
|
scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
|
f"n'accepte que les fichiers image <tt>{','.join(scu.LOGOS_IMAGES_ALLOWED_TYPES)}</tt>",
|
|
)
|
|
],
|
|
)
|
|
|
|
logo_footer = FileField(
|
|
label="Modifier l'image:",
|
|
description="logo placé en pied des documents PDF",
|
|
validators=[
|
|
FileAllowed(
|
|
scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
|
f"n'accepte que les fichiers image <tt>{','.join(scu.LOGOS_IMAGES_ALLOWED_TYPES)}</tt>",
|
|
)
|
|
],
|
|
)
|
|
|
|
submit = SubmitField("Enregistrer")
|
|
|
|
|
|
# @bp.route("/config_logos", methods=["GET", "POST"])
|
|
# @permission_required(Permission.EditPreferences)
|
|
# def config_logos(scodoc_dept):
|
|
# "Panneau de configuration général"
|
|
# form = DeptLogosConfigurationForm()
|
|
# if form.validate_on_submit():
|
|
# if form.logo_header.data:
|
|
# sco_logos.store_image(
|
|
# form.logo_header.data,
|
|
# os.path.join(
|
|
# scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_header"
|
|
# ),
|
|
# )
|
|
# if form.logo_footer.data:
|
|
# sco_logos.store_image(
|
|
# form.logo_footer.data,
|
|
# os.path.join(
|
|
# scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_footer"
|
|
# ),
|
|
# )
|
|
# app.clear_scodoc_cache()
|
|
# flash(f"Logos enregistrés")
|
|
# return flask.redirect(url_for("scolar.index_html", scodoc_dept=scodoc_dept))
|
|
#
|
|
# return render_template(
|
|
# "configuration.j2",
|
|
# title="Configuration Logos du département",
|
|
# form=form,
|
|
# scodoc_dept=scodoc_dept,
|
|
# )
|
|
#
|
|
#
|
|
# class DeptLogosConfigurationForm(FlaskForm):
|
|
# "Panneau de configuration logos dept"
|
|
#
|
|
# logo_header = FileField(
|
|
# label="Modifier l'image:",
|
|
# description="logo placé en haut des documents PDF",
|
|
# validators=[
|
|
# FileAllowed(
|
|
# scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
|
# f"n'accepte que les fichiers image <tt>{','.join([e for e in scu.LOGOS_IMAGES_ALLOWED_TYPES])}</tt>",
|
|
# )
|
|
# ],
|
|
# )
|
|
#
|
|
# logo_footer = FileField(
|
|
# label="Modifier l'image:",
|
|
# description="logo placé en pied des documents PDF",
|
|
# validators=[
|
|
# FileAllowed(
|
|
# scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
|
# f"n'accepte que les fichiers image <tt>{','.join([e for e in scu.LOGOS_IMAGES_ALLOWED_TYPES])}</tt>",
|
|
# )
|
|
# ],
|
|
# )
|
|
#
|
|
# submit = SubmitField("Enregistrer")
|
|
|
|
|
|
# @bp.route("/config_logos", methods=["GET", "POST"])
|
|
# @permission_required(Permission.EditPreferences)
|
|
# def config_logos(scodoc_dept):
|
|
# "Panneau de configuration général"
|
|
# form = DeptLogosConfigurationForm()
|
|
# if form.validate_on_submit():
|
|
# if form.logo_header.data:
|
|
# sco_logos.store_image(
|
|
# form.logo_header.data,
|
|
# os.path.join(
|
|
# scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_header"
|
|
# ),
|
|
# )
|
|
# if form.logo_footer.data:
|
|
# sco_logos.store_image(
|
|
# form.logo_footer.data,
|
|
# os.path.join(
|
|
# scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_footer"
|
|
# ),
|
|
# )
|
|
# app.clear_scodoc_cache()
|
|
# flash(f"Logos enregistrés")
|
|
# return flask.redirect(url_for("scolar.index_html", scodoc_dept=scodoc_dept))
|
|
#
|
|
# return render_template(
|
|
# "configuration.j2",
|
|
# title="Configuration Logos du département",
|
|
# form=form,
|
|
# scodoc_dept=scodoc_dept,
|
|
# )
|
|
|
|
|
|
# --------------------------------------------------------------------
|
|
#
|
|
# ETUDIANTS
|
|
#
|
|
# --------------------------------------------------------------------
|
|
|
|
|
|
@bp.route("/show_etud_log")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def show_etud_log(etudid, fmt="html"):
|
|
"""Display log of operations on this student"""
|
|
etud = Identite.get_etud(etudid)
|
|
|
|
operations = Scolog.query.filter_by(etudid=etud.id).order_by(Scolog.date.desc())
|
|
tab = GenTable(
|
|
titles={
|
|
"date": "Date",
|
|
"authenticated_user": "Utilisateur",
|
|
"method": "Opération",
|
|
"msg": "Message",
|
|
},
|
|
columns_ids=("date", "authenticated_user", "method", "msg"),
|
|
rows=[op.to_dict(convert_date=True) for op in operations],
|
|
html_sortable=True,
|
|
html_class="table_leftalign",
|
|
base_url="%s?etudid=%s" % (request.base_url, etud.id),
|
|
page_title=f"Opérations sur {etud.nom_prenom()}",
|
|
html_title=f"""<h2>Opérations effectuées sur l'étudiant{etud.e} {
|
|
etud.html_link_fiche()}</h2>""",
|
|
filename="log_" + scu.make_filename(etud.nom_prenom()),
|
|
html_next_section=f"""
|
|
<ul>
|
|
<li>Fiche de {etud.html_link_fiche()}</li>
|
|
</ul>""",
|
|
preferences=sco_preferences.SemPreferences(),
|
|
table_id="show_etud_log",
|
|
)
|
|
|
|
return tab.make_page(fmt=fmt)
|
|
|
|
|
|
# ---------- PAGE ACCUEIL (listes) --------------
|
|
|
|
|
|
@bp.route("/")
|
|
@bp.route("/index_html", alias=True)
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def index_html(showcodes=0, showsemtable=0):
|
|
"La page d'accueil département (ScoDoc/<dept>/Scolarite)"
|
|
return sco_dept.index_html(showcodes=showcodes, showsemtable=showsemtable)
|
|
|
|
|
|
@bp.route("/export_table_dept_formsemestres")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def export_table_dept_formsemestres():
|
|
"""La table de tous les semestres non EXt du département, en excel"""
|
|
table = sco_dept.index_html(showcodes=True, export_table_formsemestres=True)
|
|
return scu.send_file(
|
|
table.excel(),
|
|
f"semestres_{g.scodoc_dept}",
|
|
suffix=scu.XLSX_SUFFIX,
|
|
mime=scu.XLSX_MIMETYPE,
|
|
)
|
|
|
|
|
|
@bp.route("/install_info")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def install_info():
|
|
"""Information on install status (html str)"""
|
|
return sco_up_to_date.is_up_to_date()
|
|
|
|
|
|
@bp.route("/dept_news")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def dept_news():
|
|
"Affiche table des dernières opérations"
|
|
return render_template(
|
|
"dept_news.j2", title=f"Opérations {g.scodoc_dept}", sco=ScoData()
|
|
)
|
|
|
|
|
|
@bp.route("/dept_news_json")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def dept_news_json():
|
|
"Table des news du département"
|
|
start = request.args.get("start", type=int)
|
|
length = request.args.get("length", type=int)
|
|
|
|
log(f"dept_news_json( start={start}, length={length})")
|
|
query = ScolarNews.query.filter_by(dept_id=g.scodoc_dept_id)
|
|
# search
|
|
search = request.args.get("search[value]")
|
|
if search:
|
|
query = query.filter(
|
|
db.or_(
|
|
ScolarNews.authenticated_user.like(f"%{search}%"),
|
|
ScolarNews.text.like(f"%{search}%"),
|
|
)
|
|
)
|
|
total_filtered = query.count()
|
|
# sorting
|
|
order = []
|
|
i = 0
|
|
while True:
|
|
col_index = request.args.get(f"order[{i}][column]")
|
|
if col_index is None:
|
|
break
|
|
col_name = request.args.get(f"columns[{col_index}][data]")
|
|
if col_name not in ["date", "type", "authenticated_user"]:
|
|
col_name = "date"
|
|
descending = request.args.get(f"order[{i}][dir]") == "desc"
|
|
col = getattr(ScolarNews, col_name)
|
|
if descending:
|
|
col = col.desc()
|
|
order.append(col)
|
|
i += 1
|
|
if order:
|
|
query = query.order_by(*order)
|
|
|
|
# pagination
|
|
query = query.offset(start).limit(length)
|
|
data = [news.to_dict() for news in query]
|
|
# response
|
|
return {
|
|
"data": data,
|
|
"recordsFiltered": total_filtered,
|
|
"recordsTotal": ScolarNews.query.count(),
|
|
"draw": request.args.get("draw", type=int),
|
|
}
|
|
|
|
|
|
sco_publish(
|
|
"/trombino", sco_trombino.trombino, Permission.ScoView, methods=["GET", "POST"]
|
|
)
|
|
|
|
sco_publish(
|
|
"/pdf_trombino_tours", sco_trombino_tours.pdf_trombino_tours, Permission.ScoView
|
|
)
|
|
|
|
sco_publish(
|
|
"/pdf_feuille_releve_absences",
|
|
sco_trombino_tours.pdf_feuille_releve_absences,
|
|
Permission.ScoView,
|
|
)
|
|
|
|
sco_publish(
|
|
"/trombino_copy_photos",
|
|
sco_trombino.trombino_copy_photos,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
sco_publish(
|
|
"/groups_export_annotations",
|
|
sco_groups_exports.groups_export_annotations,
|
|
Permission.ViewEtudData,
|
|
)
|
|
|
|
|
|
@bp.route("/groups_view")
|
|
@scodoc
|
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
|
@scodoc7func
|
|
def groups_view(
|
|
group_ids=(),
|
|
fmt="html",
|
|
# Options pour listes:
|
|
with_codes=0,
|
|
etat=None,
|
|
with_paiement=0, # si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail)
|
|
with_archives=0, # ajoute colonne avec noms fichiers archivés
|
|
with_annotations=0,
|
|
with_bourse=0,
|
|
formsemestre_id=None,
|
|
):
|
|
return sco_groups_view.groups_view(
|
|
group_ids=group_ids,
|
|
fmt=fmt,
|
|
# Options pour listes:
|
|
with_codes=with_codes,
|
|
etat=etat,
|
|
with_paiement=with_paiement,
|
|
with_archives=with_archives,
|
|
with_annotations=with_annotations,
|
|
with_bourse=with_bourse,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
|
|
|
|
sco_publish(
|
|
"/export_groups_as_moodle_csv",
|
|
sco_groups_view.export_groups_as_moodle_csv,
|
|
Permission.ScoView,
|
|
)
|
|
|
|
|
|
# -------------------------- INFOS SUR ETUDIANTS --------------------------
|
|
@bp.route("/getEtudInfo")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def getEtudInfo(etudid=False, code_nip=False, filled=False, fmt=None):
|
|
"""infos sur un etudiant (API)
|
|
On peut specifier etudid ou code_nip
|
|
ou bien cherche dans les arguments de la requête: etudid, code_nip, code_ine
|
|
(dans cet ordre).
|
|
"""
|
|
etud = sco_etud.get_etud_info(etudid=etudid, code_nip=code_nip, filled=filled)
|
|
if fmt is None:
|
|
return etud
|
|
return scu.sendResult(etud, name="etud", fmt=fmt)
|
|
|
|
|
|
sco_publish(
|
|
"/search_etud_in_dept",
|
|
sco_find_etud.search_etud_in_dept,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
|
|
@bp.route("/search_etud_by_name")
|
|
@bp.route("/Notes/search_etud_by_name") # for JS apis
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@as_json
|
|
def search_etud_by_name():
|
|
"""Recherche étudiants par nom ou NIP
|
|
utilisé par autocomplete formulaire recherche
|
|
"""
|
|
term = request.args["term"]
|
|
data = sco_find_etud.search_etud_by_name(term)
|
|
return data
|
|
|
|
|
|
# XMLgetEtudInfos était le nom dans l'ancienne API ScoDoc 6
|
|
@bp.route("/etud_info", methods=["GET", "POST"]) # pour compat anciens clients PHP)
|
|
@bp.route(
|
|
"/XMLgetEtudInfos", methods=["GET", "POST"]
|
|
) # pour compat anciens clients PHP)
|
|
@bp.route(
|
|
"/Absences/XMLgetEtudInfos", methods=["GET", "POST"]
|
|
) # pour compat anciens clients PHP
|
|
@bp.route(
|
|
"/Notes/XMLgetEtudInfos", methods=["GET", "POST"]
|
|
) # pour compat anciens clients PHP
|
|
@scodoc
|
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
|
@scodoc7func
|
|
def etud_info(etudid=None, fmt="xml"):
|
|
"Donne les informations sur un etudiant"
|
|
if not fmt in ("xml", "json"):
|
|
raise ScoValueError("format demandé non supporté par cette fonction.")
|
|
t0 = time.time()
|
|
args = make_etud_args(etudid=etudid)
|
|
cnx = ndb.GetDBConnexion()
|
|
etuds = sco_etud.etudident_list(cnx, args)
|
|
if not etuds:
|
|
# etudiant non trouvé: message d'erreur
|
|
d = {
|
|
"etudid": etudid,
|
|
"nom": "?",
|
|
"nom_usuel": "",
|
|
"prenom": "?",
|
|
"civilite": "?",
|
|
"sexe": "?", # for backward compat
|
|
"email": "?",
|
|
"emailperso": "",
|
|
"error": "code etudiant inconnu",
|
|
}
|
|
return scu.sendResult(d, name="etudiant", fmt=fmt, force_outer_xml_tag=False)
|
|
d = {}
|
|
etud = etuds[0]
|
|
sco_etud.fill_etuds_info([etud])
|
|
etud["date_naissance_iso"] = ndb.DateDMYtoISO(etud["date_naissance"])
|
|
for a in (
|
|
"etudid",
|
|
"code_nip",
|
|
"code_ine",
|
|
"nom",
|
|
"nom_usuel",
|
|
"prenom",
|
|
"nomprenom",
|
|
"prenom_etat_civil",
|
|
"email",
|
|
"emailperso",
|
|
"domicile",
|
|
"codepostaldomicile",
|
|
"villedomicile",
|
|
"paysdomicile",
|
|
"telephone",
|
|
"telephonemobile",
|
|
"fax",
|
|
"bac",
|
|
"specialite",
|
|
"annee_bac",
|
|
"nomlycee",
|
|
"villelycee",
|
|
"codepostallycee",
|
|
"codelycee",
|
|
"date_naissance_iso",
|
|
):
|
|
d[a] = etud[a] # ne pas quoter car ElementTree.tostring quote déjà
|
|
d["civilite"] = etud["civilite_str"] # exception: ne sort pas les civilités brutes
|
|
d["civilite_etat_civil"] = etud["civilite_etat_civil_str"]
|
|
d["sexe"] = d["civilite"] # backward compat pour anciens clients
|
|
d["photo_url"] = sco_photos.etud_photo_url(etud)
|
|
|
|
sem = etud["cursem"]
|
|
if sem:
|
|
sco_groups.etud_add_group_infos(etud, sem["formsemestre_id"] if sem else None)
|
|
d["insemestre"] = [
|
|
{
|
|
"current": "1",
|
|
"formsemestre_id": sem["formsemestre_id"],
|
|
"date_debut": ndb.DateDMYtoISO(sem["date_debut"]),
|
|
"date_fin": ndb.DateDMYtoISO(sem["date_fin"]),
|
|
"etat": sem["ins"]["etat"],
|
|
"groupes": etud["groupes"], # slt pour semestre courant
|
|
}
|
|
]
|
|
else:
|
|
d["insemestre"] = []
|
|
for sem in etud["sems"]:
|
|
if sem != etud["cursem"]:
|
|
d["insemestre"].append(
|
|
{
|
|
"formsemestre_id": sem["formsemestre_id"],
|
|
"date_debut": ndb.DateDMYtoISO(sem["date_debut"]),
|
|
"date_fin": ndb.DateDMYtoISO(sem["date_fin"]),
|
|
"etat": sem["ins"]["etat"],
|
|
}
|
|
)
|
|
|
|
log("etud_info (%gs)" % (time.time() - t0))
|
|
return scu.sendResult(
|
|
d, name="etudiant", fmt=fmt, force_outer_xml_tag=False, quote_xml=False
|
|
)
|
|
|
|
|
|
# -------------------------- FICHE ETUDIANT --------------------------
|
|
sco_publish("/fiche_etud", sco_page_etud.fiche_etud, Permission.ScoView)
|
|
|
|
sco_publish(
|
|
"/etud_upload_file_form",
|
|
sco_archives_etud.etud_upload_file_form,
|
|
Permission.ViewEtudData,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
sco_publish(
|
|
"/etud_delete_archive",
|
|
sco_archives_etud.etud_delete_archive,
|
|
Permission.ViewEtudData,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
sco_publish(
|
|
"/etud_get_archived_file",
|
|
sco_archives_etud.etud_get_archived_file,
|
|
Permission.ViewEtudData,
|
|
)
|
|
|
|
sco_publish(
|
|
"/etudarchive_import_files_form",
|
|
sco_archives_etud.etudarchive_import_files_form,
|
|
Permission.ViewEtudData,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
sco_publish(
|
|
"/etudarchive_generate_excel_sample",
|
|
sco_archives_etud.etudarchive_generate_excel_sample,
|
|
Permission.ScoView,
|
|
)
|
|
|
|
|
|
# Debouche / devenir etudiant
|
|
sco_publish(
|
|
"/itemsuivi_suppress",
|
|
sco_debouche.itemsuivi_suppress,
|
|
Permission.EtudChangeAdr,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/itemsuivi_create",
|
|
sco_debouche.itemsuivi_create,
|
|
Permission.EtudChangeAdr,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/itemsuivi_set_date",
|
|
sco_debouche.itemsuivi_set_date,
|
|
Permission.EtudChangeAdr,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/itemsuivi_set_situation",
|
|
sco_debouche.itemsuivi_set_situation,
|
|
Permission.EtudChangeAdr,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/itemsuivi_list_etud", sco_debouche.itemsuivi_list_etud, Permission.ScoView
|
|
)
|
|
sco_publish("/itemsuivi_tag_list", sco_debouche.itemsuivi_tag_list, Permission.ScoView)
|
|
sco_publish(
|
|
"/itemsuivi_tag_search", sco_debouche.itemsuivi_tag_search, Permission.ScoView
|
|
)
|
|
sco_publish(
|
|
"/itemsuivi_tag_set",
|
|
sco_debouche.itemsuivi_tag_set,
|
|
Permission.EtudChangeAdr,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
|
|
@bp.route("/doAddAnnotation", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EtudAddAnnotations)
|
|
@scodoc7func
|
|
def doAddAnnotation(etudid, comment):
|
|
"ajoute annotation sur etudiant"
|
|
_ = Identite.get_etud(etudid) # check existence
|
|
if comment:
|
|
cnx = ndb.GetDBConnexion()
|
|
sco_etud.etud_annotations_create(
|
|
cnx,
|
|
args={
|
|
"etudid": etudid,
|
|
"comment": comment,
|
|
"author": current_user.user_name,
|
|
},
|
|
)
|
|
Scolog.logdb(method="addAnnotation", etudid=etudid, commit=True)
|
|
return flask.redirect(
|
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
|
)
|
|
|
|
|
|
@bp.route("/doSuppressAnnotation", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def doSuppressAnnotation(etudid, annotation_id):
|
|
"""Suppression annotation."""
|
|
if not sco_permissions_check.can_suppress_annotation(annotation_id):
|
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
|
|
|
cnx = ndb.GetDBConnexion()
|
|
annos = sco_etud.etud_annotations_list(cnx, args={"id": annotation_id})
|
|
if len(annos) != 1:
|
|
raise ScoValueError("annotation inexistante !")
|
|
anno = annos[0]
|
|
log(f"suppress annotation: {anno}")
|
|
Scolog.logdb(method="SuppressAnnotation", etudid=etudid)
|
|
sco_etud.etud_annotations_delete(cnx, annotation_id)
|
|
|
|
flash("Annotation supprimée")
|
|
return flask.redirect(
|
|
url_for(
|
|
"scolar.fiche_etud",
|
|
scodoc_dept=g.scodoc_dept,
|
|
etudid=etudid,
|
|
)
|
|
)
|
|
|
|
|
|
@bp.route("/form_change_coordonnees", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EtudChangeAdr)
|
|
@scodoc7func
|
|
def form_change_coordonnees(etudid):
|
|
"edit coordonnees etudiant"
|
|
if not current_user.has_permission(Permission.ViewEtudData):
|
|
raise ScoPermissionDenied()
|
|
etud = Identite.get_etud(etudid)
|
|
cnx = ndb.GetDBConnexion()
|
|
adrs = sco_etud.adresse_list(cnx, {"etudid": etudid})
|
|
if adrs:
|
|
adr = adrs[0]
|
|
else:
|
|
adr = {} # no data for this student
|
|
H = [
|
|
f"""{html_sco_header.sco_header(
|
|
page_title=f"Changement coordonnées de {etud.nomprenom}"
|
|
)}
|
|
<h2>Changement des coordonnées de {etud.nomprenom}</h2>
|
|
<p>"""
|
|
]
|
|
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
scu.get_request_args(),
|
|
(
|
|
("adresse_id", {"input_type": "hidden"}),
|
|
("etudid", {"input_type": "hidden"}),
|
|
(
|
|
"email",
|
|
{
|
|
"size": 40,
|
|
"title": "e-mail",
|
|
"explanation": "adresse institutionnelle",
|
|
},
|
|
),
|
|
(
|
|
"emailperso",
|
|
{
|
|
"size": 40,
|
|
"title": "e-mail",
|
|
"explanation": "adresse personnelle",
|
|
},
|
|
),
|
|
(
|
|
"domicile",
|
|
{"size": 65, "explanation": "numéro, rue", "title": "Adresse"},
|
|
),
|
|
("codepostaldomicile", {"size": 6, "title": "Code postal"}),
|
|
("villedomicile", {"size": 20, "title": "Ville"}),
|
|
("paysdomicile", {"size": 20, "title": "Pays"}),
|
|
("", {"input_type": "separator", "default": " "}),
|
|
("telephone", {"size": 13, "title": "Téléphone"}),
|
|
("telephonemobile", {"size": 13, "title": "Mobile"}),
|
|
),
|
|
initvalues=adr,
|
|
submitlabel="Enregistrer",
|
|
cancelbutton="Annuler",
|
|
)
|
|
dest_url = url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
|
if tf[0] == 0:
|
|
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
|
elif tf[0] == -1:
|
|
return flask.redirect(dest_url)
|
|
else:
|
|
if adrs:
|
|
sco_etud.adresse_edit(cnx, args=tf[2])
|
|
else:
|
|
sco_etud.adresse_create(cnx, args=tf[2])
|
|
Scolog.logdb(method="changeCoordonnees", etudid=etudid, commit=True)
|
|
return flask.redirect(dest_url)
|
|
|
|
|
|
# --- Gestion des groupes:
|
|
sco_publish(
|
|
"/affect_groups",
|
|
sco_groups_edit.affect_groups,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
sco_publish(
|
|
"/XMLgetGroupsInPartition", sco_groups.XMLgetGroupsInPartition, Permission.ScoView
|
|
)
|
|
|
|
sco_publish(
|
|
"/formsemestre_partition_list",
|
|
sco_groups.formsemestre_partition_list,
|
|
Permission.ScoView,
|
|
)
|
|
|
|
sco_publish("/setGroups", sco_groups.setGroups, Permission.ScoView, methods=["POST"])
|
|
|
|
|
|
sco_publish(
|
|
"/group_rename",
|
|
sco_groups_edit.group_rename,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
|
|
@bp.route("/groups_auto_repartition/<int:partition_id>", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def groups_auto_repartition(partition_id: int):
|
|
"Réparti les etudiants dans des groupes dans une partition"
|
|
partition: Partition = Partition.query.get_or_404(partition_id)
|
|
return sco_groups.groups_auto_repartition(partition)
|
|
|
|
|
|
sco_publish(
|
|
"/edit_partition_form",
|
|
sco_groups.edit_partition_form,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
sco_publish(
|
|
"/partition_delete",
|
|
sco_groups.partition_delete,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
sco_publish(
|
|
"/partition_set_attr",
|
|
sco_groups.partition_set_attr,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
sco_publish(
|
|
"/partition_move",
|
|
sco_groups.partition_move,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
sco_publish(
|
|
"/partition_set_name",
|
|
sco_groups.partition_set_name,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
sco_publish(
|
|
"/partition_rename",
|
|
sco_groups.partition_rename,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
sco_publish(
|
|
"/partition_create",
|
|
sco_groups.partition_create,
|
|
Permission.ScoView, # controle d'access ad-hoc
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
|
|
# Nouvel éditeur de partitions et groupe, @SebL Jul 2022
|
|
@bp.route("/partition_editor", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def partition_editor(formsemestre_id: int, edit_partition=False):
|
|
"""Page édition groupes et partitions
|
|
Si edit_partition, se met en mode édition des partitions.
|
|
"""
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
edit_partition = bool(int(edit_partition)) if edit_partition else False
|
|
formsemestre.setup_parcours_groups()
|
|
H = [
|
|
html_sco_header.sco_header(
|
|
cssstyles=["css/partition_editor.css"],
|
|
javascripts=[
|
|
"js/partition_editor.js",
|
|
],
|
|
page_title=f"Partitions de {formsemestre.titre_annee()}",
|
|
init_datatables=True,
|
|
),
|
|
"""<h2></h2>""",
|
|
render_template(
|
|
"scolar/partition_editor.j2",
|
|
formsemestre=formsemestre,
|
|
read_only=not formsemestre.can_change_groups(),
|
|
edit_partition=edit_partition,
|
|
is_edt_configured=sco_edt_cal.is_edt_configured(),
|
|
scu=scu,
|
|
),
|
|
html_sco_header.sco_footer(),
|
|
]
|
|
|
|
return "\n".join(H)
|
|
|
|
|
|
@bp.route("/students_groups_auto_assignment", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def students_groups_auto_assignment(formsemestre_id: int):
|
|
"""Répartition auto des groupes"""
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
H = [
|
|
html_sco_header.sco_header(
|
|
page_title="Répartition des groupes",
|
|
),
|
|
render_template(
|
|
"scolar/students_groups_auto_assignment.j2",
|
|
formsemestre=formsemestre,
|
|
),
|
|
html_sco_header.sco_footer(),
|
|
]
|
|
|
|
return "\n".join(H)
|
|
|
|
|
|
@bp.route("/create_partition_parcours", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def create_partition_parcours(formsemestre_id):
|
|
"""Création d'une partitions nommée "Parcours" (PARTITION_PARCOURS)
|
|
avec un groupe par parcours."""
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
formsemestre.setup_parcours_groups()
|
|
return flask.redirect(
|
|
url_for(
|
|
"scolar.edit_partition_form",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
|
|
|
|
sco_publish("/etud_info_html", sco_page_etud.etud_info_html, Permission.ScoView)
|
|
|
|
# --- Gestion des photos:
|
|
sco_publish("/get_photo_image", sco_photos.get_photo_image, Permission.ScoView)
|
|
|
|
sco_publish("/etud_photo_html", sco_photos.etud_photo_html, Permission.ScoView)
|
|
|
|
|
|
@bp.route("/etud_photo_orig_page/<int:etudid>")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def etud_photo_orig_page(etudid):
|
|
"Page with photo in orig. size"
|
|
etud = Identite.get_etud(etudid)
|
|
return f"""{
|
|
html_sco_header.sco_header(etudid=etud.id, page_title=etud.nomprenom)
|
|
}
|
|
<h2>{etud.nomprenom}</h2>
|
|
<div>
|
|
<a href="{etud.url_fiche()}">{etud.photo_html(size='orig')}</a>
|
|
</div>
|
|
{html_sco_header.sco_footer()}
|
|
"""
|
|
|
|
|
|
@bp.route("/form_change_photo", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EtudChangeAdr)
|
|
@scodoc7func
|
|
def form_change_photo(etudid=None):
|
|
"""Formulaire changement photo étudiant"""
|
|
etud = Identite.get_etud(etudid)
|
|
if sco_photos.etud_photo_is_local(etud.photo_filename):
|
|
photo_loc = "dans ScoDoc"
|
|
else:
|
|
photo_loc = "externe"
|
|
H = [
|
|
html_sco_header.sco_header(page_title="Changement de photo"),
|
|
f"""<h2>Changement de la photo de {etud.nomprenom}</h2>
|
|
<p>Photo actuelle ({photo_loc}):
|
|
{sco_photos.etud_photo_html(etudid=etud.id, title="photo actuelle")}
|
|
</p>
|
|
<p>Le fichier ne doit pas dépasser {sco_photos.MAX_FILE_SIZE//1024}Ko
|
|
(recadrer l'image, format "portrait" de préférence).
|
|
</p>
|
|
<p>L'image sera automagiquement réduite pour obtenir une hauteur de 90 pixels.</p>
|
|
""",
|
|
]
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
scu.get_request_args(),
|
|
(
|
|
("etudid", {"default": etud.id, "input_type": "hidden"}),
|
|
(
|
|
"photofile",
|
|
{"input_type": "file", "title": "Fichier image", "size": 20},
|
|
),
|
|
),
|
|
submitlabel="Valider",
|
|
cancelbutton="Annuler",
|
|
)
|
|
dest_url = url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
|
if tf[0] == 0:
|
|
return (
|
|
"\n".join(H)
|
|
+ f"""
|
|
{tf[1]}
|
|
<p><a class="stdlink" href="{
|
|
url_for("scolar.form_suppress_photo",
|
|
scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
|
}">Supprimer cette photo</a></p>
|
|
{html_sco_header.sco_footer()}
|
|
"""
|
|
)
|
|
elif tf[0] == -1:
|
|
return flask.redirect(dest_url)
|
|
else:
|
|
data = tf[2]["photofile"].read()
|
|
status, err_msg = sco_photos.store_photo(
|
|
etud, data, tf[2]["photofile"].filename
|
|
)
|
|
if status:
|
|
return flask.redirect(dest_url)
|
|
else:
|
|
H.append(f"""<p class="warning">Erreur: {err_msg}</p>""")
|
|
return "\n".join(H) + html_sco_header.sco_footer()
|
|
|
|
|
|
@bp.route("/form_suppress_photo", methods=["POST", "GET"])
|
|
@scodoc
|
|
@permission_required(Permission.EtudChangeAdr)
|
|
@scodoc7func
|
|
def form_suppress_photo(etudid=None, dialog_confirmed=False):
|
|
"""Formulaire suppression photo étudiant"""
|
|
etud = Identite.get_etud(etudid)
|
|
if not dialog_confirmed:
|
|
return scu.confirm_dialog(
|
|
f"<p>Confirmer la suppression de la photo de {etud.nom_disp()} ?</p>",
|
|
dest_url="",
|
|
cancel_url=url_for(
|
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
|
),
|
|
parameters={"etudid": etud.id},
|
|
)
|
|
|
|
sco_photos.suppress_photo(etud)
|
|
|
|
return flask.redirect(
|
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
|
)
|
|
|
|
|
|
#
|
|
@bp.route("/form_dem")
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@scodoc7func
|
|
def form_dem(etudid, formsemestre_id):
|
|
"Formulaire Démission Etudiant"
|
|
return _form_dem_of_def(
|
|
etudid,
|
|
formsemestre_id,
|
|
operation_name="Démission",
|
|
operation_method="do_dem_etudiant",
|
|
)
|
|
|
|
|
|
@bp.route("/form_def")
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@scodoc7func
|
|
def form_def(etudid, formsemestre_id):
|
|
"Formulaire Défaillance Etudiant"
|
|
return _form_dem_of_def(
|
|
etudid,
|
|
formsemestre_id,
|
|
operation_name="Défaillance",
|
|
operation_method="do_def_etudiant",
|
|
)
|
|
|
|
|
|
def _form_dem_of_def(
|
|
etudid: int,
|
|
formsemestre_id: int,
|
|
operation_name: str = "",
|
|
operation_method: str = "",
|
|
):
|
|
"Formulaire démission ou défaillance Etudiant"
|
|
etud = Identite.get_etud(etudid)
|
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
|
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
|
).first_or_404()
|
|
if not formsemestre.etat:
|
|
raise ScoValueError("Modification impossible: semestre verrouille")
|
|
nowdmy = time.strftime(scu.DATE_FMT)
|
|
#
|
|
header = html_sco_header.sco_header(
|
|
page_title=f"""{operation_name} de {etud.nomprenom} (du semestre {formsemestre.titre_mois()})"""
|
|
)
|
|
validations_descr = formsemestre.etud_validations_description_html(etudid)
|
|
return f"""
|
|
{header}
|
|
<h2><font color="#FF0000">{operation_name} de</font> {etud.nomprenom} ({formsemestre.titre_mois()})</h2>
|
|
|
|
<form action="{operation_method}" method="get">
|
|
<div><b>Date de la {operation_name.lower()} (J/M/AAAA): </b>
|
|
<input type="text" name="event_date" width=20 value="{nowdmy}">
|
|
</div>
|
|
<input type="hidden" name="etudid" value="{etudid}">
|
|
<input type="hidden" name="formsemestre_id" value="{formsemestre_id}">
|
|
<div class="vertical_spacing_but"><input type="submit" value="Confirmer"></div>
|
|
</form>
|
|
|
|
<div class="rappel_decisions">
|
|
{'<p class="warning">Attention: il y a des décisions de jury déjà prises !</p>' if validations_descr else ""}
|
|
{validations_descr}
|
|
{('<p><a class="stdlink" href="'
|
|
+ url_for("notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id, etudid=etudid)
|
|
+ '">modifier ces décisions</a></p>') if validations_descr else ""}
|
|
</div>
|
|
{html_sco_header.sco_footer()}
|
|
"""
|
|
|
|
|
|
@bp.route("/do_dem_etudiant")
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@scodoc7func
|
|
def do_dem_etudiant(etudid, formsemestre_id, event_date=None):
|
|
"Déclare la démission d'un etudiant dans le semestre"
|
|
return _do_dem_or_def_etud(
|
|
etudid,
|
|
formsemestre_id,
|
|
event_date=event_date,
|
|
etat_new=scu.DEMISSION,
|
|
operation_method="dem_etudiant",
|
|
event_type="DEMISSION",
|
|
)
|
|
|
|
|
|
@bp.route("/do_def_etudiant")
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@scodoc7func
|
|
def do_def_etudiant(etudid, formsemestre_id, event_date=None):
|
|
"Déclare la défaillance d'un etudiant dans le semestre"
|
|
return _do_dem_or_def_etud(
|
|
etudid,
|
|
formsemestre_id,
|
|
event_date=event_date,
|
|
etat_new=codes_cursus.DEF,
|
|
operation_method="defailleEtudiant",
|
|
event_type="DEFAILLANCE",
|
|
)
|
|
|
|
|
|
def _do_dem_or_def_etud(
|
|
etudid,
|
|
formsemestre_id,
|
|
event_date=None,
|
|
etat_new=scu.DEMISSION, # DEMISSION or DEF
|
|
operation_method="demEtudiant",
|
|
event_type="DEMISSION",
|
|
redirect=True,
|
|
):
|
|
"Démission ou défaillance d'un étudiant"
|
|
sco_formsemestre_inscriptions.do_formsemestre_demission(
|
|
etudid,
|
|
formsemestre_id,
|
|
event_date=event_date,
|
|
etat_new=etat_new, # DEMISSION or DEF
|
|
operation_method=operation_method,
|
|
event_type=event_type,
|
|
)
|
|
if redirect:
|
|
return flask.redirect(
|
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
|
)
|
|
|
|
|
|
@bp.route("/do_cancel_dem", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@scodoc7func
|
|
def do_cancel_dem(etudid, formsemestre_id, dialog_confirmed=False, args=None):
|
|
"Annule une démission"
|
|
return _do_cancel_dem_or_def(
|
|
etudid,
|
|
formsemestre_id,
|
|
dialog_confirmed=dialog_confirmed,
|
|
args=args,
|
|
operation_name="démission",
|
|
etat_current=scu.DEMISSION,
|
|
etat_new=scu.INSCRIT,
|
|
operation_method="cancelDem",
|
|
event_type="DEMISSION",
|
|
)
|
|
|
|
|
|
@bp.route("/do_cancel_def", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@scodoc7func
|
|
def do_cancel_def(etudid, formsemestre_id, dialog_confirmed=False, args=None):
|
|
"Annule la défaillance de l'étudiant"
|
|
return _do_cancel_dem_or_def(
|
|
etudid,
|
|
formsemestre_id,
|
|
dialog_confirmed=dialog_confirmed,
|
|
args=args,
|
|
operation_name="défaillance",
|
|
etat_current=codes_cursus.DEF,
|
|
etat_new=scu.INSCRIT,
|
|
operation_method="cancel_def",
|
|
event_type="DEFAILLANCE",
|
|
)
|
|
|
|
|
|
def _do_cancel_dem_or_def(
|
|
etudid,
|
|
formsemestre_id,
|
|
dialog_confirmed=False,
|
|
args=None,
|
|
operation_name="", # "démission" ou "défaillance"
|
|
etat_current=scu.DEMISSION,
|
|
etat_new=scu.INSCRIT,
|
|
operation_method="cancel_dem",
|
|
event_type="DEMISSION",
|
|
):
|
|
"Annule une démission ou une défaillance"
|
|
etud = Identite.get_etud(etudid)
|
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
|
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
|
).first_or_404()
|
|
# check lock
|
|
if not formsemestre.etat:
|
|
raise ScoValueError("Modification impossible: semestre verrouille")
|
|
# verif
|
|
if formsemestre_id not in (inscr.formsemestre_id for inscr in etud.inscriptions()):
|
|
raise ScoValueError("étudiant non inscrit dans ce semestre !")
|
|
if etud.inscription_etat(formsemestre_id) != etat_current:
|
|
raise ScoValueError(f"etudiant non {operation_name} !")
|
|
|
|
if not dialog_confirmed:
|
|
return scu.confirm_dialog(
|
|
f"<p>Confirmer l'annulation de la {operation_name} ?</p>",
|
|
dest_url="",
|
|
cancel_url=url_for(
|
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
|
),
|
|
parameters={"etudid": etudid, "formsemestre_id": formsemestre_id},
|
|
)
|
|
#
|
|
inscr = next(
|
|
inscr
|
|
for inscr in etud.inscriptions()
|
|
if inscr.formsemestre_id == formsemestre_id
|
|
)
|
|
inscr.etat = etat_new
|
|
db.session.add(inscr)
|
|
Scolog.logdb(method=operation_method, etudid=etudid)
|
|
# Efface les évènements
|
|
for event in ScolarEvent.query.filter_by(
|
|
etudid=etudid, formsemestre_id=formsemestre_id, event_type=event_type
|
|
):
|
|
db.session.delete(event)
|
|
|
|
db.session.commit()
|
|
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
|
|
|
flash(f"{operation_name} annulée.")
|
|
return flask.redirect(
|
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
|
)
|
|
|
|
|
|
@bp.route("/etudident_create_form", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@scodoc7func
|
|
def etudident_create_form():
|
|
"formulaire creation individuelle etudiant"
|
|
return _etudident_create_or_edit_form(edit=False)
|
|
|
|
|
|
@bp.route("/etudident_edit_form", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@scodoc7func
|
|
def etudident_edit_form():
|
|
"formulaire edition individuelle etudiant"
|
|
if not current_user.has_permission(Permission.ViewEtudData):
|
|
raise ScoPermissionDenied()
|
|
return _etudident_create_or_edit_form(edit=True)
|
|
|
|
|
|
def _validate_date_naissance(val: str, field) -> bool:
|
|
"vrai si date saisie valide (peut être vide)"
|
|
if not val:
|
|
return True
|
|
try:
|
|
date_naissance = scu.convert_fr_date(val)
|
|
except ScoValueError:
|
|
return False
|
|
return date_naissance < datetime.datetime.now()
|
|
|
|
|
|
def _etudident_create_or_edit_form(edit):
|
|
"Le formulaire HTML"
|
|
H = [html_sco_header.sco_header()]
|
|
F = html_sco_header.sco_footer()
|
|
vals = scu.get_request_args()
|
|
etudid = vals.get("etudid", None)
|
|
cnx = ndb.GetDBConnexion()
|
|
descr = []
|
|
if not edit:
|
|
# creation nouvel etudiant
|
|
initvalues = {}
|
|
submitlabel = "Ajouter cet étudiant"
|
|
H.append(
|
|
"""<h2>Création d'un étudiant</h2>
|
|
<p class="warning">En général, il est <b>recommandé</b> d'importer les
|
|
étudiants depuis Apogée ou via un fichier Excel (menu <b>Inscriptions</b>
|
|
dans le semestre).
|
|
</p>
|
|
<p>
|
|
N'utilisez ce formulaire au cas par cas que <b>pour les cas particuliers</b>
|
|
ou si votre établissement n'utilise pas d'autre logiciel de gestion des
|
|
inscriptions.
|
|
</p>
|
|
<p class"warning"><em>L'étudiant créé ne sera pas inscrit.
|
|
Pensez à l'inscrire dans un semestre !</em></p>
|
|
"""
|
|
)
|
|
else:
|
|
# edition donnees d'un etudiant existant
|
|
# setup form init values
|
|
if not etudid:
|
|
raise ValueError("missing etudid parameter")
|
|
etud_o: Identite = Identite.get_etud(etudid)
|
|
descr.append(("etudid", {"default": etudid, "input_type": "hidden"}))
|
|
H.append(f"""<h2>Modification des données de {etud_o.html_link_fiche()}</h2>""")
|
|
initvalues = sco_etud.etudident_list(cnx, {"etudid": etudid})
|
|
assert len(initvalues) == 1
|
|
initvalues = initvalues[0]
|
|
submitlabel = "Modifier les données"
|
|
|
|
vals = scu.get_request_args()
|
|
nom = vals.get("nom", None)
|
|
if nom is None:
|
|
nom = initvalues.get("nom", None)
|
|
if nom is None:
|
|
infos = []
|
|
else:
|
|
prenom = vals.get("prenom", "")
|
|
if vals.get("tf_submitted", False) and not prenom:
|
|
prenom = initvalues.get("prenom", "")
|
|
infos = sco_portal_apogee.get_infos_apogee(nom, prenom)
|
|
|
|
if infos:
|
|
formatted_infos = [
|
|
"""
|
|
<script type="text/javascript">
|
|
function copy_nip(nip) {
|
|
document.tf.code_nip.value = nip;
|
|
}
|
|
</script>
|
|
<ol>"""
|
|
]
|
|
nanswers = len(infos)
|
|
nmax = 10 # nb max de reponse montrées
|
|
infos = infos[:nmax]
|
|
for i in infos:
|
|
formatted_infos.append("<li><ul>")
|
|
for k in i.keys():
|
|
if k != "nip":
|
|
item = "<li>%s : %s</li>" % (k, i[k])
|
|
else:
|
|
item = (
|
|
'<li><form>%s : %s <input type="button" value="copier ce code" onmousedown="copy_nip(%s);"/></form></li>'
|
|
% (k, i[k], i[k])
|
|
)
|
|
formatted_infos.append(item)
|
|
|
|
formatted_infos.append("</ul></li>")
|
|
formatted_infos.append("</ol>")
|
|
m = "%d étudiants trouvés" % nanswers
|
|
if len(infos) != nanswers:
|
|
m += " (%d montrés)" % len(infos)
|
|
A = """<div class="infoapogee">
|
|
<h5>Informations Apogée</h5>
|
|
<p>%s</p>
|
|
%s
|
|
</div>""" % (
|
|
m,
|
|
"\n".join(formatted_infos),
|
|
)
|
|
else:
|
|
A = """<div class="infoapogee"><p>Pas d'informations d'Apogée</p></div>"""
|
|
|
|
require_ine = sco_preferences.get_preference("always_require_ine")
|
|
|
|
descr += [
|
|
("adm_id", {"input_type": "hidden"}),
|
|
("nom", {"size": 25, "title": "Nom", "allow_null": False}),
|
|
("nom_usuel", {"size": 25, "title": "Nom usuel", "allow_null": True}),
|
|
(
|
|
"prenom",
|
|
{
|
|
"size": 25,
|
|
"title": "Prénom",
|
|
"allow_null": scu.CONFIG.ALLOW_NULL_PRENOM,
|
|
},
|
|
),
|
|
(
|
|
"civilite",
|
|
{
|
|
"input_type": "menu",
|
|
"labels": ["Homme", "Femme", "Autre/neutre"],
|
|
"allowed_values": ["M", "F", "X"],
|
|
"title": "Civilité",
|
|
},
|
|
),
|
|
(
|
|
"prenom_etat_civil",
|
|
{
|
|
"size": 25,
|
|
"title": "Prénom (état-civil)",
|
|
"allow_null": True,
|
|
"explanation": "Si précisé, remplace le prénom d'usage dans les documents officiels",
|
|
},
|
|
),
|
|
(
|
|
"civilite_etat_civil",
|
|
{
|
|
"input_type": "menu",
|
|
"labels": ["(identique à civilité)", "Homme", "Femme", "Autre/neutre"],
|
|
"allowed_values": ["", "M", "F", "X"],
|
|
"title": "Civilité (état-civil)",
|
|
"explanation": "Si précisé: remplace la civilité d'usage dans les documents officiels",
|
|
},
|
|
),
|
|
(
|
|
"date_naissance",
|
|
{
|
|
"title": "Date de naissance",
|
|
"input_type": "date",
|
|
"explanation": "j/m/a",
|
|
"validator": _validate_date_naissance,
|
|
},
|
|
),
|
|
("lieu_naissance", {"title": "Lieu de naissance", "size": 32}),
|
|
("dept_naissance", {"title": "Département de naissance", "size": 5}),
|
|
("nationalite", {"size": 25, "title": "Nationalité"}),
|
|
(
|
|
"statut",
|
|
{
|
|
"size": 25,
|
|
"title": "Statut",
|
|
"explanation": '("salarie", ...) inutilisé par ScoDoc',
|
|
},
|
|
),
|
|
(
|
|
"boursier",
|
|
{
|
|
"input_type": "boolcheckbox",
|
|
"labels": ["non", "oui"],
|
|
"title": "Boursier ?",
|
|
"explanation": "actuellement",
|
|
},
|
|
),
|
|
(
|
|
"annee",
|
|
{
|
|
"size": 5,
|
|
"title": "Année admission IUT",
|
|
"type": "int",
|
|
"allow_null": False,
|
|
"explanation": "année 1ere inscription (obligatoire)",
|
|
},
|
|
),
|
|
#
|
|
("sep", {"input_type": "separator", "title": "Scolarité antérieure:"}),
|
|
("bac", {"size": 32, "explanation": "série du bac (S, STI, STT, ...)"}),
|
|
(
|
|
"specialite",
|
|
{
|
|
"size": 25,
|
|
"title": "Spécialité",
|
|
"explanation": "spécialité bac: SVT M, GENIE ELECTRONIQUE, ...",
|
|
},
|
|
),
|
|
(
|
|
"annee_bac",
|
|
{
|
|
"size": 5,
|
|
"title": "Année bac",
|
|
"type": "int",
|
|
"min_value": 1945,
|
|
"max_value": datetime.date.today().year + 1,
|
|
"explanation": "année obtention du bac",
|
|
},
|
|
),
|
|
(
|
|
"math",
|
|
{
|
|
"size": 3,
|
|
"title": "Note de mathématiques",
|
|
"explanation": "note sur 20 en terminale",
|
|
},
|
|
),
|
|
(
|
|
"physique",
|
|
{
|
|
"size": 3,
|
|
"title": "Note de physique",
|
|
"explanation": "note sur 20 en terminale",
|
|
},
|
|
),
|
|
(
|
|
"anglais",
|
|
{
|
|
"size": 3,
|
|
"title": "Note d'anglais",
|
|
"explanation": "note sur 20 en terminale",
|
|
},
|
|
),
|
|
(
|
|
"francais",
|
|
{
|
|
"size": 3,
|
|
"title": "Note de français",
|
|
"explanation": "note sur 20 obtenue au bac",
|
|
},
|
|
),
|
|
(
|
|
"type_admission",
|
|
{
|
|
"input_type": "menu",
|
|
"title": "Voie d'admission",
|
|
"allowed_values": scu.TYPES_ADMISSION,
|
|
},
|
|
),
|
|
(
|
|
"boursier_prec",
|
|
{
|
|
"input_type": "boolcheckbox",
|
|
"labels": ["non", "oui"],
|
|
"title": "Boursier ?",
|
|
"explanation": "dans le cycle précédent (lycée)",
|
|
},
|
|
),
|
|
(
|
|
"rang",
|
|
{
|
|
"size": 1,
|
|
"type": "int",
|
|
"title": "Position établissement",
|
|
"explanation": "rang de notre établissement dans les voeux du candidat (si connu)",
|
|
},
|
|
),
|
|
(
|
|
"qualite",
|
|
{
|
|
"size": 3,
|
|
"type": "float",
|
|
"title": "Qualité",
|
|
"explanation": "Note de qualité attribuée au dossier (par le jury d'adm.)",
|
|
},
|
|
),
|
|
(
|
|
"decision",
|
|
{
|
|
"input_type": "menu",
|
|
"title": "Décision",
|
|
"allowed_values": [
|
|
"ADMIS",
|
|
"ATTENTE 1",
|
|
"ATTENTE 2",
|
|
"ATTENTE 3",
|
|
"REFUS",
|
|
"?",
|
|
],
|
|
},
|
|
),
|
|
(
|
|
"score",
|
|
{
|
|
"size": 3,
|
|
"type": "float",
|
|
"title": "Score",
|
|
"explanation": "score calculé lors de l'admission",
|
|
},
|
|
),
|
|
(
|
|
"classement",
|
|
{
|
|
"size": 3,
|
|
"type": "int",
|
|
"title": "Classement",
|
|
"explanation": "Classement par le jury d'admission (de 1 à N)",
|
|
},
|
|
),
|
|
("apb_groupe", {"size": 15, "title": "Groupe APB ou PS"}),
|
|
(
|
|
"apb_classement_gr",
|
|
{
|
|
"size": 3,
|
|
"type": "int",
|
|
"title": "Classement",
|
|
"explanation": "Classement par le jury dans le groupe ABP ou PS (de 1 à Ng)",
|
|
},
|
|
),
|
|
("rapporteur", {"size": 50, "title": "Enseignant rapporteur"}),
|
|
(
|
|
"commentaire",
|
|
{
|
|
"input_type": "textarea",
|
|
"rows": 4,
|
|
"cols": 50,
|
|
"title": "Note du rapporteur",
|
|
},
|
|
),
|
|
("nomlycee", {"size": 20, "title": "Lycée d'origine"}),
|
|
("villelycee", {"size": 15, "title": "Commune du lycée"}),
|
|
("codepostallycee", {"size": 15, "title": "Code Postal lycée"}),
|
|
(
|
|
"codelycee",
|
|
{
|
|
"size": 15,
|
|
"title": "Code Lycée",
|
|
"explanation": "Code national établissement du lycée ou établissement d'origine",
|
|
},
|
|
),
|
|
("sep", {"input_type": "separator", "title": "Codes Apogée: (optionnels)"}),
|
|
(
|
|
"code_nip",
|
|
{
|
|
"size": 25,
|
|
"title": "Numéro NIP",
|
|
"allow_null": True,
|
|
"explanation": "numéro identité étudiant (Apogée)",
|
|
},
|
|
),
|
|
(
|
|
"code_ine",
|
|
{
|
|
"size": 25,
|
|
"title": "Numéro INE",
|
|
"allow_null": not require_ine,
|
|
"explanation": "numéro INE",
|
|
},
|
|
),
|
|
(
|
|
"dont_check_homonyms",
|
|
{
|
|
"title": "Autoriser les homonymes",
|
|
"input_type": "boolcheckbox",
|
|
"explanation": "ne vérifie pas les noms et prénoms proches",
|
|
},
|
|
),
|
|
]
|
|
initvalues["dont_check_homonyms"] = False
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
scu.get_request_args(),
|
|
descr,
|
|
submitlabel=submitlabel,
|
|
cancelbutton="Re-interroger Apogee",
|
|
initvalues=initvalues,
|
|
)
|
|
if tf[0] == 0:
|
|
return "\n".join(H) + tf[1] + "<p>" + A + F
|
|
elif tf[0] == -1:
|
|
return "\n".join(H) + tf[1] + "<p>" + A + F
|
|
# return '\n'.join(H) + '<h4>annulation</h4>' + F
|
|
else:
|
|
# form submission
|
|
if edit:
|
|
etudid = tf[2]["etudid"]
|
|
else:
|
|
etudid = None
|
|
ok, homonyms = sco_etud.check_nom_prenom_homonyms(
|
|
nom=tf[2]["nom"], prenom=tf[2]["prenom"], etudid=etudid
|
|
)
|
|
nb_homonyms = len(homonyms)
|
|
if not ok:
|
|
return (
|
|
"\n".join(H)
|
|
+ tf_error_message("Nom ou prénom invalide")
|
|
+ tf[1]
|
|
+ "<p>"
|
|
+ A
|
|
+ F
|
|
)
|
|
if not tf[2]["dont_check_homonyms"] and nb_homonyms > 0:
|
|
homonyms_html = f"""
|
|
<div class="homonyms"
|
|
style="border-radius: 8px; border: 1px solid black; background-color: #fdd6ad; padding: 8px; max-width: 80%;">
|
|
<div><b>Homonymes</b> (dans tous les départements)</div>
|
|
<ul>
|
|
<li>{'</li><li>'.join( [ '<b style="margin-right: 2em;">' + e.departement.acronym + "</b>" + e.html_link_fiche() for e in homonyms ])}
|
|
</ul>
|
|
</div>
|
|
"""
|
|
return (
|
|
"\n".join(H)
|
|
+ tf_error_message(
|
|
"""Attention: il y a déjà un étudiant portant des noms et prénoms proches
|
|
(voir liste en bas de page).
|
|
Vous pouvez forcer la présence d'un homonyme en cochant
|
|
"autoriser les homonymes" en bas du formulaire.
|
|
"""
|
|
)
|
|
+ tf[1]
|
|
+ "<p>"
|
|
+ A
|
|
+ homonyms_html
|
|
+ F
|
|
)
|
|
tf[2]["date_naissance"] = (
|
|
scu.convert_fr_date(tf[2]["date_naissance"])
|
|
if tf[2]["date_naissance"]
|
|
else None
|
|
)
|
|
if not edit:
|
|
etud = sco_etud.create_etud(cnx, args=tf[2])
|
|
etudid = etud["etudid"]
|
|
else:
|
|
# modif d'un etudiant
|
|
etud_o.from_dict(tf[2])
|
|
admission = etud_o.admission
|
|
if admission is None:
|
|
# ? ne devrait pas arriver mais...
|
|
admission = Admission()
|
|
etud_o.admission = admission
|
|
admission.from_dict(tf[2])
|
|
db.session.commit()
|
|
|
|
etud = sco_etud.etudident_list(cnx, {"etudid": etud_o.id})[0]
|
|
sco_etud.fill_etuds_info([etud])
|
|
# Inval semesters with this student:
|
|
to_inval = [s["formsemestre_id"] for s in etud["sems"]]
|
|
for formsemestre_id in to_inval:
|
|
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
|
#
|
|
return flask.redirect(
|
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
|
)
|
|
|
|
|
|
@bp.route("/etud_copy_in_other_dept/<int:etudid>", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(
|
|
Permission.ScoView
|
|
) # il faut aussi EtudInscrit dans le nouveau dept
|
|
def etud_copy_in_other_dept(etudid: int):
|
|
"""Crée une copie de l'étudiant (avec ses adresses et codes) dans un autre département
|
|
et l'inscrit à un formsemestre
|
|
"""
|
|
etud = Identite.get_etud(etudid)
|
|
if request.method == "POST":
|
|
action = request.form.get("action")
|
|
if action == "cancel":
|
|
return flask.redirect(
|
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
|
)
|
|
try:
|
|
formsemestre_id = int(request.form.get("formsemestre_id"))
|
|
except ValueError:
|
|
log("etud_copy_in_other_dept: invalid formsemestre_id")
|
|
abort(404, description="formsemestre_id invalide")
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
if not current_user.has_permission(
|
|
Permission.EtudInscrit, formsemestre.departement.acronym
|
|
):
|
|
raise ScoPermissionDenied("non autorisé")
|
|
new_etud = etud.clone(new_dept_id=formsemestre.dept_id)
|
|
db.session.commit()
|
|
# Attention: change le département pour opérer dans le nouveau
|
|
# avec les anciennes fonctions ScoDoc7
|
|
orig_dept = g.scodoc_dept
|
|
try:
|
|
app.set_sco_dept(formsemestre.departement.acronym, open_cnx=False)
|
|
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
|
|
formsemestre.id,
|
|
new_etud.id,
|
|
method="etud_copy_in_other_dept",
|
|
dept_id=formsemestre.dept_id,
|
|
)
|
|
finally:
|
|
app.set_sco_dept(orig_dept, open_cnx=False)
|
|
flash(f"Etudiant dupliqué et inscrit en {formsemestre.departement.acronym}")
|
|
# Attention, ce redirect change de département !
|
|
return flask.redirect(
|
|
url_for(
|
|
"scolar.fiche_etud",
|
|
scodoc_dept=formsemestre.departement.acronym,
|
|
etudid=new_etud.id,
|
|
)
|
|
)
|
|
departements = {
|
|
dept.id: dept
|
|
for dept in Departement.query.order_by(Departement.acronym)
|
|
if current_user.has_permission(Permission.EtudInscrit, dept.acronym)
|
|
and dept.id != etud.dept_id
|
|
}
|
|
formsemestres_by_dept = {
|
|
dept.id: dept.formsemestres.filter_by(etat=True)
|
|
.filter(FormSemestre.modalite != "EXT")
|
|
.order_by(FormSemestre.date_debut, FormSemestre.semestre_id)
|
|
.all()
|
|
for dept in departements.values()
|
|
}
|
|
return render_template(
|
|
"scolar/etud_copy_in_other_dept.j2",
|
|
departements=departements,
|
|
etud=etud,
|
|
formsemestres_by_dept=formsemestres_by_dept,
|
|
)
|
|
|
|
|
|
@bp.route("/etudident_delete/<int:etudid>", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@scodoc7func
|
|
def etudident_delete(etudid: int = -1, dialog_confirmed=False):
|
|
"Delete a student"
|
|
etud = Identite.get_etud(etudid)
|
|
if not dialog_confirmed:
|
|
return scu.confirm_dialog(
|
|
f"""<h2>Confirmer la suppression de l'étudiant <b>{etud.nomprenom}</b> ?</h2>
|
|
</p>
|
|
<p style="top-margin: 2ex; bottom-margin: 2ex;">Prenez le temps de vérifier
|
|
que vous devez vraiment supprimer cet étudiant !
|
|
</p>
|
|
<p>Cette opération <font color="red"><b>irréversible</b></font>
|
|
efface toute trace de l'étudiant: inscriptions, <b>notes</b>, absences...
|
|
dans <b>tous les semestres</b> qu'il a fréquenté.
|
|
</p>
|
|
<p>Dans la plupart des cas, vous avez seulement besoin de le <b>désinscrire</b>
|
|
d'un semestre ! (pour cela, passez par sa fiche, menu associé au semestre)</p>
|
|
|
|
<p><a class="stdlink" href="{url_for(
|
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
|
)}">Vérifier la fiche de {etud.nomprenom}</a>
|
|
</p>""",
|
|
dest_url="",
|
|
cancel_url=url_for(
|
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
|
),
|
|
OK="Supprimer définitivement cet étudiant",
|
|
parameters={"etudid": etudid},
|
|
)
|
|
log(f"etudident_delete: {etud}")
|
|
formsemestre_ids_to_inval = [
|
|
ins.formsemestre_id for ins in etud.formsemestre_inscriptions
|
|
]
|
|
|
|
# delete in all tables !
|
|
# c'est l'ancienne façon de gérer les cascades dans notre pseudo-ORM :)
|
|
tables = [
|
|
"notes_appreciations",
|
|
"scolar_autorisation_inscription",
|
|
"scolar_formsemestre_validation",
|
|
"apc_validation_rcue",
|
|
"apc_validation_annee",
|
|
"scolar_events",
|
|
"notes_notes_log",
|
|
"notes_notes",
|
|
"notes_moduleimpl_inscription",
|
|
"notes_formsemestre_inscription",
|
|
"group_membership",
|
|
"etud_annotations",
|
|
"scolog",
|
|
"adresse",
|
|
"absences",
|
|
"absences_notifications",
|
|
"billet_absence",
|
|
]
|
|
for table in tables:
|
|
db.session.execute(
|
|
sa.text(f"""delete from {table} where etudid=:etudid"""), {"etudid": etudid}
|
|
)
|
|
db.session.delete(etud)
|
|
db.session.commit()
|
|
# Inval semestres où il était inscrit:
|
|
for formsemestre_id in formsemestre_ids_to_inval:
|
|
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
|
flash("Étudiant supprimé !")
|
|
return flask.redirect(url_for("scolar.index_html", scodoc_dept=g.scodoc_dept))
|
|
|
|
|
|
@bp.route("/check_group_apogee")
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@scodoc7func
|
|
def check_group_apogee(group_id, etat=None, fix=False, fixmail=False):
|
|
"""Verification des codes Apogee et mail de tout un groupe.
|
|
Si fix == True, change les codes avec Apogée.
|
|
|
|
XXX A re-écrire pour API 2: prendre liste dans l'étape et vérifier à partir de cela.
|
|
"""
|
|
etat = etat or None
|
|
members, group, _, sem, _ = sco_groups.get_group_infos(group_id, etat=etat)
|
|
formsemestre_id = group["formsemestre_id"]
|
|
|
|
cnx = ndb.GetDBConnexion()
|
|
H = [
|
|
html_sco_header.html_sem_header(
|
|
"Étudiants du %s" % (group["group_name"] or "semestre")
|
|
),
|
|
'<table class="sortable" id="listegroupe">',
|
|
"<tr><th>Nom</th><th>Nom usuel</th><th>Prénom</th><th>Mail</th><th>NIP (ScoDoc)</th><th>Apogée</th></tr>",
|
|
]
|
|
nerrs = 0 # nombre d'anomalies détectées
|
|
nfix = 0 # nb codes changes
|
|
nmailmissing = 0 # nb etuds sans mail
|
|
for t in members:
|
|
nom, nom_usuel, prenom, etudid, email, code_nip = (
|
|
t["nom"],
|
|
t["nom_usuel"],
|
|
t["prenom"],
|
|
t["etudid"],
|
|
t["email"],
|
|
t["code_nip"],
|
|
)
|
|
infos = sco_portal_apogee.get_infos_apogee(nom, prenom)
|
|
if not infos:
|
|
info_apogee = (
|
|
'<b>Pas d\'information</b> (<a href="etudident_edit_form?etudid=%s">Modifier identité</a>)'
|
|
% etudid
|
|
)
|
|
nerrs += 1
|
|
else:
|
|
if len(infos) == 1:
|
|
nip_apogee = infos[0]["nip"]
|
|
if code_nip != nip_apogee:
|
|
if fix:
|
|
# Update database
|
|
sco_etud.identite_edit(
|
|
cnx,
|
|
args={"etudid": etudid, "code_nip": nip_apogee},
|
|
)
|
|
info_apogee = (
|
|
'<span style="color:green">copié %s</span>' % nip_apogee
|
|
)
|
|
nfix += 1
|
|
else:
|
|
info_apogee = '<span style="color:red">%s</span>' % nip_apogee
|
|
nerrs += 1
|
|
else:
|
|
info_apogee = "ok"
|
|
else:
|
|
info_apogee = (
|
|
'<b>%d correspondances</b> (<a href="etudident_edit_form?etudid=%s">Choisir</a>)'
|
|
% (len(infos), etudid)
|
|
)
|
|
nerrs += 1
|
|
# check mail
|
|
if email:
|
|
mailstat = "ok"
|
|
else:
|
|
if fixmail and len(infos) == 1 and "mail" in infos[0]:
|
|
mail_apogee = infos[0]["mail"]
|
|
adrs = sco_etud.adresse_list(cnx, {"etudid": etudid})
|
|
if adrs:
|
|
adr = adrs[0] # modif adr existante
|
|
args = {"adresse_id": adr["adresse_id"], "email": mail_apogee}
|
|
sco_etud.adresse_edit(cnx, args=args, disable_notify=True)
|
|
else:
|
|
# creation adresse
|
|
args = {"etudid": etudid, "email": mail_apogee}
|
|
sco_etud.adresse_create(cnx, args=args)
|
|
mailstat = '<span style="color:green">copié</span>'
|
|
else:
|
|
mailstat = "inconnu"
|
|
nmailmissing += 1
|
|
H.append(
|
|
'<tr><td><a href="%s">%s</a></td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>'
|
|
% (
|
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
|
nom,
|
|
nom_usuel,
|
|
prenom,
|
|
mailstat,
|
|
code_nip,
|
|
info_apogee,
|
|
)
|
|
)
|
|
H.append("</table>")
|
|
H.append("<ul>")
|
|
if nfix:
|
|
H.append("<li><b>%d</b> codes modifiés</li>" % nfix)
|
|
H.append("<li>Codes NIP: <b>%d</b> anomalies détectées</li>" % nerrs)
|
|
H.append("<li>Adresse mail: <b>%d</b> étudiants sans adresse</li>" % nmailmissing)
|
|
H.append("</ul>")
|
|
H.append(
|
|
"""
|
|
<form method="get" action="%s">
|
|
<input type="hidden" name="formsemestre_id" value="%s"/>
|
|
<input type="hidden" name="group_id" value="%s"/>
|
|
<input type="hidden" name="etat" value="%s"/>
|
|
<input type="hidden" name="fix" value="1"/>
|
|
<input type="submit" value="Mettre à jour les codes NIP depuis Apogée"/>
|
|
</form>
|
|
<p><a href="Notes/formsemestre_status?formsemestre_id=%s"> Retour au semestre</a>
|
|
"""
|
|
% (
|
|
request.base_url,
|
|
formsemestre_id,
|
|
scu.strnone(group_id),
|
|
scu.strnone(etat),
|
|
formsemestre_id,
|
|
)
|
|
)
|
|
H.append(
|
|
"""
|
|
<form method="get" action="%s">
|
|
<input type="hidden" name="formsemestre_id" value="%s"/>
|
|
<input type="hidden" name="group_id" value="%s"/>
|
|
<input type="hidden" name="etat" value="%s"/>
|
|
<input type="hidden" name="fixmail" value="1"/>
|
|
<input type="submit" value="Renseigner les e-mail manquants (adresse institutionnelle)"/>
|
|
</form>
|
|
<p><a href="Notes/formsemestre_status?formsemestre_id=%s"> Retour au semestre</a>
|
|
"""
|
|
% (
|
|
request.base_url,
|
|
formsemestre_id,
|
|
scu.strnone(group_id),
|
|
scu.strnone(etat),
|
|
formsemestre_id,
|
|
)
|
|
)
|
|
|
|
return "\n".join(H) + html_sco_header.sco_footer()
|
|
|
|
|
|
@bp.route("/export_etudiants_courants")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def export_etudiants_courants():
|
|
"""Table export de tous les étudiants des formsemestres en cours."""
|
|
fmt = request.args.get("fmt", "html")
|
|
departement = Departement.query.get(g.scodoc_dept_id)
|
|
if not departement:
|
|
raise ScoValueError("département invalide")
|
|
formsemestres = FormSemestre.get_dept_formsemestres_courants(departement).all()
|
|
if not formsemestres:
|
|
raise ScoValueError("aucun semestre courant !")
|
|
table = list_etuds.table_etudiants_courants(formsemestres)
|
|
if fmt.startswith("xls"):
|
|
return scu.send_file(
|
|
table.excel(),
|
|
f"""{formsemestres[0].departement.acronym}-etudiants-{
|
|
datetime.datetime.now().strftime("%Y-%m-%dT%Hh%M")}""",
|
|
scu.XLSX_SUFFIX,
|
|
mime=scu.XLSX_MIMETYPE,
|
|
)
|
|
elif fmt == "html":
|
|
return render_template(
|
|
"scolar/export_etudiants_courants.j2", sco=ScoData(), table=table
|
|
)
|
|
else:
|
|
raise ScoValueError("invalid fmt value")
|
|
|
|
|
|
@bp.route("/form_students_import_excel", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@scodoc7func
|
|
def form_students_import_excel(formsemestre_id=None):
|
|
"formulaire import xls"
|
|
formsemestre_id = int(formsemestre_id) if formsemestre_id else None
|
|
if formsemestre_id:
|
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
dest_url = url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
else:
|
|
sem = None
|
|
dest_url = url_for("scolar.index_html", scodoc_dept=g.scodoc_dept)
|
|
if sem and not sem["etat"]:
|
|
raise ScoValueError("Modification impossible: semestre verrouille")
|
|
H = [
|
|
html_sco_header.sco_header(page_title="Import etudiants"),
|
|
"""<h2 class="formsemestre">Téléchargement d\'une nouvelle liste d\'etudiants</h2>
|
|
<div style="color: red">
|
|
<p>A utiliser pour importer de <b>nouveaux</b> étudiants (typiquement au
|
|
<b>premier semestre</b>).</p>
|
|
<p>Si les étudiants à inscrire sont déjà dans un autre
|
|
semestre, utiliser le menu "<em>Inscriptions (passage des étudiants)
|
|
depuis d'autres semestres</em> à partir du semestre destination.
|
|
</p>
|
|
<p>Si vous avez un portail Apogée, il est en général préférable d'importer les
|
|
étudiants depuis Apogée, via le menu "<em>Synchroniser avec étape Apogée</em>".
|
|
</p>
|
|
</div>
|
|
<p>
|
|
L'opération se déroule en deux étapes. Dans un premier temps,
|
|
vous téléchargez une feuille Excel type. Vous devez remplir
|
|
cette feuille, une ligne décrivant chaque étudiant. Ensuite,
|
|
vous indiquez le nom de votre fichier dans la case "Fichier Excel"
|
|
ci-dessous, et cliquez sur "Télécharger" pour envoyer au serveur
|
|
votre liste.
|
|
</p>
|
|
""",
|
|
] # '
|
|
if sem:
|
|
H.append(
|
|
"""<p style="color: red">Les étudiants importés seront inscrits dans
|
|
le semestre <b>%s</b></p>"""
|
|
% sem["titremois"]
|
|
)
|
|
else:
|
|
H.append(
|
|
f"""
|
|
<p>Pour inscrire directement les étudiants dans un semestre de
|
|
formation, il suffit d'indiquer le code de ce semestre
|
|
(qui doit avoir été créé au préalable).
|
|
<a class="stdlink" href="{
|
|
url_for("scolar.index_html", showcodes=1, scodoc_dept=g.scodoc_dept)
|
|
}">Cliquez ici pour afficher les codes</a>
|
|
</p>
|
|
"""
|
|
)
|
|
|
|
H.append("""<ol><li>""")
|
|
if formsemestre_id:
|
|
H.append(
|
|
"""
|
|
<a class="stdlink" href="import_generate_excel_sample?with_codesemestre=0">
|
|
"""
|
|
)
|
|
else:
|
|
H.append("""<a class="stdlink" href="import_generate_excel_sample">""")
|
|
H.append(
|
|
"""Obtenir la feuille excel à remplir</a></li>
|
|
<li>"""
|
|
)
|
|
|
|
F = html_sco_header.sco_footer()
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
scu.get_request_args(),
|
|
(
|
|
(
|
|
"csvfile",
|
|
{"title": "Fichier Excel:", "input_type": "file", "size": 40},
|
|
),
|
|
(
|
|
"check_homonyms",
|
|
{
|
|
"title": "Vérifier les homonymes",
|
|
"input_type": "boolcheckbox",
|
|
"explanation": "arrète l'importation si plus de 10% d'homonymes",
|
|
},
|
|
),
|
|
(
|
|
"require_ine",
|
|
{
|
|
"title": "Importer INE",
|
|
"input_type": "boolcheckbox",
|
|
"explanation": "n'importe QUE les étudiants avec nouveau code INE",
|
|
},
|
|
),
|
|
("formsemestre_id", {"input_type": "hidden"}),
|
|
),
|
|
initvalues={"check_homonyms": True, "require_ine": False},
|
|
submitlabel="Télécharger",
|
|
)
|
|
S = [
|
|
"""<hr/><p>Le fichier Excel décrivant les étudiants doit comporter les colonnes suivantes.
|
|
<p>Les colonnes peuvent être placées dans n'importe quel ordre, mais
|
|
le <b>titre</b> exact (tel que ci-dessous) doit être sur la première ligne.
|
|
</p>
|
|
<p>
|
|
Les champs avec un astérisque (*) doivent être présents (nulls non autorisés).
|
|
</p>
|
|
|
|
|
|
<p>
|
|
<table>
|
|
<tr><td><b>Attribut</b></td><td><b>Type</b></td><td><b>Description</b></td></tr>"""
|
|
]
|
|
for t in sco_import_etuds.sco_import_format(
|
|
with_codesemestre=(formsemestre_id is None)
|
|
):
|
|
if int(t[3]):
|
|
ast = ""
|
|
else:
|
|
ast = "*"
|
|
S.append(
|
|
"<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>"
|
|
% (t[0], t[1], t[4], ast)
|
|
)
|
|
if tf[0] == 0:
|
|
return "\n".join(H) + tf[1] + "</li></ol>" + "\n".join(S) + F
|
|
elif tf[0] == -1:
|
|
return flask.redirect(dest_url)
|
|
else:
|
|
return sco_import_etuds.students_import_excel(
|
|
tf[2]["csvfile"],
|
|
formsemestre_id=int(formsemestre_id) if formsemestre_id else None,
|
|
check_homonyms=tf[2]["check_homonyms"],
|
|
require_ine=tf[2]["require_ine"],
|
|
)
|
|
|
|
|
|
@bp.route("/import_generate_excel_sample")
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@scodoc7func
|
|
def import_generate_excel_sample(with_codesemestre="1"):
|
|
"une feuille excel pour importation etudiants"
|
|
if with_codesemestre:
|
|
with_codesemestre = int(with_codesemestre)
|
|
else:
|
|
with_codesemestre = 0
|
|
fmt = sco_import_etuds.sco_import_format()
|
|
data = sco_import_etuds.sco_import_generate_excel_sample(
|
|
fmt, with_codesemestre, exclude_cols=["photo_filename"]
|
|
)
|
|
return scu.send_file(
|
|
data, "ImportEtudiants", scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE
|
|
)
|
|
|
|
|
|
# --- Données admission
|
|
@bp.route("/import_generate_admission_sample")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def import_generate_admission_sample(formsemestre_id):
|
|
"une feuille excel pour importation données admissions"
|
|
group = sco_groups.get_group(sco_groups.get_default_group(formsemestre_id))
|
|
fmt = sco_import_etuds.sco_import_format()
|
|
data = sco_import_etuds.sco_import_generate_excel_sample(
|
|
fmt,
|
|
only_tables=["identite", "admissions", "adresse"],
|
|
exclude_cols=["nationalite", "foto", "photo_filename"],
|
|
group_ids=[group["group_id"]],
|
|
)
|
|
return scu.send_file(
|
|
data, "AdmissionEtudiants", scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE
|
|
)
|
|
# return sco_excel.send_excel_file(data, "AdmissionEtudiants" + scu.XLSX_SUFFIX)
|
|
|
|
|
|
# --- Données admission depuis fichier excel (version nov 2016)
|
|
@bp.route("/form_students_import_infos_admissions", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def form_students_import_infos_admissions(formsemestre_id=None):
|
|
"formulaire import xls"
|
|
authuser = current_user
|
|
F = html_sco_header.sco_footer()
|
|
if not authuser.has_permission(Permission.EtudInscrit):
|
|
# autorise juste l'export
|
|
H = [
|
|
html_sco_header.sco_header(
|
|
page_title="Export données admissions (Parcoursup ou autre)",
|
|
),
|
|
f"""<h2 class="formsemestre">Téléchargement des informations sur l'admission
|
|
des étudiants</h2>
|
|
<p>
|
|
<a href="{ url_for('scolar.import_generate_admission_sample',
|
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id )
|
|
}">Exporter les informations de ScoDoc (classeur Excel)</a> (ce fichier
|
|
peut être ré-importé après d'éventuelles modifications)
|
|
</p>
|
|
<p class="warning">Vous n'avez pas le droit d'importer les données</p>
|
|
""",
|
|
]
|
|
return "\n".join(H) + F
|
|
|
|
# On a le droit d'importer:
|
|
H = [
|
|
html_sco_header.sco_header(page_title="Import données admissions Parcoursup"),
|
|
f"""<h2 class="formsemestre">Téléchargement des informations sur l'admission des étudiants
|
|
depuis feuilles import Parcoursup</h2>
|
|
<div style="color: red">
|
|
<p>A utiliser pour renseigner les informations sur l'origine des étudiants (lycées, bac, etc).
|
|
Ces informations sont facultatives mais souvent utiles pour mieux connaitre les étudiants
|
|
et aussi pour effectuer des statistiques (résultats suivant le type de bac...).
|
|
Les données sont affichées sur les fiches individuelles des étudiants.
|
|
</p>
|
|
</div>
|
|
<div class="help">
|
|
<p>
|
|
Vous pouvez importer ici la feuille excel utilisée pour envoyer
|
|
le classement Parcoursup.
|
|
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.
|
|
</p>
|
|
<p>
|
|
<em>Seules les données "admission" seront modifiées
|
|
(et pas l'identité de l'étudiant).</em>
|
|
</p>
|
|
<p>
|
|
<em>Les colonnes "nom" et "prenom" sont requises,
|
|
ou bien une colonne "etudid" si la case
|
|
"Utiliser l'identifiant d'étudiant ScoDoc" est cochée.
|
|
</em>
|
|
</p>
|
|
<p>
|
|
Avant d'importer vos données, il est recommandé d'enregistrer
|
|
les informations actuelles:
|
|
<a class="stdlink" href="{
|
|
url_for("scolar.import_generate_admission_sample",
|
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
|
}">exporter les données actuelles de ScoDoc</a>
|
|
(ce fichier peut être ré-importé après d'éventuelles modifications)
|
|
</p>
|
|
</div>
|
|
""",
|
|
]
|
|
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
scu.get_request_args(),
|
|
(
|
|
(
|
|
"csvfile",
|
|
{"title": "Fichier Excel:", "input_type": "file", "size": 40},
|
|
),
|
|
(
|
|
"use_etudid",
|
|
{
|
|
"input_type": "boolcheckbox",
|
|
"title": "Utiliser l'identifiant d'étudiant ScoDoc (<tt>etudid</tt>)",
|
|
"explanation": """si cochée, utilise le code pour retrouver dans ScoDoc
|
|
les étudiants du fichier excel. Sinon, utilise les noms/prénoms.""",
|
|
},
|
|
),
|
|
(
|
|
"type_admission",
|
|
{
|
|
"title": "Type d'admission",
|
|
"explanation": "sera attribué aux étudiants modifiés par cet import n'ayant pas déjà un type",
|
|
"input_type": "menu",
|
|
"allowed_values": scu.TYPES_ADMISSION,
|
|
},
|
|
),
|
|
("formsemestre_id", {"input_type": "hidden"}),
|
|
),
|
|
submitlabel="Télécharger",
|
|
)
|
|
|
|
help_text = (
|
|
"""<p>Les colonnes importables par cette fonction sont indiquées
|
|
dans la table ci-dessous.
|
|
Seule la première feuille du classeur sera utilisée.
|
|
<div id="adm_table_description_format">
|
|
"""
|
|
+ sco_import_etuds.adm_table_description_format().html()
|
|
+ """</div>"""
|
|
)
|
|
|
|
if tf[0] == 0:
|
|
return "\n".join(H) + tf[1] + help_text + F
|
|
elif tf[0] == -1:
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
else:
|
|
return sco_import_etuds.students_import_admission(
|
|
tf[2]["csvfile"],
|
|
type_admission=tf[2]["type_admission"],
|
|
formsemestre_id=formsemestre_id,
|
|
use_etudid=tf[2]["use_etudid"],
|
|
)
|
|
|
|
|
|
@bp.route("/formsemestre_import_etud_admission")
|
|
@scodoc
|
|
@permission_required(Permission.EtudChangeAdr)
|
|
@scodoc7func
|
|
def formsemestre_import_etud_admission(
|
|
formsemestre_id=None, import_email=True, tous_courants=False
|
|
):
|
|
"""Ré-importe donnees admissions par synchro Portail Apogée.
|
|
Si tous_courants, le fait pour tous les formsemestres courants du département
|
|
"""
|
|
if tous_courants:
|
|
departement = Departement.query.get(g.scodoc_dept_id)
|
|
formsemestres = FormSemestre.get_dept_formsemestres_courants(departement)
|
|
else:
|
|
formsemestres = [FormSemestre.get_formsemestre(formsemestre_id)]
|
|
|
|
diag_by_sem = {}
|
|
for formsemestre in formsemestres:
|
|
(
|
|
etuds_no_nip,
|
|
etuds_unknown,
|
|
changed_mails,
|
|
) = sco_synchro_etuds.formsemestre_import_etud_admission(
|
|
formsemestre.id, import_identite=True, import_email=import_email
|
|
)
|
|
diag = ""
|
|
if etuds_no_nip:
|
|
diag += f"""<p>Attention: étudiants sans NIP:
|
|
{', '.join([e.html_link_fiche() for e in etuds_no_nip])}
|
|
</p>"""
|
|
|
|
if etuds_unknown:
|
|
diag += f"""<p>Attention: étudiants inconnus du portail:
|
|
{', '.join([(e.html_link_fiche() + ' (nip= ' + e.code_nip + ')')
|
|
for e in etuds_unknown])}
|
|
</p>"""
|
|
|
|
if changed_mails:
|
|
diag += """<p>Adresses mails modifiées:</p><ul>"""
|
|
for etud, old_mail in changed_mails:
|
|
diag += f"""<li>{etud.nom}: <tt>{old_mail}</tt> devient <tt>{etud.email}</tt></li>"""
|
|
diag += "</ul>"
|
|
diag_by_sem[formsemestre.id] = diag
|
|
|
|
return f"""
|
|
{ html_sco_header.html_sem_header("Ré-import données admission") }
|
|
<h3>Opération effectuée</h3>
|
|
<p>Sur le(s) semestres(s):</p>
|
|
<ul>
|
|
<li>
|
|
{ '</li><li>'.join( [(s.html_link_status() + diag_by_sem[s.id]) for s in formsemestres ]) }
|
|
</li>
|
|
</ul>
|
|
{ html_sco_header.sco_footer() }
|
|
"""
|
|
|
|
|
|
sco_publish(
|
|
"/photos_import_files_form",
|
|
sco_trombino.photos_import_files_form,
|
|
Permission.EtudChangeAdr,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/photos_generate_excel_sample",
|
|
sco_trombino.photos_generate_excel_sample,
|
|
Permission.EtudChangeAdr,
|
|
)
|
|
|
|
|
|
# --- Statistiques
|
|
@bp.route("/stat_bac")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def stat_bac(formsemestre_id):
|
|
"Renvoie statistisques sur nb d'etudiants par bac"
|
|
cnx = ndb.GetDBConnexion()
|
|
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
|
args={"formsemestre_id": formsemestre_id}
|
|
)
|
|
Bacs = {} # type bac : nb etud
|
|
for i in ins:
|
|
etud = sco_etud.etudident_list(cnx, {"etudid": i["etudid"]})[0]
|
|
typebac = "%(bac)s %(specialite)s" % etud
|
|
Bacs[typebac] = Bacs.get(typebac, 0) + 1
|
|
return Bacs
|
|
|
|
|
|
# --- Dump (assistance)
|
|
@bp.route("/sco_dump_and_send_db", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def sco_dump_and_send_db(message="", request_url="", traceback_str_base64=""):
|
|
"Send anonymized data to supervision"
|
|
|
|
r = sco_dump_db.sco_dump_and_send_db(
|
|
message, request_url, traceback_str_base64=traceback_str_base64
|
|
)
|
|
|
|
status_code = r.status_code
|
|
|
|
try:
|
|
r_msg = r.json()["message"]
|
|
except (requests.exceptions.JSONDecodeError, KeyError):
|
|
r_msg = "Erreur: code <tt>"
|
|
+status_code
|
|
+'</tt> Merci de contacter <a href="mailto:'
|
|
+scu.SCO_DEV_MAIL
|
|
+'">'
|
|
+scu.SCO_DEV_MAIL
|
|
+"</a>"
|
|
|
|
H = [html_sco_header.sco_header(page_title="Assistance technique")]
|
|
if status_code == requests.codes.OK: # pylint: disable=no-member
|
|
H.append(f"""<p>Opération effectuée.</p><p>{r_msg}</p>""")
|
|
else:
|
|
H.append(f"""<p class="warning">{r_msg}</p>""")
|
|
flash("Données envoyées au serveur d'assistance")
|
|
return "\n".join(H) + html_sco_header.sco_footer()
|
|
|
|
|
|
# --- Report form (assistance)
|
|
@bp.route("/sco_bug_report", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def sco_bug_report_form():
|
|
"Formulaire de création d'un ticket d'assistance"
|
|
|
|
form = CreateBugReport()
|
|
if request.method == "POST" and form.cancel.data: # cancel button
|
|
return flask.redirect(url_for("scolar.index_html", scodoc_dept=g.scodoc_dept))
|
|
if form.validate_on_submit():
|
|
r = sco_bug_report.sco_bug_report(
|
|
form.title.data, form.message.data, form.etab.data, form.include_dump.data
|
|
)
|
|
|
|
status_code = r.status_code
|
|
try:
|
|
r_msg = r.json()["message"]
|
|
except (requests.exceptions.JSONDecodeError, KeyError):
|
|
log(f"sco_bug_report: error {status_code}")
|
|
r_msg = f"""Erreur: code <tt>{status_code}</tt>
|
|
Merci de contacter
|
|
<a href="mailto:{scu.SCO_DEV_MAIL}">{scu.SCO_DEV_MAIL}</a>
|
|
"""
|
|
|
|
H = [html_sco_header.sco_header(page_title="Assistance technique")]
|
|
if r.status_code >= 200 and r.status_code < 300:
|
|
H.append(f"""<p>Opération effectuée.</p><p>{r_msg}</p>""")
|
|
else:
|
|
H.append(f"""<p class="warning">{r_msg}</p>""")
|
|
return "\n".join(H) + html_sco_header.sco_footer()
|
|
|
|
return render_template(
|
|
"sco_bug_report.j2",
|
|
form=form,
|
|
)
|