diff --git a/datasette/app.py b/datasette/app.py
index 27f192e3..192b2303 100644
--- a/datasette/app.py
+++ b/datasette/app.py
@@ -383,7 +383,83 @@ class DatabaseDownload(BaseView):
)
-class TableView(BaseView):
+class RowTableShared(BaseView):
+ async def make_display_rows(self, database, database_hash, table, rows, display_columns, pks, is_view, use_rowid):
+ # Get fancy with foreign keys
+ expanded = {}
+ tables = self.ds.inspect()[database]['tables']
+ table_info = tables.get(table) or {}
+ if table_info and not is_view:
+ foreign_keys = table_info['foreign_keys']['outgoing']
+ for fk in foreign_keys:
+ label_column = tables.get(fk['other_table'], {}).get('label_column')
+ if not label_column:
+ # We only link cells to other tables with label columns defined
+ continue
+ ids_to_lookup = set([row[fk['column']] for row in rows])
+ sql = 'select "{other_column}", "{label_column}" from {other_table} where "{other_column}" in ({placeholders})'.format(
+ other_column=fk['other_column'],
+ label_column=label_column,
+ other_table=escape_sqlite_table_name(fk['other_table']),
+ placeholders=', '.join(['?'] * len(ids_to_lookup)),
+ )
+ try:
+ results = await self.execute(database, sql, list(set(ids_to_lookup)))
+ except sqlite3.OperationalError:
+ # Probably hit the timelimit
+ pass
+ else:
+ for id, value in results:
+ expanded[(fk['column'], id)] = (fk['other_table'], value)
+
+ to_return = []
+ for row in rows:
+ cells = []
+ # Unless we are a view, the first column is a link - either to the rowid
+ # or to the simple or compound primary key
+ if not is_view:
+ display_value = jinja2.Markup(
+ '{flat_pks}'.format(
+ database=database,
+ database_hash=database_hash,
+ table=urllib.parse.quote_plus(table),
+ flat_pks=path_from_row_pks(row, pks, use_rowid),
+ )
+ )
+ cells.append({
+ 'column': 'rowid' if use_rowid else 'Link',
+ 'value': display_value,
+ })
+
+ for value, column in zip(row, display_columns):
+ if use_rowid and column == 'rowid':
+ # We already showed this in the linked first column
+ continue
+ elif (column, value) in expanded:
+ other_table, label = expanded[(column, value)]
+ display_value = jinja2.Markup(
+ # TODO: Escape id/label/etc so no XSS here
+ '{label} {id}'.format(
+ database=database,
+ database_hash=database_hash,
+ table=escape_sqlite_table_name(other_table),
+ id=value,
+ label=label,
+ )
+ )
+ elif value is None:
+ display_value = jinja2.Markup(' ')
+ else:
+ display_value = str(value)
+ cells.append({
+ 'column': column,
+ 'value': display_value,
+ })
+ to_return.append(cells)
+ return to_return
+
+
+class TableView(RowTableShared):
template = 'table.html'
async def data(self, request, name, hash, table):
@@ -575,82 +651,8 @@ class TableView(BaseView):
'next_url': next_url,
}, extra_template
- async def make_display_rows(self, database, database_hash, table, rows, display_columns, pks, is_view, use_rowid):
- # Get fancy with foreign keys
- expanded = {}
- tables = self.ds.inspect()[database]['tables']
- table_info = tables.get(table) or {}
- if table_info and not is_view:
- foreign_keys = table_info['foreign_keys']['outgoing']
- for fk in foreign_keys:
- label_column = tables.get(fk['other_table'], {}).get('label_column')
- if not label_column:
- # We only link cells to other tables with label columns defined
- continue
- ids_to_lookup = set([row[fk['column']] for row in rows])
- sql = 'select "{other_column}", "{label_column}" from {other_table} where "{other_column}" in ({placeholders})'.format(
- other_column=fk['other_column'],
- label_column=label_column,
- other_table=escape_sqlite_table_name(fk['other_table']),
- placeholders=', '.join(['?'] * len(ids_to_lookup)),
- )
- try:
- results = await self.execute(database, sql, list(set(ids_to_lookup)))
- except sqlite3.OperationalError:
- # Probably hit the timelimit
- pass
- else:
- for id, value in results:
- expanded[(fk['column'], id)] = (fk['other_table'], value)
- to_return = []
- for row in rows:
- cells = []
- # Unless we are a view, the first column is a link - either to the rowid
- # or to the simple or compound primary key
- if not is_view:
- display_value = jinja2.Markup(
- '{flat_pks}'.format(
- database=database,
- database_hash=database_hash,
- table=urllib.parse.quote_plus(table),
- flat_pks=path_from_row_pks(row, pks, use_rowid),
- )
- )
- cells.append({
- 'column': 'rowid' if use_rowid else 'Link',
- 'value': display_value,
- })
-
- for value, column in zip(row, display_columns):
- if use_rowid and column == 'rowid':
- # We already showed this in the linked first column
- continue
- elif (column, value) in expanded:
- other_table, label = expanded[(column, value)]
- display_value = jinja2.Markup(
- # TODO: Escape id/label/etc so no XSS here
- '{label} {id}'.format(
- database=database,
- database_hash=database_hash,
- table=escape_sqlite_table_name(other_table),
- id=value,
- label=label,
- )
- )
- elif value is None:
- display_value = jinja2.Markup(' ')
- else:
- display_value = str(value)
- cells.append({
- 'column': column,
- 'value': display_value,
- })
- to_return.append(cells)
- return to_return
-
-
-class RowView(BaseView):
+class RowView(RowTableShared):
template = 'row.html'
async def data(self, request, name, hash, table, pk_path):
@@ -683,6 +685,8 @@ class RowView(BaseView):
return {
'database_hash': hash,
'foreign_key_tables': await self.foreign_key_tables(name, table, pk_values),
+ 'display_columns': columns,
+ 'display_rows': await self.make_display_rows(name, hash, table, rows, columns, pks, False, use_rowid),
}
data = {
diff --git a/datasette/templates/_rows_and_columns.html b/datasette/templates/_rows_and_columns.html
new file mode 100644
index 00000000..7695be3d
--- /dev/null
+++ b/datasette/templates/_rows_and_columns.html
@@ -0,0 +1,16 @@
+
+
+
+ {% for column in display_columns %}| {{ column }} | {% endfor %}
+
+
+
+ {% for row in display_rows %}
+
+ {% for cell in row %}
+ | {{ cell.value }} |
+ {% endfor %}
+
+ {% endfor %}
+
+
diff --git a/datasette/templates/row.html b/datasette/templates/row.html
index c4ecf959..b2d6ed70 100644
--- a/datasette/templates/row.html
+++ b/datasette/templates/row.html
@@ -19,22 +19,7 @@
This data as .json, .jsono
-
-
-
- {% for column in columns %}| {{ column }} | {% endfor %}
-
-
-
- {% for row in rows %}
-
- {% for td in row %}
- | {{ td }} |
- {% endfor %}
-
- {% endfor %}
-
-
+{% include "_rows_and_columns.html" %}
{% if foreign_key_tables %}
Links from other tables
diff --git a/datasette/templates/table.html b/datasette/templates/table.html
index 06a6c71f..aba8b1e1 100644
--- a/datasette/templates/table.html
+++ b/datasette/templates/table.html
@@ -78,22 +78,7 @@
Returned {% if truncated %}more than {% endif %}{{ "{:,}".format(display_rows|length) }} row{% if display_rows|length == 1 %}{% else %}s{% endif %}
-
-
-
- {% for column in display_columns %}| {{ column }} | {% endfor %}
-
-
-
- {% for row in display_rows %}
-
- {% for cell in row %}
- | {{ cell.value }} |
- {% endfor %}
-
- {% endfor %}
-
-
+{% include "_rows_and_columns.html" %}
{% if next_url %}
Next page