# -*- 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 """ import sys import traceback import time import string import glob import re import urllib import urllib2 import cgi import xml import jaxml try: from cStringIO import StringIO except: from StringIO import StringIO from zipfile import ZipFile import thread import psycopg2 from flask import g from flask import current_app from config import Config from scodoc_manager import sco_mgr from app.decorators import ( scodoc7func, ScoDoc7Context, permission_required, admin_required, login_required, ) from app.auth.models import Permission from app.views import scolar_bp as bp from app.scodoc.notes_log import log import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc.scolog import logdb from app.scodoc.sco_permissions import ( ScoAbsChange, ScoView, ScoEnsView, ScoImplement, ScoChangeFormation, ScoChangePreferences, ScoObservateur, ScoEtudAddAnnotations, ScoEtudInscrit, ScoEtudChangeGroups, ScoEtudChangeAdr, ScoEditAllEvals, ScoEditAllNotes, ScoEditFormationTags, ScoEditApo, ScoSuperAdmin, ) import app.scodoc.sco_permissions from app.scodoc.sco_exceptions import ( AccessDenied, ScoException, ScoValueError, ScoInvalidDateError, ScoLockedFormError, ScoGenError, ScoInvalidDept, ) from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message import app.scodoc.scolars import app.scodoc.sco_codes_parcours import app.scodoc.sco_preferences import app.scodoc.sco_formations from app.scodoc.scolars import ( format_nom, format_prenom, format_civilite, format_lycee, format_lycee_from_code, ) from app.scodoc.scolars import format_telephone, format_pays, make_etud_args import app.scodoc.sco_find_etud import app.scodoc.sco_photos import app.scodoc.sco_formsemestre import app.scodoc.sco_formsemestre_edit import app.scodoc.sco_news from app.scodoc.sco_news import NEWS_INSCR, NEWS_NOTE, NEWS_FORM, NEWS_SEM, NEWS_MISC import app.scodoc.html_sco_header import app.scodoc.html_sidebar from app.scodoc.gen_tables import GenTable import app.scodoc.sco_excel import app.scodoc.imageresize import app.scodoc.ImportScolars import app.scodoc.sco_abs import app.scodoc.sco_portal_apogee import app.scodoc.sco_synchro_etuds import app.scodoc.sco_page_etud import app.scodoc.sco_groups import app.scodoc.sco_trombino import app.scodoc.sco_groups_view import app.scodoc.sco_trombino_tours import app.scodoc.sco_parcours_dut import app.scodoc.sco_report import app.scodoc.sco_archives_etud import app.scodoc.sco_debouche import app.scodoc.sco_groups_edit import app.scodoc.sco_up_to_date import app.scodoc.sco_edt_cal import app.scodoc.sco_dept import app.scodoc.sco_dump_db from app.scodoc.VERSION import SCOVERSION, SCONEWS log.set_log_directory(Config.INSTANCE_HOME + "/log") log("ScoDoc8 restarting...") # -------------------------------------------------------------------- # # SCOLARITE (/ScoDoc//Scolarite/...) # # -------------------------------------------------------------------- # ? à déplacer ? def DeptId(): if g.scodoc_dept in sco_mgr.get_dept_ids(): return g.scodoc_dept raise ScoInvalidDept("département invalide: %s" % g.scodoc_dept) # sco_publish( # "/formsemestre_evaluations_delai_correction", # sco_evaluations.formsemestre_evaluations_delai_correction, # Permission.ScoView, # ) sco_publish("/sco_header", html_sco_header.sco_header, Permission.ScoView) sco_publish("/sco_footer", html_sco_header.sco_footer, Permission.ScoView) @bp.route("/about") @permission_required(Permission.ScoView) @scodoc7func(context) def about(context, REQUEST): "version info" H = [ """

Système de gestion scolarité

© Emmanuel Viennet 1997-2021

Version %s

