?_labels= and ?_label=COL to expand foreign keys in JSON/CSV

These new querystring arguments can be used to request expanded foreign keys
in both JSON and CSV formats.

?_labels=on turns on expansions for ALL foreign key columns

?_label=COLUMN1&_label=COLUMN2 can be used to pick specific columns to expand

e.g. `Street_Tree_List.json?_label=qSpecies&_label=qLegalStatus`

    {
        "rowid": 233,
        "TreeID": 121240,
        "qLegalStatus": {
            "value" 2,
            "label": "Private"
        }
        "qSpecies": {
            "value": 16,
            "label": "Sycamore"
        }
        "qAddress": "91 Commonwealth Ave",
        ...
    }

The labels option also works for the HTML and CSV views.

HTML defaults to `?_labels=on`, so if you pass `?_labels=off` you can disable
foreign key expansion entirely - or you can use `?_label=COLUMN` to request
just specific columns.

If you expand labels on CSV you get additional columns in the output:

`/Street_Tree_List.csv?_label=qLegalStatus`

    rowid,TreeID,qLegalStatus,qLegalStatus_label...
    1,141565,1,Permitted Site...
    2,232565,2,Undocumented...

I also refactored the existing foreign key expansion code.

Closes #233. Refs #266.
This commit is contained in:
Simon Willison 2018-06-16 15:18:57 -07:00
commit ed631e690b
No known key found for this signature in database
GPG key ID: 17E2DEA2588B7F52
9 changed files with 276 additions and 69 deletions

View file

@ -1096,6 +1096,52 @@ def test_suggest_facets_off():
).json["suggested_facets"]
def test_expand_labels(app_client):
response = app_client.get(
"/test_tables/facetable.json?_shape=object&_labels=1&_size=2"
"&neighborhood__contains=c"
)
assert {
"2": {
"pk": 2,
"planet_int": 1,
"state": "CA",
"city_id": {
"value": 1,
"label": "San Francisco"
},
"neighborhood": "Dogpatch"
},
"13": {
"pk": 13,
"planet_int": 1,
"state": "MI",
"city_id": {
"value": 3,
"label": "Detroit"
},
"neighborhood": "Corktown"
}
} == response.json
def test_expand_label(app_client):
response = app_client.get(
"/test_tables/foreign_key_references.json?_shape=object"
"&_label=foreign_key_with_label"
)
assert {
"1": {
"pk": "1",
"foreign_key_with_label": {
"value": "1",
"label": "hello"
},
"foreign_key_with_no_label": "1"
}
} == response.json
@pytest.mark.parametrize('path,expected_cache_control', [
("/test_tables/facetable.json", "max-age=31536000"),
("/test_tables/facetable.json?_ttl=invalid", "max-age=31536000"),

View file

@ -11,6 +11,24 @@ hello
world
'''.replace('\n', '\r\n')
EXPECTED_TABLE_WITH_LABELS_CSV = '''
pk,planet_int,state,city_id,city_id_label,neighborhood
1,1,CA,1,San Francisco,Mission
2,1,CA,1,San Francisco,Dogpatch
3,1,CA,1,San Francisco,SOMA
4,1,CA,1,San Francisco,Tenderloin
5,1,CA,1,San Francisco,Bernal Heights
6,1,CA,1,San Francisco,Hayes Valley
7,1,CA,2,Los Angeles,Hollywood
8,1,CA,2,Los Angeles,Downtown
9,1,CA,2,Los Angeles,Los Feliz
10,1,CA,2,Los Angeles,Koreatown
11,1,MI,3,Detroit,Downtown
12,1,MI,3,Detroit,Greektown
13,1,MI,3,Detroit,Corktown
14,1,MI,3,Detroit,Mexicantown
15,2,MC,4,Memnonia,Arcadia Planitia
'''.lstrip().replace('\n', '\r\n')
def test_table_csv(app_client):
response = app_client.get('/test_tables/simple_primary_key.csv')
@ -19,6 +37,13 @@ def test_table_csv(app_client):
assert EXPECTED_TABLE_CSV == response.text
def test_table_csv_with_labels(app_client):
response = app_client.get('/test_tables/facetable.csv?_labels=1')
assert response.status == 200
assert 'text/plain; charset=utf-8' == response.headers['Content-Type']
assert EXPECTED_TABLE_WITH_LABELS_CSV == response.text
def test_custom_sql_csv(app_client):
response = app_client.get(
'/test_tables.csv?sql=select+content+from+simple_primary_key+limit+2'

View file

@ -388,6 +388,20 @@ def test_table_html_foreign_key_links(app_client):
assert expected == [[str(td) for td in tr.select('td')] for tr in table.select('tbody tr')]
def test_table_html_disable_foreign_key_links_with_labels(app_client):
response = app_client.get('/test_tables/foreign_key_references?_labels=off')
assert response.status == 200
table = Soup(response.body, 'html.parser').find('table')
expected = [
[
'<td class="col-pk"><a href="/test_tables/foreign_key_references/1">1</a></td>',
'<td class="col-foreign_key_with_label">1</td>',
'<td class="col-foreign_key_with_no_label">1</td>'
]
]
assert expected == [[str(td) for td in tr.select('td')] for tr in table.select('tbody tr')]
def test_table_html_foreign_key_custom_label_column(app_client):
response = app_client.get('/test_tables/custom_foreign_key_label')
assert response.status == 200