Projet

Général

Profil

sconet.py

Fichier corrigé pour Base élèves en 18.1.1.2.0 - Laurent Couillaud, 30/05/2018 04:23

Télécharger (19,8 ko)

 
1
# -*- coding: utf-8 -*-
2
###########################################################################
3
#
4
# Eole NG - 2011
5
# Copyright Pole de Competence Eole  (Ministere Education - Academie Dijon)
6
# Licence CeCill  http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
7
# eole@ac-dijon.fr
8
#
9
###########################################################################
10
"""
11
 librairie pour le parsing des fichiers de données Sconet/STS-Web
12

13
 - parse_sco_eleves : parsing des fichiers XML issus de Sconet
14
 - parse_sts_profs  : parsing des fichiers XML issus de STS-Web
15

16
"""
17

    
18
from scribe.eoletools import replace_cars, formate_civilite, \
19
not_empty, is_empty, formate_date, replace_more_cars
20
from scribe.importation import log
21
from scribe.importation.config import DEBUG_NUM
22
from scribe.storage import Eleve, \
23
Responsable, Adresse, JointureResponsableEleve, \
24
Classe, Niveau, Enseignant, Administratif, \
25
EnsClasse, JointureClasseEnseignant, \
26
Matiere, JointureMatiereEnseignant, \
27
Groupe, JointureGroupeUser, Service
28
from scribe.parsing.tools import parse_xml
29
from scribe.parsing.nomenclature import REGIMES
30

    
31
##########################################
32
# Extraction Sconet Elèves & Responsables
33
# Fichiers :
34
# - ElevesSansAdresses.xml
35
# - ResponsablesAvecAdresses.xml
36
##########################################
37

    
38
ERRMEF = "Le niveau portant le code MEF %s n'est pas défini dans la nomenclature"
39

    
40
def parse_sco_divisions(storage, structure_file, nomenclature_file):
41
    """
42
    Liaison Division (classe) - MEF (niveau)
43
    d'après : Structures.xml et Nomenclatures.xml
44
    """
45
    num = 0
46
    log.infolog("Lecture des classes et des niveaux...", title=True)
47
    niveaux = {}
48
    context, _ = parse_xml(nomenclature_file, 'MEF')
49
    for _, tnom in context:
50
        mef = tnom.attrib['CODE_MEF']
51
        niveaux[mef] = {}
52
        clean_formation = replace_cars(tnom.find('FORMATION').text)
53
        niveaux[mef]['label'] = unicode(clean_formation)
54
        if tnom.find('MEF_RATTACHEMENT') is not None:
55
            niveaux[mef]['real'] = tnom.find('MEF_RATTACHEMENT').text
56
    divisions = {}
57
    context, _ = parse_xml(structure_file, 'DIVISION')
58
    for _, tstr in context:
59
        div = tstr.attrib['CODE_STRUCTURE']
60
        if div.lower() == 'inactifs':
61
            # élèves inactifs (cf. #913)
62
            continue
63
        tmef = tstr.findall('MEFS_APPARTENANCE/MEF_APPARTENANCE/CODE_MEF')
64
        if len(tmef) == 1:
65
            try:
66
                divisions[div] = niveaux[tmef[0].text]['label']
67
            except KeyError:
68
                msg = ERRMEF % tmef[0].text
69
                log.errorlog(msg)
70
                raise Exception(msg)
71
        else:
72
            # si plusieurs mef, on prend celle de rattachement
73
            errmef = set()
74
            for txtmef in tmef:
75
                try:
76
                    mef = niveaux[txtmef.text]['real']
77
                except KeyError:
78
                    log.errorlog(ERRMEF % tmef[0].text)
79
                    errmef.add(tmef[0].text)
80
                    continue
81
                try:
82
                    divisions[div] = niveaux[mef]['label']
83
                    break
84
                except KeyError:
85
                    log.errorlog(ERRMEF % mef)
86
                    errmef.add(mef)
87
                    continue
88
            if not divisions.has_key(div):
89
                if errmef:
90
                    raise Exception(ERRMEF % list(errmef))
91
                else:
92
                    raise Exception("La division %s n'est associée à aucune MEF" % div)
93
    for classe, niveau in divisions.items():
94
        my_niveau = storage.findOrCreate(Niveau, nom=niveau)
95
        clean_classe = unicode(replace_cars(classe))
96
        storage.findOrCreate(Classe, nom=clean_classe,
97
                             niveau=my_niveau)
98
        num += 1
99
    log.infolog("TOTAL : %d classes" % num)
100

    
101
def parse_sco_groupes(store, structure_file):
102
    """
103
    Recherche des Groupes (Options) et de leurs libellés
104
    """
