mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Display column type in column action menu, closes #993
Also added new documented db.table_column_details() introspection method.
This commit is contained in:
parent
e807c4eac0
commit
5a184a5d21
8 changed files with 103 additions and 28 deletions
|
|
@ -16,6 +16,7 @@ from .utils import (
|
|||
sqlite_timelimit,
|
||||
sqlite3,
|
||||
table_columns,
|
||||
table_column_details,
|
||||
)
|
||||
from .inspect import inspect_hash
|
||||
|
||||
|
|
@ -231,6 +232,9 @@ class Database:
|
|||
async def table_columns(self, table):
|
||||
return await self.execute_fn(lambda conn: table_columns(conn, table))
|
||||
|
||||
async def table_column_details(self, table):
|
||||
return await self.execute_fn(lambda conn: table_column_details(conn, table))
|
||||
|
||||
async def primary_keys(self, table):
|
||||
return await self.execute_fn(lambda conn: detect_primary_keys(conn, table))
|
||||
|
||||
|
|
|
|||
|
|
@ -396,7 +396,6 @@ svg.dropdown-menu-icon {
|
|||
opacity: 0.8;
|
||||
}
|
||||
.dropdown-menu {
|
||||
display: inline-flex;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
line-height: 1.4;
|
||||
|
|
@ -410,6 +409,13 @@ svg.dropdown-menu-icon {
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.dropdown-menu .dropdown-column-type {
|
||||
font-size: 0.7em;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding: 4px 8px 4px 8px;
|
||||
}
|
||||
.dropdown-menu li {
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ var DROPDOWN_HTML = `<div class="dropdown-menu">
|
|||
<li><a class="dropdown-facet" href="#">Facet by this</a></li>
|
||||
<li><a class="dropdown-not-blank" href="#">Show not-blank rows</a></li>
|
||||
</ul>
|
||||
<p class="dropdown-column-type"></p>
|
||||
</div>`;
|
||||
|
||||
var DROPDOWN_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
|
|
@ -115,10 +116,20 @@ var DROPDOWN_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" heig
|
|||
} else {
|
||||
notBlank.style.display = 'none';
|
||||
}
|
||||
var columnTypeP = menu.querySelector('.dropdown-column-type');
|
||||
var columnType = th.dataset.columnType;
|
||||
var notNull = th.dataset.columnNotNull == 1 ? ' NOT NULL' : '';
|
||||
|
||||
if (columnType) {
|
||||
columnTypeP.style.display = 'block';
|
||||
columnTypeP.innerText = `Type: ${columnType.toUpperCase()}${notNull}`;
|
||||
} else {
|
||||
columnTypeP.style.display = 'none';
|
||||
}
|
||||
menu.style.position = 'absolute';
|
||||
menu.style.top = (menuTop + 6) + 'px';
|
||||
menu.style.left = menuLeft + 'px';
|
||||
menu.style.display = 'inline-flex';
|
||||
menu.style.display = 'block';
|
||||
}
|
||||
var svg = document.createElement('div');
|
||||
svg.innerHTML = DROPDOWN_ICON_SVG;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
{% for column in display_columns %}
|
||||
<th class="col-{{ column.name|to_css_class }}" scope="col" data-column="{{ column.name }}" data-is-pk="{% if column.is_pk %}1{% else %}0{% endif %}">
|
||||
<th class="col-{{ column.name|to_css_class }}" scope="col" data-column="{{ column.name }}" data-column-type="{{ column.type }}" data-column-not-null="{{ column.notnull }}" data-is-pk="{% if column.is_pk %}1{% else %}0{% endif %}">
|
||||
{% if not column.sortable %}
|
||||
{{ column.name }}
|
||||
{% else %}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import asyncio
|
||||
from contextlib import contextmanager
|
||||
from collections import OrderedDict
|
||||
from collections import OrderedDict, namedtuple
|
||||
import base64
|
||||
import click
|
||||
import hashlib
|
||||
|
|
@ -54,6 +54,11 @@ RUN apt-get update && \
|
|||
ENV SQLITE_EXTENSIONS /usr/lib/x86_64-linux-gnu/mod_spatialite.so
|
||||
"""
|
||||
|
||||
# Can replace this with Column from sqlite_utils when I add that dependency
|
||||
Column = namedtuple(
|
||||
"Column", ("cid", "name", "type", "notnull", "default_value", "is_pk")
|
||||
)
|
||||
|
||||
|
||||
async def await_me_maybe(value):
|
||||
if callable(value):
|
||||
|
|
@ -525,8 +530,12 @@ def detect_json1(conn=None):
|
|||
|
||||
|
||||
def table_columns(conn, table):
|
||||
return [column.name for column in table_column_details(conn, table)]
|
||||
|
||||
|
||||
def table_column_details(conn, table):
|
||||
return [
|
||||
r[1]
|
||||
Column(*r)
|
||||
for r in conn.execute(
|
||||
"PRAGMA table_info({});".format(escape_sqlite(table))
|
||||
).fetchall()
|
||||
|
|
|
|||
|
|
@ -90,19 +90,31 @@ class RowTableShared(DataView):
|
|||
"Returns columns, rows for specified table - including fancy foreign key treatment"
|
||||
db = self.ds.databases[database]
|
||||
table_metadata = self.ds.table_metadata(database, table)
|
||||
column_details = {col.name: col for col in await db.table_column_details(table)}
|
||||
sortable_columns = await self.sortable_columns_for_table(database, table, True)
|
||||
pks = await db.primary_keys(table)
|
||||
pks_for_display = pks
|
||||
if not pks_for_display:
|
||||
pks_for_display = ["rowid"]
|
||||
columns = [
|
||||
{
|
||||
"name": r[0],
|
||||
"sortable": r[0] in sortable_columns,
|
||||
"is_pk": r[0] in pks_for_display,
|
||||
}
|
||||
for r in description
|
||||
]
|
||||
|
||||
columns = []
|
||||
for r in description:
|
||||
if r[0] == "rowid" and "rowid" not in column_details:
|
||||
type_ = "integer"
|
||||
notnull = 0
|
||||
else:
|
||||
type_ = column_details[r[0]].type
|
||||
notnull = column_details[r[0]].notnull
|
||||
columns.append(
|
||||
{
|
||||
"name": r[0],
|
||||
"sortable": r[0] in sortable_columns,
|
||||
"is_pk": r[0] in pks_for_display,
|
||||
"type": type_,
|
||||
"notnull": notnull,
|
||||
}
|
||||
)
|
||||
|
||||
column_to_foreign_key_table = {
|
||||
fk["column"]: fk["other_table"]
|
||||
for fk in await db.foreign_keys_for_table(table)
|
||||
|
|
@ -217,12 +229,25 @@ class RowTableShared(DataView):
|
|||
# Add the link column header.
|
||||
# If it's a simple primary key, we have to remove and re-add that column name at
|
||||
# the beginning of the header row.
|
||||
first_column = None
|
||||
if len(pks) == 1:
|
||||
columns = [col for col in columns if col["name"] != pks[0]]
|
||||
|
||||
columns = [
|
||||
{"name": pks[0] if len(pks) == 1 else "Link", "sortable": len(pks) == 1}
|
||||
] + columns
|
||||
first_column = {
|
||||
"name": pks[0],
|
||||
"sortable": len(pks) == 1,
|
||||
"is_pk": True,
|
||||
"type": column_details[pks[0]].type,
|
||||
"notnull": column_details[pks[0]].notnull,
|
||||
}
|
||||
else:
|
||||
first_column = {
|
||||
"name": "Link",
|
||||
"sortable": False,
|
||||
"is_pk": False,
|
||||
"type": "",
|
||||
"notnull": 0,
|
||||
}
|
||||
columns = [first_column] + columns
|
||||
return columns, cell_rows
|
||||
|
||||
|
||||
|
|
@ -291,7 +316,8 @@ class TableView(RowTableShared):
|
|||
)
|
||||
|
||||
pks = await db.primary_keys(table)
|
||||
table_columns = await db.table_columns(table)
|
||||
table_column_details = await db.table_column_details(table)
|
||||
table_columns = [column.name for column in table_column_details]
|
||||
|
||||
select_columns = ", ".join(escape_sqlite(t) for t in table_columns)
|
||||
|
||||
|
|
|
|||
|
|
@ -500,6 +500,9 @@ The ``Database`` class also provides properties and methods for introspecting th
|
|||
``await db.table_columns(table)`` - list of strings
|
||||
Names of columns in a specific table.
|
||||
|
||||
``await db.table_column_details(table)`` - list of named tuples
|
||||
Full details of the columns in a specific table. Each column is represented by a ``Column`` named tuple with fields ``cid`` (integer representing the column position), ``name`` (string), ``type`` (string, e.g. ``REAL`` or ``VARCHAR(30)``), ``notnull`` (integer 1 or 0), ``default_value`` (string or None), ``is_pk`` (integer 1 or 0).
|
||||
|
||||
``await db.primary_keys(table)`` - list of strings
|
||||
Names of the columns that are part of the primary key for this table.
|
||||
|
||||
|
|
|
|||
|
|
@ -342,80 +342,96 @@ def test_sort_links(app_client):
|
|||
}
|
||||
for th in ths
|
||||
]
|
||||
assert [
|
||||
assert attrs_and_link_attrs == [
|
||||
{
|
||||
"attrs": {
|
||||
"class": ["col-Link"],
|
||||
"data-column": "Link",
|
||||
"data-is-pk": "0",
|
||||
"scope": "col",
|
||||
"data-column": "Link",
|
||||
"data-column-type": "",
|
||||
"data-column-not-null": "0",
|
||||
"data-is-pk": "0",
|
||||
},
|
||||
"a_href": None,
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"class": ["col-pk1"],
|
||||
"data-column": "pk1",
|
||||
"data-is-pk": "1",
|
||||
"scope": "col",
|
||||
"data-column": "pk1",
|
||||
"data-column-type": "varchar(30)",
|
||||
"data-column-not-null": "0",
|
||||
"data-is-pk": "1",
|
||||
},
|
||||
"a_href": None,
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"class": ["col-pk2"],
|
||||
"data-column": "pk2",
|
||||
"data-is-pk": "1",
|
||||
"scope": "col",
|
||||
"data-column": "pk2",
|
||||
"data-column-type": "varchar(30)",
|
||||
"data-column-not-null": "0",
|
||||
"data-is-pk": "1",
|
||||
},
|
||||
"a_href": None,
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"class": ["col-content"],
|
||||
"data-column": "content",
|
||||
"data-is-pk": "0",
|
||||
"scope": "col",
|
||||
"data-column": "content",
|
||||
"data-column-type": "text",
|
||||
"data-column-not-null": "0",
|
||||
"data-is-pk": "0",
|
||||
},
|
||||
"a_href": None,
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"class": ["col-sortable"],
|
||||
"data-column": "sortable",
|
||||
"data-is-pk": "0",
|
||||
"scope": "col",
|
||||
"data-column": "sortable",
|
||||
"data-column-type": "integer",
|
||||
"data-column-not-null": "0",
|
||||
"data-is-pk": "0",
|
||||
},
|
||||
"a_href": "sortable?_sort_desc=sortable",
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"class": ["col-sortable_with_nulls"],
|
||||
"data-column": "sortable_with_nulls",
|
||||
"data-is-pk": "0",
|
||||
"scope": "col",
|
||||
"data-column": "sortable_with_nulls",
|
||||
"data-column-type": "real",
|
||||
"data-column-not-null": "0",
|
||||
"data-is-pk": "0",
|
||||
},
|
||||
"a_href": "sortable?_sort=sortable_with_nulls",
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"class": ["col-sortable_with_nulls_2"],
|
||||
"data-column": "sortable_with_nulls_2",
|
||||
"data-is-pk": "0",
|
||||
"scope": "col",
|
||||
"data-column": "sortable_with_nulls_2",
|
||||
"data-column-type": "real",
|
||||
"data-column-not-null": "0",
|
||||
"data-is-pk": "0",
|
||||
},
|
||||
"a_href": "sortable?_sort=sortable_with_nulls_2",
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"class": ["col-text"],
|
||||
"data-column": "text",
|
||||
"data-is-pk": "0",
|
||||
"scope": "col",
|
||||
"data-column": "text",
|
||||
"data-column-type": "text",
|
||||
"data-column-not-null": "0",
|
||||
"data-is-pk": "0",
|
||||
},
|
||||
"a_href": "sortable?_sort=text",
|
||||
},
|
||||
] == attrs_and_link_attrs
|
||||
]
|
||||
|
||||
|
||||
def test_facet_display(app_client):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue