mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
parent
81df47e8d9
commit
50920cfe3d
9 changed files with 177 additions and 55 deletions
|
|
@ -71,6 +71,15 @@ CONFIG_OPTIONS = (
|
|||
ConfigOption("facet_suggest_time_limit_ms", 50, """
|
||||
Time limit for calculating a suggested facet
|
||||
""".strip()),
|
||||
ConfigOption("allow_facet", True, """
|
||||
Allow users to specify columns to facet using ?_facet= parameter
|
||||
""".strip()),
|
||||
ConfigOption("allow_download", True, """
|
||||
Allow users to download the original SQLite database files
|
||||
""".strip()),
|
||||
ConfigOption("suggest_facets", True, """
|
||||
Calculate and display suggested facets
|
||||
""".strip()),
|
||||
)
|
||||
DEFAULT_CONFIG = {
|
||||
option.name: option.default
|
||||
|
|
|
|||
|
|
@ -28,21 +28,35 @@ class StaticMount(click.ParamType):
|
|||
class Config(click.ParamType):
|
||||
name = "config"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
ok = True
|
||||
if ":" not in value:
|
||||
ok = False
|
||||
else:
|
||||
name, intvalue = value.split(":")
|
||||
ok = intvalue.isdigit()
|
||||
if not ok:
|
||||
def convert(self, config, param, ctx):
|
||||
if ":" not in config:
|
||||
self.fail(
|
||||
'"{}" should be of format name:integer'.format(value),
|
||||
param, ctx
|
||||
'"{}" should be name:value'.format(config), param, ctx
|
||||
)
|
||||
return
|
||||
name, value = config.split(":")
|
||||
if name not in DEFAULT_CONFIG:
|
||||
self.fail("{} is not a valid limit".format(name), param, ctx)
|
||||
return name, int(intvalue)
|
||||
self.fail("{} is not a valid option".format(name), param, ctx)
|
||||
return
|
||||
# Type checking
|
||||
default = DEFAULT_CONFIG[name]
|
||||
if isinstance(default, bool):
|
||||
if value not in ('on', 'off', 'true', 'false', '1', '0'):
|
||||
self.fail(
|
||||
'"{}" should be on/off/true/false'.format(name), param, ctx
|
||||
)
|
||||
return
|
||||
return name, value in ('on', 'true', '1')
|
||||
elif isinstance(default, int):
|
||||
if not value.isdigit():
|
||||
self.fail(
|
||||
'"{}" should be an integer'.format(name), param, ctx
|
||||
)
|
||||
return
|
||||
return name, int(value)
|
||||
else:
|
||||
# Should never happen:
|
||||
self.fail('Invalid option')
|
||||
|
||||
|
||||
@click.group(cls=DefaultGroup, default="serve", default_if_no_args=True)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,9 @@
|
|||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<p>Download SQLite DB: <a href="/{{ database }}-{{ database_hash }}.db">{{ database }}.db</a></p>
|
||||
{% if config.allow_download %}
|
||||
<p>Download SQLite DB: <a href="/{{ database }}-{{ database_hash }}.db">{{ database }}.db</a></p>
|
||||
{% endif %}
|
||||
|
||||
{% include "_codemirror_foot.html" %}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from sanic import response
|
|||
|
||||
from datasette.utils import to_css_class, validate_sql_select
|
||||
|
||||
from .base import BaseView
|
||||
from .base import BaseView, DatasetteError
|
||||
|
||||
|
||||
class DatabaseView(BaseView):
|
||||
|
|
@ -29,6 +29,7 @@ class DatabaseView(BaseView):
|
|||
{"name": query_name, "sql": query_sql}
|
||||
for query_name, query_sql in (metadata.get("queries") or {}).items()
|
||||
],
|
||||
"config": self.ds.config,
|
||||
}, {
|
||||
"database_hash": hash,
|
||||
"show_hidden": request.args.get("_show_hidden"),
|
||||
|
|
@ -42,6 +43,8 @@ class DatabaseView(BaseView):
|
|||
class DatabaseDownload(BaseView):
|
||||
|
||||
async def view_get(self, request, name, hash, **kwargs):
|
||||
if not self.ds.config["allow_download"]:
|
||||
raise DatasetteError("Database download is forbidden", status=403)
|
||||
filepath = self.ds.inspect()[name]["file"]
|
||||
return await response.file_stream(
|
||||
filepath,
|
||||
|
|
|
|||
|
|
@ -542,6 +542,8 @@ class TableView(RowTableShared):
|
|||
facet_size = self.ds.config["default_facet_size"]
|
||||
metadata_facets = table_metadata.get("facets", [])
|
||||
facets = metadata_facets[:]
|
||||
if request.args.get("_facet") and not self.ds.config["allow_facet"]:
|
||||
raise DatasetteError("_facet= is not allowed", status=400)
|
||||
try:
|
||||
facets.extend(request.args["_facet"])
|
||||
except KeyError:
|
||||
|
|
@ -650,41 +652,44 @@ class TableView(RowTableShared):
|
|||
|
||||
# Detect suggested facets
|
||||
suggested_facets = []
|
||||
for facet_column in columns:
|
||||
if facet_column in facets:
|
||||
continue
|
||||
suggested_facet_sql = '''
|
||||
select distinct {column} {from_sql}
|
||||
{and_or_where} {column} is not null
|
||||
limit {limit}
|
||||
'''.format(
|
||||
column=escape_sqlite(facet_column),
|
||||
from_sql=from_sql,
|
||||
and_or_where='and' if from_sql_where_clauses else 'where',
|
||||
limit=facet_size+1
|
||||
)
|
||||
distinct_values = None
|
||||
try:
|
||||
distinct_values = await self.ds.execute(
|
||||
name, suggested_facet_sql, from_sql_params,
|
||||
truncate=False,
|
||||
custom_time_limit=self.ds.config["facet_suggest_time_limit_ms"],
|
||||
if self.ds.config["suggest_facets"] and self.ds.config["allow_facet"]:
|
||||
for facet_column in columns:
|
||||
if facet_column in facets:
|
||||
continue
|
||||
if not self.ds.config["suggest_facets"]:
|
||||
continue
|
||||
suggested_facet_sql = '''
|
||||
select distinct {column} {from_sql}
|
||||
{and_or_where} {column} is not null
|
||||
limit {limit}
|
||||
'''.format(
|
||||
column=escape_sqlite(facet_column),
|
||||
from_sql=from_sql,
|
||||
and_or_where='and' if from_sql_where_clauses else 'where',
|
||||
limit=facet_size+1
|
||||
)
|
||||
num_distinct_values = len(distinct_values)
|
||||
if (
|
||||
num_distinct_values and
|
||||
num_distinct_values > 1 and
|
||||
num_distinct_values <= facet_size and
|
||||
num_distinct_values < filtered_table_rows_count
|
||||
):
|
||||
suggested_facets.append({
|
||||
'name': facet_column,
|
||||
'toggle_url': path_with_added_args(
|
||||
request, {'_facet': facet_column}
|
||||
),
|
||||
})
|
||||
except InterruptedError:
|
||||
pass
|
||||
distinct_values = None
|
||||
try:
|
||||
distinct_values = await self.ds.execute(
|
||||
name, suggested_facet_sql, from_sql_params,
|
||||
truncate=False,
|
||||
custom_time_limit=self.ds.config["facet_suggest_time_limit_ms"],
|
||||
)
|
||||
num_distinct_values = len(distinct_values)
|
||||
if (
|
||||
num_distinct_values and
|
||||
num_distinct_values > 1 and
|
||||
num_distinct_values <= facet_size and
|
||||
num_distinct_values < filtered_table_rows_count
|
||||
):
|
||||
suggested_facets.append({
|
||||
'name': facet_column,
|
||||
'toggle_url': path_with_added_args(
|
||||
request, {'_facet': facet_column}
|
||||
),
|
||||
})
|
||||
except InterruptedError:
|
||||
pass
|
||||
|
||||
# human_description_en combines filters AND search, if provided
|
||||
human_description_en = filters.human_description_en(extra=search_descriptions)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue