Support title/description for canned queries, closes #342

Demo here: https://latest.datasette.io/fixtures/neighborhood_search
This commit is contained in:
Simon Willison 2018-07-15 19:33:30 -07:00
commit 6e37f091ed
No known key found for this signature in database
GPG key ID: 17E2DEA2588B7F52
9 changed files with 62 additions and 17 deletions

View file

@ -174,6 +174,14 @@ class Datasette:
]
return self._app_css_hash
def get_canned_queries(self, database_name):
names = self.metadata.get("databases", {}).get(database_name, {}).get(
"queries", {}
).keys()
return [
self.get_canned_query(database_name, name) for name in names
]
def get_canned_query(self, database_name, query_name):
query = self.metadata.get("databases", {}).get(database_name, {}).get(
"queries", {}
@ -181,7 +189,10 @@ class Datasette:
query_name
)
if query:
return {"name": query_name, "sql": query}
if not isinstance(query, dict):
query = {"sql": query}
query["name"] = query_name
return query
async def get_table_definition(self, database_name, table, type_="table"):
table_definition_rows = list(

View file

@ -51,7 +51,7 @@
<h2>Queries</h2>
<ul>
{% for query in queries %}
<li><a href="/{{ database }}-{{ database_hash }}/{{ query.name|urlencode }}" title="{{ query.sql }}">{{ query.name }}</a></li>
<li><a href="/{{ database }}-{{ database_hash }}/{{ query.name|urlencode }}" title="{{ query.description or query.sql }}">{{ query.title or query.name }}</a></li>
{% endfor %}
</ul>
{% endif %}

View file

@ -21,7 +21,9 @@
{% block content %}
<div class="hd"><a href="/">home</a> / <a href="/{{ database }}-{{ database_hash }}">{{ database }}</a></div>
<h1 style="padding-left: 10px; border-left: 10px solid #{{ database_hash[:6] }}">{{ database }}</h1>
<h1 style="padding-left: 10px; border-left: 10px solid #{{ database_hash[:6] }}">{{ metadata.title or database }}</h1>
{% block description_source_license %}{% include "_description_source_license.html" %}{% endblock %}
<form class="sql" action="/{{ database }}-{{ database_hash }}{% if canned_query %}/{{ canned_query }}{% endif %}" method="get">
<h3>Custom SQL query{% if rows %} returning {% if truncated %}more than {% endif %}{{ "{:,}".format(rows|length) }} row{% if rows|length == 1 %}{% else %}s{% endif %}{% endif %}</h3>

View file

@ -433,7 +433,7 @@ class BaseView(RenderMixin):
async def custom_sql(
self, request, name, hash, sql, editable=True, canned_query=None,
_size=None
metadata=None, _size=None
):
params = request.raw_args
if "sql" in params:
@ -483,6 +483,7 @@ class BaseView(RenderMixin):
"named_parameter_values": named_parameter_values,
"editable": editable,
"canned_query": canned_query,
"metadata": metadata,
"config": self.ds.config,
}, templates

View file

@ -27,10 +27,7 @@ class DatabaseView(BaseView):
"tables": tables,
"hidden_count": len([t for t in tables if t["hidden"]]),
"views": info["views"],
"queries": [
{"name": query_name, "sql": query_sql}
for query_name, query_sql in (metadata.get("queries") or {}).items()
],
"queries": self.ds.get_canned_queries(name),
}, {
"database_hash": hash,
"show_hidden": request.args.get("_show_hidden"),

View file

@ -232,6 +232,7 @@ class TableView(RowTableShared):
name,
hash,
canned_query["sql"],
metadata=canned_query,
editable=False,
canned_query=table,
)

View file

@ -73,7 +73,9 @@ queries inside your ``metadata.json`` file. Here's an example::
"databases": {
"sf-trees": {
"queries": {
"just_species": "select qSpecies from Street_Tree_List"
"just_species": {
"sql": select qSpecies from Street_Tree_List"
}
}
}
}
@ -92,6 +94,11 @@ For the above example, that URL would be::
/sf-trees/just_species
You can optionally include ``"title"`` and ``"description"`` keys to show a
title and description on the canned query page. As with regular table metadata
you can alternatively specify ``"description_html"`` to have your description
rendered as HTML (rather than having HTML special characters escaped).
Canned queries support named parameters, so if you include those in the SQL you
will then be able to enter them using the form fields on the canned query page
or by adding them to the URL. This means canned queries can be used to create
@ -111,7 +118,11 @@ In the canned query JSON it looks like this::
"databases": {
"fixtures": {
"queries": {
"neighborhood_search": "select neighborhood, facet_cities.name, state\nfrom facetable join facet_cities on facetable.city_id = facet_cities.id\nwhere neighborhood like '%' || :text || '%' order by neighborhood;"
"neighborhood_search": {
"sql": "select neighborhood, facet_cities.name, state\nfrom facetable join facet_cities on facetable.city_id = facet_cities.id\nwhere neighborhood like '%' || :text || '%' order by neighborhood;",
"title": "Search neighborhoods",
"description_html": "<b>Demonstrating</b> simple like search"
}
}
}
}

View file

@ -161,13 +161,18 @@ METADATA = {
},
'queries': {
'pragma_cache_size': 'PRAGMA cache_size;',
'neighborhood_search': '''
select neighborhood, facet_cities.name, state
from facetable
join facet_cities on facetable.city_id = facet_cities.id
where neighborhood like '%' || :text || '%'
order by neighborhood;
'''
'neighborhood_search': {
'sql': '''
select neighborhood, facet_cities.name, state
from facetable
join facet_cities
on facetable.city_id = facet_cities.id
where neighborhood like '%' || :text || '%'
order by neighborhood;
''',
'title': 'Search neighborhoods',
'description_html': '<b>Demonstrating</b> simple like search',
},
}
},
}

View file

@ -754,3 +754,20 @@ def test_404_trailing_slash_redirect(app_client, path, expected_redirect):
response = app_client.get(path, allow_redirects=False)
assert 302 == response.status
assert expected_redirect == response.headers["Location"]
def test_canned_query_with_custom_metadata(app_client):
response = app_client.get("/fixtures/neighborhood_search?text=town")
assert response.status == 200
soup = Soup(response.body, "html.parser")
assert "Search neighborhoods" == soup.find("h1").text
assert (
"""
<div class="metadata-description">
<b>
Demonstrating
</b>
simple like search
</div>""".strip()
== soup.find("div", {"class": "metadata-description"}).prettify().strip()
)