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))