# -*- coding: utf-8 -*-
##############################################################################
#
# ScoDoc
#
# Copyright (c) 1999 - 2021 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: issu de ScoDoc7 / ZScolar.py
Emmanuel Viennet, 2021
"""
from __future__ import absolute_import
import sys
import traceback
import time
import string
import glob
import re
import cgi
import xml
import jaxml
# StringIO => io.StringIO or io.BytesIO for text and data respectively. #py3
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
from zipfile import ZipFile
import psycopg2
from flask import g
from flask import current_app
from config import Config
import scodoc_manager
from app.decorators import (
scodoc7func,
ScoDoc7Context,
permission_required,
admin_required,
login_required,
)
from app.views import scolar_bp as bp
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc.notes_log import log
from app.scodoc.scolog import logdb
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import (
AccessDenied,
ScoException,
ScoValueError,
ScoInvalidDateError,
ScoLockedFormError,
ScoGenError,
ScoInvalidDept,
)
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
from app.scodoc import VERSION
from app.scodoc.gen_tables import GenTable
from app.scodoc import html_sco_header
from app.scodoc import html_sidebar
from app.scodoc import imageresize
from app.scodoc import sco_import_etuds
from app.scodoc import sco_abs
from app.scodoc import sco_archives_etud
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_core
from app.scodoc import sco_debouche
from app.scodoc import sco_dept
from app.scodoc import sco_dump_db
from app.scodoc import sco_edt_cal
from app.scodoc import sco_excel
from app.scodoc import sco_find_etud
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_edit
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_groups
from app.scodoc import sco_groups_edit
from app.scodoc import sco_groups_view
from app.scodoc import sco_news
from app.scodoc import sco_page_etud
from app.scodoc import sco_parcours_dut
from app.scodoc import sco_permissions
from app.scodoc import sco_permissions_check
from app.scodoc import sco_photos
from app.scodoc import sco_portal_apogee
from app.scodoc import sco_preferences
from app.scodoc import sco_report
from app.scodoc import sco_synchro_etuds
from app.scodoc import sco_trombino
from app.scodoc import sco_trombino_tours
from app.scodoc import sco_up_to_date
from app.scodoc import sco_etud
context = ScoDoc7Context("scolar")
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)(
permission_required(permission)(scodoc7func(context)(function))
)
log.set_log_directory(Config.INSTANCE_HOME + "/log")
log("ScoDoc8 restarting...")
# --------------------------------------------------------------------
#
# SCOLARITE (/ScoDoc/ © Emmanuel Viennet 1997-2021 Version %s Logiciel libre écrit en Python. Utilise ReportLab pour générer les documents PDF, et pyExcelerator pour le traitement des documents Excel. '
% etud
]
header = html_sco_header.sco_header(
context, REQUEST, page_title="Changement adresse de %(nomprenom)s" % etud
)
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
(
("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="Valider le formulaire",
)
dest_url = scu.ScoURL() + "/ficheEtud?etudid=" + etudid
if tf[0] == 0:
return header + "\n".join(H) + tf[1] + html_sco_header.sco_footer(REQUEST)
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(dest_url)
else:
if adrs:
sco_etud.adresse_edit(cnx, args=tf[2], context=context)
else:
sco_etud.adresse_create(cnx, args=tf[2])
logdb(REQUEST, cnx, method="changeCoordonnees", etudid=etudid)
return REQUEST.RESPONSE.redirect(dest_url)
# --- Gestion des groupes:
sco_publish("/affectGroups", sco_groups_edit.affectGroups, Permission.ScoView)
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)
sco_publish("/createGroup", sco_groups.createGroup, Permission.ScoView)
sco_publish("/suppressGroup", sco_groups.suppressGroup, Permission.ScoView)
sco_publish("/group_set_name", sco_groups.group_set_name, Permission.ScoView)
sco_publish("/group_rename", sco_groups.group_rename, Permission.ScoView)
sco_publish(
"/groups_auto_repartition", sco_groups.groups_auto_repartition, Permission.ScoView
)
sco_publish("/editPartitionForm", sco_groups.editPartitionForm, Permission.ScoView)
sco_publish("/partition_delete", sco_groups.partition_delete, Permission.ScoView)
sco_publish("/partition_set_attr", sco_groups.partition_set_attr, Permission.ScoView)
sco_publish("/partition_move", sco_groups.partition_move, Permission.ScoView)
sco_publish("/partition_set_name", sco_groups.partition_set_name, Permission.ScoView)
sco_publish("/partition_rename", sco_groups.partition_rename, Permission.ScoView)
sco_publish("/partition_create", sco_groups.partition_create, Permission.ScoView)
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")
@permission_required(Permission.ScoView)
@scodoc7func(context)
def etud_photo_orig_page(context, etudid=None, REQUEST=None):
"Page with photo in orig. size"
etud = sco_etud.get_etud_info(etudid=etudid, filled=1, REQUEST=REQUEST)[0]
H = [
html_sco_header.sco_header(context, REQUEST, page_title=etud["nomprenom"]),
" Photo actuelle (%(photoloc)s):
"""
% etud,
sco_photos.etud_photo_html(
context, etud, title="photo actuelle", REQUEST=REQUEST
),
""" Le fichier ne doit pas dépasser 500Ko (recadrer l'image, format "portrait" de préférence). L'image sera automagiquement réduite pour obtenir une hauteur de 90 pixels. Erreur:' + diag + " Confirmer la suppression de la photo de %(nomprenom)s ? '
% etud
]
H.append(
""" Confirmer l'annulation de la %s ? En général, il est recommandé d'importer les étudiants depuis Apogée.
N'utilisez ce formulaire que pour les cas particuliers ou si votre établissement
n'utilise pas d'autre logiciel de gestion des inscriptions. L'étudiant créé ne sera pas inscrit.
Pensez à l'inscrire dans un semestre ! %s Pas d'informations d'Apogée " + A + F
elif tf[0] == -1:
return "\n".join(H) + tf[1] + " " + A + F
# return '\n'.join(H) + ' "
+ A
+ F
)
# log('NbHomonyms=%s' % NbHomonyms)
if not tf[2]["dont_check_homonyms"] and NbHomonyms > 0:
return (
"\n".join(H)
+ tf_error_message(
"""Attention: il y a déjà un étudiant portant des noms et prénoms proches. Vous pouvez forcer la présence d'un homonyme en cochant "autoriser les homonymes" en bas du formulaire."""
)
+ tf[1]
+ " "
+ A
+ F
)
if not edit:
etud = sco_etud.create_etud(context, cnx, args=tf[2], REQUEST=REQUEST)
etudid = etud["etudid"]
else:
# modif d'un etudiant
sco_etud.etudident_edit(cnx, tf[2], context=context, REQUEST=REQUEST)
etud = sco_etud.etudident_list(cnx, {"etudid": etudid})[0]
sco_etud.fill_etuds_info([etud])
# Inval semesters with this student:
to_inval = [s["formsemestre_id"] for s in etud["sems"]]
if to_inval:
sco_core.inval_cache(
context, formsemestre_id_list=to_inval
) # > etudident_create_or_edit
#
return REQUEST.RESPONSE.redirect("ficheEtud?etudid=" + etudid)
@bp.route("/etudident_delete")
@permission_required(Permission.ScoEtudInscrit)
@scodoc7func(context)
def etudident_delete(context, etudid, dialog_confirmed=False, REQUEST=None):
"Delete a student"
cnx = ndb.GetDBConnexion()
etuds = sco_etud.etudident_list(cnx, {"etudid": etudid})
if not etuds:
raise ScoValueError("Etudiant inexistant !")
else:
etud = etuds[0]
sco_etud.fill_etuds_info([etud])
if not dialog_confirmed:
return scu.confirm_dialog(
context,
"""Système de gestion scolarité
Dernières évolutions
" + VERSION.SCONEWS)
H.append(
'Opérations effectuées sur l'étudiant %(nomprenom)s
" % etud,
filename="log_" + scu.make_filename(etud["nomprenom"]),
html_next_section=''
% etud,
preferences=sco_preferences.SemPreferences(
context,
),
)
return tab.make_page(context, format=format, REQUEST=REQUEST)
# ---------- PAGE ACCUEIL (listes) --------------
@bp.route("/")
@bp.route("/index_html")
@permission_required(Permission.ScoView)
@scodoc7func(context)
def index_html(context, REQUEST=None, showcodes=0, showsemtable=0):
return sco_dept.index_html(
context, REQUEST=REQUEST, showcodes=showcodes, showsemtable=showsemtable
)
@bp.route("/rssnews")
@permission_required(Permission.ScoView)
@scodoc7func(context)
def rssnews(context, REQUEST=None):
"rss feed"
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
return sco_news.scolar_news_summary_rss(
context,
"Nouvelles de " + sco_preferences.get_preference(context, "DeptName"),
scu.ScoURL(),
)
sco_publish("/trombino", sco_trombino.trombino, Permission.ScoView)
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
)
sco_publish("/groups_view", sco_groups_view.groups_view, Permission.ScoView)
sco_publish(
"/export_groups_as_moodle_csv",
sco_groups_view.export_groups_as_moodle_csv,
Permission.ScoView,
)
# -------------------------- INFOS SUR ETUDIANTS --------------------------
@bp.route("/getEtudInfo")
@permission_required(Permission.ScoView)
@scodoc7func(context)
def getEtudInfo(
context, etudid=False, code_nip=False, filled=False, REQUEST=None, format=None
):
"""infos sur un etudiant (API)
On peut specifier etudid ou code_nip
ou bien cherche dans REQUEST.form: etudid, code_nip, code_ine
(dans cet ordre).
"""
etud = sco_etud.get_etud_info(
etudid=etudid, code_nip=code_nip, filled=filled, REQUEST=REQUEST
)
if format is None:
return etud
else:
return scu.sendResult(REQUEST, etud, name="etud", format=format)
sco_publish(
"/search_etud_in_dept", sco_find_etud.search_etud_in_dept, Permission.ScoView
)
sco_publish(
"/search_etud_by_name", sco_find_etud.search_etud_by_name, Permission.ScoView
)
# XMLgetEtudInfos était le nom dans l'ancienne API ScoDoc 6
@bp.route("/etud_info")
@bp.route("/XMLgetEtudInfos")
@permission_required(Permission.ScoView)
@scodoc7func(context)
def etud_info(context, etudid=None, format="xml", REQUEST=None):
"Donne les informations sur un etudiant"
t0 = time.time()
args = sco_etud.make_etud_args(etudid=etudid, REQUEST=REQUEST)
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(
REQUEST, d, name="etudiant", format=format, 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",
"email",
"emailperso",
"domicile",
"codepostaldomicile",
"villedomicile",
"paysdomicile",
"telephone",
"telephonemobile",
"fax",
"bac",
"specialite",
"annee_bac",
"nomlycee",
"villelycee",
"codepostallycee",
"codelycee",
"date_naissance_iso",
):
d[a] = scu.quote_xml_attr(etud[a])
d["civilite"] = scu.quote_xml_attr(
etud["civilite_str"]
) # exception: ne sort pas la civilite brute
d["sexe"] = d["civilite"] # backward compat pour anciens clients
d["photo_url"] = scu.quote_xml_attr(sco_photos.etud_photo_url(context, etud))
sem = etud["cursem"]
if sem:
sco_groups.etud_add_group_infos(context, etud, sem)
d["insemestre"] = [
{
"current": "1",
"formsemestre_id": sem["formsemestre_id"],
"date_debut": ndb.DateDMYtoISO(sem["date_debut"]),
"date_fin": ndb.DateDMYtoISO(sem["date_fin"]),
"etat": scu.quote_xml_attr(sem["ins"]["etat"]),
"groupes": scu.quote_xml_attr(
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": scu.quote_xml_attr(sem["ins"]["etat"]),
}
)
log("etud_info (%gs)" % (time.time() - t0))
return scu.sendResult(
REQUEST, d, name="etudiant", format=format, force_outer_xml_tag=False
)
# -------------------------- FICHE ETUDIANT --------------------------
sco_publish("/ficheEtud", sco_page_etud.ficheEtud, Permission.ScoView)
sco_publish(
"/etud_upload_file_form",
sco_archives_etud.etud_upload_file_form,
Permission.ScoView,
methods=["GET", "POST"],
)
sco_publish(
"/etud_delete_archive", sco_archives_etud.etud_delete_archive, Permission.ScoView
)
sco_publish(
"/etud_get_archived_file",
sco_archives_etud.etud_get_archived_file,
Permission.ScoView,
)
sco_publish(
"/etudarchive_import_files_form",
sco_archives_etud.etudarchive_import_files_form,
Permission.ScoView,
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.ScoEtudChangeAdr
)
sco_publish(
"/itemsuivi_create", sco_debouche.itemsuivi_create, Permission.ScoEtudChangeAdr
)
sco_publish(
"/itemsuivi_set_date", sco_debouche.itemsuivi_set_date, Permission.ScoEtudChangeAdr
)
sco_publish(
"/itemsuivi_set_situation",
sco_debouche.itemsuivi_set_situation,
Permission.ScoEtudChangeAdr,
)
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.ScoEtudChangeAdr
)
@bp.route("/doAddAnnotation")
@permission_required(Permission.ScoEtudAddAnnotations)
@scodoc7func(context)
def doAddAnnotation(context, etudid, comment, REQUEST):
"ajoute annotation sur etudiant"
authuser = REQUEST.AUTHENTICATED_USER
cnx = ndb.GetDBConnexion()
sco_etud.etud_annotations_create(
cnx,
args={
"etudid": etudid,
"comment": comment,
"zope_authenticated_user": str(authuser),
"zope_remote_addr": REQUEST.REMOTE_ADDR,
},
)
logdb(REQUEST, cnx, method="addAnnotation", etudid=etudid)
return REQUEST.RESPONSE.redirect("ficheEtud?etudid=" + etudid)
@bp.route("/doSuppressAnnotation")
@permission_required(Permission.ScoView)
@scodoc7func(context)
def doSuppressAnnotation(context, etudid, annotation_id, REQUEST):
"""Suppression annotation."""
if not sco_permissions_check.can_suppress_annotation(
context, annotation_id, REQUEST
):
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("suppress annotation: %s" % str(anno))
logdb(REQUEST, cnx, method="SuppressAnnotation", etudid=etudid)
sco_etud.etud_annotations_delete(cnx, annotation_id)
return REQUEST.RESPONSE.redirect(
"ficheEtud?etudid=%s&head_message=Annotation%%20supprimée" % (etudid)
)
@bp.route("/formChangeCoordonnees", methods=["GET", "POST"])
@permission_required(Permission.ScoEtudChangeAdr)
@scodoc7func(context)
def formChangeCoordonnees(context, etudid, REQUEST):
"edit coordonnees etudiant"
cnx = ndb.GetDBConnexion()
etud = sco_etud.get_etud_info(etudid=etudid, filled=1, REQUEST=REQUEST)[0]
adrs = sco_etud.adresse_list(cnx, {"etudid": etudid})
if adrs:
adr = adrs[0]
else:
adr = {} # no data for this student
H = [
'Changement des coordonnées de %(nomprenom)s
%s
" % etud["nomprenom"],
'",
html_sco_header.sco_footer(REQUEST),
]
return "\n".join(H)
@bp.route("/formChangePhoto", methods=["GET", "POST"])
@permission_required(Permission.ScoEtudChangeAdr)
@scodoc7func(context)
def formChangePhoto(context, etudid=None, REQUEST=None):
"""Formulaire changement photo étudiant"""
etud = sco_etud.get_etud_info(filled=1, REQUEST=REQUEST)[0]
if sco_photos.etud_photo_is_local(context, etud):
etud["photoloc"] = "dans ScoDoc"
else:
etud["photoloc"] = "externe"
H = [
html_sco_header.sco_header(context, REQUEST, page_title="Changement de photo"),
"""Changement de la photo de %(nomprenom)s
%(operation_name)s de %(nomprenom)s (semestre %(semtitre)s)
Création d'un étudiant
Modification d\'un étudiant (fiche)
'
% etudid
)
initvalues = sco_etud.etudident_list(cnx, {"etudid": etudid})
assert len(initvalues) == 1
initvalues = initvalues[0]
submitlabel = "Modifier les données"
# recuperation infos Apogee
# Si on a le code NIP, fait juste une requete, sinon tente de rechercher par nom
# (la recherche par nom ne fonctionne plus à Paris 13)
# XXX A terminer
# code_nip = initvalues.get("code_nip", "")
# if code_nip:
# try:
# infos = sco_portal_apogee.get_etud_apogee(context, code_nip)
# except ValueError:
# infos = None
# pass # XXX a terminer
nom = REQUEST.form.get("nom", None)
if nom is None:
nom = initvalues.get("nom", None)
if nom is None:
infos = []
else:
prenom = REQUEST.form.get("prenom", "")
if REQUEST.form.get("tf-submitted", False) and not prenom:
prenom = initvalues.get("prenom", "")
infos = sco_portal_apogee.get_infos_apogee(context, nom, prenom)
if infos:
formatted_infos = [
"""
"""
]
nanswers = len(infos)
nmax = 10 # nb max de reponse montrees
infos = infos[:nmax]
for i in infos:
formatted_infos.append("
")
m = "%d étudiants trouvés" % nanswers
if len(infos) != nanswers:
m += " (%d montrés)" % len(infos)
A = """")
for k in i.keys():
if k != "nip":
item = "
Informations Apogée
annulation
' + F
else:
# form submission
if edit:
etudid = tf[2]["etudid"]
else:
etudid = None
ok, NbHomonyms = sco_etud.check_nom_prenom(
cnx, nom=tf[2]["nom"], prenom=tf[2]["prenom"], etudid=etudid
)
if not ok:
return (
"\n".join(H)
+ tf_error_message("Nom ou prénom invalide")
+ tf[1]
+ "Confirmer la suppression de l'étudiant %(nomprenom)s ?
Prenez le temps de vérifier que vous devez vraiment supprimer cet étudiant !
Cette opération irréversible efface toute trace de l'étudiant: inscriptions, notes, absences... dans tous les semestres qu'il a fréquenté.
Dans la plupart des cas, vous avez seulement besoin de le
Vérifier la fiche de %(nomprenom)s
""" % etud, dest_url="", REQUEST=REQUEST, cancel_url="ficheEtud?etudid=%s" % etudid, OK="Supprimer définitivement cet étudiant", parameters={"etudid": etudid}, ) log("etudident_delete: etudid=%(etudid)s nomprenom=%(nomprenom)s" % etud) # delete in all tables ! tables = [ "notes_appreciations", "scolar_autorisation_inscription", "scolar_formsemestre_validation", "scolar_events", "notes_notes_log", "notes_notes", "notes_moduleimpl_inscription", "notes_formsemestre_inscription", "group_membership", "entreprise_contact", "etud_annotations", "scolog", "admissions", "adresse", "absences", "billet_absence", "identite", ] cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) for table in tables: cursor.execute("delete from %s where etudid=%%(etudid)s" % table, etud) cnx.commit() # Inval semestres où il était inscrit: to_inval = [s["formsemestre_id"] for s in etud["sems"]] if to_inval: sco_core.inval_cache(context, formsemestre_id_list=to_inval) # > return REQUEST.RESPONSE.redirect( scu.ScoURL() + r"?head_message=Etudiant%20supprimé" ) @bp.route("/check_group_apogee") @permission_required(Permission.ScoEtudInscrit) @scodoc7func(context) def check_group_apogee( context, group_id, REQUEST=None, 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(context, group_id, etat=etat) formsemestre_id = group["formsemestre_id"] cnx = ndb.GetDBConnexion() H = [ html_sco_header.html_sem_header( context, REQUEST, "Etudiants du %s" % (group["group_name"] or "semestre"), sem, ), 'Nom | Nom usuel | Prénom | NIP (ScoDoc) | Apogée | |
---|---|---|---|---|---|
%s | %s | %s | %s | %s | %s |
Retour au semestre """ % ( REQUEST.URL0, formsemestre_id, scu.strnone(group_id), scu.strnone(etat), formsemestre_id, ) ) H.append( """
Retour au semestre """ % ( REQUEST.URL0, formsemestre_id, scu.strnone(group_id), scu.strnone(etat), formsemestre_id, ) ) return "\n".join(H) + html_sco_header.sco_footer(REQUEST) @bp.route("/form_students_import_excel") @permission_required(Permission.ScoEtudInscrit) @scodoc7func(context) def form_students_import_excel(context, REQUEST, formsemestre_id=None): "formulaire import xls" if formsemestre_id: sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) dest_url = ( scu.ScoURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id ) else: sem = None dest_url = scu.ScoURL() if sem and sem["etat"] != "1": raise ScoValueError("Modification impossible: semestre verrouille") H = [ html_sco_header.sco_header(context, REQUEST, page_title="Import etudiants"), """
A utiliser pour importer de nouveaux étudiants (typiquement au premier semestre).
Si les étudiants à inscrire sont déjà dans un autre semestre, utiliser le menu "Inscriptions (passage des étudiants) depuis d'autres semestres à partir du semestre destination.
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 "Synchroniser avec étape Apogée".
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.
""", ] # ' if sem: H.append( """Les étudiants importés seront inscrits dans le semestre %s
""" % sem["titremois"] ) else: H.append( """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). Cliquez ici pour afficher les codes
""" % (scu.ScoURL()) ) H.append("""Le fichier Excel décrivant les étudiants doit comporter les colonnes suivantes.
Les colonnes peuvent être placées dans n'importe quel ordre, mais le titre exact (tel que ci-dessous) doit être sur la première ligne.
Les champs avec un astérisque (*) doivent être présents (nulls non autorisés).
Attribut | Type | Description | |
%s | %s | %s | %s |