330 lines
13 KiB
Python
330 lines
13 KiB
Python
# passerelle - uniform access to multiple data sources and services
|
|
# Copyright (C) 2019 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 binascii
|
|
|
|
from django.db import models
|
|
from django.utils import dateformat, dateparse
|
|
from django.utils.encoding import force_text
|
|
from django.utils.six.moves import urllib
|
|
from django.utils.translation import ugettext_lazy as _
|
|
import lxml.etree
|
|
from zeep import helpers
|
|
from zeep.exceptions import Fault
|
|
|
|
from passerelle.base.models import BaseResource
|
|
from passerelle.utils.api import endpoint
|
|
from passerelle.utils.jsonresponse import APIError
|
|
from . import schemas
|
|
|
|
|
|
DATE_FORMAT = 'l d F Y, G:i'
|
|
|
|
|
|
def process_response(demande_number):
|
|
if demande_number.startswith('DIT') or demande_number.startswith('DPR'):
|
|
return {'data': {'demande_number': demande_number}}
|
|
raise APIError(demande_number)
|
|
|
|
|
|
class ATALConnector(BaseResource):
|
|
base_soap_url = models.URLField(
|
|
max_length=400, verbose_name=_('Base SOAP endpoint'), help_text=_('URL of the base SOAP endpoint')
|
|
)
|
|
category = _('Business Process Connectors')
|
|
|
|
class Meta:
|
|
verbose_name = _('ATAL connector')
|
|
|
|
DEMANDE_NUMBER_PARAM = {'description': _('Demande number'), 'example_value': 'DIT18050001'}
|
|
|
|
def _soap_call(self, wsdl, method, **kwargs):
|
|
wsdl_url = urllib.parse.urljoin(self.base_soap_url, '%s?wsdl' % wsdl)
|
|
client = self.soap_client(wsdl_url=wsdl_url)
|
|
try:
|
|
return getattr(client.service, method)(**kwargs)
|
|
except Fault as e:
|
|
raise APIError(force_text(e))
|
|
|
|
def _basic_ref(self, wsdl, method):
|
|
soap_res = self._soap_call(wsdl=wsdl, method=method)
|
|
res = []
|
|
for elem in soap_res:
|
|
res.append({'id': elem.code, 'text': elem.libelle})
|
|
return {'data': res}
|
|
|
|
def _xml_ref(self, wsdl, method, root_elem):
|
|
soap_res = self._soap_call(wsdl=wsdl, method=method)
|
|
tree = lxml.etree.fromstring(soap_res.encode('utf-8')).getroottree()
|
|
types = tree.xpath('//%s' % root_elem)[0]
|
|
res = []
|
|
for type_elem in types.getchildren():
|
|
res.append({'id': type_elem.get('id'), 'text': type_elem.get('label')})
|
|
return {'data': res}
|
|
|
|
@endpoint(methods=['get'], perm='can_access', name='get-thematique')
|
|
def get_thematique(self, request):
|
|
return self._xml_ref('DemandeService', 'getThematiqueATAL', 'thematiques')
|
|
|
|
@endpoint(methods=['get'], perm='can_access', name='get-type-activite')
|
|
def get_type_activite(self, request):
|
|
return self._basic_ref('VilleAgileService', 'getTypeActivite')
|
|
|
|
@endpoint(methods=['get'], perm='can_access', name='get-type-de-voie')
|
|
def get_type_de_voie(self, request):
|
|
return self._basic_ref('VilleAgileService', 'getTypeDeVoie')
|
|
|
|
@endpoint(methods=['get'], perm='can_access', name='get-types-equipement')
|
|
def get_types_equipement(self, request):
|
|
return self._xml_ref('VilleAgileService', 'getTypesEquipement', 'types')
|
|
|
|
@endpoint(
|
|
perm='can_access',
|
|
name='insert-action-comment',
|
|
post={
|
|
'description': _('Insert action comment'),
|
|
'request_body': {'schema': {'application/json': schemas.INSERT_ACTION_COMMENT}},
|
|
},
|
|
)
|
|
def insert_action_comment(self, request, post_data):
|
|
demande_number = self._soap_call(
|
|
wsdl='DemandeService',
|
|
method='insertActionComment',
|
|
numeroDemande=post_data['numero_demande'],
|
|
commentaire=post_data['commentaire'],
|
|
)
|
|
return process_response(demande_number)
|
|
|
|
@endpoint(
|
|
perm='can_access',
|
|
name='insert-demande-complet-by-type',
|
|
post={
|
|
'description': _('Insert demande complet by type'),
|
|
'request_body': {'schema': {'application/json': schemas.INSERT_DEMANDE_COMPLET_BY_TYPE}},
|
|
},
|
|
)
|
|
def insert_demande_complet_by_type(self, request, post_data):
|
|
data = {}
|
|
for recv, send in [
|
|
('type_demande', 'typeDemande'),
|
|
('code_service_demandeur', 'codeServiceDemandeur'),
|
|
('date_saisie', 'dateSaisie'),
|
|
('date_demande', 'dateDemande'),
|
|
('date_souhaite', 'dateSouhaitee'),
|
|
('date_butoir', 'dateButoir'),
|
|
('contact_civilite', 'contactCivilite'),
|
|
('contact_nom', 'contactNom'),
|
|
('contact_prenom', 'contactPrenom'),
|
|
('contact_tel', 'contactTelephone'),
|
|
('contact_mobile', 'contactMobile'),
|
|
('contact_email', 'contactCourriel'),
|
|
('contact_info_compl', 'contactInfoCompl'),
|
|
('demande_type_support', 'demandeTypeDeSupport'),
|
|
('contact_adresse', 'contactAdresse'),
|
|
('contact_adresse_compl', 'contactAdresseCompl'),
|
|
('contact_code_postal', 'contactCodePostal'),
|
|
('contact_ville', 'contactVille'),
|
|
('contact_organisme', 'contactOrganisme'),
|
|
('contact_titre', 'contactTitre'),
|
|
('code_equipement', 'codeEquipement'),
|
|
('code_mairie_equipement', 'codeMairieEquipement'),
|
|
('code_sig_equipement', 'codeSIGEquipement'),
|
|
('code_collectivite_equipement', 'codeCollectiviteEquipement'),
|
|
('code_quartier_equipement', 'codeQuartierEquipement'),
|
|
('code_type_equipement', 'codeTypeEquipement'),
|
|
('demande_lieu', 'demandeLieu'),
|
|
('coord_x', 'coordX'),
|
|
('coord_y', 'coordY'),
|
|
('demande_priorite', 'demandePriorite'),
|
|
('demande_objet', 'demandeObjet'),
|
|
('demande_description', 'demandeDescription'),
|
|
('demande_commentaire', 'demandeCommentaire'),
|
|
('demande_mots_cles', 'demandeMotsCles'),
|
|
('code_thematique', 'codeThematiqueATAL'),
|
|
('code_priorite', 'codePrioriteATAL'),
|
|
('demande_thematique', 'demandeThematique'),
|
|
('code_projet', 'codeProjetATAL'),
|
|
]:
|
|
if recv in post_data:
|
|
data[send] = post_data[recv]
|
|
|
|
demande_number = self._soap_call(wsdl='DemandeService', method='insertDemandeCompletByType', **data)
|
|
return process_response(demande_number)
|
|
|
|
@endpoint(
|
|
methods=['get'],
|
|
perm='can_access',
|
|
example_pattern='{demande_number}/',
|
|
pattern='^(?P<demande_number>\w+)/$',
|
|
name='retrieve-details-demande',
|
|
parameters={'demande_number': DEMANDE_NUMBER_PARAM},
|
|
)
|
|
def retrieve_details_demande(self, request, demande_number):
|
|
soap_res = self._soap_call(
|
|
wsdl='DemandeService', method='retrieveDetailsDemande', demandeNumberParam=demande_number
|
|
)
|
|
return {'data': helpers.serialize_object(soap_res)}
|
|
|
|
@endpoint(
|
|
methods=['get'],
|
|
perm='can_access',
|
|
example_pattern='{demande_number}/',
|
|
pattern='^(?P<demande_number>\w+)/$',
|
|
name='retrieve-etat-travaux',
|
|
parameters={'demande_number': DEMANDE_NUMBER_PARAM},
|
|
)
|
|
def retrieve_etat_travaux(self, request, demande_number):
|
|
soap_res = self._soap_call(wsdl='DemandeService', method='retrieveEtatTravaux', numero=demande_number)
|
|
return {'data': helpers.serialize_object(soap_res)}
|
|
|
|
@endpoint(
|
|
methods=['get'],
|
|
perm='can_access',
|
|
example_pattern='{demande_number}/',
|
|
pattern='^(?P<demande_number>\w+)/$',
|
|
parameters={
|
|
'demande_number': DEMANDE_NUMBER_PARAM,
|
|
'full': {
|
|
'description': _('Full'),
|
|
'example_value': 'true',
|
|
'type': 'bool',
|
|
},
|
|
},
|
|
)
|
|
def infos(self, request, demande_number, full=False):
|
|
demand_details = helpers.serialize_object(
|
|
self._soap_call(
|
|
wsdl='DemandeService', method='retrieveDetailsDemande', demandeNumberParam=demande_number
|
|
)
|
|
)
|
|
if not demand_details:
|
|
raise APIError('Could not get a status')
|
|
|
|
status = (demand_details.get('etatDemande') or {}).get('description')
|
|
if not status:
|
|
raise APIError('Could not get a status')
|
|
|
|
responses = (demand_details.get('reponses') or {}).get('Reponse') or []
|
|
works_comments = []
|
|
if responses:
|
|
for response in responses:
|
|
comment = {'text': response.get('commentaires'), 'date': None}
|
|
if 'dateReponse' in response:
|
|
comment['date'] = dateformat.format(response['dateReponse'], DATE_FORMAT)
|
|
works_comments.append(comment)
|
|
|
|
works_comment = {'text': None, 'date': None}
|
|
if works_comments:
|
|
works_comment = works_comments[-1]
|
|
|
|
data = {
|
|
'status': status,
|
|
'works_comment': works_comment,
|
|
'demand_details': None,
|
|
'works_comments': [],
|
|
}
|
|
if full:
|
|
data['demand_details'] = demand_details
|
|
data['works_comments'] = works_comments
|
|
|
|
if status not in ('PRISE EN COMPTE', 'ARCHIVEE'):
|
|
return {'data': data}
|
|
|
|
works_status = helpers.serialize_object(
|
|
self._soap_call(wsdl='DemandeService', method='retrieveEtatTravaux', numero=demande_number)
|
|
)
|
|
status = works_status.get('libelle')
|
|
if not status:
|
|
raise APIError('Could not get a status')
|
|
|
|
data['status'] = status
|
|
data['demand_comment'] = works_status.get('commentaires', '')
|
|
data['works_status'] = None
|
|
|
|
if full:
|
|
data['works_status'] = works_status
|
|
|
|
return {'data': data}
|
|
|
|
@endpoint(
|
|
perm='can_access',
|
|
post={
|
|
'description': _('Upload a file'),
|
|
'request_body': {'schema': {'application/json': schemas.UPLOAD}},
|
|
},
|
|
)
|
|
def upload(self, request, post_data):
|
|
try:
|
|
content = base64.b64decode(post_data['file']['content'])
|
|
except (TypeError, binascii.Error):
|
|
raise APIError('Invalid base64 string')
|
|
|
|
data = {
|
|
'donneesFichier': content,
|
|
'numeroDemande': post_data['numero_demande'],
|
|
'nomFichier': post_data['nom_fichier'],
|
|
}
|
|
self._soap_call(wsdl='ChargementPiecesJointesService', method='upload', **data)
|
|
return {}
|
|
|
|
@endpoint(
|
|
methods=['get'],
|
|
perm='can_access',
|
|
example_pattern='{demande_number}/',
|
|
pattern='^(?P<demande_number>\w+)/$',
|
|
name='new-comments',
|
|
parameters={
|
|
'demande_number': DEMANDE_NUMBER_PARAM,
|
|
},
|
|
)
|
|
def new_comments(self, request, demande_number, last_datetime=None):
|
|
def issup(datetime1, datetime2):
|
|
if datetime1.tzinfo is None or datetime2.tzinfo is None:
|
|
datetime1 = datetime1.replace(tzinfo=None)
|
|
datetime2 = datetime2.replace(tzinfo=None)
|
|
return datetime1 > datetime2
|
|
|
|
if last_datetime is not None:
|
|
last_datetime = dateparse.parse_datetime(last_datetime)
|
|
if last_datetime is None:
|
|
raise APIError('Wrong datetime format', http_status=400)
|
|
|
|
demand_details = helpers.serialize_object(
|
|
self._soap_call(
|
|
wsdl='DemandeService', method='retrieveDetailsDemande', demandeNumberParam=demande_number
|
|
)
|
|
)
|
|
if not demand_details:
|
|
raise APIError('Could not get comments')
|
|
|
|
new_comments, all_comments, last_date = [], [], None
|
|
responses = (demand_details.get('reponses') or {}).get('Reponse') or []
|
|
for response in responses:
|
|
comment = {'text': response.get('commentaires'), 'date': None, 'date_raw': None}
|
|
dateobj = None
|
|
if 'dateReponse' in response:
|
|
dateobj = response['dateReponse']
|
|
comment['date'] = dateformat.format(dateobj, DATE_FORMAT)
|
|
comment['date_raw'] = dateformat.format(dateobj, 'c')
|
|
last_date = comment['date_raw']
|
|
|
|
all_comments.append(comment)
|
|
if dateobj and issup(dateobj, last_datetime) or last_datetime is None:
|
|
if comment not in new_comments:
|
|
new_comments.append(comment)
|
|
return {'data': {'new_comments': new_comments, 'all_comments': all_comments, 'last_date': last_date}}
|