2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2022-01-01 14:49:42 +01:00
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
2020-09-26 16:19:37 +02:00
#
# 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
#
##############################################################################
""" Exportation des résultats des étudiants vers Apogée.
2021-02-01 23:54:46 +01:00
Ce code a été au départ inspiré par les travaux de Damien Mascré , scodoc2apogee ( en Java ) .
2020-09-26 16:19:37 +02:00
A utiliser en fin de semestre , après les jury .
On communique avec Apogée via des fichiers CSV .
Le fichier CSV , champs séparés par des tabulations , a la structure suivante :
< pre >
XX - APO_TITRES - XX
apoC_annee 2007 / 2008
apoC_cod_dip VDTCJ
apoC_Cod_Exp 1
apoC_cod_vdi 111
apoC_Fichier_Exp VDTCJ_V1CJ . txt
apoC_lib_dip DUT CJ
apoC_Titre1 Export Apogée du 13 / 06 / 2008 à 14 : 29
apoC_Titre2
XX - APO_COLONNES - XX
apoL_a01_code Type Objet Code Version Année Session Admission / Admissibilité Type Rés . Etudiant Numéro
apoL_a02_nom 1 Nom
apoL_a03_prenom 1 Prénom
apoL_a04_naissance Session Admissibilité Naissance
APO_COL_VAL_DEB
apoL_c0001 VET V1CJ 111 2007 0 1 N V1CJ - DUT CJ an1 0 1 Note
apoL_c0002 VET V1CJ 111 2007 0 1 B 0 1 Barème
apoL_c0003 VET V1CJ 111 2007 0 1 R 0 1 Résultat
APO_COL_VAL_FIN
apoL_c0030 APO_COL_VAL_FIN
XX - APO_VALEURS - XX
apoL_a01_code apoL_a02_nom apoL_a03_prenom apoL_a04_naissance apoL_c0001 apoL_c0002 apoL_c0003 apoL_c0004 apoL_c0005 apoL_c0006 apoL_c0007 apoL_c0008 apoL_c0009 apoL_c0010 apoL_c0011 apoL_c0012 apoL_c0013 apoL_c0014 apoL_c0015 apoL_c0016 apoL_c0017 apoL_c0018 apoL_c0019 apoL_c0020 apoL_c0021 apoL_c0022 apoL_c0023 apoL_c0024 apoL_c0025 apoL_c0026 apoL_c0027 apoL_c0028 apoL_c0029
10601232 AARIF MALIKA 22 / 09 / 1986 18 20 ADM 18 20 ADM 18 20 ADM 18 20 ADM 18 20 ADM 18 20 18 20 ADM 18 20 ADM 18 20 ADM 18 20 ADM
< / pre >
On récupère nos éléments pédagogiques dans la section XX - APO - COLONNES - XX et
notre liste d ' étudiants dans la section XX-APO_VALEURS-XX. Les champs de la
section XX - APO_VALEURS - XX sont décrits par les lignes successives de la
section XX - APO_COLONNES - XX .
Le fichier CSV correspond à une étape , qui est récupérée sur la ligne
< pre >
apoL_c0001 VET V1CJ . . .
< / pre >
XXX A vérifier :
AJAC car 1 sem . validé et pas de NAR
"""
2021-02-01 23:54:46 +01:00
import collections
import datetime
2021-08-31 19:32:12 +02:00
from functools import reduce
2022-04-05 11:44:08 +02:00
import functools
2021-08-15 15:01:13 +02:00
import io
2021-02-01 23:54:46 +01:00
import os
2021-08-31 19:32:12 +02:00
import pprint
2021-08-15 15:01:13 +02:00
import re
import time
2020-09-26 16:19:37 +02:00
from zipfile import ZipFile
2021-08-31 19:32:12 +02:00
from flask import send_file
2020-09-26 16:19:37 +02:00
2021-06-24 18:54:59 +02:00
# Pour la détection auto de l'encodage des fichiers Apogée:
2021-07-19 19:53:01 +02:00
from chardet import detect as chardet_detect
2020-09-26 16:19:37 +02:00
2022-02-13 23:53:11 +01:00
from app import log
from app . comp import res_sem
2022-03-27 22:25:00 +02:00
from app . comp . res_compat import NotesTableCompat
2022-02-26 20:22:18 +01:00
from app . models import FormSemestre , Identite
2022-01-21 00:46:45 +01:00
from app . models . config import ScoDocSiteConfig
2021-06-19 23:21:37 +02:00
import app . scodoc . sco_utils as scu
2021-12-03 14:13:49 +01:00
from app . scodoc . sco_exceptions import ScoValueError , ScoFormatError
2021-06-19 23:21:37 +02:00
from app . scodoc . gen_tables import GenTable
2021-07-10 13:55:35 +02:00
from app . scodoc . sco_vdi import ApoEtapeVDI
2021-06-19 23:21:37 +02:00
from app . scodoc . sco_codes_parcours import code_semestre_validant
from app . scodoc . sco_codes_parcours import (
DEF ,
2022-01-21 00:46:45 +01:00
DEM ,
2021-06-19 23:21:37 +02:00
NAR ,
RAT ,
)
from app . scodoc import sco_formsemestre
from app . scodoc import sco_parcours_dut
from app . scodoc import sco_etud
2020-09-26 16:19:37 +02:00
APO_PORTAL_ENCODING = (
" utf8 " # encodage du fichier CSV Apogée (était 'ISO-8859-1' avant jul. 2016)
)
APO_INPUT_ENCODING = " ISO-8859-1 " #
APO_OUTPUT_ENCODING = APO_INPUT_ENCODING # encodage des fichiers Apogee générés
APO_DECIMAL_SEP = " , " # separateur décimal: virgule
APO_SEP = " \t "
APO_NEWLINE = " \r \n "
2022-04-05 11:44:08 +02:00
def _apo_fmt_note ( note , fmt = " %3.2f " ) :
2020-09-26 16:19:37 +02:00
" Formatte une note pour Apogée (séparateur décimal: ' , ' ) "
2022-01-31 23:15:42 +01:00
# if not note and isinstance(note, float): changé le 31/1/2022, étrange ?
# return ""
2020-09-26 16:19:37 +02:00
try :
val = float ( note )
except ValueError :
return " "
2022-04-05 11:44:08 +02:00
return ( fmt % val ) . replace ( " . " , APO_DECIMAL_SEP )
2020-09-26 16:19:37 +02:00
def guess_data_encoding ( text , threshold = 0.6 ) :
""" Guess string encoding, using chardet heuristics.
2021-07-19 19:53:01 +02:00
Returns encoding , or None if detection failed ( confidence below threshold )
2020-09-26 16:19:37 +02:00
"""
r = chardet_detect ( text )
if r [ " confidence " ] < threshold :
return None
else :
return r [ " encoding " ]
def fix_data_encoding (
2022-01-07 17:58:34 +01:00
text : bytes ,
default_source_encoding = APO_INPUT_ENCODING ,
dest_encoding = APO_INPUT_ENCODING ,
) - > bytes :
2020-09-26 16:19:37 +02:00
""" Try to ensure that text is using dest_encoding
2021-01-01 18:40:47 +01:00
returns converted text , and a message describing the conversion .
2020-09-26 16:19:37 +02:00
"""
message = " "
detected_encoding = guess_data_encoding ( text )
if not detected_encoding :
if default_source_encoding != dest_encoding :
message = " converting from %s to %s " % (
default_source_encoding ,
dest_encoding ,
)
2021-07-12 15:13:10 +02:00
text = text . decode ( default_source_encoding ) . encode (
dest_encoding
) # XXX #py3 #sco8 à tester
2020-09-26 16:19:37 +02:00
else :
if detected_encoding != dest_encoding :
message = " converting from detected %s to %s " % (
detected_encoding ,
dest_encoding ,
)
2021-07-12 15:13:10 +02:00
text = text . decode ( detected_encoding ) . encode ( dest_encoding ) # XXX
2020-09-26 16:19:37 +02:00
return text , message
class StringIOFileLineWrapper ( object ) :
2022-01-07 17:58:34 +01:00
def __init__ ( self , data : str ) :
2021-08-15 15:01:13 +02:00
self . f = io . StringIO ( data )
2020-09-26 16:19:37 +02:00
self . lineno = 0
def close ( self ) :
return self . f . close ( )
def readline ( self ) :
self . lineno + = 1
return self . f . readline ( )
class DictCol ( dict ) :
" A dict, where we can add attributes "
pass
2021-07-09 23:31:16 +02:00
class ApoElt ( object ) :
2020-09-26 16:19:37 +02:00
""" Definition d ' un Element Apogee
sur plusieurs colonnes du fichier CSV
"""
def __init__ ( self , cols ) :
assert len ( cols ) > 0
assert len ( set ( [ c [ " Code " ] for c in cols ] ) ) == 1 # colonnes de meme code
assert len ( set ( [ c [ " Type Objet " ] for c in cols ] ) ) == 1 # colonnes de meme type
self . cols = cols
self . code = cols [ 0 ] [ " Code " ]
self . version = cols [ 0 ] [ " Version " ]
self . type_objet = cols [ 0 ] [ " Type Objet " ]
def append ( self , col ) :
assert col [ " Code " ] == self . code
if col [ " Type Objet " ] != self . type_objet :
log (
" Warning: ApoElt: duplicate id %s ( %s and %s ) "
% ( self . code , self . type_objet , col [ " Type Objet " ] )
)
self . type_objet = col [ " Type Objet " ]
self . cols . append ( col )
def __repr__ ( self ) :
return " ApoElt(code= ' %s ' , cols= %s ) " % ( self . code , pprint . pformat ( self . cols ) )
2021-07-09 23:31:16 +02:00
class EtuCol ( object ) :
2020-09-26 16:19:37 +02:00
""" Valeurs colonnes d ' un element pour un etudiant """
def __init__ ( self , nip , apo_elt , init_vals ) :
pass # XXX
ETUD_OK = " ok "
ETUD_ORPHELIN = " orphelin "
ETUD_NON_INSCRIT = " non_inscrit "
VOID_APO_RES = dict ( N = " " , B = " " , J = " " , R = " " , M = " " )
class ApoEtud ( dict ) :
2021-01-01 18:40:47 +01:00
""" Etudiant Apogee: """
2020-09-26 16:19:37 +02:00
def __init__ (
self ,
nip = " " ,
nom = " " ,
prenom = " " ,
naissance = " " ,
cols = { } ,
export_res_etape = True ,
export_res_sem = True ,
export_res_ues = True ,
export_res_modules = True ,
export_res_sdj = True ,
export_res_rat = True ,
) :
self [ " nip " ] = nip
self [ " nom " ] = nom
self [ " prenom " ] = prenom
self [ " naissance " ] = naissance
self . cols = cols # { col_id : value } colid = 'apoL_c0001'
self . new_cols = { } # { col_id : value to record in csv }
self . etud = None # etud ScoDoc
self . etat = None # ETUD_OK, ...
self . is_NAR = False # set to True si NARé dans un semestre
self . log = [ ]
self . has_logged_no_decision = False
self . export_res_etape = export_res_etape # VET, ...
self . export_res_sem = export_res_sem # elt_sem_apo
self . export_res_ues = export_res_ues
self . export_res_modules = export_res_modules
self . export_res_sdj = export_res_sdj # export meme si pas de decision de jury
self . export_res_rat = export_res_rat
2022-04-05 11:44:08 +02:00
self . fmt_note = functools . partial (
_apo_fmt_note , fmt = ScoDocSiteConfig . get_code_apo ( " NOTES_FMT " ) or " 3.2f "
)
2020-09-26 16:19:37 +02:00
def __repr__ ( self ) :
return " ApoEtud( nom= ' %s ' , nip= ' %s ' ) " % ( self [ " nom " ] , self [ " nip " ] )
2021-08-21 00:24:51 +02:00
def lookup_scodoc ( self , etape_formsemestre_ids ) :
2021-06-25 16:27:55 +02:00
""" Cherche l ' étudiant ScoDoc associé à cet étudiant Apogée.
S ' il n ' est pas trouvé ( état " orphelin " , dans Apo mais pas chez nous ) ,
met . etud à None .
Sinon , cherche le semestre , et met l ' état à ETUD_OK ou ETUD_NON_INSCRIT.
"""
2021-06-19 23:21:37 +02:00
etuds = sco_etud . get_etud_info ( code_nip = self [ " nip " ] , filled = True )
2020-09-26 16:19:37 +02:00
if not etuds :
# pas dans ScoDoc
self . etud = None
self . log . append ( " non inscrit dans ScoDoc " )
self . etat = ETUD_ORPHELIN
else :
self . etud = etuds [ 0 ]
# cherche le semestre ScoDoc correspondant à l'un de ceux de l'etape:
formsemestre_ids = { s [ " formsemestre_id " ] for s in self . etud [ " sems " ] }
self . in_formsemestre_ids = formsemestre_ids . intersection (
etape_formsemestre_ids
)
if not self . in_formsemestre_ids :
self . log . append (
" connu dans ScoDoc, mais pas inscrit dans un semestre de cette étape "
)
self . etat = ETUD_NON_INSCRIT
else :
self . etat = ETUD_OK
2021-08-21 00:24:51 +02:00
def associate_sco ( self , apo_data ) :
2020-09-26 16:19:37 +02:00
""" Recherche les valeurs des éléments Apogée pour cet étudiant
Set . new_cols
"""
self . col_elts = { } # {'V1RT': {'R': 'ADM', 'J': '', 'B': 20, 'N': '12.14'}}
if self . etat is None :
2021-08-21 00:24:51 +02:00
self . lookup_scodoc ( apo_data . etape_formsemestre_ids )
2020-09-26 16:19:37 +02:00
if self . etat != ETUD_OK :
self . new_cols = (
self . cols
) # etudiant inconnu, recopie les valeurs existantes dans Apo
else :
sco_elts = { } # valeurs trouvées dans ScoDoc code : { N, B, J, R }
for col_id in apo_data . col_ids [ 4 : ] :
code = apo_data . cols [ col_id ] [ " Code " ] # 'V1RT'
el = sco_elts . get (
code , None
) # {'R': ADM, 'J': '', 'B': 20, 'N': '12.14'}
if el is None : # pas déjà trouvé
2021-08-21 00:24:51 +02:00
cur_sem , autre_sem = self . etud_semestres_de_etape ( apo_data )
2020-09-26 16:19:37 +02:00
for sem in apo_data . sems_etape :
2021-08-21 00:24:51 +02:00
el = self . search_elt_in_sem ( code , sem , cur_sem , autre_sem )
2020-09-26 16:19:37 +02:00
if el != None :
sco_elts [ code ] = el
break
self . col_elts [ code ] = el
if el is None :
self . new_cols [ col_id ] = self . cols [ col_id ]
else :
try :
self . new_cols [ col_id ] = sco_elts [ code ] [
apo_data . cols [ col_id ] [ " Type Rés. " ]
]
except KeyError :
log (
" associate_sco: missing key, etud= %s \n code= ' %s ' \n etape= ' %s ' "
% ( self , code , apo_data . etape_apogee )
)
raise ScoValueError (
""" L ' élément %s n ' a pas de résultat: peut-être une erreur dans les codes sur le programme pédagogique (vérifier qu ' il est bien associé à une UE ou semestre)? """
% code
)
# recopie les 4 premieres colonnes (nom, ..., naissance):
for col_id in apo_data . col_ids [ : 4 ] :
self . new_cols [ col_id ] = self . cols [ col_id ]
2021-02-01 23:54:46 +01:00
# def unassociated_codes(self, apo_data):
# "list of apo elements for this student without a value in ScoDoc"
# codes = set([apo_data.cols[col_id].code for col_id in apo_data.col_ids])
# return codes - set(sco_elts)
2020-09-26 16:19:37 +02:00
2021-08-21 00:24:51 +02:00
def search_elt_in_sem ( self , code , sem , cur_sem , autre_sem ) :
2020-09-26 16:19:37 +02:00
"""
VET code jury etape
ELP élément pédagogique : UE , module
Autres éléments : résultats du semestre ou de l ' année scolaire:
= > VRTW1 : code additionnel au semestre ( " code élement semestre " , elt_sem_apo )
= > VRT1A : le même que le VET : ( " code élement annuel " , elt_annee_apo )
2021-01-01 18:40:47 +01:00
Attention , si le semestre couvre plusieurs étapes , indiquer les codes des éléments ,
2020-09-26 16:19:37 +02:00
séparés par des virgules .
Args :
code ( str ) : code apo de l ' element cherché
sem ( dict ) : semestre dans lequel on cherche l ' élément
cur_sem ( dict ) : semestre " courant " pour résultats annuels ( VET )
autre_sem ( dict ) : autre semestre utilisé pour calculé les résultats annuels ( VET )
2021-01-01 18:40:47 +01:00
2020-09-26 16:19:37 +02:00
Returns :
dict : with N , B , J , R keys , ou None si elt non trouvé
"""
etudid = self . etud [ " etudid " ]
2022-02-13 23:53:11 +01:00
formsemestre = FormSemestre . query . get_or_404 ( sem [ " formsemestre_id " ] )
nt : NotesTableCompat = res_sem . load_formsemestre_results ( formsemestre )
2020-09-26 16:19:37 +02:00
if etudid not in nt . identdict :
return None # etudiant non inscrit dans ce semestre
decision = nt . get_etud_decision_sem ( etudid )
if not self . export_res_sdj and not decision :
# pas de decision de jury, on n'enregistre rien
# (meme si démissionnaire)
if not self . has_logged_no_decision :
self . log . append ( " Pas de decision " )
self . has_logged_no_decision = True
return VOID_APO_RES
if decision and decision [ " code " ] == NAR :
self . is_NAR = True
# Element etape (annuel ou non):
if sco_formsemestre . sem_has_etape ( sem , code ) or (
2022-04-22 14:35:44 +02:00
code in { x . strip ( ) for x in sem [ " elt_annee_apo " ] . split ( " , " ) }
2020-09-26 16:19:37 +02:00
) :
export_res_etape = self . export_res_etape
2021-06-25 16:27:55 +02:00
if ( not export_res_etape ) and cur_sem :
2020-09-26 16:19:37 +02:00
# exporte toujours le résultat de l'étape si l'étudiant est diplômé
Se = sco_parcours_dut . SituationEtudParcours (
2021-08-19 10:28:35 +02:00
self . etud , cur_sem [ " formsemestre_id " ]
2020-09-26 16:19:37 +02:00
)
export_res_etape = Se . all_other_validated ( )
if export_res_etape :
2021-08-21 00:24:51 +02:00
return self . comp_elt_annuel ( etudid , cur_sem , autre_sem )
2020-09-26 16:19:37 +02:00
else :
return VOID_APO_RES
# Element semestre:
2022-04-22 14:35:44 +02:00
if code in { x . strip ( ) for x in sem [ " elt_sem_apo " ] . split ( " , " ) } :
2020-09-26 16:19:37 +02:00
if self . export_res_sem :
2021-08-21 00:24:51 +02:00
return self . comp_elt_semestre ( nt , decision , etudid )
2020-09-26 16:19:37 +02:00
else :
return VOID_APO_RES
# Elements UE
decisions_ue = nt . get_etud_decision_ues ( etudid )
2021-12-24 00:08:25 +01:00
for ue in nt . get_ues_stat_dict ( ) :
2022-04-22 14:35:44 +02:00
if ue [ " code_apogee " ] and code in {
x . strip ( ) for x in ue [ " code_apogee " ] . split ( " , " )
} :
2020-09-26 16:19:37 +02:00
if self . export_res_ues :
if decisions_ue and ue [ " ue_id " ] in decisions_ue :
ue_status = nt . get_etud_ue_status ( etudid , ue [ " ue_id " ] )
code_decision_ue = decisions_ue [ ue [ " ue_id " ] ] [ " code " ]
return dict (
2022-04-05 11:44:08 +02:00
N = self . fmt_note ( ue_status [ " moy " ] if ue_status else " " ) ,
2020-09-26 16:19:37 +02:00
B = 20 ,
J = " " ,
2022-01-21 00:46:45 +01:00
R = ScoDocSiteConfig . get_code_apo ( code_decision_ue ) ,
2020-09-26 16:19:37 +02:00
M = " " ,
)
else :
return VOID_APO_RES
else :
return VOID_APO_RES
# Elements Modules
2021-12-26 19:15:47 +01:00
modimpls = nt . get_modimpls_dict ( )
2020-09-26 16:19:37 +02:00
module_code_found = False
for modimpl in modimpls :
2022-04-22 14:35:44 +02:00
module = modimpl [ " module " ]
if module [ " code_apogee " ] and code in {
x . strip ( ) for x in module [ " code_apogee " ] . split ( " , " )
} :
2020-09-26 16:19:37 +02:00
n = nt . get_etud_mod_moy ( modimpl [ " moduleimpl_id " ] , etudid )
if n != " NI " and self . export_res_modules :
2022-04-05 11:44:08 +02:00
return dict ( N = self . fmt_note ( n ) , B = 20 , J = " " , R = " " )
2020-09-26 16:19:37 +02:00
else :
module_code_found = True
if module_code_found :
return VOID_APO_RES
#
return None # element Apogee non trouvé dans ce semestre
2021-08-21 00:24:51 +02:00
def comp_elt_semestre ( self , nt , decision , etudid ) :
2020-09-26 16:19:37 +02:00
""" Calcul résultat apo semestre """
2022-02-26 20:22:18 +01:00
if decision is None :
etud = Identite . query . get ( etudid )
nomprenom = etud . nomprenom if etud else " (inconnu) "
raise ScoValueError (
f " decision absente pour l ' étudiant { nomprenom } ( { etudid } ) "
)
2020-09-26 16:19:37 +02:00
# resultat du semestre
2022-01-21 00:46:45 +01:00
decision_apo = ScoDocSiteConfig . get_code_apo ( decision [ " code " ] )
2020-09-26 16:19:37 +02:00
note = nt . get_etud_moy_gen ( etudid )
2022-01-21 00:46:45 +01:00
if decision_apo == " DEF " or decision [ " code " ] == DEM or decision [ " code " ] == DEF :
2020-09-26 16:19:37 +02:00
note_str = " 0,01 " # note non nulle pour les démissionnaires
else :
2022-04-05 11:44:08 +02:00
note_str = self . fmt_note ( note )
2020-09-26 16:19:37 +02:00
return dict ( N = note_str , B = 20 , J = " " , R = decision_apo , M = " " )
2021-08-21 00:24:51 +02:00
def comp_elt_annuel ( self , etudid , cur_sem , autre_sem ) :
2020-09-26 16:19:37 +02:00
""" Calcul resultat annuel (VET) à partir du semestre courant
et de l ' autre (le suivant ou le précédent complétant l ' année scolaire )
"""
# Code annuel:
# - Note: moyenne des moyennes générales des deux semestres (pas vraiment de sens, mais faute de mieux)
# on pourrait aussi bien prendre seulement la note du dernier semestre (S2 ou S4). Paramétrable ?
# - Résultat jury:
# si l'autre est validé, code du semestre courant (ex: S1 (ADM), S2 (AJ) => année AJ)
# si l'autre n'est pas validé ou est DEF ou DEM, code de l'autre
#
# XXX cette règle est discutable, à valider
# print 'comp_elt_annuel cur_sem=%s autre_sem=%s' % (cur_sem['formsemestre_id'], autre_sem['formsemestre_id'])
if not cur_sem :
# l'étudiant n'a pas de semestre courant ?!
2021-08-31 19:32:12 +02:00
log ( " comp_elt_annuel: etudid %s has no cur_sem " % etudid )
2020-09-26 16:19:37 +02:00
return VOID_APO_RES
2022-02-13 23:53:11 +01:00
cur_formsemestre = FormSemestre . query . get_or_404 ( cur_sem [ " formsemestre_id " ] )
cur_nt : NotesTableCompat = res_sem . load_formsemestre_results ( cur_formsemestre )
2020-09-26 16:19:37 +02:00
cur_decision = cur_nt . get_etud_decision_sem ( etudid )
if not cur_decision :
# pas de decision => pas de résultat annuel
return VOID_APO_RES
if ( cur_decision [ " code " ] == RAT ) and not self . export_res_rat :
# ne touche pas aux RATs
return VOID_APO_RES
if not autre_sem :
# formations monosemestre, ou code VET semestriel,
# ou jury intermediaire et etudiant non redoublant...
2021-08-21 00:24:51 +02:00
return self . comp_elt_semestre ( cur_nt , cur_decision , etudid )
2020-09-26 16:19:37 +02:00
2022-01-21 00:46:45 +01:00
decision_apo = ScoDocSiteConfig . get_code_apo ( cur_decision [ " code " ] )
2020-09-26 16:19:37 +02:00
2022-02-13 23:53:11 +01:00
autre_formsemestre = FormSemestre . query . get_or_404 ( autre_sem [ " formsemestre_id " ] )
autre_nt : NotesTableCompat = res_sem . load_formsemestre_results (
autre_formsemestre
)
2020-09-26 16:19:37 +02:00
autre_decision = autre_nt . get_etud_decision_sem ( etudid )
if not autre_decision :
# pas de decision dans l'autre => pas de résultat annuel
return VOID_APO_RES
2022-01-21 00:46:45 +01:00
autre_decision_apo = ScoDocSiteConfig . get_code_apo ( autre_decision [ " code " ] )
2020-09-26 16:19:37 +02:00
if (
autre_decision_apo == " DEF "
2022-01-21 00:46:45 +01:00
or autre_decision [ " code " ] == DEM
2020-09-26 16:19:37 +02:00
or autre_decision [ " code " ] == DEF
) or (
decision_apo == " DEF "
2022-01-21 00:46:45 +01:00
or cur_decision [ " code " ] == DEM
2020-09-26 16:19:37 +02:00
or cur_decision [ " code " ] == DEF
) :
note_str = " 0,01 " # note non nulle pour les démissionnaires
else :
note = cur_nt . get_etud_moy_gen ( etudid )
autre_note = autre_nt . get_etud_moy_gen ( etudid )
# print 'note=%s autre_note=%s' % (note, autre_note)
try :
moy_annuelle = ( note + autre_note ) / 2
2021-07-09 19:50:40 +02:00
except TypeError :
2020-09-26 16:19:37 +02:00
moy_annuelle = " "
2022-04-05 11:44:08 +02:00
note_str = self . fmt_note ( moy_annuelle )
2020-09-26 16:19:37 +02:00
if code_semestre_validant ( autre_decision [ " code " ] ) :
decision_apo_annuelle = decision_apo
else :
decision_apo_annuelle = autre_decision_apo
return dict ( N = note_str , B = 20 , J = " " , R = decision_apo_annuelle , M = " " )
2021-08-21 00:24:51 +02:00
def etud_semestres_de_etape ( self , apo_data ) :
2020-09-26 16:19:37 +02:00
"""
Lorsqu ' on a une formation semestrialisée mais avec un code étape annuel,
il faut considérer les deux semestres ( ( S1 , S2 ) ou ( S3 , S4 ) ) pour calculer
le code annuel ( VET ou VRT1A ( voir elt_annee_apo ) ) .
Pour les jurys intermediaires ( janvier , S1 ou S3 ) : ( S2 ou S4 ) de la même étape lors d ' une année précédente ?
Renvoie le semestre " courant " et l ' autre semestre, ou None s ' il n ' y en a pas.
"""
# Cherche le semestre "courant":
cur_sems = [
sem
for sem in self . etud [ " sems " ]
if (
( sem [ " semestre_id " ] == apo_data . cur_semestre_id )
and ( apo_data . etape in sem [ " etapes " ] )
and (
2021-08-19 10:28:35 +02:00
sco_formsemestre . sem_in_annee_scolaire ( sem , apo_data . annee_scolaire )
2020-09-26 16:19:37 +02:00
)
)
]
if not cur_sems :
cur_sem = None
else :
# prend le plus recent avec decision
cur_sem = None
for sem in cur_sems :
2022-02-13 23:53:11 +01:00
formsemestre = FormSemestre . query . get_or_404 ( sem [ " formsemestre_id " ] )
nt : NotesTableCompat = res_sem . load_formsemestre_results ( formsemestre )
2020-09-26 16:19:37 +02:00
decision = nt . get_etud_decision_sem ( self . etud [ " etudid " ] )
if decision :
cur_sem = sem
break
if cur_sem is None :
cur_sem = cur_sems [ 0 ] # aucun avec decison, prend le plus recent
if apo_data . cur_semestre_id < = 0 :
return (
cur_sem ,
None ,
) # "autre_sem" non pertinent pour sessions sans semestres
if apo_data . jury_intermediaire : # jury de janvier
# Le semestre suivant: exemple 2 si on est en jury de S1
autre_semestre_id = apo_data . cur_semestre_id + 1
else :
# Le précédent (S1 si on est en S2)
autre_semestre_id = apo_data . cur_semestre_id - 1
# L'autre semestre DOIT être antérieur au courant indiqué par apo_data
if apo_data . periode is not None :
if apo_data . periode == 1 :
courant_annee_debut = apo_data . annee_scolaire
courant_mois_debut = 9 # periode = 1 (sept-jan)
elif apo_data . periode == 2 :
courant_annee_debut = apo_data . annee_scolaire + 1
courant_mois_debut = 1 # ou 2 (fev-jul)
else :
raise ValueError ( " invalid pediode value ! " ) # bug ?
courant_date_debut = " %d - %02d -01 " % (
courant_annee_debut ,
courant_mois_debut ,
)
else :
courant_date_debut = " 9999-99-99 "
# etud['sems'] est la liste des semestres de l'étudiant, triés par date,
# le plus récemment effectué en tête.
# Cherche les semestres (antérieurs) de l'indice autre de la même étape apogée
# s'il y en a plusieurs, choisit le plus récent ayant une décision
autres_sems = [ ]
for sem in self . etud [ " sems " ] :
if (
sem [ " semestre_id " ] == autre_semestre_id
and apo_data . etape_apogee in sem [ " etapes " ]
) :
if (
sem [ " date_debut_iso " ] < courant_date_debut
) : # on demande juste qu'il ait démarré avant
autres_sems . append ( sem )
if not autres_sems :
autre_sem = None
elif len ( autres_sems ) == 1 :
autre_sem = autres_sems [ 0 ]
else :
autre_sem = None
for sem in autres_sems :
2022-02-13 23:53:11 +01:00
formsemestre = FormSemestre . query . get_or_404 ( sem [ " formsemestre_id " ] )
nt : NotesTableCompat = res_sem . load_formsemestre_results ( formsemestre )
2020-09-26 16:19:37 +02:00
decision = nt . get_etud_decision_sem ( self . etud [ " etudid " ] )
if decision :
autre_sem = sem
break
if autre_sem is None :
autre_sem = autres_sems [ 0 ] # aucun avec decision, prend le plus recent
return cur_sem , autre_sem
2021-07-09 23:31:16 +02:00
class ApoData ( object ) :
2020-09-26 16:19:37 +02:00
def __init__ (
self ,
2022-01-07 17:58:34 +01:00
data : str ,
2020-09-26 16:19:37 +02:00
periode = None ,
export_res_etape = True ,
export_res_sem = True ,
export_res_ues = True ,
export_res_modules = True ,
export_res_sdj = True ,
export_res_rat = True ,
orig_filename = None ,
) :
""" Lecture du fichier CSV Apogée
Regroupe les élements importants d ' un fichier CSV Apogée
periode = 1 ( sept - jan ) ou 2 ( fev - jul ) , mais cette info n ' est pas
( toujours ) présente dans les CSV Apogée et doit être indiquée par l ' utilisateur
Laisser periode à None si etape en 1 semestre ( LP , décalés , . . . )
"""
self . export_res_etape = export_res_etape # VET, ...
self . export_res_sem = export_res_sem # elt_sem_apo
self . export_res_ues = export_res_ues
self . export_res_modules = export_res_modules
self . export_res_sdj = export_res_sdj
self . export_res_rat = export_res_rat
self . orig_filename = orig_filename
self . periode = periode #
try :
self . read_csv ( data )
2021-12-03 14:13:49 +01:00
except ScoFormatError as e :
2020-09-26 16:19:37 +02:00
# essaie de retrouver le nom du fichier pour enrichir le message d'erreur
filename = " "
if self . orig_filename is None :
if hasattr ( self , " titles " ) :
filename = self . titles . get ( " apoC_Fichier_Exp " , filename )
else :
filename = self . orig_filename
2021-12-03 14:13:49 +01:00
raise ScoFormatError (
2020-09-26 16:19:37 +02:00
" <h3>Erreur lecture du fichier Apogée <tt> %s </tt></h3><p> " % filename
+ e . args [ 0 ]
+ " </p> "
2022-01-07 17:58:34 +01:00
) from e
2020-09-26 16:19:37 +02:00
self . etape_apogee = self . get_etape_apogee ( ) # 'V1RT'
self . vdi_apogee = self . get_vdi_apogee ( ) # '111'
self . etape = ApoEtapeVDI ( etape = self . etape_apogee , vdi = self . vdi_apogee )
self . cod_dip_apogee = self . get_cod_dip_apogee ( )
self . annee_scolaire = self . get_annee_scolaire ( )
self . jury_intermediaire = (
False # True si jury à mi-étape, eg jury de S1 dans l'étape (S1, S2)
)
log (
" ApoData( periode= %s , annee_scolaire= %s ) "
% ( self . periode , self . annee_scolaire )
)
def set_periode ( self , periode ) : # currently unused
self . periode = periode
2021-08-21 00:24:51 +02:00
def setup ( self ) :
2021-01-01 18:40:47 +01:00
""" Recherche semestres ScoDoc concernés """
2021-08-21 00:24:51 +02:00
self . sems_etape = comp_apo_sems ( self . etape_apogee , self . annee_scolaire )
2020-09-26 16:19:37 +02:00
self . etape_formsemestre_ids = { s [ " formsemestre_id " ] for s in self . sems_etape }
if self . periode != None :
self . sems_periode = [
s
for s in self . sems_etape
if ( s [ " periode " ] == self . periode ) or s [ " semestre_id " ] < 0
]
if not self . sems_periode :
log ( " ** Warning: ApoData.setup: sems_periode is empty " )
log (
" ** (periode= %s , sems_etape [periode]= %s ) "
% ( self . periode , [ s [ " periode " ] for s in self . sems_etape ] )
)
self . sems_periode = None
self . cur_semestre_id = - 1 # ?
else :
self . cur_semestre_id = self . sems_periode [ 0 ] [ " semestre_id " ]
# Les semestres de la période ont le même indice, n'est-ce pas ?
if not all (
self . cur_semestre_id == s [ " semestre_id " ] for s in self . sems_periode
) :
# debugging information
import pprint
log ( " *** ApoData.set() error ! " )
log (
" ApoData( periode= %s , annee_scolaire= %s , cur_semestre_id= %s ) "
% ( self . periode , self . annee_scolaire , self . cur_semestre_id )
)
log ( " %d semestres dans la periode: " % len ( self . sems_periode ) )
for s in self . sems_periode :
log ( pprint . pformat ( s ) )
raise ValueError (
" incohérence détectée (contacter les développeurs) "
)
# Cette condition sera inadaptée si semestres décalés
# (mais ils n'ont pas d'étape annuelle, espérons!)
if self . cur_semestre_id > = 0 : # non pertinent pour sessions sans semestres
self . jury_intermediaire = ( self . cur_semestre_id % 2 ) != 0
else :
self . sems_periode = None
2021-08-31 19:32:12 +02:00
def read_csv ( self , data : str ) :
2020-09-26 16:19:37 +02:00
if not data :
2021-12-03 14:13:49 +01:00
raise ScoFormatError ( " Fichier Apogée vide ! " )
2021-08-31 19:32:12 +02:00
f = StringIOFileLineWrapper ( data ) # pour traiter comme un fichier
2020-09-26 16:19:37 +02:00
# check that we are at the begining of Apogee CSV
line = f . readline ( ) . strip ( )
if line != " XX-APO_TITRES-XX " :
2021-12-03 14:13:49 +01:00
raise ScoFormatError ( " format incorrect: pas de XX-APO_TITRES-XX " )
2020-09-26 16:19:37 +02:00
# 1-- En-tête: du début jusqu'à la balise XX-APO_VALEURS-XX
2022-01-07 17:58:34 +01:00
try :
idx = data . index ( " XX-APO_VALEURS-XX " )
except ValueError as exc :
raise ScoFormatError ( " format incorrect: pas de XX-APO_VALEURS-XX " ) from exc
2021-08-31 19:32:12 +02:00
self . header = data [ : idx ]
2020-09-26 16:19:37 +02:00
# 2-- Titres:
# on va y chercher apoC_Fichier_Exp qui donnera le nom du fichier
# ainsi que l'année scolaire et le code diplôme.
self . titles = _apo_read_TITRES ( f )
# 3-- La section XX-APO_TYP_RES-XX est ignorée:
line = f . readline ( ) . strip ( )
if line != " XX-APO_TYP_RES-XX " :
2021-12-03 14:13:49 +01:00
raise ScoFormatError ( " format incorrect: pas de XX-APO_TYP_RES-XX " )
2020-09-26 16:19:37 +02:00
_apo_skip_section ( f )
# 4-- Définition de colonnes: (on y trouve aussi l'étape)
line = f . readline ( ) . strip ( )
if line != " XX-APO_COLONNES-XX " :
2021-12-03 14:13:49 +01:00
raise ScoFormatError ( " format incorrect: pas de XX-APO_COLONNES-XX " )
2020-09-26 16:19:37 +02:00
self . cols = _apo_read_cols ( f )
self . apo_elts = self . _group_elt_cols ( self . cols )
# 5-- Section XX-APO_VALEURS-XX
# Lecture des étudiants et de leurs résultats
while True : # skip
line = f . readline ( )
if not line :
2021-12-03 14:13:49 +01:00
raise ScoFormatError ( " format incorrect: pas de XX-APO_VALEURS-XX " )
2020-09-26 16:19:37 +02:00
if line . strip ( ) == " XX-APO_VALEURS-XX " :
break
self . column_titles = f . readline ( )
self . col_ids = self . column_titles . strip ( ) . split ( )
self . etuds = self . apo_read_etuds ( f )
self . etud_by_nip = { e [ " nip " ] : e for e in self . etuds }
def get_etud_by_nip ( self , nip ) :
" returns ApoEtud with a given NIP code "
return self . etud_by_nip [ nip ]
def _group_elt_cols ( self , cols ) :
""" Return ordered dict of ApoElt from list of ApoCols.
Clé : id apogée , eg ' V1RT ' , ' V1GE2201 ' , . . .
Valeur : ApoElt , avec les attributs code , type_objet
2021-01-01 18:40:47 +01:00
Si les id Apogée ne sont pas uniques ( ce n ' est pas garanti), garde le premier
2020-09-26 16:19:37 +02:00
"""
elts = collections . OrderedDict ( )
2021-07-09 17:47:06 +02:00
for col_id in sorted ( list ( cols . keys ( ) ) , reverse = True ) :
2020-09-26 16:19:37 +02:00
col = cols [ col_id ]
if col [ " Code " ] in elts :
elts [ col [ " Code " ] ] . append ( col )
else :
elts [ col [ " Code " ] ] = ApoElt ( [ col ] )
return elts # { code apo : ApoElt }
def apo_read_etuds ( self , f ) :
""" Lecture des etudiants (et resultats) du fichier CSV Apogée
- > liste de dicts
"""
L = [ ]
while True :
line = f . readline ( )
if not line :
break
if not line . strip ( ) :
continue # silently ignore blank lines
line = line . strip ( APO_NEWLINE )
fs = line . split ( APO_SEP )
cols = { } # { col_id : value }
for i in range ( len ( fs ) ) :
cols [ self . col_ids [ i ] ] = fs [ i ]
L . append (
ApoEtud (
nip = fs [ 0 ] , # id etudiant
nom = fs [ 1 ] ,
prenom = fs [ 2 ] ,
naissance = fs [ 3 ] ,
cols = cols ,
export_res_etape = self . export_res_etape ,
export_res_sem = self . export_res_sem ,
export_res_ues = self . export_res_ues ,
export_res_modules = self . export_res_modules ,
export_res_sdj = self . export_res_sdj ,
export_res_rat = self . export_res_rat ,
)
)
return L
def get_etape_apogee ( self ) :
2021-01-01 18:40:47 +01:00
""" Le code etape: ' V1RT ' , donné par le code de l ' élément VET """
2020-09-26 16:19:37 +02:00
for elt in self . apo_elts . values ( ) :
if elt . type_objet == " VET " :
return elt . code
raise ScoValueError ( " Pas de code etape Apogee (manque élément VET) " )
def get_vdi_apogee ( self ) :
""" le VDI (version de diplôme), stocké dans l ' élément VET
( note : on pourrait peut - être aussi bien le récupérer dans l ' en-tête XX-APO_TITRES-XX apoC_cod_vdi)
"""
for elt in self . apo_elts . values ( ) :
if elt . type_objet == " VET " :
return elt . version
raise ScoValueError ( " Pas de VDI Apogee (manque élément VET) " )
def get_cod_dip_apogee ( self ) :
""" Le code diplôme, indiqué dans l ' en-tête de la maquette
exemple : VDTRT
Retourne ' ' si absent .
"""
return self . titles . get ( " apoC_cod_dip " , " " )
def get_annee_scolaire ( self ) :
""" Annee scolaire du fichier Apogee: un integer
= annee du mois de septembre de début
"""
m = re . match ( " [12][0-9] {3} " , self . titles [ " apoC_annee " ] )
if not m :
2021-12-03 14:13:49 +01:00
raise ScoFormatError (
2020-09-26 16:19:37 +02:00
' Annee scolaire (apoC_annee) invalide: " %s " ' % self . titles [ " apoC_annee " ]
)
return int ( m . group ( 0 ) )
def write_header ( self , f ) :
""" write apo CSV header on f
( beginning of CSV until columns titles just after XX - APO_VALEURS - XX line )
"""
f . write ( self . header )
f . write ( APO_NEWLINE )
f . write ( " XX-APO_VALEURS-XX " + APO_NEWLINE )
f . write ( self . column_titles )
def write_etuds ( self , f ) :
2021-01-01 18:40:47 +01:00
""" write apo CSV etuds on f """
2020-09-26 16:19:37 +02:00
for e in self . etuds :
fs = [ ] # e['nip'], e['nom'], e['prenom'], e['naissance'] ]
for col_id in self . col_ids :
try :
fs . append ( str ( e . new_cols [ col_id ] ) )
except KeyError :
log (
" Error: %s %s missing column key %s "
% ( e [ " nip " ] , e [ " nom " ] , col_id )
)
log ( " Details: \n e = %s " % pprint . pformat ( e ) )
log ( " col_ids= %s " % pprint . pformat ( self . col_ids ) )
log ( " etudiant ignore. \n " )
f . write ( APO_SEP . join ( fs ) + APO_NEWLINE )
def list_unknown_elements ( self ) :
""" Liste des codes des elements Apogee non trouvés dans ScoDoc
( après traitement de tous les étudiants )
"""
s = set ( )
for e in self . etuds :
ul = [ code for code in e . col_elts if e . col_elts [ code ] is None ]
s . update ( ul )
L = list ( s )
L . sort ( )
return L
def list_elements ( self ) :
""" Liste les codes des elements Apogée de la maquette
et ceux des semestres ScoDoc associés
Retourne deux ensembles
"""
try :
maq_elems = { self . cols [ col_id ] [ " Code " ] for col_id in self . col_ids [ 4 : ] }
except KeyError :
# une colonne déclarée dans l'en-tête n'est pas présente
declared = self . col_ids [ 4 : ] # id des colones dans l'en-tête
present = sorted ( self . cols . keys ( ) ) # colones presentes
log ( " Fichier Apogee invalide: " )
log ( " Colonnes declarees: %s " % declared )
log ( " Colonnes presentes: %s " % present )
2021-12-03 14:13:49 +01:00
raise ScoFormatError (
2020-09-26 16:19:37 +02:00
""" Fichier Apogee invalide<br/>Colonnes declarees: <tt> %s </tt>
< br / > Colonnes presentes : < tt > % s < / tt > """
% ( declared , present )
)
# l'ensemble de tous les codes des elements apo des semestres:
2021-07-09 17:47:06 +02:00
sem_elems = reduce ( set . union , list ( self . get_codes_by_sem ( ) . values ( ) ) , set ( ) )
2020-09-26 16:19:37 +02:00
return maq_elems , sem_elems
def get_codes_by_sem ( self ) :
2022-04-22 14:35:44 +02:00
""" Pour chaque semestre associé, donne l ' ensemble des codes de cette maquette Apogée
qui s ' y trouvent (dans le semestre, les UE ou les modules).
Return : { formsemestre_id : { ' code1 ' , ' code2 ' , . . . } }
2020-09-26 16:19:37 +02:00
"""
codes_by_sem = { }
for sem in self . sems_etape :
s = set ( )
codes_by_sem [ sem [ " formsemestre_id " ] ] = s
for col_id in self . col_ids [ 4 : ] :
code = self . cols [ col_id ] [ " Code " ] # 'V1RT'
# associé à l'étape, l'année ou les semestre:
if (
sco_formsemestre . sem_has_etape ( sem , code )
2022-04-22 14:35:44 +02:00
or ( code in { x . strip ( ) for x in sem [ " elt_sem_apo " ] . split ( " , " ) } )
or ( code in { x . strip ( ) for x in sem [ " elt_annee_apo " ] . split ( " , " ) } )
2020-09-26 16:19:37 +02:00
) :
s . add ( code )
continue
# associé à une UE:
2022-02-13 23:53:11 +01:00
formsemestre = FormSemestre . query . get_or_404 ( sem [ " formsemestre_id " ] )
nt : NotesTableCompat = res_sem . load_formsemestre_results ( formsemestre )
2021-12-24 00:08:25 +01:00
for ue in nt . get_ues_stat_dict ( ) :
2022-04-22 14:35:44 +02:00
if ue [ " code_apogee " ] :
codes = { x . strip ( ) for x in ue [ " code_apogee " ] . split ( " , " ) }
if code in codes :
s . add ( code )
continue
2020-09-26 16:19:37 +02:00
# associé à un module:
2021-12-26 19:15:47 +01:00
modimpls = nt . get_modimpls_dict ( )
2020-09-26 16:19:37 +02:00
for modimpl in modimpls :
2022-04-22 14:35:44 +02:00
module = modimpl [ " module " ]
if module [ " code_apogee " ] :
codes = { x . strip ( ) for x in module [ " code_apogee " ] . split ( " , " ) }
if code in codes :
s . add ( code )
continue
2020-09-26 16:19:37 +02:00
# log('codes_by_sem=%s' % pprint.pformat(codes_by_sem))
return codes_by_sem
def build_cr_table ( self ) :
2021-01-01 18:40:47 +01:00
""" Table compte rendu des décisions """
2020-09-26 16:19:37 +02:00
CR = [ ] # tableau compte rendu des decisions
for e in self . etuds :
cr = {
" NIP " : e [ " nip " ] ,
" nom " : e [ " nom " ] ,
" prenom " : e [ " prenom " ] ,
" est_NAR " : e . is_NAR ,
" commentaire " : " ; " . join ( e . log ) ,
}
if e . col_elts and e . col_elts [ self . etape_apogee ] != None :
cr [ " etape " ] = e . col_elts [ self . etape_apogee ] . get ( " R " , " " )
cr [ " etape_note " ] = e . col_elts [ self . etape_apogee ] . get ( " N " , " " )
else :
cr [ " etape " ] = " "
cr [ " etape_note " ] = " "
CR . append ( cr )
columns_ids = [ " NIP " , " nom " , " prenom " ]
columns_ids . extend ( ( " etape " , " etape_note " , " est_NAR " , " commentaire " ) )
T = GenTable (
columns_ids = columns_ids ,
titles = dict ( zip ( columns_ids , columns_ids ) ) ,
rows = CR ,
xls_sheet_name = " Decisions ScoDoc " ,
)
return T
def _apo_read_cols ( f ) :
2021-01-01 18:40:47 +01:00
""" Lecture colonnes apo :
2020-09-26 16:19:37 +02:00
Démarre après la balise XX - APO_COLONNES - XX
et s ' arrête après la balise APO_COL_VAL_FIN
Colonne Apogee : les champs sont données par la ligne
2021-01-01 18:40:47 +01:00
apoL_a01_code de la section XX - APO_COLONNES - XX
2020-09-26 16:19:37 +02:00
col_id est apoL_c0001 , apoL_c0002 , . . .
2021-01-01 18:40:47 +01:00
2020-09-26 16:19:37 +02:00
: return : { col_id : { title : value } }
Example : { ' apoL_c0001 ' : { ' Type Objet ' : ' VET ' , ' Code ' : ' V1IN ' , . . . } , . . . }
"""
line = f . readline ( ) . strip ( " " + APO_NEWLINE )
fs = line . split ( APO_SEP )
if fs [ 0 ] != " apoL_a01_code " :
2021-12-03 14:13:49 +01:00
raise ScoFormatError ( " invalid line: %s (expecting apoL_a01_code) " % line )
2020-09-26 16:19:37 +02:00
col_keys = fs
while True : # skip premiere partie (apoL_a02_nom, ...)
line = f . readline ( ) . strip ( " " + APO_NEWLINE )
if line == " APO_COL_VAL_DEB " :
break
# après APO_COL_VAL_DEB
cols = { }
i = 0
while True :
line = f . readline ( ) . strip ( " " + APO_NEWLINE )
if line == " APO_COL_VAL_FIN " :
break
i + = 1
fs = line . split ( APO_SEP )
# print fs[0], len(fs)
# sanity check
col_id = fs [ 0 ] # apoL_c0001, ...
if col_id in cols :
2021-12-03 14:13:49 +01:00
raise ScoFormatError ( " duplicate column definition: %s " % col_id )
2020-09-26 16:19:37 +02:00
m = re . match ( r " ^apoL_c([0-9] {4} )$ " , col_id )
if not m :
2021-12-03 14:13:49 +01:00
raise ScoFormatError (
2021-02-01 23:54:46 +01:00
" invalid column id: %s (expecting apoL_c %04d ) " % ( line , col_id )
)
2020-09-26 16:19:37 +02:00
if int ( m . group ( 1 ) ) != i :
2021-12-03 14:13:49 +01:00
raise ScoFormatError ( " invalid column id: %s for index %s " % ( col_id , i ) )
2020-09-26 16:19:37 +02:00
2021-07-09 17:47:06 +02:00
cols [ col_id ] = DictCol ( list ( zip ( col_keys , fs ) ) )
2020-09-26 16:19:37 +02:00
cols [ col_id ] . lineno = f . lineno # for debuging purpose
return cols
def _apo_read_TITRES ( f ) :
" Lecture section TITRES du fichier Apogée, renvoie dict "
d = { }
while True :
line = f . readline ( ) . strip (
" " + APO_NEWLINE
) # ne retire pas le \t (pour les clés vides)
if not line . strip ( ) : # stoppe sur ligne pleines de \t
break
fields = line . split ( APO_SEP )
if len ( fields ) == 2 :
k , v = fields
else :
log ( " Error read CSV: \n line= %s \n fields= %s " % ( line , fields ) )
log ( dir ( f ) )
2021-12-03 14:13:49 +01:00
raise ScoFormatError (
2020-09-26 16:19:37 +02:00
" Fichier Apogee incorrect (section titres, %d champs au lieu de 2) "
% len ( fields )
)
d [ k ] = v
#
if not d . get ( " apoC_Fichier_Exp " , None ) :
2021-12-03 14:13:49 +01:00
raise ScoFormatError ( " Fichier Apogee incorrect: pas de titre apoC_Fichier_Exp " )
2020-09-26 16:19:37 +02:00
# keep only basename: may be a windows or unix pathname
s = d [ " apoC_Fichier_Exp " ] . split ( " / " ) [ - 1 ]
s = s . split ( " \\ " ) [ - 1 ] # for DOS paths, eg C:\TEMP\VL4RT_V3ASR.TXT
d [ " apoC_Fichier_Exp " ] = s
return d
def _apo_skip_section ( f ) :
" Saute section Apo: s ' arrete apres ligne vide "
while True :
line = f . readline ( ) . strip ( )
if not line :
break
# -------------------------------------
2021-08-21 00:24:51 +02:00
def comp_apo_sems ( etape_apogee , annee_scolaire ) :
2020-09-26 16:19:37 +02:00
"""
: param etape_apogee : etape ( string or ApoEtapeVDI )
: param annee_scolaire : annee ( int )
: return : list of sems for etape_apogee in annee_scolaire
"""
return sco_formsemestre . list_formsemestre_by_etape (
2021-08-19 10:28:35 +02:00
etape_apo = str ( etape_apogee ) , annee_scolaire = annee_scolaire
2020-09-26 16:19:37 +02:00
)
2021-08-21 00:24:51 +02:00
def nar_etuds_table ( apo_data , NAR_Etuds ) :
2021-01-01 18:40:47 +01:00
""" Liste les NAR -> excel table """
2020-09-26 16:19:37 +02:00
code_etape = apo_data . etape_apogee
today = datetime . datetime . today ( ) . strftime ( " %d / % m/ % y " )
L = [ ]
NAR_Etuds . sort ( key = lambda k : k [ " nom " ] )
for e in NAR_Etuds :
L . append (
{
" nom " : e [ " nom " ] ,
" prenom " : e [ " prenom " ] ,
" c0 " : " " ,
" c1 " : " AD " ,
" etape " : code_etape ,
" c3 " : " " ,
" c4 " : " " ,
" c5 " : " " ,
" c6 " : " N " ,
" c7 " : " " ,
" c8 " : " " ,
" NIP " : e [ " nip " ] ,
" c10 " : " " ,
" c11 " : " " ,
" c12 " : " " ,
" c13 " : " NAR - Jury " ,
" date " : today ,
}
)
columns_ids = (
" NIP " ,
" nom " ,
" prenom " ,
" etape " ,
" c0 " ,
" c1 " ,
" c3 " ,
" c4 " ,
" c5 " ,
" c6 " ,
" c7 " ,
" c8 " ,
" c10 " ,
" c11 " ,
" c12 " ,
" c13 " ,
" date " ,
)
T = GenTable (
columns_ids = columns_ids ,
titles = dict ( zip ( columns_ids , columns_ids ) ) ,
rows = L ,
xls_sheet_name = " NAR ScoDoc " ,
)
return T . excel ( )
def export_csv_to_apogee (
2022-01-07 17:58:34 +01:00
apo_csv_data : str ,
2020-09-26 16:19:37 +02:00
periode = None ,
dest_zip = None ,
export_res_etape = True ,
export_res_sem = True ,
export_res_ues = True ,
export_res_modules = True ,
export_res_sdj = True ,
export_res_rat = True ,
) :
2021-01-01 18:40:47 +01:00
""" Genere un fichier CSV Apogée
2020-09-26 16:19:37 +02:00
à partir d ' un fichier CSV Apogée vide (ou partiellement rempli)
et des résultats ScoDoc .
Si dest_zip , ajoute les fichiers générés à ce zip
sinon crée un zip et le publie
"""
apo_data = ApoData (
apo_csv_data ,
periode = periode ,
export_res_etape = export_res_etape ,
export_res_sem = export_res_sem ,
export_res_ues = export_res_ues ,
export_res_modules = export_res_modules ,
export_res_sdj = export_res_sdj ,
export_res_rat = export_res_rat ,
)
2021-08-21 00:24:51 +02:00
apo_data . setup ( ) # -> .sems_etape
2020-09-26 16:19:37 +02:00
for e in apo_data . etuds :
2021-08-21 00:24:51 +02:00
e . lookup_scodoc ( apo_data . etape_formsemestre_ids )
e . associate_sco ( apo_data )
2020-09-26 16:19:37 +02:00
# Ré-écrit le fichier Apogée
2021-08-15 15:01:13 +02:00
f = io . StringIO ( )
2020-09-26 16:19:37 +02:00
apo_data . write_header ( f )
apo_data . write_etuds ( f )
# Table des NAR:
NAR_Etuds = [ e for e in apo_data . etuds if e . is_NAR ]
if NAR_Etuds :
2021-08-21 00:24:51 +02:00
nar_xls = nar_etuds_table ( apo_data , NAR_Etuds )
2020-09-26 16:19:37 +02:00
else :
nar_xls = None
# Journaux & Comptes-rendus
# Orphelins: etudiants dans fichier Apogée mais pas dans ScoDoc
Apo_Non_ScoDoc = [ e for e in apo_data . etuds if e . etat == ETUD_ORPHELIN ]
# Non inscrits: connus de ScoDoc mais pas inscrit dans l'étape cette année
Apo_Non_ScoDoc_Inscrits = [ e for e in apo_data . etuds if e . etat == ETUD_NON_INSCRIT ]
# CR table
cr_table = apo_data . build_cr_table ( )
cr_xls = cr_table . excel ( )
# Create ZIP
if not dest_zip :
2021-08-31 19:32:12 +02:00
data = io . BytesIO ( )
2020-09-26 16:19:37 +02:00
dest_zip = ZipFile ( data , " w " )
my_zip = True
else :
my_zip = False
# Ensure unique filenames
filename = apo_data . titles [ " apoC_Fichier_Exp " ]
basename , ext = os . path . splitext ( filename )
csv_filename = filename
if csv_filename in dest_zip . namelist ( ) :
basename = filename + " - " + apo_data . vdi_apogee
csv_filename = basename + ext
nf = 1
tmplname = basename
while csv_filename in dest_zip . namelist ( ) :
basename = tmplname + " - %d " % nf
csv_filename = basename + ext
nf + = 1
log_filename = " scodoc- " + basename + " .log.txt "
2021-08-12 14:49:53 +02:00
nar_filename = basename + " -nar " + scu . XLSX_SUFFIX
cr_filename = basename + " -decisions " + scu . XLSX_SUFFIX
2020-09-26 16:19:37 +02:00
2021-08-15 15:01:13 +02:00
logf = io . StringIO ( )
2020-09-26 16:19:37 +02:00
logf . write ( " export_to_apogee du %s \n \n " % time . ctime ( ) )
logf . write ( " Semestres ScoDoc sources: \n " )
for sem in apo_data . sems_etape :
logf . write ( " \t %(titremois)s \n " % sem )
logf . write ( " Periode: %s \n " % periode )
logf . write ( " export_res_etape: %s \n " % int ( export_res_etape ) )
logf . write ( " export_res_sem: %s \n " % int ( export_res_sem ) )
logf . write ( " export_res_ues: %s \n " % int ( export_res_ues ) )
logf . write ( " export_res_modules: %s \n " % int ( export_res_modules ) )
logf . write ( " export_res_sdj: %s \n " % int ( export_res_sdj ) )
logf . write (
" \n Etudiants Apogee non trouves dans ScoDoc: \n "
+ " \n " . join (
[ " %s \t %s \t %s " % ( e [ " nip " ] , e [ " nom " ] , e [ " prenom " ] ) for e in Apo_Non_ScoDoc ]
)
)
logf . write (
" \n Etudiants Apogee non inscrits sur ScoDoc dans cette étape: \n "
+ " \n " . join (
[
" %s \t %s \t %s " % ( e [ " nip " ] , e [ " nom " ] , e [ " prenom " ] )
for e in Apo_Non_ScoDoc_Inscrits
]
)
)
logf . write (
" \n \n Elements Apogee inconnus dans ces semestres ScoDoc: \n "
+ " \n " . join ( apo_data . list_unknown_elements ( ) )
)
log ( logf . getvalue ( ) ) # sortie aussi sur le log ScoDoc
2021-08-31 19:32:12 +02:00
csv_data = f . getvalue ( ) . encode ( APO_OUTPUT_ENCODING )
2020-09-26 16:19:37 +02:00
# Write data to ZIP
dest_zip . writestr ( csv_filename , csv_data )
dest_zip . writestr ( log_filename , logf . getvalue ( ) )
if nar_xls :
dest_zip . writestr ( nar_filename , nar_xls )
dest_zip . writestr ( cr_filename , cr_xls )
if my_zip :
dest_zip . close ( )
2021-08-31 19:32:12 +02:00
data . seek ( 0 )
return send_file (
data ,
mimetype = " application/zip " ,
2021-08-31 20:18:50 +02:00
download_name = scu . sanitize_filename ( basename + " -scodoc.zip " ) ,
2021-08-31 19:32:12 +02:00
as_attachment = True ,
2020-09-26 16:19:37 +02:00
)
else :
return None # zip modified in place