mirror of
https://github.com/simonw/datasette.git
synced 2026-06-24 01:34:41 +02:00
RowContext - row page now renders a documented Context dataclass
RowView declares context_class = RowContext; BaseView.render() constructs the dataclass from the assembled context, dropping any keys not declared on the class, after select_templates and alternate_url_json have been added. Extras-named fields use extra_field() so their documentation comes from the Extra classes; view-added fields carry help metadata next to the view code. Refs #2127 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
8b89a3aca8
commit
3cc0fc07b4
3 changed files with 97 additions and 1 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
import csv
|
||||
import dataclasses
|
||||
import hashlib
|
||||
import sys
|
||||
import textwrap
|
||||
|
|
@ -88,6 +89,9 @@ class View:
|
|||
class BaseView:
|
||||
ds = None
|
||||
has_json_alternate = True
|
||||
# Set to a Context subclass to render a documented template context -
|
||||
# keys not declared on the class are dropped before rendering
|
||||
context_class = None
|
||||
|
||||
def __init__(self, datasette):
|
||||
self.ds = datasette
|
||||
|
|
@ -169,6 +173,11 @@ class BaseView:
|
|||
)
|
||||
}
|
||||
)
|
||||
if self.context_class is not None:
|
||||
declared = {f.name for f in dataclasses.fields(self.context_class)}
|
||||
template_context = self.context_class(
|
||||
**{k: v for k, v in template_context.items() if k in declared}
|
||||
)
|
||||
return Response.html(
|
||||
await self.ds.render_template(
|
||||
template,
|
||||
|
|
|
|||
|
|
@ -11,16 +11,95 @@ from datasette.utils import (
|
|||
escape_sqlite,
|
||||
)
|
||||
from datasette.plugins import pm
|
||||
from dataclasses import dataclass, field
|
||||
import json
|
||||
import markupsafe
|
||||
import sqlite_utils
|
||||
from datasette.extras import extra_names_from_request
|
||||
from datasette.extras import extra_names_from_request, ExtraScope
|
||||
from . import Context, extra_field
|
||||
from .table import display_columns_and_rows
|
||||
from .table_extras import RowExtraContext, resolve_row_extras, table_extra_registry
|
||||
|
||||
|
||||
@dataclass
|
||||
class RowContext(Context):
|
||||
"The page showing an individual row, e.g. /fixtures/facetable/1"
|
||||
|
||||
extras_scope = ExtraScope.ROW
|
||||
|
||||
# Fields resolved by registered extras - their documentation comes
|
||||
# from the description on each Extra class in table_extras.py
|
||||
columns: list = extra_field()
|
||||
database: str = extra_field()
|
||||
database_color: str = extra_field()
|
||||
foreign_key_tables: list = extra_field()
|
||||
metadata: dict = extra_field()
|
||||
primary_keys: list = extra_field()
|
||||
private: bool = extra_field()
|
||||
table: str = extra_field()
|
||||
|
||||
# Fields added by the view code
|
||||
ok: bool = field(
|
||||
metadata={"help": "True if the data for this page was retrieved without errors"}
|
||||
)
|
||||
rows: list = field(
|
||||
metadata={
|
||||
"help": "The rows for this page, as a list of dictionaries mapping column name to value"
|
||||
}
|
||||
)
|
||||
primary_key_values: list = field(
|
||||
metadata={"help": "Values of the primary keys for this row, from the URL"}
|
||||
)
|
||||
query_ms: float = field(
|
||||
metadata={
|
||||
"help": "Time taken by the SQL queries for this page, in milliseconds"
|
||||
}
|
||||
)
|
||||
display_columns: list = field(
|
||||
metadata={"help": "Column objects formatted for the HTML table display"}
|
||||
)
|
||||
display_rows: list = field(
|
||||
metadata={"help": "Row data formatted for the HTML table display"}
|
||||
)
|
||||
custom_table_templates: list = field(
|
||||
metadata={
|
||||
"help": "Custom template names that were considered for displaying this table"
|
||||
}
|
||||
)
|
||||
row_actions: list = field(
|
||||
metadata={"help": "Row actions made available by plugin hooks"}
|
||||
)
|
||||
top_row: callable = field(
|
||||
metadata={"help": "Async function rendering the top_row plugin slot"}
|
||||
)
|
||||
renderers: dict = field(
|
||||
metadata={
|
||||
"help": "Dictionary mapping output format names (e.g. json) to their URLs for this page"
|
||||
}
|
||||
)
|
||||
url_csv: str = field(metadata={"help": "URL for the CSV export of this page"})
|
||||
url_csv_path: str = field(metadata={"help": "Path portion of the CSV export URL"})
|
||||
url_csv_hidden_args: list = field(
|
||||
metadata={
|
||||
"help": "(name, value) pairs for hidden form fields used by the CSV export form"
|
||||
}
|
||||
)
|
||||
settings: dict = field(
|
||||
metadata={"help": "Dictionary of Datasette's current settings"}
|
||||
)
|
||||
select_templates: list = field(
|
||||
metadata={
|
||||
"help": "List of template names that were considered for this page, the one used marked with an asterisk"
|
||||
}
|
||||
)
|
||||
alternate_url_json: str = field(
|
||||
metadata={"help": "URL for the JSON version of this page"}
|
||||
)
|
||||
|
||||
|
||||
class RowView(DataView):
|
||||
name = "row"
|
||||
context_class = RowContext
|
||||
|
||||
async def data(self, request, default_labels=False):
|
||||
resolved = await self.ds.resolve_row(request)
|
||||
|
|
|
|||
|
|
@ -186,6 +186,14 @@ def test_table_context_fields_match_documented_contract():
|
|||
}
|
||||
|
||||
|
||||
def test_row_context_fields_match_documented_contract():
|
||||
from datasette.views.row import RowContext
|
||||
|
||||
assert {f.name for f in RowContext.documented_fields()} == {
|
||||
key.name for key in PAGES["row"].documented_keys()
|
||||
}
|
||||
|
||||
|
||||
def test_base_context_keys_all_have_docs():
|
||||
for key in BASE_CONTEXT_KEYS:
|
||||
assert key.doc, "Base context key {} is missing docs".format(key.name)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue