186 lines
7.0 KiB
Python
186 lines
7.0 KiB
Python
# passerelle - uniform access to multiple data sources and services
|
|
# Copyright (C) 2017 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 uuid
|
|
|
|
from django.db import models
|
|
from django.template import Context, Template
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
from jsonfield import JSONField
|
|
|
|
from passerelle.base.models import BaseResource
|
|
from passerelle.compat import json_loads
|
|
from passerelle.utils.api import endpoint, APIError
|
|
from passerelle.utils.conversion import simplify
|
|
|
|
|
|
def get_hex_uuid():
|
|
return uuid.uuid4().hex
|
|
|
|
|
|
def clean_json_data(data):
|
|
try:
|
|
payload = json_loads(data)
|
|
if not isinstance(payload, dict):
|
|
raise APIError('payload must be a dict')
|
|
return payload
|
|
except ValueError:
|
|
raise APIError('could not decode body to json')
|
|
|
|
|
|
class JsonData(models.Model):
|
|
datastore = models.ForeignKey('JsonDataStore', null=True, on_delete=models.CASCADE)
|
|
|
|
uuid = models.CharField(_('uuid'), max_length=32,
|
|
default=get_hex_uuid, editable=False, unique=True)
|
|
name_id = models.CharField(max_length=256, blank=True)
|
|
content = JSONField(_('Content'))
|
|
text = models.CharField(max_length=256, blank=True)
|
|
|
|
creation_datetime = models.DateTimeField(auto_now_add=True)
|
|
last_update_datetime = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
ordering = ['text']
|
|
|
|
def save(self, *args, **kwargs):
|
|
text_value_template = self.datastore.text_value_template
|
|
if text_value_template:
|
|
template = Template(text_value_template)
|
|
context = Context(self.content)
|
|
self.text = template.render(context).strip()
|
|
return super(JsonData, self).save(*args, **kwargs)
|
|
|
|
def to_json(self):
|
|
return {
|
|
'id': self.uuid,
|
|
'text': self.text,
|
|
'content': self.content,
|
|
'creation_datetime': self.creation_datetime,
|
|
'last_update_datetime': self.last_update_datetime,
|
|
}
|
|
|
|
|
|
class JsonDataStore(BaseResource):
|
|
category = _('Data Sources')
|
|
documentation_url = 'https://doc-publik.entrouvert.com/admin-fonctionnel/parametrage-avance/json-data-store/'
|
|
text_value_template = models.CharField(_('Template for "text" key value'),
|
|
max_length=256, blank=True)
|
|
|
|
class Meta:
|
|
verbose_name = _('JSON Data Store')
|
|
|
|
@endpoint(perm='can_access', name='data', pattern=r'$',
|
|
description=_('Listing'),
|
|
long_description=_(
|
|
'More filtering on attributes is possible '
|
|
'using "key=val" additionals parameters'),
|
|
parameters={
|
|
'name_id': {
|
|
'description': _('Object identifier'),
|
|
'example_value': '12345'
|
|
},
|
|
'q': {
|
|
'description': _('Filter on "text" key value'),
|
|
'example_value': 'rue du chateau'
|
|
},
|
|
}
|
|
)
|
|
def list(self, request, name_id=None, q=None, **kwargs):
|
|
objects = JsonData.objects.filter(datastore=self)
|
|
if name_id is not None:
|
|
objects = objects.filter(name_id=name_id)
|
|
if q:
|
|
q = simplify(q)
|
|
objects = [o for o in objects if q in simplify(o.text)]
|
|
for key, value in kwargs.items():
|
|
objects = [o for o in objects if o.content.get(key) == value]
|
|
return {'data': [x.to_json() for x in objects]}
|
|
|
|
@endpoint(perm='can_access', methods=['post'], name='data',
|
|
pattern=r'create$',
|
|
example_pattern='create',
|
|
description=_('Create'))
|
|
def create(self, request, name_id=None, **kwargs):
|
|
content = clean_json_data(request.body)
|
|
attrs = {
|
|
'content': content,
|
|
'datastore': self,
|
|
}
|
|
if name_id is not None:
|
|
attrs['name_id'] = name_id
|
|
data = JsonData(**attrs)
|
|
data.save()
|
|
return {'id': data.uuid, 'text': data.text}
|
|
|
|
def get_data_object(self, uuid, name_id=None):
|
|
attrs = {
|
|
'uuid': uuid,
|
|
'datastore': self,
|
|
}
|
|
if name_id is not None:
|
|
attrs['name_id'] = name_id
|
|
return JsonData.objects.get(**attrs)
|
|
|
|
@endpoint(perm='can_access', methods=['get', 'post', 'patch'], name='data',
|
|
pattern=r'(?P<uuid>\w+)/$',
|
|
example_pattern='{uuid}/',
|
|
description_get=_('Get'),
|
|
description_post=_('Replace'),
|
|
description_patch=_('Update'),
|
|
parameters={'uuid': {'description': _('Object identifier'), 'example_value': '12345'}},
|
|
)
|
|
def get_or_replace(self, request, uuid, name_id=None):
|
|
data = self.get_data_object(uuid, name_id)
|
|
if request.method == 'POST':
|
|
new_content = clean_json_data(request.body)
|
|
data.content = new_content
|
|
data.save()
|
|
elif request.method == 'PATCH':
|
|
new_content = clean_json_data(request.body)
|
|
data.content.update(new_content)
|
|
data.save()
|
|
return data.to_json()
|
|
|
|
@endpoint(perm='can_access', methods=['post'], name='data',
|
|
description=_('Delete'),
|
|
pattern=r'(?P<uuid>\w+)/delete$',
|
|
example_pattern='{uuid}/delete',
|
|
parameters={'uuid': {'description': _('Object identifier'), 'example_value': '12345'}},
|
|
)
|
|
def delete_(self, request, uuid, name_id=None):
|
|
# delete() would collide with Model.delete()
|
|
self.get_data_object(uuid, name_id).delete()
|
|
return {}
|
|
|
|
@endpoint(perm='can_access', name='data',
|
|
pattern=r'by/(?P<attribute>[\w-]+)/$',
|
|
example_pattern='by/{attribute}/',
|
|
description=_('Get a single object by attribute'),
|
|
parameters={'attribute': {'description': _('Attribute name'), 'example_value': 'code'},
|
|
'value': {'description': _('Attribute value'), 'example_value': '12345'},
|
|
},
|
|
)
|
|
def get_by_attribute(self, request, attribute, value, name_id=None):
|
|
objects = JsonData.objects.filter(datastore=self)
|
|
if name_id is not None:
|
|
objects = objects.filter(name_id=name_id)
|
|
for data in objects:
|
|
if data.content and data.content.get(attribute) == value:
|
|
return data.to_json()
|
|
raise APIError('no such object')
|