From 7dff73ba45391c575b3acc5e4be2834c8f5ea3b4 Mon Sep 17 00:00:00 2001 From: Serghei Mihai Date: Tue, 12 Dec 2017 10:13:51 +0100 Subject: [PATCH] opengis: add feature queries (#20535) --- passerelle/apps/opengis/forms.py | 30 +++++++++++ .../opengis/migrations/0003_auto_20171212_1620.py | 33 ++++++++++++ passerelle/apps/opengis/models.py | 57 +++++++++++++++++++++ .../opengis/featurequery_confirm_delete.html | 25 +++++++++ .../templates/opengis/featurequery_form.html | 33 ++++++++++++ .../opengis/templates/opengis/opengis_detail.html | 38 ++++++++++++++ passerelle/apps/opengis/urls.py | 28 ++++++++++ passerelle/apps/opengis/views.py | 59 ++++++++++++++++++++++ 8 files changed, 303 insertions(+) create mode 100644 passerelle/apps/opengis/forms.py create mode 100644 passerelle/apps/opengis/migrations/0003_auto_20171212_1620.py create mode 100644 passerelle/apps/opengis/templates/opengis/featurequery_confirm_delete.html create mode 100644 passerelle/apps/opengis/templates/opengis/featurequery_form.html create mode 100644 passerelle/apps/opengis/templates/opengis/opengis_detail.html create mode 100644 passerelle/apps/opengis/urls.py create mode 100644 passerelle/apps/opengis/views.py diff --git a/passerelle/apps/opengis/forms.py b/passerelle/apps/opengis/forms.py new file mode 100644 index 0000000..49dcc64 --- /dev/null +++ b/passerelle/apps/opengis/forms.py @@ -0,0 +1,30 @@ +# passerelle - uniform access to multiple data sources and services +# Copyright (C) 2016 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 . + +from django import forms + +from .models import FeatureQuery + + +class FeatureQueryForm(forms.ModelForm): + class Meta: + model = FeatureQuery + widgets = { + 'resource': forms.HiddenInput(), + 'query_filters': forms.Textarea(attrs={'rows': 2}), + 'response_filters': forms.Textarea(attrs={'rows': 2}), + } + fields = '__all__' diff --git a/passerelle/apps/opengis/migrations/0003_auto_20171212_1620.py b/passerelle/apps/opengis/migrations/0003_auto_20171212_1620.py new file mode 100644 index 0000000..ac4b3a3 --- /dev/null +++ b/passerelle/apps/opengis/migrations/0003_auto_20171212_1620.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('opengis', '0002_auto_20171129_1814'), + ] + + operations = [ + migrations.CreateModel( + name='FeatureQuery', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('slug', models.SlugField(verbose_name='Name (slug)')), + ('label', models.CharField(max_length=100, verbose_name='Label')), + ('description', models.TextField(verbose_name='Description', blank=True)), + ('query_filters', models.TextField(help_text='List of filters (name:expression)', verbose_name='Query Filters', blank=True)), + ('response_filters', models.TextField(help_text='List of filters (name:expression)', verbose_name='Response Filters', blank=True)), + ], + options={ + 'ordering': ['slug'], + }, + ), + migrations.AddField( + model_name='featurequery', + name='resource', + field=models.ForeignKey(to='opengis.OpenGIS'), + ), + ] diff --git a/passerelle/apps/opengis/models.py b/passerelle/apps/opengis/models.py index f47f31a..b2f0800 100644 --- a/passerelle/apps/opengis/models.py +++ b/passerelle/apps/opengis/models.py @@ -43,6 +43,46 @@ def build_dict_from_xml(elem): return d +class FeatureQuery(models.Model): + resource = models.ForeignKey('OpenGIS') + slug = models.SlugField(_('Name (slug)')) + label = models.CharField(_('Label'), max_length=100) + description = models.TextField(_('Description'), blank=True) + query_filters = models.TextField(_('Query Filters'), blank=True, + help_text=_('List of filters (name:expression)')) + response_filters = models.TextField(_('Response Filters'), blank=True, + help_text=_('List of filters (name:expression)')) + + class Meta: + ordering = ['slug'] + + def get_list(self, attribute): + if not getattr(self, attribute): + return [] + return getattr(self, attribute).strip().splitlines() + + def process_query_filters(self, params): + filters = {} + for filter_line in self.get_list('query_filters'): + name, value = filter_line.split(':', 1) + filters[name] = eval(value.strip(), {}, params) + return filters + + def process_response_filters(self, features): + filters = self.get_list('response_filters') + if not filters: + return features + + result = [] + for feature in features: + filtered_feature = {} + for line in filters: + name, value = line.split(':', 1) + filtered_feature[name] = eval(value.strip(), {}, feature) + result.append(filtered_feature) + return result + + class OpenGIS(BaseResource): category = _('Geographic information system') wms_service_url = models.URLField(_('Web Map Service (WMS) URL'), max_length=256, blank=True) @@ -106,6 +146,23 @@ class OpenGIS(BaseResource): data.append(feature) return {'data': data} + @endpoint(perm='can_access', methods=['get'], + name='feature', pattern='^(?P[\w-]+)/$') + def feature_query(self, request, query_name, **kwargs): + try: + query = FeatureQuery.objects.get(resource=self.id, slug=query_name) + except Query.DoesNotExist: + raise APIError(u'no such query') + params = { + 'VERSION': self.get_wfs_service_version(), + 'SERVICE': 'WFS', + 'REQUEST': 'GetFeature', + 'OUTPUTFORMAT': 'json', + } + params.update(query.process_query_filters(request.GET.dict())) + response = self.requests.get(self.wfs_service_url, params=params) + data = query.process_response_filters(response.json()['features']) + return {'data': data} @endpoint(perm='can_access', description=_('Get feature info'), diff --git a/passerelle/apps/opengis/templates/opengis/featurequery_confirm_delete.html b/passerelle/apps/opengis/templates/opengis/featurequery_confirm_delete.html new file mode 100644 index 0000000..25920e0 --- /dev/null +++ b/passerelle/apps/opengis/templates/opengis/featurequery_confirm_delete.html @@ -0,0 +1,25 @@ +{% extends "passerelle/manage.html" %} +{% load i18n %} + +{% block breadcrumb %} +{{ block.super }} +{{ resource.title }} +{{ object.slug }} +{% endblock %} + +{% block appbar %} +

{% trans 'Feature Query:' %} {{ object.slug }}

+{% endblock %} + +{% block content %} + +
+ {% csrf_token %} +

{% blocktrans %}Are you sure you want to delete this feature query?{% endblocktrans %}

+
+ + {% trans 'Cancel' %} +
+
+ +{% endblock %} diff --git a/passerelle/apps/opengis/templates/opengis/featurequery_form.html b/passerelle/apps/opengis/templates/opengis/featurequery_form.html new file mode 100644 index 0000000..c628752 --- /dev/null +++ b/passerelle/apps/opengis/templates/opengis/featurequery_form.html @@ -0,0 +1,33 @@ +{% extends "passerelle/manage.html" %} +{% load i18n %} + +{% block breadcrumb %} +{{ block.super }} +{{ resource.title }} +{% if object.id %} +{{ object.slug }} +{% else %} +{% trans 'Add Feature Query' %} +{% endif %} +{% endblock %} + +{% block appbar %} +

{% if object.id %}{% trans 'Feature Query:' %} {{ object.slug }}{% else %}{% trans 'New Feature Query' %}{% endif %}

+{% endblock %} + +{% block content %} + +
+
+ {% csrf_token %} + {{ form.as_p }} +
+ {% block buttons %} +
+ + {% trans 'Cancel' %} +
+ {% endblock %} +
+ +{% endblock %} diff --git a/passerelle/apps/opengis/templates/opengis/opengis_detail.html b/passerelle/apps/opengis/templates/opengis/opengis_detail.html new file mode 100644 index 0000000..af7b74f --- /dev/null +++ b/passerelle/apps/opengis/templates/opengis/opengis_detail.html @@ -0,0 +1,38 @@ +{% extends "passerelle/manage/service_view.html" %} +{% load i18n passerelle %} + +{% block endpoints %} +{{ block.super }} +
    + {% for query in object.featurequery_set.all %} +
  • {{ query.label }}: + {% url 'generic-endpoint' connector='opengis' slug=object.slug endpoint='feature' rest=query.slug as query_url %} + {{ query_url }}/ + {% if query.description %}— {{ query.description }}{% endif %} +
  • + {% endfor %} +
+ +{% if object|can_edit:request.user %} +
+

{% trans 'Feature queries' %}

+
+{% if object.featurequery_set.count %} + +{% else %} +

{% trans 'No feature queries are defined.' %}

+{% endif %} +

+{% trans 'New Feature Query' %} +

+
+
+{% endif %} +{% endblock %} + diff --git a/passerelle/apps/opengis/urls.py b/passerelle/apps/opengis/urls.py new file mode 100644 index 0000000..e707355 --- /dev/null +++ b/passerelle/apps/opengis/urls.py @@ -0,0 +1,28 @@ +# 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 . + +from django.conf.urls import url + +from .views import * + +management_urlpatterns = [ + url(r'^(?P[\w,-]+)/queries/new/$', + NewFeatureQueryView.as_view(), name='opengis-new-featurequery'), + url(r'^(?P[\w,-]+)/queries/(?P[\w,-]+)/$', + UpdateFeatureQueryView.as_view(), name='opengis-edit-featurequery'), + url(r'^(?P[\w,-]+)/queries/(?P[\w,-]+)/delete$', + DeleteFeatureQueryView.as_view(), name='opengis-delete-featurequery'), +] diff --git a/passerelle/apps/opengis/views.py b/passerelle/apps/opengis/views.py new file mode 100644 index 0000000..5ad9e1c --- /dev/null +++ b/passerelle/apps/opengis/views.py @@ -0,0 +1,59 @@ +# 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 . + +from django.views.generic import UpdateView, CreateView, DeleteView + +from .forms import FeatureQueryForm +from .models import OpenGIS, FeatureQuery + + +class FeatureQueryView(object): + model = FeatureQuery + + def get_success_url(self): + return self.object.resource.get_absolute_url() + + def get_context_data(self, **kwargs): + ctx = super(FeatureQueryView, self).get_context_data(**kwargs) + ctx['resource'] = OpenGIS.objects.get(slug=self.kwargs['connector_slug']) + return ctx + + +class NewFeatureQueryView(FeatureQueryView, CreateView): + form_class = FeatureQueryForm + template_name = 'opengis/featurequery_form.html' + + def get_context_data(self, **kwargs): + ctx = super(NewFeatureQueryView, self).get_context_data(**kwargs) + ctx['resource'] = OpenGIS.objects.get(slug=self.kwargs['connector_slug']) + return ctx + + def get_initial(self): + return {'resource': OpenGIS.objects.get(slug=self.kwargs['connector_slug']).id} + + +class UpdateFeatureQueryView(FeatureQueryView, UpdateView): + form_class = FeatureQueryForm + template_name = 'opengis/featurequery_form.html' + + def get_context_data(self, **kwargs): + ctx = super(UpdateFeatureQueryView, self).get_context_data(**kwargs) + ctx['resource'] = OpenGIS.objects.get(slug=self.kwargs['connector_slug']) + return ctx + + +class DeleteFeatureQueryView(FeatureQueryView, DeleteView): + pass -- 2.15.1