kopia lustrzana https://github.com/simonw/datasette
Support title/description for canned queries, closes #342
Demo here: https://latest.datasette.io/fixtures/neighborhood_searchpull/349/head
rodzic
58fec99ab0
commit
6e37f091ed
|
@ -174,6 +174,14 @@ class Datasette:
|
||||||
]
|
]
|
||||||
return self._app_css_hash
|
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):
|
def get_canned_query(self, database_name, query_name):
|
||||||
query = self.metadata.get("databases", {}).get(database_name, {}).get(
|
query = self.metadata.get("databases", {}).get(database_name, {}).get(
|
||||||
"queries", {}
|
"queries", {}
|
||||||
|
@ -181,7 +189,10 @@ class Datasette:
|
||||||
query_name
|
query_name
|
||||||
)
|
)
|
||||||
if query:
|
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"):
|
async def get_table_definition(self, database_name, table, type_="table"):
|
||||||
table_definition_rows = list(
|
table_definition_rows = list(
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
<h2>Queries</h2>
|
<h2>Queries</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{% for query in queries %}
|
{% 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 %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -21,7 +21,9 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="hd"><a href="/">home</a> / <a href="/{{ database }}-{{ database_hash }}">{{ database }}</a></div>
|
<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">
|
<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>
|
<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>
|
||||||
|
|
|
@ -433,7 +433,7 @@ class BaseView(RenderMixin):
|
||||||
|
|
||||||
async def custom_sql(
|
async def custom_sql(
|
||||||
self, request, name, hash, sql, editable=True, canned_query=None,
|
self, request, name, hash, sql, editable=True, canned_query=None,
|
||||||
_size=None
|
metadata=None, _size=None
|
||||||
):
|
):
|
||||||
params = request.raw_args
|
params = request.raw_args
|
||||||
if "sql" in params:
|
if "sql" in params:
|
||||||
|
@ -483,6 +483,7 @@ class BaseView(RenderMixin):
|
||||||
"named_parameter_values": named_parameter_values,
|
"named_parameter_values": named_parameter_values,
|
||||||
"editable": editable,
|
"editable": editable,
|
||||||
"canned_query": canned_query,
|
"canned_query": canned_query,
|
||||||
|
"metadata": metadata,
|
||||||
"config": self.ds.config,
|
"config": self.ds.config,
|
||||||
}, templates
|
}, templates
|
||||||
|
|
||||||
|
|
|
@ -27,10 +27,7 @@ class DatabaseView(BaseView):
|
||||||
"tables": tables,
|
"tables": tables,
|
||||||
"hidden_count": len([t for t in tables if t["hidden"]]),
|
"hidden_count": len([t for t in tables if t["hidden"]]),
|
||||||
"views": info["views"],
|
"views": info["views"],
|
||||||
"queries": [
|
"queries": self.ds.get_canned_queries(name),
|
||||||
{"name": query_name, "sql": query_sql}
|
|
||||||
for query_name, query_sql in (metadata.get("queries") or {}).items()
|
|
||||||
],
|
|
||||||
}, {
|
}, {
|
||||||
"database_hash": hash,
|
"database_hash": hash,
|
||||||
"show_hidden": request.args.get("_show_hidden"),
|
"show_hidden": request.args.get("_show_hidden"),
|
||||||
|
|
|
@ -232,6 +232,7 @@ class TableView(RowTableShared):
|
||||||
name,
|
name,
|
||||||
hash,
|
hash,
|
||||||
canned_query["sql"],
|
canned_query["sql"],
|
||||||
|
metadata=canned_query,
|
||||||
editable=False,
|
editable=False,
|
||||||
canned_query=table,
|
canned_query=table,
|
||||||
)
|
)
|
||||||
|
|
|
@ -73,7 +73,9 @@ queries inside your ``metadata.json`` file. Here's an example::
|
||||||
"databases": {
|
"databases": {
|
||||||
"sf-trees": {
|
"sf-trees": {
|
||||||
"queries": {
|
"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
|
/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
|
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
|
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
|
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": {
|
"databases": {
|
||||||
"fixtures": {
|
"fixtures": {
|
||||||
"queries": {
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,13 +161,18 @@ METADATA = {
|
||||||
},
|
},
|
||||||
'queries': {
|
'queries': {
|
||||||
'pragma_cache_size': 'PRAGMA cache_size;',
|
'pragma_cache_size': 'PRAGMA cache_size;',
|
||||||
'neighborhood_search': '''
|
'neighborhood_search': {
|
||||||
select neighborhood, facet_cities.name, state
|
'sql': '''
|
||||||
from facetable
|
select neighborhood, facet_cities.name, state
|
||||||
join facet_cities on facetable.city_id = facet_cities.id
|
from facetable
|
||||||
where neighborhood like '%' || :text || '%'
|
join facet_cities
|
||||||
order by neighborhood;
|
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',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -754,3 +754,20 @@ def test_404_trailing_slash_redirect(app_client, path, expected_redirect):
|
||||||
response = app_client.get(path, allow_redirects=False)
|
response = app_client.get(path, allow_redirects=False)
|
||||||
assert 302 == response.status
|
assert 302 == response.status
|
||||||
assert expected_redirect == response.headers["Location"]
|
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()
|
||||||
|
)
|
||||||
|
|
Ładowanie…
Reference in New Issue