diff --git a/datasette/app.py b/datasette/app.py index 37f35dbd..209cf44a 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -101,6 +101,9 @@ CONFIG_OPTIONS = ( ConfigOption("max_csv_mb", 100, """ Maximum size allowed for CSV export in MB. Set 0 to disable this limit. """.strip()), + ConfigOption("truncate_cells_html", 2048, """ + Truncate cells longer than this in HTML table view. Set to 0 to disable. + """.strip()), ) DEFAULT_CONFIG = { option.name: option.default diff --git a/datasette/views/table.py b/datasette/views/table.py index 843960b9..281788e6 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -117,6 +117,7 @@ class RowTableShared(BaseView): description, rows, link_column=False, + truncate_cells=0, ): "Returns columns, rows for specified table - including fancy foreign key treatment" table_metadata = self.table_metadata(database, table) @@ -202,6 +203,8 @@ class RowTableShared(BaseView): ) else: display_value = str(value) + if truncate_cells and len(display_value) > truncate_cells: + display_value = display_value[:truncate_cells] + u"\u2026" cells.append({"column": column, "value": display_value}) cell_rows.append(cells) @@ -723,6 +726,7 @@ class TableView(RowTableShared): results.description, rows, link_column=not is_view, + truncate_cells=self.ds.config["truncate_cells_html"], ) metadata = self.ds.metadata.get("databases", {}).get(name, {}).get( "tables", {} @@ -827,6 +831,7 @@ class RowView(RowTableShared): results.description, rows, link_column=False, + truncate_cells=0, ) for column in display_columns: column["sortable"] = False diff --git a/docs/config.rst b/docs/config.rst index 57bb28eb..81f68602 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -152,3 +152,14 @@ You can disable the limit entirely by settings this to 0: :: datasette mydatabase.db --config max_csv_mb:0 + +.. _config_truncate_cells_html: + +truncate_cells_html +------------------- + +In the HTML table view, truncate any strings that are longer than this value. The full value will still be available in CSV, JSON and on the individual row HTML page. Set this to 0 to disable truncation. + +:: + + datasette mydatabase.db --config truncate_cells_html:0 diff --git a/tests/test_api.py b/tests/test_api.py index bc20c6dd..9e35697d 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -974,6 +974,7 @@ def test_config_json(app_client): "cache_size_kb": 0, "allow_csv_stream": True, "max_csv_mb": 100, + "truncate_cells_html": 2048, } == response.json diff --git a/tests/test_html.py b/tests/test_html.py index 41a8bab6..12e70af9 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -69,6 +69,40 @@ def test_row_strange_table_name(app_client): assert response.status == 200 +def test_table_cell_truncation(): + for client in app_client(config={ + "truncate_cells_html": 5, + }): + response = client.get("/fixtures/facetable") + assert response.status == 200 + table = Soup(response.body, "html.parser").find("table") + assert table["class"] == ["rows-and-columns"] + assert [ + "Missi…", "Dogpa…", "SOMA", "Tende…", "Berna…", "Hayes…", + "Holly…", "Downt…", "Los F…", "Korea…", "Downt…", "Greek…", + "Corkt…", "Mexic…", "Arcad…" + ] == [ + td.string for td in table.findAll("td", { + "class": "col-neighborhood" + }) + ] + + +def test_row_page_does_not_truncate(): + for client in app_client(config={ + "truncate_cells_html": 5, + }): + response = client.get("/fixtures/facetable/1") + assert response.status == 200 + table = Soup(response.body, "html.parser").find("table") + assert table["class"] == ["rows-and-columns"] + assert ["Mission"] == [ + td.string for td in table.findAll("td", { + "class": "col-neighborhood" + }) + ] + + def test_add_filter_redirects(app_client): filter_args = urllib.parse.urlencode({ '_filter_column': 'content',