mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Added ?_through= table argument, closes #355
Also added much more interesting many-to-many fixtures - roadside attractions!
This commit is contained in:
parent
c902590ada
commit
172da009d8
4 changed files with 305 additions and 98 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
import urllib
|
import urllib
|
||||||
import itertools
|
import itertools
|
||||||
|
import json
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
from sanic.exceptions import NotFound
|
from sanic.exceptions import NotFound
|
||||||
|
|
@ -17,6 +18,7 @@ from datasette.utils import (
|
||||||
escape_sqlite,
|
escape_sqlite,
|
||||||
filters_should_redirect,
|
filters_should_redirect,
|
||||||
get_all_foreign_keys,
|
get_all_foreign_keys,
|
||||||
|
get_outbound_foreign_keys,
|
||||||
is_url,
|
is_url,
|
||||||
path_from_row_pks,
|
path_from_row_pks,
|
||||||
path_with_added_args,
|
path_with_added_args,
|
||||||
|
|
@ -289,6 +291,41 @@ class TableView(RowTableShared):
|
||||||
for text in request.args["_where"]
|
for text in request.args["_where"]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Support for ?_through={table, column, value}
|
||||||
|
extra_human_descriptions = []
|
||||||
|
if "_through" in request.args:
|
||||||
|
for through in request.args["_through"]:
|
||||||
|
through_data = json.loads(through)
|
||||||
|
through_table = through_data["table"]
|
||||||
|
other_column = through_data["column"]
|
||||||
|
value = through_data["value"]
|
||||||
|
outgoing_foreign_keys = await self.ds.execute_against_connection_in_thread(
|
||||||
|
database,
|
||||||
|
lambda conn: get_outbound_foreign_keys(conn, through_table),
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
fk_to_us = [
|
||||||
|
fk for fk in outgoing_foreign_keys if fk["other_table"] == table
|
||||||
|
][0]
|
||||||
|
except IndexError:
|
||||||
|
raise DatasetteError(
|
||||||
|
"Invalid _through - could not find corresponding foreign key"
|
||||||
|
)
|
||||||
|
param = "p{}".format(len(params))
|
||||||
|
where_clauses.append(
|
||||||
|
"{our_pk} in (select {our_column} from {through_table} where {other_column} = :{param})".format(
|
||||||
|
through_table=escape_sqlite(through_table),
|
||||||
|
our_pk=escape_sqlite(fk_to_us["other_column"]),
|
||||||
|
our_column=escape_sqlite(fk_to_us["column"]),
|
||||||
|
other_column=escape_sqlite(other_column),
|
||||||
|
param=param,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
params[param] = value
|
||||||
|
extra_human_descriptions.append(
|
||||||
|
'{}.{} = "{}"'.format(through_table, other_column, value)
|
||||||
|
)
|
||||||
|
|
||||||
# _search support:
|
# _search support:
|
||||||
fts_table = special_args.get("_fts_table")
|
fts_table = special_args.get("_fts_table")
|
||||||
fts_table = fts_table or table_metadata.get("fts_table")
|
fts_table = fts_table or table_metadata.get("fts_table")
|
||||||
|
|
@ -299,7 +336,6 @@ class TableView(RowTableShared):
|
||||||
search_args = dict(
|
search_args = dict(
|
||||||
pair for pair in special_args.items() if pair[0].startswith("_search")
|
pair for pair in special_args.items() if pair[0].startswith("_search")
|
||||||
)
|
)
|
||||||
search_descriptions = []
|
|
||||||
search = ""
|
search = ""
|
||||||
if fts_table and search_args:
|
if fts_table and search_args:
|
||||||
if "_search" in search_args:
|
if "_search" in search_args:
|
||||||
|
|
@ -310,7 +346,7 @@ class TableView(RowTableShared):
|
||||||
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))
|
extra_human_descriptions.append('search matches "{}"'.format(search))
|
||||||
params["search"] = search
|
params["search"] = search
|
||||||
else:
|
else:
|
||||||
# More complex: search against specific columns
|
# More complex: search against specific columns
|
||||||
|
|
@ -328,7 +364,7 @@ class TableView(RowTableShared):
|
||||||
i=i,
|
i=i,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
search_descriptions.append(
|
extra_human_descriptions.append(
|
||||||
'search column "{}" matches "{}"'.format(
|
'search column "{}" matches "{}"'.format(
|
||||||
search_col, search_text
|
search_col, search_text
|
||||||
)
|
)
|
||||||
|
|
@ -637,7 +673,9 @@ class TableView(RowTableShared):
|
||||||
suggested_facets.extend(await facet.suggest())
|
suggested_facets.extend(await facet.suggest())
|
||||||
|
|
||||||
# human_description_en combines filters AND search, if provided
|
# human_description_en combines filters AND search, if provided
|
||||||
human_description_en = filters.human_description_en(extra=search_descriptions)
|
human_description_en = filters.human_description_en(
|
||||||
|
extra=extra_human_descriptions
|
||||||
|
)
|
||||||
|
|
||||||
if sort or sort_desc:
|
if sort or sort_desc:
|
||||||
sorted_by = "sorted by {}{}".format(
|
sorted_by = "sorted by {}{}".format(
|
||||||
|
|
|
||||||
|
|
@ -293,6 +293,32 @@ Special table arguments
|
||||||
* `facetable?_where=neighborhood like "%c%"&_where=city_id=3 <https://latest.datasette.io/fixtures/facetable?_where=neighborhood%20like%20%22%c%%22&_where=city_id=3>`__
|
* `facetable?_where=neighborhood like "%c%"&_where=city_id=3 <https://latest.datasette.io/fixtures/facetable?_where=neighborhood%20like%20%22%c%%22&_where=city_id=3>`__
|
||||||
* `facetable?_where=city_id in (select id from facet_cities where name != "Detroit") <https://latest.datasette.io/fixtures/facetable?_where=city_id%20in%20(select%20id%20from%20facet_cities%20where%20name%20!=%20%22Detroit%22)>`__
|
* `facetable?_where=city_id in (select id from facet_cities where name != "Detroit") <https://latest.datasette.io/fixtures/facetable?_where=city_id%20in%20(select%20id%20from%20facet_cities%20where%20name%20!=%20%22Detroit%22)>`__
|
||||||
|
|
||||||
|
``?_through={json}``
|
||||||
|
This can be used to filter rows via a join against another table.
|
||||||
|
|
||||||
|
The JSON parameter must include three keys: ``table``, ``column`` and ``value``.
|
||||||
|
|
||||||
|
``table`` must be a table that the current table is related to via a foreign key relationship.
|
||||||
|
|
||||||
|
``column`` must be a column in that other table.
|
||||||
|
|
||||||
|
``value`` is the value that you want to match against.
|
||||||
|
|
||||||
|
For example, to filter ``roadside_attractions`` to just show the attractions that have a characteristic of "museum", you would construct this JSON::
|
||||||
|
|
||||||
|
{
|
||||||
|
"table": "roadside_attraction_characteristics",
|
||||||
|
"column": "characteristic_id",
|
||||||
|
"value": "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
As a URL, that looks like this:
|
||||||
|
|
||||||
|
``?_through={%22table%22:%22roadside_attraction_characteristics%22,%22column%22:%22characteristic_id%22,%22value%22:%221%22}``
|
||||||
|
|
||||||
|
Here's `an example <https://latest.datasette.io/fixtures/roadside_attractions?_through={%22table%22:%22roadside_attraction_characteristics%22,%22column%22:%22characteristic_id%22,%22value%22:%221%22}>`__.
|
||||||
|
|
||||||
|
|
||||||
``?_group_count=COLUMN``
|
``?_group_count=COLUMN``
|
||||||
Executes a SQL query that returns a count of the number of rows matching
|
Executes a SQL query that returns a count of the number of rows matching
|
||||||
each unique value in that column, with the most common ordered first.
|
each unique value in that column, with the most common ordered first.
|
||||||
|
|
|
||||||
|
|
@ -551,6 +551,63 @@ CREATE TABLE binary_data (
|
||||||
data BLOB
|
data BLOB
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Many 2 Many demo: roadside attractions!
|
||||||
|
|
||||||
|
CREATE TABLE roadside_attractions (
|
||||||
|
pk integer primary key,
|
||||||
|
name text,
|
||||||
|
address text,
|
||||||
|
latitude real,
|
||||||
|
longitude real
|
||||||
|
);
|
||||||
|
INSERT INTO roadside_attractions VALUES (
|
||||||
|
1, "The Mystery Spot", "465 Mystery Spot Road, Santa Cruz, CA 95065",
|
||||||
|
37.0167, -122.0024
|
||||||
|
);
|
||||||
|
INSERT INTO roadside_attractions VALUES (
|
||||||
|
2, "Winchester Mystery House", "525 South Winchester Boulevard, San Jose, CA 95128",
|
||||||
|
37.3184, -121.9511
|
||||||
|
);
|
||||||
|
INSERT INTO roadside_attractions VALUES (
|
||||||
|
3, "Burlingame Museum of PEZ Memorabilia", "214 California Drive, Burlingame, CA 94010",
|
||||||
|
37.5793, -122.3442
|
||||||
|
);
|
||||||
|
INSERT INTO roadside_attractions VALUES (
|
||||||
|
4, "Bigfoot Discovery Museum", "5497 Highway 9, Felton, CA 95018",
|
||||||
|
37.0414, -122.0725
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE attraction_characteristic (
|
||||||
|
pk integer primary key,
|
||||||
|
name text
|
||||||
|
);
|
||||||
|
INSERT INTO attraction_characteristic VALUES (
|
||||||
|
1, "Museum"
|
||||||
|
);
|
||||||
|
INSERT INTO attraction_characteristic VALUES (
|
||||||
|
2, "Paranormal"
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE roadside_attraction_characteristics (
|
||||||
|
attraction_id INTEGER REFERENCES roadside_attractions(pk),
|
||||||
|
characteristic_id INTEGER REFERENCES attraction_characteristic(pk)
|
||||||
|
);
|
||||||
|
INSERT INTO roadside_attraction_characteristics VALUES (
|
||||||
|
1, 2
|
||||||
|
);
|
||||||
|
INSERT INTO roadside_attraction_characteristics VALUES (
|
||||||
|
2, 2
|
||||||
|
);
|
||||||
|
INSERT INTO roadside_attraction_characteristics VALUES (
|
||||||
|
4, 2
|
||||||
|
);
|
||||||
|
INSERT INTO roadside_attraction_characteristics VALUES (
|
||||||
|
3, 1
|
||||||
|
);
|
||||||
|
INSERT INTO roadside_attraction_characteristics VALUES (
|
||||||
|
4, 1
|
||||||
|
);
|
||||||
|
|
||||||
INSERT INTO simple_primary_key VALUES (1, 'hello');
|
INSERT INTO simple_primary_key VALUES (1, 'hello');
|
||||||
INSERT INTO simple_primary_key VALUES (2, 'world');
|
INSERT INTO simple_primary_key VALUES (2, 'world');
|
||||||
INSERT INTO simple_primary_key VALUES (3, '');
|
INSERT INTO simple_primary_key VALUES (3, '');
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ def test_homepage(app_client):
|
||||||
assert response.json.keys() == {"fixtures": 0}.keys()
|
assert response.json.keys() == {"fixtures": 0}.keys()
|
||||||
d = response.json["fixtures"]
|
d = response.json["fixtures"]
|
||||||
assert d["name"] == "fixtures"
|
assert d["name"] == "fixtures"
|
||||||
assert d["tables_count"] == 21
|
assert d["tables_count"] == 24
|
||||||
assert len(d["tables_and_views_truncated"]) == 5
|
assert len(d["tables_and_views_truncated"]) == 5
|
||||||
assert d["tables_and_views_more"] is True
|
assert d["tables_and_views_more"] is True
|
||||||
# 4 hidden FTS tables + no_primary_key (hidden in metadata)
|
# 4 hidden FTS tables + no_primary_key (hidden in metadata)
|
||||||
|
|
@ -44,9 +44,9 @@ def test_homepage_sort_by_relationships(app_client):
|
||||||
assert [
|
assert [
|
||||||
"simple_primary_key",
|
"simple_primary_key",
|
||||||
"complex_foreign_keys",
|
"complex_foreign_keys",
|
||||||
|
"roadside_attraction_characteristics",
|
||||||
"searchable_tags",
|
"searchable_tags",
|
||||||
"foreign_key_references",
|
"foreign_key_references",
|
||||||
"facetable",
|
|
||||||
] == tables
|
] == tables
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -56,115 +56,134 @@ def test_database_page(app_client):
|
||||||
assert "fixtures" == data["database"]
|
assert "fixtures" == data["database"]
|
||||||
assert [
|
assert [
|
||||||
{
|
{
|
||||||
"columns": ["content"],
|
|
||||||
"name": "123_starts_with_digits",
|
"name": "123_starts_with_digits",
|
||||||
|
"columns": ["content"],
|
||||||
|
"primary_keys": [],
|
||||||
"count": 0,
|
"count": 0,
|
||||||
"hidden": False,
|
"hidden": False,
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
||||||
"fts_table": None,
|
"fts_table": None,
|
||||||
"primary_keys": [],
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": ["pk", "content"],
|
|
||||||
"name": "Table With Space In Name",
|
"name": "Table With Space In Name",
|
||||||
|
"columns": ["pk", "content"],
|
||||||
|
"primary_keys": ["pk"],
|
||||||
"count": 0,
|
"count": 0,
|
||||||
"hidden": False,
|
"hidden": False,
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
||||||
"fts_table": None,
|
"fts_table": None,
|
||||||
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attraction_characteristic",
|
||||||
|
"columns": ["pk", "name"],
|
||||||
"primary_keys": ["pk"],
|
"primary_keys": ["pk"],
|
||||||
},
|
"count": 2,
|
||||||
{
|
|
||||||
"columns": ["data"],
|
|
||||||
"count": 1,
|
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
||||||
"fts_table": None,
|
|
||||||
"hidden": False,
|
|
||||||
"name": "binary_data",
|
|
||||||
"primary_keys": [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"columns": ["pk", "f1", "f2", "f3"],
|
|
||||||
"name": "complex_foreign_keys",
|
|
||||||
"count": 1,
|
|
||||||
"foreign_keys": {
|
|
||||||
"incoming": [],
|
|
||||||
"outgoing": [
|
|
||||||
{
|
|
||||||
"column": "f3",
|
|
||||||
"other_column": "id",
|
|
||||||
"other_table": "simple_primary_key",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"column": "f2",
|
|
||||||
"other_column": "id",
|
|
||||||
"other_table": "simple_primary_key",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"column": "f1",
|
|
||||||
"other_column": "id",
|
|
||||||
"other_table": "simple_primary_key",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"hidden": False,
|
"hidden": False,
|
||||||
"fts_table": None,
|
"fts_table": None,
|
||||||
"primary_keys": ["pk"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"columns": ["pk1", "pk2", "content"],
|
|
||||||
"name": "compound_primary_key",
|
|
||||||
"count": 1,
|
|
||||||
"hidden": False,
|
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
||||||
"fts_table": None,
|
|
||||||
"primary_keys": ["pk1", "pk2"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"columns": ["pk1", "pk2", "pk3", "content"],
|
|
||||||
"name": "compound_three_primary_keys",
|
|
||||||
"count": 1001,
|
|
||||||
"hidden": False,
|
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
||||||
"fts_table": None,
|
|
||||||
"primary_keys": ["pk1", "pk2", "pk3"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"columns": ["pk", "foreign_key_with_custom_label"],
|
|
||||||
"name": "custom_foreign_key_label",
|
|
||||||
"count": 1,
|
|
||||||
"hidden": False,
|
|
||||||
"foreign_keys": {
|
|
||||||
"incoming": [],
|
|
||||||
"outgoing": [
|
|
||||||
{
|
|
||||||
"column": "foreign_key_with_custom_label",
|
|
||||||
"other_column": "id",
|
|
||||||
"other_table": "primary_key_multiple_columns_explicit_label",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"fts_table": None,
|
|
||||||
"primary_keys": ["pk"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"columns": ["id", "name"],
|
|
||||||
"name": "facet_cities",
|
|
||||||
"count": 4,
|
|
||||||
"foreign_keys": {
|
"foreign_keys": {
|
||||||
"incoming": [
|
"incoming": [
|
||||||
{
|
{
|
||||||
"column": "id",
|
"other_table": "roadside_attraction_characteristics",
|
||||||
"other_column": "city_id",
|
"column": "pk",
|
||||||
"other_table": "facetable",
|
"other_column": "characteristic_id",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outgoing": [],
|
"outgoing": [],
|
||||||
},
|
},
|
||||||
"fts_table": None,
|
|
||||||
"hidden": False,
|
|
||||||
"primary_keys": ["id"],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"name": "binary_data",
|
||||||
|
"columns": ["data"],
|
||||||
|
"primary_keys": [],
|
||||||
|
"count": 1,
|
||||||
|
"hidden": False,
|
||||||
|
"fts_table": None,
|
||||||
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "complex_foreign_keys",
|
||||||
|
"columns": ["pk", "f1", "f2", "f3"],
|
||||||
|
"primary_keys": ["pk"],
|
||||||
|
"count": 1,
|
||||||
|
"hidden": False,
|
||||||
|
"fts_table": None,
|
||||||
|
"foreign_keys": {
|
||||||
|
"incoming": [],
|
||||||
|
"outgoing": [
|
||||||
|
{
|
||||||
|
"other_table": "simple_primary_key",
|
||||||
|
"column": "f3",
|
||||||
|
"other_column": "id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"other_table": "simple_primary_key",
|
||||||
|
"column": "f2",
|
||||||
|
"other_column": "id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"other_table": "simple_primary_key",
|
||||||
|
"column": "f1",
|
||||||
|
"other_column": "id",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compound_primary_key",
|
||||||
|
"columns": ["pk1", "pk2", "content"],
|
||||||
|
"primary_keys": ["pk1", "pk2"],
|
||||||
|
"count": 1,
|
||||||
|
"hidden": False,
|
||||||
|
"fts_table": None,
|
||||||
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compound_three_primary_keys",
|
||||||
|
"columns": ["pk1", "pk2", "pk3", "content"],
|
||||||
|
"primary_keys": ["pk1", "pk2", "pk3"],
|
||||||
|
"count": 1001,
|
||||||
|
"hidden": False,
|
||||||
|
"fts_table": None,
|
||||||
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "custom_foreign_key_label",
|
||||||
|
"columns": ["pk", "foreign_key_with_custom_label"],
|
||||||
|
"primary_keys": ["pk"],
|
||||||
|
"count": 1,
|
||||||
|
"hidden": False,
|
||||||
|
"fts_table": None,
|
||||||
|
"foreign_keys": {
|
||||||
|
"incoming": [],
|
||||||
|
"outgoing": [
|
||||||
|
{
|
||||||
|
"other_table": "primary_key_multiple_columns_explicit_label",
|
||||||
|
"column": "foreign_key_with_custom_label",
|
||||||
|
"other_column": "id",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "facet_cities",
|
||||||
|
"columns": ["id", "name"],
|
||||||
|
"primary_keys": ["id"],
|
||||||
|
"count": 4,
|
||||||
|
"hidden": False,
|
||||||
|
"fts_table": None,
|
||||||
|
"foreign_keys": {
|
||||||
|
"incoming": [
|
||||||
|
{
|
||||||
|
"other_table": "facetable",
|
||||||
|
"column": "id",
|
||||||
|
"other_column": "city_id",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outgoing": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "facetable",
|
||||||
"columns": [
|
"columns": [
|
||||||
"pk",
|
"pk",
|
||||||
"created",
|
"created",
|
||||||
|
|
@ -175,94 +194,137 @@ def test_database_page(app_client):
|
||||||
"neighborhood",
|
"neighborhood",
|
||||||
"tags",
|
"tags",
|
||||||
],
|
],
|
||||||
"name": "facetable",
|
"primary_keys": ["pk"],
|
||||||
"count": 15,
|
"count": 15,
|
||||||
|
"hidden": False,
|
||||||
|
"fts_table": None,
|
||||||
"foreign_keys": {
|
"foreign_keys": {
|
||||||
"incoming": [],
|
"incoming": [],
|
||||||
"outgoing": [
|
"outgoing": [
|
||||||
{
|
{
|
||||||
|
"other_table": "facet_cities",
|
||||||
"column": "city_id",
|
"column": "city_id",
|
||||||
"other_column": "id",
|
"other_column": "id",
|
||||||
"other_table": "facet_cities",
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"fts_table": None,
|
|
||||||
"hidden": False,
|
|
||||||
"primary_keys": ["pk"],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": ["pk", "foreign_key_with_label", "foreign_key_with_no_label"],
|
|
||||||
"name": "foreign_key_references",
|
"name": "foreign_key_references",
|
||||||
|
"columns": ["pk", "foreign_key_with_label", "foreign_key_with_no_label"],
|
||||||
|
"primary_keys": ["pk"],
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"hidden": False,
|
"hidden": False,
|
||||||
|
"fts_table": None,
|
||||||
"foreign_keys": {
|
"foreign_keys": {
|
||||||
"incoming": [],
|
"incoming": [],
|
||||||
"outgoing": [
|
"outgoing": [
|
||||||
{
|
{
|
||||||
|
"other_table": "primary_key_multiple_columns",
|
||||||
"column": "foreign_key_with_no_label",
|
"column": "foreign_key_with_no_label",
|
||||||
"other_column": "id",
|
"other_column": "id",
|
||||||
"other_table": "primary_key_multiple_columns",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"other_table": "simple_primary_key",
|
||||||
"column": "foreign_key_with_label",
|
"column": "foreign_key_with_label",
|
||||||
"other_column": "id",
|
"other_column": "id",
|
||||||
"other_table": "simple_primary_key",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"fts_table": None,
|
|
||||||
"primary_keys": ["pk"],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "infinity",
|
"name": "infinity",
|
||||||
"columns": ["value"],
|
"columns": ["value"],
|
||||||
"count": 3,
|
|
||||||
"primary_keys": [],
|
"primary_keys": [],
|
||||||
|
"count": 3,
|
||||||
"hidden": False,
|
"hidden": False,
|
||||||
"fts_table": None,
|
"fts_table": None,
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": ["id", "content", "content2"],
|
|
||||||
"name": "primary_key_multiple_columns",
|
"name": "primary_key_multiple_columns",
|
||||||
|
"columns": ["id", "content", "content2"],
|
||||||
|
"primary_keys": ["id"],
|
||||||
"count": 1,
|
"count": 1,
|
||||||
|
"hidden": False,
|
||||||
|
"fts_table": None,
|
||||||
"foreign_keys": {
|
"foreign_keys": {
|
||||||
"incoming": [
|
"incoming": [
|
||||||
{
|
{
|
||||||
|
"other_table": "foreign_key_references",
|
||||||
"column": "id",
|
"column": "id",
|
||||||
"other_column": "foreign_key_with_no_label",
|
"other_column": "foreign_key_with_no_label",
|
||||||
"other_table": "foreign_key_references",
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outgoing": [],
|
"outgoing": [],
|
||||||
},
|
},
|
||||||
"hidden": False,
|
|
||||||
"fts_table": None,
|
|
||||||
"primary_keys": ["id"],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": ["id", "content", "content2"],
|
|
||||||
"name": "primary_key_multiple_columns_explicit_label",
|
"name": "primary_key_multiple_columns_explicit_label",
|
||||||
|
"columns": ["id", "content", "content2"],
|
||||||
|
"primary_keys": ["id"],
|
||||||
"count": 1,
|
"count": 1,
|
||||||
|
"hidden": False,
|
||||||
|
"fts_table": None,
|
||||||
"foreign_keys": {
|
"foreign_keys": {
|
||||||
"incoming": [
|
"incoming": [
|
||||||
{
|
{
|
||||||
|
"other_table": "custom_foreign_key_label",
|
||||||
"column": "id",
|
"column": "id",
|
||||||
"other_column": "foreign_key_with_custom_label",
|
"other_column": "foreign_key_with_custom_label",
|
||||||
"other_table": "custom_foreign_key_label",
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outgoing": [],
|
"outgoing": [],
|
||||||
},
|
},
|
||||||
"hidden": False,
|
|
||||||
"fts_table": None,
|
|
||||||
"primary_keys": ["id"],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": ["pk", "text1", "text2", "name with . and spaces"],
|
"name": "roadside_attraction_characteristics",
|
||||||
|
"columns": ["attraction_id", "characteristic_id"],
|
||||||
|
"primary_keys": [],
|
||||||
|
"count": 5,
|
||||||
|
"hidden": False,
|
||||||
|
"fts_table": None,
|
||||||
|
"foreign_keys": {
|
||||||
|
"incoming": [],
|
||||||
|
"outgoing": [
|
||||||
|
{
|
||||||
|
"other_table": "attraction_characteristic",
|
||||||
|
"column": "characteristic_id",
|
||||||
|
"other_column": "pk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"other_table": "roadside_attractions",
|
||||||
|
"column": "attraction_id",
|
||||||
|
"other_column": "pk",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "roadside_attractions",
|
||||||
|
"columns": ["pk", "name", "address", "latitude", "longitude"],
|
||||||
|
"primary_keys": ["pk"],
|
||||||
|
"count": 4,
|
||||||
|
"hidden": False,
|
||||||
|
"fts_table": None,
|
||||||
|
"foreign_keys": {
|
||||||
|
"incoming": [
|
||||||
|
{
|
||||||
|
"other_table": "roadside_attraction_characteristics",
|
||||||
|
"column": "pk",
|
||||||
|
"other_column": "attraction_id",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outgoing": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
"name": "searchable",
|
"name": "searchable",
|
||||||
|
"columns": ["pk", "text1", "text2", "name with . and spaces"],
|
||||||
|
"primary_keys": ["pk"],
|
||||||
"count": 2,
|
"count": 2,
|
||||||
|
"hidden": False,
|
||||||
|
"fts_table": "searchable_fts",
|
||||||
"foreign_keys": {
|
"foreign_keys": {
|
||||||
"incoming": [
|
"incoming": [
|
||||||
{
|
{
|
||||||
|
|
@ -273,9 +335,6 @@ def test_database_page(app_client):
|
||||||
],
|
],
|
||||||
"outgoing": [],
|
"outgoing": [],
|
||||||
},
|
},
|
||||||
"fts_table": "searchable_fts",
|
|
||||||
"hidden": False,
|
|
||||||
"primary_keys": ["pk"],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "searchable_tags",
|
"name": "searchable_tags",
|
||||||
|
|
@ -297,48 +356,49 @@ def test_database_page(app_client):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": ["group", "having", "and", "json"],
|
|
||||||
"name": "select",
|
"name": "select",
|
||||||
|
"columns": ["group", "having", "and", "json"],
|
||||||
|
"primary_keys": [],
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"hidden": False,
|
"hidden": False,
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
||||||
"fts_table": None,
|
"fts_table": None,
|
||||||
"primary_keys": [],
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": ["id", "content"],
|
|
||||||
"name": "simple_primary_key",
|
"name": "simple_primary_key",
|
||||||
|
"columns": ["id", "content"],
|
||||||
|
"primary_keys": ["id"],
|
||||||
"count": 4,
|
"count": 4,
|
||||||
"hidden": False,
|
"hidden": False,
|
||||||
|
"fts_table": None,
|
||||||
"foreign_keys": {
|
"foreign_keys": {
|
||||||
"incoming": [
|
"incoming": [
|
||||||
{
|
{
|
||||||
|
"other_table": "foreign_key_references",
|
||||||
"column": "id",
|
"column": "id",
|
||||||
"other_column": "foreign_key_with_label",
|
"other_column": "foreign_key_with_label",
|
||||||
"other_table": "foreign_key_references",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"other_table": "complex_foreign_keys",
|
||||||
"column": "id",
|
"column": "id",
|
||||||
"other_column": "f3",
|
"other_column": "f3",
|
||||||
"other_table": "complex_foreign_keys",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"other_table": "complex_foreign_keys",
|
||||||
"column": "id",
|
"column": "id",
|
||||||
"other_column": "f2",
|
"other_column": "f2",
|
||||||
"other_table": "complex_foreign_keys",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"other_table": "complex_foreign_keys",
|
||||||
"column": "id",
|
"column": "id",
|
||||||
"other_column": "f1",
|
"other_column": "f1",
|
||||||
"other_table": "complex_foreign_keys",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"outgoing": [],
|
"outgoing": [],
|
||||||
},
|
},
|
||||||
"fts_table": None,
|
|
||||||
"primary_keys": ["id"],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"name": "sortable",
|
||||||
"columns": [
|
"columns": [
|
||||||
"pk1",
|
"pk1",
|
||||||
"pk2",
|
"pk2",
|
||||||
|
|
@ -348,21 +408,20 @@ def test_database_page(app_client):
|
||||||
"sortable_with_nulls_2",
|
"sortable_with_nulls_2",
|
||||||
"text",
|
"text",
|
||||||
],
|
],
|
||||||
"name": "sortable",
|
"primary_keys": ["pk1", "pk2"],
|
||||||
"count": 201,
|
"count": 201,
|
||||||
"hidden": False,
|
"hidden": False,
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
||||||
"fts_table": None,
|
"fts_table": None,
|
||||||
"primary_keys": ["pk1", "pk2"],
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": ["pk", "content"],
|
|
||||||
"name": "table/with/slashes.csv",
|
"name": "table/with/slashes.csv",
|
||||||
|
"columns": ["pk", "content"],
|
||||||
|
"primary_keys": ["pk"],
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"hidden": False,
|
"hidden": False,
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
||||||
"fts_table": None,
|
"fts_table": None,
|
||||||
"primary_keys": ["pk"],
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "tags",
|
"name": "tags",
|
||||||
|
|
@ -383,33 +442,34 @@ def test_database_page(app_client):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": ["pk", "distance", "frequency"],
|
|
||||||
"name": "units",
|
"name": "units",
|
||||||
|
"columns": ["pk", "distance", "frequency"],
|
||||||
|
"primary_keys": ["pk"],
|
||||||
"count": 3,
|
"count": 3,
|
||||||
"hidden": False,
|
"hidden": False,
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
||||||
"fts_table": None,
|
"fts_table": None,
|
||||||
"primary_keys": ["pk"],
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": ["content", "a", "b", "c"],
|
|
||||||
"name": "no_primary_key",
|
"name": "no_primary_key",
|
||||||
|
"columns": ["content", "a", "b", "c"],
|
||||||
|
"primary_keys": [],
|
||||||
"count": 201,
|
"count": 201,
|
||||||
"hidden": True,
|
"hidden": True,
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
||||||
"fts_table": None,
|
"fts_table": None,
|
||||||
"primary_keys": [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"columns": ["text1", "text2", "name with . and spaces", "content"],
|
|
||||||
"count": 2,
|
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
"fts_table": "searchable_fts",
|
|
||||||
"hidden": True,
|
|
||||||
"name": "searchable_fts",
|
|
||||||
"primary_keys": [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"name": "searchable_fts",
|
||||||
|
"columns": ["text1", "text2", "name with . and spaces", "content"],
|
||||||
|
"primary_keys": [],
|
||||||
|
"count": 2,
|
||||||
|
"hidden": True,
|
||||||
|
"fts_table": "searchable_fts",
|
||||||
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "searchable_fts_content",
|
||||||
"columns": [
|
"columns": [
|
||||||
"docid",
|
"docid",
|
||||||
"c0text1",
|
"c0text1",
|
||||||
|
|
@ -417,14 +477,14 @@ def test_database_page(app_client):
|
||||||
"c2name with . and spaces",
|
"c2name with . and spaces",
|
||||||
"c3content",
|
"c3content",
|
||||||
],
|
],
|
||||||
"count": 2,
|
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
||||||
"fts_table": None,
|
|
||||||
"hidden": True,
|
|
||||||
"name": "searchable_fts_content",
|
|
||||||
"primary_keys": ["docid"],
|
"primary_keys": ["docid"],
|
||||||
|
"count": 2,
|
||||||
|
"hidden": True,
|
||||||
|
"fts_table": None,
|
||||||
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"name": "searchable_fts_segdir",
|
||||||
"columns": [
|
"columns": [
|
||||||
"level",
|
"level",
|
||||||
"idx",
|
"idx",
|
||||||
|
|
@ -433,21 +493,20 @@ def test_database_page(app_client):
|
||||||
"end_block",
|
"end_block",
|
||||||
"root",
|
"root",
|
||||||
],
|
],
|
||||||
"count": 1,
|
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
||||||
"fts_table": None,
|
|
||||||
"hidden": True,
|
|
||||||
"name": "searchable_fts_segdir",
|
|
||||||
"primary_keys": ["level", "idx"],
|
"primary_keys": ["level", "idx"],
|
||||||
|
"count": 1,
|
||||||
|
"hidden": True,
|
||||||
|
"fts_table": None,
|
||||||
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": ["blockid", "block"],
|
|
||||||
"count": 0,
|
|
||||||
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
||||||
"fts_table": None,
|
|
||||||
"hidden": True,
|
|
||||||
"name": "searchable_fts_segments",
|
"name": "searchable_fts_segments",
|
||||||
|
"columns": ["blockid", "block"],
|
||||||
"primary_keys": ["blockid"],
|
"primary_keys": ["blockid"],
|
||||||
|
"count": 0,
|
||||||
|
"hidden": True,
|
||||||
|
"fts_table": None,
|
||||||
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
||||||
},
|
},
|
||||||
] == data["tables"]
|
] == data["tables"]
|
||||||
|
|
||||||
|
|
@ -981,6 +1040,33 @@ def test_table_filter_extra_where_disabled_if_no_sql_allowed():
|
||||||
assert "_where= is not allowed" == response.json["error"]
|
assert "_where= is not allowed" == response.json["error"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_table_through(app_client):
|
||||||
|
# Just the museums:
|
||||||
|
response = app_client.get(
|
||||||
|
'/fixtures/roadside_attractions.json?_through={"table":"roadside_attraction_characteristics","column":"characteristic_id","value":"1"}'
|
||||||
|
)
|
||||||
|
assert [
|
||||||
|
[
|
||||||
|
3,
|
||||||
|
"Burlingame Museum of PEZ Memorabilia",
|
||||||
|
"214 California Drive, Burlingame, CA 94010",
|
||||||
|
37.5793,
|
||||||
|
-122.3442,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4,
|
||||||
|
"Bigfoot Discovery Museum",
|
||||||
|
"5497 Highway 9, Felton, CA 95018",
|
||||||
|
37.0414,
|
||||||
|
-122.0725,
|
||||||
|
],
|
||||||
|
] == response.json["rows"]
|
||||||
|
assert (
|
||||||
|
'where roadside_attraction_characteristics.characteristic_id = "1"'
|
||||||
|
== response.json["human_description_en"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_max_returned_rows(app_client):
|
def test_max_returned_rows(app_client):
|
||||||
response = app_client.get("/fixtures.json?sql=select+content+from+no_primary_key")
|
response = app_client.get("/fixtures.json?sql=select+content+from+no_primary_key")
|
||||||
data = response.json
|
data = response.json
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue