passerelle/passerelle/contrib/grenoble_gru/models.py

230 lines
9.8 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (C) 2018 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/>.
from lxml import etree
from django.core.cache import cache
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import dateparse
from django.utils.http import urlencode
from django.utils.six.moves.urllib import parse as urlparse
from passerelle.base.models import BaseResource
from passerelle.compat import json_loads
from passerelle.utils.api import endpoint
from passerelle.utils.jsonresponse import APIError
from passerelle.views import WrongParameter
RESPONSE_CODES = {
'01': _('Success'),
'02': _('Remote service error'),
'10': _('Authentication failed'),
'11': _('Collectivity not defined'),
'20': _('Invalid input format'),
'21': _('Required field not provided'),
'22': _('Unexpected value (referentials)'),
'23': _('Demand already exists')
}
def xml2dict(element):
data = {}
for attr in element.keys():
data[attr] = element.get(attr)
if element.text:
return element.text
for child in element:
data[child.tag] = xml2dict(child)
if data:
return data
def check_value(data, field_name, values):
value = data[field_name]
if value not in values:
raise ValueError('%s must be one of %s' % (field_name, values))
return value
class GrenobleGRU(BaseResource):
base_url = models.URLField(max_length=256, blank=False,
verbose_name=_('Base URL'),
help_text=_('Grenoble GRU API base URL'))
username = models.CharField(max_length=128, verbose_name=_('Username'))
password = models.CharField(max_length=128, verbose_name=_('Password'))
category = _('Business Process Connectors')
class Meta:
verbose_name = "Grenoble - Gestion des signalements"
@classmethod
def get_verbose_name(cls):
return cls._meta.verbose_name
def request(self, endpoint, payload={}):
payload['uti_identifiant'] = self.username
payload['uti_motdepasse'] = self.password
url = urlparse.urljoin(self.base_url, endpoint)
return self.requests.post(url, data=payload)
def check_status(self):
response = self.request('ws_typologie_demande.php')
response.raise_for_status()
def build_gru_params(self, data):
payload = {
'id': data['application_id'],
# applicant informations
'dem_nom': data['applicant_lastname'],
'dem_prenom': data['applicant_firstname'],
'dem_tel': data['applicant_phone'],
'dem_mail': data['applicant_email'],
'dem_reponse': 1 if data.get('applicant_requires_reply') is True else 0,
'dem_moyen_contact': check_value(data, 'applicant_contact_mode', self.types('//modeContact', True)),
'dem_nature': check_value(data, 'applicant_status', self.types('//natureContact', True)),
# intervention informations
'int_type_adresse': check_value(data, 'intervention_address_type', self.types('//typeAdresse', True)),
'int_numerovoie': data['intervention_street_number'],
'int_libellevoie': data['intervention_street_name'],
'int_insee': data['intervention_address_insee'],
'int_secteur': check_value(data, 'intervention_sector', self.types('//secteur', True)),
'int_type_numero': check_value(data, 'intervention_number_type', self.types('//typeNumero', True)),
'int_date_demande': dateparse.parse_datetime(data['intervention_datetime']).strftime('%d%m%Y %H:%M'),
# comments
'obs_demande_urgente': 1 if data.get('urgent_demand') is True else 0,
'obs_type_dysfonctionnement': check_value(
data, 'dysfonction_type', self.types('//typeDysfonctionnement', True)),
'obs_motif': check_value(data, 'intervention_reason', self.types('//motif', True)),
'obs_description_probleme': data.get('comment_description', ''),
}
if data['intervention_reason'] == '24':
# code for reason 'Autre' in which case it should be specified
payload['obs_motifautre'] = data.get('intervention_custom_reason', '')
if 'intervention_free_address' in data:
payload['int_adresse_manuelle'] = data['intervention_free_address']
if 'applicant_free_address' in data:
payload['dem_adresse_manuelle'] = data['applicant_free_address']
return payload
def types(self, path, as_list=False):
cache_key = 'grenoble-gru-%s' % self.id
xml_content = cache.get(cache_key)
if not xml_content:
xml_content = self.request('ws_typologie_demande.php').content
try:
root = etree.fromstring(xml_content)
except etree.XMLSyntaxError as e:
raise APIError('Invalid XML returned: %s', e)
cache.set(cache_key, xml_content, 3600)
if as_list:
return [el.find('identifiant').text for el in root.xpath(path)]
return {
'data': [
{
'id': el.find('identifiant').text,
'text': el.find('libelle').text
} for el in root.xpath(path)
]
}
@endpoint(name='contact-modes', perm='can_access', description=_('Lists contact modes'))
def contact_modes(self, request, *args, **kwargs):
return self.types('//modeContact')
@endpoint(name='contact-types', perm='can_access', description=_('Lists contact types'))
def contact_types(self, request, *args, **kwargs):
return self.types('//natureContact')
@endpoint(name='sectors', perm='can_access', description=_('Lists sectors'))
def sectors(self, request, *args, **kwargs):
return self.types('//secteur')
@endpoint(name='address-types', perm='can_access', description=_('Lists address types'))
def address_types(self, request, *args, **kwargs):
return self.types('//typeAdresse')
@endpoint(name='number-types', perm='can_access', description=_('Lists number types'))
def number_types(self, request, *args, **kwargs):
return self.types('//typeNumero')
@endpoint(name='dysfunction-types', perm='can_access', description=_('Lists dysfunction types'))
def dysfunction_types(self, request, *args, **kwargs):
return self.types('//typeDysfonctionnement')
@endpoint(name='intervention-descriptions', perm='can_access', description=_('Lists intervention descriptions'))
def intervention_descriptions(self, request, *args, **kwargs):
return self.types('//descIntervention')
@endpoint(name='intervention-reasons', perm='can_access', description=_('Lists intervention reasons'))
def intervention_reasons(self, request, *args, **kwargs):
return self.types('//motif')
@endpoint(name='create-demand', perm='can_access', methods=['post'], description=_('Create a demand'))
def create_demand(self, request, *args, **kwargs):
try:
payload = self.build_gru_params(json_loads(request.body))
except (KeyError, ValueError) as e:
raise APIError(e)
response = self.request('ws_creation_demande.php', payload)
if response.text != '01':
raise APIError(RESPONSE_CODES.get(response.text, _('Unknown error code (%s)') % response.text))
return {'data': 'Demand successfully created'}
@endpoint(name='demand', perm='can_access', methods=['post'], description=_('Add attachment to a demand'),
pattern=r'(?P<demand_id>[\w-]+)/add-attachment/$',)
def add_attachment_to_demand(self, request, demand_id, **kwargs):
data = json_loads(request.body)
if 'file' not in data:
raise WrongParameter(['file'], [])
file_data = data['file']
if not isinstance(file_data, dict):
raise APIError('file should be a dict')
if 'filename' not in file_data:
raise WrongParameter(['file[filename]'], [])
if 'content_type' not in file_data:
raise WrongParameter(['file[content_type]'], [])
if 'content' not in file_data:
raise WrongParameter(['file[content]'], [])
# file data should be ordered
file_data = (('filetype', file_data['content_type']),
('filename', file_data['filename']),
('filecontent', file_data['content']))
# file parameters should be urlencoded and sent as 'piece_jointe' param
payload = {'dem_tiers_id': demand_id, 'piece_jointe': urlencode(file_data)}
response = self.request('ws_update_demandePJ.php', payload)
if response.content == '01':
return True
return False
@endpoint(name='demand', perm='can_access', description=_('Get demand'),
pattern=r'(?P<demand_id>[\w-]+)/$')
def get_demand(self, request, demand_id, **kwargs):
payload = {'dem_tiers_id': demand_id}
response = self.request('ws_get_demande.php', payload)
try:
demand = etree.fromstring(response.content)
except etree.XMLSyntaxError as e:
raise APIError('Invalid XML returned: %s', e)
return {'data': xml2dict(demand)}