235 lines
8.2 KiB
Python
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
|