kopia lustrzana https://github.com/simonw/datasette
New hide_sql canned query option, refs #1422
rodzic
b7037f5ece
commit
66e143c76e
|
@ -33,7 +33,9 @@
|
||||||
{% block description_source_license %}{% include "_description_source_license.html" %}{% endblock %}
|
{% block description_source_license %}{% include "_description_source_license.html" %}{% endblock %}
|
||||||
|
|
||||||
<form class="sql" action="{{ urls.database(database) }}{% if canned_query %}/{{ canned_query }}{% endif %}" method="{% if canned_write %}post{% else %}get{% endif %}">
|
<form class="sql" action="{{ urls.database(database) }}{% if canned_query %}/{{ canned_query }}{% endif %}" method="{% if canned_write %}post{% else %}get{% endif %}">
|
||||||
<h3>Custom SQL query{% if display_rows %} returning {% if truncated %}more than {% endif %}{{ "{:,}".format(display_rows|length) }} row{% if display_rows|length == 1 %}{% else %}s{% endif %}{% endif %}{% if not query_error %} <span class="show-hide-sql">{% if hide_sql %}(<a href="{{ path_with_removed_args(request, {'_hide_sql': '1'}) }}">show</a>){% else %}(<a href="{{ path_with_added_args(request, {'_hide_sql': '1'}) }}">hide</a>){% endif %}</span>{% endif %}</h3>
|
<h3>Custom SQL query{% if display_rows %} returning {% if truncated %}more than {% endif %}{{ "{:,}".format(display_rows|length) }} row{% if display_rows|length == 1 %}{% else %}s{% endif %}{% endif %}{% if not query_error %}
|
||||||
|
<span class="show-hide-sql">(<a href="{{ show_hide_link }}">{{ show_hide_text }}</a>)</span>
|
||||||
|
{% endif %}</h3>
|
||||||
{% if error %}
|
{% if error %}
|
||||||
<p class="message-error">{{ error }}</p>
|
<p class="message-error">{{ error }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -44,8 +46,11 @@
|
||||||
<pre id="sql-query">{% if query %}{{ query.sql }}{% endif %}</pre>
|
<pre id="sql-query">{% if query %}{{ query.sql }}{% endif %}</pre>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if not canned_query %}<input type="hidden" name="sql" value="{% if query and query.sql %}{{ query.sql }}{% else %}select * from {{ tables[0].name|escape_sqlite }}{% endif %}">{% endif %}
|
{% if not canned_query %}
|
||||||
<input type="hidden" name="_hide_sql" value="1">
|
<input type="hidden" name="sql"
|
||||||
|
value="{% if query and query.sql %}{{ query.sql }}{% else %}select * from {{ tables[0].name|escape_sqlite }}{% endif %}"
|
||||||
|
>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if named_parameter_values %}
|
{% if named_parameter_values %}
|
||||||
<h3>Query parameters</h3>
|
<h3>Query parameters</h3>
|
||||||
|
@ -54,9 +59,10 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>
|
<p>
|
||||||
<button id="sql-format" type="button" hidden>Format SQL</button>
|
{% if not hide_sql %}<button id="sql-format" type="button" hidden>Format SQL</button>{% endif %}
|
||||||
{% if canned_write %}<input type="hidden" name="csrftoken" value="{{ csrftoken() }}">{% endif %}
|
{% if canned_write %}<input type="hidden" name="csrftoken" value="{{ csrftoken() }}">{% endif %}
|
||||||
<input type="submit" value="Run SQL">
|
<input type="submit" value="Run SQL">
|
||||||
|
{{ show_hide_hidden }}
|
||||||
{% if canned_query and edit_sql_url %}<a href="{{ edit_sql_url }}" class="canned-query-edit-sql">Edit SQL</a>{% endif %}
|
{% if canned_query and edit_sql_url %}<a href="{{ edit_sql_url }}" class="canned-query-edit-sql">Edit SQL</a>{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -5,6 +5,8 @@ import json
|
||||||
from markupsafe import Markup, escape
|
from markupsafe import Markup, escape
|
||||||
from urllib.parse import parse_qsl, urlencode
|
from urllib.parse import parse_qsl, urlencode
|
||||||
|
|
||||||
|
import markupsafe
|
||||||
|
|
||||||
from datasette.utils import (
|
from datasette.utils import (
|
||||||
await_me_maybe,
|
await_me_maybe,
|
||||||
check_visibility,
|
check_visibility,
|
||||||
|
@ -415,6 +417,29 @@ class QueryView(DataView):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
show_hide_hidden = ""
|
||||||
|
if metadata.get("hide_sql"):
|
||||||
|
if bool(params.get("_show_sql")):
|
||||||
|
show_hide_link = path_with_removed_args(request, {"_show_sql"})
|
||||||
|
show_hide_text = "hide"
|
||||||
|
show_hide_hidden = (
|
||||||
|
'<input type="hidden" name="_show_sql" value="1">'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
show_hide_link = path_with_added_args(request, {"_show_sql": 1})
|
||||||
|
show_hide_text = "show"
|
||||||
|
else:
|
||||||
|
if bool(params.get("_hide_sql")):
|
||||||
|
show_hide_link = path_with_removed_args(request, {"_hide_sql"})
|
||||||
|
show_hide_text = "show"
|
||||||
|
show_hide_hidden = (
|
||||||
|
'<input type="hidden" name="_hide_sql" value="1">'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
show_hide_link = path_with_added_args(request, {"_hide_sql": 1})
|
||||||
|
show_hide_text = "hide"
|
||||||
|
hide_sql = show_hide_text == "show"
|
||||||
return {
|
return {
|
||||||
"display_rows": display_rows,
|
"display_rows": display_rows,
|
||||||
"custom_sql": True,
|
"custom_sql": True,
|
||||||
|
@ -425,9 +450,10 @@ class QueryView(DataView):
|
||||||
"metadata": metadata,
|
"metadata": metadata,
|
||||||
"config": self.ds.config_dict(),
|
"config": self.ds.config_dict(),
|
||||||
"request": request,
|
"request": request,
|
||||||
"path_with_added_args": path_with_added_args,
|
"show_hide_link": show_hide_link,
|
||||||
"path_with_removed_args": path_with_removed_args,
|
"show_hide_text": show_hide_text,
|
||||||
"hide_sql": "_hide_sql" in params,
|
"show_hide_hidden": markupsafe.Markup(show_hide_hidden),
|
||||||
|
"hide_sql": hide_sql,
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -674,7 +674,7 @@ The main focus of this release is a major upgrade to the :ref:`plugin_register_o
|
||||||
* Visually distinguish float and integer columns - useful for figuring out why order-by-column might be returning unexpected results. (:issue:`729`)
|
* Visually distinguish float and integer columns - useful for figuring out why order-by-column might be returning unexpected results. (:issue:`729`)
|
||||||
* The :ref:`internals_request`, which is passed to several plugin hooks, is now documented. (:issue:`706`)
|
* The :ref:`internals_request`, which is passed to several plugin hooks, is now documented. (:issue:`706`)
|
||||||
* New ``metadata.json`` option for setting a custom default page size for specific tables and views, see :ref:`metadata_page_size`. (:issue:`751`)
|
* New ``metadata.json`` option for setting a custom default page size for specific tables and views, see :ref:`metadata_page_size`. (:issue:`751`)
|
||||||
* Canned queries can now be configured with a default URL fragment hash, useful when working with plugins such as `datasette-vega <https://github.com/simonw/datasette-vega>`__, see :ref:`canned_queries_default_fragment`. (:issue:`706`)
|
* Canned queries can now be configured with a default URL fragment hash, useful when working with plugins such as `datasette-vega <https://github.com/simonw/datasette-vega>`__, see :ref:`canned_queries_options`. (:issue:`706`)
|
||||||
* Fixed a bug in ``datasette publish`` when running on operating systems where the ``/tmp`` directory lives in a different volume, using a backport of the Python 3.8 ``shutil.copytree()`` function. (:issue:`744`)
|
* Fixed a bug in ``datasette publish`` when running on operating systems where the ``/tmp`` directory lives in a different volume, using a backport of the Python 3.8 ``shutil.copytree()`` function. (:issue:`744`)
|
||||||
* Every plugin hook is now covered by the unit tests, and a new unit test checks that each plugin hook has at least one corresponding test. (:issue:`771`, :issue:`773`)
|
* Every plugin hook is now covered by the unit tests, and a new unit test checks that each plugin hook has at least one corresponding test. (:issue:`771`, :issue:`773`)
|
||||||
|
|
||||||
|
|
|
@ -187,14 +187,28 @@ You can alternatively provide an explicit list of named parameters using the ``"
|
||||||
order by neighborhood
|
order by neighborhood
|
||||||
title: Search neighborhoods
|
title: Search neighborhoods
|
||||||
|
|
||||||
.. _canned_queries_default_fragment:
|
.. _canned_queries_options:
|
||||||
|
|
||||||
Setting a default fragment
|
Additional canned query options
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Additional options can be specified for canned queries in the YAML or JSON configuration.
|
||||||
|
|
||||||
|
hide_sql
|
||||||
|
++++++++
|
||||||
|
|
||||||
|
Canned queries default to displaying their SQL query at the top of the page. If the query is extremely long you may want to hide it by default, with a "show" link that can be used to make it visible.
|
||||||
|
|
||||||
|
Add the ``"hide_sql": true`` option to hide the SQL query by default.
|
||||||
|
|
||||||
|
fragment
|
||||||
|
++++++++
|
||||||
|
|
||||||
Some plugins, such as `datasette-vega <https://github.com/simonw/datasette-vega>`__, can be configured by including additional data in the fragment hash of the URL - the bit that comes after a ``#`` symbol.
|
Some plugins, such as `datasette-vega <https://github.com/simonw/datasette-vega>`__, can be configured by including additional data in the fragment hash of the URL - the bit that comes after a ``#`` symbol.
|
||||||
|
|
||||||
You can set a default fragment hash that will be included in the link to the canned query from the database index page using the ``"fragment"`` key:
|
You can set a default fragment hash that will be included in the link to the canned query from the database index page using the ``"fragment"`` key.
|
||||||
|
|
||||||
|
This example demonstrates both ``fragment`` and ``hide_sql``:
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
|
@ -204,7 +218,8 @@ You can set a default fragment hash that will be included in the link to the can
|
||||||
"queries": {
|
"queries": {
|
||||||
"neighborhood_search": {
|
"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;",
|
"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;",
|
||||||
"fragment": "fragment-goes-here"
|
"fragment": "fragment-goes-here",
|
||||||
|
"hide_sql": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -360,6 +360,7 @@ METADATA = {
|
||||||
"title": "Search neighborhoods",
|
"title": "Search neighborhoods",
|
||||||
"description_html": "<b>Demonstrating</b> simple like search",
|
"description_html": "<b>Demonstrating</b> simple like search",
|
||||||
"fragment": "fragment-goes-here",
|
"fragment": "fragment-goes-here",
|
||||||
|
"hide_sql": True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1241,7 +1241,7 @@ def test_show_hide_sql_query(app_client):
|
||||||
def test_canned_query_with_hide_has_no_hidden_sql(app_client):
|
def test_canned_query_with_hide_has_no_hidden_sql(app_client):
|
||||||
# For a canned query the show/hide should NOT have a hidden SQL field
|
# For a canned query the show/hide should NOT have a hidden SQL field
|
||||||
# https://github.com/simonw/datasette/issues/1411
|
# https://github.com/simonw/datasette/issues/1411
|
||||||
response = app_client.get("/fixtures/neighborhood_search?_hide_sql=1")
|
response = app_client.get("/fixtures/pragma_cache_size?_hide_sql=1")
|
||||||
soup = Soup(response.body, "html.parser")
|
soup = Soup(response.body, "html.parser")
|
||||||
hiddens = soup.find("form").select("input[type=hidden]")
|
hiddens = soup.find("form").select("input[type=hidden]")
|
||||||
assert [
|
assert [
|
||||||
|
@ -1249,6 +1249,55 @@ def test_canned_query_with_hide_has_no_hidden_sql(app_client):
|
||||||
] == [(hidden["name"], hidden["value"]) for hidden in hiddens]
|
] == [(hidden["name"], hidden["value"]) for hidden in hiddens]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hide_sql,querystring,expected_hidden,expected_show_hide_link,expected_show_hide_text",
|
||||||
|
(
|
||||||
|
(False, "", None, "/_memory/one?_hide_sql=1", "hide"),
|
||||||
|
(False, "?_hide_sql=1", "_hide_sql", "/_memory/one", "show"),
|
||||||
|
(True, "", None, "/_memory/one?_show_sql=1", "show"),
|
||||||
|
(True, "?_show_sql=1", "_show_sql", "/_memory/one", "hide"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_canned_query_show_hide_metadata_option(
|
||||||
|
hide_sql,
|
||||||
|
querystring,
|
||||||
|
expected_hidden,
|
||||||
|
expected_show_hide_link,
|
||||||
|
expected_show_hide_text,
|
||||||
|
):
|
||||||
|
with make_app_client(
|
||||||
|
metadata={
|
||||||
|
"databases": {
|
||||||
|
"_memory": {
|
||||||
|
"queries": {
|
||||||
|
"one": {
|
||||||
|
"sql": "select 1 + 1",
|
||||||
|
"hide_sql": hide_sql,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
memory=True,
|
||||||
|
) as client:
|
||||||
|
expected_show_hide_fragment = '(<a href="{}">{}</a>)'.format(
|
||||||
|
expected_show_hide_link, expected_show_hide_text
|
||||||
|
)
|
||||||
|
response = client.get("/_memory/one" + querystring)
|
||||||
|
html = response.text
|
||||||
|
show_hide_fragment = html.split('<span class="show-hide-sql">')[1].split(
|
||||||
|
"</span>"
|
||||||
|
)[0]
|
||||||
|
assert show_hide_fragment == expected_show_hide_fragment
|
||||||
|
if expected_hidden:
|
||||||
|
assert (
|
||||||
|
'<input type="hidden" name="{}" value="1">'.format(expected_hidden)
|
||||||
|
in html
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
assert '<input type="hidden" ' not in html
|
||||||
|
|
||||||
|
|
||||||
def test_extra_where_clauses(app_client):
|
def test_extra_where_clauses(app_client):
|
||||||
response = app_client.get(
|
response = app_client.get(
|
||||||
"/fixtures/facetable?_where=neighborhood='Dogpatch'&_where=city_id=1"
|
"/fixtures/facetable?_where=neighborhood='Dogpatch'&_where=city_id=1"
|
||||||
|
|
Ładowanie…
Reference in New Issue