230 lines
9.8 KiB
Python
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)}
|