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 #189
pull/196/head
Simon Willison 2018-04-08 21:58:25 -07:00
rodzic 7ef95d6b96
commit d1756d7736
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 17E2DEA2588B7F52
7 zmienionych plików z 93 dodań i 23 usunięć

Wyświetl plik

@ -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', {}

Wyświetl plik

@ -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 }}&nbsp;</a>
{% if column.name == sort %}
<a href="{{ path_with_added_args(request, {'_sort_desc': column.name, '_sort': None, '_next': None}) }}" rel="nofollow">{{ column.name }}&nbsp;</a>
{% else %}
<a href="{{ path_with_added_args(request, {'_sort': column, '_sort_desc': None, '_next': None}) }}" rel="nofollow">{{ column }}{% if column == sort_desc %}&nbsp;▲{% 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 %}&nbsp;▲{% endif %}</a>
{% endif %}
{% endif %}
</th>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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
------------------------------

Wyświetl plik

@ -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': [],
},
}
}
},
}
}

Wyświetl plik

@ -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'],

Wyświetl plik

@ -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),