passerelle/passerelle/apps/esabora/models.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

199 lines
6.8 KiB
Python
Raw Normal View History

import urllib.parse
import requests
from django.db import models
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from passerelle.base.models import BaseResource, HTTPResource
from passerelle.utils.api import endpoint
from passerelle.utils.conversion import exception_to_text
from passerelle.utils.jsonresponse import APIError
MULT_SCHEMA = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'Multi-criterion search',
'unflatten': True,
'description': '',
'type': 'object',
'required': ['search_name', 'criterions'],
'properties': {
'search_name': {
'description': _('Search name, as specified in the Esabora webservice'),
'type': 'string',
'examples': ['WS_ETAT_DOSSIER_SAS'],
},
'criterions': {
'description': _('A mapping of criterions'),
'type': 'object',
'examples': [{'SAS_Référence': 'HISTO0001'}],
},
},
}
DO_TREATMENT_SCHEMA = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'unflatten': True,
'title': 'Treatment creation',
'description': 'Additional fields in the payload will be transmitted to the Esabora service.',
'type': 'object',
'required': ['treatment_name'],
'properties': {
'endpoint': {
'description': _('Endpoint name, such as modbdd or addevt. Defaults to modbdd'),
'type': 'string',
'examples': ['modbdd'],
},
'treatment_name': {
'description': _('Treatment name, as specified in the Esabora service'),
'type': 'string',
'examples': ['IMPORT HISTOLOGE'],
},
},
}
class Esabora(BaseResource, HTTPResource):
service_url = models.URLField(
blank=False,
verbose_name=_('Service URL'),
help_text=_('Base Web Service URL, such as https://example.domain/ws/rest/'),
)
api_key = models.CharField(max_length=256, default='', blank=True, verbose_name=_('API key'))
category = _('Business Process Connectors')
class Meta:
verbose_name = _('Esabora')
def post(self, path, payload, **kwargs):
url = urllib.parse.urljoin(self.service_url, path)
headers = {'Authorization': f'Bearer {self.api_key}'}
try:
response = self.requests.post(url, json=payload, headers=headers, timeout=5, **kwargs)
except requests.RequestException as e:
raise APIError(
'Esabora platform "%s" connection error: %s' % (self.service_url, exception_to_text(e)),
log_error=True,
data={
'code': 'connection-error',
'service_url': self.service_url,
'error': str(e),
},
)
try:
data = response.json()
except requests.JSONDecodeError as e:
raise APIError(
'Esabora platform "%s" invalid JSON response: %s' % (self.service_url, exception_to_text(e)),
log_error=True,
data={
'status_code': response.status_code,
},
)
if not response.ok:
raise APIError(
'Esabora platform "%s" answered with HTTP error' % (self.service_url),
log_error=True,
data={
'status_code': response.status_code,
'content': data,
},
)
return data
@endpoint(
name='do-search',
description=_('Multi-criterion search'),
perm='can_access',
methods=['post'],
post={'request_body': {'schema': {'application/json': MULT_SCHEMA}}},
json_schema_response={},
)
def do_search(self, request, post_data):
payload = {
'searchName': post_data['search_name'],
'criterionList': [
{'criterionName': name, 'criterionValueList': [value]}
for name, value in post_data['criterions'].items()
],
}
data = self.post('mult/', payload, params={'task': 'doSearch'})
columns = {slugify(c).replace('-', '_'): c for c in data['columnList']}
keys = {slugify(c).replace('-', '_'): c for c in data['keyList']}
cleaned_data = {
'meta': {
'nbResults': data['nbResults'],
'searchId': data['searchId'],
'columns_name': columns,
'keys_name': keys,
},
'data': [
esabora_row_to_object(list(columns.keys()), list(keys.keys()), row) for row in data['rowList']
],
}
return cleaned_data
@endpoint(
name='do-treatment',
description=_('Create a new treatment'),
perm='can_access',
methods=['post'],
post={'request_body': {'schema': {'application/json': DO_TREATMENT_SCHEMA}}},
json_schema_response={},
)
def do_treatment(self, request, post_data):
endpoint = post_data.pop('endpoint', None) or 'modbdd'
payload = get_treatment_payload(post_data)
data = self.post(f'{endpoint}/', payload, params={'task': 'doTreatment'})
keys = [slugify(c).replace('-', '_') for c in data['keyList']]
cleaned_data = esabora_row_to_object([], keys, data)
cleaned_data['action'] = data['action']
return cleaned_data
def esabora_row_to_object(columns, key_list, row):
key_data_list = row.get('keyDataList', [])
all_values = row.get('columnDataList', []) + key_data_list
obj = dict(zip(columns + key_list, all_values))
obj['text'] = all_values[0] if all_values else None
if key_data_list:
obj['id'] = key_data_list[0]
return obj
def get_treatment_payload(post_data):
payload = {'treatmentName': post_data.pop('treatment_name'), 'fieldList': []}
for key, value in post_data.items():
field = {
'fieldName': key,
}
if isinstance(value, list):
populate_document_field(field, value)
elif isinstance(value, dict):
populate_document_field(field, [value])
else:
field['fieldValue'] = value
payload['fieldList'].append(field)
return payload
def populate_document_field(field, value):
field['fieldDocumentUpdate'] = 1
field['fieldValue'] = []
# one or more files were sent,
# let's compute their size in bytes based on the base64 payload (without = padding)
# cf https://stackoverflow.com/a/45401395/2844093
for f in value:
if not f:
continue
size = (len(f['content']) * 3) / 4 - f['content'].count('=', -2)
field['fieldValue'].append(
{'documentContent': f['content'], 'documentName': f['filename'], 'documentSize': size}
)