mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Apply black to everything, enforce via unit tests (#449)
I've run the black code formatting tool against everything:
black tests datasette setup.py
I also added a new unit test, in tests/test_black.py, which will fail if the code does not
conform to black's exacting standards.
This unit test only runs on Python 3.6 or higher, because black itself doesn't run on 3.5.
This commit is contained in:
parent
66c87cee0c
commit
35d6ee2790
31 changed files with 2758 additions and 2702 deletions
|
|
@ -33,8 +33,15 @@ HASH_LENGTH = 7
|
|||
|
||||
|
||||
class DatasetteError(Exception):
|
||||
|
||||
def __init__(self, message, title=None, error_dict=None, status=500, template=None, messagge_is_html=False):
|
||||
def __init__(
|
||||
self,
|
||||
message,
|
||||
title=None,
|
||||
error_dict=None,
|
||||
status=500,
|
||||
template=None,
|
||||
messagge_is_html=False,
|
||||
):
|
||||
self.message = message
|
||||
self.title = title
|
||||
self.error_dict = error_dict or {}
|
||||
|
|
@ -43,18 +50,19 @@ class DatasetteError(Exception):
|
|||
|
||||
|
||||
class RenderMixin(HTTPMethodView):
|
||||
|
||||
def _asset_urls(self, key, template, context):
|
||||
# Flatten list-of-lists from plugins:
|
||||
seen_urls = set()
|
||||
for url_or_dict in itertools.chain(
|
||||
itertools.chain.from_iterable(getattr(pm.hook, key)(
|
||||
template=template.name,
|
||||
database=context.get("database"),
|
||||
table=context.get("table"),
|
||||
datasette=self.ds
|
||||
)),
|
||||
(self.ds.metadata(key) or [])
|
||||
itertools.chain.from_iterable(
|
||||
getattr(pm.hook, key)(
|
||||
template=template.name,
|
||||
database=context.get("database"),
|
||||
table=context.get("table"),
|
||||
datasette=self.ds,
|
||||
)
|
||||
),
|
||||
(self.ds.metadata(key) or []),
|
||||
):
|
||||
if isinstance(url_or_dict, dict):
|
||||
url = url_or_dict["url"]
|
||||
|
|
@ -73,14 +81,12 @@ class RenderMixin(HTTPMethodView):
|
|||
def database_url(self, database):
|
||||
db = self.ds.databases[database]
|
||||
if self.ds.config("hash_urls") and db.hash:
|
||||
return "/{}-{}".format(
|
||||
database, db.hash[:HASH_LENGTH]
|
||||
)
|
||||
return "/{}-{}".format(database, db.hash[:HASH_LENGTH])
|
||||
else:
|
||||
return "/{}".format(database)
|
||||
|
||||
def database_color(self, database):
|
||||
return 'ff0000'
|
||||
return "ff0000"
|
||||
|
||||
def render(self, templates, **context):
|
||||
template = self.ds.jinja_env.select_template(templates)
|
||||
|
|
@ -95,7 +101,7 @@ class RenderMixin(HTTPMethodView):
|
|||
database=context.get("database"),
|
||||
table=context.get("table"),
|
||||
view_name=self.name,
|
||||
datasette=self.ds
|
||||
datasette=self.ds,
|
||||
):
|
||||
body_scripts.append(jinja2.Markup(script))
|
||||
return response.html(
|
||||
|
|
@ -116,14 +122,14 @@ class RenderMixin(HTTPMethodView):
|
|||
"format_bytes": format_bytes,
|
||||
"database_url": self.database_url,
|
||||
"database_color": self.database_color,
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class BaseView(RenderMixin):
|
||||
name = ''
|
||||
name = ""
|
||||
re_named_parameter = re.compile(":([a-zA-Z0-9_]+)")
|
||||
|
||||
def __init__(self, datasette):
|
||||
|
|
@ -171,32 +177,30 @@ class BaseView(RenderMixin):
|
|||
expected = "000"
|
||||
if db.hash is not None:
|
||||
expected = db.hash[:HASH_LENGTH]
|
||||
correct_hash_provided = (expected == hash)
|
||||
correct_hash_provided = expected == hash
|
||||
|
||||
if not correct_hash_provided:
|
||||
if "table_and_format" in kwargs:
|
||||
|
||||
async def async_table_exists(t):
|
||||
return await self.ds.table_exists(name, t)
|
||||
|
||||
table, _format = await resolve_table_and_format(
|
||||
table_and_format=urllib.parse.unquote_plus(
|
||||
kwargs["table_and_format"]
|
||||
),
|
||||
table_exists=async_table_exists,
|
||||
allowed_formats=self.ds.renderers.keys()
|
||||
allowed_formats=self.ds.renderers.keys(),
|
||||
)
|
||||
kwargs["table"] = table
|
||||
if _format:
|
||||
kwargs["as_format"] = ".{}".format(_format)
|
||||
elif "table" in kwargs:
|
||||
kwargs["table"] = urllib.parse.unquote_plus(
|
||||
kwargs["table"]
|
||||
)
|
||||
kwargs["table"] = urllib.parse.unquote_plus(kwargs["table"])
|
||||
|
||||
should_redirect = "/{}-{}".format(name, expected)
|
||||
if "table" in kwargs:
|
||||
should_redirect += "/" + urllib.parse.quote_plus(
|
||||
kwargs["table"]
|
||||
)
|
||||
should_redirect += "/" + urllib.parse.quote_plus(kwargs["table"])
|
||||
if "pk_path" in kwargs:
|
||||
should_redirect += "/" + kwargs["pk_path"]
|
||||
if "as_format" in kwargs:
|
||||
|
|
@ -219,7 +223,9 @@ class BaseView(RenderMixin):
|
|||
if should_redirect:
|
||||
return self.redirect(request, should_redirect, remove_args={"_hash"})
|
||||
|
||||
return await self.view_get(request, database, hash, correct_hash_provided, **kwargs)
|
||||
return await self.view_get(
|
||||
request, database, hash, correct_hash_provided, **kwargs
|
||||
)
|
||||
|
||||
async def as_csv(self, request, database, hash, **kwargs):
|
||||
stream = request.args.get("_stream")
|
||||
|
|
@ -228,9 +234,7 @@ class BaseView(RenderMixin):
|
|||
if not self.ds.config("allow_csv_stream"):
|
||||
raise DatasetteError("CSV streaming is disabled", status=400)
|
||||
if request.args.get("_next"):
|
||||
raise DatasetteError(
|
||||
"_next not allowed for CSV streaming", status=400
|
||||
)
|
||||
raise DatasetteError("_next not allowed for CSV streaming", status=400)
|
||||
kwargs["_size"] = "max"
|
||||
# Fetch the first page
|
||||
try:
|
||||
|
|
@ -271,9 +275,7 @@ class BaseView(RenderMixin):
|
|||
if next:
|
||||
kwargs["_next"] = next
|
||||
if not first:
|
||||
data, _, _ = await self.data(
|
||||
request, database, hash, **kwargs
|
||||
)
|
||||
data, _, _ = await self.data(request, database, hash, **kwargs)
|
||||
if first:
|
||||
writer.writerow(headings)
|
||||
first = False
|
||||
|
|
@ -293,7 +295,7 @@ class BaseView(RenderMixin):
|
|||
new_row.append(cell)
|
||||
writer.writerow(new_row)
|
||||
except Exception as e:
|
||||
print('caught this', e)
|
||||
print("caught this", e)
|
||||
r.write(str(e))
|
||||
return
|
||||
|
||||
|
|
@ -304,15 +306,11 @@ class BaseView(RenderMixin):
|
|||
if request.args.get("_dl", None):
|
||||
content_type = "text/csv; charset=utf-8"
|
||||
disposition = 'attachment; filename="{}.csv"'.format(
|
||||
kwargs.get('table', database)
|
||||
kwargs.get("table", database)
|
||||
)
|
||||
headers["Content-Disposition"] = disposition
|
||||
|
||||
return response.stream(
|
||||
stream_fn,
|
||||
headers=headers,
|
||||
content_type=content_type
|
||||
)
|
||||
return response.stream(stream_fn, headers=headers, content_type=content_type)
|
||||
|
||||
async def get_format(self, request, database, args):
|
||||
""" Determine the format of the response from the request, from URL
|
||||
|
|
@ -325,22 +323,20 @@ class BaseView(RenderMixin):
|
|||
if not _format:
|
||||
_format = (args.pop("as_format", None) or "").lstrip(".")
|
||||
if "table_and_format" in args:
|
||||
|
||||
async def async_table_exists(t):
|
||||
return await self.ds.table_exists(database, t)
|
||||
|
||||
table, _ext_format = await resolve_table_and_format(
|
||||
table_and_format=urllib.parse.unquote_plus(
|
||||
args["table_and_format"]
|
||||
),
|
||||
table_and_format=urllib.parse.unquote_plus(args["table_and_format"]),
|
||||
table_exists=async_table_exists,
|
||||
allowed_formats=self.ds.renderers.keys()
|
||||
allowed_formats=self.ds.renderers.keys(),
|
||||
)
|
||||
_format = _format or _ext_format
|
||||
args["table"] = table
|
||||
del args["table_and_format"]
|
||||
elif "table" in args:
|
||||
args["table"] = urllib.parse.unquote_plus(
|
||||
args["table"]
|
||||
)
|
||||
args["table"] = urllib.parse.unquote_plus(args["table"])
|
||||
return _format, args
|
||||
|
||||
async def view_get(self, request, database, hash, correct_hash_provided, **kwargs):
|
||||
|
|
@ -351,7 +347,7 @@ class BaseView(RenderMixin):
|
|||
|
||||
if _format is None:
|
||||
# HTML views default to expanding all foriegn key labels
|
||||
kwargs['default_labels'] = True
|
||||
kwargs["default_labels"] = True
|
||||
|
||||
extra_template_data = {}
|
||||
start = time.time()
|
||||
|
|
@ -367,11 +363,16 @@ class BaseView(RenderMixin):
|
|||
else:
|
||||
data, extra_template_data, templates = response_or_template_contexts
|
||||
except InterruptedError:
|
||||
raise DatasetteError("""
|
||||
raise DatasetteError(
|
||||
"""
|
||||
SQL query took too long. The time limit is controlled by the
|
||||
<a href="https://datasette.readthedocs.io/en/stable/config.html#sql-time-limit-ms">sql_time_limit_ms</a>
|
||||
configuration option.
|
||||
""", title="SQL Interrupted", status=400, messagge_is_html=True)
|
||||
""",
|
||||
title="SQL Interrupted",
|
||||
status=400,
|
||||
messagge_is_html=True,
|
||||
)
|
||||
except (sqlite3.OperationalError, InvalidSql) as e:
|
||||
raise DatasetteError(str(e), title="Invalid SQL", status=400)
|
||||
|
||||
|
|
@ -408,14 +409,14 @@ class BaseView(RenderMixin):
|
|||
raise NotFound("No data")
|
||||
|
||||
response_args = {
|
||||
'content_type': result.get('content_type', 'text/plain'),
|
||||
'status': result.get('status_code', 200)
|
||||
"content_type": result.get("content_type", "text/plain"),
|
||||
"status": result.get("status_code", 200),
|
||||
}
|
||||
|
||||
if type(result.get('body')) == bytes:
|
||||
response_args['body_bytes'] = result.get('body')
|
||||
if type(result.get("body")) == bytes:
|
||||
response_args["body_bytes"] = result.get("body")
|
||||
else:
|
||||
response_args['body'] = result.get('body')
|
||||
response_args["body"] = result.get("body")
|
||||
|
||||
r = response.HTTPResponse(**response_args)
|
||||
else:
|
||||
|
|
@ -431,14 +432,12 @@ class BaseView(RenderMixin):
|
|||
url_labels_extra = {"_labels": "on"}
|
||||
|
||||
renderers = {
|
||||
key: path_with_format(request, key, {**url_labels_extra}) for key in self.ds.renderers.keys()
|
||||
}
|
||||
url_csv_args = {
|
||||
"_size": "max",
|
||||
**url_labels_extra
|
||||
key: path_with_format(request, key, {**url_labels_extra})
|
||||
for key in self.ds.renderers.keys()
|
||||
}
|
||||
url_csv_args = {"_size": "max", **url_labels_extra}
|
||||
url_csv = path_with_format(request, "csv", url_csv_args)
|
||||
url_csv_path = url_csv.split('?')[0]
|
||||
url_csv_path = url_csv.split("?")[0]
|
||||
context = {
|
||||
**data,
|
||||
**extras,
|
||||
|
|
@ -450,10 +449,11 @@ class BaseView(RenderMixin):
|
|||
(key, value)
|
||||
for key, value in urllib.parse.parse_qsl(request.query_string)
|
||||
if key not in ("_labels", "_facet", "_size")
|
||||
] + [("_size", "max")],
|
||||
]
|
||||
+ [("_size", "max")],
|
||||
"datasette_version": __version__,
|
||||
"config": self.ds.config_dict(),
|
||||
}
|
||||
},
|
||||
}
|
||||
if "metadata" not in context:
|
||||
context["metadata"] = self.ds.metadata
|
||||
|
|
@ -474,9 +474,9 @@ class BaseView(RenderMixin):
|
|||
if self.ds.cache_headers and response.status == 200:
|
||||
ttl = int(ttl)
|
||||
if ttl == 0:
|
||||
ttl_header = 'no-cache'
|
||||
ttl_header = "no-cache"
|
||||
else:
|
||||
ttl_header = 'max-age={}'.format(ttl)
|
||||
ttl_header = "max-age={}".format(ttl)
|
||||
response.headers["Cache-Control"] = ttl_header
|
||||
response.headers["Referrer-Policy"] = "no-referrer"
|
||||
if self.ds.cors:
|
||||
|
|
@ -484,8 +484,15 @@ class BaseView(RenderMixin):
|
|||
return response
|
||||
|
||||
async def custom_sql(
|
||||
self, request, database, hash, sql, editable=True, canned_query=None,
|
||||
metadata=None, _size=None
|
||||
self,
|
||||
request,
|
||||
database,
|
||||
hash,
|
||||
sql,
|
||||
editable=True,
|
||||
canned_query=None,
|
||||
metadata=None,
|
||||
_size=None,
|
||||
):
|
||||
params = request.raw_args
|
||||
if "sql" in params:
|
||||
|
|
@ -565,10 +572,14 @@ class BaseView(RenderMixin):
|
|||
"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
|
||||
return (
|
||||
{
|
||||
"database": database,
|
||||
"rows": results.rows,
|
||||
"truncated": results.truncated,
|
||||
"columns": columns,
|
||||
"query": {"sql": sql, "params": params},
|
||||
},
|
||||
extra_template,
|
||||
templates,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from .base import HASH_LENGTH, RenderMixin
|
|||
|
||||
|
||||
class IndexView(RenderMixin):
|
||||
name = 'index'
|
||||
name = "index"
|
||||
|
||||
def __init__(self, datasette):
|
||||
self.ds = datasette
|
||||
|
|
@ -43,23 +43,25 @@ class IndexView(RenderMixin):
|
|||
}
|
||||
hidden_tables = [t for t in tables.values() if t["hidden"]]
|
||||
|
||||
databases.append({
|
||||
"name": name,
|
||||
"hash": db.hash,
|
||||
"color": db.hash[:6] if db.hash else hashlib.md5(name.encode("utf8")).hexdigest()[:6],
|
||||
"path": self.database_url(name),
|
||||
"tables_truncated": sorted(
|
||||
tables.values(), key=lambda t: t["count"] or 0, reverse=True
|
||||
)[
|
||||
:5
|
||||
],
|
||||
"tables_count": len(tables),
|
||||
"tables_more": len(tables) > 5,
|
||||
"table_rows_sum": sum((t["count"] or 0) for t in tables.values()),
|
||||
"hidden_table_rows_sum": sum(t["count"] for t in hidden_tables),
|
||||
"hidden_tables_count": len(hidden_tables),
|
||||
"views_count": len(views),
|
||||
})
|
||||
databases.append(
|
||||
{
|
||||
"name": name,
|
||||
"hash": db.hash,
|
||||
"color": db.hash[:6]
|
||||
if db.hash
|
||||
else hashlib.md5(name.encode("utf8")).hexdigest()[:6],
|
||||
"path": self.database_url(name),
|
||||
"tables_truncated": sorted(
|
||||
tables.values(), key=lambda t: t["count"] or 0, reverse=True
|
||||
)[:5],
|
||||
"tables_count": len(tables),
|
||||
"tables_more": len(tables) > 5,
|
||||
"table_rows_sum": sum((t["count"] or 0) for t in tables.values()),
|
||||
"hidden_table_rows_sum": sum(t["count"] for t in hidden_tables),
|
||||
"hidden_tables_count": len(hidden_tables),
|
||||
"views_count": len(views),
|
||||
}
|
||||
)
|
||||
if as_format:
|
||||
headers = {}
|
||||
if self.ds.cors:
|
||||
|
|
|
|||
|
|
@ -18,14 +18,8 @@ class JsonDataView(RenderMixin):
|
|||
if self.ds.cors:
|
||||
headers["Access-Control-Allow-Origin"] = "*"
|
||||
return response.HTTPResponse(
|
||||
json.dumps(data),
|
||||
content_type="application/json",
|
||||
headers=headers
|
||||
json.dumps(data), content_type="application/json", headers=headers
|
||||
)
|
||||
|
||||
else:
|
||||
return self.render(
|
||||
["show_json.html"],
|
||||
filename=self.filename,
|
||||
data=data
|
||||
)
|
||||
return self.render(["show_json.html"], filename=self.filename, data=data)
|
||||
|
|
|
|||
|
|
@ -31,12 +31,13 @@ from datasette.utils import (
|
|||
from datasette.filters import Filters
|
||||
from .base import BaseView, DatasetteError, ureg
|
||||
|
||||
LINK_WITH_LABEL = '<a href="/{database}/{table}/{link_id}">{label}</a> <em>{id}</em>'
|
||||
LINK_WITH_LABEL = (
|
||||
'<a href="/{database}/{table}/{link_id}">{label}</a> <em>{id}</em>'
|
||||
)
|
||||
LINK_WITH_VALUE = '<a href="/{database}/{table}/{link_id}">{id}</a>'
|
||||
|
||||
|
||||
class RowTableShared(BaseView):
|
||||
|
||||
async def sortable_columns_for_table(self, database, table, use_rowid):
|
||||
table_metadata = self.ds.table_metadata(database, table)
|
||||
if "sortable_columns" in table_metadata:
|
||||
|
|
@ -51,18 +52,14 @@ class RowTableShared(BaseView):
|
|||
# Returns list of (fk_dict, label_column-or-None) pairs for that table
|
||||
expandables = []
|
||||
for fk in await self.ds.foreign_keys_for_table(database, table):
|
||||
label_column = await self.ds.label_column_for_table(database, fk["other_table"])
|
||||
label_column = await self.ds.label_column_for_table(
|
||||
database, fk["other_table"]
|
||||
)
|
||||
expandables.append((fk, label_column))
|
||||
return expandables
|
||||
|
||||
async def display_columns_and_rows(
|
||||
self,
|
||||
database,
|
||||
table,
|
||||
description,
|
||||
rows,
|
||||
link_column=False,
|
||||
truncate_cells=0,
|
||||
self, database, table, description, rows, link_column=False, truncate_cells=0
|
||||
):
|
||||
"Returns columns, rows for specified table - including fancy foreign key treatment"
|
||||
table_metadata = self.ds.table_metadata(database, table)
|
||||
|
|
@ -121,8 +118,10 @@ class RowTableShared(BaseView):
|
|||
if plugin_display_value is not None:
|
||||
display_value = plugin_display_value
|
||||
elif isinstance(value, bytes):
|
||||
display_value = jinja2.Markup("<Binary data: {} byte{}>".format(
|
||||
len(value), "" if len(value) == 1 else "s")
|
||||
display_value = jinja2.Markup(
|
||||
"<Binary data: {} byte{}>".format(
|
||||
len(value), "" if len(value) == 1 else "s"
|
||||
)
|
||||
)
|
||||
elif isinstance(value, dict):
|
||||
# It's an expanded foreign key - display link to other row
|
||||
|
|
@ -133,13 +132,15 @@ class RowTableShared(BaseView):
|
|||
link_template = (
|
||||
LINK_WITH_LABEL if (label != value) else LINK_WITH_VALUE
|
||||
)
|
||||
display_value = jinja2.Markup(link_template.format(
|
||||
database=database,
|
||||
table=urllib.parse.quote_plus(other_table),
|
||||
link_id=urllib.parse.quote_plus(str(value)),
|
||||
id=str(jinja2.escape(value)),
|
||||
label=str(jinja2.escape(label)),
|
||||
))
|
||||
display_value = jinja2.Markup(
|
||||
link_template.format(
|
||||
database=database,
|
||||
table=urllib.parse.quote_plus(other_table),
|
||||
link_id=urllib.parse.quote_plus(str(value)),
|
||||
id=str(jinja2.escape(value)),
|
||||
label=str(jinja2.escape(label)),
|
||||
)
|
||||
)
|
||||
elif value in ("", None):
|
||||
display_value = jinja2.Markup(" ")
|
||||
elif is_url(str(value).strip()):
|
||||
|
|
@ -180,9 +181,18 @@ class RowTableShared(BaseView):
|
|||
|
||||
|
||||
class TableView(RowTableShared):
|
||||
name = 'table'
|
||||
name = "table"
|
||||
|
||||
async def data(self, request, database, hash, table, default_labels=False, _next=None, _size=None):
|
||||
async def data(
|
||||
self,
|
||||
request,
|
||||
database,
|
||||
hash,
|
||||
table,
|
||||
default_labels=False,
|
||||
_next=None,
|
||||
_size=None,
|
||||
):
|
||||
canned_query = self.ds.get_canned_query(database, table)
|
||||
if canned_query is not None:
|
||||
return await self.custom_sql(
|
||||
|
|
@ -271,12 +281,13 @@ class TableView(RowTableShared):
|
|||
raise DatasetteError("_where= is not allowed", status=400)
|
||||
else:
|
||||
where_clauses.extend(request.args["_where"])
|
||||
extra_wheres_for_ui = [{
|
||||
"text": text,
|
||||
"remove_url": path_with_removed_args(
|
||||
request, {"_where": text}
|
||||
)
|
||||
} for text in request.args["_where"]]
|
||||
extra_wheres_for_ui = [
|
||||
{
|
||||
"text": text,
|
||||
"remove_url": path_with_removed_args(request, {"_where": text}),
|
||||
}
|
||||
for text in request.args["_where"]
|
||||
]
|
||||
|
||||
# _search support:
|
||||
fts_table = special_args.get("_fts_table")
|
||||
|
|
@ -296,8 +307,7 @@ class TableView(RowTableShared):
|
|||
search = search_args["_search"]
|
||||
where_clauses.append(
|
||||
"{fts_pk} in (select rowid from {fts_table} where {fts_table} match :search)".format(
|
||||
fts_table=escape_sqlite(fts_table),
|
||||
fts_pk=escape_sqlite(fts_pk)
|
||||
fts_table=escape_sqlite(fts_table), fts_pk=escape_sqlite(fts_pk)
|
||||
)
|
||||
)
|
||||
search_descriptions.append('search matches "{}"'.format(search))
|
||||
|
|
@ -306,14 +316,16 @@ class TableView(RowTableShared):
|
|||
# More complex: search against specific columns
|
||||
for i, (key, search_text) in enumerate(search_args.items()):
|
||||
search_col = key.split("_search_", 1)[1]
|
||||
if search_col not in await self.ds.table_columns(database, fts_table):
|
||||
if search_col not in await self.ds.table_columns(
|
||||
database, fts_table
|
||||
):
|
||||
raise DatasetteError("Cannot search by that column", status=400)
|
||||
|
||||
where_clauses.append(
|
||||
"rowid in (select rowid from {fts_table} where {search_col} match :search_{i})".format(
|
||||
fts_table=escape_sqlite(fts_table),
|
||||
search_col=escape_sqlite(search_col),
|
||||
i=i
|
||||
i=i,
|
||||
)
|
||||
)
|
||||
search_descriptions.append(
|
||||
|
|
@ -325,7 +337,9 @@ class TableView(RowTableShared):
|
|||
|
||||
sortable_columns = set()
|
||||
|
||||
sortable_columns = await self.sortable_columns_for_table(database, table, use_rowid)
|
||||
sortable_columns = await self.sortable_columns_for_table(
|
||||
database, table, use_rowid
|
||||
)
|
||||
|
||||
# Allow for custom sort order
|
||||
sort = special_args.get("_sort")
|
||||
|
|
@ -346,9 +360,9 @@ class TableView(RowTableShared):
|
|||
|
||||
from_sql = "from {table_name} {where}".format(
|
||||
table_name=escape_sqlite(table),
|
||||
where=(
|
||||
"where {} ".format(" and ".join(where_clauses))
|
||||
) if where_clauses else "",
|
||||
where=("where {} ".format(" and ".join(where_clauses)))
|
||||
if where_clauses
|
||||
else "",
|
||||
)
|
||||
# Copy of params so we can mutate them later:
|
||||
from_sql_params = dict(**params)
|
||||
|
|
@ -410,7 +424,9 @@ class TableView(RowTableShared):
|
|||
column=escape_sqlite(sort or sort_desc),
|
||||
op=">" if sort else "<",
|
||||
p=len(params),
|
||||
extra_desc_only="" if sort else " or {column2} is null".format(
|
||||
extra_desc_only=""
|
||||
if sort
|
||||
else " or {column2} is null".format(
|
||||
column2=escape_sqlite(sort or sort_desc)
|
||||
),
|
||||
next_clauses=" and ".join(next_by_pk_clauses),
|
||||
|
|
@ -470,9 +486,7 @@ class TableView(RowTableShared):
|
|||
order_by=order_by,
|
||||
)
|
||||
sql = "{sql_no_limit} limit {limit}{offset}".format(
|
||||
sql_no_limit=sql_no_limit.rstrip(),
|
||||
limit=page_size + 1,
|
||||
offset=offset,
|
||||
sql_no_limit=sql_no_limit.rstrip(), limit=page_size + 1, offset=offset
|
||||
)
|
||||
|
||||
if request.raw_args.get("_timelimit"):
|
||||
|
|
@ -486,15 +500,17 @@ class TableView(RowTableShared):
|
|||
filtered_table_rows_count = None
|
||||
if count_sql:
|
||||
try:
|
||||
count_rows = list(await self.ds.execute(
|
||||
database, count_sql, from_sql_params
|
||||
))
|
||||
count_rows = list(
|
||||
await self.ds.execute(database, count_sql, from_sql_params)
|
||||
)
|
||||
filtered_table_rows_count = count_rows[0][0]
|
||||
except InterruptedError:
|
||||
pass
|
||||
|
||||
# facets support
|
||||
if not self.ds.config("allow_facet") and any(arg.startswith("_facet") for arg in request.args):
|
||||
if not self.ds.config("allow_facet") and any(
|
||||
arg.startswith("_facet") for arg in request.args
|
||||
):
|
||||
raise DatasetteError("_facet= is not allowed", status=400)
|
||||
|
||||
# pylint: disable=no-member
|
||||
|
|
@ -505,19 +521,23 @@ class TableView(RowTableShared):
|
|||
facets_timed_out = []
|
||||
facet_instances = []
|
||||
for klass in facet_classes:
|
||||
facet_instances.append(klass(
|
||||
self.ds,
|
||||
request,
|
||||
database,
|
||||
sql=sql_no_limit,
|
||||
params=params,
|
||||
table=table,
|
||||
metadata=table_metadata,
|
||||
row_count=filtered_table_rows_count,
|
||||
))
|
||||
facet_instances.append(
|
||||
klass(
|
||||
self.ds,
|
||||
request,
|
||||
database,
|
||||
sql=sql_no_limit,
|
||||
params=params,
|
||||
table=table,
|
||||
metadata=table_metadata,
|
||||
row_count=filtered_table_rows_count,
|
||||
)
|
||||
)
|
||||
|
||||
for facet in facet_instances:
|
||||
instance_facet_results, instance_facets_timed_out = await facet.facet_results()
|
||||
instance_facet_results, instance_facets_timed_out = (
|
||||
await facet.facet_results()
|
||||
)
|
||||
facet_results.update(instance_facet_results)
|
||||
facets_timed_out.extend(instance_facets_timed_out)
|
||||
|
||||
|
|
@ -542,9 +562,7 @@ class TableView(RowTableShared):
|
|||
columns_to_expand = request.args["_label"]
|
||||
if columns_to_expand is None and all_labels:
|
||||
# expand all columns with foreign keys
|
||||
columns_to_expand = [
|
||||
fk["column"] for fk, _ in expandable_columns
|
||||
]
|
||||
columns_to_expand = [fk["column"] for fk, _ in expandable_columns]
|
||||
|
||||
if columns_to_expand:
|
||||
expanded_labels = {}
|
||||
|
|
@ -557,9 +575,9 @@ class TableView(RowTableShared):
|
|||
column_index = columns.index(column)
|
||||
values = [row[column_index] for row in rows]
|
||||
# Expand them
|
||||
expanded_labels.update(await self.ds.expand_foreign_keys(
|
||||
database, table, column, values
|
||||
))
|
||||
expanded_labels.update(
|
||||
await self.ds.expand_foreign_keys(database, table, column, values)
|
||||
)
|
||||
if expanded_labels:
|
||||
# Rewrite the rows
|
||||
new_rows = []
|
||||
|
|
@ -569,8 +587,8 @@ class TableView(RowTableShared):
|
|||
value = row[column]
|
||||
if (column, value) in expanded_labels:
|
||||
new_row[column] = {
|
||||
'value': value,
|
||||
'label': expanded_labels[(column, value)]
|
||||
"value": value,
|
||||
"label": expanded_labels[(column, value)],
|
||||
}
|
||||
else:
|
||||
new_row[column] = value
|
||||
|
|
@ -608,7 +626,11 @@ class TableView(RowTableShared):
|
|||
# Detect suggested facets
|
||||
suggested_facets = []
|
||||
|
||||
if self.ds.config("suggest_facets") and self.ds.config("allow_facet") and not _next:
|
||||
if (
|
||||
self.ds.config("suggest_facets")
|
||||
and self.ds.config("allow_facet")
|
||||
and not _next
|
||||
):
|
||||
for facet in facet_instances:
|
||||
# TODO: ensure facet is not suggested if it is already active
|
||||
# used to use 'if facet_column in facets' for this
|
||||
|
|
@ -634,10 +656,11 @@ class TableView(RowTableShared):
|
|||
link_column=not is_view,
|
||||
truncate_cells=self.ds.config("truncate_cells_html"),
|
||||
)
|
||||
metadata = (self.ds.metadata("databases") or {}).get(database, {}).get(
|
||||
"tables", {}
|
||||
).get(
|
||||
table, {}
|
||||
metadata = (
|
||||
(self.ds.metadata("databases") or {})
|
||||
.get(database, {})
|
||||
.get("tables", {})
|
||||
.get(table, {})
|
||||
)
|
||||
self.ds.update_with_inherited_metadata(metadata)
|
||||
form_hidden_args = []
|
||||
|
|
@ -656,7 +679,7 @@ class TableView(RowTableShared):
|
|||
"sorted_facet_results": sorted(
|
||||
facet_results.values(),
|
||||
key=lambda f: (len(f["results"]), f["name"]),
|
||||
reverse=True
|
||||
reverse=True,
|
||||
),
|
||||
"extra_wheres_for_ui": extra_wheres_for_ui,
|
||||
"form_hidden_args": form_hidden_args,
|
||||
|
|
@ -682,32 +705,36 @@ class TableView(RowTableShared):
|
|||
"table_definition": await self.ds.get_table_definition(database, table),
|
||||
}
|
||||
|
||||
return {
|
||||
"database": database,
|
||||
"table": table,
|
||||
"is_view": is_view,
|
||||
"human_description_en": human_description_en,
|
||||
"rows": rows[:page_size],
|
||||
"truncated": results.truncated,
|
||||
"filtered_table_rows_count": filtered_table_rows_count,
|
||||
"expanded_columns": expanded_columns,
|
||||
"expandable_columns": expandable_columns,
|
||||
"columns": columns,
|
||||
"primary_keys": pks,
|
||||
"units": units,
|
||||
"query": {"sql": sql, "params": params},
|
||||
"facet_results": facet_results,
|
||||
"suggested_facets": suggested_facets,
|
||||
"next": next_value and str(next_value) or None,
|
||||
"next_url": next_url,
|
||||
}, extra_template, (
|
||||
"table-{}-{}.html".format(to_css_class(database), to_css_class(table)),
|
||||
"table.html",
|
||||
return (
|
||||
{
|
||||
"database": database,
|
||||
"table": table,
|
||||
"is_view": is_view,
|
||||
"human_description_en": human_description_en,
|
||||
"rows": rows[:page_size],
|
||||
"truncated": results.truncated,
|
||||
"filtered_table_rows_count": filtered_table_rows_count,
|
||||
"expanded_columns": expanded_columns,
|
||||
"expandable_columns": expandable_columns,
|
||||
"columns": columns,
|
||||
"primary_keys": pks,
|
||||
"units": units,
|
||||
"query": {"sql": sql, "params": params},
|
||||
"facet_results": facet_results,
|
||||
"suggested_facets": suggested_facets,
|
||||
"next": next_value and str(next_value) or None,
|
||||
"next_url": next_url,
|
||||
},
|
||||
extra_template,
|
||||
(
|
||||
"table-{}-{}.html".format(to_css_class(database), to_css_class(table)),
|
||||
"table.html",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class RowView(RowTableShared):
|
||||
name = 'row'
|
||||
name = "row"
|
||||
|
||||
async def data(self, request, database, hash, table, pk_path, default_labels=False):
|
||||
pk_values = urlsafe_components(pk_path)
|
||||
|
|
@ -720,15 +747,13 @@ class RowView(RowTableShared):
|
|||
select = "rowid, *"
|
||||
pks = ["rowid"]
|
||||
wheres = ['"{}"=:p{}'.format(pk, i) for i, pk in enumerate(pks)]
|
||||
sql = 'select {} from {} where {}'.format(
|
||||
sql = "select {} from {} where {}".format(
|
||||
select, escape_sqlite(table), " AND ".join(wheres)
|
||||
)
|
||||
params = {}
|
||||
for i, pk_value in enumerate(pk_values):
|
||||
params["p{}".format(i)] = pk_value
|
||||
results = await self.ds.execute(
|
||||
database, sql, params, truncate=True
|
||||
)
|
||||
results = await self.ds.execute(database, sql, params, truncate=True)
|
||||
columns = [r[0] for r in results.description]
|
||||
rows = list(results.rows)
|
||||
if not rows:
|
||||
|
|
@ -760,13 +785,10 @@ class RowView(RowTableShared):
|
|||
),
|
||||
"_rows_and_columns.html",
|
||||
],
|
||||
"metadata": (
|
||||
self.ds.metadata("databases") or {}
|
||||
).get(database, {}).get(
|
||||
"tables", {}
|
||||
).get(
|
||||
table, {}
|
||||
),
|
||||
"metadata": (self.ds.metadata("databases") or {})
|
||||
.get(database, {})
|
||||
.get("tables", {})
|
||||
.get(table, {}),
|
||||
}
|
||||
|
||||
data = {
|
||||
|
|
@ -784,8 +806,13 @@ class RowView(RowTableShared):
|
|||
database, table, pk_values
|
||||
)
|
||||
|
||||
return data, template_data, (
|
||||
"row-{}-{}.html".format(to_css_class(database), to_css_class(table)), "row.html"
|
||||
return (
|
||||
data,
|
||||
template_data,
|
||||
(
|
||||
"row-{}-{}.html".format(to_css_class(database), to_css_class(table)),
|
||||
"row.html",
|
||||
),
|
||||
)
|
||||
|
||||
async def foreign_key_tables(self, database, table, pk_values):
|
||||
|
|
@ -801,7 +828,7 @@ class RowView(RowTableShared):
|
|||
|
||||
sql = "select " + ", ".join(
|
||||
[
|
||||
'(select count(*) from {table} where {column}=:id)'.format(
|
||||
"(select count(*) from {table} where {column}=:id)".format(
|
||||
table=escape_sqlite(fk["other_table"]),
|
||||
column=escape_sqlite(fk["other_column"]),
|
||||
)
|
||||
|
|
@ -822,8 +849,8 @@ class RowView(RowTableShared):
|
|||
)
|
||||
foreign_key_tables = []
|
||||
for fk in foreign_keys:
|
||||
count = foreign_table_counts.get(
|
||||
(fk["other_table"], fk["other_column"])
|
||||
) or 0
|
||||
count = (
|
||||
foreign_table_counts.get((fk["other_table"], fk["other_column"])) or 0
|
||||
)
|
||||
foreign_key_tables.append({**fk, **{"count": count}})
|
||||
return foreign_key_tables
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue