mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Edit SQL button on canned queries, closes #1019
This commit is contained in:
parent
acf07a6772
commit
f3a087a578
4 changed files with 85 additions and 4 deletions
|
|
@ -447,3 +447,9 @@ svg.dropdown-menu-icon {
|
||||||
border-right: 5px solid transparent;
|
border-right: 5px solid transparent;
|
||||||
border-bottom: 5px solid #666;
|
border-bottom: 5px solid #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.canned-query-edit-sql {
|
||||||
|
padding-left: 0.5em;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@
|
||||||
<button id="sql-format" type="button" hidden>Format SQL</button>
|
<button id="sql-format" type="button" hidden>Format SQL</button>
|
||||||
{% 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">
|
||||||
|
{% 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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import os
|
||||||
import itertools
|
import itertools
|
||||||
import jinja2
|
import jinja2
|
||||||
import json
|
import json
|
||||||
from urllib.parse import parse_qsl
|
from urllib.parse import parse_qsl, urlencode
|
||||||
|
|
||||||
from datasette.utils import (
|
from datasette.utils import (
|
||||||
check_visibility,
|
check_visibility,
|
||||||
|
|
@ -11,6 +11,7 @@ from datasette.utils import (
|
||||||
is_url,
|
is_url,
|
||||||
path_with_added_args,
|
path_with_added_args,
|
||||||
path_with_removed_args,
|
path_with_removed_args,
|
||||||
|
InvalidSql,
|
||||||
)
|
)
|
||||||
from datasette.utils.asgi import AsgiFileDownload, Response, Forbidden
|
from datasette.utils.asgi import AsgiFileDownload, Response, Forbidden
|
||||||
from datasette.plugins import pm
|
from datasette.plugins import pm
|
||||||
|
|
@ -301,6 +302,10 @@ class QueryView(DataView):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
allow_execute_sql = await self.ds.permission_allowed(
|
||||||
|
request.actor, "execute-sql", database, default=True
|
||||||
|
)
|
||||||
|
|
||||||
async def extra_template():
|
async def extra_template():
|
||||||
display_rows = []
|
display_rows = []
|
||||||
for row in results.rows:
|
for row in results.rows:
|
||||||
|
|
@ -329,12 +334,38 @@ class QueryView(DataView):
|
||||||
)
|
)
|
||||||
display_row.append(display_value)
|
display_row.append(display_value)
|
||||||
display_rows.append(display_row)
|
display_rows.append(display_row)
|
||||||
|
|
||||||
|
# Show 'Edit SQL' button only if:
|
||||||
|
# - User is allowed to execute SQL
|
||||||
|
# - SQL is an approved SELECT statement
|
||||||
|
# - No magic parameters, so no :_ in the SQL string
|
||||||
|
edit_sql_url = None
|
||||||
|
is_validated_sql = False
|
||||||
|
try:
|
||||||
|
validate_sql_select(sql)
|
||||||
|
is_validated_sql = True
|
||||||
|
except InvalidSql:
|
||||||
|
pass
|
||||||
|
if allow_execute_sql and is_validated_sql and ":_" not in sql:
|
||||||
|
edit_sql_url = (
|
||||||
|
self.database_url(database)
|
||||||
|
+ "?"
|
||||||
|
+ urlencode(
|
||||||
|
{
|
||||||
|
**{
|
||||||
|
"sql": sql,
|
||||||
|
},
|
||||||
|
**named_parameter_values,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
"display_rows": display_rows,
|
"display_rows": display_rows,
|
||||||
"custom_sql": True,
|
"custom_sql": True,
|
||||||
"named_parameter_values": named_parameter_values,
|
"named_parameter_values": named_parameter_values,
|
||||||
"editable": editable,
|
"editable": editable,
|
||||||
"canned_query": canned_query,
|
"canned_query": canned_query,
|
||||||
|
"edit_sql_url": edit_sql_url,
|
||||||
"metadata": metadata,
|
"metadata": metadata,
|
||||||
"config": self.ds.config_dict(),
|
"config": self.ds.config_dict(),
|
||||||
"request": request,
|
"request": request,
|
||||||
|
|
@ -352,9 +383,7 @@ class QueryView(DataView):
|
||||||
"columns": columns,
|
"columns": columns,
|
||||||
"query": {"sql": sql, "params": params},
|
"query": {"sql": sql, "params": params},
|
||||||
"private": private,
|
"private": private,
|
||||||
"allow_execute_sql": await self.ds.permission_allowed(
|
"allow_execute_sql": allow_execute_sql,
|
||||||
request.actor, "execute-sql", database, default=True
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
extra_template,
|
extra_template,
|
||||||
templates,
|
templates,
|
||||||
|
|
|
||||||
|
|
@ -1403,3 +1403,48 @@ def test_base_url_config(base_url, path):
|
||||||
"href_or_src": href,
|
"href_or_src": href,
|
||||||
"element_parent": str(el.parent),
|
"element_parent": str(el.parent),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path,expected",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"/fixtures/neighborhood_search",
|
||||||
|
"/fixtures?sql=%0Aselect+neighborhood%2C+facet_cities.name%2C+state%0Afrom+facetable%0A++++join+facet_cities%0A++++++++on+facetable.city_id+%3D+facet_cities.id%0Awhere+neighborhood+like+%27%25%27+%7C%7C+%3Atext+%7C%7C+%27%25%27%0Aorder+by+neighborhood%3B%0A&text=",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/fixtures/neighborhood_search?text=ber",
|
||||||
|
"/fixtures?sql=%0Aselect+neighborhood%2C+facet_cities.name%2C+state%0Afrom+facetable%0A++++join+facet_cities%0A++++++++on+facetable.city_id+%3D+facet_cities.id%0Awhere+neighborhood+like+%27%25%27+%7C%7C+%3Atext+%7C%7C+%27%25%27%0Aorder+by+neighborhood%3B%0A&text=ber",
|
||||||
|
),
|
||||||
|
("/fixtures/pragma_cache_size", None),
|
||||||
|
(
|
||||||
|
"/fixtures/𝐜𝐢𝐭𝐢𝐞𝐬",
|
||||||
|
"/fixtures?sql=select+id%2C+name+from+facet_cities+order+by+id+limit+1%3B",
|
||||||
|
),
|
||||||
|
("/fixtures/magic_parameters", None),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_edit_sql_link_on_canned_queries(app_client, path, expected):
|
||||||
|
response = app_client.get(path)
|
||||||
|
expected_link = '<a href="{}" class="canned-query-edit-sql">Edit SQL</a>'.format(
|
||||||
|
expected
|
||||||
|
)
|
||||||
|
if expected:
|
||||||
|
assert expected_link in response.text
|
||||||
|
else:
|
||||||
|
assert "Edit SQL" not in response.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("permission_allowed", [True, False])
|
||||||
|
def test_edit_sql_link_not_shown_if_user_lacks_permission(permission_allowed):
|
||||||
|
with make_app_client(
|
||||||
|
metadata={
|
||||||
|
"allow_sql": None if permission_allowed else {"id": "not-you"},
|
||||||
|
"databases": {"fixtures": {"queries": {"simple": "select 1 + 1"}}},
|
||||||
|
}
|
||||||
|
) as client:
|
||||||
|
response = client.get("/fixtures/simple")
|
||||||
|
if permission_allowed:
|
||||||
|
assert "Edit SQL" in response.text
|
||||||
|
else:
|
||||||
|
assert "Edit SQL" not in response.text
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue