From 07e208cc6d9e901b87552c1be2854c220b3f9b6d Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Thu, 2 Apr 2020 18:12:13 -0700 Subject: [PATCH] Refactored .custom_sql() method to new QueryView class Refs #698 --- datasette/views/base.py | 101 ------------------------------- datasette/views/database.py | 117 +++++++++++++++++++++++++++++++++++- datasette/views/table.py | 6 +- 3 files changed, 119 insertions(+), 105 deletions(-) diff --git a/datasette/views/base.py b/datasette/views/base.py index 0a3045ab..840cad54 100644 --- a/datasette/views/base.py +++ b/datasette/views/base.py @@ -461,104 +461,3 @@ class DataView(BaseView): if self.ds.cors: response.headers["Access-Control-Allow-Origin"] = "*" return response - - async def custom_sql( - self, - request, - database, - hash, - sql, - editable=True, - canned_query=None, - metadata=None, - _size=None, - ): - params = request.raw_args - if "sql" in params: - params.pop("sql") - if "_shape" in params: - params.pop("_shape") - # Extract any :named parameters - named_parameters = self.re_named_parameter.findall(sql) - named_parameter_values = { - named_parameter: params.get(named_parameter) or "" - for named_parameter in named_parameters - } - - # Set to blank string if missing from params - for named_parameter in named_parameters: - if named_parameter not in params: - params[named_parameter] = "" - - extra_args = {} - if params.get("_timelimit"): - extra_args["custom_time_limit"] = int(params["_timelimit"]) - if _size: - extra_args["page_size"] = _size - results = await self.ds.execute( - database, sql, params, truncate=True, **extra_args - ) - columns = [r[0] for r in results.description] - - templates = ["query-{}.html".format(to_css_class(database)), "query.html"] - if canned_query: - templates.insert( - 0, - "query-{}-{}.html".format( - to_css_class(database), to_css_class(canned_query) - ), - ) - - async def extra_template(): - display_rows = [] - for row in results.rows: - display_row = [] - for column, value in zip(results.columns, row): - display_value = value - # Let the plugins have a go - # pylint: disable=no-member - plugin_value = pm.hook.render_cell( - value=value, - column=column, - table=None, - database=database, - datasette=self.ds, - ) - if plugin_value is not None: - display_value = plugin_value - else: - if value in ("", None): - display_value = jinja2.Markup(" ") - elif is_url(str(display_value).strip()): - display_value = jinja2.Markup( - '{url}'.format( - url=jinja2.escape(value.strip()) - ) - ) - display_row.append(display_value) - display_rows.append(display_row) - return { - "display_rows": display_rows, - "custom_sql": True, - "named_parameter_values": named_parameter_values, - "editable": editable, - "canned_query": canned_query, - "metadata": metadata, - "config": self.ds.config_dict(), - "request": request, - "path_with_added_args": path_with_added_args, - "path_with_removed_args": path_with_removed_args, - "hide_sql": "_hide_sql" in params, - } - - return ( - { - "database": database, - "rows": results.rows, - "truncated": results.truncated, - "columns": columns, - "query": {"sql": sql, "params": params}, - }, - extra_template, - templates, - ) diff --git a/datasette/views/database.py b/datasette/views/database.py index 31d6af59..0ae06729 100644 --- a/datasette/views/database.py +++ b/datasette/views/database.py @@ -1,7 +1,15 @@ import os +import jinja2 -from datasette.utils import to_css_class, validate_sql_select +from datasette.utils import ( + to_css_class, + validate_sql_select, + is_url, + path_with_added_args, + path_with_removed_args, +) from datasette.utils.asgi import AsgiFileDownload +from datasette.plugins import pm from .base import DatasetteError, DataView @@ -18,7 +26,7 @@ class DatabaseView(DataView): raise DatasetteError("sql= is not allowed", status=400) sql = request.raw_args.pop("sql") validate_sql_select(sql) - return await self.custom_sql( + return await QueryView(self.ds).data( request, database, hash, sql, _size=_size, metadata=metadata ) @@ -85,3 +93,108 @@ class DatabaseDownload(DataView): filename=os.path.basename(filepath), content_type="application/octet-stream", ) + + +class QueryView(DataView): + name = "query" + + async def data( + self, + request, + database, + hash, + sql, + editable=True, + canned_query=None, + metadata=None, + _size=None, + ): + params = request.raw_args + if "sql" in params: + params.pop("sql") + if "_shape" in params: + params.pop("_shape") + # Extract any :named parameters + named_parameters = self.re_named_parameter.findall(sql) + named_parameter_values = { + named_parameter: params.get(named_parameter) or "" + for named_parameter in named_parameters + } + + # Set to blank string if missing from params + for named_parameter in named_parameters: + if named_parameter not in params: + params[named_parameter] = "" + + extra_args = {} + if params.get("_timelimit"): + extra_args["custom_time_limit"] = int(params["_timelimit"]) + if _size: + extra_args["page_size"] = _size + results = await self.ds.execute( + database, sql, params, truncate=True, **extra_args + ) + columns = [r[0] for r in results.description] + + templates = ["query-{}.html".format(to_css_class(database)), "query.html"] + if canned_query: + templates.insert( + 0, + "query-{}-{}.html".format( + to_css_class(database), to_css_class(canned_query) + ), + ) + + async def extra_template(): + display_rows = [] + for row in results.rows: + display_row = [] + for column, value in zip(results.columns, row): + display_value = value + # Let the plugins have a go + # pylint: disable=no-member + plugin_value = pm.hook.render_cell( + value=value, + column=column, + table=None, + database=database, + datasette=self.ds, + ) + if plugin_value is not None: + display_value = plugin_value + else: + if value in ("", None): + display_value = jinja2.Markup(" ") + elif is_url(str(display_value).strip()): + display_value = jinja2.Markup( + '{url}'.format( + url=jinja2.escape(value.strip()) + ) + ) + display_row.append(display_value) + display_rows.append(display_row) + return { + "display_rows": display_rows, + "custom_sql": True, + "named_parameter_values": named_parameter_values, + "editable": editable, + "canned_query": canned_query, + "metadata": metadata, + "config": self.ds.config_dict(), + "request": request, + "path_with_added_args": path_with_added_args, + "path_with_removed_args": path_with_removed_args, + "hide_sql": "_hide_sql" in params, + } + + return ( + { + "database": database, + "rows": results.rows, + "truncated": results.truncated, + "columns": columns, + "query": {"sql": sql, "params": params}, + }, + extra_template, + templates, + ) diff --git a/datasette/views/table.py b/datasette/views/table.py index 12bc8572..10e86eeb 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -26,6 +26,7 @@ from datasette.utils import ( from datasette.utils.asgi import NotFound from datasette.filters import Filters from .base import DataView, DatasetteError, ureg +from .database import QueryView LINK_WITH_LABEL = ( '{label} {id}' @@ -221,8 +222,8 @@ class TableView(RowTableShared): _size=None, ): canned_query = self.ds.get_canned_query(database, table) - if canned_query is not None: - return await self.custom_sql( + if canned_query: + return await QueryView(self.ds).data( request, database, hash, @@ -231,6 +232,7 @@ class TableView(RowTableShared): editable=False, canned_query=table, ) + db = self.ds.databases[database] is_view = bool(await db.get_view_definition(table)) table_exists = bool(await db.table_exists(table))