passerelle/passerelle/apps/mdel/mdel.py

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