""" % (scu.get_scodoc_version()) ] H.append( '

Logiciel libre écrit en Python.

Utilise ReportLab pour générer les documents PDF, et pyExcelerator pour le traitement des documents Excel.

' ) H.append("

Dernières évolutions

" + SCONEWS) H.append( '" ) d = "" return context.sco_header(REQUEST) + "\n".join(H) + d + context.sco_footer(REQUEST) # -------------------------------------------------------------------- # # PREFERENCES # # -------------------------------------------------------------------- @bp.route("/edit_preferences") @permission_required(Permission.ScoChangePreferences) @scodoc7func(context) def edit_preferences(context, REQUEST): """Edit global preferences (lien eParamétrage" département)""" return sco_preferences.get_base_preferences(context).edit(REQUEST=REQUEST) @bp.route("/formsemestre_edit_preferences") @permission_required(Permission.ScoView) @scodoc7func(context) def formsemestre_edit_preferences(context, formsemestre_id, REQUEST): """Edit preferences for a semestre""" authuser = REQUEST.AUTHENTICATED_USER sem = sco_formsemestre.get_formsemestre(context.Notes, formsemestre_id) ok = ( authuser.has_permission(ScoImplement, context) or ((str(authuser) in sem["responsables"]) and sem["resp_can_edit"]) ) and (sem["etat"] == "1") if ok: return context.get_preferences(formsemestre_id=formsemestre_id).edit( REQUEST=REQUEST ) else: raise AccessDenied("Modification impossible pour %s" % authuser) @bp.route("/doc_preferences") @permission_required(Permission.ScoView) @scodoc7func(context) def doc_preferences(context, REQUEST): """List preferences for wiki documentation""" REQUEST.RESPONSE.setHeader("content-type", "text/plain") return sco_preferences.doc_preferences(context) # -------------------------------------------------------------------- # # ETUDIANTS # # -------------------------------------------------------------------- # ----------------- BANDEAUX ------------------- sco_publish("/sidebar", html_sidebar.sidebar, Permission.ScoView) sco_publish("/sidebar_dept", html_sidebar.sidebar_dept, Permission.ScoView) @bp.route("/showEtudLog") @permission_required(Permission.ScoView) @scodoc7func(context) def showEtudLog(context, etudid, format="html", REQUEST=None): """Display log of operations on this student""" etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] ops = scolars.list_scolog(context, etudid) tab = GenTable( titles={ "date": "Date", "authenticated_user": "Utilisateur", "remote_addr": "IP", "method": "Opération", "msg": "Message", }, columns_ids=("date", "authenticated_user", "remote_addr", "method", "msg"), rows=ops, html_sortable=True, html_class="table_leftalign", base_url="%s?etudid=%s" % (REQUEST.URL0, etudid), page_title="Opérations sur %(nomprenom)s" % etud, html_title="

Opérations effectuées sur l'étudiant %(nomprenom)s

" % etud, filename="log_" + scu.make_filename(etud["nomprenom"]), html_next_section='' % etud, preferences=context.get_preferences(), ) return tab.make_page(context, format=format, REQUEST=REQUEST) # ---------- PAGE ACCUEIL (listes) -------------- sco_publish("/index_html", sco_dept.index_html, Permission.ScoView) @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 " + context.get_preference("DeptName"), context.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 bien cherche dans REQUEST.form: etudid, code_nip, code_ine (dans cet ordre). """ if etudid is None: return [] cnx = context.GetDBConnexion() args = make_etud_args(etudid=etudid, code_nip=code_nip, REQUEST=REQUEST) etud = scolars.etudident_list(cnx, args=args) if filled: fillEtudsInfo(context, etud) 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", "/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 = make_etud_args(etudid=etudid, REQUEST=REQUEST) cnx = context.GetDBConnexion() etuds = scolars.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] context.fillEtudsInfo([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, ) 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, ) sco_publish( "/etudarchive_generate_excel_sample", sco_archives_etud.etudarchive_generate_excel_sample, Permission.ScoView, ) # Debouche / devenir etudiant # vrai si l'utilisateur peut modifier les informations de suivi sur la page etud" def can_edit_suivi(context, REQUEST=None): authuser = REQUEST.AUTHENTICATED_USER return authuser.has_permission(ScoEtudChangeAdr, context) 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 = context.GetDBConnexion() scolars.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("/canSuppressAnnotation") @permission_required(Permission.ScoView) @scodoc7func(context) def canSuppressAnnotation(context, annotation_id, REQUEST): """True if current user can suppress this annotation Seuls l'auteur de l'annotation et le chef de dept peuvent supprimer une annotation. """ cnx = context.GetDBConnexion() annos = scolars.etud_annotations_list(cnx, args={"id": annotation_id}) if len(annos) != 1: raise ScoValueError("annotation inexistante !") anno = annos[0] authuser = REQUEST.AUTHENTICATED_USER # note: les anciennes installations n'ont pas le role ScoEtudSupprAnnotations # c'est pourquoi on teste aussi ScoEtudInscrit (normalement détenue par le chef) return ( (str(authuser) == anno["zope_authenticated_user"]) or authuser.has_permission(ScoEtudSupprAnnotations, context) or authuser.has_permission(ScoEtudInscrit, context) ) @bp.route("/doSuppressAnnotation") @permission_required(Permission.ScoView) @scodoc7func(context) def doSuppressAnnotation(context, etudid, annotation_id, REQUEST): """Suppression annotation.""" if not context.canSuppressAnnotation(annotation_id, REQUEST): raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") cnx = context.GetDBConnexion() annos = scolars.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) scolars.etud_annotations_delete(cnx, annotation_id) return REQUEST.RESPONSE.redirect( "ficheEtud?etudid=%s&head_message=Annotation%%20supprimée" % (etudid) ) @bp.route("/formChangeCoordonnees") @permission_required(Permission.ScoEtudChangeAdr) @scodoc7func(context) def formChangeCoordonnees(context, etudid, REQUEST): "edit coordonnees etudiant" cnx = context.GetDBConnexion() etud = context.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0] adrs = scolars.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

' % etud ] header = context.sco_header( 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 = context.ScoURL() + "/ficheEtud?etudid=" + etudid if tf[0] == 0: return header + "\n".join(H) + tf[1] + context.sco_footer(REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect(dest_url) else: if adrs: scolars.adresse_edit(cnx, args=tf[2], context=context) else: scolars.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 = context.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0] H = [ context.sco_header(REQUEST, page_title=etud["nomprenom"]), "

%s

" % etud["nomprenom"], '
' % etudid, sco_photos.etud_photo_orig_html(context, etud), "
", context.sco_footer(REQUEST), ] return "\n".join(H) @bp.route("/formChangePhoto") @permission_required(Permission.ScoEtudChangeAdr) @scodoc7func(context) def formChangePhoto(context, etudid=None, REQUEST=None): """Formulaire changement photo étudiant""" etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] if sco_photos.etud_photo_is_local(context, etud): etud["photoloc"] = "dans ScoDoc" else: etud["photoloc"] = "externe" H = [ context.sco_header(REQUEST, page_title="Changement de photo"), """

Changement de la photo de %(nomprenom)s

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.

""", ] tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, ( ("etudid", {"default": etudid, "input_type": "hidden"}), ( "photofile", {"input_type": "file", "title": "Fichier image", "size": 20}, ), ), submitlabel="Valider", cancelbutton="Annuler", ) dest_url = context.ScoURL() + "/ficheEtud?etudid=" + etud["etudid"] if tf[0] == 0: return ( "\n".join(H) + tf[1] + '

Supprimer cette photo

' % etudid + context.sco_footer(REQUEST) ) elif tf[0] == -1: return REQUEST.RESPONSE.redirect(dest_url) else: data = tf[2]["photofile"].read() status, diag = sco_photos.store_photo(context, etud, data, REQUEST=REQUEST) if status != 0: return REQUEST.RESPONSE.redirect(dest_url) else: H.append('

Erreur:' + diag + "

") return "\n".join(H) + context.sco_footer(REQUEST) @bp.route("/formSuppressPhoto") @permission_required(Permission.ScoEtudChangeAdr) @scodoc7func(context) def formSuppressPhoto(context, etudid=None, REQUEST=None, dialog_confirmed=False): """Formulaire suppression photo étudiant""" etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] if not dialog_confirmed: return scu.confirm_dialog( context, "

Confirmer la suppression de la photo de %(nomprenom)s ?

" % etud, dest_url="", REQUEST=REQUEST, cancel_url="ficheEtud?etudid=%s" % etudid, parameters={"etudid": etudid}, ) sco_photos.suppress_photo(context, etud, REQUEST=REQUEST) return REQUEST.RESPONSE.redirect( context.ScoURL() + "/ficheEtud?etudid=" + etud["etudid"] ) # @bp.route("/formDem") @permission_required(Permission.ScoEtudInscrit) @scodoc7func(context) def formDem(context, etudid, formsemestre_id, REQUEST): "Formulaire Démission Etudiant" return context._formDem_of_Def( etudid, formsemestre_id, REQUEST=REQUEST, operation_name="Démission", operation_method="doDemEtudiant", ) @bp.route("/formDef") @permission_required(Permission.ScoEtudInscrit) @scodoc7func(context) def formDef(context, etudid, formsemestre_id, REQUEST): "Formulaire Défaillance Etudiant" return context._formDem_of_Def( etudid, formsemestre_id, REQUEST=REQUEST, operation_name="Défaillance", operation_method="doDefEtudiant", ) def _formDem_of_Def( context, etudid, formsemestre_id, REQUEST=None, operation_name="", operation_method="", ): "Formulaire démission ou défaillance Etudiant" etud = context.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0] sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) if sem["etat"] != "1": raise ScoValueError("Modification impossible: semestre verrouille") etud["formsemestre_id"] = formsemestre_id etud["semtitre"] = sem["titremois"] etud["nowdmy"] = time.strftime("%d/%m/%Y") etud["operation_name"] = operation_name # header = context.sco_header( REQUEST, page_title="%(operation_name)s de %(nomprenom)s (du semestre %(semtitre)s)" % etud, ) H = [ '

%(operation_name)s de %(nomprenom)s (semestre %(semtitre)s)

' % etud ] H.append( """

Date de la %s (J/M/AAAA):  """ % (operation_method, scu.strlower(operation_name)) ) H.append( """

""" % etud ) return header + "\n".join(H) + context.sco_footer(REQUEST) @bp.route("/doDemEtudiant") @permission_required(Permission.ScoEtudInscrit) @scodoc7func(context) def doDemEtudiant(context, etudid, formsemestre_id, event_date=None, REQUEST=None): "Déclare la démission d'un etudiant dans le semestre" return context._doDem_or_Def_Etudiant( etudid, formsemestre_id, event_date=event_date, etat_new="D", operation_method="demEtudiant", event_type="DEMISSION", REQUEST=REQUEST, ) @bp.route("/doDefEtudiant") @permission_required(Permission.ScoEtudInscrit) @scodoc7func(context) def doDefEtudiant(context, etudid, formsemestre_id, event_date=None, REQUEST=None): "Déclare la défaillance d'un etudiant dans le semestre" return context._doDem_or_Def_Etudiant( etudid, formsemestre_id, event_date=event_date, etat_new=sco_codes_parcours.DEF, operation_method="defailleEtudiant", event_type="DEFAILLANCE", REQUEST=REQUEST, ) def _doDem_or_Def_Etudiant( context, etudid, formsemestre_id, event_date=None, etat_new="D", # 'D' or DEF operation_method="demEtudiant", event_type="DEMISSION", REQUEST=None, ): "Démission ou défaillance d'un étudiant" # marque 'D' ou DEF dans l'inscription au semestre et ajoute # un "evenement" scolarite cnx = context.GetDBConnexion() # check lock sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) if sem["etat"] != "1": raise ScoValueError("Modification impossible: semestre verrouille") # ins = context.Notes.do_formsemestre_inscription_list( {"etudid": etudid, "formsemestre_id": formsemestre_id} )[0] if not ins: raise ScoException("etudiant non inscrit ?!") ins["etat"] = etat_new context.Notes.do_formsemestre_inscription_edit( args=ins, formsemestre_id=formsemestre_id ) logdb(REQUEST, cnx, method=operation_method, etudid=etudid) scolars.scolar_events_create( cnx, args={ "etudid": etudid, "event_date": event_date, "formsemestre_id": formsemestre_id, "event_type": event_type, }, ) if REQUEST: return REQUEST.RESPONSE.redirect("ficheEtud?etudid=" + etudid) @bp.route("/doCancelDem") @permission_required(Permission.ScoEtudInscrit) @scodoc7func(context) def doCancelDem( context, etudid, formsemestre_id, dialog_confirmed=False, args=None, REQUEST=None ): "Annule une démission" return context._doCancelDem_or_Def( etudid, formsemestre_id, dialog_confirmed=dialog_confirmed, args=args, operation_name="démission", etat_current="D", etat_new="I", operation_method="cancelDem", event_type="DEMISSION", REQUEST=REQUEST, ) @bp.route("/doCancelDef") @permission_required(Permission.ScoEtudInscrit) @scodoc7func(context) def doCancelDef( context, etudid, formsemestre_id, dialog_confirmed=False, args=None, REQUEST=None ): "Annule la défaillance de l'étudiant" return context._doCancelDem_or_Def( etudid, formsemestre_id, dialog_confirmed=dialog_confirmed, args=args, operation_name="défaillance", etat_current=sco_codes_parcours.DEF, etat_new="I", operation_method="cancelDef", event_type="DEFAILLANCE", REQUEST=REQUEST, ) def _doCancelDem_or_Def( context, etudid, formsemestre_id, dialog_confirmed=False, args=None, operation_name="", # "démission" ou "défaillance" etat_current="D", etat_new="I", operation_method="cancelDem", event_type="DEMISSION", REQUEST=None, ): "Annule une demission ou une défaillance" # check lock sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) if sem["etat"] != "1": raise ScoValueError("Modification impossible: semestre verrouille") # verif info = context.getEtudInfo(etudid, filled=True)[0] ok = False for i in info["ins"]: if i["formsemestre_id"] == formsemestre_id: if i["etat"] != etat_current: raise ScoValueError("etudiant non %s !" % operation_name) ok = True break if not ok: raise ScoValueError("etudiant non inscrit ???") if not dialog_confirmed: return scu.confirm_dialog( context, "

Confirmer l'annulation de la %s ?

" % operation_name, dest_url="", REQUEST=REQUEST, cancel_url="ficheEtud?etudid=%s" % etudid, parameters={"etudid": etudid, "formsemestre_id": formsemestre_id}, ) # ins = context.Notes.do_formsemestre_inscription_list( {"etudid": etudid, "formsemestre_id": formsemestre_id} )[0] if ins["etat"] != etat_current: raise ScoException("etudiant non %s !!!" % etat_current) # obviously a bug ins["etat"] = etat_new cnx = context.GetDBConnexion() context.Notes.do_formsemestre_inscription_edit( args=ins, formsemestre_id=formsemestre_id ) logdb(REQUEST, cnx, method=operation_method, etudid=etudid) cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor.execute( "delete from scolar_events where etudid=%(etudid)s and formsemestre_id=%(formsemestre_id)s and event_type='" + event_type + "'", {"etudid": etudid, "formsemestre_id": formsemestre_id}, ) cnx.commit() return REQUEST.RESPONSE.redirect("ficheEtud?etudid=%s" % etudid) @bp.route("/etudident_create_form") @permission_required(Permission.ScoEtudInscrit) @scodoc7func(context) def etudident_create_form(context, REQUEST=None): "formulaire creation individuelle etudiant" return context._etudident_create_or_edit_form(REQUEST, edit=False) @bp.route("/etudident_edit_form") @permission_required(Permission.ScoEtudInscrit) @scodoc7func(context) def etudident_edit_form(context, REQUEST=None): "formulaire edition individuelle etudiant" return context._etudident_create_or_edit_form(REQUEST, edit=True) def _etudident_create_or_edit_form(context, REQUEST, edit): "Le formulaire HTML" H = [context.sco_header(REQUEST, init_jquery_ui=True)] F = context.sco_footer(REQUEST) etudid = REQUEST.form.get("etudid", None) cnx = context.GetDBConnexion() descr = [] if not edit: # creation nouvel etudiant initvalues = {} submitlabel = "Ajouter cet étudiant" H.append( """

Création d'un étudiant

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 !

""" ) else: # edition donnees d'un etudiant existant # setup form init values if not etudid: raise ValueError("missing etudid parameter") descr.append(("etudid", {"default": etudid, "input_type": "hidden"})) H.append( '

Modification d\'un étudiant (fiche)

' % etudid ) initvalues = scolars.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("
    • ") for k in i.keys(): if k != "nip": item = "
    • %s : %s
    • " % (k, i[k]) else: item = ( '
    • %s : %s
    • ' % (k, i[k], i[k]) ) formatted_infos.append(item) formatted_infos.append("
  1. ") formatted_infos.append("
") m = "%d étudiants trouvés" % nanswers if len(infos) != nanswers: m += " (%d montrés)" % len(infos) A = """
Informations Apogée

%s

%s
""" % ( m, "\n".join(formatted_infos), ) else: A = """

Pas d'informations d'Apogée

""" require_ine = context.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é", }, ), ( "date_naissance", { "title": "Date de naissance", "input_type": "date", "explanation": "j/m/a", }, ), ("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', }, ), ( "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": 5, "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", "explanation": "année obtention du bac", }, ), ( "math", { "size": 3, "type": "float", "title": "Note de mathématiques", "explanation": "note sur 20 en terminale", }, ), ( "physique", { "size": 3, "type": "float", "title": "Note de physique", "explanation": "note sur 20 en terminale", }, ), ( "anglais", { "size": 3, "type": "float", "title": "Note d'anglais", "explanation": "note sur 20 en terminale", }, ), ( "francais", { "size": 3, "type": "float", "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.URL0, REQUEST.form, descr, submitlabel=submitlabel, cancelbutton="Re-interroger Apogee", initvalues=initvalues, ) if tf[0] == 0: return "\n".join(H) + tf[1] + "

" + A + F elif tf[0] == -1: return "\n".join(H) + tf[1] + "

" + A + F # return '\n'.join(H) + '

annulation

' + F else: # form submission if edit: etudid = tf[2]["etudid"] else: etudid = None ok, NbHomonyms = scolars.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] + "

" + 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 = scolars.create_etud(context, cnx, args=tf[2], REQUEST=REQUEST) etudid = etud["etudid"] else: # modif d'un etudiant scolars.etudident_edit(cnx, tf[2], context=context, REQUEST=REQUEST) etud = scolars.etudident_list(cnx, {"etudid": etudid})[0] context.fillEtudsInfo([etud]) # Inval semesters with this student: to_inval = [s["formsemestre_id"] for s in etud["sems"]] if to_inval: context.Notes._inval_cache( 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 = context.GetDBConnexion() etuds = scolars.etudident_list(cnx, {"etudid": etudid}) if not etuds: raise ScoValueError("Etudiant inexistant !") else: etud = etuds[0] context.fillEtudsInfo([etud]) if not dialog_confirmed: return scu.confirm_dialog( context, """

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

d'un semestre ? (dans ce cas passez par sa fiche, menu associé au semestre)

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: context.Notes._inval_cache(formsemestre_id_list=to_inval) # > return REQUEST.RESPONSE.redirect( context.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 = context.GetDBConnexion() H = [ context.Notes.html_sem_header( REQUEST, "Etudiants du %s" % (group["group_name"] or "semestre"), sem ), '', "", ] 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(context, nom, prenom) if not infos: info_apogee = ( 'Pas d\'information (Modifier identité)' % etudid ) nerrs += 1 else: if len(infos) == 1: nip_apogee = infos[0]["nip"] if code_nip != nip_apogee: if fix: # Update database scolars.identite_edit( cnx, args={"etudid": etudid, "code_nip": nip_apogee}, context=context, ) info_apogee = ( 'copié %s' % nip_apogee ) nfix += 1 else: info_apogee = '%s' % nip_apogee nerrs += 1 else: info_apogee = "ok" else: info_apogee = ( '%d correspondances (Choisir)' % (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 = scolars.adresse_list(cnx, {"etudid": etudid}) if adrs: adr = adrs[0] # modif adr existante args = {"adresse_id": adr["adresse_id"], "email": mail_apogee} scolars.adresse_edit(cnx, args=args) else: # creation adresse args = {"etudid": etudid, "email": mail_apogee} scolars.adresse_create(cnx, args=args) mailstat = 'copié' else: mailstat = "inconnu" nmailmissing += 1 H.append( '' % (etudid, nom, nom_usuel, prenom, mailstat, code_nip, info_apogee) ) H.append("
NomNom usuelPrénomMailNIP (ScoDoc)Apogée
%s%s%s%s%s%s
") H.append("") H.append( """

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) + context.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.Notes, formsemestre_id) dest_url = ( context.ScoURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id ) else: sem = None dest_url = context.ScoURL() if sem and sem["etat"] != "1": raise ScoValueError("Modification impossible: semestre verrouille") H = [ context.sco_header(REQUEST, page_title="Import etudiants"), """

Téléchargement d\'une nouvelle liste d\'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

""" % (context.ScoURL()) ) H.append("""
  1. """) if formsemestre_id: H.append( """ """ ) else: H.append("""""") H.append( """Obtenir la feuille excel à remplir
  2. """ ) F = context.sco_footer(REQUEST) tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, ( ( "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 = [ """

    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).

    """ ] for t in ImportScolars.sco_import_format( with_codesemestre=(formsemestre_id == None) ): if int(t[3]): ast = "" else: ast = "*" S.append( "" % (t[0], t[1], t[4], ast) ) if tf[0] == 0: return "\n".join(H) + tf[1] + "" + "\n".join(S) + F elif tf[0] == -1: return REQUEST.RESPONSE.redirect(dest_url) else: return ImportScolars.students_import_excel( context, tf[2]["csvfile"], REQUEST=REQUEST, formsemestre_id=formsemestre_id, check_homonyms=tf[2]["check_homonyms"], require_ine=tf[2]["require_ine"], ) @bp.route("/import_generate_excel_sample") @permission_required(Permission.ScoEtudInscrit) @scodoc7func(context) def import_generate_excel_sample(context, REQUEST, with_codesemestre="1"): "une feuille excel pour importation etudiants" if with_codesemestre: with_codesemestre = int(with_codesemestre) else: with_codesemestre = 0 format = ImportScolars.sco_import_format() data = ImportScolars.sco_import_generate_excel_sample( format, with_codesemestre, exclude_cols=["photo_filename"], REQUEST=REQUEST ) return sco_excel.sendExcelFile(REQUEST, data, "ImportEtudiants.xls") # --- Données admission @bp.route("/import_generate_admission_sample") @permission_required(Permission.ScoView) @scodoc7func(context) def import_generate_admission_sample(context, REQUEST, formsemestre_id): "une feuille excel pour importation données admissions" group = sco_groups.get_group( context, sco_groups.get_default_group(context, formsemestre_id) ) fmt = ImportScolars.sco_import_format() data = ImportScolars.sco_import_generate_excel_sample( fmt, only_tables=["identite", "admissions", "adresse"], exclude_cols=["nationalite", "foto", "photo_filename"], group_ids=[group["group_id"]], context=context.Notes, REQUEST=REQUEST, ) return sco_excel.sendExcelFile(REQUEST, data, "AdmissionEtudiants.xls") # --- Données admission depuis fichier excel (version nov 2016) @bp.route("/form_students_import_infos_admissions") @permission_required(Permission.ScoView) @scodoc7func(context) def form_students_import_infos_admissions(context, REQUEST, formsemestre_id=None): "formulaire import xls" authuser = REQUEST.AUTHENTICATED_USER F = context.sco_footer(REQUEST) if not authuser.has_permission(ScoEtudInscrit, context): # autorise juste l'export H = [ context.sco_header( REQUEST, page_title="Export données admissions (Parcoursup ou autre)", ), """

    Téléchargement des informations sur l'admission des étudiants

    Exporter les informations de ScoDoc (classeur Excel) (ce fichier peut être ré-importé après d'éventuelles modifications)

    Vous n'avez pas le droit d'importer les données

    """ % {"formsemestre_id": formsemestre_id}, ] return "\n".join(H) + F # On a le droit d'importer: H = [ context.sco_header(REQUEST, page_title="Import données admissions Parcoursup"), """

    Téléchargement des informations sur l'admission des étudiants depuis feuilles import Parcoursup

    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.

    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.
    Seules les données "admission" seront modifiées (et pas l'identité de l'étudiant).
    Les colonnes "nom" et "prenom" sont requises, ou bien une colonne "etudid".

    Avant d'importer vos données, il est recommandé d'enregistrer les informations actuelles: exporter les données actuelles de ScoDoc (ce fichier peut être ré-importé après d'éventuelles modifications)

    """ % {"formsemestre_id": formsemestre_id}, ] # ' type_admission_list = ( "Autre", "Parcoursup", "Parcoursup PC", "APB", "APB PC", "CEF", "Direct", ) tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, ( ( "csvfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}, ), ( "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": type_admission_list, }, ), ("formsemestre_id", {"input_type": "hidden"}), ), submitlabel="Télécharger", ) help_text = ( """

    Les colonnes importables par cette fonction sont indiquées dans la table ci-dessous. Seule la première feuille du classeur sera utilisée.

    """ + ImportScolars.adm_table_description_format(context).html() + """
    """ ) if tf[0] == 0: return "\n".join(H) + tf[1] + help_text + F elif tf[0] == -1: return REQUEST.RESPONSE.redirect( context.ScoURL() + "/formsemestre_status?formsemestre_id=" + formsemestre_id ) else: return context._students_import_admission( tf[2]["csvfile"], type_admission=tf[2]["type_admission"], REQUEST=REQUEST, formsemestre_id=formsemestre_id, ) # unpublished def _students_import_admission( context, csvfile, type_admission="", REQUEST=None, formsemestre_id=None ): "import donnees admission from Excel file (v2016)" diag = ImportScolars.scolars_import_admission( csvfile, context.Notes, REQUEST, formsemestre_id=formsemestre_id, type_admission=type_admission, ) if REQUEST: H = [context.sco_header(REQUEST, page_title="Import données admissions")] H.append("

    Import terminé !

    ") H.append( '

    Continuer

    ' % "formsemestre_status?formsemestre_id=%s" % formsemestre_id ) if diag: H.append("

    Diagnostic:

    • %s

    " % "
  3. ".join(diag)) return "\n".join(H) + context.sco_footer(REQUEST) @bp.route("/formsemestre_import_etud_admission") @permission_required(Permission.ScoEtudChangeAdr) @scodoc7func(context) def formsemestre_import_etud_admission( context, formsemestre_id, import_email=True, REQUEST=None ): """Reimporte donnees admissions par synchro Portail Apogée""" ( no_nip, unknowns, changed_mails, ) = sco_synchro_etuds.formsemestre_import_etud_admission( context.Notes, formsemestre_id, import_identite=True, import_email=import_email ) H = [ context.Notes.html_sem_header(REQUEST, "Reimport données admission"), "

    Opération effectuée

    ", ] if no_nip: H.append("

    Attention: étudiants sans NIP: " + str(no_nip) + "

    ") if unknowns: H.append( "

    Attention: étudiants inconnus du portail: codes NIP=" + str(unknowns) + "

    " ) if changed_mails: H.append("

    Adresses mails modifiées:

    ") for (info, new_mail) in changed_mails: H.append( "%s: %s devient %s
    " % (info["nom"], info["email"], new_mail) ) return "\n".join(H) + context.sco_footer(REQUEST) sco_publish( "/photos_import_files_form", sco_trombino.photos_import_files_form, Permission.ScoEtudChangeAdr, ) sco_publish( "/photos_generate_excel_sample", sco_trombino.photos_generate_excel_sample, Permission.ScoEtudChangeAdr, ) # --- Statistiques @bp.route("/stat_bac") @permission_required(Permission.ScoView) @scodoc7func(context) def stat_bac(context, formsemestre_id): "Renvoie statistisques sur nb d'etudiants par bac" cnx = context.GetDBConnexion() ins = context.Notes.do_formsemestre_inscription_list( args={"formsemestre_id": formsemestre_id} ) Bacs = {} # type bac : nb etud for i in ins: etud = scolars.etudident_list(cnx, {"etudid": i["etudid"]})[0] typebac = "%(bac)s %(specialite)s" % etud Bacs[typebac] = Bacs.get(typebac, 0) + 1 return Bacs # --- Dump sco_publish( "/sco_dump_and_send_db", sco_dump_db.sco_dump_and_send_db, Permission.ScoView ) # sco_publish( "/formsemestre_edit_uecoefs", sco_formsemestre_edit.formsemestre_edit_uecoefs, Permission.ScoView, )
  4. AttributTypeDescription
    %s%s%s%s