481 lines
18 KiB
Python
481 lines
18 KiB
Python
# Passerelle - uniform access to data and services
|
|
# Copyright (C) 2016 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import base64
|
|
import datetime
|
|
import os
|
|
from xml.dom import minidom
|
|
from xml.etree import ElementTree as etree
|
|
|
|
from dateutil.parser import parse as dateutil_parse
|
|
from django.core.files.base import ContentFile
|
|
from django.core.files.storage import default_storage
|
|
|
|
from passerelle.apps.mdel.utils import ElementFactory
|
|
from passerelle.utils.jsonresponse import APIError
|
|
|
|
|
|
def get_resource_base_dir():
|
|
return default_storage.path('mdel')
|
|
|
|
|
|
class AttachedFile:
|
|
def __init__(self, code, filename, b64_content):
|
|
if code not in ('JI', 'JD'):
|
|
raise APIError('%s is not a valid code (JI or JD)' % code)
|
|
self.filename = self.title = filename
|
|
self.content = base64.b64decode(b64_content)
|
|
self.code = code
|
|
|
|
def save(self, directory):
|
|
"""Writes file into directory"""
|
|
path = os.path.join(directory, self.filename)
|
|
default_storage.save(path, ContentFile(self.content))
|
|
|
|
|
|
class MDELBase:
|
|
def to_string(self):
|
|
raw_string = etree.tostring(self.xml, encoding='utf-8')
|
|
parsed_string = minidom.parseString(raw_string)
|
|
return parsed_string.toprettyxml(indent='\t')
|
|
|
|
def save(self, subfolder, filename):
|
|
"""Save object as xml file"""
|
|
folder = os.path.join(get_resource_base_dir(), subfolder)
|
|
path = os.path.join(folder, filename)
|
|
default_storage.save(path, ContentFile(self.to_string()))
|
|
|
|
|
|
class Common(MDELBase):
|
|
xmlns = 'http://finances.gouv.fr/dgme/gf/composants/teledemarchexml/donnee/metier'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.flow_type = args[0]
|
|
self.num = args[1]
|
|
self.doc = kwargs.get('doc')
|
|
super().__init__()
|
|
|
|
def save(self, folder):
|
|
return super().save(folder, self.filename)
|
|
|
|
@property
|
|
def code(self):
|
|
return '%s-%s' % (self.num, self.flow_type)
|
|
|
|
def teledemarche_xml(self, num, date=None, platform_id=1):
|
|
"""Returns XML Teledemarche Element"""
|
|
if not date:
|
|
date = datetime.datetime.utcnow().isoformat()
|
|
else:
|
|
date = dateutil_parse(date).isoformat()
|
|
|
|
teledemarche = etree.Element('Teledemarche')
|
|
teledemarche.extend(
|
|
[
|
|
ElementFactory('NumeroTeledemarche', text=num),
|
|
ElementFactory('Date', text=date),
|
|
ElementFactory('IdentifiantPlateforme', text=platform_id),
|
|
]
|
|
)
|
|
return teledemarche
|
|
|
|
def routage_xml(self, code_insee):
|
|
"""Returns XML Routage Element"""
|
|
routage = etree.Element('Routage')
|
|
donnee = etree.Element('Donnee')
|
|
donnee.extend([ElementFactory('Id', text='CodeINSEE'), ElementFactory('Valeur', text=code_insee)])
|
|
routage.append(donnee)
|
|
return routage
|
|
|
|
def piecejointe_xml(self, attached_files=None):
|
|
"""Returns XML PieceJointe Element"""
|
|
if not attached_files:
|
|
return
|
|
|
|
for f in attached_files:
|
|
piece_jointe = etree.Element('PieceJointe')
|
|
piece_jointe.extend(
|
|
[
|
|
ElementFactory('Intitule', text=f.title),
|
|
ElementFactory('Code', text=f.code),
|
|
ElementFactory('Fichier', text=f.filename),
|
|
]
|
|
)
|
|
|
|
yield piece_jointe
|
|
|
|
def document_xml(self, code, form_files=None):
|
|
"""Returns a XML Document Element"""
|
|
document = ElementFactory('Document')
|
|
document.extend(
|
|
[
|
|
ElementFactory('Code', text=code),
|
|
ElementFactory('Nom', text=code),
|
|
ElementFactory('FichierFormulaire'),
|
|
]
|
|
)
|
|
|
|
if not form_files and not self.doc:
|
|
form_files = [self.code + '-doc-.xml']
|
|
if not form_files and self.doc:
|
|
form_files = [self.doc.filename]
|
|
|
|
for f in form_files:
|
|
document.find('FichierFormulaire').append(ElementFactory('FichierDonnees', text=f))
|
|
|
|
return document
|
|
|
|
|
|
class Message(Common):
|
|
"""Class descrbing a message.xml file"""
|
|
|
|
filename = 'message.xml'
|
|
ns_prefix = 'ns2'
|
|
ns_uri = 'http://finances.gouv.fr/dgme/pec/message/v1'
|
|
|
|
def __init__(
|
|
self,
|
|
flow_type,
|
|
num,
|
|
zip_code,
|
|
date=None,
|
|
platform_id='1',
|
|
form_files=None,
|
|
attached_files=None,
|
|
doc=None,
|
|
):
|
|
super().__init__(flow_type, num, doc=doc)
|
|
|
|
etree._namespace_map[self.ns_uri] = self.ns_prefix
|
|
|
|
root = ElementFactory('Message', namespace=self.ns_uri)
|
|
root.set('xmlns', self.xmlns)
|
|
|
|
# HEADERS
|
|
header = ElementFactory('Header', namespace=self.ns_uri)
|
|
routing = ElementFactory('Routing', namespace=self.ns_uri)
|
|
routing.extend(
|
|
[
|
|
ElementFactory('MessageId', text=num, namespace=self.ns_uri),
|
|
ElementFactory('FlowType', text=flow_type, namespace=self.ns_uri),
|
|
]
|
|
)
|
|
header.append(routing)
|
|
|
|
# BODY
|
|
body = ElementFactory('Body', namespace=self.ns_uri)
|
|
content = ElementFactory('Content', namespace=self.ns_uri)
|
|
aller = ElementFactory('Aller', namespace=self.ns_uri)
|
|
|
|
code = self.code
|
|
if 'AEC-LA' in self.flow_type:
|
|
aller.append(ElementFactory('NumeroDemarche', text='EtatCivil'))
|
|
code = 'ActeEtatCivil-XML'
|
|
|
|
aller.extend(
|
|
[
|
|
self.teledemarche_xml(num, date, platform_id),
|
|
self.routage_xml(zip_code),
|
|
self.document_xml(code, form_files),
|
|
]
|
|
)
|
|
|
|
for f in self.piecejointe_xml(attached_files):
|
|
if f:
|
|
aller.append(f)
|
|
|
|
content.append(aller)
|
|
body.append(content)
|
|
|
|
root.extend([header, body])
|
|
|
|
self.xml = root
|
|
|
|
|
|
class Description(Common):
|
|
"""Class describing a -ent-.xml file"""
|
|
|
|
def __init__(
|
|
self,
|
|
flow_type,
|
|
num,
|
|
zip_code,
|
|
date=None,
|
|
platform_id='1',
|
|
form_files=None,
|
|
attached_files=None,
|
|
step=None,
|
|
doc=None,
|
|
user_comment=None,
|
|
):
|
|
form_files = form_files or []
|
|
attached_files = attached_files or []
|
|
super().__init__(flow_type, num, zip_code, date, platform_id, form_files, attached_files, doc=doc)
|
|
self.step = step
|
|
|
|
root = ElementFactory('EnteteMetierEnveloppe', attrib={'xmlns': self.xmlns})
|
|
|
|
code = self.code
|
|
if 'AEC-LA' in self.flow_type:
|
|
root.append(ElementFactory('NumeroDemarche', text='EtatCivil'))
|
|
code = 'ActeEtatCivil-XML'
|
|
|
|
root.extend(
|
|
[
|
|
self.teledemarche_xml(num, date, platform_id),
|
|
self.routage_xml(zip_code),
|
|
self.document_xml(code, form_files),
|
|
]
|
|
)
|
|
|
|
for f in self.piecejointe_xml(attached_files):
|
|
root.append(f)
|
|
|
|
# add Logitud Siecle specific tag
|
|
if user_comment:
|
|
local_access = ElementFactory('LocalAccess')
|
|
local_access.append(ElementFactory('CommentaireUsager', text=user_comment))
|
|
root.append(local_access)
|
|
|
|
self.xml = root
|
|
|
|
@property
|
|
def filename(self):
|
|
if 'AEC-LA' in self.flow_type:
|
|
return '%s-EtatCivil-ent-%s.xml' % (self.num, self.step)
|
|
return '%s-ent-.xml' % self.code
|
|
|
|
|
|
class Data(MDELBase):
|
|
"""Class describing a -doc.xml file"""
|
|
|
|
def __init__(self, demand_id, data, step=None):
|
|
self.data = self.data_validator(data)
|
|
self.demand_id = demand_id
|
|
self.step = step
|
|
|
|
@property
|
|
def filename(self):
|
|
if 'AEC-LA' in self.demand_id:
|
|
return '%s-doc-ActeEtatCivil-XML-1-%s.xml' % (
|
|
self.demand_id.replace('AEC-LA', 'EtatCivil'),
|
|
self.step,
|
|
)
|
|
return '%s-doc-.xml' % self.demand_id
|
|
|
|
def save(self, folder):
|
|
return super().save(folder, self.filename)
|
|
|
|
def data_validator(self, data):
|
|
"""Checks if required keys are present"""
|
|
missing_field = set(self.required).difference(set(data.keys()))
|
|
if missing_field:
|
|
raise APIError('field %s is required' % missing_field.pop())
|
|
|
|
return data
|
|
|
|
@property
|
|
def xml(self):
|
|
elements = []
|
|
|
|
for key, path in self.mapping:
|
|
if key in self.data:
|
|
elements.append((path, self.data[key]))
|
|
|
|
root = ElementFactory(self.root_element, **self.root_attributes)
|
|
|
|
for path, value in elements:
|
|
path_splitted = path.split('_')
|
|
root.append(json_to_xml(path_splitted, value, root), allow_new=False)
|
|
|
|
return root
|
|
|
|
|
|
class ILEData(Data):
|
|
mapping = [
|
|
('nom_famille', 'Inscription_Electeur_Noms_NomFamille'),
|
|
('nom_usage', 'Inscription_Electeur_Noms_NomUsage'),
|
|
('prenoms', 'Inscription_Electeur_Prenoms_Prenom'),
|
|
('date_naissance', 'Inscription_Electeur_DateDeNaissance'),
|
|
('lieunaiss_localite', 'Inscription_Electeur_LieuDeNaissance_Localite'),
|
|
('lieunaiss_division_territoriale', 'Inscription_Electeur_LieuDeNaissance_DivisionTerritoriale'),
|
|
('lieunaiss_code_postal', 'Inscription_Electeur_LieuDeNaissance_CodePostal'),
|
|
('lieunaiss_pays_raw', 'Inscription_Electeur_LieuDeNaissance_Pays'),
|
|
('sexe_raw', 'Inscription_Electeur_Sexe'),
|
|
('nationalite_raw', 'Inscription_Electeur_Nationalite'),
|
|
('adresse_complement', 'Inscription_Electeur_AdresseDeLElecteur_Complement'),
|
|
('adresse_num_voie', 'Inscription_Electeur_AdresseDeLElecteur_NumeroVoie'),
|
|
('adresse_type_voie', 'Inscription_Electeur_AdresseDeLElecteur_TypeVoie'),
|
|
('adresse_nom_voie', 'Inscription_Electeur_AdresseDeLElecteur_NomVoie'),
|
|
('adresse_code_postal', 'Inscription_Electeur_AdresseDeLElecteur_CodePostal'),
|
|
('adresse_localite', 'Inscription_Electeur_AdresseDeLElecteur_Localite'),
|
|
('adresse_division_territoriale', 'Inscription_Electeur_AdresseDeLElecteur_DivisionTerritoriale'),
|
|
('contact_code', 'Inscription_Electeur_MethodeDeContact_CanalCode'),
|
|
('contact_uri', 'Inscription_Electeur_MethodeDeContact_URI'),
|
|
('inscription_liste_type_raw', 'Inscription_TypeDeListe'),
|
|
('inscription_localite', 'Inscription_CommuneDInscription_Localite'),
|
|
('inscription_division_territoriale', 'Inscription_CommuneDInscription_DivisionTerritoriale'),
|
|
('inscription_code_insee', 'Inscription_CommuneDInscription_CodeInsee'),
|
|
('inscription_date', 'DateDInscription'),
|
|
('inscription_type', 'TypeDInscription'),
|
|
('anterieur_situation_raw', 'SituationElectoraleAnterieure_SituationDeLElecteur'),
|
|
(
|
|
'anterieur_consulat',
|
|
'SituationElectoraleAnterieure_ConsulatDePrecedenteInscription_AmbassadeOuPosteConsulaire',
|
|
),
|
|
('anterieur_consulat_pays_raw', 'SituationElectoraleAnterieure_ConsulatDePrecedenteInscription_Pays'),
|
|
('anterieur_localite', 'SituationElectoraleAnterieure_CommuneDePrecedenteInscription_Localite'),
|
|
('anterieur_ue_localite', 'SituationElectoraleAnterieure_PaysUeDerniereInscription_Localite'),
|
|
(
|
|
'anterieur_division_territoriale',
|
|
'SituationElectoraleAnterieure_CommuneDePrecedenteInscription_DivisionTerritoriale',
|
|
),
|
|
(
|
|
'anterieur_ue_division_territoriale',
|
|
'SituationElectoraleAnterieure_PaysUeDerniereInscription_DivisionTerritoriale',
|
|
),
|
|
('anterieur_ue_localite_pays_raw', 'SituationElectoraleAnterieure_PaysUeDerniereInscription_Pays'),
|
|
]
|
|
|
|
required = [
|
|
'nom_famille',
|
|
'nom_usage',
|
|
'prenoms',
|
|
'date_naissance',
|
|
'lieunaiss_localite',
|
|
'lieunaiss_code_postal',
|
|
'lieunaiss_pays_raw',
|
|
'sexe_raw',
|
|
'nationalite_raw',
|
|
'adresse_num_voie',
|
|
'adresse_nom_voie',
|
|
'adresse_code_postal',
|
|
'adresse_localite',
|
|
'inscription_liste_type',
|
|
'anterieur_situation_raw',
|
|
]
|
|
|
|
def __init__(self, demand_id, data):
|
|
self.root_element = 'AvisDInscription'
|
|
self.root_attributes = {}
|
|
# only possible value : vol as 'Inscription Volontaire'
|
|
data['inscription_type'] = 'vol'
|
|
|
|
super().__init__(demand_id, data)
|
|
|
|
|
|
class AECData(Data):
|
|
mapping = [
|
|
('aec_type_raw', 'DemandeActe_TypeActe_Code'),
|
|
('aec_type', 'DemandeActe_TypeActe_Libelle'),
|
|
('aec_nature_raw', 'DemandeActe_NatureActe_Code'),
|
|
('aec_nature', 'DemandeActe_NatureActe_Libelle'),
|
|
('date_acte', 'DemandeActe_DateActe'),
|
|
('code_postal', 'DemandeActe_LieuActe_CodePostal'),
|
|
('ville', 'DemandeActe_LieuActe_Ville'),
|
|
('code_insee', 'DemandeActe_LieuActe_CodeINSEE'),
|
|
('nombre_exemplaires', 'DemandeActe_NombreExemplaires'),
|
|
('motif_demande', 'DemandeActe_MotifDemande_Libelle'),
|
|
('motif_demande_raw', 'DemandeActe_MotifDemande_Commentaire'),
|
|
('qualite_demandeur_raw', 'DemandeActe_Demandeur_QualiteDemandeur_Code'),
|
|
('qualite_demandeur', 'DemandeActe_Demandeur_QualiteDemandeur_Libelle'),
|
|
('demandeur_civilite', 'DemandeActe_Demandeur_Civilite'),
|
|
('demandeur_nom', 'DemandeActe_Demandeur_Nom'),
|
|
('demandeur_prenom', 'DemandeActe_Demandeur_Prenom'),
|
|
('demandeur_adresse_etage', 'DemandeActe_Demandeur_AdresseFrancaise_Etage'),
|
|
('demandeur_adresse_batiment', 'DemandeActe_Demandeur_AdresseFrancaise_Batiment'),
|
|
('demandeur_adresse_voie', 'DemandeActe_Demandeur_AdresseFrancaise_Voie'),
|
|
('demandeur_adresse_boite_postal', 'DemandeActe_Demandeur_AdresseFrancaise_BoitePostale'),
|
|
('demandeur_adresse_code_postal', 'DemandeActe_Demandeur_AdresseFrancaise_CodePostal'),
|
|
('demandeur_adresse_ville', 'DemandeActe_Demandeur_AdresseFrancaise_Ville'),
|
|
('demandeur_adresse_adresse', 'DemandeActe_Demandeur_AdresseEtrangere_Adresse'),
|
|
('demandeur_adresse_etrangere', 'DemandeActe_Demandeur_AdresseEtrangere_Adresse'),
|
|
('demandeur_adresse_etrangere_pays_raw', 'DemandeActe_Demandeur_AdresseEtrangere_Pays'),
|
|
('demandeur_email', 'DemandeActe_Demandeur_Courriel'),
|
|
('demandeur_telephone', 'DemandeActe_Demandeur_Telephone'),
|
|
('titulaire_civilite', 'DemandeActe_Titulaire_Civilite'),
|
|
('titulaire_nom', 'DemandeActe_Titulaire_NomNaissance'),
|
|
('titulaire_prenoms', 'DemandeActe_Titulaire_Prenoms'),
|
|
('titulaire_pere_nom', 'DemandeActe_Titulaire_Filiation_Pere_Nom'),
|
|
('titulaire_pere_prenoms', 'DemandeActe_Titulaire_Filiation_Pere_Prenoms'),
|
|
('titulaire_mere_nom', 'DemandeActe_Titulaire_Filiation_Mere_Nom'),
|
|
('titulaire_mere_prenoms', 'DemandeActe_Titulaire_Filiation_Mere_Prenoms'),
|
|
('titulaire_naiss_date', 'DemandeActe_Titulaire_DateDeNaissance'),
|
|
('titulaire_naiss_ville', 'DemandeActe_Titulaire_LieuDeNaissance'),
|
|
('titulaire_naiss_departement_raw', 'DemandeActe_Titulaire_DepartementDeNaissance'),
|
|
('titulaire_naiss_pays_raw', 'DemandeActe_Titulaire_PaysDeNaissance'),
|
|
('titulaire_nationalite_raw', 'DemandeActe_Titulaire_Nationalite'),
|
|
('titulaire2_civilite', 'DemandeActe_Titulaire2_Civilite'),
|
|
('titulaire2_nom', 'DemandeActe_Titulaire2_NomNaissance'),
|
|
('titulaire2_prenoms', 'DemandeActe_Titulaire2_Prenoms'),
|
|
('titulaire2_pere_nom', 'DemandeActe_Titulaire2_Filiation_Pere_Nom'),
|
|
('titulaire2_pere_prenoms', 'DemandeActe_Titulaire2_Filiation_Pere_Prenoms'),
|
|
('titulaire2_mere_nom', 'DemandeActe_Titulaire2_Filiation_Mere_Nom'),
|
|
('titulaire2_mere_prenoms', 'DemandeActe_Titulaire2_Filiation_Mere_Prenoms'),
|
|
('titulaire2_naiss_date', 'DemandeActe_Titulaire2_DateDeNaissance'),
|
|
('titulaire2_naiss_ville', 'DemandeActe_Titulaire2_LieuDeNaissance'),
|
|
('titulaire2_naiss_departement_raw', 'DemandeActe_Titulaire2_DepartementDeNaissance'),
|
|
('titulaire2_naiss_pays_raw', 'DemandeActe_Titulaire2_PaysDeNaissance'),
|
|
('titulaire2_nationalite_raw', 'DemandeActe_Titulaire2_Nationalite'),
|
|
]
|
|
|
|
required = [
|
|
'aec_nature',
|
|
'aec_nature_raw',
|
|
'aec_type',
|
|
'aec_type_raw',
|
|
'code_insee',
|
|
'code_postal',
|
|
'demandeur_civilite',
|
|
'demandeur_email',
|
|
'demandeur_nom',
|
|
'demandeur_prenom',
|
|
'nombre_exemplaires',
|
|
'motif_demande',
|
|
'motif_demande_raw',
|
|
'qualite_demandeur',
|
|
'qualite_demandeur_raw',
|
|
'titulaire_civilite',
|
|
'titulaire_mere_nom',
|
|
'titulaire_mere_prenoms',
|
|
'titulaire_nom',
|
|
'titulaire_pere_nom',
|
|
'titulaire_pere_prenoms',
|
|
'titulaire_prenoms',
|
|
]
|
|
|
|
def __init__(self, demand_id, data, demand_num):
|
|
self.root_element = 'EnveloppeMetierType'
|
|
self.root_attributes = {'canal_utilise': '0'}
|
|
|
|
super().__init__(demand_id, data, demand_num)
|
|
|
|
|
|
def json_to_xml(path, value, parent):
|
|
if isinstance(path, list) and len(path) > 1:
|
|
parent_child = parent.find(path[0])
|
|
if parent_child is not None:
|
|
element = parent_child
|
|
path.pop(0)
|
|
else:
|
|
element = ElementFactory(path.pop(0))
|
|
|
|
element.append(json_to_xml(path, value, element), allow_new=False)
|
|
else:
|
|
element = ElementFactory(path[0], text=value)
|
|
|
|
return element
|