?_fts_table= and ?_fts_pk= arguments, closes #428

pull/430/head
Simon Willison 2019-04-11 21:21:17 -07:00
rodzic 9cd3b44277
commit db74cf0144
6 zmienionych plików z 60 dodań i 4 usunięć

Wyświetl plik

@ -84,6 +84,9 @@
{% for facet in sorted_facet_results %}
<input type="hidden" name="_facet" value="{{ facet.name }}">
{% endfor %}
{% for key, value in form_hidden_args %}
<input type="hidden" name="{{ key }}" value="{{ value }}">
{% endfor %}
<input type="submit" value="Apply">
</div>
</form>

Wyświetl plik

@ -296,10 +296,12 @@ class TableView(RowTableShared):
where_clauses, params = filters.build_where_clauses(table)
# _search support:
fts_table = await self.ds.execute_against_connection_in_thread(
fts_table = special_args.get("_fts_table")
fts_table = fts_table or table_metadata.get("fts_table")
fts_table = fts_table or await self.ds.execute_against_connection_in_thread(
database, lambda conn: detect_fts(conn, table)
)
fts_pk = table_metadata.get("fts_pk", "rowid")
fts_pk = special_args.get("_fts_pk", table_metadata.get("fts_pk", "rowid"))
search_args = dict(
pair for pair in special_args.items() if pair[0].startswith("_search")
)
@ -731,6 +733,10 @@ class TableView(RowTableShared):
table, {}
)
self.ds.update_with_inherited_metadata(metadata)
form_hidden_args = []
for arg in ("_fts_table", "_fts_pk"):
if arg in special_args:
form_hidden_args.append((arg, special_args[arg]))
return {
"supports_search": bool(fts_table),
"search": search or "",
@ -745,6 +751,7 @@ class TableView(RowTableShared):
key=lambda f: (len(f["results"]), f["name"]),
reverse=True
),
"form_hidden_args": form_hidden_args,
"facet_hideable": lambda facet: facet not in metadata_facets,
"is_sortable": any(c["sortable"] for c in display_columns),
"path_with_replaced_args": path_with_replaced_args,

Wyświetl plik

@ -78,9 +78,13 @@ Configuring full-text search for a table or view
If a table has a corresponding FTS table set up using the ``content=`` argument to ``CREATE VIRTUAL TABLE`` shown above, Datasette will detect it automatically and add a search interface to the table page for that table.
You can also manually configure which table should be used for full-text search using :ref:`metadata`. You can set the associated FTS table for a specific table and you can also set one for a view - if you do that, the page for that SQL view will offer a search option.
You can also manually configure which table should be used for full-text search using querystring parameters or :ref:`metadata`. You can set the associated FTS table for a specific table and you can also set one for a view - if you do that, the page for that SQL view will offer a search option.
The ``fts_table`` property can be used to specify an associated FTS table. If the primary key column in your table which was used to populate the FTS table is something other than ``rowid``, you can specify the column to use with the ``fts_pk`` property.
Use ``?_fts_table=x`` to over-ride the FTS table for a specific page. If the primary key was something other than ``rowid`` you can use ``?_fts_pk=col`` to set that as well. This is particularly useful for views, for example:
https://latest.datasette.io/fixtures/searchable_view?_fts_table=searchable_fts&_fts_pk=pk
The ``fts_table`` metadata property can be used to specify an associated FTS table. If the primary key column in your table which was used to populate the FTS table is something other than ``rowid``, you can specify the column to use with the ``fts_pk`` property.
Here is an example which enables full-text search for a ``display_ads`` view which is defined against the ``ads`` table and hence needs to run FTS against the ``ads_fts`` table, using the ``id`` as the primary key::

Wyświetl plik

@ -209,6 +209,10 @@ METADATA = {
},
'simple_view': {
'sortable_columns': ['content'],
},
'searchable_view_configured_by_metadata': {
'fts_table': 'searchable_fts',
'fts_pk': 'pk'
}
},
'queries': {
@ -564,6 +568,12 @@ INSERT INTO [table/with/slashes.csv] VALUES (3, 'hey');
CREATE VIEW simple_view AS
SELECT content, upper(content) AS upper_content FROM simple_primary_key;
CREATE VIEW searchable_view AS
SELECT * from searchable;
CREATE VIEW searchable_view_configured_by_metadata AS
SELECT * from searchable;
''' + '\n'.join([
'INSERT INTO no_primary_key VALUES ({i}, "a{i}", "b{i}", "c{i}");'.format(i=i + 1)
for i in range(201)

Wyświetl plik

@ -847,6 +847,24 @@ def test_searchable(app_client, path, expected_rows):
assert expected_rows == response.json['rows']
@pytest.mark.parametrize('path,expected_rows', [
('/fixtures/searchable_view_configured_by_metadata.json?_search=weasel', [
[2, 'terry dog', 'sara weasel', 'puma'],
]),
# This should return all results because search is not configured:
('/fixtures/searchable_view.json?_search=weasel', [
[1, 'barry cat', 'terry dog', 'panther'],
[2, 'terry dog', 'sara weasel', 'puma'],
]),
('/fixtures/searchable_view.json?_search=weasel&_fts_table=searchable_fts&_fts_pk=pk', [
[2, 'terry dog', 'sara weasel', 'puma'],
]),
])
def test_searchable_views(app_client, path, expected_rows):
response = app_client.get(path)
assert expected_rows == response.json['rows']
def test_searchable_invalid_column(app_client):
response = app_client.get(
'/fixtures/searchable.json?_search_invalid=x'

Wyświetl plik

@ -185,6 +185,20 @@ def test_empty_search_parameter_gets_removed(app_client):
)
def test_searchable_view_persists_fts_table(app_client):
# The search form should persist ?_fts_table as a hidden field
response = app_client.get(
"/fixtures/searchable_view?_fts_table=searchable_fts&_fts_pk=pk"
)
inputs = Soup(response.body, "html.parser").find("form").findAll("input")
hiddens = [i for i in inputs if i["type"] == "hidden"]
assert [
('_fts_table', 'searchable_fts'), ('_fts_pk', 'pk')
] == [
(hidden['name'], hidden['value']) for hidden in hiddens
]
def test_sort_by_desc_redirects(app_client):
path_base = '/fixtures/sortable'
path = path_base + '?' + urllib.parse.urlencode({