105
    num = 0
106
    log.infolog("Lecture des groupes (options)...", title=True)
107
    context, _ = parse_xml(structure_file, 'GROUPE')
108
    for _, tstr in context:
109
        grp = unicode(replace_cars(tstr.attrib['CODE_STRUCTURE']))
110
        desc = unicode(replace_cars(tstr.find('LIBELLE_LONG').text))
111
        Groupe(store=store, nom=grp, description=desc)
112
        num += 1
113
    log.infolog("TOTAL : %d groupes" % num)
114

    
115
def parse_sco_eleves(store, eleve_file):
116
    """
117
    parsing des élèves depuis Sconet
118
    """
119
    num = 0
120
    log.infolog("Lecture des élèves...", title=True)
121
    context, eleve_file = parse_xml(eleve_file, 'ELEVE')
122
    # nb : la date de naissance est au format jj/mm/aaaa
123
    mapping = {'nom':'NOM_DE_FAMILLE',
124
              'deprecated_nom':'NOM',
125
              'prenom':'PRENOM',
126
              'date':'DATE_NAISS',
127
              'civilite':'CODE_SEXE',
128
              'numero':'ELENOET',
129
              'ine':'ID_NATIONAL',
130
              'prenom2':'PRENOM2',
131
              'regime':'CODE_REGIME',
132
    }
133

    
134
    # parcours des élèves
135
    for _, televe in context:
136
        eleid = televe.attrib['ELEVE_ID']
137
        sortie = televe.find('DATE_SORTIE')
138
        if sortie != None and sortie.text != '':
139
            continue
140
        # attributs non initialisés
141
        eleve = {'int_id':unicode(eleid)}
142
        for cle, balise in mapping.items():
143
            try:
144
                clean_text = replace_more_cars(televe.find(balise).text)
145
                eleve[cle] = unicode(clean_text)
146
            except:
147
                pass
148
        if eleve.get('nom') is None and 'deprecated_nom' in eleve:
149
            # ancien format Sconet #16876
150
            eleve['nom'] = eleve.get('deprecated_nom')
151
            eleve.pop('deprecated_nom')
152
        if not eleve.get('civilite', ''):
153
            log.infolog("Attribution arbitraire d'une civilité à l'élève : %s %s" % (
154
                         str(eleve['prenom']), str(eleve['nom'])))
155
            eleve['civilite'] = u'1'
156
        if eleve.get('regime', ''):
157
            # mapping du régime de l'élève selon la nomenclature Sconet
158
            if eleve['regime'].rstrip() in REGIMES:
159
                eleve['regime'] = unicode(REGIMES[eleve['regime'].rstrip()])
160
        try:
161
            Eleve(store=store, **eleve)
162
            num += 1
163
            if num % DEBUG_NUM == 0:
164
                log.debuglog("%d élèves lus..." % num)
165
        except TypeError, msg:
166
            log.infolog("Erreur sur l'élève %s : %s" % (eleid, msg))
167
            continue
168
    log.infolog("TOTAL : %d élèves" % num)
169

    
170
    # affectation des élèves
171
    context, _ = parse_xml(eleve_file, 'STRUCTURES_ELEVE', check=False)
172
    num = 0
173
    for _, tstruct in context:
174
        eleid = tstruct.attrib['ELEVE_ID']
175
        eleve = store.findFirst(Eleve, Eleve.int_id==unicode(eleid))
176
        if eleve is None:
177
            continue
178
        for tstr in tstruct.getiterator('STRUCTURE'):
179
            type_struct = str(tstr.find('TYPE_STRUCTURE').text)
180
            if type_struct == 'D':
181
                # c'est une classe
182
                nom_classe = replace_cars(tstr.find('CODE_STRUCTURE').text)
183
                if nom_classe.lower() == 'inactifs':
184
                    # élèves inactifs (cf. #913)
185
                    continue
186
                classe = store.findFirst(Classe, Classe.nom==unicode(nom_classe))
187
                eleve.classe = classe
188
                eleve.niveau = classe.niveau
189
                num += 1
190
            elif type_struct == 'G':
191
                # c'est un groupe (-> option)
192
                nom_groupe = replace_cars(tstr.find('CODE_STRUCTURE').text)
193
                groupe = store.findFirst(Groupe, Groupe.nom==unicode(nom_groupe))
194
                if groupe is None:
195
                    log.infolog("Groupe inconnu : %s" % nom_groupe)
196
                    continue
197
                JointureGroupeUser(store=store, groupe=groupe, user=eleve)
198
                num += 1
199
            else:
200
                log.infolog("Type de structure inconnu :", type_struct)
201
            if num % DEBUG_NUM == 0:
202
                log.debuglog("%d affectations lues..." % num)
203
    log.infolog("TOTAL : %d affectations d'élèves" % num)
204

    
205

    
206
def parse_sco_responsables(store, responsable_file):
207
    """
208
    parsing des responsables depuis Sconet
209
    """
210
    num = 0
211
    log.infolog("Lecture des responsables...", title=True)
212
    context, responsable_file = parse_xml(responsable_file, 'PERSONNE')
213
    # liste des responsables
214
    for _, tresp in context:
215
        rid = tresp.attrib['PERSONNE_ID']
216
        responsable = {'int_id':unicode(rid)}
217
        mapping = {'nom':'NOM_DE_FAMILLE',
218
              'deprecated_nom':'NOM',
219
              'prenom':'PRENOM',
220
              'mail':'MEL',
221
              'telephone':'TEL_PERSONNEL',
222
              'civilite':'LC_CIVILITE',
223
              'id_adresse':'ADRESSE_ID',
224
              'tel_portable':'TEL_PORTABLE',
225
              'tel_pro':'TEL_PROFESSIONNEL',
226
        }
227
        for cle, balise in mapping.items():
228
            try:
229
                clean_text = replace_more_cars(tresp.find(balise).text)
230
                responsable[cle] = unicode(clean_text)
231
            except:
232
                pass
233
        if responsable.get('nom') is None and 'deprecated_nom' in responsable:
234
            # ancien format Sconet #16876
235
            responsable['nom'] = responsable.get('deprecated_nom')
236
            responsable.pop('deprecated_nom')
237
        if responsable.has_key('civilite'):
238
            responsable['civilite'] = unicode(formate_civilite(str(responsable['civilite'])))
239
        try:
240
            Responsable(store=store, **responsable)
241
            num += 1
242
            if num % DEBUG_NUM == 0:
243
                log.debuglog("%d responsables lus..." % num)
244
        except TypeError, msg:
245
            log.infolog("Erreur sur le responsable %s : %s" % (rid, msg))
246
    log.infolog("TOTAL : %d responsables élèves" % num)
247

    
248
    # adresses des responsables
249
    num = 0
250
    context, _ = parse_xml(responsable_file, 'ADRESSE', check=False)
251
    for _, taddr in context:
252
        aid = taddr.attrib['ADRESSE_ID']
253
        adresse = {'int_id':unicode(aid)}
254
        mapping = {'code_postal':'CODE_POSTAL',
255
                   'ville':'LIBELLE_POSTAL',
256
                   'pays':'LL_PAYS'}
257
        for cle, balise in mapping.items():
258
            try:
259
                clean_text = replace_cars(taddr.find(balise).text)
260
                adresse[cle] = unicode(clean_text)
261
            except:
262
                pass
263
        adr = Adresse(store=store, **adresse)
264
        txt_addr = []
265
        for balise in ['LIGNE1_ADRESSE', 'LIGNE2_ADRESSE',
266
                'LIGNE3_ADRESSE', 'LIGNE4_ADRESSE']:
267
            try:
268
                clean_text = replace_cars(taddr.find(balise).text)
269
                if clean_text != '':
270
                    txt_addr.append(unicode(clean_text))
271
            except:
272
                pass
273
            if txt_addr != []:
274
                # FIXME "\n" ?
275
                adr.adresse = unicode("\n".join(txt_addr))
276
        resp = store.findFirst(Responsable,
277
                Responsable.id_adresse==unicode(aid))
278
        if resp is None:
279
            # l'adresse n'est à personne
280
            continue
281
        resp.adresse = adr
282
        num += 1
283
        if num % DEBUG_NUM == 0:
284
            log.debuglog("%d adresse lues..." % num)
285
    log.infolog("TOTAL : %d adresses de responsables" % num)
286

    
287
    # jointures responsable/élève
288
    num = 0
289
    context, _ = parse_xml(responsable_file, 'RESPONSABLE_ELEVE', check=False)
290
    for _, tjoint in context:
291
        resp_legal = tjoint.find('NIVEAU_RESPONSABILITE').text
292
        if resp_legal == '0':
293
            # on ne garde que les responsables 1 et 2 (ref #1163)
294
            continue
295
        eleve_id = tjoint.find('ELEVE_ID').text
296
        responsable_id = tjoint.find('PERSONNE_ID').text
297
        eleve = store.findFirst(Eleve,
298
                Eleve.int_id==unicode(eleve_id))
299
        if eleve is None:
