refactor with buildout
authorOlivier Larchevêque <olivier.larcheveque@auf.org>
Wed, 13 Apr 2011 20:36:16 +0000 (16:36 -0400)
committerOlivier Larchevêque <olivier.larcheveque@auf.org>
Wed, 13 Apr 2011 20:36:16 +0000 (16:36 -0400)
41 files changed:
buildout.cfg
devel.cfg
project/README.txt [new file with mode: 0644]
project/__init__.py [new file with mode: 0644]
project/conf.py.edit [new file with mode: 0644]
project/development.py [new file with mode: 0644]
project/django.wsgi.edit [new file with mode: 0644]
project/formwcs/__init__.py [new file with mode: 0644]
project/formwcs/extension.py [new file with mode: 0755]
project/formwcs/pieces.py [new file with mode: 0644]
project/formwcs/views.py [new file with mode: 0644]
project/formwcs/wcs2json/README.txt [new file with mode: 0755]
project/formwcs/wcs2json/demande-de-bourse-de-doctorat-2010-2011_field-names.txt [new file with mode: 0755]
project/formwcs/wcs2json/jsontopdf.py [new file with mode: 0755]
project/formwcs/wcs2json/settings.py [new file with mode: 0755]
project/formwcs/wcs2json/style.css [new file with mode: 0644]
project/formwcs/wcs2json/template.html [new file with mode: 0755]
project/formwcs/wcs2json/template.py [new file with mode: 0644]
project/formwcs/wcs2json/template.sh [new file with mode: 0644]
project/formwcs/wcs2json/wcs2json.py [new file with mode: 0755]
project/formwcs/wcs2json/wcs2json.sh [new file with mode: 0755]
project/media/css/pdf.css [new file with mode: 0644]
project/settings.py [new file with mode: 0644]
project/sigma_v1/__init__.py [new file with mode: 0644]
project/sigma_v1/models.py [new file with mode: 0644]
project/sigma_v1/models_autres_inspectdb.py [new file with mode: 0644]
project/sigma_v1/settings.py [new file with mode: 0644]
project/urls.py [new file with mode: 0644]
project/wcs/__init__.py [new file with mode: 0644]
project/wcs/delete.py [new file with mode: 0644]
project/wcs/docs.py [new file with mode: 0644]
project/wcs/export.py [new file with mode: 0644]
project/wcs/import.py [new file with mode: 0644]
project/wcs/jsontopdf.py [new file with mode: 0755]
project/wcs/lib.py [new file with mode: 0644]
project/wcs/models.py [new file with mode: 0644]
project/wcs/settings.py [new file with mode: 0644]
project/wcs/templates/candidature.html [new file with mode: 0755]
project/wcs/tests.py [new file with mode: 0644]
project/wcs/urls.py [new file with mode: 0644]
project/wcs/views.py [new file with mode: 0644]

index 9b77a9f..faaca29 100644 (file)
@@ -12,6 +12,10 @@ eggs =
     south
     django-admin-tools
     auf.django.skin
+    simplejson
+    reportlab
+    html5lib
+    pisa
 
 [versions]
 django = 1.2.3
index dfece77..4adf5ea 100644 (file)
--- a/devel.cfg
+++ b/devel.cfg
@@ -5,5 +5,4 @@ extends=buildout.cfg
 wsgi=false
 settings=development
 eggs = ${buildout:eggs}
-    django-debug-toolbar
 
