New ?_shape=objects/object/lists param for JSON API (#192)

New _shape= parameter replacing old .jsono extension

Now instead of this:

	/database/table.jsono

We use the _shape parameter like this:

	/database/table.json?_shape=objects

Also introduced a new _shape called 'object' which looks like this:

	/database/table.json?_shape=object

Returning an object for the rows key:

	...
	"rows": {
		"pk1": {
			...
		},
		"pk2": {
			...
		}
	}

Refs #122
This commit is contained in:
Simon Willison 2018-04-03 07:52:54 -07:00 committed by GitHub
commit 0abd3abacb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 244 additions and 23 deletions

View file

@ -221,8 +221,20 @@ class BaseView(RenderMixin):
if value:
data[key] = value
if as_json:
# Special case for .jsono extension
# Special case for .jsono extension - redirect to _shape=objects
if as_json == '.jsono':
return self.redirect(
request,
path_with_added_args(
request,
{'_shape': 'objects'},
path=request.path.rsplit('.jsono', 1)[0] + '.json'
),
forward_querystring=False
)
# Deal with the _shape option
shape = request.args.get('_shape', 'lists')
if shape in ('objects', 'object'):
columns = data.get('columns')
rows = data.get('rows')
if rows and columns:
@ -230,6 +242,28 @@ class BaseView(RenderMixin):
dict(zip(columns, row))
for row in rows
]
if shape == 'object':
error = None
if 'primary_keys' not in data:
error = '_shape=object is only available on tables'
else:
pks = data['primary_keys']
if not pks:
error = '_shape=object not available for tables with no primary keys'
else:
object_rows = {}
for row in data['rows']:
pk_string = path_from_row_pks(row, pks, not pks)
object_rows[pk_string] = row
data['rows'] = object_rows
if error:
data = {
'ok': False,
'error': error,
'database': name,
'database_hash': hash,
}
headers = {}
if self.ds.cors:
headers['Access-Control-Allow-Origin'] = '*'
@ -278,6 +312,8 @@ class BaseView(RenderMixin):
params = request.raw_args
if 'sql' in params:
params.pop('sql')
if '_shape' in params:
params.pop('_shape')
# Extract any :named parameters
named_parameters = self.re_named_parameter.findall(sql)
named_parameter_values = {

View file

@ -40,7 +40,7 @@
</form>
{% if rows %}
<p>This data as <a href="{{ url_json }}">.json</a>, <a href="{{ url_jsono }}">.jsono</a></p>
<p>This data as <a href="{{ url_json }}">.json</a></p>
<table>
<thead>
<tr>

View file

@ -22,7 +22,7 @@
{% block description_source_license %}{% include "_description_source_license.html" %}{% endblock %}
<p>This data as <a href="{{ url_json }}">.json</a>, <a href="{{ url_jsono }}">.jsono</a></p>
<p>This data as <a href="{{ url_json }}">.json</a></p>
{% include custom_rows_and_columns_templates %}

View file

@ -74,7 +74,7 @@
<p><a class="not-underlined" title="{{ query.sql }}" href="/{{ database }}-{{ database_hash }}?{{ {'sql': query.sql}|urlencode|safe }}{% if query.params %}&amp;{{ query.params|urlencode|safe }}{% endif %}">&#x270e; <span class="underlined">View and edit SQL</span></a></p>
{% endif %}
<p>This data as <a href="{{ url_json }}">.json</a>, <a href="{{ url_jsono }}">.jsono</a></p>
<p>This data as <a href="{{ url_json }}">.json</a></p>
{% include custom_rows_and_columns_templates %}

View file

@ -134,7 +134,8 @@ def validate_sql_select(sql):
raise InvalidSql(msg)
def path_with_added_args(request, args):
def path_with_added_args(request, args, path=None):
path = path or request.path
if isinstance(args, dict):
args = args.items()
arg_keys = set(a[0] for a in args)
@ -151,7 +152,7 @@ def path_with_added_args(request, args):
query_string = urllib.parse.urlencode(sorted(current))
if query_string:
query_string = '?{}'.format(query_string)
return request.path + query_string
return path + query_string
def path_with_ext(request, ext):