300
            continue
301
        responsable = store.findFirst(Responsable,
302
                Responsable.int_id==unicode(responsable_id))
303
        if responsable is None:
304
            log.infolog("responsable n°%s non trouvé" % responsable_id)
305
            continue
306
        jointure = dict(eleve=eleve, responsable=responsable)
307
        mapping = {'resp_legal':'NIVEAU_RESPONSABILITE',
308
                   'resp_financier':'RESP_FINANCIER',
309
                   'code_parente':'CODE_PARENTE',}
310
        for cle, balise in mapping.items():
311
            try:
312
                clean_text = replace_cars(tjoint.find(balise).text)
313
                jointure[cle] = unicode(clean_text)
314
            except:
315
                pass
316
        JointureResponsableEleve(store=store, **jointure)
317
        num += 1
318
        if num % DEBUG_NUM == 0:
319
            log.debuglog("%d liens responsable/élève lus..." % num)
320
    log.infolog("TOTAL : %d liens responsable/élève" % num)
321

    
322

    
323
##########################################
324
# Extraction STS Professeurs
325
# Fixhier :
326
# - sts_emp_$RNE$_$ANNEE$.xml
327
##########################################
328
def _parse_service(tree):
329
    """
330
    lecture des services d'une classe ou d'une option
331
    """
332
    tservs = tree.find('SERVICES')
333
    if tservs is None:
334
        return []
335
    machin = []
336
    for tserv in tservs.getiterator('SERVICE'):
337
        type_cours = tserv.attrib['CODE_MOD_COURS']
338
        #matiere = t.ok_groupe(code_matieres[tserv.attrib['CODE_MATIERE']], 'm')
339
        matiere = tserv.attrib['CODE_MATIERE']
340
        tens = tserv.find('ENSEIGNANTS')
341
        for ten in tens.getiterator('ENSEIGNANT'):
342
            # id enseignant
343
            #print "le prof", ten.attrib['ID'], "enseigne", matiere, "c'est", type_cours
344
            machin.append([ten.attrib['ID'], matiere, type_cours])
345
    return machin
346

    
347
def _parse_division_appartenance(tree):
348
    """
349
    lecture des divisions affectées à une option
350
    """
351
    tdivs = tree.find('DIVISIONS_APPARTENANCE')
352
    if tdivs is None:
353
        return []
354
    div = []
355
    for tdiv in tdivs.getiterator('DIVISION_APPARTENANCE'):
356
        div.append(tdiv.attrib['CODE'])
357
    return div
358

    
359
def parse_sts_profs(storage, sts_file):
360
    """
361
    parsing des professeurs depuis sts
362
    """
363
    num = 0
364
    log.infolog("Lecture des personnels", title=True)
365
    context, sts_file = parse_xml(sts_file, 'MATIERE')
366
    code_matieres = {}
367
    # lien code/libellé pour les matières
368
    for _, tcmat in context:
369
        code = tcmat.attrib['CODE']
370
        mat = tcmat.find('CODE_GESTION').text
371
        lib = tcmat.find('LIBELLE_EDITION').text
372
        code_matieres[code] = (mat, lib)
373

    
374
    # liens prof-classes, prof-matieres, prof-options
375
    classes = {}
376
    matieres = {}
377
    options = {}
378

    
379
    # -- parcours des classes -- #
380
    context, _ = parse_xml(sts_file, 'DIVISION', check=False)
381
    for _, tmat in context:
382
        code_classe = tmat.attrib['CODE']
383
        nom = unicode(replace_cars(code_classe))
384
        my_classe = storage.findOrCreate(EnsClasse, nom=nom)
385
        for prof, mat, _ in _parse_service(tmat):
386
            classes.setdefault(prof, []).append(my_classe)
387
            if code_matieres.has_key(mat):
388
                matiere = code_matieres[mat]
389
                nom = unicode(replace_cars(matiere[0]))
390
                desc = unicode(replace_cars(matiere[1]))
391
                my_mat = storage.findOrCreate(Matiere, nom=nom, description=desc)
392
                matieres.setdefault(prof, []).append(my_mat)
393
            else:
394
                log.infolog("matière %s inconnue" % mat)
395

    
396
    # -- parcours des groupes (options) -- #
397
    context, _ = parse_xml(sts_file, 'GROUPE', check=False)
398
    for _, tgrp in context:
399
        code_groupe = tgrp.attrib['CODE']
400
        nom = unicode(replace_cars(code_groupe))
401
        desc = unicode(replace_cars(tgrp.find('LIBELLE_LONG').text))
402
        my_groupe = storage.findOrCreate(Groupe, nom=nom, description=desc)
