passerelle/passerelle/apps/adullact_pastell/models.py

235 lines
8.2 KiB
Python

# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2023 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
from urllib import parse as urlparse
import requests
from django.core.exceptions import ValidationError
from django.db import models
from django.http import HttpResponse
from django.utils.translation import gettext_lazy as _
from passerelle.base.models import BaseResource, HTTPResource
from passerelle.utils.api import endpoint
from passerelle.utils.jsonresponse import APIError
FILE_OBJECT_PROPERTIES = {
'title': _('File object'),
'type': 'object',
'properties': {
'filename': {
'type': 'string',
'description': _('Filename'),
},
'content': {
'type': 'string',
'description': _('Content'),
},
'content_type': {
'type': 'string',
'description': _('Content type'),
},
},
'required': ['filename', 'content'],
}
DOCUMENT_CREATION_SCHEMA = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'type': 'object',
'required': ['type'],
'additionalProperties': True,
'properties': {
'type': {'type': 'string', 'description': _('Document type')},
'file_field_name': {
'type': 'string',
'description': _('Document file\'s field name'),
},
'file': FILE_OBJECT_PROPERTIES,
'filename': {
'type': 'string',
'description': _('Filename (takes precedence over filename in "file" object)'),
},
},
}
DOCUMENT_FILE_UPLOAD_SCHEMA = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'type': 'object',
'required': ['file', 'file_field_name'],
'additionalProperties': False,
'properties': {
'filename': {
'type': 'string',
'description': _('Filename (takes precedence over filename in "file" object)'),
},
'file': FILE_OBJECT_PROPERTIES,
'file_field_name': {
'type': 'string',
'description': _('Document file\'s field name'),
},
},
}
class AdullactPastell(BaseResource, HTTPResource):
api_base_url = models.URLField(
max_length=128,
verbose_name=_('API base URL'),
help_text=_('Example: https://pastell.example.com/api/v2/'),
)
token = models.CharField(max_length=128, blank=True, verbose_name=_('API token'))
category = _('Business Process Connectors')
class Meta:
verbose_name = _('Adullact Pastell')
def clean(self, *args, **kwargs):
if not self.token and not self.basic_auth_username:
raise ValidationError(_('API token or authentication username and password should be defined.'))
return super().clean(*args, **kwargs)
def call(self, path, method='get', params=None, **kwargs):
url = urlparse.urljoin(self.api_base_url, path)
if self.token:
kwargs.update({'headers': {'Authorization': 'Bearer: %s' % self.token}, 'auth': None})
try:
response = self.requests.request(url=url, method=method, params=params, **kwargs)
response.raise_for_status()
except (requests.Timeout, requests.RequestException) as e:
raise APIError(str(e))
return response
def check_status(self):
try:
response = self.call('version')
except APIError as e:
raise Exception('Pastell server is down: %s' % e)
return {'data': response.json()}
def upload_file(self, entity_id, document_id, file_field_name, data, **kwargs):
filename = kwargs.get('filename') or data['filename']
file_data = {
'file_content': (
filename,
base64.b64decode(data['content']),
data.get('content_type'),
)
}
return self.call(
'entite/%s/document/%s/file/%s' % (entity_id, document_id, file_field_name),
'post',
files=file_data,
data={'file_name': filename},
)
@endpoint(
description=_('List entities'),
datasource=True,
)
def entities(self, request):
data = []
response = self.call('entite')
for item in response.json():
item['id'] = item['id_e']
item['text'] = item['denomination']
data.append(item)
return {'data': data}
@endpoint(
description=_('List entity documents'),
parameters={'entity_id': {'description': _('Entity ID'), 'example_value': '42'}},
datasource=True,
)
def documents(self, request, entity_id):
if request.GET.get('id'):
response = self.call('entite/%s/document/%s' % (entity_id, request.GET['id']))
return {'data': response.json()}
data = []
response = self.call('entite/%s/document' % entity_id)
for item in response.json():
item['id'] = item['id_d']
item['text'] = item['titre']
data.append(item)
return {'data': data}
@endpoint(
post={
'description': _('Create a document for an entity'),
'request_body': {'schema': {'application/json': DOCUMENT_CREATION_SCHEMA}},
},
name='create-document',
parameters={
'entity_id': {'description': _('Entity ID'), 'example_value': '42'},
},
)
def create_document(self, request, entity_id, post_data):
file_data = post_data.pop('file', None)
file_field_name = post_data.pop('file_field_name', None)
# create document
response = self.call('entite/%s/document' % entity_id, 'post', params=post_data)
document_id = response.json()['id_d']
# update it with other attributes
response = self.call('entite/%s/document/%s' % (entity_id, document_id), 'patch', params=post_data)
# upload file if it's filled
if file_field_name and file_data:
self.upload_file(entity_id, document_id, file_field_name, file_data, **post_data)
return {'data': response.json()}
@endpoint(
post={
'description': _('Upload a file to a document'),
'request_body': {'schema': {'application/json': DOCUMENT_FILE_UPLOAD_SCHEMA}},
},
name='upload-document-file',
parameters={
'entity_id': {'description': _('Entity ID'), 'example_value': '42'},
'document_id': {'description': _('Document ID'), 'example_value': 'hDWtdSC'},
},
)
def upload_document_file(self, request, entity_id, document_id, post_data):
file_field_name = post_data.pop('file_field_name')
file_data = post_data.pop('file')
response = self.upload_file(entity_id, document_id, file_field_name, file_data, **post_data)
return {'data': response.json()}
@endpoint(
description=_('Get document\'s file'),
name='get-document-file',
parameters={
'entity_id': {'description': _('Entity ID'), 'example_value': '42'},
'document_id': {'description': _('Document ID'), 'example_value': 'hDWtdSC'},
'field_name': {
'description': _('Document file\'s field name'),
'example_value': 'document',
},
},
)
def get_document_file(self, request, entity_id, document_id, field_name):
document = self.call('entite/%s/document/%s/file/%s' % (entity_id, document_id, field_name))
response = HttpResponse(document.content, content_type=document.headers['Content-Type'])
response['Content-Disposition'] = document.headers['Content-disposition']
return response