diff --git a/project/README.txt b/project/README.txt
new file mode 100644 (file)
index 0000000..a77f96f
--- /dev/null
@@ -0,0 +1,36 @@
+# DÉPLOIEMENT
+# config
+Pour déploiement, créer sur la racine :
+conf.py
+
+...sur la base du fichier :
+conf.py.edit
+
+# DOCUMENTS
+Les documents uploadés par les candidats sont servis par l'application et ne sont pas suivis dans Git.
+Les déployer dans 
+/formwcs/docs/
+
+problèmes JSON :
+json/0333  modifier manuellement ses caractères accentués
+ex. : é --> %|% puis %|% --> é
+
+json/0295 champ particip_prog_auf_dernier
+caract. invisible entre mot "distant" et double-quote json : supprimer
+
+# IMPORT WCS
+> cd sigmawcs
+> python manage.py shell
+> cd formwcs
+> run views.py
+
+# MISES À JOUR
+git pull
+
+reloader apache
+
+# WCS2JSON
+Fichiers pour générer 
+- les JSON, 
+- le form en PDF et 
+- les zip (form + 3 pj.)
diff --git a/project/__init__.py b/project/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/project/conf.py.edit b/project/conf.py.edit
new file mode 100644 (file)
index 0000000..2a5662e
--- /dev/null
@@ -0,0 +1,12 @@
+# -*- encoding: utf-8 -*-
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+AUTH_PASSWORD_REQUIRED = False
+
+# DEV
+DATABASE_ENGINE = 'mysql'
+DATABASE_NAME = 'sigmawcs'
+DATABASE_USER = ''
+DATABASE_PASSWORD = ''
+DATABASE_HOST = 'new-dev.auf'
+DATABASE_PORT = ''
diff --git a/project/development.py b/project/development.py
new file mode 100644 (file)
index 0000000..1a763c0
--- /dev/null
@@ -0,0 +1,12 @@
+# -*- encoding: utf-8 -*-
+
+from project.settings import *
+DEBUG=True
+TEMPLATE_DEBUG=DEBUG
+
+# Décommentez ces lignes pour activer la debugtoolbar
+#INTERNAL_IPS = ('127.0.0.1',)
+#INSTALLED_APPS += ('debug_toolbar',)
+#MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
+
+AUTH_PASSWORD_REQUIRED = False
diff --git a/project/django.wsgi.edit b/project/django.wsgi.edit
new file mode 100644 (file)
index 0000000..b4ce3db
--- /dev/null
@@ -0,0 +1,10 @@
+import os
+import sys
+
+sys.path.append('/srv/www/rh-eval-test.auf')
+sys.path.append('/srv/www/rh-eval-test.auf/rh-eval-test/')
+
+os.environ['DJANGO_SETTINGS_MODULE'] = 'rh-eval-test.settings'
+
+import django.core.handlers.wsgi
+application = django.core.handlers.wsgi.WSGIHandler()
diff --git a/project/formwcs/__init__.py b/project/formwcs/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/project/formwcs/extension.py b/project/formwcs/extension.py
new file mode 100755 (executable)
index 0000000..cfabc91
--- /dev/null
@@ -0,0 +1,100 @@
+# -=- encoding: utf-8 -=-
+import os.path
+import unicodedata
+from simplejson import loads
+
+
+# HELPERS
+def jsonCleanUp(json):
+    for k,v in json.iteritems():
+        if type(v) is unicode :
+            # encodage : correction caractères
+            v = replaceUnicodeNotIso(v)
+            # strip espaces
+            v = v.lstrip()
+            v = v.rstrip()
+            json[k] = v
+        if type(v) is str :
+            print ('%s %s a str : %s') % (json['num_dossier'], k, v)
+    return json
+
+def replaceUnicodeNotIso(string):
+    """Remplace caractère Unicode (scripts latin) n'ayant pas d'équivalent ISO-8859-1 
+    par un caractère visuellement similaire.
+    """
+    result = ''
+    
+#    nkfd_form = unicodedata.normalize('NFKD', unicode(string))
+#    string = u"".join([c for c in nkfd_form if not unicodedata.combining(c)])
+    
+    # Latin Extended-A
+    avant = u'ĂŞşŢţν'
+    apres = u'ASsTtv'
+
+    for char in string:
+        i = avant.find(char)
+        if i >= 0:
+            char = apres[i]
+        result += char
+
+    return result
+
+
+# MAIN
+if __name__ == "__main__":
+
+    rootDocsJson = 'docs/json/'
+    listjson = []
+    
+    if os.path.exists(rootDocsJson):               
+        listjson = os.listdir(rootDocsJson)
+    
+        extensions = set()
+        erreurs = set()
+        tests = {}
+        i = 0
+        for candidature in listjson :
+            fichier = "%s%s" % (rootDocsJson, candidature)
+            
+            f = open(fichier, 'r')
+            data = f.read()
+            f.close()
+            
+            data = loads(data, encoding='utf-8')
+            data = jsonCleanUp(data)
+            
+            extension_dt = data['descriptif_these'].rsplit('.', 1)[1]
+            extension_pr = data['protocole_recherche'].rsplit('.', 1)[1]
+            extension_cv = data['curriculum_vitae'].rsplit('.', 1)[1]
+
+            if len(extension_dt) < 5 :
+                if extension_dt not in extensions :
+                    tests[extension_dt] = data['num_dossier']
+                extensions.add(extension_dt)
+            else :
+                erreurs.add(data['num_dossier'])
+
+            if len(extension_pr) < 5 :
+                if extension_pr not in extensions :
+                    tests[extension_pr] = data['num_dossier']
+                extensions.add(extension_pr)
+            else :
+                erreurs.add(data['num_dossier'])
+
+            if len(extension_cv) < 5 :
+                if extension_cv not in extensions :
+                    tests[extension_cv] = data['num_dossier']
+                extensions.add(extension_cv)
+            else :
+                erreurs.add(data['num_dossier'])
+            
+            i = i + 1
+
+            print '%d - %s, %s, %s' % (i, extension_dt, extension_pr, extension_cv)
+        print 'Total = %d' % (i)
+        print 'Extensions ='
+        print ', '.join(extensions)
+        print 'Erreurs (num_dossier WCS) ='
+        print ', '.join([str(e) for e in erreurs])
+        print 'Tests : cas a tester pour chaque extension :'
+        print tests
diff --git a/project/formwcs/pieces.py b/project/formwcs/pieces.py
new file mode 100644 (file)
index 0000000..5987825
--- /dev/null
@@ -0,0 +1,59 @@
+# -=- encoding: utf-8 -=-
+import os.path
+import unicodedata
+from datetime import datetime
+from datetime import date
+from simplejson import loads
+
+from sigma_v1.models import Dossier, DossierPieces
+
+from settings import USER, STATUT_DOSSIER, MOBILITE
+
+def createDossierPieces(d):
+### DOSSIER PIÈCES
+    pieces_created = []
+    
+    pieces_WCS = [
+        45, # etabl origine = membre
+        46, # etabl accueil = membre
+        55, # date naissance
+        56, # etabl orig et acc = pays diff
+        ]
+    
+    for piece in pieces_WCS:
+        dp = DossierPieces()
+        #dp.id = 
+        dp.dossier = d
+        dp.presente = False
+        dp.piece = piece
+        #dp.conforme = 
+        #dp.commentaire = u"Pièce chargée par candidat via formulaire électronique."
+        dp.save()
+        pieces_created.append(dp)
+            
+    return pieces_created
+    
+# MAIN
+if __name__ == "__main__":
+
+    candidatures = range(8017, 9776)    # réel [8017, 9775], 2e param range = exclu
+
+    nombre = len(candidatures)
+
+    print 'AJOUT PIECES CANDIDATURES'
+    print '--------------------------------------------------'
+    print 'Nombre à traiter = %d' % (nombre)
+    print '--------------------------------------------------'
+
+    i = 0
+    for candidature in candidatures:
+        d = Dossier(id=candidature)
+        pieces = createDossierPieces(d)
+        
+        print candidature
+        
+        i = i + 1
+            
+    print '--------------------------------------------------'
+    print 'Total traité = %d' % (i)
+    print '--------------------------------------------------'
diff --git a/project/formwcs/views.py b/project/formwcs/views.py
new file mode 100644 (file)
index 0000000..ff687ec
--- /dev/null
@@ -0,0 +1,552 @@
+# -=- encoding: utf-8 -=-
+import os.path
+import unicodedata
+from datetime import datetime
+from datetime import date
+from simplejson import loads
+
+from sigma_v1.models import Personne, Dossier, DossierOrigine, DossierAccueil, DossierMobilite, DossierPieces
+from sigma_v1.models import Etablissement, Implantation, Bureau
+
+from settings import USER, STATUT_DOSSIER, MOBILITE
+
+def createPersonne(data):
+### PERSONNE
+    p = Personne()
+    
+    #p.id
+    p.utilisateur_creation = USER
+    #p.utilisateur_maj = 
+    #p.date_creation = 
+    #p.date_maj = 
+
+    if data['nom'] is not None :
+        p.nom = majSansAccent(data['nom'].upper())[0:100]
+        p.nom_index = p.nom
+    if data['nom_jeune_fille'] is not None :
+        p.nom_jeune_fille = majSansAccent(data['nom_jeune_fille'].upper())[0:100]
+        p.nom_jeune_fille_index = p.nom_jeune_fille
+    if data['prenom'] is not None :
+        p.prenom  = majSansAccent(data['prenom'].title())[0:100]
+        p.prenom_index = p.prenom.upper()
+    p.pays_nationalite = data['pays_nationalite']
+    #p.sexe = 
+
+    p.civilite = str2civilite(data['civilite'])
+
+    p.pays_naissance = data['pays_naissance'] or u""
+    p.date_naissance = str2date(data['date_naissance'])
+    p.ville_naissance = majSansAccent(data['ville_naissance'].title()) or u""
+
+    p.adresse = data['adresse'] or u""
+    p.ville = majSansAccent(data['ville'].title()) or u""
+    p.region = data['region'] or u""
+    if data['code_postal'] is not None :
+        p.code_postal = data['code_postal'][0:10]
+    p.pays_residence = data['pays_residence'] or u""
+    if data['tel'] is not None :
+        p.tel = data['tel'][0:25]
+    #p.fax
+    if data['tel_pro'] is not None :
+        p.tel_pro = data['tel_pro'][0:25]
+    #p.fax_pro
+    if data['email'] is not None :
+        p.email = data['email'][0:100] or u""
+
+    p.save()
+    
+    return p
+   
+def createDossier(data, p): 
+### DOSSIER
+    d = Dossier()
+
+    #d.id
+    d.statut = STATUT_DOSSIER
+    d.mobilite = MOBILITE
+    d.personne = p
+    d.statut_personne = str2statutPersonne(data['statut_personne'])
+    if data['fonction'] is not None :
+        d.fonction = data['fonction'][0:100]
+    
+    # bureau rattachement via établissement d'origine
+    try:
+        id_etablissement = data['o_etablissement'].lstrip('0')
+        etablissement = Etablissement.objects.get(id_gde=id_etablissement)
+        d.bureau_rattachement = etablissement.implantation_sigma.bureau.id_num
+    except Etablissement.DoesNotExist:
+        pass
+
+    if data['intitule_d_diplome'] is not None :
+        d.intitule_d_diplome = data['intitule_d_diplome'][0:100]
+    d.date_d_diplome = str2date(data['date_d_diplome'])
+    if data['nom_etb'] is not None :
+        d.nom_etb = data['nom_etb'][0:100]
+    d.pays_etb = data['pays_etb'] or u""
+    d.niveau = str2niveauEtude(data['niveau'])
+    
+    #data['particip_prog_auf']
+    if data['particip_prog_auf_dernier'] is not None :
+        d.programme = data['particip_prog_auf_dernier'][0:255]
+    d.annee_programme = data['particip_prog_auf_annee'] or 0
+    #data['boursier_auf']
+    if data['boursier_auf_type'] is not None :
+        d.categorie_bourse = data['boursier_auf_type'][0:12]
+    d.annee_bourse = data['boursier_auf_annee'] or 0
+    
+    #d.etat
+    #d.dd_activation
+    #d.df_activation
+    d.utilisateur_creation = USER
+    d.utilisateur_maj = USER
+    #d.date_maj
+    
+    #d.classement_1
+    #d.classement_2
+    #d.classement_3
+    #d.opp_regionale
+    #d.coche_selection
+    #d.reponse_notification
+    #d.commentaire_notification
+    #d.moyenne_academique
+    #d.autres_criteres
+    #d.erreurs_recevabilite
+    #d.repechage
+    #d.rendu_irrecevable
+
+    d.save()
+    
+    return d
+
+def createDossierOrigine(data, d):
+### DOSSIER ORIGINE
+    do = DossierOrigine()
+    
+    #do.id
+    do.dossier = d
+    
+    # établissement
+    id_etablissement = data['o_etablissement'].lstrip('0')
+    try:
+        etablissement = Etablissement.objects.get(id_gde=id_etablissement)
+
+        # id
+        do.etablissement = etablissement.id     # PAS etablissement.id_gde
+
+        # coordonnées
+        do.pays = etablissement.pays
+        do.adresse = etablissement.adresse
+        if etablissement.code_postal is not None :
+            do.code_postal = etablissement.code_postal[0:10]
+        if etablissement.ville is not None :
+            do.ville = etablissement.ville[0:50]
+        if etablissement.province is not None :
+            do.region = etablissement.province[0:50]
+        #do.url_site
+    except Etablissement.DoesNotExist:
+        pass
+        
+    if data['o_etablissement_unite'] is not None :
+        do.sc_faculte = data['o_etablissement_unite'][0:150]
+    
+    #do.erreur_recevabilite_etbt_origine = 
+    #do.autre_etb = 
+
+    # accord scientifique
+    do.sc_civilite = str2civilite(data['o_sc_civilite'])
+    if data['o_sc_nom'] is not None :
+        do.sc_nom = majSansAccent(data['o_sc_nom'].upper())[0:100]
+    if data['o_sc_prenom'] is not None :
+        do.sc_prenom = majSansAccent(data['o_sc_prenom'].title())[0:100]
+    if data['o_sc_fonction'] is not None :
+        do.sc_fonction = data['o_sc_fonction'][0:100]
+    #do.sc_adresse = 
+    #do.sc_code_postal = 
+    if data['o_sc_ville'] is not None :
+        do.sc_ville = majSansAccent(data['o_sc_ville'].title())[0:50]
+    if data['o_sc_email'] is not None :
+        do.sc_email = data['o_sc_email'][0:100]
+    if data['o_sc_tel_pro'] is not None :
+        do.sc_tel_pro = data['o_sc_tel_pro'][0:25]
+    #do.sc_fax_pro = 
+
+    # accord institutionnel
+    #do.inst_civilite = 
+    #do.inst_nom = 
+    #do.inst_prenom = 
+    #do.inst_fonction = 
+    
+    do.save()
+    
+    return do
+
+def createDossierAccueil(data, d):
+### DOSSIER ACCUEIL
+    da = DossierAccueil()
+    
+    #da.id = 
+    da.dossier = d
+    
+    # établissement
+    id_etablissement = data['a_etablissement'].lstrip('0')
+    try:
+        etablissement = Etablissement.objects.get(id_gde=id_etablissement)
+
+        # id
+        da.etablissement = etablissement.id     # PAS etablissement.id_gde
+
+        # coordonnées
+        da.pays = etablissement.pays
+        da.adresse = etablissement.adresse
+        if etablissement.code_postal is not None :
+            da.code_postal = etablissement.code_postal[0:10]
+        if etablissement.ville is not None :
+            da.ville = etablissement.ville[0:50]
+        if etablissement.province is not None :
+            da.region = etablissement.province[0:50]
+        #da.url_site
+    except Etablissement.DoesNotExist:
+        pass
+            
+    if data['a_etablissement_unite'] is not None :
+        da.sc_faculte = data['a_etablissement_unite'][0:150]
+        
+    #da.erreur_recevabilite_etbt_accueil = 
+    #da.autre_etb = 
+
+    # accord scientifique
+    da.sc_civilite = str2civilite(data['a_sc_civilite'])
+    if data['a_sc_nom'] is not None :
+        da.sc_nom = majSansAccent(data['a_sc_nom'].upper())[0:100]
+    if data['a_sc_prenom'] is not None :
+        da.sc_prenom = majSansAccent(data['a_sc_prenom'].title())[0:100]
+    if data['a_sc_fonction'] is not None :
+        da.sc_fonction = data['a_sc_fonction'][0:100]
+    #da.sc_adresse = 
+    #da.sc_code_postal = 
+    if data['a_sc_ville'] is not None :
+        da.sc_ville = majSansAccent(data['a_sc_ville'].title())[0:50]
+    if data['a_sc_email'] is not None :
+        da.sc_email = data['a_sc_email'][0:100]
+    if data['a_sc_tel_pro'] is not None :
+        da.sc_tel_pro = data['a_sc_tel_pro'][0:25]
+    #da.sc_fax_pro = 
+
+    # accord institutionnel
+    #da.inst_civilite = 
+    #da.inst_nom = 
+    #da.inst_prenom = 
+    #da.inst_fonction = 
+    #da.inst_email = 
+    #da.inst_tel_pro = 
+    #da.inst_fax_pro = 
+    
+    da.save()
+    
+    return da
+
+def createDossierMobilite(data, d):
+### DOSSIER MOBILITÉ
+    dm = DossierMobilite()
+    
+    #dm.id = 
+    dm.dossier = d
+    
+    # calcul dates
+#    o_date_debut = str2date(data['o_date_debut_mobilite'])
+#    a_date_debut = str2date(data['a_date_debut_mobilite'])
+    
+#    if (o_date_debut < a_date_debut):
+#        date_debut = o_date_debut
+#    else :
+#        date_debut = a_date_debut
+    
+#    o_date_fin = str2date(data['o_date_fin_mobilite'])
+#    a_date_fin = str2date(data['a_date_fin_mobilite'])
+    
+#    if (o_date_fin < a_date_fin):
+#        date_fin = o_date_fin
+#    else :
+#        date_fin = a_date_fin
+    
+#    dm.dd_mobilite = date_debut
+#    dm.df_mobilite = date_fin
+#    dm.total_mobilite = # intervalle dd_mobilite, df_mobilite
+    
+    dm.alt_mois_origine = str2nbMois(data['o_nb_mois'])
+    dm.alt_mois_accueil = str2nbMois(data['a_nb_mois'])
+
+    dm.intitule_projet = premiereMaj(data['intitule_projet']) or u""
+    # ajout point à la fin...
+    if dm.intitule_projet != u"" and dm.intitule_projet[-1] != u"." :
+        dm.intitule_projet = dm.intitule_projet + u"."
+
+    if data['mot_clef1'] is not None :
+        dm.mot_clef1 = majSansAccent(data['mot_clef1'].upper())[0:50]
+    if data['mot_clef2'] is not None :
+        dm.mot_clef2 = majSansAccent(data['mot_clef2'].upper())[0:50]
+    if data['mot_clef3'] is not None :
+        dm.mot_clef3 = majSansAccent(data['mot_clef3'].upper())[0:50]
+    
+    dm.intitule_diplome = data['programme'] or u""
+    if dm.intitule_diplome == u"" and data['programme_autre'] is not None :
+        dm.intitule_diplome = data['programme_autre']
+    
+    dm.niveau_encours = str2niveauEtude(data['annee_programme'])
+        
+    #dm.type_intervention = 
+    #dm.public_vise = 
+    #dm.autres_publics = 
+    
+    dm.discipline = data['discipline'] or u""
+    #dm.sous_discipline = 
+    
+    #dm.mobilite_accueil =  # debut mobilité à l'accueil?
+    #dm.intitule_diplome_demande = 
+    #dm.niveau_demande = 
+    #dm.obtention_prevu = 
+    
+    dm.date_inscription_these = str2date(data['date_inscription_these'])
+    dm.pays_soutenance = data['pays_soutenance'] or u""
+    dm.date_soutenance_these = str2date(data['date_soutenance_these'])
+    dm.type_these = str2typeThese(data['type_these'])
+    # workaround : champ dm.date_soutenance_these pas affiché in SIGMA :
+    dm.type_these_autre = u"Date soutenance prévue : %s" % (dm.date_soutenance_these)
+    dm.type_these_autre = dm.type_these_autre[0:255]
+    
+    dm.dir_ori_civilite = str2civilite(data['o_inst_civilite'])
+    if data['o_inst_nom'] is not None :
+        dm.dir_ori_nom = majSansAccent(data['o_inst_nom'].upper())[0:100]
+    if data['o_inst_prenom'] is not None :
+        dm.dir_ori_prenom = majSansAccent(data['o_inst_prenom'].title())[0:100]
+    # email absent in SIGMA... sale workaround
+    if data['o_inst_email'] is not None :
+        dm.dir_ori_prenom = u"%s [%s]" % (dm.dir_ori_prenom, data['o_inst_email'])
+        dm.dir_ori_prenom = dm.dir_ori_prenom[0:100]
+    
+    dm.dir_acc_civilite = str2civilite(data['a_inst_civilite'])
+    if data['a_inst_nom'] is not None :
+        dm.dir_acc_nom = majSansAccent(data['a_inst_nom'].upper())[0:100]
+    if data['a_inst_prenom'] is not None :
+        dm.dir_acc_prenom = majSansAccent(data['a_inst_prenom'].title())[0:100]
+    # email absent in SIGMA... sale workaround
+    if data['a_inst_email'] is not None :
+        dm.dir_acc_prenom = u"%s [%s]" % (dm.dir_acc_prenom, data['a_inst_email'])
+        dm.dir_acc_prenom = dm.dir_acc_prenom[0:100]
+    
+    dm.save()
+    
+    return dm
+
+def createDossierPieces(data, d):
+### DOSSIER PIÈCES
+    pieces_created = []
+    
+    #data['descriptif_these']
+    #data['protocole_recherche']
+    #data['curriculum_vitae']
+    pieces_WCS = [
+        19, # formulaire
+        17, # descriptif thèse
+        27, # protocole de recherche
+        16, # curriculum_vitae
+        ]
+    
+    for piece in pieces_WCS:
+        dp = DossierPieces()
+        #dp.id = 
+        dp.dossier = d
+        dp.presente = True
+        dp.piece = piece
+        #dp.conforme = 
+        #dp.commentaire = u"Pièce chargée par candidat via formulaire électronique."
+        dp.save()
+        pieces_created.append(dp)
+        
+    # Autres pièces in SIGMA
+    pieces_SIGMA = [1,4,5,8,10,12,47]
+    
+    for piece in pieces_SIGMA:
+        dp = DossierPieces()
+        dp.dossier = d
+        dp.piece = piece
+        dp.save()
+        pieces_created.append(dp)
+    
+    return pieces_created
+
+### AUTRE
+    #data['num_dossier']
+    
+    #data['engagement_nom']
+    #data['engagement_prenom']
+    #data['engagement_respect_reglement']
+
+# HELPERS
+def jsonCleanUp(json):
+    for k,v in json.iteritems():
+        if type(v) is unicode :
+            # encodage : correction caractères
+            v = replaceUnicodeNotIso(v)
+            # strip espaces
+            v = v.lstrip()
+            v = v.rstrip()
+            json[k] = v
+        if type(v) is str :
+            print ('%s %s a str : %s') % (json['num_dossier'], k, v)
+    return json
+
+def replaceUnicodeNotIso(string):
+    """Remplace caractère Unicode (scripts latin) n'ayant pas d'équivalent ISO-8859-1 
+    par un caractère visuellement similaire.
+    """
+    result = ''
+    
+#    nkfd_form = unicodedata.normalize('NFKD', unicode(string))
+#    string = u"".join([c for c in nkfd_form if not unicodedata.combining(c)])
+    
+    # Latin Extended-A
+    avant = u'ĂŞşŢţνγδệịạỄợụĐốữẦươȘβğПΣăúµÁẾ'
+    apres = u'ASsTtvgdêiaEouDouAuoSBgPEaumAE'
+
+    for char in string:
+        i = avant.find(char)
+        if i >= 0:
+            char = apres[i]
+        result += char
+
+    return result
+
+def premiereMaj(s):
+    """Met le premier caractère en majuscule et reste minuscule"""
+    premiere = s[0]
+    reste = s[1:]
+    
+    return premiere.upper() + reste.lower()
+
+def majSansAccent(s):
+    result = ''
+    avant = u'ÇÁÀÂÉÈÊËÍÌÎÏÓÒÔÖÚÙÛÜÝỲŶŸ'
+    apres = u'CAAAEEEEIIIIOOOOUUUUYYYY'
+
+    for char in s:
+        i = avant.find(char)
+        if i >= 0:
+            char = apres[i]
+        result += char
+
+    return result
+
+def str2date(s):
+    d = datetime.strptime(s, '%Y-%m-%d')
+    return date(d.year, d.month, d.day)
+    
+def str2civilite(s):
+    output = ''
+    if s == 'M.':
+        output = 'MR'
+    elif s == 'Mme':
+        output = 'MM'
+    elif s == 'Mlle':
+        output = 'ME'       
+    return output
+    
+def str2typeThese(s):
+    output = ''
+    if s == 'Co-tutelle':
+        output = 'CT'
+    elif s == 'Co-direction':
+        output = 'CD'
+    elif s == 'Autre':
+        output = 'AU'       
+    return output
+    
+def str2statutPersonne(s):
+    output = 0
+    try :
+        # id du statut = permière lettre
+        output = int(s[0])
+    except ValueError :
+        pass
+    return output
+    
+def str2nbMois(s):
+    output = 0
+    try :
+        # permière lettre = nb de mois
+        output = int(s[0])
+    except ValueError :
+        pass
+    return output
+       
+def str2niveauEtude(s):
+    output = 0
+    try :
+        # années études = permière lettre
+        annees = int(s[0])
+        # sauf 10 ans = 2 premières lettres...
+        if annees == 1:
+            annees = 10
+            
+        #C_NIVEAU    L_INTITULE_NIVEAU   L_NIVEAU (annees)
+        #6             ...                 0
+        #1             Licence 2           2
+        #2             Licence 3           3
+        #3             Master 1            4
+        #4             Master 2            5
+        #7             Master 2 +          6
+        #5             Doctorat            8
+        #8             Bac + 7             7
+        #9             Doctorat            9
+            
+        if annees == 4 : output = 3
+        elif annees == 5 : output = 4
+        elif annees == 6 : output = 7
+        elif annees == 7 : output = 8
+        elif annees == 8 : output = 5
+        elif annees == 9 : output = 9
+        elif annees == 10 : output = 9
+        
+    except ValueError :
+        pass
+    return output
+    
+# MAIN
+if __name__ == "__main__":
+
+    rootDocsJson = 'docs/json/'
+    listjson = []
+    
+    if os.path.exists(rootDocsJson):               
+        listjson = os.listdir(rootDocsJson)
+        nombre = len(listjson)
+        
+        print 'IMPORT CANDIDATURES : WCS -> json -> SIGMA'
+        print '--------------------------------------------------'
+        print 'Nombre à traiter = %d' % (nombre)
+        print '--------------------------------------------------'
+        
+        i = 0
+        for candidature in listjson :
+            fichier = "%s%s" % (rootDocsJson, candidature)
+            
+            f = open(fichier, 'r')
+            data = f.read()
+            f.close()
+            
+            data = loads(data, encoding='utf-8')
+            data = jsonCleanUp(data)
+            
+            p = createPersonne(data)
+            d = createDossier(data, p)
+            do = createDossierOrigine(data, d)
+            da = createDossierAccueil(data, d)
+            dm = createDossierMobilite(data, d)
+            pieces = createDossierPieces(data, d)
+            
+            i = i + 1
+            
+            print '%d - %s' % (i, p)
+        print '--------------------------------------------------'
+        print 'Total traité = %d' % (i)
+        print '--------------------------------------------------'
diff --git a/project/formwcs/wcs2json/README.txt b/project/formwcs/wcs2json/README.txt
new file mode 100755 (executable)
index 0000000..d7eece1
--- /dev/null
@@ -0,0 +1,65 @@
+WCS2JSON
+
+
+Note: wcs n'est apparemment pas compatible avec python-2.6, 
+      il ne fonctionne qu'avec 2.5.
+Note: Il manque surement d'autres dependances
+
+
+1. Dependances
+=====================================================================
+
+1.1. Packages Debian
+
+$ sudo apt-get install python-html5lib python-lxml python-quixote wcs
+
+
+1.2. Easy install
+
+$ sudo easy_install-2.5 pisa
+
+
+
+2. Organisation
+=====================================================================
+
+
+2.1. wcs2json
+
+2.1.1. Fichiers
+
+wcs2json.py   : Extracte les donnees, les ecrit en json, peut etre 
+                execute directement.
+wcs2json.sh   : Wrapper au script python, verifie des permissions et 
+                fork l'execution du script.
+
+
+2.2. Generation de PDF
+
+2.2.1. Fichiers
+
+jsontopdf.py  : Classe qui definit le convertisseur
+template.html : Template django initial incomplet, le convertisseur va aller 
+                le chercher dans le dossier courant.
+settings.py   : Pour faire croire a l'engine de template de django qu'on
+                est dans un projet django
+
+2.2.2. Exemple
+
+    from jsontopdf import JsonToPdf
+    converter = JsonToPdf ("/path/to/template") #param facultatif
+    pdf = converter.convertFromFile (jsonFile)  #jsonFile est le path
+
+
+3. Utilisation
+=====================================================================
+
+1. Copier le dossier /var/lib/wcs/formulaire.auf.org du serveur de prod
+   en local. Ce path est a priori invariable.
+
+2. Editer wcs2json.py:
+    - Ligne 10; Mettre le nom du formulaire a traiter.
+    - Ligne 12/13: Specifier le dossier ou on peut que les fichiers 
+      json et zip soient places.
+
+3. L'executer et attendre
diff --git a/project/formwcs/wcs2json/demande-de-bourse-de-doctorat-2010-2011_field-names.txt b/project/formwcs/wcs2json/demande-de-bourse-de-doctorat-2010-2011_field-names.txt
new file mode 100755 (executable)
index 0000000..24131bb
--- /dev/null
@@ -0,0 +1,79 @@
+5:civilite
+6:nom
+7:nom_jeune_fille
+8:prenom
+9:pays_nationalite
+10:pays_naissance
+11:date_naissance
+12:ville_naissance
+14:adresse
+15:ville
+16:region
+17:code_postal
+18:pays_residence
+19:tel
+20:tel_pro
+21:email
+23:statut_personne
+24:fonction
+26:intitule_d_diplome
+27:date_d_diplome
+28:nom_etb
+29:pays_etb
+30:niveau
+32:particip_prog_auf
+33:particip_prog_auf_dernier
+34:particip_prog_auf_annee
+35:boursier_auf
+36:boursier_auf_type
+37:boursier_auf_annee
+39:programme
+40:programme_autre
+41:annee_programme
+44:o_etablissement_unite
+45:o_sc_civilite
+46:o_sc_nom
+47:o_sc_prenom
+48:o_sc_fonction
+49:o_sc_ville
+50:o_sc_tel_pro
+51:o_sc_email
+53:o_inst_civilite
+54:o_inst_nom
+55:o_inst_prenom
+56:o_inst_email
+59:a_etablissement_unite
+60:a_sc_civilite
+61:a_sc_nom
+62:a_sc_prenom
+63:a_sc_fonction
+64:a_sc_ville
+65:a_sc_tel_pro
+66:a_sc_email
+68:a_inst_civilite
+69:a_inst_nom
+70:a_inst_prenom
+71:a_inst_email
+75:o_date_debut_mobilite
+76:o_date_fin_mobilite
+77:o_nb_mois
+79:a_date_debut_mobilite
+80:a_date_fin_mobilite
+81:a_nb_mois
+83:date_inscription_these
+84:date_soutenance_these
+85:pays_soutenance
+86:type_these
+87:discipline
+88:intitule_projet
+90:mot_clef1
+91:mot_clef2
+92:mot_clef3
+95:engagement_nom
+96:engagement_prenom
+98:engagement_respect_reglement
+101:descriptif_these
+102:protocole_recherche
+103:curriculum_vitae
+108:a_etablissement
+109:o_etablissement
diff --git a/project/formwcs/wcs2json/jsontopdf.py b/project/formwcs/wcs2json/jsontopdf.py
new file mode 100755 (executable)
index 0000000..25281b8
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/bin/python
+
+import os, sys, simplejson, StringIO
+from ho import pisa
+
+from django.template import Context, Template
+from django.conf import settings
+
+
+class JsonToPdf:
+    "Convertit un fichier json de candidature en pdf."
+    inputBuffer = ""
+    templateFile = ""
+
+    def __init__ (self, 
+                  templateFileName = "template.html"):
+        self.templateFile = templateFileName
+
+    def readFile (self, fileName):
+        """Lire le fichier fileName et retourne son contenu.
+        Vide si le fichier n'existe pas
+        """
+        buffer = ""
+
+        try:
+            inFile = open (fileName, 'r')
+        except:
+            print "Fichier", fileName, "illisible"
+        else:
+            buffer = inFile.read ()
+
+        return buffer
+
+    def asJson (self, jsonBuffer):
+        """Convertit le buffer json en objet, 
+        retourne None si l'evaluation echoue"""
+        object = None
+
+        try:
+            object = simplejson.loads (jsonBuffer)
+        except:
+            print "Format json invalide"
+
+        return object
+
+    def convert (self):
+        "Effectue la conversion"
+
+        if len (self.inputBuffer) > 0:
+            object = self.asJson (self.inputBuffer)
+            if object is not None:
+                # Render
+                template = Template (self.readFile (self.templateFile))
+                context = Context (object)
+                # As HTML
+                html = template.render (context)
+                # Write PDF
+                pdf = StringIO.StringIO ()
+                pisa.CreatePDF(html, pdf)
+                return pdf
+
+    def convertFromFile (self, inputFile):
+        self.inputBuffer = self.readFile (inputFile)
+        return self.convert ()
+    
+    def convertBuffer (self, buffer):
+        self.inputBuffer = buffer
+        return self.convert ()
+
+
+
+if __name__ == "__main__":
+    if len (sys.argv) > 1:
+        settings.configure ()
+        for inputFile in sys.argv[1:]:
+            tmp = JsonToPdf ()
+            tmp.convertFromFile (inputFile)
+            #tmp.convertFromBuffer ("blabla", "out.pdf")
diff --git a/project/formwcs/wcs2json/settings.py b/project/formwcs/wcs2json/settings.py
new file mode 100755 (executable)
index 0000000..f02d56f
--- /dev/null
@@ -0,0 +1,7 @@
+# Bogus Django settings
+
+import os
+
+
+DEBUG = True
+
diff --git a/project/formwcs/wcs2json/style.css b/project/formwcs/wcs2json/style.css
new file mode 100644 (file)
index 0000000..85f308a
--- /dev/null
@@ -0,0 +1,28 @@
+h1 { color:#c60 ; margin:0px; padding:0px; }
+h2 { padding:5px; background-color:#ccc; }
+h3 { margin:0px; padding:0px; }
+p { margin:0px; padding:0px; }
+
+div.spacer { line-height:0.25cm; }
+
+table { width:100%; padding:0px; }
+table.titre { margin:10px 0px 10px 0px; }
+table.data { margin-top:5px; }
+
+td { width:49%; padding:2px; vertical-align:top; }
+table.titre td { padding:0px; }
+
+.bloc { border: solid 1px black; padding:5px; }
+.gutter { width:2%; }
+
+td.w15 { width:15%; padding:0px; vertical-align:top; }
+td.w25 { width:25%; padding:0px; vertical-align:top; }
+td.w30 { width:30%; padding:0px; vertical-align:top; }
+td.w35 { width:35%; padding:0px; vertical-align:top; }
+td.w50 { width:50%; padding:0px; vertical-align:top; }
+
+td.w20 { width:20%; padding:0px; vertical-align:top; }
+td.w80 { width:80%; padding:0px; vertical-align:top; }
+
+td.w40 { width:40%; padding:0px; vertical-align:top; }
+td.w60 { width:60%; padding:0px; vertical-align:top; }
diff --git a/project/formwcs/wcs2json/template.html b/project/formwcs/wcs2json/template.html
new file mode 100755 (executable)
index 0000000..4bdde39
--- /dev/null
@@ -0,0 +1,358 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>    
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />\r
+    <title>{{nom.upper}}, {{prenom.title}} : Bourses de doctorat - 2010-2011</title>\r
+    <link href="style.css" rel="stylesheet" type="text/css" />
+</head>
+<body>
+
+<h1>{{civilite}} {{prenom.title}} {{nom.upper}}</h1>
+
+<h2>Renseignements personnels</h2>
+
+<table class="data">
+<tr>
+    <td class="bloc">
+        <h3>Identification</h3>
+        <p>
+        {{civilite}} {{prenom.title}} {{nom.upper}}
+        {% if nom_jeune_fille %}<br />Nom de jeune fille : {{nom_jeune_fille.upper}}{% endif %}
+        </p>
+        <table>
+            <tr><td class="w40"><i>Nationalité : </i></td><td class="w60">{{pays_nationalite_orig}}</td></tr>
+            <tr>
+                <td class="w40"><i>Date de naissance : </i></td>
+                <td class="w60">                
+                {% with date_naissance as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr><td class="w40"><i>Ville de naissance : </i></td><td class="w60">{{ville_naissance.title|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Pays de naissance : </i></td><td class="w60">{{pays_naissance_orig|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <h3>Adresse de correspondance</h3>
+        <p>
+        {{adresse}}
+        <br />
+        {% if ville %}{{ville.title}}{% endif %}
+        {% if region %}, {{region}}{% endif %}
+        {% if code_postal %} {{code_postal}} {% endif %}
+        {% if pays_residence_orig %}<br />{{pays_residence_orig}}{% endif %}
+        </p>
+        <table>
+            <tr><td class="w40"><i>Téléphone personnel : </i></td><td class="w60">{{tel|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Téléphone professionel : </i></td><td class="w60">{{tel_pro|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Adresse électronique : </i></td><td class="w60">{{email|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+<table class="data">
+<tr>
+    <td class="bloc">
+        <h3>Situation universitaire</h3>
+        <table>
+            <tr><td class="w20"><i>Statut : </i></td><td class="w80">{{statut_personne|slice:"4:"|default:"n/d"}}</td></tr>
+            <tr><td class="w20"><i>Fonction : </i></td><td class="w80">{{fonction|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <h3>Dernier diplôme obtenu</h3>
+        <table>
+            <tr><td class="w40"><i>Intitulé : </i></td><td class="w60">{{intitule_d_diplome|default:"n/d"}}</td></tr>
+            <tr>
+                <td class="w40"><i>Date d'obtention : </i></td>
+                <td class="w60">
+                {% with date_d_diplome as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr><td class="w40"><i>Établissement : </i></td><td class="w60">{{nom_etb|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Pays : </i></td><td class="w60">{{pays_etb_orig|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Niveau (nb. années univ.) : </i></td><td class="w60">{{niveau|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+<table class="data">
+<tr>
+    <td class="bloc" colspan="3">
+        <h3>Lien avec l'AUF</h3>
+        <table>
+            <tr>
+            <td class="w35"><i>Candidat a déjà participé à un programme de l'AUF : </i></td><td class="w15">{{particip_prog_auf|default:"n/d"}}</td>
+            <td class="w35" style="padding-left:5px;"><i>Candidat a déjà bénéficié d'une bourse AUF : </i></td><td class="w15">{{boursier_auf|default:"n/d"}}</td>
+            </tr>
+            <tr>
+            <td class="w35"><i>Dernier programme participé : </i></td><td class="w15">{{particip_prog_auf_dernier|default:"n/d"}}</td>
+            <td class="w35" style="padding-left:5px;"><i>Type de bourse : </i></td><td class="w15">{{boursier_auf_type_orig|default:"n/d"}}</td>
+            </tr>
+            <tr>
+            <td class="w35"><i>Année : </i></td><td class="w15">{{particip_prog_auf_annee|default:"n/d"}}</td>
+            <td class="w35" style="padding-left:5px;"><i>Année : </i></td><td class="w15">{{boursier_auf_annee|default:"n/d"}}</td>
+            </tr>
+        </table>
+    </td>
+</tr>
+</table>
+
+<div class="spacer">&nbsp;</div>
+<table class="titre">
+<tr>
+    <td>
+        <h2>Origine</h2>
+    </td>
+    <td class="gutter"></td>
+    <td>
+        <h2>Accueil</h2>
+    </td>
+</tr>
+</table>
+
+<table class="data">
+<tr>
+    <td class="bloc">
+        <h3>Établissement d'origine</h3>
+        <p>
+        {{o_etablissement_orig|default:"n/d"}}
+        </p>
+        <table>
+            <tr><td class="w40"><i>Faculté, départ. ou labo : </i></td><td class="w60">{{o_etablissement_unite|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Ville : </i></td><td class="w60">{{o_sc_ville.title|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <h3>Établissement d'accueil</h3>
+        <p>
+        {{a_etablissement_orig|default:"n/d"}}
+        </p>
+        <table>
+            <tr><td class="w40"><i>Faculté, départ. ou labo : </i></td><td class="w60">{{a_etablissement_unite|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Ville : </i></td><td class="w60">{{a_sc_ville.title|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+<table class="data">
+<tr>
+    <td class="bloc">
+        <table>
+            <tr>
+            <td class="w40"><h3>Accord scientifique</h3></td>
+            <td class="w60">
+                {% if o_sc_civilite %}{{o_sc_civilite}} {% endif %}
+                {% if o_sc_prenom %}{{o_sc_prenom.title}} {% endif %}
+                {% if o_sc_nom %}{{o_sc_nom.upper}}{% endif %}
+            </td>
+            </tr>
+            <tr><td class="w40"><i>Fonction : </i></td><td class="w60">{{o_sc_fonction|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Téléphone professionnel : </i></td><td class="w60">{{o_sc_tel_pro|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Adresse électronique : </i></td><td class="w60">{{o_sc_email|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <table>
+            <tr>
+            <td class="w40"><h3>Accord scientifique</h3></td>
+            <td class="w60">
+                {% if a_sc_civilite %}{{a_sc_civilite}} {% endif %}
+                {% if a_sc_prenom %}{{a_sc_prenom.title}} {% endif %}
+                {% if a_sc_nom %}{{a_sc_nom.upper}}{% endif %}
+            </td>
+            </tr>
+            <tr><td class="w40"><i>Fonction : </i></td><td class="w60">{{a_sc_fonction|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Téléphone professionnel : </i></td><td class="w60">{{a_sc_tel_pro|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Adresse électronique : </i></td><td class="w60">{{a_sc_email|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+<table class="data">
+<tr>
+    <td class="bloc">
+        
+        <table>
+            <tr>
+            <td class="w40"><h3>Directeur de thèse</h3></td>
+            <td class="w60">
+                {% if o_inst_civilite %}{{o_inst_civilite}} {% endif %}
+                {% if o_inst_prenom %}{{o_inst_prenom.title}} {% endif %}
+                {% if o_inst_nom %}{{o_inst_nom.upper}}{% endif %}
+            </td>
+            </tr>
+            <tr><td class="w40"><i>Adresse électronique : </i></td><td class="w60">{{o_inst_email|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <table>
+            <tr>
+            <td class="w40"><h3>Directeur de thèse</h3></td>
+            <td class="w60">
+                {% if a_inst_civilite %}{{a_inst_civilite}} {% endif %}
+                {% if a_inst_prenom %}{{a_inst_prenom.title}} {% endif %}
+                {% if a_inst_nom %}{{a_inst_nom.upper}}{% endif %}
+            </td>
+            </tr>
+            <tr><td class="w40"><i>Adresse électronique : </i></td><td class="w60">{{a_inst_email|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+
+<div class="spacer">&nbsp;</div>
+<h2>Mobilité</h2>
+
+<table>
+<tr>
+    <td class="bloc">
+        <h3>Période de mobilité - Origine</h3>
+        <table>
+            <tr>
+                <td class="w40"><i>Date de début : </i></td>
+                <td class="w60">
+                {% with o_date_debut_mobilite as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr>
+                <td class="w40"><i>Date de fin : </i></td>
+                <td class="w60">
+                {% with o_date_fin_mobilite as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr><td class="w40"><i>Nombre de mois : </i></td><td class="w60">{{o_nb_mois|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <h3>Période de mobilité - Accueil</h3>
+        <table>
+            <tr>
+                <td class="w40"><i>Date de début : </i></td>
+                <td class="w60">
+                {% with a_date_debut_mobilite as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+                </tr>
+            <tr>
+                <td class="w40"><i>Date de fin : </i></td>
+                <td class="w60">
+                {% with a_date_fin_mobilite as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr><td class="w40"><i>Nombre de mois : </i></td><td class="w60">{{a_nb_mois|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+<table class="data">
+<tr>
+    <td class="bloc">
+        <h3>Thèse</h3>
+        <table>
+            <tr>
+                <td class="w40"><i>Date d'inscription en thèse : </i></td>
+                <td class="w60">
+                {% with date_inscription_these as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr>
+                <td class="w40"><i>Date de soutenance prévue : </i></td>
+                <td class="w60">
+                {% with date_soutenance_these as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr><td class="w40"><i>Pays de soutenance prévu : </i></td><td class="w60">{{pays_soutenance_orig|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Type de thèse : </i></td><td class="w60">{{type_these|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <h3>Formation en cours</h3>
+        <table>
+            <tr><td class="w40"><i>Diplôme préparé : </i></td><td class="w60">{{programme|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Si autre : </i></td><td class="w60">{{programme_autre|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Niveau (nb. années univ.) : </i></td><td class="w60">{{annee_programme|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+<table class="data">
+<tr>
+    <td class="bloc">
+        <h3>Dossier scientifique</h3>
+        <table>
+            <tr><td class="w40"><i>Intitulé du sujet de thèse : </i></td><td class="w60">{{intitule_projet|default:"n/d"}}</td></tr>
+            <tr><td class="w40"><i>Mots-clés : </i></td>
+            <td class="w60">
+                {% if mot_clef1 %}{{mot_clef1.upper}}{% endif %}
+                {% if mot_clef2 %}, {{mot_clef2.upper}}{% endif %}
+                {% if mot_clef3 %}, {{mot_clef3.upper}}{% endif %}
+            </td>
+            </tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <h3>Discipline</h3>
+        <p>
+        {{discipline_orig|default:"n/d"}}
+        </p>
+    </td>
+</tr>
+</table>
+
+</body>
+</html>
diff --git a/project/formwcs/wcs2json/template.py b/project/formwcs/wcs2json/template.py
new file mode 100644 (file)
index 0000000..8865b17
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/python2.5
+
+from django.template import Context, Template
+
+from jsontopdf import JsonToPdf
+from django.conf import settings
+
+if __name__ == "__main__":
+    converter = JsonToPdf()
+    pdf = converter.convertFromFile('../docs/template_test/test.json')
+
+    file = open('test.pdf', 'w')
+    file.write(pdf.getvalue())
+    file.close()
diff --git a/project/formwcs/wcs2json/template.sh b/project/formwcs/wcs2json/template.sh
new file mode 100644 (file)
index 0000000..4298ce6
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+export DJANGO_SETTINGS_MODULE=settings
+python2.5 template.py
diff --git a/project/formwcs/wcs2json/wcs2json.py b/project/formwcs/wcs2json/wcs2json.py
new file mode 100755 (executable)
index 0000000..3316670
--- /dev/null
@@ -0,0 +1,195 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+####
+# Constantes
+
+# le nom d'hote hébergeant wcs
+VHOST = "formulaires.auf.org"
+# nom du formulaire à explorer
+FORM_NAME = "demande-de-bourse-de-doctorat-2010-2011"
+# nom des fichiers à générer (un par formulaire)
+OUTPUT_DIRECTORY = "/srv/www/wcs/%s/%s" % (VHOST, FORM_NAME)
+OUTPUT_DIRECTORY = "/home/cyril/docs"
+
+URL_BASE = "http://%s/backoffice/%s" % (VHOST, FORM_NAME)
+URL_DOWNLOAD = "%s/%s" % (URL_BASE, "%s/download?f=%s")
+#TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
+TIME_FORMAT = "%Y-%m-%d"
+
+
+###
+# Imports
+
+# Quixote se cache
+import sys
+sys.path += ['/usr/share/python-support/python-quixote']
+
+import os, zipfile, time, re, simplejson, shutil
+from wcs import publisher
+from wcs.formdef import FormDef
+from wcs.fields import TitleField, CommentField, TextField, \
+                       StringField, ItemField, EmailField, \
+                       DateField, FileField, BoolField
+
+from jsontopdf import JsonToPdf
+
+
+###
+# Methodes
+
+# Supprime les accents des caracteres accentues, 
+# et remplace les caracteres non alphabetiques par '-'
+def cleanup(string):
+    result = ''
+    avec_accent = u'çÇáàâÁÀÂéèêëÉÈÊËíìîïÍÌÎÏóòôöÓÒÔÖúùûüÚÙÛÜýỳyÿÝỲYŸ'
+    sans_accent = u'cCaaaAAAeeeeEEEEiiiiIIIIooooOOOOuuuuUUUUyyyyYYYY'
+
+    if type(string) is not unicode:
+        string = unicode(string, 'utf-8')
+        wasUnicode  = False
+
+    for char in string:
+        i = avec_accent.find (char)
+        if i >= 0:
+            char = sans_accent[i]
+        elif char != '-' and not ('a' <= char.lower() <= 'z'):
+            char = '-'
+        result += char
+
+    if not wasUnicode:
+        result = result.encode('utf-8')
+
+    return result
+
+
+###
+# Main
+
+# Cherche les "noms" des champs
+f = open(FORM_NAME + '_field-names.txt', 'r')
+field_names = dict([l.strip().split(':') for l in f.readlines()])
+f.close()
+
+# Cree un publisher pour app_dir
+pub = publisher.WcsPublisher.create_publisher()
+pub.app_dir = os.path.join(pub.app_dir, VHOST)
+
+# Ouvre la definition du form, et va parcourir les entites
+formdef = FormDef.get_by_urlname(FORM_NAME)
+for entity in formdef.data_class().select():
+    result = { 'num_dossier': entity.id }
+    qfiles = { }
+    jsonFile = ""
+    attachments = []
+    # Parcours des champs de l'entite
+    for field in formdef.fields:
+        field_id = str(field.id)
+
+        # Champs a ignorer
+        if not field_id in entity.data:
+            continue
+        if isinstance(field, TitleField) or isinstance(field, CommentField):
+            continue
+
+        # Recupere le nom et la valeur
+        field_name = field_names.get(field_id, field.label)
+        data = entity.data.get(field_id)
+
+        if isinstance(field, StringField) or isinstance(field, TextField) \
+                or isinstance(field, EmailField):
+            result[field_name] = data
+
+        elif isinstance(field, ItemField):
+            # Suppression des informations non necessaires
+            if field_name in ("o_etablissement", "a_etablissement"):
+                result[field_name + '_orig'] = data
+                matches = re.search ("\((\d+).*\)$", data)
+                if matches:
+                    groups = matches.groups ()
+                    if len (groups) > 0:
+                        data = groups[0]
+            elif field_name.startswith ("pays"):
+                result[field_name + '_orig'] = data
+                matches = re.search ("\(([a-zA-Z]{2}).*\)$", data)
+                if matches:
+                    groups = matches.groups ()
+                    if len (groups) > 0:
+                        data = groups[0]
+            elif field_name == "boursier_auf_type" and data is not None:
+                result[field_name + '_orig'] = data
+                matches = re.search ("\(([a-zA-Z]{2}).*\)$", data)
+                if matches:
+                    groups = matches.groups ()
+                    if len (groups) > 0:
+                        data = groups[0]
+            elif field_name == 'discipline':
+                result[field_name + '_orig'] = data
+                m = re
+                index = data.rfind('(')
+                if index >= 0:
+                    end = data.find(')', index+1)
+                    data = data[index+1:end]
+            result[field_name] = data
+
+        elif isinstance(field, BoolField):
+            result[field_name] = (data == 'True')
+
+        elif isinstance(field, FileField):
+            extension = data.orig_filename.rpartition('.')[2].lower()
+            result[field_name] = "%s.%s" % (field_name, extension)
+            qfiles[field_name] = data.qfilename
+
+        elif isinstance(field, DateField):
+            data = time.strftime(TIME_FORMAT, entity.data.get(field_id))
+            result[field_name] = data
+
+        else:
+            data = entity.data.get(field_id)
+            print "WARNING: unknown field type '%s' for '%s'" % \
+                                    (field.__class__.__name__, field.label)
+            raise RuntimeError
+
+    # Prepare le nom de fichier json
+    num_dossier = result['num_dossier']
+    nom = cleanup('-'.join(result['nom'].split()).upper())
+    prenom = cleanup('-'.join(result['prenom'].split()).upper())
+    email = result['email'].replace('@','-').lower()
+
+    baseFile = "%04d-%s-%s-%s" % (num_dossier, nom, prenom, email)
+
+    jsonFile = "%s/json/%s.json" % (OUTPUT_DIRECTORY, baseFile)
+
+    # Ecriture json
+    f = open(jsonFile, 'w')
+    f.write(simplejson.dumps(result, ensure_ascii=False))
+    f.close()
+
+    # Prepare la copie dans le script shell
+    for f in qfiles:
+        currentFileName = "%s/uploads/%s" % (pub.app_dir, qfiles[f])
+        newFileName = "%s/docs/%s_%s" % (OUTPUT_DIRECTORY, baseFile, result[f])
+        attachments.append ((currentFileName, newFileName))
+
+    # Generation du pdf
+    pdfFile = "%s/docs/%s.pdf" % (OUTPUT_DIRECTORY, baseFile)
+    converter = JsonToPdf ()
+    pdf = converter.convertFromFile (jsonFile)
+    f = open (pdfFile, 'w')
+    f.write (pdf.getvalue ())
+    f.close ()
+
+    # Creation de l'archive
+    zipFile = "%s/docs/%s.zip" % (OUTPUT_DIRECTORY, baseFile)
+    archive = zipfile.ZipFile(zipFile, "w", zipfile.ZIP_STORED)
+    
+    # PDF
+    archive.write (pdfFile, os.path.basename (pdfFile))
+    
+    # Autres fichiers, avec noms reels
+    for (attachment, displayName) in attachments:
+        shutil.copy (attachment, displayName)
+        archive.write (displayName, os.path.basename (displayName))
+
+    archive.close ()
+
diff --git a/project/formwcs/wcs2json/wcs2json.sh b/project/formwcs/wcs2json/wcs2json.sh
new file mode 100755 (executable)
index 0000000..25ad291
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/sh
+#sudo chmod -R g+rX /var/lib/wcs/
+export DJANGO_SETTINGS_MODULE=settings
+python2.5 wcs2json.py >& wcs2json.log
+tail wcs2json.log
diff --git a/project/media/css/pdf.css b/project/media/css/pdf.css
new file mode 100644 (file)
index 0000000..85f308a
--- /dev/null
@@ -0,0 +1,28 @@
+h1 { color:#c60 ; margin:0px; padding:0px; }
+h2 { padding:5px; background-color:#ccc; }
+h3 { margin:0px; padding:0px; }
+p { margin:0px; padding:0px; }
+
+div.spacer { line-height:0.25cm; }
+
+table { width:100%; padding:0px; }
+table.titre { margin:10px 0px 10px 0px; }
+table.data { margin-top:5px; }
+
+td { width:49%; padding:2px; vertical-align:top; }
+table.titre td { padding:0px; }
+
+.bloc { border: solid 1px black; padding:5px; }
+.gutter { width:2%; }
+
+td.w15 { width:15%; padding:0px; vertical-align:top; }
+td.w25 { width:25%; padding:0px; vertical-align:top; }
+td.w30 { width:30%; padding:0px; vertical-align:top; }
+td.w35 { width:35%; padding:0px; vertical-align:top; }
+td.w50 { width:50%; padding:0px; vertical-align:top; }
+
+td.w20 { width:20%; padding:0px; vertical-align:top; }
+td.w80 { width:80%; padding:0px; vertical-align:top; }
+
+td.w40 { width:40%; padding:0px; vertical-align:top; }
+td.w60 { width:60%; padding:0px; vertical-align:top; }
diff --git a/project/settings.py b/project/settings.py
new file mode 100644 (file)
index 0000000..c01d1d2
--- /dev/null
@@ -0,0 +1,76 @@
+# Django settings for sigmawcs project.
+
+import os.path
+
+from conf import *
+
+ADMINS = (
+    # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Montreal'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'fr-ca'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = '/media/'
+STATIC_DOC_ROOT = os.path.join(os.path.dirname(__file__), 'media').replace('\\','/')
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/admin/media/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'b+&#6w^)l4s6feh+*r(k2i-%a#a+zhir-xfidz#9bou%(f8ma$'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.load_template_source',
+    'django.template.loaders.app_directories.load_template_source',
+#     'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+)
+
+ROOT_URLCONF = 'sigmawcs.urls'
+
+TEMPLATE_DIRS = (
+    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+    # Always use forward slashes, even on Windows.
+    # Don't forget to use absolute paths, not relative paths.
+    os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),
+)
+
+INSTALLED_APPS = (
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'wcs',
+)
diff --git a/project/sigma_v1/__init__.py b/project/sigma_v1/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/project/sigma_v1/models.py b/project/sigma_v1/models.py
new file mode 100644 (file)
index 0000000..cd04f09
--- /dev/null
@@ -0,0 +1,389 @@
+# -*- encoding: utf-8 -*-
+from datetime import date
+
+from django.db import models
+
+GENRE = (
+    ('M', "Masculin"),
+    ('F', "Féminin"),
+)
+CIVILITE = (
+    ('MR', "Monsieur"),
+    ('MM', "Madame"),
+    ('ME', "Mademoiselle"),
+)
+
+
+class Personne(models.Model):
+    """Informations personnelles de chaque candidat.
+    """
+        
+    id = models.AutoField(primary_key=True, db_column='C_PERSONNE')
+    sexe = models.CharField(max_length=3, db_column='Y_SEXE', blank=True, default=None)
+    civilite = models.CharField(max_length=6, db_column='Y_CIVILITE', blank=True, choices=CIVILITE)
+    nom = models.CharField(max_length=100, db_column='L_NOM')
+    nom_index = models.CharField(max_length=100, db_column='L_NOM_INDEX')
+    prenom = models.CharField(max_length=100, db_column='L_PRENOM', default="")
+    prenom_index = models.CharField(max_length=100, db_column='L_PRENOM_INDEX', default="")
+    nom_jeune_fille = models.CharField(max_length=100, db_column='L_NOM_JEUNE_FILLE', default="")
+    nom_jeune_fille_index = models.CharField(max_length=100, db_column='L_NOM_JEUNE_FILLE_INDEX', default="")
+    pays_nationalite = models.CharField(max_length=10, db_column='F_PAYS_NATIONALITE') # foreign
+    
+    # naissance
+    ville_naissance = models.CharField(max_length=50, db_column='L_VILLE_NAISSANCE')
+    date_naissance = models.DateField(db_column='D_NAISSANCE')
+    pays_naissance = models.CharField(max_length=10, db_column='F_PAYS_NAISSANCE')    # foreign
+    
+    # coordonnées
+    pays_residence = models.CharField(max_length=10, db_column='F_PAYS_RESIDENCE')    # foreign
+    adresse = models.TextField(db_column='L_ADRESSE')
+    code_postal = models.CharField(max_length=10, db_column='L_CODE_POSTAL', default="")
+    ville = models.CharField(max_length=50, db_column='L_VILLE')
+    region = models.CharField(max_length=50, db_column='L_REGION', default="")
+    tel = models.CharField(max_length=25, db_column='N_TEL', default="")
+    fax = models.CharField(max_length=25, db_column='N_FAX')
+    tel_pro = models.CharField(max_length=25, db_column='N_TEL_PRO', default="")
+    fax_pro = models.CharField(max_length=25, db_column='N_FAX_PRO', default="")
+    email = models.CharField(max_length=100, db_column='L_EMAIL', default="")
+
+    # meta
+    utilisateur_creation = models.IntegerField(db_column='F_UTILISATEUR_CREATION', default=0) #foreign
+    utilisateur_maj = models.IntegerField(null=True, db_column='F_UTILISATEUR_MAJ', blank=True, default=None) # foreign
+    date_creation = models.DateField(auto_now_add=True, db_column='D_CREATION_PERSONNE')
+    date_maj = models.DateField(null=True, db_column='D_MAJ_PERSONNE', blank=True)
+    
+    class Meta:
+        db_table = u'GM_PERSONNE'
+        
+    def __unicode__(self):
+        return u"%s, %s [%d]" % (self.nom, self.prenom, self.id)
+
+
+class Dossier(models.Model):
+    """Informations générales du dossier de candidature.
+    """
+    REPONSE = (
+        ('sr', "sr"),
+        ('a', "a"),
+        ('r', "r"),
+    )
+    
+    id = models.AutoField(primary_key=True, db_column='C_DOSSIER')
+    statut = models.IntegerField(db_column='F_STATUT', default=1)   # foreign
+    mobilite = models.IntegerField(unique=True, db_column='F_MOBILITE', default=0)   # foreign
+    personne = models.OneToOneField('Personne', db_column='F_PERSONNE')
+    statut_personne = models.IntegerField(db_column='F_STATUT_PERSONNE', default=0)  # foreign
+    bureau_rattachement = models.IntegerField(null=True, db_column='F_BUREAU_RATTACHEMENT', blank=True)   # foreign Bureau.id_num
+    fonction = models.CharField(max_length=100, db_column='L_FONCTION', default="")
+    intitule_d_diplome = models.CharField(max_length=100, db_column='L_INTITULE_D_DIPLOME', default="")
+    date_d_diplome = models.DateField(db_column='D_D_DIPLOME')
+    nom_etb = models.CharField(max_length=100, db_column='L_NOM_ETB', default="")
+    pays_etb = models.CharField(max_length=10, db_column='F_PAYS_ETB')    # foreign
+    niveau = models.IntegerField(db_column='F_NIVEAU', default=0)    # foreign
+    programme = models.CharField(max_length=255, db_column='L_PROGRAMME', default="")
+    annee_programme = models.IntegerField(db_column='N_ANNEE_PROGRAMME', default=0)
+    categorie_bourse = models.CharField(max_length=12, db_column='F_CATEGORIE_BOURSE', default="")    # foreign
+    annee_bourse = models.IntegerField(db_column='N_ANNEE_BOURSE', default=0)
+    
+    # traitement
+    classement_1 = models.IntegerField(null=True, db_column='N_CLASSEMENT_1', blank=True)
+    classement_2 = models.IntegerField(null=True, db_column='N_CLASSEMENT_2', blank=True)
+    classement_3 = models.IntegerField(null=True, db_column='N_CLASSEMENT_3', blank=True)
+    opp_regionale = models.CharField(max_length=9, db_column='L_OPP_REGIONALE')
+    coche_selection = models.BooleanField(db_column='I_COCHE_SELECTION', default=False)
+    reponse_notification = models.CharField(max_length=6, db_column='Y_REPONSE_NOTIFICATION', choices=REPONSE, default='sr')
+    commentaire_notification = models.CharField(max_length=255, db_column='L_COMMENTAIRE_NOTIFICATION')
+    moyenne_academique = models.FloatField(null=True, db_column='N_MOYENNE_ACADEMIQUE', blank=True)
+    autres_criteres = models.CharField(max_length=255, db_column='L_AUTRES_CRITERES')
+    erreurs_recevabilite = models.TextField(db_column='L_ERREURS_RECEVABILITE')
+    repechage = models.BooleanField(db_column='I_REPECHAGE', default=False)
+    rendu_irrecevable = models.BooleanField(db_column='I_RENDU_IRRECEVABLE', default=False)
+    
+    # meta
+    etat = models.BooleanField(db_column='I_ETAT', default=False)
+    date_maj = models.CharField(max_length=10, db_column='D_MAJ_DOSSIER', default='0000-00-00')
+    dd_activation = models.CharField(max_length=10, db_column='DD_ACTIVATION', default='0000-00-00')
+    df_activation = models.CharField(max_length=10, db_column='DF_ACTIVATION', default='0000-00-00')
+    utilisateur_creation = models.IntegerField(db_column='F_UTILISATEUR_CREATION', default=0)    # foreign
+    utilisateur_maj = models.IntegerField(db_column='F_UTILISATEUR_MAJ')  # foreign
+    
+    class Meta:
+        db_table = u'GM_DOSSIER'
+        
+    def __unicode__(self):
+        return u"%s - appel %d - dossier [%d]" % (self.personne, self.mobilite, self.id)
+
+
+class DossierOrigine(models.Model):
+    """Informations sur le contexte d'origine du candidat.
+    """
+    
+    dossier = models.OneToOneField('Dossier', db_column='F_DOSSIER')
+    id = models.AutoField(primary_key=True, db_column='ID_DOSSIER_ORIGINE')
+    pays = models.CharField(max_length=10, db_column='F_PAYS_ORIGINE', default="")    # foreign
+    etablissement = models.CharField(max_length=10, db_column='F_ETABLISSEMENT', blank=True)  # foreign, PAS id de GDE = PAS C_N_ETABLISSEMENT
+    erreur_recevabilite_etbt_origine = models.BooleanField(db_column='ERREUR_RECEVABILITE_ETBT_ORIGINE', default=False)
+    autre_etb = models.CharField(max_length=100, db_column='L_AUTRE_ETB', default="")
+    adresse = models.TextField(db_column='L_ADRESSE', default="")
+    code_postal = models.CharField(max_length=10, db_column='L_CODE_POSTAL', default="")
+    ville = models.CharField(max_length=50, db_column='L_VILLE', default="")
+    region = models.CharField(max_length=50, db_column='L_REGION', default="")
+    url_site = models.CharField(max_length=150, db_column='L_URL_SITE', default="")
+    inst_civilite = models.CharField(max_length=6, db_column='Y_INST_CIVILITE', choices=CIVILITE, default='MR')
+    inst_nom = models.CharField(max_length=100, db_column='L_INST_NOM', default="s/o")
+    inst_prenom = models.CharField(max_length=100, db_column='L_INST_PRENOM', default="s/o")
+    inst_fonction = models.CharField(max_length=100, db_column='L_INST_FONCTION', default="s/o")
+    sc_civilite = models.CharField(max_length=6, db_column='Y_SC_CIVILITE', choices=CIVILITE, default='MR')
+    sc_nom = models.CharField(max_length=100, db_column='L_SC_NOM', default="")
+    sc_prenom = models.CharField(max_length=100, db_column='L_SC_PRENOM', default="")
+    sc_fonction = models.CharField(max_length=100, db_column='L_SC_FONCTION', default="")
+    sc_faculte = models.CharField(max_length=150, db_column='L_SC_FACULTE', default="")
+    sc_adresse = models.TextField(db_column='L_SC_ADRESSE')
+    sc_code_postal = models.CharField(max_length=10, db_column='L_SC_CODE_POSTAL')
+    sc_ville = models.CharField(max_length=50, db_column='L_SC_VILLE', default="")
+    sc_email = models.CharField(max_length=100, db_column='L_SC_EMAIL', default="")
+    sc_tel_pro = models.CharField(max_length=25, db_column='L_SC_TEL_PRO', default="")
+    sc_fax_pro = models.CharField(max_length=25, db_column='L_SC_FAX_PRO')
+    
+    class Meta:
+        db_table = u'GM_DOSSIER_ORIGINE'
+        
+    def __unicode__(self):
+        return u"%s - origine [%d]" % (self.dossier.personne, self.id)
+
+
+class DossierAccueil(models.Model):
+    """Informations sur le contexte d'accueil du candidat.
+    """
+    
+    dossier = models.OneToOneField('Dossier', db_column='F_DOSSIER', default=0)
+    id = models.AutoField(primary_key=True, db_column='ID_DOSSIER_ACCUEIL')
+    pays = models.CharField(max_length=10, db_column='F_PAYS_ACCUEIL', default="")    # foreign
+    etablissement = models.CharField(max_length=10, db_column='F_ETABLISSEMENT', blank=True)  # foreign, PAS id de GDE = PAS C_N_ETABLISSEMENT
+    erreur_recevabilite_etbt_accueil = models.BooleanField(db_column='ERREUR_RECEVABILITE_ETBT_ACCUEIL', default=False)
+    autre_etb = models.CharField(max_length=100, db_column='L_AUTRE_ETB', default="")
+    adresse = models.TextField(db_column='L_ADRESSE', default="")
+    code_postal = models.CharField(max_length=10, db_column='L_CODE_POSTAL', default="")
+    ville = models.CharField(max_length=50, db_column='L_VILLE', default="")
+    region = models.CharField(max_length=50, db_column='L_REGION', default="")
+    url_site = models.CharField(max_length=150, db_column='L_URL_SITE', default="")
+    inst_civilite = models.CharField(max_length=6, db_column='Y_INST_CIVILITE', choices=CIVILITE, default='MR')
+    inst_nom = models.CharField(max_length=100, db_column='L_INST_NOM')
+    inst_prenom = models.CharField(max_length=100, db_column='L_INST_PRENOM')
+    inst_fonction = models.CharField(max_length=100, db_column='L_INST_FONCTION')
+    inst_email = models.CharField(max_length=100, db_column='L_INST_EMAIL')
+    inst_tel_pro = models.CharField(max_length=25, db_column='L_INST_TEL_PRO')
+    inst_fax_pro = models.CharField(max_length=25, db_column='L_INST_FAX_PRO')
+    sc_civilite = models.CharField(max_length=6, db_column='Y_SC_CIVILITE', choices=CIVILITE, default='MR')
+    sc_nom = models.CharField(max_length=100, db_column='L_SC_NOM', default="")
+    sc_prenom = models.CharField(max_length=100, db_column='L_SC_PRENOM', default="")
+    sc_fonction = models.CharField(max_length=100, db_column='L_SC_FONCTION', default="")
+    sc_faculte = models.CharField(max_length=150, db_column='L_SC_FACULTE', default="")
+    sc_adresse = models.TextField(db_column='L_SC_ADRESSE')
+    sc_code_postal = models.CharField(max_length=10, db_column='L_SC_CODE_POSTAL')
+    sc_ville = models.CharField(max_length=50, db_column='L_SC_VILLE', default="")
+    sc_email = models.CharField(max_length=100, db_column='L_SC_EMAIL', default="")
+    sc_tel_pro = models.CharField(max_length=25, db_column='L_SC_TEL_PRO', default="")
+    sc_fax_pro = models.CharField(max_length=25, db_column='L_SC_FAX_PRO')
+    
+    class Meta:
+        db_table = u'GM_DOSSIER_ACCUEIL'
+        
+    def __unicode__(self):
+        return u"%s - accueil [%d]" % (self.dossier.personne, self.id)
+
+
+class DossierMobilite(models.Model):
+    """Informations sur la mobilité demandée par le candidat.
+    """
+    TYPE_THESE = (
+        ('CT', "Co-Tutelle"),
+        ('CD', "Co-Direction"),
+        ('AU', "Autre"),
+    )
+    
+    id = models.AutoField(primary_key=True, db_column='ID_DOSSIER_MOBILITE')
+    dossier = models.OneToOneField('Dossier', db_column='F_DOSSIER', default=0)
+    dd_mobilite = models.DateField(db_column='DD_MOBILITE')
+    df_mobilite = models.DateField(db_column='DF_MOBILITE')
+    total_mobilite = models.IntegerField(db_column='N_TOTAL_MOBILITE', default=0, verbose_name="Durée totale mobilité souhaitée")
+    intitule_projet = models.TextField(db_column='L_INTITULE_PROJET')
+    mot_clef1 = models.CharField(max_length=50, db_column='L_MOT_CLEF1', default="")
+    mot_clef2 = models.CharField(max_length=50, db_column='L_MOT_CLEF2', default="")
+    mot_clef3 = models.CharField(max_length=50, db_column='L_MOT_CLEF3', default="")
+    intitule_diplome = models.CharField(max_length=100, db_column='L_INTITULE_DIPLOME')
+    niveau_encours = models.IntegerField(db_column='F_NIVEAU_ENCOURS', default=0)    # foreign
+    type_intervention = models.IntegerField(db_column='F_TYPE_INTERVENTION', default=0)  # foreign
+    public_vise = models.IntegerField(db_column='F_PUBLIC_VISE', default=0)  # foreign
+    autres_publics = models.CharField(max_length=50, db_column='L_AUTRES_PUBLICS')
+    discipline = models.CharField(max_length=10, db_column='F_DISCIPLINE', default=0)    # foreign
+    sous_discipline = models.CharField(max_length=50, db_column='L_SOUS_DISCIPLINE', default="")
+    alt_mois_accueil = models.IntegerField(db_column='N_ALT_MOIS_ACCUEIL', default=0)
+    alt_mois_origine = models.IntegerField(db_column='N_ALT_MOIS_ORIGINE', default=0)
+    mobilite_accueil = models.BooleanField(db_column='I_MOBILITE_ACCUEIL', default=False)
+    intitule_diplome_demande = models.CharField(max_length=100, db_column='L_INTITULE_DIPLOME_DEMANDE', default="")
+    niveau_demande = models.IntegerField(db_column='F_NIVEAU_DEMANDE', default=0)    # foreign
+    obtention_prevu = models.CharField(max_length=10, db_column='D_OBTENTION_PREVU', default='0000-00-00')
+    date_inscription_these = models.DateField(db_column='D_INSCRIPTION_THESE')
+    pays_soutenance = models.CharField(max_length=10, db_column='F_PAYS_SOUTENANCE', default=0)  # foreign
+    date_soutenance_these = models.DateField(db_column='D_SOUTENANCE_THESE')
+    type_these = models.CharField(max_length=6, db_column='Y_TYPE_THESE', choices=TYPE_THESE, default='CT')
+    type_these_autre = models.CharField(max_length=255, db_column='L_TYPE_THESE_AUTRE')
+    dir_acc_civilite = models.CharField(max_length=6, db_column='Y_DIR_ACC_CIVILITE', choices=CIVILITE, default='MR')
+    dir_ori_civilite = models.CharField(max_length=6, db_column='Y_DIR_ORI_CIVILITE', choices=CIVILITE, default='MR')
+    dir_acc_nom = models.CharField(max_length=100, db_column='L_DIR_ACC_NOM', default="")
+    dir_acc_prenom = models.CharField(max_length=100, db_column='L_DIR_ACC_PRENOM', default="")
+    dir_ori_nom = models.CharField(max_length=100, db_column='L_DIR_ORI_NOM', default="")
+    dir_ori_prenom = models.CharField(max_length=100, db_column='L_DIR_ORI_PRENOM', default="")
+    
+    class Meta:
+        db_table = u'GM_DOSSIER_MOBILITE'
+        
+    def __unicode__(self):
+        return u"%s - mobilite [%d]" % (self.dossier.personne, self.id)
+
+
+class DossierPieces(models.Model):
+    """Informations sur les pièces administratives jointes au dossier de candidature.
+    """
+    
+    id = models.AutoField(primary_key=True, db_column='ID_DOSSIER_PIECE')
+    dossier = models.OneToOneField('Dossier', db_column='F_DOSSIER', default=0)
+    presente = models.BooleanField(db_column='I_PIECE', default=False)
+    piece = models.IntegerField(db_column='F_PIECE', default=0)  # foreign
+    conforme = models.BooleanField(db_column='I_CONFORME', default=False)
+    commentaire = models.CharField(max_length=255, db_column='L_PIECE_COMMENTAIRE', default="")
+    
+    class Meta:
+        db_table = u'GM_DOSSIER_PIECES'
+        
+    def __unicode__(self):
+        return u"%s - piece %d [%d]" % (self.dossier.personne, self.piece, self.id)
+    
+class Mobilite(object):     # Appel
+    id = 0  #c_mobilite = models.IntegerField(primary_key=True, db_column='C_MOBILITE')
+#    id_mobilite = models.IntegerField(db_column='ID_MOBILITE')
+    code_budgetaire = ''    #f_projet = models.CharField(max_length=36, db_column='F_PROJET')
+    wcs_form_name = ''
+#    f_commutateur = models.IntegerField(db_column='F_COMMUTATEUR')
+#    f_id_categorie_bourse = models.IntegerField(db_column='F_ID_CATEGORIE_BOURSE')
+#    f_statut_mobilite = models.IntegerField(db_column='F_STATUT_MOBILITE')
+    nom = models.CharField(max_length=150, db_column='L_MOBILITE', blank=True)
+#    i_coda = models.IntegerField(db_column='I_CODA')
+#    i_ao = models.IntegerField(db_column='I_AO')
+#    y_periode_mobilite = models.CharField(max_length=30, db_column='Y_PERIODE_MOBILITE')
+#    y_selection = models.CharField(max_length=15, db_column='Y_SELECTION')
+#    n_validite_diplome = models.IntegerField(db_column='N_VALIDITE_DIPLOME')
+#    i_nb_ex = models.IntegerField(db_column='I_NB_EX')
+    dd_mobilite = models.DateField(db_column='DD_MOBILITE')
+    df_mobilite = models.DateField(db_column='DF_MOBILITE')
+#    y_except_expertise = models.CharField(max_length=3, db_column='Y_EXCEPT_EXPERTISE', blank=True)
+#    y_except_bareme = models.CharField(max_length=3, db_column='Y_EXCEPT_BAREME', blank=True)
+#    n_except_min_mobilite = models.IntegerField(null=True, db_column='N_EXCEPT_MIN_MOBILITE', blank=True)
+#    n_except_total_mobilite = models.IntegerField(null=True, db_column='N_EXCEPT_TOTAL_MOBILITE', blank=True)
+#    m_except_inter_s_b = models.FloatField(null=True, db_column='M_EXCEPT_INTER_S_B', blank=True)
+#    m_except_inter_s_f = models.FloatField(null=True, db_column='M_EXCEPT_INTER_S_F', blank=True)
+#    m_except_inter_n_b = models.FloatField(null=True, db_column='M_EXCEPT_INTER_N_B', blank=True)
+#    m_except_inter_n_f = models.FloatField(null=True, db_column='M_EXCEPT_INTER_N_F', blank=True)
+#    m_except_intra_s_b = models.FloatField(null=True, db_column='M_EXCEPT_INTRA_S_B', blank=True)
+#    m_except_intra_s_f = models.FloatField(null=True, db_column='M_EXCEPT_INTRA_S_F', blank=True)
+#    m_except_intra_n_b = models.FloatField(null=True, db_column='M_EXCEPT_INTRA_N_B', blank=True)
+#    m_except_intra_n_f = models.FloatField(null=True, db_column='M_EXCEPT_INTRA_N_F', blank=True)
+#    i_mobilite_active = models.IntegerField(db_column='I_MOBILITE_ACTIVE')
+    dd_activation_mobilite = models.DateField(db_column='DD_ACTIVATION_MOBILITE')
+    df_activation_mobilite = models.DateField(null=True, db_column='DF_ACTIVATION_MOBILITE', blank=True)
+#    i_accueil_origine = models.IntegerField(db_column='I_ACCUEIL_ORIGINE')
+#    i_institut_auf = models.IntegerField(db_column='I_INSTITUT_AUF')
+#    i_candidature_en_ligne = models.IntegerField(db_column='I_CANDIDATURE_EN_LIGNE')
+#    class Meta:
+#        db_table = u'GM_MOBILITES'
+
+    def __init__(self, id, code_budgetaire, wcs_form_name):
+        self.id = id
+        self.code_budgetaire = code_budgetaire
+        self.wcs_form_name = wcs_form_name
+        
+class MobilitePiece(models.Model):
+    mobilite = models.IntegerField(primary_key=True, db_column='F_MOBILITE') # Field name made lowercase.
+    piece = models.IntegerField(primary_key=True, db_column='F_PIECE') # Field name made lowercase.
+    commentaire = models.CharField(max_length=765, db_column='L_COMMENTAIRE', blank=True) # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_PIECE_MOBILITE'
+        
+### REFERENCES
+class Etablissement(models.Model):
+    id = models.CharField(max_length=10, primary_key=True, db_column='C_ETABLISSEMENT')
+    id_gde = models.IntegerField(null=True, db_column='C_N_ETABLISSEMENT', blank=True)  # unique
+    cgrm = models.CharField(max_length=12, db_column='C_CGRM')  # unique
+    pays = models.CharField(max_length=10, db_column='F_PAYS')  # foreign
+    implantation_sigma = models.ForeignKey('Implantation', db_column='F_IMPLANTATION_SIGMA')
+    implantation_inst = models.CharField(max_length=10, db_column='F_IMPLANTATION_INST')     # foreign
+    zone_etablissement = models.CharField(max_length=4, db_column='Y_ZONE_ETABLISSEMENT')
+    nom = models.CharField(max_length=200, db_column='L_ETABLISSEMENT')
+    statut = models.CharField(max_length=15, db_column='Y_STATUT_ETABLISSEMENT', default="")
+    actif = models.IntegerField(null=True, db_column='I_ETABLISSEMENT_ACTIF', blank=True)   # Boolean
+    institut = models.IntegerField(db_column='I_INSTITUT')  # Boolean
+    dd_activation = models.DateField(null=True, db_column='DD_ACTIVATION_ETABLISSEMENT', blank=True)
+    df_activation = models.DateField(null=True, db_column='DF_ACTIVATION_ETABLISSEMENT', blank=True)
+    bureau_geographique = models.CharField(max_length=12, db_column='F_BUREAU_GEOGRAPHIQUE')  # foreign
+    bureau_gestion = models.CharField(max_length=12, db_column='F_BUREAU_GESTION', blank=True)    # foreign
+    president_universite = models.CharField(max_length=255, db_column='L_PRESIDENT_UNIVERSITE', blank=True)
+    sexe_responsable_etablissement = models.CharField(max_length=1, db_column='I_SEXE_RESPONSABLE_ETABLISSEMENT')
+    nom_responsable_etablissement = models.CharField(max_length=100, db_column='L_NOM_RESPONSABLE_ETABLISSEMENT')
+    prenom_responsable_etablissement = models.CharField(max_length=100, db_column='L_PRENOM_RESPONSABLE_ETABLISSEMENT')
+    adresse = models.CharField(max_length=255, db_column='L_ADRESSE', blank=True)
+    code_postal = models.CharField(max_length=12, db_column='L_CODE_POSTAL', blank=True)
+    cedex = models.CharField(max_length=12, db_column='L_CEDEX', blank=True)
+    ville = models.CharField(max_length=64, db_column='L_VILLE', blank=True)
+    province = models.CharField(max_length=64, db_column='L_PROVINCE', blank=True)
+    tel = models.CharField(max_length=32, db_column='N_TEL', blank=True)
+    fax = models.CharField(max_length=32, db_column='N_FAX', blank=True)
+    
+    class Meta:
+        db_table = u'RE_ETABLISSEMENT'
+
+    def __unicode__(self):
+        return u"%s [%s]" % (self.nom, unicode(self.id_gde))
+
+
+class Implantation(models.Model):
+    id_implantus = models.IntegerField(unique=True, db_column='C_N_IMPLANTATION')
+    id = models.CharField(max_length=10, primary_key=True, db_column='C_IMPLANTATION')
+    bureau = models.ForeignKey('Bureau', db_column='F_BUREAU')
+    nom = models.CharField(max_length=255, db_column='L_IMPLANTATION')
+    nom_court = models.CharField(max_length=20, db_column='L_IMPLANTATION_COURT')
+    actif = models.IntegerField(null=True, db_column='I_IMPLANTATION_ACTIVE', blank=True)   # Boolean
+    dd_activation = models.DateField(null=True, db_column='DD_ACTIVATION_IMPLANTATION', blank=True)
+    df_activation = models.DateField(null=True, db_column='DF_ACTIVATION_IMPLANTATION', blank=True)
+    
+    class Meta:
+        db_table = u'RE_IMPLANTATION'
+
+    def __unicode__(self):
+        return u"%s [%s]" % (self.nom, self.id)
+        
+        
+class Bureau(models.Model):
+    id_num = models.IntegerField(unique=True, db_column='C_N_BUREAU')
+    id = models.CharField(max_length=12, primary_key=True, db_column='C_BUREAU')
+    nom = models.CharField(max_length=100, db_column='L_BUREAU')
+    type = models.CharField(max_length=20, db_column='Y_BUREAU')
+    actif = models.IntegerField(null=True, db_column='I_BUREAU_ACTIF', blank=True)  # Boolean
+    dd_activation = models.DateField(null=True, db_column='DD_ACTIVATION', blank=True)
+    df_activation = models.DateField(null=True, db_column='DF_ACTIVATION', blank=True)
+    pays = models.CharField(max_length=10, db_column='F_PAYS_GEOGRAPHIQUE', blank=True)    # foreign
+    adresse1 = models.CharField(max_length=128, db_column='L_LIGNE_ADRESSE1', blank=True)
+    adresse2 = models.CharField(max_length=128, db_column='L_LIGNE_ADRESSE2', blank=True)
+    adresse3 = models.CharField(max_length=128, db_column='L_LIGNE_ADRESSE3', blank=True)
+    code_postal = models.CharField(max_length=12, db_column='L_CODE_POSTAL', blank=True)
+    ville = models.CharField(max_length=64, db_column='L_VILLE', blank=True)
+    province = models.CharField(max_length=64, db_column='L_PROVINCE', blank=True)
+    tel = models.CharField(max_length=32, db_column='N_TEL', blank=True)
+    tel2 = models.CharField(max_length=32, db_column='N_TEL2', blank=True)
+    fax = models.CharField(max_length=32, db_column='N_FAX', blank=True)
+    
+    class Meta:
+        db_table = u'RE_BUREAU'
+
+    def __unicode__(self):
+        return u"%s - %s" % (self.id, self.nom)
diff --git a/project/sigma_v1/models_autres_inspectdb.py b/project/sigma_v1/models_autres_inspectdb.py
new file mode 100644 (file)
index 0000000..c776074
--- /dev/null
@@ -0,0 +1,339 @@
+# This is an auto-generated Django model module.
+# You'll have to do the following manually to clean this up:
+#     * Rearrange models' order
+#     * Make sure each model has one field with primary_key=True
+# Feel free to rename the models, but don't rename db_table values or field names.
+#
+# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'
+# into your database.
+
+from django.db import models
+
+class GmBlocsChamps(models.Model):
+    c_bloc = models.IntegerField(primary_key=True, db_column='C_BLOC') # Field name made lowercase.
+    l_bloc = models.CharField(max_length=300, db_column='L_BLOC') # Field name made lowercase.
+    i_bloc_actif = models.IntegerField(db_column='I_BLOC_ACTIF') # Field name made lowercase.
+    dd_activation = models.DateField(db_column='DD_ACTIVATION') # Field name made lowercase.
+    df_activation = models.DateField(db_column='DF_ACTIVATION') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_BLOCS_CHAMPS'
+
+class GmCategoriesBlocs(models.Model):
+    c_categorie_bloc = models.IntegerField(primary_key=True, db_column='C_CATEGORIE_BLOC') # Field name made lowercase.
+    c_bloc = models.IntegerField(unique=True, db_column='C_BLOC') # Field name made lowercase.
+    c_categorie_bourse = models.CharField(unique=True, max_length=36, db_column='C_CATEGORIE_BOURSE') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_CATEGORIES_BLOCS'
+
+class GmChamps(models.Model):
+    c_champ = models.IntegerField(primary_key=True, db_column='C_CHAMP') # Field name made lowercase.
+    l_bdd_table = models.CharField(max_length=60, db_column='L_BDD_TABLE') # Field name made lowercase.
+    l_bdd_champ = models.CharField(max_length=120, db_column='L_BDD_CHAMP') # Field name made lowercase.
+    l_champ = models.CharField(max_length=450, db_column='L_CHAMP') # Field name made lowercase.
+    f_bloc = models.IntegerField(db_column='F_BLOC') # Field name made lowercase.
+    m_version = models.FloatField(db_column='M_VERSION') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_CHAMPS'
+
+class GmChampMobilite(models.Model):
+    f_champ = models.IntegerField(primary_key=True, db_column='F_CHAMP') # Field name made lowercase.
+    f_mobilite = models.CharField(max_length=36, primary_key=True, db_column='F_MOBILITE') # Field name made lowercase.
+    l_commentaire = models.TextField(db_column='L_COMMENTAIRE') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_CHAMP_MOBILITE'
+
+class GmCommutateur(models.Model):
+    c_commutateur = models.IntegerField(primary_key=True, db_column='C_COMMUTATEUR') # Field name made lowercase.
+    i_actif = models.IntegerField(db_column='I_ACTIF') # Field name made lowercase.
+    y_etat = models.CharField(max_length=18, db_column='Y_ETAT') # Field name made lowercase.
+    dd_activation = models.DateField(db_column='DD_ACTIVATION') # Field name made lowercase.
+    df_activation = models.DateField(db_column='DF_ACTIVATION') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_COMMUTATEUR'
+
+class GmConvention(models.Model):
+    c_convention_technique = models.IntegerField(primary_key=True, db_column='C_CONVENTION_TECHNIQUE') # Field name made lowercase.
+    f_premier_etablissement = models.CharField(max_length=30, db_column='F_PREMIER_ETABLISSEMENT') # Field name made lowercase.
+    f_second_etablissement = models.CharField(max_length=30, db_column='F_SECOND_ETABLISSEMENT') # Field name made lowercase.
+    c_convention = models.CharField(max_length=36, db_column='C_CONVENTION') # Field name made lowercase.
+    i_convention_active = models.IntegerField(db_column='I_CONVENTION_ACTIVE') # Field name made lowercase.
+    dd_activation_convention = models.DateField(db_column='DD_ACTIVATION_CONVENTION') # Field name made lowercase.
+    df_activation_convention = models.DateField(null=True, db_column='DF_ACTIVATION_CONVENTION', blank=True) # Field name made lowercase.
+    l_commentaire = models.CharField(max_length=765, db_column='L_COMMENTAIRE') # Field name made lowercase.
+    l_collection = models.CharField(max_length=60, db_column='L_COLLECTION') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_CONVENTION'
+
+class GmCourriers(models.Model):
+    c_courrier = models.IntegerField(primary_key=True, db_column='C_COURRIER') # Field name made lowercase.
+    l_libelle = models.CharField(max_length=765, db_column='L_LIBELLE', blank=True) # Field name made lowercase.
+    l_modele = models.CharField(max_length=765, db_column='L_MODELE', blank=True) # Field name made lowercase.
+    f_mobilite = models.IntegerField(db_column='F_MOBILITE') # Field name made lowercase.
+    f_courrier_reference = models.IntegerField(db_column='F_COURRIER_REFERENCE') # Field name made lowercase.
+    d_template = models.DateTimeField(db_column='D_TEMPLATE') # Field name made lowercase.
+    d_modele = models.DateTimeField(db_column='D_MODELE') # Field name made lowercase.
+    l_nom_template = models.CharField(max_length=150, db_column='L_NOM_TEMPLATE') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_COURRIERS'
+
+class GmCourriersReferences(models.Model):
+    c_courrier_reference = models.IntegerField(primary_key=True, db_column='C_COURRIER_REFERENCE') # Field name made lowercase.
+    l_libelle = models.CharField(max_length=765, db_column='L_LIBELLE', blank=True) # Field name made lowercase.
+    l_modele = models.CharField(max_length=765, db_column='L_MODELE', blank=True) # Field name made lowercase.
+    f_type_courrier = models.IntegerField(db_column='F_TYPE_COURRIER') # Field name made lowercase.
+    d_template = models.DateTimeField(db_column='D_TEMPLATE') # Field name made lowercase.
+    d_modele = models.DateTimeField(db_column='D_MODELE') # Field name made lowercase.
+    l_nom_template = models.CharField(max_length=150, db_column='L_NOM_TEMPLATE') # Field name made lowercase.
+    f_categorie_bourse = models.CharField(max_length=36, db_column='F_CATEGORIE_BOURSE') # Field name made lowercase.
+    i_generique = models.IntegerField(db_column='I_GENERIQUE') # Field name made lowercase.
+    i_accessible = models.IntegerField(db_column='I_ACCESSIBLE') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_COURRIERS_REFERENCES'
+
+class GmCritereRecevabilite(models.Model):
+    c_critere_recevabilite = models.IntegerField(primary_key=True, db_column='C_CRITERE_RECEVABILITE') # Field name made lowercase.
+    f_mobilite = models.IntegerField(unique=True, db_column='F_MOBILITE') # Field name made lowercase.
+    f_niveau = models.IntegerField(unique=True, db_column='F_NIVEAU') # Field name made lowercase.
+    n_age_max = models.IntegerField(db_column='N_AGE_MAX') # Field name made lowercase.
+    i_flux_nord = models.IntegerField(db_column='I_FLUX_NORD') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_CRITERE_RECEVABILITE'
+
+
+class GmDossierExpert(models.Model):
+    c_dossier_expert = models.IntegerField(primary_key=True, db_column='C_DOSSIER_EXPERT') # Field name made lowercase.
+    f_dossier = models.IntegerField(db_column='F_DOSSIER') # Field name made lowercase.
+    f_expert = models.IntegerField(db_column='F_EXPERT') # Field name made lowercase.
+    n_numero = models.IntegerField(db_column='N_NUMERO') # Field name made lowercase.
+    n_note_1 = models.FloatField(null=True, db_column='N_NOTE_1', blank=True) # Field name made lowercase.
+    n_note_2 = models.FloatField(null=True, db_column='N_NOTE_2', blank=True) # Field name made lowercase.
+    n_note_3 = models.FloatField(null=True, db_column='N_NOTE_3', blank=True) # Field name made lowercase.
+    n_note_4 = models.FloatField(null=True, db_column='N_NOTE_4', blank=True) # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_DOSSIER_EXPERT'
+
+class GmExpert(models.Model):
+    c_expert = models.IntegerField(primary_key=True, db_column='C_EXPERT') # Field name made lowercase.
+    f_utilisateur_creation = models.IntegerField(null=True, db_column='F_UTILISATEUR_CREATION', blank=True) # Field name made lowercase.
+    f_utilisateur_maj = models.IntegerField(null=True, db_column='F_UTILISATEUR_MAJ', blank=True) # Field name made lowercase.
+    f_bureau_gestion = models.IntegerField(db_column='F_BUREAU_GESTION') # Field name made lowercase.
+    dd_activation = models.DateField(null=True, db_column='DD_ACTIVATION', blank=True) # Field name made lowercase.
+    df_activation = models.DateField(null=True, db_column='DF_ACTIVATION', blank=True) # Field name made lowercase.
+    f_pays_nationalite = models.CharField(max_length=30, db_column='F_PAYS_NATIONALITE') # Field name made lowercase.
+    y_civilite = models.CharField(max_length=6, db_column='Y_CIVILITE', blank=True) # Field name made lowercase.
+    l_nom = models.CharField(max_length=300, db_column='L_NOM', blank=True) # Field name made lowercase.
+    l_prenom = models.CharField(max_length=300, db_column='L_PRENOM', blank=True) # Field name made lowercase.
+    l_nom_index = models.CharField(max_length=300, db_column='L_NOM_INDEX', blank=True) # Field name made lowercase.
+    y_sexe = models.CharField(max_length=3, db_column='Y_SEXE', blank=True) # Field name made lowercase.
+    f_etablissement = models.CharField(max_length=30, db_column='F_ETABLISSEMENT', blank=True) # Field name made lowercase.
+    l_autre_etablissement = models.CharField(max_length=300, db_column='L_AUTRE_ETABLISSEMENT', blank=True) # Field name made lowercase.
+    f_pays_autre_etablissement = models.CharField(max_length=30, db_column='F_PAYS_AUTRE_ETABLISSEMENT') # Field name made lowercase.
+    l_titre_et_fonction = models.CharField(max_length=300, db_column='L_TITRE_ET_FONCTION', blank=True) # Field name made lowercase.
+    l_adresse = models.TextField(db_column='L_ADRESSE', blank=True) # Field name made lowercase.
+    l_code_postal = models.CharField(max_length=30, db_column='L_CODE_POSTAL', blank=True) # Field name made lowercase.
+    l_ville = models.CharField(max_length=150, db_column='L_VILLE', blank=True) # Field name made lowercase.
+    l_region = models.CharField(max_length=150, db_column='L_REGION', blank=True) # Field name made lowercase.
+    f_pays_correspondance = models.CharField(max_length=30, db_column='F_PAYS_CORRESPONDANCE') # Field name made lowercase.
+    n_tel = models.CharField(max_length=75, db_column='N_TEL', blank=True) # Field name made lowercase.
+    n_fax = models.CharField(max_length=75, db_column='N_FAX', blank=True) # Field name made lowercase.
+    n_tel_pro = models.CharField(max_length=75, db_column='N_TEL_PRO', blank=True) # Field name made lowercase.
+    n_fax_pro = models.CharField(max_length=75, db_column='N_FAX_PRO', blank=True) # Field name made lowercase.
+    l_email = models.CharField(max_length=300, db_column='L_EMAIL', blank=True) # Field name made lowercase.
+    l_commentaire = models.TextField(db_column='L_COMMENTAIRE', blank=True) # Field name made lowercase.
+    i_actif = models.IntegerField(null=True, db_column='I_ACTIF', blank=True) # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_EXPERT'
+
+class GmExpertsDisciplines(models.Model):
+    c_expert_disc = models.IntegerField(primary_key=True, db_column='C_EXPERT_DISC') # Field name made lowercase.
+    f_expert = models.IntegerField(null=True, db_column='F_EXPERT', blank=True) # Field name made lowercase.
+    f_discipline = models.CharField(max_length=30, db_column='F_DISCIPLINE') # Field name made lowercase.
+    l_sous_discipline = models.CharField(max_length=300, db_column='L_SOUS_DISCIPLINE', blank=True) # Field name made lowercase.
+    n_ordre_discipline = models.IntegerField(db_column='N_ORDRE_DISCIPLINE') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_EXPERTS_DISCIPLINES'
+
+class GmHabilitations(models.Model):
+    c_habilitation = models.IntegerField(primary_key=True, db_column='C_HABILITATION') # Field name made lowercase.
+    f_matricule = models.IntegerField(db_column='F_MATRICULE') # Field name made lowercase.
+    l_habilitation = models.CharField(max_length=45, db_column='L_HABILITATION') # Field name made lowercase.
+    dd_activ = models.DateField(null=True, db_column='DD_ACTIV', blank=True) # Field name made lowercase.
+    df_activ = models.DateField(null=True, db_column='DF_ACTIV', blank=True) # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_HABILITATIONS'
+
+class GmInterventions(models.Model):
+    c_intervention = models.IntegerField(primary_key=True, db_column='C_INTERVENTION') # Field name made lowercase.
+    l_intervention = models.CharField(max_length=300, db_column='L_INTERVENTION') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_INTERVENTIONS'
+
+class GmMobiliteRegionStatut(models.Model):
+    c_statut_mobilite_bsc = models.IntegerField(primary_key=True, db_column='C_STATUT_MOBILITE_BSC') # Field name made lowercase.
+    f_statut_bureau = models.IntegerField(unique=True, db_column='F_STATUT_BUREAU') # Field name made lowercase.
+    f_statut_mobilite = models.IntegerField(unique=True, db_column='F_STATUT_MOBILITE') # Field name made lowercase.
+    f_statut_bsc = models.IntegerField(db_column='F_STATUT_BSC') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_MOBILITE_REGION_STATUT'
+
+class GmNiveauxEtudes(models.Model):
+    c_niveau = models.IntegerField(primary_key=True, db_column='C_NIVEAU') # Field name made lowercase.
+    l_intitule_niveau = models.CharField(max_length=90, db_column='L_INTITULE_NIVEAU') # Field name made lowercase.
+    l_niveau = models.CharField(max_length=150, db_column='L_NIVEAU') # Field name made lowercase.
+    l_cycle = models.CharField(max_length=150, db_column='L_CYCLE', blank=True) # Field name made lowercase.
+    i_niveau_actif = models.IntegerField(null=True, db_column='I_NIVEAU_ACTIF', blank=True) # Field name made lowercase.
+    dd_activation = models.DateField(db_column='DD_ACTIVATION') # Field name made lowercase.
+    df_activation = models.DateField(null=True, db_column='DF_ACTIVATION', blank=True) # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_NIVEAUX_ETUDES'
+
+class GmPieceAdministrative(models.Model):
+    c_piece = models.IntegerField(primary_key=True, db_column='C_PIECE') # Field name made lowercase.
+    l_piece = models.CharField(max_length=765, db_column='L_PIECE') # Field name made lowercase.
+    i_piece_active = models.IntegerField(db_column='I_PIECE_ACTIVE') # Field name made lowercase.
+    dd_activation_piece = models.DateField(db_column='DD_ACTIVATION_PIECE') # Field name made lowercase.
+    df_activation_piece = models.DateField(null=True, db_column='DF_ACTIVATION_PIECE', blank=True) # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_PIECE_ADMINISTRATIVE'
+
+class GmPublicsVises(models.Model):
+    c_public = models.IntegerField(primary_key=True, db_column='C_PUBLIC') # Field name made lowercase.
+    l_public = models.CharField(max_length=300, db_column='L_PUBLIC') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_PUBLICS_VISES'
+
+class GmStatutDossier(models.Model):
+    y_niveau_traitement = models.CharField(max_length=3, db_column='Y_NIVEAU_TRAITEMENT') # Field name made lowercase.
+    l_niveau_traitement = models.CharField(max_length=300, db_column='L_NIVEAU_TRAITEMENT') # Field name made lowercase.
+    l_droit = models.CharField(max_length=60, db_column='L_DROIT') # Field name made lowercase.
+    c_statut = models.IntegerField(primary_key=True, db_column='C_STATUT') # Field name made lowercase.
+    l_libelle_statut = models.CharField(max_length=300, db_column='L_LIBELLE_STATUT') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_STATUT_DOSSIER'
+
+class GmStatutMobilite(models.Model):
+    c_statut_mobilite = models.IntegerField(primary_key=True, db_column='C_STATUT_MOBILITE') # Field name made lowercase.
+    l_statut_mobilite = models.CharField(max_length=90, db_column='L_STATUT_MOBILITE') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_STATUT_MOBILITE'
+
+class GmStatutPersonne(models.Model):
+    c_statut_personne = models.IntegerField(primary_key=True, db_column='C_STATUT_PERSONNE') # Field name made lowercase.
+    l_statut_personne = models.CharField(max_length=150, db_column='L_STATUT_PERSONNE') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_STATUT_PERSONNE'
+
+class GmTypeCourrier(models.Model):
+    c_type_courrier = models.IntegerField(primary_key=True, db_column='C_TYPE_COURRIER') # Field name made lowercase.
+    l_type_courrier = models.CharField(max_length=150, db_column='L_TYPE_COURRIER') # Field name made lowercase.
+    l_nom_requete = models.CharField(max_length=150, db_column='L_NOM_REQUETE') # Field name made lowercase.
+    class Meta:
+        db_table = u'GM_TYPE_COURRIER'
+
+class ReCategorieBourse(models.Model):
+    c_categorie_bourse = models.CharField(max_length=36, db_column='C_CATEGORIE_BOURSE') # Field name made lowercase.
+    id_categorie_bourse = models.IntegerField(primary_key=True, db_column='ID_CATEGORIE_BOURSE') # Field name made lowercase.
+    l_categorie_bourse = models.CharField(max_length=300, db_column='L_CATEGORIE_BOURSE') # Field name made lowercase.
+    i_bourse_active = models.IntegerField(db_column='I_BOURSE_ACTIVE') # Field name made lowercase.
+    dd_activation_bourse = models.DateField(db_column='DD_ACTIVATION_BOURSE') # Field name made lowercase.
+    df_activation_bourse = models.DateField(null=True, db_column='DF_ACTIVATION_BOURSE', blank=True) # Field name made lowercase.
+    y_expertise = models.CharField(max_length=3, db_column='Y_EXPERTISE') # Field name made lowercase.
+    y_bareme = models.CharField(max_length=3, db_column='Y_BAREME') # Field name made lowercase.
+    n_min_mobilite = models.IntegerField(db_column='N_MIN_MOBILITE') # Field name made lowercase.
+    n_total_mobilite = models.IntegerField(db_column='N_TOTAL_MOBILITE') # Field name made lowercase.
+    i_exception = models.IntegerField(db_column='I_EXCEPTION') # Field name made lowercase.
+    i_statut_etudiant = models.IntegerField(db_column='I_STATUT_ETUDIANT') # Field name made lowercase.
+    i_origine = models.IntegerField(db_column='I_ORIGINE') # Field name made lowercase.
+    i_accueil = models.IntegerField(db_column='I_ACCUEIL') # Field name made lowercase.
+    i_age = models.IntegerField(db_column='I_AGE') # Field name made lowercase.
+    i_validite_exigee = models.IntegerField(db_column='I_VALIDITE_EXIGEE') # Field name made lowercase.
+    i_rnvt = models.IntegerField(db_column='I_RNVT') # Field name made lowercase.
+    n_rnvt = models.IntegerField(db_column='N_RNVT') # Field name made lowercase.
+    i_alternance = models.IntegerField(db_column='I_ALTERNANCE') # Field name made lowercase.
+    n_mois_min = models.IntegerField(db_column='N_MOIS_MIN') # Field name made lowercase.
+    m_inter_s_b = models.FloatField(db_column='M_INTER_S_B') # Field name made lowercase.
+    m_inter_s_f = models.FloatField(db_column='M_INTER_S_F') # Field name made lowercase.
+    m_inter_n_b = models.FloatField(db_column='M_INTER_N_B') # Field name made lowercase.
+    m_inter_n_f = models.FloatField(db_column='M_INTER_N_F') # Field name made lowercase.
+    m_intra_s_b = models.FloatField(db_column='M_INTRA_S_B') # Field name made lowercase.
+    m_intra_s_f = models.FloatField(db_column='M_INTRA_S_F') # Field name made lowercase.
+    m_intra_n_b = models.FloatField(db_column='M_INTRA_N_B') # Field name made lowercase.
+    m_intra_n_f = models.FloatField(db_column='M_INTRA_N_F') # Field name made lowercase.
+    class Meta:
+        db_table = u'RE_CATEGORIE_BOURSE'
+
+class ReDiscipline(models.Model):
+    c_discipline = models.CharField(max_length=30, primary_key=True, db_column='C_DISCIPLINE') # Field name made lowercase.
+    lc_discipline = models.CharField(max_length=765, db_column='LC_DISCIPLINE') # Field name made lowercase.
+    ll_discipline = models.CharField(max_length=765, db_column='LL_DISCIPLINE') # Field name made lowercase.
+    i_discipline_active = models.IntegerField(db_column='I_DISCIPLINE_ACTIVE') # Field name made lowercase.
+    class Meta:
+        db_table = u'RE_DISCIPLINE'
+
+class RePays(models.Model):
+    c_pays = models.CharField(max_length=30, primary_key=True, db_column='C_PAYS') # Field name made lowercase.
+    f_bureau = models.CharField(max_length=36, db_column='F_BUREAU') # Field name made lowercase.
+    l_nom_pays = models.CharField(max_length=300, db_column='L_NOM_PAYS') # Field name made lowercase.
+    y_zone = models.CharField(max_length=12, db_column='Y_ZONE') # Field name made lowercase.
+    l_nationalite = models.CharField(max_length=300, db_column='L_NATIONALITE') # Field name made lowercase.
+    i_pays_actif = models.IntegerField(db_column='I_PAYS_ACTIF') # Field name made lowercase.
+    dd_activation_pays = models.DateField(db_column='DD_ACTIVATION_PAYS') # Field name made lowercase.
+    df_activation_pays = models.DateField(db_column='DF_ACTIVATION_PAYS') # Field name made lowercase.
+    class Meta:
+        db_table = u'RE_PAYS'
+
+class ReProgramme(models.Model):
+    c_programme = models.CharField(max_length=36, primary_key=True, db_column='C_PROGRAMME') # Field name made lowercase.
+    l_programme = models.CharField(max_length=765, db_column='L_PROGRAMME') # Field name made lowercase.
+    i_programme_actif = models.IntegerField(null=True, db_column='I_PROGRAMME_ACTIF', blank=True) # Field name made lowercase.
+    dd_activation_programme = models.DateField(null=True, db_column='DD_ACTIVATION_PROGRAMME', blank=True) # Field name made lowercase.
+    df_activation_programme = models.DateField(null=True, db_column='DF_ACTIVATION_PROGRAMME', blank=True) # Field name made lowercase.
+    class Meta:
+        db_table = u'RE_PROGRAMME'
+
+class ReProjet(models.Model):
+    c_code_projet = models.CharField(max_length=36, primary_key=True, db_column='C_CODE_PROJET') # Field name made lowercase.
+    l_projet_intitule = models.CharField(max_length=765, db_column='L_PROJET_INTITULE') # Field name made lowercase.
+    f_bureau = models.CharField(max_length=36, db_column='F_BUREAU') # Field name made lowercase.
+    f_programme = models.CharField(max_length=36, db_column='F_PROGRAMME') # Field name made lowercase.
+    i_projet_actif = models.IntegerField(db_column='I_PROJET_ACTIF') # Field name made lowercase.
+    dd_projet = models.DateField(db_column='DD_PROJET') # Field name made lowercase.
+    df_projet = models.DateField(db_column='DF_PROJET') # Field name made lowercase.
+    class Meta:
+        db_table = u'RE_PROJET'
+
+class ReProjetPoste(models.Model):
+    c_projet_poste = models.CharField(max_length=36, primary_key=True, db_column='C_PROJET_POSTE') # Field name made lowercase.
+    f_projet_ref = models.CharField(max_length=36, db_column='F_PROJET_REF') # Field name made lowercase.
+    f_bureau = models.CharField(max_length=36, db_column='F_BUREAU') # Field name made lowercase.
+    f_programme = models.CharField(max_length=36, db_column='F_PROGRAMME') # Field name made lowercase.
+    l_projet = models.CharField(max_length=765, db_column='L_PROJET') # Field name made lowercase.
+    f_categorie_bourse = models.IntegerField(db_column='F_CATEGORIE_BOURSE') # Field name made lowercase.
+    i_projet_actif = models.IntegerField(db_column='I_PROJET_ACTIF') # Field name made lowercase.
+    dd_activation_projet = models.DateField(db_column='DD_ACTIVATION_PROJET') # Field name made lowercase.
+    df_activation_projet = models.DateField(null=True, db_column='DF_ACTIVATION_PROJET', blank=True) # Field name made lowercase.
+    class Meta:
+        db_table = u'RE_PROJET_POSTE'
+
+class ReUtilisateurSigma(models.Model):
+    c_matricule = models.IntegerField(primary_key=True, db_column='C_MATRICULE') # Field name made lowercase.
+    c_matricule_sigma = models.CharField(unique=True, max_length=36, db_column='C_MATRICULE_SIGMA') # Field name made lowercase.
+    f_bureau_gestion = models.CharField(max_length=36, db_column='F_BUREAU_GESTION', blank=True) # Field name made lowercase.
+    f_bureau_geographique = models.CharField(max_length=36, db_column='F_BUREAU_GEOGRAPHIQUE', blank=True) # Field name made lowercase.
+    c_implantation_dpp = models.CharField(max_length=30, db_column='C_IMPLANTATION_DPP') # Field name made lowercase.
+    l_email = models.CharField(max_length=300, db_column='L_EMAIL') # Field name made lowercase.
+    c_utilisateur = models.CharField(max_length=180, db_column='C_UTILISATEUR') # Field name made lowercase.
+    i_sexe_utilisateur = models.IntegerField(null=True, db_column='I_SEXE_UTILISATEUR', blank=True) # Field name made lowercase.
+    l_nom_utilisateur = models.CharField(max_length=300, db_column='L_NOM_UTILISATEUR') # Field name made lowercase.
+    l_prenom_utilisateur = models.CharField(max_length=300, db_column='L_PRENOM_UTILISATEUR') # Field name made lowercase.
+    l_fonction_utilisateur = models.CharField(max_length=765, db_column='L_FONCTION_UTILISATEUR') # Field name made lowercase.
+    c_mot_passe = models.CharField(max_length=96, db_column='C_MOT_PASSE') # Field name made lowercase.
+    i_utilisateur_actif = models.IntegerField(null=True, db_column='I_UTILISATEUR_ACTIF', blank=True) # Field name made lowercase.
+    dd_activation_utilisateur = models.DateField(null=True, db_column='DD_ACTIVATION_UTILISATEUR', blank=True) # Field name made lowercase.
+    df_activation_utilisateur = models.DateField(null=True, db_column='DF_ACTIVATION_UTILISATEUR', blank=True) # Field name made lowercase.
+    n_compteur_connexion = models.IntegerField(null=True, db_column='N_COMPTEUR_CONNEXION', blank=True) # Field name made lowercase.
+    i_correpondant_bourse = models.IntegerField(null=True, db_column='I_CORREPONDANT_BOURSE', blank=True) # Field name made lowercase.
+    i_assistant_programme = models.IntegerField(db_column='I_ASSISTANT_PROGRAMME') # Field name made lowercase.
+    class Meta:
+        db_table = u'RE_UTILISATEUR_SIGMA'
+
diff --git a/project/sigma_v1/settings.py b/project/sigma_v1/settings.py
new file mode 100644 (file)
index 0000000..e4c6622
--- /dev/null
@@ -0,0 +1,54 @@
+# -*- encoding: utf-8 -*-
+
+# Pièces nom requis pour apparaître dans interface SIGMA
+PIECES_SUFFIXE = {
+    # id_sigma: 'nom_fichier_sigma'  # Nom affichage SIGMA   
+    #1:'',  # Attestation d’accord du(des) directeur(s) de thèse justifiant l'alternance proposée
+    2:'attestation_o_dir',  # Attestation d'accord - Établissement d'accueil
+    3:'attestation_a_dir',  # Attestation d'accord - Établissement d'origine
+    #4:'attestation_a_sc',  # Attestation d'accord à l'accueil - Responsable
+    #5:'attestation_o_sc',  # Attestation d'accord à l'origine - Responsable
+    #6:'',  # Attestation d'admission - Établissement d'accueil
+    #7:'',  # Demande d'inscription pour l'année de mobilité
+    #8:'',  # Attestation d'inscription pour l'année en cours
+    #9:'',  # Budget prévisionnel détaillé
+    #10:'',  # Calendrier de l'alternance (dates conforme au règlement)
+    #11:'',  # Convention de stage
+    #12:'',  # Copie du dernier diplôme obtenu
+    #13:'',  # Copie du dernier relevé de notes
+    #14:'',  # Copie du diplôme de doctorat et du rapport de soutenance de thèse
+    #15:'',  # Copie du dossier de scolarité universitaire
+    16:'curriculum_vitae',  # Curriculum vitae actualisé
+    17:'descriptif_these',  # Description détaillée
+    #18:'',  # Fiche de renseignements personnels dûment complétée et signée
+    # formulaire (PDF) : pas de suffixe, seulement extension .pdf
+    19:'',  # Formulaire de demande dûment complété et signé
+    #20:'',  # Lettre de motivation
+    #21:'',  # Liste des activités pédagogiques
+    #22:'',  # Liste des membres du comité scientifique de la manifestation
+    #23:'',  # Liste détaillée des publications
+    #24:'',  # Liste détaillée des publications, communications et recherches non publiées
+    #25:'',  # Photocopie du passeport (no et date d’expiration)
+    #26:'',  # Profil de l'établissement d'accueil
+    27:'protocole_recherche',  # Protocole de recherche
+    #28:'',  # Calendrier
+    #29:'',  # Justificatif d'inscription deux années consécutives dans un même établissement durant les trois dernières années
+    #30:'',  # Composition du comité scientifique de la manifestation
+    #31:'',  # Opportunité pour les Universités partenaires au Sud et au Nord et retombées
+    #32:'',  # Classement de l'établissement des dossiers par ordre de priorité
+    #33:'',  # Attestation d'accord supplémentaire - Établissement d'origine
+    #34:'',  # Dossier reçu avant le
+    #45:'',  # Etablissement d'origine membre AUF
+    #46:'',  # Etablissement d'accueil membre AUF
+    #47:'',  # Année académique de 1ère inscription en thèse
+    #49:'',  # Année d'interruption après l'obtention du doctorat
+    #50:'',  # Programme de la formation demandée durant la période de mobilité
+    #51:'',  # Copie convention liant les établissements d’accueil et d’origine pour la formation visée
+    #52:'',  # Justificatif du statut de retraité(e)
+    #53:'',  # Attestation d'accord de l'enseignant pour dispenser le cours (séminaire, travaux dirigés/pratiques)
+    #54:'',  # Description détaillée des enseignements / cours à dispenser
+    #55:'',  # Date de naissance
+    #56:'',  # Etablissements Origine et Accueil situés dans 2 pays différents (Nord/Sud - Sud/Nord - Sud/Sud)
+    #57:'',  # Date de première inscription en thèse
+    58:'etat_travaux',  # Etat des travaux réalisés
+    }
diff --git a/project/urls.py b/project/urls.py
new file mode 100644 (file)
index 0000000..551cbd9
--- /dev/null
@@ -0,0 +1,9 @@
+# -*- encoding: utf-8 -*-
+
+from django.conf.urls.defaults import *
+
+from wcs.settings import WCS_URL_NAMESPACE
+
+urlpatterns = patterns('',
+    (r'^' + WCS_URL_NAMESPACE + r'/', include('wcs.urls')),
+)
diff --git a/project/wcs/__init__.py b/project/wcs/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/project/wcs/delete.py b/project/wcs/delete.py
new file mode 100644 (file)
index 0000000..ebb274d
--- /dev/null
@@ -0,0 +1,71 @@
+# -=- encoding: utf-8 -=-
+
+from sigma_v1.models import Personne as P
+from sigma_v1.models import Dossier as D
+from sigma_v1.models import DossierOrigine as DO
+from sigma_v1.models import DossierAccueil as DA
+from sigma_v1.models import DossierMobilite as DM
+from sigma_v1.models import DossierPieces as DP
+
+from conf import DATABASE_HOST
+from wcs.conf import MOBILITE, CODE_BUDGETAIRE, FORMNAME
+
+if __name__ == "__main__":
+    """
+    bin/django shell # python manage.py shell
+    >>> cd wcs
+    >>> run delete.py
+    """
+    
+    # Dossiers de l'appel
+    ds = D.objects.filter(mobilite=MOBILITE)
+    ds_ids = [d.id for d in ds]
+    nb_ds = len(ds_ids)
+            
+    # rapport : header
+    print '--------------------------------------------------'
+    print 'SUPPRESSION CANDIDATURES SIGMA'
+    print '**** SERVEUR : %s ****' % DATABASE_HOST
+    print 'Formulaire : %s' % FORMNAME
+    print 'Mobilité : %d' % MOBILITE
+    print '--------------------------------------------------'
+    print 'Nombre à traiter = %d' % (nb_ds)
+    print '--------------------------------------------------'
+    
+    # Suppression des pièces jointes
+    dps = DP.objects.filter(dossier__in=ds_ids)
+    nb_dps = len(dps)
+    dps.delete()
+    print 'Pièces jointes supprimées   = %d' % (nb_dps)
+    
+    # Suppression des dossiers mobilité
+    dms = DM.objects.filter(dossier__in=ds_ids)
+    nb_dms = len(dms)
+    dms.delete()
+    print 'Dossiers mobilité supprimés = %d' % (nb_dms)
+    
+    # Suppression des dossiers accueil
+    das = DA.objects.filter(dossier__in=ds_ids)
+    nb_das = len(das)
+    das.delete()
+    print 'Dossiers accueil supprimés = %d' % (nb_das)
+    
+    # Suppression des dossiers origine
+    dos = DO.objects.filter(dossier__in=ds_ids)
+    nb_dos = len(dos)
+    dos.delete()
+    print 'Dossiers origine supprimés = %d' % (nb_dos)
+    
+    # Suppression des personnes
+    ps_ids = [d.personne_id for d in ds]
+    ps = P.objects.filter(id__in=ps_ids)
+    nb_ps = len(ps)
+    ps.delete()
+    print 'Personnes supprimées       = %d' % (nb_ps)
+    
+    # Suppression des dossiers
+    ds.delete()
+    print 'Dossiers supprimés         = %d' % (nb_ds)
+    
+    # rapport : footer
+    print '--------------------------------------------------'
diff --git a/project/wcs/docs.py b/project/wcs/docs.py
new file mode 100644 (file)
index 0000000..9f182d4
--- /dev/null
@@ -0,0 +1,62 @@
+# -=- encoding: utf-8 -=-
+
+import os
+from simplejson import loads
+import urllib2
+import zipfile
+
+from wcs.settings import FORMNAME
+
+from lib import getCandidaturesJson, createPdf, createZip
+from models import JsonWcs2JsonSigma
+
+# MAIN
+if __name__ == "__main__":
+    """
+    bin/django shell # python manage.py shell
+    >>> cd wcs
+    >>> run views.py
+    """
+    
+    # json WCS
+    candidatures = getCandidaturesJson()
+    nombre = len(candidatures)
+    spams = 0
+    traites = 0
+    traitesPdf = 0
+    traitesZip = 0
+    
+    # rapport : header
+    print 'CRÉATION DES DOCUMENTS : PDF ET ZIP'
+    print 'Formulaire : %s' % FORMNAME
+    print '--------------------------------------------------'
+    print 'Nombre à traiter = %d' % (nombre)
+    print '--------------------------------------------------'
+        
+    # IMPORT
+    jj = JsonWcs2JsonSigma()
+    for candidature in candidatures :
+        if candidature['wcs_workflow_status'] == 'SPAM':
+            spams = spams + 1
+        else :
+            candidature = jj.mapper(candidature)
+            traites = traites + 1
+            print '%d - %s' % (traites, candidature['wcs_num_dossier'])
+            # pdf
+            createPdf(candidature)
+            traitesPdf = traitesPdf + 1
+            print '* PDF : %s' % (candidature['wcs_num_dossier'])
+            # zip
+            createZip(candidature)
+            traitesZip = traitesZip + 1
+            print '* ZIP : %s' % (candidature['wcs_num_dossier'])
+            print ''
+    
+    # rapport : footer
+    print '--------------------------------------------------'
+    print 'Total spams   = %d sur %d' % (spams, nombre)
+    print 'Total traités = %d sur %d' % (traites, nombre)
+    print 'Total         = %d sur %d' % (spams + traites, nombre)
+    print 'Total PDF     = %d sur %d' % (traitesPdf, traites)
+    print 'Total ZIP     = %d sur %d' % (traitesZip, traites)
+    print '--------------------------------------------------'
diff --git a/project/wcs/export.py b/project/wcs/export.py
new file mode 100644 (file)
index 0000000..8b6e07b
--- /dev/null
@@ -0,0 +1,17 @@
+# -=- encoding: utf-8 -=-
+
+from sigma_v1.models import Mobilite
+
+from wcs.conf import MOBILITE, CODE_BUDGETAIRE, FORMNAME
+from views import exportSigmaFiles
+
+if __name__ == "__main__":
+    """
+    bin/django shell # python manage.py shell
+    >>> cd wcs
+    >>> run export.py
+    """
+    
+    # Appel en cours
+    appel = Mobilite(id=MOBILITE, code_budgetaire=CODE_BUDGETAIRE, wcs_form_name=FORMNAME)
+    exportSigmaFiles(appel)
diff --git a/project/wcs/import.py b/project/wcs/import.py
new file mode 100644 (file)
index 0000000..61ce6f2
--- /dev/null
@@ -0,0 +1,17 @@
+# -=- encoding: utf-8 -=-
+
+from sigma_v1.models import Mobilite
+
+from wcs.conf import MOBILITE, CODE_BUDGETAIRE, FORMNAME
+from views import importDossiersCandidaturesWcs
+
+if __name__ == "__main__":
+    """
+    bin/django shell # python manage.py shell
+    >>> cd wcs
+    >>> run import.py
+    """
+    
+    # Appel en cours
+    appel = Mobilite(id=MOBILITE, code_budgetaire=CODE_BUDGETAIRE, wcs_form_name=FORMNAME)
+    importDossiersCandidaturesWcs(appel)
diff --git a/project/wcs/jsontopdf.py b/project/wcs/jsontopdf.py
new file mode 100755 (executable)
index 0000000..655c31f
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/python
+# -=- encoding: utf-8 -=-
+
+import os, sys, simplejson, StringIO
+from ho import pisa
+
+from django.template import Context, Template
+from wcs.settings import FORMNAME
+
+class JsonToPdf:
+    "Convertit un fichier json de candidature en pdf."
+    inputBuffer = ""
+    templateFile = ""
+
+    def __init__ (self, 
+                  templateFileName = "templates/candidature.html"):
+        self.templateFile = templateFileName
+
+    def readFile (self, fileName):
+        """Lire le fichier fileName et retourne son contenu.
+        Vide si le fichier n'existe pas
+        """
+        buffer = ""
+
+        try:
+            inFile = open (fileName, 'r')
+        except:
+            print "Fichier", fileName, "illisible"
+        else:
+            buffer = inFile.read ()
+
+        return buffer
+
+    def convert (self):
+        "Effectue la conversion"
+
+        if len (self.inputBuffer) > 0:
+            object = self.inputBuffer
+            if object is not None:
+                # Render
+                template = Template (self.readFile (self.templateFile))
+                object['formname'] = unicode(FORMNAME)
+                context = Context (object)
+                # As HTML
+                html = template.render (context)
+                # Write PDF
+                pdf = StringIO.StringIO ()
+                pisa.CreatePDF(html, pdf)
+                return pdf
+
+    def convertFromFile (self, inputFile):
+        self.inputBuffer = inputFile
+        return self.convert ()
diff --git a/project/wcs/lib.py b/project/wcs/lib.py
new file mode 100644 (file)
index 0000000..101ff89
--- /dev/null
@@ -0,0 +1,83 @@
+# -=- encoding: utf-8 -=-
+
+import os.path
+from simplejson import loads
+import zipfile
+
+from jsontopdf import JsonToPdf
+from wcs.settings import DATA_DIR
+
+def getCandidaturesJson(source='local'):
+    candidatures = []
+    if source == 'local':
+        # Fichiers WCS importés en local : .json et pièces jointes
+        if os.path.exists(DATA_DIR):               
+            listjson = [f for f in os.listdir(DATA_DIR) if f.endswith('.json')]
+            for candidature in listjson :
+                fichier = "%s%s" % (DATA_DIR, candidature)
+                
+                f = open(fichier, 'r')
+                data = f.read()
+                f.close()
+                
+                data = loads(data, encoding='utf-8')
+                data['wcs_json_filename'] = unicode(candidature)
+                candidatures.append(data)
+    else:
+        pass
+    return candidatures
+
+def createPdf(candidature):
+    """Créer un fichier PDF représentant la candidature WCS avec toutes ses données reçues.
+    PDF basé sur la structure de données de WCS (pas SIGMA) : 
+    ingérable à court terme sauf si créé côté serveur.
+    
+    À terme, le PDF doit être généré à partir de la page HTML du formulaire WCS
+    tel qu'il peut être vu par le candidat.
+    -> Construction des PDF serveur side.
+    """
+    baseName = candidature['wcs_json_filename'].rstrip('.json')
+    pdfFilePath = "%s%s.pdf" % (DATA_DIR, baseName)
+    
+    converter = JsonToPdf()
+    pdf = converter.convertFromFile(candidature)
+    f = open(pdfFilePath, 'w')
+    f.write(pdf.getvalue())
+    f.close()
+        
+def createZip(candidature):
+    """Constitue une dossier ZIP comprenant :
+    * le formulaire en PDF
+    * les pièces jointes soumises par candidat
+    """
+    baseName = candidature['wcs_json_filename'].rstrip('.json')
+    zipFilePath = "%s%s.zip" % (DATA_DIR, baseName)
+    archive = zipfile.ZipFile(zipFilePath, "w", zipfile.ZIP_STORED)
+    
+    if os.path.exists(DATA_DIR):               
+        files = [f for f in os.listdir(DATA_DIR) \
+            if f.startswith(baseName) and not f.endswith('.json') and not f.endswith('.zip')]
+        for f in files:
+            filePath = DATA_DIR + f
+            archive.write(filePath, f)
+    archive.close()
+
+def premiereMaj(s):
+    """Met le premier caractère en majuscule et reste minuscule"""
+    premiere = s[0]
+    reste = s[1:]
+    
+    return premiere.upper() + reste.lower()
+
+def majSansAccent(s):
+    result = ''
+    avant = u'ÇÁÀÂÉÈÊËÍÌÎÏÓÒÔÖÚÙÛÜÝỲŶŸ'
+    apres = u'CAAAEEEEIIIIOOOOUUUUYYYY'
+
+    for char in s:
+        i = avant.find(char)
+        if i >= 0:
+            char = apres[i]
+        result += char
+
+    return result
diff --git a/project/wcs/models.py b/project/wcs/models.py
new file mode 100644 (file)
index 0000000..b8a1974
--- /dev/null
@@ -0,0 +1,794 @@
+# -=- encoding: utf-8 -=-
+
+from datetime import datetime, date as datetime_date
+import os
+import re
+#import unicodedata
+
+from sigma_v1.models import Personne, Dossier, DossierOrigine, DossierAccueil, DossierMobilite, DossierPieces
+from sigma_v1.models import Etablissement, Implantation, Bureau
+
+from wcs.settings import USER, STATUT_DOSSIER, MOBILITE, BUREAU, MAPPING, DATA_DIR
+from wcs.settings import SIGMA_DOSSIER_M_ALT_MOIS_ORIGINE, SIGMA_DOSSIER_M_MOBILITE_ACCUEIL
+from wcs.settings import PATTERN_STATUT_PERSONNE, PATTERN_ETABL
+from wcs.settings import PIECES_SUFFIXE, PIECES_WCS, PIECES_SIGMA
+from lib import majSansAccent, premiereMaj
+
+#class Formulaire(object):
+#    Formulaire WCS (associé à un appel SIGMA)
+
+#class Candidature(object):
+#    Candidature WCS (associé à un formulaire WCS)
+#    init = via une candidature au format json
+#
+#    def createPdf(request, candidature):
+#        crée le PDF à partir d'un template local (futur : à partir du form wcs)
+#    
+#    def createZip(request, candidature):
+#        crée le dossier zip contenant PDF et toutes les pièces jointes
+
+class JsonSigma2ObjectSigma(object):
+    # TEST : en cas d'échec, erreur levée en console
+    creation_django_ok = True
+    creation_db_ok = True
+
+    def createPersonne(self, data, save=True):
+        """
+        Créer Personne SIGMA à partir du json reçu, nettoyé, mappé et converti.
+        """
+        p = Personne()
+        
+        #p.id
+        p.utilisateur_creation = USER
+        #p.utilisateur_maj = 
+        #p.date_creation = 
+        #p.date_maj = 
+
+        p.civilite = data['sigma_personne_civilite']
+        p.nom = majSansAccent(data['sigma_personne_nom'].upper())[0:100]
+        p.nom_index = p.nom
+        p.nom_jeune_fille = majSansAccent(data['sigma_personne_nom_jeune_fille'].upper())[0:100]
+        p.nom_jeune_fille_index = p.nom_jeune_fille
+        p.prenom  = majSansAccent(data['sigma_personne_prenom'].title())[0:100]
+        p.prenom_index = p.prenom.upper()
+        p.pays_nationalite = data['sigma_personne_pays_nationalite']
+        #p.sexe = 
+
+        p.pays_naissance = data['sigma_personne_pays_naissance']
+        p.date_naissance = data['sigma_personne_date_naissance']
+        p.ville_naissance = majSansAccent(data['sigma_personne_ville_naissance'].title())[0:50]
+
+        p.adresse = data['sigma_personne_adresse']
+        p.ville = majSansAccent(data['sigma_personne_ville'].title())[0:50]
+        p.region = data['sigma_personne_region'][0:50]
+        p.code_postal = data['sigma_personne_code_postal'][0:10]
+        p.pays_residence = data['sigma_personne_pays_residence']
+        p.tel = data['sigma_personne_tel'][0:25]
+        #p.fax
+        p.tel_pro = data['sigma_personne_tel_pro'][0:25]
+        #p.fax_pro
+        p.email = data['sigma_personne_email'][0:100]
+
+        if save:
+            p.save()
+        
+        return p
+       
+    def createDossier(self, data, p, save=True):
+        """
+        Créer Dossier SIGMA à partir du json reçu, nettoyé, mappé et converti.
+        """
+        d = Dossier()
+
+        #d.id
+        d.statut = STATUT_DOSSIER
+        d.mobilite = MOBILITE
+        d.personne = p
+        
+        d.statut_personne = data['sigma_dossier_statut_personne']
+        d.fonction = data['sigma_dossier_fonction'][0:100]
+        
+        if BUREAU is not None :
+            d.bureau_rattachement = BUREAU
+        else:
+            # bureau rattachement via établissement d'origine
+            try:
+                id_etablissement = data['sigma_dossier_o_etablissement'].lstrip('0')
+                etablissement = Etablissement.objects.get(id_gde=id_etablissement)
+                d.bureau_rattachement = etablissement.implantation_sigma.bureau.id_num
+            except Etablissement.DoesNotExist:
+                pass
+
+        d.intitule_d_diplome = data['sigma_dossier_intitule_d_diplome'][0:100]
+        d.date_d_diplome = data['sigma_dossier_date_d_diplome']
+        d.nom_etb = data['sigma_dossier_nom_etabl'][0:100]
+        d.pays_etb = data['sigma_dossier_pays_etabl']
+        d.niveau = data['sigma_dossier_niveau']
+        
+        #data['sigma_dossier_particip_prog_auf']
+        d.programme = data['sigma_dossier_programme'][0:255]
+        d.annee_programme = data['sigma_dossier_annee_programme'] or 0
+        #data['sigma_dossier_boursier_auf']
+        d.categorie_bourse = data['sigma_dossier_categorie_bourse'][0:12]
+        d.annee_bourse = data['sigma_dossier_annee_bourse'] or 0
+        
+        #d.etat
+        #d.dd_activation
+        #d.df_activation
+        d.utilisateur_creation = USER
+        d.utilisateur_maj = USER
+        #d.date_maj
+        
+        #d.classement_1
+        #d.classement_2
+        #d.classement_3
+        #d.opp_regionale
+        #d.coche_selection
+        #d.reponse_notification
+        #d.commentaire_notification
+        #d.moyenne_academique
+        nl = u'\n\n'
+        d.autres_criteres = nl.join(data['sigma_dossier_autres_criteres'])[0:255]
+        #d.erreurs_recevabilite
+        #d.repechage
+        #d.rendu_irrecevable
+
+        if save:
+            d.save()
+        
+        return d
+
+    def createDossierOrigine(self, data, d, save=True):
+        """
+        Créer DossierOrigine SIGMA à partir du json reçu, nettoyé, mappé et converti.
+        """
+        do = DossierOrigine()
+        
+        #do.id
+        do.dossier = d
+        
+        # établissement
+        id_etablissement = data['sigma_dossier_o_etablissement'].lstrip('0')
+        try:
+            etablissement = Etablissement.objects.get(id_gde=id_etablissement)
+
+            # id
+            do.etablissement = etablissement.id     # PAS etablissement.id_gde
+
+            # coordonnées
+            do.pays = etablissement.pays
+            do.adresse = etablissement.adresse
+            if etablissement.code_postal is not None :
+                do.code_postal = etablissement.code_postal[0:10]
+            if etablissement.ville is not None :
+                do.ville = etablissement.ville[0:50]
+            if etablissement.province is not None :
+                do.region = etablissement.province[0:50]
+            #do.url_site
+        except Etablissement.DoesNotExist:
+            pass
+        do.sc_faculte = data['sigma_dossier_o_sc_faculte'][0:150]
+        
+        #do.erreur_recevabilite_etbt_origine = 
+        #do.autre_etb = 
+
+        # accord scientifique
+        do.sc_civilite = data['sigma_dossier_o_sc_civilite']
+        do.sc_nom = majSansAccent(data['sigma_dossier_o_sc_nom'].upper())[0:100]
+        do.sc_prenom = majSansAccent(data['sigma_dossier_o_sc_prenom'].title())[0:100]
+        do.sc_fonction = data['sigma_dossier_o_sc_fonction'][0:100]
+        #do.sc_adresse = 
+        #do.sc_code_postal = 
+        do.sc_ville = majSansAccent(data['sigma_dossier_o_sc_ville'].title())[0:50]
+        do.sc_tel_pro = data['sigma_dossier_o_sc_tel_pro'][0:25]
+        do.sc_email = data['sigma_dossier_o_sc_email'][0:100]
+        #do.sc_fax_pro = 
+
+        # accord institutionnel
+        #do.inst_civilite = 
+        #do.inst_nom = 
+        #do.inst_prenom = 
+        #do.inst_fonction = 
+        
+        if save:
+            do.save()
+        
+        return do
+
+    def createDossierAccueil(self, data, d, save=True):
+        """
+        Créer DossierAccueil SIGMA à partir du json reçu, nettoyé, mappé et converti.
+        """
+        da = DossierAccueil()
+        
+        #da.id = 
+        da.dossier = d
+        
+        # établissement
+        id_etablissement = data['sigma_dossier_a_etablissement'].lstrip('0')
+        try:
+            etablissement = Etablissement.objects.get(id_gde=id_etablissement)
+
+            # id
+            da.etablissement = etablissement.id     # PAS etablissement.id_gde
+
+            # coordonnées
+            da.pays = etablissement.pays
+            da.adresse = etablissement.adresse
+            if etablissement.code_postal is not None :
+                da.code_postal = etablissement.code_postal[0:10]
+            if etablissement.ville is not None :
+                da.ville = etablissement.ville[0:50]
+            if etablissement.province is not None :
+                da.region = etablissement.province[0:50]
+            #da.url_site
+        except Etablissement.DoesNotExist:
+            pass
+        da.sc_faculte = data['sigma_dossier_a_sc_faculte'][0:150]
+            
+        #da.erreur_recevabilite_etbt_accueil = 
+        #da.autre_etb = 
+
+        # accord scientifique
+        da.sc_civilite = data['sigma_dossier_a_sc_civilite']
+        da.sc_nom = majSansAccent(data['sigma_dossier_a_sc_nom'].upper())[0:100]
+        da.sc_prenom = majSansAccent(data['sigma_dossier_a_sc_prenom'].title())[0:100]
+        da.sc_fonction = data['sigma_dossier_a_sc_fonction'][0:100]
+        #da.sc_adresse = 
+        #da.sc_code_postal = 
+        da.sc_ville = majSansAccent(data['sigma_dossier_a_sc_ville'].title())[0:50]
+        da.sc_tel_pro = data['sigma_dossier_a_sc_tel_pro'][0:25]
+        da.sc_email = data['sigma_dossier_a_sc_email'][0:100]
+        #da.sc_fax_pro = 
+
+        # accord institutionnel
+        #da.inst_civilite = 
+        #da.inst_nom = 
+        #da.inst_prenom = 
+        #da.inst_fonction = 
+        #da.inst_email = 
+        #da.inst_tel_pro = 
+        #da.inst_fax_pro = 
+        
+        if save:
+            da.save()
+        
+        return da
+
+    def createDossierMobilite(self, data, d, save=True):
+        """
+        Créer DossierMobilite SIGMA à partir du json reçu, nettoyé, mappé et converti.
+        """
+        dm = DossierMobilite()
+        
+        #dm.id = 
+        dm.dossier = d
+        
+        dm.intitule_diplome = data['sigma_dossier_m_intitule_diplome'][0:100]
+        if not dm.intitule_diplome and data['sigma_dossier_m_intitule_diplome_autre'] :
+            dm.intitule_diplome = data['sigma_dossier_m_intitule_diplome_autre'][0:100]
+        
+        dm.niveau_encours = data['sigma_dossier_m_niveau_encours']
+        
+        # calculs : ce qu'on peut dériver des data
+        mobilite_accueil = None
+        dd_mobilite = None
+        df_mobilite = None
+        
+        o_dd = data['sigmawcs_dossier_o_dd']
+        o_df = data['sigmawcs_dossier_o_df']
+        a_dd = data['sigmawcs_dossier_a_dd']
+        a_df = data['sigmawcs_dossier_a_df']
+        
+        # origine et accueil
+        if o_dd is not None and a_dd is not None:
+            if (o_dd < a_dd):
+                # commence mobilité à origine
+                mobilite_accueil = False
+                dd_mobilite = o_dd
+                df_mobilite = a_df
+            else :
+                # commence mobilité à accueil
+                mobilite_accueil = True
+                dd_mobilite = a_dd
+                df_mobilite = o_df
+        # origine seul
+        elif o_dd is not None and a_dd is None:
+            if (o_dd < a_dd):
+                # commence mobilité à origine
+                mobilite_accueil = False
+                dd_mobilite = o_dd
+                df_mobilite = o_df
+        # accueil seul
+        elif o_dd is None and a_dd is not None:
+                # commence mobilité à accueil
+                mobilite_accueil = True
+                dd_mobilite = a_dd
+                df_mobilite = a_df
+        
+        if data.has_key('sigma_dossier_m_dd_mobilite') and data.has_key('sigma_dossier_m_df_mobilite'):
+            dd_mobilite = data['sigma_dossier_m_dd_mobilite']
+            df_mobilite = data['sigma_dossier_m_df_mobilite']
+        # 1. data WCS
+        if data.has_key('sigma_dossier_m_mobilite_accueil'):
+            mobilite_accueil = data['sigma_dossier_m_mobilite_accueil']
+        # 2. conf de l'appel
+        elif SIGMA_DOSSIER_M_MOBILITE_ACCUEIL is not None:
+            mobilite_accueil = SIGMA_DOSSIER_M_MOBILITE_ACCUEIL
+        
+        dm.dd_mobilite = dd_mobilite
+        dm.df_mobilite = df_mobilite
+        #dm.total_mobilite = total_mobilite     # intervalle dd_mobilite, df_mobilite  # computé par SIGMA?
+        
+        # 1. default
+        alt_mois_origine = 0
+        # 2. data WCS
+        if data.has_key('sigma_dossier_m_alt_mois_origine'):
+            alt_mois_origine = data['sigma_dossier_m_alt_mois_origine']
+        # 3. conf de l'appel
+        elif SIGMA_DOSSIER_M_ALT_MOIS_ORIGINE is not None:
+            alt_mois_origine = SIGMA_DOSSIER_M_ALT_MOIS_ORIGINE
+        dm.alt_mois_origine = alt_mois_origine
+        dm.alt_mois_accueil = data['sigma_dossier_m_alt_mois_accueil']
+        dm.mobilite_accueil = mobilite_accueil
+        
+        dm.date_inscription_these = data['sigma_dossier_m_date_inscription_these']
+        dm.date_soutenance_these = data['sigma_dossier_m_date_soutenance_these']
+        dm.pays_soutenance = data['sigma_dossier_m_pays_soutenance']
+        dm.type_these = data['sigma_dossier_m_type_these']
+        # workaround : champ dm.date_soutenance_these pas affiché in SIGMA :
+        dm.type_these_autre = u"Date soutenance prévue : %s" % (dm.date_soutenance_these)
+        dm.type_these_autre = dm.type_these_autre[0:255]
+        
+        dm.discipline = data['sigma_dossier_m_discipline'] or u""
+        #dm.sous_discipline = 
+
+        dm.intitule_projet = premiereMaj(data['sigma_dossier_m_intitule_projet'])
+        # ajout point à la fin...
+        if dm.intitule_projet != u"" and dm.intitule_projet[-1] != u"." :
+            dm.intitule_projet = dm.intitule_projet + u"."
+
+        dm.mot_clef1 = majSansAccent(data['sigma_dossier_m_mot_clef1'].upper())[0:50]
+        dm.mot_clef2 = majSansAccent(data['sigma_dossier_m_mot_clef2'].upper())[0:50]
+        dm.mot_clef3 = majSansAccent(data['sigma_dossier_m_mot_clef3'].upper())[0:50]
+            
+        #dm.type_intervention = 
+        #dm.public_vise = 
+        #dm.autres_publics = 
+        
+        #dm.mobilite_accueil =  # debut mobilité à l'accueil?
+        #dm.intitule_diplome_demande = 
+        #dm.niveau_demande = 
+        #dm.obtention_prevu = 
+        
+        dm.dir_ori_civilite = data['sigma_dossier_m_dir_ori_civilite']
+        dm.dir_ori_nom = majSansAccent(data['sigma_dossier_m_dir_ori_nom'].upper())[0:100]
+        dm.dir_ori_prenom = majSansAccent(data['sigma_dossier_m_dir_ori_prenom'].title())[0:100]
+        # email absent in SIGMA... sale workaround
+        dm.dir_ori_prenom = u"%s [%s]" % (dm.dir_ori_prenom, data['sigma_dossier_m_dir_ori_email'])
+        dm.dir_ori_prenom = dm.dir_ori_prenom[0:100]
+        
+        dm.dir_acc_civilite = data['sigma_dossier_m_dir_acc_civilite']
+        dm.dir_acc_nom = majSansAccent(data['sigma_dossier_m_dir_acc_nom'].upper())[0:100]
+        dm.dir_acc_prenom = majSansAccent(data['sigma_dossier_m_dir_acc_prenom'].title())[0:100]
+        # email absent in SIGMA... sale workaround
+        dm.dir_acc_prenom = u"%s [%s]" % (dm.dir_acc_prenom, data['sigma_dossier_m_dir_acc_email'])
+        dm.dir_acc_prenom = dm.dir_acc_prenom[0:100]
+        
+        if save:
+            dm.save()
+        
+        return dm
+
+    def createDossierPieces(self, data, d, save=True):
+        """
+        Créer DossierPieces SIGMA à partir du json reçu, nettoyé, mappé et converti.
+        """
+        pieces_created = []
+        
+        #data['sigma_dossier_p_*']
+        mapping_reverse = {}
+        for k,v in MAPPING.items():
+            mapping_reverse[v] = k
+        
+        for piece in PIECES_WCS:
+            # pièce présente? accès fichier réel
+            presente = False
+            if piece == 19:
+                # formulaire
+                presente = True
+            else :
+                # autres pièces
+                wcs_suffixe = PIECES_SUFFIXE[piece]
+                sigma_key = mapping_reverse[wcs_suffixe]
+                filename = data[sigma_key]
+                if os.path.exists(filename):
+                    presente = True
+            
+            dp = DossierPieces()
+            #dp.id = 
+            dp.dossier = d
+            dp.presente = presente
+            dp.piece = piece
+            #dp.conforme = 
+            #dp.commentaire = u"Pièce chargée par candidat via formulaire électronique."
+            if save:
+                dp.save()
+            pieces_created.append(dp)
+            
+        # Autres pièces in SIGMA
+        
+        for piece in PIECES_SIGMA:
+            dp = DossierPieces()
+            dp.dossier = d
+            dp.piece = piece
+            if save:
+                dp.save()
+            pieces_created.append(dp)
+        
+        return pieces_created
+
+    ### AUTRE
+        #data['wcs_num_dossier']
+        
+        #data['sigma_confirmation_nom']
+        #data['sigma_confirmation_prenom']
+        #data['sigma_confirmation']
+
+class JsonWcs2JsonSigma(object):
+    """
+    Converti les données de formulaires WCS, notamment les <option> des <select>
+    en données exploitables par SIGMA.
+    Ex.: 
+    input : Algérie - Centre de recherche en anthropologie sociale et culturelle (Sud/BEOM/547)
+    output : 547
+    """
+    
+    ### types SIGMA
+    # champs WCS = liste
+    civilite = []
+    statut = []
+    niveau = []
+    diplome = []
+    mois = []
+    these = []
+    bourse = []
+    pays = []
+    etablissement = []
+    discipline = []
+    # champs WCS = date
+    date = []
+    
+    # civilite
+    civilite.append('sigma_personne_civilite')
+    civilite.append('sigma_dossier_o_sc_civilite')
+    civilite.append('sigma_dossier_m_dir_ori_civilite')
+    civilite.append('sigma_dossier_a_sc_civilite')
+    civilite.append('sigma_dossier_m_dir_acc_civilite')
+    # statut
+    statut.append('sigma_dossier_statut_personne')
+    # niveau
+    niveau.append('sigma_dossier_niveau')
+    niveau.append('sigma_dossier_m_niveau_encours')
+    # diplome
+    diplome.append('sigma_dossier_m_intitule_diplome')
+    # mois
+    mois.append('sigma_dossier_m_alt_mois_origine')
+    mois.append('sigma_dossier_m_alt_mois_accueil')
+    # these
+    these.append('sigma_dossier_m_type_these')
+    # bourse
+    bourse.append('sigma_dossier_categorie_bourse')
+    # pays
+    pays.append('sigma_personne_pays_nationalite')
+    pays.append('sigma_personne_pays_naissance')
+    pays.append('sigma_personne_pays_residence')
+    pays.append('sigma_dossier_pays_etabl')
+    pays.append('sigma_dossier_m_pays_soutenance')
+    # etablissement
+    etablissement.append('sigma_dossier_o_etablissement')
+    etablissement.append('sigma_dossier_a_etablissement')
+    # discipline
+    discipline.append('sigma_dossier_m_discipline')
+    # date
+    date.append('sigma_personne_date_naissance')
+    date.append('sigma_dossier_date_d_diplome')
+    date.append('sigmawcs_dossier_o_dd')
+    date.append('sigmawcs_dossier_o_df')
+    date.append('sigmawcs_dossier_a_dd')
+    date.append('sigmawcs_dossier_a_df')
+    date.append('sigma_dossier_m_date_inscription_these')
+    date.append('sigma_dossier_m_date_soutenance_these')
+    
+    ### TEST (utilisé par test.py) : résultats conversion globale de tous les .json
+    convert_ok = True
+    convert_errors = []
+
+    ### NETTOYAGE unicode -> ISO
+    def cleanup(self, json):
+        for k,v in json.iteritems():
+            if type(v) is unicode :
+                # encodage : correction caractères
+                v = self.replaceUnicodeNotIso(v)
+                # strip espaces
+                v = v.lstrip()
+                v = v.rstrip()
+                json[k] = v
+            if type(v) is str :
+                print ('%s : %s a str = %s') % (json['wcs_num_dossier'], k, v)
+        return json
+
+    def replaceUnicodeNotIso(self, string):
+        """
+        Remplace caractère Unicode (scripts latin) n'ayant pas d'équivalent ISO-8859-1 
+        par un caractère visuellement similaire.
+        """
+        result = ''
+        
+        #nkfd_form = unicodedata.normalize('NFKD', unicode(string))
+        #string = u"".join([c for c in nkfd_form if not unicodedata.combining(c)])
+        
+        # Latin Extended-A
+        avant = u'ĂŞşŢţνγδệịạỄợụĐốữẦươȘβğПΣăúµÁẾ◦õễọạươộảỨũửỆỘƯƠ'
+        apres = u'ASsTtvgdêiaEouDouAuoSBgPEaumAE*oeoauooaUuuEOUO'
+
+        for char in string:
+            i = avant.find(char)
+            if i >= 0:
+                char = apres[i]
+            result += char
+
+        return result
+    
+    ### MAPPAGE pour formulaire en cours
+    mapping = MAPPING    
+    
+    def mapper(self, json):
+        """
+        Traitement sur les champs reçus : mapper pour associer à SIGMA.
+        """
+        newJson = {}
+        mapping_reverse = {}
+        for k,v in self.mapping.items():
+            mapping_reverse[v] = k
+        # renomme champ avec nom SIGMA si mappé sinon conserve nom
+        for k,v in json.items():
+            if k in self.mapping.values():
+                new_k = mapping_reverse[k]
+                newJson[new_k] = v
+            else:
+                newJson[k] = v
+        # ajouter tous les champs SIGMA qui ne serait pas dans le formulaire reçu
+        for k,v in self.mapping.items():
+            if not newJson.has_key(k):
+                newJson[k] = None 
+        return newJson
+    
+    ### CONVERSION (de string s pour key k)
+    def convert(self, json):
+        """
+        Traitement sur les valeurs reçues : convertir pour rencontre conforme SIGMA.
+        """
+        for k,s in json.iteritems():
+            if s is None:
+                s = u''
+                json[k] = s
+            if k in self.civilite :
+                s2 = self.str2civilite(s)
+                json[k] = s2
+                if s2 == s:
+                    self.log_error(json, k, s)
+            elif k in self.statut :
+                json[k] = self.str2statutPersonne(s)
+                # no log_error
+            elif k in self.niveau :
+                json[k] = self.str2niveauEtude(s)
+                # no log_error
+            elif k in self.diplome :
+                json[k] = self.str2diplome(s)
+                # no log_error
+            elif k in self.mois :
+                json[k] = self.str2nbMois(s)
+                # no log_error
+            elif k in self.these :
+                s2 = self.str2typeThese(s)
+                json[k] = s2
+                if s2 == s:
+                    self.log_error(json, k, s)
+            elif k in self.bourse :
+                s2 = self.str2bourse(s)
+                json[k] = s2
+                if s != u'' and s2 == s:
+                    self.log_error(json, k, s)
+            elif k in self.pays:
+                s2 = self.str2pays(s)
+                json[k] = s2
+                if s2 == s:
+                    self.log_error(json, k, s)
+            elif k in self.etablissement :
+                s2 = self.str2etablissement(s)
+                json[k] = s2
+                if s2 == s:
+                    self.log_error(json, k, s)
+            elif k in self.discipline :
+                s2 = self.str2discipline(s)
+                json[k] = s2
+                if s2 == s:
+                    self.log_error(json, k, s)
+            elif k in self.date :
+                s2 = self.str2date(s)
+                json[k] = s2
+                if s2 == s:
+                    self.log_error(json, k, s)
+        return json
+        
+    def log_error(self, json, k, s):
+        self.convert_ok = False
+        error = '%s : %s = %s' % (json['wcs_num_dossier'], k, s)
+        self.convert_errors.append(error)
+        
+    def str2civilite(self, s):
+        output = s
+        if s == 'M.':
+            output = 'MR'
+        elif s == 'Mme':
+            output = 'MM'
+        elif s == 'Mlle':
+            output = 'ME'
+        return output
+        
+    def str2statutPersonne(self, s):
+        """
+        Exemple :
+        pattern 1 (SIGMA) :
+        1 Étudiant
+        2 Chercheur
+        3 Enseignant
+        4 Enseignant-chercheur
+        5 Post-Doc
+        
+        pattern 2:
+        1 Chercheur
+        2 Enseignant-chercheur
+        3 Étudiant
+        """
+        output = 0
+        try :
+            # id du statut = permière lettre
+            input = int(s[0])
+            # WCS : valeurs différentes de SIGMA... :(
+            if PATTERN_STATUT_PERSONNE is not None:
+                if PATTERN_STATUT_PERSONNE == 1:
+                    output = input
+                elif PATTERN_STATUT_PERSONNE == 2:
+                    if input == 1:
+                        output = 2
+                    elif input == 2:
+                        output = 4
+                    elif input == 3:
+                        output = 1
+        except ValueError :
+            pass
+        return output
+           
+    def str2niveauEtude(self, s):
+        """
+        C_NIVEAU    L_INTITULE_NIVEAU   L_NIVEAU (annees)
+        6              ...                 0
+        1              Licence 2           2
+        2              Licence 3           3
+        3              Master 1            4
+        4              Master 2            5
+        7              Master 2 +          6
+        5              Doctorat            8
+        8              Bac + 7             7
+        9              Doctorat            9
+        """
+        output = 0
+        try :
+            # années études = permière lettre
+            annees = int(s[0])
+            # sauf 10 ans = 2 premières lettres...
+            if annees == 1:
+                annees = 10
+                
+            if annees == 4 : output = 3
+            elif annees == 5 : output = 4
+            elif annees == 6 : output = 7
+            elif annees == 7 : output = 8
+            elif annees == 8 : output = 5
+            elif annees == 9 : output = 9
+            elif annees == 10 : output = 9
+            
+        except ValueError :
+            pass
+        return output
+        
+    def str2diplome(self, s):
+        output = s
+        if s.lower().startswith('autre'):
+            output = '' # retourne vide car traitement teste si vide pour prendre valeur autre champ
+        return output
+        
+    def str2nbMois(self, s):
+        output = 0
+        try :
+            # permière lettre = nb de mois
+            output = int(s[0])
+        except (ValueError, IndexError) :
+            pass
+        return output
+        
+    def str2typeThese(self, s):
+        output = s
+        if s == 'Co-tutelle':
+            output = 'CT'
+        elif s == 'Co-direction':
+            output = 'CD'
+        elif s == 'Autre':
+            output = 'AU'       
+        return output
+        
+    def str2bourse(self, s):
+        """
+        Exemple :
+        input = Bourse de doctorat (Formation à la Recherche - FR)
+        output = FR
+        """
+        pattern = r'.*(?P<code>\w{2})\)$'
+        return self.code_from_pattern(pattern, s)
+
+    def str2pays(self, s):
+        """
+        Exemple :
+        input = Arménie (AM - Europe centrale et orientale)
+        output = AM
+        """
+        pattern = r'.*\((?P<code>\w{2}).*\)$'
+        return self.code_from_pattern(pattern, s)
+
+    def str2etablissement(self, s):
+        """
+        Exemple :
+        pattern 1 :
+        input = Algérie - Centre universitaire d'El Oued (Sud/BEOM/1120)
+        output = 1120
+        
+        pattern 2 :
+        input = Cameroun - Université de Ngaoundéré (265 - Sud)
+        output = 265
+        """
+        patterns = {
+            1: r'.*\(.*/(?P<code>\d*)\)$',
+            2: r'.*\((?P<code>\d*) .*\)$',
+        }
+        pattern = patterns[PATTERN_ETABL]
+        return self.code_from_pattern(pattern, s)
+
+    def str2discipline(self, s):
+        """
+        Exemple :
+        input = Anthropologie (D104)
+        output = D104
+        """
+        pattern = r'.*\((?P<code>\w*)\)$'
+        return self.code_from_pattern(pattern, s)
+
+    def str2date(self, s):
+        date = None
+        try:
+            d = datetime.strptime(s, '%Y-%m-%d')
+            date = datetime_date(d.year, d.month, d.day)
+        except ValueError:
+            pass
+        return date
+        
+    def code_from_pattern(self, pattern=r'^$', s=u''):
+        if s is None:
+            s = u''
+        output = s
+        if s :
+            m = re.match(pattern, s)
+            if m and m.group('code'):
+                output = m.group('code')
+        return output
diff --git a/project/wcs/settings.py b/project/wcs/settings.py
new file mode 100644 (file)
index 0000000..03622a3
--- /dev/null
@@ -0,0 +1,25 @@
+# -=- encoding: utf-8 -=-
+
+from wcs.conf import *
+
+# WCS
+WCS_SIGMA_URL = 'https://formulaires.auf.org/sigma'
+
+# SIGMA
+# statut SIGMA des dossiers importés de WCS
+STATUT_DOSSIER = 3  # En cours
+# Pièces : nom du champ WCS attendu (sinon passer par conf.MAPPING)
+PIECES_FIELDNAME = {
+    2:'sigma_dossier_p_attestation_a_dir',
+    3:'sigma_dossier_p_attestation_o_dir',
+    16:'sigma_dossier_p_cv',
+    17:'sigma_dossier_p_descriptif_these',
+    19:'', # formulaire (PDF) : pas encore fourni par WCS, généré localement par sigmawcs.wcs.docs
+    27:'sigma_dossier_p_protocole_recherche',
+    58:'sigma_dossier_p_etat_travaux',
+    }
+    
+# SIGMAWCS
+WCS_URL_NAMESPACE = 'wcs'
+DATA_DIR = 'docs/%s/data/' % FORMNAME
+
diff --git a/project/wcs/templates/candidature.html b/project/wcs/templates/candidature.html
new file mode 100755 (executable)
index 0000000..efa4a71
--- /dev/null
@@ -0,0 +1,391 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>    
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />\r
+    <title>{{ sigma_personne_nom.upper }}, {{ sigma_personne_prenom.title }} : {{ formname }}</title>\r
+    {% comment %}\r
+    <link href="{{ MEDIA_URL }}pdf.css" rel="stylesheet" type="text/css" />
+    {% endcomment %}
+    <style type="text/css">
+h1 { color:#c60 ; margin:0px; padding:0px; }
+h2 { padding:5px; background-color:#ccc; }
+h3 { margin:0px; padding:0px; }
+p { margin:0px; padding:0px; }
+
+div.spacer { line-height:0.25cm; }
+
+table { width:100%; padding:0px; }
+table.titre { margin:10px 0px 10px 0px; }
+table.data { margin-top:5px; }
+
+td { width:49%; padding:2px; vertical-align:top; }
+table.titre td { padding:0px; }
+
+.bloc { border: solid 1px black; padding:5px; }
+.gutter { width:2%; }
+
+td.w15 { width:15%; padding:0px; vertical-align:top; }
+td.w25 { width:25%; padding:0px; vertical-align:top; }
+td.w30 { width:30%; padding:0px; vertical-align:top; }
+td.w35 { width:35%; padding:0px; vertical-align:top; }
+td.w50 { width:50%; padding:0px; vertical-align:top; }
+
+td.w20 { width:20%; padding:0px; vertical-align:top; }
+td.w80 { width:80%; padding:0px; vertical-align:top; }
+
+td.w40 { width:40%; padding:0px; vertical-align:top; }
+td.w60 { width:60%; padding:0px; vertical-align:top; }
+    </style>
+</head>
+<body>
+
+<h1>{{ sigma_personne_civilite }} {{ sigma_personne_prenom.title }} {{ sigma_personne_nom.upper }}</h1>
+
+<h2>Renseignements personnels</h2>
+
+<table class="data">
+<tr>
+    <td class="bloc">
+        <h3>Identification</h3>
+        <p>
+        {{ sigma_personne_civilite }} {{ sigma_personne_prenom.title }} {{ sigma_personne_nom.upper }}
+        {% if sigma_personne_nom_jeune_fille %}<br />Nom de jeune fille : {{ sigma_personne_nom_jeune_fille.upper }}{% endif %}
+        </p>
+        <table>
+            <tr><td class="w40"><i>Nationalité : </i></td><td class="w60">{{ sigma_personne_pays_nationalite }}</td></tr>
+            <tr>
+                <td class="w40"><i>Date de naissance : </i></td>
+                <td class="w60">                
+                {% with sigma_personne_date_naissance as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr><td class="w40"><i>Ville de naissance : </i></td><td class="w60">{{ sigma_personne_ville_naissance.title|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Pays de naissance : </i></td><td class="w60">{{ sigma_personne_pays_naissance|default:"n/d" }}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <h3>Adresse de correspondance</h3>
+        <p>
+        {{ sigma_personne_adresse }}
+        <br />
+        {% if sigma_personne_ville %}{{ sigma_personne_ville.title }}{% endif %}
+        {% if sigma_personne_region %}, {{ sigma_personne_region }}{% endif %}
+        {% if sigma_personne_code_postal %} {{ sigma_personne_code_postal }} {% endif %}
+        {% if sigma_personne_pays_residence %}<br />{{ sigma_personne_pays_residence }}{% endif %}
+        </p>
+        <table>
+            <tr><td class="w40"><i>Téléphone personnel : </i></td><td class="w60">{{ sigma_personne_tel|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Téléphone professionel : </i></td><td class="w60">{{ sigma_personne_tel_pro|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Adresse électronique : </i></td><td class="w60">{{ sigma_personne_email|default:"n/d" }}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+<table class="data">
+<tr>
+    <td class="bloc">
+        <h3>Situation universitaire</h3>
+        <table>
+            <tr><td class="w20"><i>Statut : </i></td><td class="w80">{{ sigma_dossier_statut_personne|slice:"4:"|default:"n/d" }}</td></tr>
+            <tr><td class="w20"><i>Fonction : </i></td><td class="w80">{{ sigma_dossier_fonction|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <h3>Dernier diplôme obtenu</h3>
+        <table>
+            <tr><td class="w40"><i>Intitulé : </i></td><td class="w60">{{ sigma_dossier_intitule_d_diplome|default:"n/d" }}</td></tr>
+            <tr>
+                <td class="w40"><i>Date d'obtention : </i></td>
+                <td class="w60">
+                {% with sigma_dossier_date_d_diplome as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr><td class="w40"><i>Établissement : </i></td><td class="w60">{{ sigma_dossier_nom_etabl|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Pays : </i></td><td class="w60">{{ sigma_dossier_pays_etabl|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Niveau (nb. années univ.) : </i></td><td class="w60">{{ sigma_dossier_niveau|default:"n/d" }}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+<table class="data">
+<tr>
+    <td class="bloc" colspan="3">
+        <h3>Lien avec l'AUF</h3>
+        <table>
+            <tr>
+            <td class="w35"><i>Candidat a déjà participé à un programme de l'AUF : </i></td><td class="w15">{{ sigma_dossier_particip_prog_auf|default:"n/d" }}</td>
+            <td class="w35" style="padding-left:5px;"><i>Candidat a déjà bénéficié d'une bourse AUF : </i></td><td class="w15">{{ sigma_dossier_boursier_auf|default:"n/d" }}</td>
+            </tr>
+            <tr>
+            <td class="w35"><i>Dernier programme participé : </i></td><td class="w15">{{ sigma_dossier_programme|default:"n/d" }}</td>
+            <td class="w35" style="padding-left:5px;"><i>Type de bourse : </i></td><td class="w15">{{ sigma_dossier_categorie_bourse|default:"n/d" }}</td>
+            </tr>
+            <tr>
+            <td class="w35"><i>Année : </i></td><td class="w15">{{ sigma_dossier_annee_programme|default:"n/d" }}</td>
+            <td class="w35" style="padding-left:5px;"><i>Année : </i></td><td class="w15">{{ sigma_dossier_annee_bourse|default:"n/d" }}</td>
+            </tr>
+        </table>
+    </td>
+</tr>
+</table>
+
+<div class="spacer">&nbsp;</div>
+<table class="titre">
+<tr>
+    <td>
+        <h2>Origine</h2>
+    </td>
+    <td class="gutter"></td>
+    <td>
+        <h2>Accueil</h2>
+    </td>
+</tr>
+</table>
+
+<table class="data">
+<tr>
+    <td class="bloc">
+        <h3>Établissement d'origine</h3>
+        <p>
+        {{ sigma_dossier_o_etablissement|default:"n/d" }}
+        </p>
+        <table>
+            <tr><td class="w40"><i>Faculté, départ. ou labo : </i></td><td class="w60">{{ sigma_dossier_o_sc_faculte|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Ville : </i></td><td class="w60">{{ sigma_dossier_o_sc_ville.title|default:"n/d" }}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <h3>Établissement d'accueil</h3>
+        <p>
+        {{ sigma_dossier_a_etablissement|default:"n/d" }}
+        </p>
+        <table>
+            <tr><td class="w40"><i>Faculté, départ. ou labo : </i></td><td class="w60">{{ sigma_dossier_a_sc_faculte|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Ville : </i></td><td class="w60">{{ sigma_dossier_a_sc_ville.title|default:"n/d" }}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+<table class="data">
+<tr>
+    <td class="bloc">
+        <table>
+            <tr>
+            <td class="w40"><h3>Accord scientifique</h3></td>
+            <td class="w60">
+                {% if sigma_dossier_o_sc_civilite %}{{ sigma_dossier_o_sc_civilite }} {% endif %}
+                {% if sigma_dossier_o_sc_prenom %}{{ sigma_dossier_o_sc_prenom.title }} {% endif %}
+                {% if sigma_dossier_o_sc_nom %}{{ sigma_dossier_o_sc_nom.upper }}{% endif %}
+            </td>
+            </tr>
+            <tr><td class="w40"><i>Fonction : </i></td><td class="w60">{{ sigma_dossier_o_sc_fonction|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Téléphone professionnel : </i></td><td class="w60">{{ sigma_dossier_o_sc_tel_pro|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Adresse électronique : </i></td><td class="w60">{{ sigma_dossier_o_sc_email|default:"n/d" }}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <table>
+            <tr>
+            <td class="w40"><h3>Accord scientifique</h3></td>
+            <td class="w60">
+                {% if sigma_dossier_a_sc_civilite %}{{ sigma_dossier_a_sc_civilite }} {% endif %}
+                {% if sigma_dossier_a_sc_prenom %}{{ sigma_dossier_a_sc_prenom.title }} {% endif %}
+                {% if sigma_dossier_a_sc_nom %}{{ sigma_dossier_a_sc_nom.upper }}{% endif %}
+            </td>
+            </tr>
+            <tr><td class="w40"><i>Fonction : </i></td><td class="w60">{{ sigma_dossier_a_sc_fonction|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Téléphone professionnel : </i></td><td class="w60">{{ sigma_dossier_a_sc_tel_pro|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Adresse électronique : </i></td><td class="w60">{{ sigma_dossier_a_sc_email|default:"n/d" }}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+<table class="data">
+<tr>
+    <td class="bloc">
+        
+        <table>
+            <tr>
+            <td class="w40"><h3>Directeur de thèse</h3></td>
+            <td class="w60">
+                {% if sigma_dossier_m_dir_ori_civilite %}{{ sigma_dossier_m_dir_ori_civilite }} {% endif %}
+                {% if sigma_dossier_m_dir_ori_prenom %}{{ sigma_dossier_m_dir_ori_prenom.title }} {% endif %}
+                {% if sigma_dossier_m_dir_ori_nom %}{{ sigma_dossier_m_dir_ori_nom.upper }}{% endif %}
+            </td>
+            </tr>
+            <tr><td class="w40"><i>Adresse électronique : </i></td><td class="w60">{{ sigma_dossier_m_dir_ori_email|default:"n/d" }}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <table>
+            <tr>
+            <td class="w40"><h3>Directeur de thèse</h3></td>
+            <td class="w60">
+                {% if sigma_dossier_m_dir_acc_civilite %}{{ sigma_dossier_m_dir_acc_civilite }} {% endif %}
+                {% if sigma_dossier_m_dir_acc_prenom %}{{ sigma_dossier_m_dir_acc_prenom.title }} {% endif %}
+                {% if sigma_dossier_m_dir_acc_nom %}{{ sigma_dossier_m_dir_acc_nom.upper }}{% endif %}
+            </td>
+            </tr>
+            <tr><td class="w40"><i>Adresse électronique : </i></td><td class="w60">{{ sigma_dossier_m_dir_acc_email|default:"n/d" }}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+
+<div class="spacer">&nbsp;</div>
+<h2>Mobilité</h2>
+
+<table>
+<tr>
+    <td class="bloc">
+        <h3>Période de mobilité - Origine</h3>
+        <table>
+            <tr>
+                <td class="w40"><i>Date de début : </i></td>
+                <td class="w60">
+                {% with sigmawcs_dossier_o_dd as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr>
+                <td class="w40"><i>Date de fin : </i></td>
+                <td class="w60">
+                {% with sigmawcs_dossier_o_df as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr><td class="w40"><i>Nombre de mois : </i></td><td class="w60">{{sigma_dossier_m_alt_mois_origine|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <h3>Période de mobilité - Accueil</h3>
+        <table>
+            <tr>
+                <td class="w40"><i>Date de début : </i></td>
+                <td class="w60">
+                {% with sigmawcs_dossier_a_dd as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+                </tr>
+            <tr>
+                <td class="w40"><i>Date de fin : </i></td>
+                <td class="w60">
+                {% with sigmawcs_dossier_a_df as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr><td class="w40"><i>Nombre de mois : </i></td><td class="w60">{{sigma_dossier_m_alt_mois_accueil|default:"n/d"}}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+<table class="data">
+<tr>
+    <td class="bloc">
+        <h3>Thèse</h3>
+        <table>
+            <tr>
+                <td class="w40"><i>Date d'inscription en thèse : </i></td>
+                <td class="w60">
+                {% with sigma_dossier_m_date_inscription_these as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr>
+                <td class="w40"><i>Date de soutenance prévue : </i></td>
+                <td class="w60">
+                {% with sigma_dossier_m_date_soutenance_these as date %}
+                    {% if date %}
+                        {{date|slice:"8:"}}-{{date|slice:"5:7"}}-{{date|slice:"0:4"}}
+                    {% else %}
+                        n/d
+                    {% endif %}
+                {% endwith %}
+                </td>
+            </tr>
+            <tr><td class="w40"><i>Pays de soutenance prévu : </i></td><td class="w60">{{ sigma_dossier_m_pays_soutenance|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Type de thèse : </i></td><td class="w60">{{ sigma_dossier_m_type_these|default:"n/d" }}</td></tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <h3>Formation en cours</h3>
+        <table>
+            <tr>
+                <td class="w40"><i>Diplôme préparé : </i></td><td class="w60">{{ sigma_dossier_m_intitule_diplome|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Si autre : </i></td><td class="w60">{{ sigma_dossier_m_intitule_diplome_autre|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Niveau (nb. années univ.) : </i></td><td class="w60">{{ sigma_dossier_m_niveau_encours|default:"n/d" }}</td></tr>
+        </table>
+    </td>
+</tr>
+</table>
+<table class="data">
+<tr>
+    <td class="bloc">
+        <h3>Dossier scientifique</h3>
+        <table>
+            <tr><td class="w40"><i>Intitulé du sujet de thèse : </i></td><td class="w60">{{ sigma_dossier_m_intitule_projet|default:"n/d" }}</td></tr>
+            <tr><td class="w40"><i>Mots-clés : </i></td>
+            <td class="w60">
+                {% if sigma_dossier_m_mot_clef1 %}{{ sigma_dossier_m_mot_clef1.upper }}{% endif %}
+                {% if sigma_dossier_m_mot_clef2 %}, {{ sigma_dossier_m_mot_clef2.upper }}{% endif %}
+                {% if sigma_dossier_m_mot_clef3 %}, {{ sigma_dossier_m_mot_clef3.upper }}{% endif %}
+            </td>
+            </tr>
+        </table>
+    </td>
+    <td class="gutter"></td>
+    <td class="bloc">
+        <h3>Discipline</h3>
+        <p>
+        {{ sigma_dossier_m_discipline|default:"n/d" }}
+        </p>
+    </td>
+</tr>
+</table>
+
+</body>
+</html>
diff --git a/project/wcs/tests.py b/project/wcs/tests.py
new file mode 100644 (file)
index 0000000..a128a8e
--- /dev/null
@@ -0,0 +1,78 @@
+# -=- encoding: utf-8 -=-
+
+from wcs.settings import FORMNAME
+
+from lib import getCandidaturesJson, createPdf, createZip
+from models import JsonWcs2JsonSigma, JsonSigma2ObjectSigma
+
+# MAIN
+if __name__ == "__main__":
+    """
+    bin/django shell # python manage.py shell
+    >>> cd wcs
+    >>> run tests.py
+    """
+
+    # json WCS
+    candidatures = getCandidaturesJson()
+    nombre = len(candidatures)
+    spams = 0
+    traites = 0
+    
+    # rapport : header
+    print 'IMPORT CANDIDATURES : WCS => json => SIGMA'
+    print 'Formulaire : %s' % FORMNAME
+    print '--------------------------------------------------'
+    print 'Nombre à traiter = %d' % (nombre)
+    print '--------------------------------------------------'
+    
+    # TESTS
+    jj = JsonWcs2JsonSigma()
+    jo = JsonSigma2ObjectSigma()
+#    candidature = candidatures[3]
+    for candidature in candidatures :
+        if candidature['wcs_workflow_status'] == 'SPAM':
+            spams = spams + 1
+        else:
+            candidature = jj.mapper(candidature)
+            # json
+            candidature = jj.cleanup(candidature)
+            candidature = jj.convert(candidature)
+            # objets SIGMA
+            save = False
+            p = jo.createPersonne(candidature, save)
+            d = jo.createDossier(candidature, p, save)
+            do = jo.createDossierOrigine(candidature, d, save)
+            da = jo.createDossierAccueil(candidature, d, save)
+            dm = jo.createDossierMobilite(candidature, d, save)
+            pieces = jo.createDossierPieces(candidature, d, save)
+            save = True
+            p = jo.createPersonne(candidature, save)
+            d = jo.createDossier(candidature, p, save)
+            do = jo.createDossierOrigine(candidature, d, save)
+            da = jo.createDossierAccueil(candidature, d, save)
+            dm = jo.createDossierMobilite(candidature, d, save)
+            pieces = jo.createDossierPieces(candidature, d, save)
+            traites = traites + 1
+
+    # RÉSULTATS
+    if jj.convert_ok:
+        print 'OK : Test de conversion passé avec succès.'
+    else:
+        print 'ERREUR : Tests de conversion ont échoué pour les cas suivants :'
+        jj.convert_errors.sort()
+        for error in jj.convert_errors:
+            print error
+        print '--------------------------------------------------'
+        print 'ERREUR : %d erreurs lors de traitement' % len(jj.convert_errors)
+    if jo.creation_django_ok:
+        print 'Ok : Test de création des objets Django passé avec succès.'
+    if jo.creation_db_ok:
+        print 'Ok : Test de sauvegarde dans DB passé avec succès.'
+    
+    # rapport : footer
+    print '--------------------------------------------------'
+    print 'Total spams   = %d sur %d' % (spams, nombre)
+    print 'Total traités = %d sur %d' % (traites, nombre)
+    print 'Total         = %d sur %d' % (spams + traites, nombre)
+    print '--------------------------------------------------'
diff --git a/project/wcs/urls.py b/project/wcs/urls.py
new file mode 100644 (file)
index 0000000..9cc259f
--- /dev/null
@@ -0,0 +1,7 @@
+# -*- encoding: utf-8 -*-
+
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+    url(r'^import/$', 'wcs.views.importDossiersCandidaturesWcs', name='import'),
+)
diff --git a/project/wcs/views.py b/project/wcs/views.py
new file mode 100644 (file)
index 0000000..19697e9
--- /dev/null
@@ -0,0 +1,330 @@
+# -=- encoding: utf-8 -=-
+
+import os
+from simplejson import loads
+import urllib2
+import zipfile
+
+from sigma_v1.models import Dossier
+from sigma_v1.settings import PIECES_SUFFIXE as PIECES_SIGMA_SUFFIXE
+from wcs.settings import PIECES_FIELDNAME, PIECES_SUFFIXE as PIECES_WCS_SUFFIXE
+from wcs.settings import MOBILITE, FORMNAME, WCS_SIGMA_URL, WCS_SIGMA_USER, WCS_SIGMA_PASS
+
+from lib import getCandidaturesJson
+from models import JsonWcs2JsonSigma, JsonSigma2ObjectSigma
+
+def importDossiersCandidaturesWcs(appel):
+    """Appel distant sur WCS et copie locale de tout le répertoire d'un appel
+    """
+    def retrieve(url, filename, force=True):
+        written = False
+        if not os.path.exists(filename) or force:
+            pagehandle = urllib2.urlopen(url)
+            f = file(filename, 'wb')
+            f.write(pagehandle.read())
+            f.close()
+            pagehandle.close()
+            written = True
+        return written
+    
+    # conf
+    src = '%s/%s' % (WCS_SIGMA_URL, appel.wcs_form_name)
+    dst = 'docs/%s' % (appel.wcs_form_name)
+    dossierDir = 'data'
+    meta = [
+        'field-names.json',
+        'field-names.txt',
+        'last-run.log',
+        'liste-dossiers.json',
+        ]
+    erreurs = []
+    
+    # local
+    if not os.path.exists(dst):
+        dstData = '%s/%s' % (dst, dossierDir)
+        os.makedirs(dstData)    # mode 0777 ?
+    
+    # accès WCS
+    passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
+    passman.add_password(None, WCS_SIGMA_URL, WCS_SIGMA_USER, WCS_SIGMA_PASS)
+    authhandler = urllib2.HTTPBasicAuthHandler(passman)
+    opener = urllib2.build_opener(authhandler)
+    urllib2.install_opener(opener)
+    
+    # rapport : header
+    print '--------------------------------------------------'
+    print 'IMPORT WCS'
+    print 'Formulaire : %s' % (appel.wcs_form_name)
+    print '--------------------------------------------------'
+    
+    # import meta
+    print 'Import des fichiers de méta-données sur le formulaire :'
+    os.chdir(dst)
+    
+    nb_meta = len(meta)
+    nb_meta_done = 0
+    for filename in meta:
+        url = '%s/%s' % (src, filename)
+        rapport = '%s : %s' % ('meta', filename)
+        try:
+            retrieve(url, filename)
+            print rapport
+            nb_meta_done = nb_meta_done + 1
+        except urllib2.HTTPError:
+            erreurs.append(rapport)
+    print '--------------------------------------------------'
+    
+    # import data
+    print 'Import des dossiers de candidature (json et pièces jointes) :'
+    f = open('liste-dossiers.json', 'r')
+    data = f.read()
+    f.close()
+    listjson = loads(data, encoding='utf-8')
+    listjson.sort()
+    os.chdir(dossierDir)
+
+    nb_json = len(listjson)
+    nb_json_done = 0
+    nb_json_imported = 0
+    nb_pieces = 0
+    for id, piece in PIECES_WCS_SUFFIXE.items():
+        if piece:
+            nb_pieces = nb_pieces + 1
+    nb_pieces = nb_pieces * nb_json
+    nb_pieces_done = 0
+    for filename in listjson:
+        # json
+        url = '%s/%s/%s' % (src, dossierDir, filename)
+        rapport = '%s : %s' % ('json', filename)
+        try:
+            imported = retrieve(url, filename, force=False)
+            if imported :
+                print rapport
+                nb_json_imported = nb_json_imported + 1
+            nb_json_done = nb_json_done + 1
+        except urllib2.HTTPError:
+            erreurs.append(rapport)
+        
+        # pièces jointes
+        f = open(filename, 'r')
+        data = f.read()
+        f.close()
+        json = loads(data, encoding='utf-8')
+        for id, piece in PIECES_WCS_SUFFIXE.items():
+            if piece:
+                try:
+                    pieceName = json[piece]
+                    url = '%s/%s/%s' % (src, dossierDir, pieceName)
+                    rapport = '* %s : %s' % (piece, pieceName)
+                except KeyError:
+                    erreurs.append(rapport)
+                try:
+                    retrieve(url, pieceName)
+                    print rapport
+                    nb_pieces_done = nb_pieces_done + 1
+                except urllib2.HTTPError:
+                    erreurs.append(rapport)
+        print ''
+    print '--------------------------------------------------'
+    
+    if erreurs:
+        print '%d ERREURS' % (len(erreurs))
+        for erreur in erreurs:
+            print erreur
+        print '--------------------------------------------------'
+    
+    # rapport : footer
+    print 'FIN IMPORT WCS'
+    print 'Méta traités    : %d sur %d' % (nb_meta_done, nb_meta)
+    print 'Json traités    : %d sur %d' % (nb_json_done, nb_json)
+    print 'Json importés   : %d sur %d' % (nb_json_imported, nb_json)
+    print 'Pièces traitées : %d sur %d' % (nb_pieces_done, nb_pieces)
+    print '--------------------------------------------------'
+    
+    
+#def getCandidaturesJsonWcs(request, appel):
+#    retourne la liste locale des json importés de WCS
+    
+#def exportSigmaData(request, appel):
+#    exporte dans la base de données SIGMA les données des candidatures json de WCS
+
+def exportSigmaFiles(appel):
+    """Déplace les fichiers ZIP et PDF de la candidature à leur emplacement final sigma.
+    """
+    
+    # conf
+    dossierDir = 'data'
+    src = 'docs/%s/%s' % (appel.wcs_form_name, dossierDir)
+    dst = 'docs/%s/export' % (appel.wcs_form_name)
+    dstZip = "docs/%s/candidatures" % (appel.wcs_form_name)
+    erreurs = []
+    
+    if not os.path.exists(dst):
+        os.makedirs(dst)    # mode 0777 ?
+    if not os.path.exists(dstZip):
+        os.mkdir(dstZip)    # mode 0777 ?
+        
+    mappingPiecesSuffixes = {}
+    for id, suffixe_wcs in PIECES_WCS_SUFFIXE.items():
+        if suffixe_wcs:
+            # nom champ json normalisé pour cette pièce
+            field = PIECES_FIELDNAME[id]
+            # suffixe SIGMA
+            suffixe_sigma = PIECES_SIGMA_SUFFIXE[id]
+            # mapping
+            mappingPiecesSuffixes[field] = (suffixe_wcs, suffixe_sigma)
+        
+    # rapport : header
+    print '--------------------------------------------------'
+    print 'EXPORT DOSSIERS DE CANDIDATURE (FICHIERS) : SIGMAWCS => SIGMA'
+    print 'Formulaire : %s' % (appel.wcs_form_name)
+    print '--------------------------------------------------'
+    
+    # création du répertoire local pour rsync
+    print "Création du répertoire locale des dossiers de candidature (fichiers renommés) :"
+    exportCandidaturesPath = "%s/%d.zip" % (dstZip, MOBILITE)
+    archive = zipfile.ZipFile(exportCandidaturesPath, "w", zipfile.ZIP_STORED)
+        
+    candidatures = getCandidaturesJson()
+    nb_cand = len(candidatures)
+    nb_spam = 0
+    nb_cand_done = 0
+
+    jj = JsonWcs2JsonSigma()
+    for candidature in candidatures :
+        if candidature['wcs_workflow_status'] == 'SPAM':
+            nb_spam = nb_spam + 1
+        else:
+            candidature = jj.mapper(candidature)
+            candidature = jj.cleanup(candidature)     
+            candidature = jj.convert(candidature)
+            filename = candidature['wcs_json_filename']
+            basename = filename.rstrip('.json')
+            nb_cand_done = nb_cand_done + 1
+            print '%d : %s' % (nb_cand_done, basename)
+            # pièces jointes : renommage et copie
+            for field, mapping in mappingPiecesSuffixes.items():
+                nom_src = candidature[field]
+                nom_dst = nom_src.replace(mapping[0], mapping[1])
+                # pièces peuvent être facultatives
+                if nom_src:
+                    path_src = '%s/%s' % (src, nom_src)
+                    path_dst = '%s/%s' % (dst, nom_dst)
+                    fsrc = open(path_src, 'r')
+                    fdst = file(path_dst, 'wb')
+                    fdst.write(fsrc.read())
+                    fdst.close()
+                    fsrc.close()
+            # formulaire (.pdf) et dossier complet (.zip) : copie
+            extensions = ('.pdf', '.zip')
+            for ext in extensions:
+                nom_src = basename + ext
+                nom_dst = nom_src
+                path_src = '%s/%s' % (src, nom_src)
+                path_dst = '%s/%s' % (dst, nom_dst)
+                rapport = '* %s' % (path_src)
+                if os.path.exists(path_src):
+                    fsrc = open(path_src, 'r')
+                    fdst = file(path_dst, 'wb')
+                    fdst.write(fsrc.read())
+                    fdst.close()
+                    fsrc.close()
+                    # zip de tous les dossiers de candidature (zip des zips)
+                    if ext == '.zip':
+                        zipname = '%s-%s-%s.zip' % (candidature['sigma_personne_nom'].upper(), \
+                            candidature['sigma_personne_prenom'].lower().title(), \
+                            candidature['sigma_dossier_m_discipline'])
+                        archive.write(path_src, zipname)
+                    print rapport
+                else:
+                    erreurs.append(rapport)
+            print ''
+    print '--------------------------------------------------'
+    archive.close()
+    
+    # déplacement de l'archive sur le serveur
+    # attention : archive plus créée (répertoire rsyncé)
+    # todo : avec fabric
+    
+    # extraction de l'archive dans les sources SIGMA
+    # attention : archive plus créée (répertoire rsyncé)
+    # todo : 
+    # from wcs.conf import MOBILITE
+    # project/modules/candidatures/docs/%s/ % (MOBILITE)
+    
+    # rapport : erreurs
+    if erreurs:
+        print '%d ERREURS' % (len(erreurs))
+        for erreur in erreurs:
+            print erreur
+        print '--------------------------------------------------'
+    
+    # rapport : footer
+    print 'FIN EXPORT DOSSIERS'
+    print 'Candidatures : %d' % (nb_cand)
+    print 'Spams : %d sur %d' % (nb_spam, nb_cand)
+    print 'Json traités : %d sur %d' % (nb_cand_done, nb_cand)
+#    print 'PDF traités  : %d sur %d' % (nb_pdf_done, nb_cand_done)
+#    print 'ZIP traités  : %d sur %d' % (nb_zip_done, nb_cand_done)
+    print '--------------------------------------------------'
+
+# MAIN
+if __name__ == "__main__":
+    """
+    bin/django shell # python manage.py shell
+    >>> cd wcs
+    >>> run views.py
+    """
+    # candidats déjà dans SIGMA pour cet appel
+    existants = set()
+    try:
+        dossiers = Dossier.objects.filter(mobilite=MOBILITE)
+    except:
+        dossiers = Dossier.objects.none()
+    for dossier in dossiers:
+        existants.add(dossier.personne.email)
+    nb_existants = len(existants)
+    
+    # json WCS
+    candidatures = getCandidaturesJson()
+    nombre = len(candidatures)
+    spams = 0
+    traites = 0
+    
+    # rapport : header
+    print 'IMPORT CANDIDATURES : WCS => json => SIGMA'
+    print 'Formulaire : %s' % FORMNAME
+    print '--------------------------------------------------'
+    print 'Nombre à traiter = %d' % (nombre)
+    print '--------------------------------------------------'
+    # IMPORT
+    jj = JsonWcs2JsonSigma()
+    jo = JsonSigma2ObjectSigma()
+    for candidature in candidatures :
+        candidature = jj.mapper(candidature)
+        print 'WCS %s : en cours...' % candidature['wcs_num_dossier']
+        if candidature['wcs_workflow_status'] == 'SPAM':
+            spams = spams + 1
+        elif candidature['sigma_personne_email'] in existants:
+            print 'existant : %s' % (candidature['sigma_personne_email'])
+        else :
+            # json
+            candidature = jj.cleanup(candidature)     
+            candidature = jj.convert(candidature)
+            # objets SIGMA
+            p = jo.createPersonne(candidature)
+            d = jo.createDossier(candidature, p)
+            do = jo.createDossierOrigine(candidature, d)
+            da = jo.createDossierAccueil(candidature, d)
+            dm = jo.createDossierMobilite(candidature, d)
+            pieces = jo.createDossierPieces(candidature, d)
+            traites = traites + 1
+            print '%d - %s' % (traites, p)
+    
+    # rapport : footer
+    print '--------------------------------------------------'
+    print 'Total existants = %d sur %d' % (nb_existants, nombre)
+    print 'Total spams     = %d sur %d' % (spams, nombre)
+    print 'Total traités   = %d sur %d' % (traites, nombre)
+    print 'Total           = %d sur %d' % (nb_existants + spams + traites, nombre)
+    print '--------------------------------------------------'