403
        my_classes = []
404
        for div in _parse_division_appartenance(tgrp):
405
            nom = unicode(replace_cars(div))
406
            my_classes.append(storage.findOrCreate(EnsClasse, nom=nom))
407
        for prof, mat, _ in _parse_service(tgrp):
408
            options.setdefault(prof, []).append(my_groupe)
409
            for my_classe in my_classes:
410
                # on ajoute les classes touchées par l'option au prof
411
                classes.setdefault(prof, []).append(my_classe)
412
            if code_matieres.has_key(mat):
413
                matiere = code_matieres[mat]
414
                nom = unicode(replace_cars(matiere[0]))
415
                desc = unicode(replace_cars(matiere[1]))
416
                my_mat = storage.findOrCreate(Matiere, nom=nom, description=desc)
417
                matieres.setdefault(prof, []).append(my_mat)
418
            else:
419
                log.infolog("matière %s inconnue" % mat)
420

    
421
    mapping = {'NOM_USAGE':'nom',
422
               'PRENOM':'prenom',
423
               'DATE_NAISSANCE':'date',
424
               'CIVILITE':'civilite',
425
               'NOM_PATRONYMIQUE':'nom_patronymique',
426
    }
427
    context, _ = parse_xml(sts_file, 'INDIVIDU', check=False)
428
    for _, ind in context:
429
        #<INDIVIDU ID='2453' TYPE='epp'>
430
        profid = ind.attrib['ID']
431
        professeur = {'int_id':unicode(profid)}
432
        fonction = ind.find('FONCTION')
433
        if fonction is None:
434
            log.infolog("(pas de fonction pour le personnel %s)" % profid)
435
            continue
436
        for balise, cle in mapping.items():
437
            value = ind.find(balise)
438
            if value is None:
439
                professeur[cle] = u''
440
            else:
441
                clean_text = replace_more_cars(value.text)
442
                professeur[cle] = unicode(clean_text)
443
        if not_empty(professeur, 'date'):
444
            my_date = formate_date(str(professeur['date']).replace('-', ''))
445
            professeur['date'] = unicode(my_date)
446
        else:
447
            # date de naissance arbitraire #1730
448
            professeur['date'] = u'01/01/0001'
449
        if is_empty(professeur, 'civilite'):
450
            # civilité arbitraire #2599
451
            log.infolog("Attribution arbitraire d'une civilité au personnel : %s %s" % (
452
                         str(professeur['prenom']), str(professeur['nom'])))
453
            professeur['civilite'] = u'1'
454
        if fonction.text == 'ENS':
455
            # c'est un enseignant !
456
            # on regarde si il est prof principal
457
            principal = []
458
            tprincs = ind.findall('PROFS_PRINC/PROF_PRINC')
459
            for tprinc in tprincs:
460
                nom = unicode(replace_cars(tprinc.find('CODE_STRUCTURE').text))
461
                my_classe = storage.findOrCreate(EnsClasse, nom=nom)
462
                principal.append(my_classe)
463
            try:
464
                prof = Enseignant(store=storage, **professeur)
465
                num += 1
466
            except Exception, msg:
467
                log.infolog("Erreur sur l'enseignant %s : %s" % (profid, msg))
468
            # affectation des classes
469
            if classes.has_key(profid):
470
                for classe in classes[profid]:
471
                    if classe in principal:
472
                        is_principal = True
473
                    else:
474
                        is_principal = False
475
                    JointureClasseEnseignant(store=storage, classe=classe,
476
                            enseignant=prof, profprincipal=is_principal)
477

    
478
            # affectation des options
479
            if options.has_key(profid):
480
                for groupe in options[profid]:
481
                    JointureGroupeUser(store=storage, groupe=groupe,
482
                            user=prof)
483

    
484
            # affectation des matieres
485
            if matieres.has_key(profid):
486
                for mat in matieres[profid]:
487
                    storage.findOrCreate(JointureMatiereEnseignant,
488
                            matiere=mat, enseignant=prof)
489
        else:
490
            # c'est un administratif
491
            try:
492
                admin = Administratif(store=storage, **professeur)
493
                nom = unicode(replace_cars(fonction.text))
494
                groupe = storage.findOrCreate(Service, nom=nom)
495
                admin.groupe = groupe
496
                num += 1
497
            except Exception, msg:
498
                log.infolog("Erreur sur le personnel %s : %s" % (profid, msg))
499
        if num % DEBUG_NUM == 0:
500
            log.debuglog("%d personnels lus..." % num)
501
    log.infolog("TOTAL : %d personnels" % num)
502