passerelle/passerelle/apps/jsondatastore/models.py

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')