Skip to content

Commit

Permalink
[OD-1693] Add ckan 2.9 support (OpenGov-OpenData#5)
Browse files Browse the repository at this point in the history
* Add ckan 2.9 support

* Fix regex, update getting request params, and update responses

The ur prefix is not supported anymore. The request params are different in pylons and flask. Toolkit doesn't have a flask response object so we make one
  • Loading branch information
jguo144 authored Sep 2, 2021
1 parent b334ee1 commit 95d989d
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 190 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ syntax: glob
*.pyc
*.sw*
*.egg-info

.DS_Store
61 changes: 42 additions & 19 deletions ckanext/odata/actions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import re
import simplejson as json
import ckan.plugins.toolkit as t

import ckan.plugins as p

from flask import make_response
from ckan.exceptions import CkanVersionException

try:
from collections import OrderedDict # from python 2.7
Expand All @@ -21,11 +22,20 @@
'text': 'Edm.String',
}

name_pattern = r'[^:A-Z_a-z.0-9\u00B7\u00C0-\u00D6\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u0300-\u036F\u203F-\u2040]'

t = p.toolkit
_base_url = None


def get_request_param():
try:
requires_ckan_version("2.9")
except:
return t.request.params
else:
return t.request.args


def name_2_xml_tag(name):
''' Convert a name into a xml safe name.
Expand All @@ -42,13 +52,10 @@ def name_2_xml_tag(name):
'''

# leave well-formed XML element characters only
name = re.sub(ur'[^A-Z_a-z\u00C0-\u00D6\u0370-\u037D\u037F-\u1FFF'
ur'\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF'
ur'\uF900-\uFDCF\uFDF0-\uFFFD-0-9\u00B7\u0300-\u036F'
ur'\u203F-\u2040]', '', name)
name = re.sub(name_pattern, '', name)

# add '_' in front of non-NameStart characters
name = re.sub(re.compile(ur'(?P<q>^[-.0-9\u00B7#\u0300-\u036F\u203F-\u2040])', re.MULTILINE),
name = re.sub(re.compile(r'(?P<q>^[-.0-9\u00B7#\u0300-\u036F\u203F-\u2040])', re.MULTILINE),
'_\g<q>', name)

# No valid XML element at all
Expand All @@ -59,8 +66,9 @@ def name_2_xml_tag(name):


def get_qs_int(param, default):
''' Get a query sting param as an int '''
value = t.request.GET.get(param, default)
''' Get a query string param as an int '''
request_params = get_request_param()
value = request_params.get(param, default)
try:
value = int(value)
except ValueError:
Expand Down Expand Up @@ -90,15 +98,16 @@ def odata(context, data_dict):
resource_id = uri
filters = {}

output_json = t.request.GET.get('$format') == 'json'
request_params = get_request_param()
output_json = request_params.get('$format') == 'json'

# Ignore $limit & $top paramters if $sqlfilter is specified
# as they should be specified by the sql query
if t.request.GET.get('$sqlfilter'):
if request_params.get('$sqlfilter'):
action = t.get_action('datastore_search_sql')

query = t.request.GET.get('$sqlfilter')
sql = "SELECT * FROM \"%s\" %s"%(resource_id, query)
query = request_params.get('$sqlfilter', '')
sql = "SELECT * FROM \"{}\" {}".format(resource_id, query)

data_dict = {
'sql': sql
Expand All @@ -125,7 +134,7 @@ def odata(context, data_dict):
except t.ValidationError as e:
return json.dumps(e.error_dict)

if not t.request.GET.get('$sqlfilter'):
if not request_params.get('$sqlfilter'):
num_results = result['total']
if num_results > offset + limit:
next_query_string = '$skip=%s&$top=%s' % (offset + limit, limit)
Expand Down Expand Up @@ -162,8 +171,15 @@ def odata(context, data_dict):
'entries': result['records'],
'next_query_string': next_query_string,
}
t.response.headers['Content-Type'] = 'application/atom+xml;type=feed;charset=utf-8'
return t.render('ckanext-odata/collection.xml', data)
try:
t.requires_ckan_version("2.9")
except CkanVersionException:
t.response.headers['Content-Type'] = 'application/atom+xml;type=feed;charset=utf-8'
return t.render('ckanext-odata/collection.xml', data)
else:
response = make_response(t.render('ckanext-odata/collection.xml', data))
response.headers['Content-Type'] = 'application/atom+xml;type=feed;charset=utf-8'
return response


def odata_metadata(context, data_dict):
Expand Down Expand Up @@ -211,5 +227,12 @@ def odata_metadata(context, data_dict):

data = { 'collections' : collections }

t.response.headers['Content-Type'] = 'application/xml;charset=utf-8'
return t.render('ckanext-odata/metadata.xml', data)
try:
t.requires_ckan_version("2.9")
except CkanVersionException:
t.response.headers['Content-Type'] = 'application/xml;charset=utf-8'
return t.render('ckanext-odata/metadata.xml', data)
else:
response = make_response(t.render('ckanext-odata/metadata.xml', data))
response.headers['Content-Type'] = 'application/xml;charset=utf-8'
return response
15 changes: 5 additions & 10 deletions ckanext/odata/controller.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import ckan.plugins as p
from ckan.plugins.toolkit import BaseController
from ckanext.odata import utils


class ODataController(p.toolkit.BaseController):
class ODataController(BaseController):

def odata(self, uri):
data_dict = {'uri': uri}
action = p.toolkit.get_action('ckanext-odata_odata')
result = action({}, data_dict)
return result
return utils.odata(uri)

def odata_metadata(self):
action = p.toolkit.get_action('ckanext-odata_metadata')
result = action({},{})
return result
return utils.odata_metadata()
40 changes: 0 additions & 40 deletions ckanext/odata/plugin.py

This file was deleted.

33 changes: 33 additions & 0 deletions ckanext/odata/plugin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import ckan.plugins as p
import ckanext.odata.actions as action

if p.toolkit.check_ckan_version('2.9'):
from ckanext.odata.plugin.flask_plugin import MixinPlugin
else:
from ckanext.odata.plugin.pylons_plugin import MixinPlugin


def link(resource_id):
return '{0}{1}'.format(action.base_url(), resource_id)


class ODataPlugin(MixinPlugin, p.SingletonPlugin):
p.implements(p.IConfigurer)
p.implements(p.IActions)
p.implements(p.ITemplateHelpers, inherit=True)

def update_config(self, config):
p.toolkit.add_template_directory(config, '../templates')
p.toolkit.add_resource('../resources', 'odata')

def get_actions(self):
actions = {
'ckanext-odata_metadata': action.odata_metadata,
'ckanext-odata_odata': action.odata,
}
return actions

def get_helpers(self):
return {
'ckanext_odata_link': link,
}
9 changes: 9 additions & 0 deletions ckanext/odata/plugin/flask_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import ckan.plugins as p
import ckanext.odata.views as views


class MixinPlugin(p.SingletonPlugin):
p.implements(p.IBlueprint)

def get_blueprint(self):
return views.get_blueprints()
14 changes: 14 additions & 0 deletions ckanext/odata/plugin/pylons_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import ckan.plugins as p


class MixinPlugin(p.SingletonPlugin):
p.implements(p.IRoutes, inherit=True)

def before_map(self, m):
m.connect('/datastore/odata3.0/$metadata',
controller='ckanext.odata.controller:ODataController',
action='odata_metadata')
m.connect('/datastore/odata3.0/{uri:.*?}',
controller='ckanext.odata.controller:ODataController',
action='odata')
return m
Loading

0 comments on commit 95d989d

Please sign in to comment.