kopia lustrzana https://github.com/simonw/datasette
New sortable_columns option in metadata.json to control sort options
You can now explicitly set which columns in a table can be used for sorting using the _sort and _sort_desc arguments using metadata.json: { "databases": { "database1": { "tables": { "example_table": { "sortable_columns": [ "height", "weight" ] } } } } } Refs #189pull/196/head
rodzic
7ef95d6b96
commit
d1756d7736
|
@ -456,10 +456,27 @@ class DatabaseDownload(BaseView):
|
|||
|
||||
|
||||
class RowTableShared(BaseView):
|
||||
def sortable_columns_for_table(self, name, table, use_rowid):
|
||||
table_metadata = self.ds.metadata.get(
|
||||
'databases', {}
|
||||
).get(name, {}).get('tables', {}).get(table, {})
|
||||
if 'sortable_columns' in table_metadata:
|
||||
sortable_columns = set(table_metadata['sortable_columns'])
|
||||
else:
|
||||
table_info = self.ds.inspect()[name]['tables'].get(table) or {}
|
||||
sortable_columns = set(table_info.get('columns', []))
|
||||
if use_rowid:
|
||||
sortable_columns.add('rowid')
|
||||
return sortable_columns
|
||||
|
||||
async def display_columns_and_rows(self, database, table, description, rows, link_column=False, expand_foreign_keys=True):
|
||||
"Returns columns, rows for specified table - including fancy foreign key treatment"
|
||||
info = self.ds.inspect()[database]
|
||||
columns = [r[0] for r in description]
|
||||
sortable_columns = self.sortable_columns_for_table(database, table, True)
|
||||
columns = [{
|
||||
'name': r[0],
|
||||
'sortable': r[0] in sortable_columns,
|
||||
} for r in description]
|
||||
tables = info['tables']
|
||||
table_info = tables.get(table) or {}
|
||||
pks = await self.pks_for_table(database, table)
|
||||
|
@ -505,7 +522,8 @@ class RowTableShared(BaseView):
|
|||
)
|
||||
),
|
||||
})
|
||||
for value, column in zip(row, columns):
|
||||
for value, column_dict in zip(row, columns):
|
||||
column = column_dict['name']
|
||||
if (column, value) in expanded:
|
||||
other_table, label = expanded[(column, value)]
|
||||
display_value = jinja2.Markup(
|
||||
|
@ -533,7 +551,10 @@ class RowTableShared(BaseView):
|
|||
cell_rows.append(cells)
|
||||
|
||||
if link_column:
|
||||
columns = ['Link'] + columns
|
||||
columns = [{
|
||||
'name': 'Link',
|
||||
'sortable': False,
|
||||
}] + columns
|
||||
return columns, cell_rows
|
||||
|
||||
|
||||
|
@ -623,7 +644,7 @@ class TableView(RowTableShared):
|
|||
if not is_view:
|
||||
table_info = info[name]['tables'][table]
|
||||
table_rows = table_info['count']
|
||||
sortable_columns = set(table_info['columns'])
|
||||
sortable_columns = self.sortable_columns_for_table(name, table, use_rowid)
|
||||
|
||||
# Allow for custom sort order
|
||||
sort = special_args.get('_sort')
|
||||
|
@ -885,6 +906,8 @@ class RowView(RowTableShared):
|
|||
display_columns, display_rows = await self.display_columns_and_rows(
|
||||
name, table, description, rows, link_column=False, expand_foreign_keys=True
|
||||
)
|
||||
for column in display_columns:
|
||||
column['sortable'] = False
|
||||
return {
|
||||
'database_hash': hash,
|
||||
'foreign_key_tables': await self.foreign_key_tables(name, table, pk_values),
|
||||
|
@ -895,7 +918,6 @@ class RowView(RowTableShared):
|
|||
'_rows_and_columns-row-{}-{}.html'.format(to_css_class(name), to_css_class(table)),
|
||||
'_rows_and_columns.html',
|
||||
],
|
||||
'disable_sort': True,
|
||||
'enumerate': enumerate,
|
||||
'metadata': self.ds.metadata.get(
|
||||
'databases', {}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{% for i, column in enumerate(display_columns) %}
|
||||
{% for column in display_columns %}
|
||||
<th scope="col">
|
||||
{% if i == 0 or disable_sort %}
|
||||
{{ column }}
|
||||
{% if not column.sortable %}
|
||||
{{ column.name }}
|
||||
{% else %}
|
||||
{% if column == sort %}
|
||||
<a href="{{ path_with_added_args(request, {'_sort_desc': column, '_sort': None, '_next': None}) }}" rel="nofollow">{{ column }} ▼</a>
|
||||
{% if column.name == sort %}
|
||||
<a href="{{ path_with_added_args(request, {'_sort_desc': column.name, '_sort': None, '_next': None}) }}" rel="nofollow">{{ column.name }} ▼</a>
|
||||
{% else %}
|
||||
<a href="{{ path_with_added_args(request, {'_sort': column, '_sort_desc': None, '_next': None}) }}" rel="nofollow">{{ column }}{% if column == sort_desc %} ▲{% endif %}</a>
|
||||
<a href="{{ path_with_added_args(request, {'_sort': column.name, '_sort_desc': None, '_next': None}) }}" rel="nofollow">{{ column.name }}{% if column.name == sort_desc %} ▲{% endif %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</th>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<style>
|
||||
@media only screen and (max-width: 576px) {
|
||||
{% for column in display_columns %}
|
||||
td:nth-of-type({{ loop.index }}):before { content: "{{ column|escape_css_string }}"; }
|
||||
td:nth-of-type({{ loop.index }}):before { content: "{{ column.name|escape_css_string }}"; }
|
||||
{% endfor %}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -55,6 +55,33 @@ You can also provide metadata at the per-database or per-table level, like this:
|
|||
|
||||
Each of the top-level metadata fields can be used at the database and table level.
|
||||
|
||||
Setting which columns can be used for sorting
|
||||
---------------------------------------------
|
||||
|
||||
Datasette allows any column to be used for sorting by default. If you need to
|
||||
control which columns are available for sorting you can do so using the optional
|
||||
``sortable_columns`` key::
|
||||
|
||||
{
|
||||
"databases": {
|
||||
"database1": {
|
||||
"tables": {
|
||||
"example_table": {
|
||||
"sortable_columns": [
|
||||
"height",
|
||||
"weight"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
This will restrict sorting of ``example_table`` to just the ``height`` and
|
||||
``weight`` columns.
|
||||
|
||||
You can also disable sorting entirely by setting ``"sortable_columns": []``
|
||||
|
||||
Generating a metadata skeleton
|
||||
------------------------------
|
||||
|
||||
|
|
|
@ -68,9 +68,19 @@ METADATA = {
|
|||
'simple_primary_key': {
|
||||
'description_html': 'Simple <em>primary</em> key',
|
||||
'title': 'This <em>HTML</em> is escaped',
|
||||
}
|
||||
},
|
||||
'sortable': {
|
||||
'sortable_columns': [
|
||||
'sortable',
|
||||
'sortable_with_nulls',
|
||||
'sortable_with_nulls_2',
|
||||
]
|
||||
},
|
||||
'no_primary_key': {
|
||||
'sortable_columns': [],
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -412,12 +412,27 @@ def test_sortable_argument_errors(app_client):
|
|||
)
|
||||
assert 'Cannot sort table by badcolumn2' == response.json['error']
|
||||
response = app_client.get(
|
||||
'/test_tables/sortable.json?_sort=content&_sort_desc=pk2',
|
||||
'/test_tables/sortable.json?_sort=sortable_with_nulls&_sort_desc=sortable',
|
||||
gather_request=False
|
||||
)
|
||||
assert 'Cannot use _sort and _sort_desc at the same time' == response.json['error']
|
||||
|
||||
|
||||
def test_sortable_columns_metadata(app_client):
|
||||
response = app_client.get(
|
||||
'/test_tables/sortable.json?_sort=content',
|
||||
gather_request=False
|
||||
)
|
||||
assert 'Cannot sort table by content' == response.json['error']
|
||||
# no_primary_key has ALL sort options disabled
|
||||
for column in ('content', 'a', 'b', 'c'):
|
||||
response = app_client.get(
|
||||
'/test_tables/sortable.json?_sort={}'.format(column),
|
||||
gather_request=False
|
||||
)
|
||||
assert 'Cannot sort table by {}'.format(column) == response.json['error']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('path,expected_rows', [
|
||||
('/test_tables/simple_primary_key.json?content=hello', [
|
||||
['1', 'hello'],
|
||||
|
|
|
@ -200,14 +200,10 @@ def test_row_html_simple_primary_key(app_client):
|
|||
def test_table_html_no_primary_key(app_client):
|
||||
response = app_client.get('/test_tables/no_primary_key', gather_request=False)
|
||||
table = Soup(response.body, 'html.parser').find('table')
|
||||
ths = table.findAll('th')
|
||||
assert 'Link' == ths[0].string.strip()
|
||||
for expected_col, th in zip(('rowid', 'content', 'a', 'b', 'c'), ths[1:]):
|
||||
a = th.find('a')
|
||||
assert expected_col == a.string
|
||||
assert a['href'].endswith('/no_primary_key?_sort={}'.format(
|
||||
expected_col
|
||||
))
|
||||
# We have disabled sorting for this table using metadata.json
|
||||
assert [
|
||||
'content', 'a', 'b', 'c'
|
||||
] == [th.string.strip() for th in table.select('thead th')[2:]]
|
||||
expected = [
|
||||
[
|
||||
'<td><a href="/test_tables/no_primary_key/{}">{}</a></td>'.format(i, i),
|
||||
|
|
Ładowanie…
Reference in New Issue