URLify URLs in custom SQL queries, closes #298

This commit is contained in:
Simon Willison 2018-07-23 20:56:32 -07:00
commit 581b4c97ee
No known key found for this signature in database
GPG key ID: 17E2DEA2588B7F52
3 changed files with 47 additions and 12 deletions

View file

@ -26,7 +26,7 @@
{% block description_source_license %}{% include "_description_source_license.html" %}{% endblock %} {% 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 display_rows %} returning {% if truncated %}more than {% endif %}{{ "{:,}".format(display_rows|length) }} row{% if display_rows|length == 1 %}{% else %}s{% endif %}{% endif %}</h3>
{% if editable and config.allow_sql %} {% if editable and config.allow_sql %}
<p><textarea name="sql">{% if query and query.sql %}{{ query.sql }}{% else %}select * from {{ tables[0].name|escape_sqlite }}{% endif %}</textarea></p> <p><textarea name="sql">{% if query and query.sql %}{{ query.sql }}{% else %}select * from {{ tables[0].name|escape_sqlite }}{% endif %}</textarea></p>
{% else %} {% else %}
@ -41,7 +41,7 @@
<p><input type="submit" value="Run SQL"></p> <p><input type="submit" value="Run SQL"></p>
</form> </form>
{% if rows %} {% if display_rows %}
<p class="export-links">This data as <a href="{{ url_json }}">JSON</a>, <a href="{{ url_csv }}">CSV</a></p> <p class="export-links">This data as <a href="{{ url_json }}">JSON</a>, <a href="{{ url_csv }}">CSV</a></p>
<table class="rows-and-columns"> <table class="rows-and-columns">
<thead> <thead>
@ -50,7 +50,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for row in rows %} {% for row in display_rows %}
<tr> <tr>
{% for column, td in zip(columns, row) %} {% for column, td in zip(columns, row) %}
<td class="col-{{ column|to_css_class }}">{% if td == None %}{{ "&nbsp;"|safe }}{% else %}{{ td }}{% endif %}</td> <td class="col-{{ column|to_css_class }}">{% if td == None %}{{ "&nbsp;"|safe }}{% else %}{{ td }}{% endif %}</td>

View file

@ -6,6 +6,7 @@ import sqlite3
import time import time
import urllib import urllib
import jinja2
import pint import pint
from sanic import response from sanic import response
from sanic.exceptions import NotFound from sanic.exceptions import NotFound
@ -17,6 +18,7 @@ from datasette.utils import (
InterruptedError, InterruptedError,
InvalidSql, InvalidSql,
LimitedWriter, LimitedWriter,
is_url,
path_from_row_pks, path_from_row_pks,
path_with_added_args, path_with_added_args,
path_with_format, path_with_format,
@ -485,21 +487,40 @@ class BaseView(RenderMixin):
), ),
) )
async def extra_template():
display_rows = []
for row in results.rows:
display_row = []
for value in row:
display_value = value
if value in ("", None):
display_value = jinja2.Markup("&nbsp;")
elif is_url(str(value).strip()):
display_value = jinja2.Markup(
'<a href="{url}">{url}</a>'.format(
url=jinja2.escape(value.strip())
)
)
display_row.append(display_value)
display_rows.append(display_row)
return {
"display_rows": display_rows,
"database_hash": hash,
"custom_sql": True,
"named_parameter_values": named_parameter_values,
"editable": editable,
"canned_query": canned_query,
"metadata": metadata,
"config": self.ds.config,
}
return { return {
"database": name, "database": name,
"rows": results.rows, "rows": results.rows,
"truncated": results.truncated, "truncated": results.truncated,
"columns": columns, "columns": columns,
"query": {"sql": sql, "params": params}, "query": {"sql": sql, "params": params},
}, { }, extra_template, templates
"database_hash": hash,
"custom_sql": True,
"named_parameter_values": named_parameter_values,
"editable": editable,
"canned_query": canned_query,
"metadata": metadata,
"config": self.ds.config,
}, templates
def convert_specific_columns_to_json(rows, columns, json_cols): def convert_specific_columns_to_json(rows, columns, json_cols):

View file

@ -793,3 +793,17 @@ def test_advanced_export_box(app_client, path, has_object, has_stream, has_expan
# "expand labels" option # "expand labels" option
if has_expand: if has_expand:
assert "expand labels" in str(div) assert "expand labels" in str(div)
def test_urlify_custom_queries(app_client):
path = "/fixtures?" + urllib.parse.urlencode({
"sql": "select ('https://twitter.com/' || 'simonw') as user_url;"
})
response = app_client.get(path)
assert response.status == 200
soup = Soup(response.body, "html.parser")
assert '''<td class="col-user_url">
<a href="https://twitter.com/simonw">
https://twitter.com/simonw
</a>
</td>''' == soup.find("td", {"class": "col-user_url"}).prettify().strip()