From ea2eea826a3d9fee44293974cf093595e0ef13b9 Mon Sep 17 00:00:00 2001 From: David Roe Date: Sun, 19 Jul 2026 11:15:20 -0400 Subject: [PATCH 1/4] Improve /api/ schema display and surface table-description knowls (LMFDB#4811, LMFDB#4812) #4812: give the schema table an Example column populated from a random row (db.
.random(projection=2)), with values truncated at 100 chars, string reprs quoted, and "[binary data]" for bytea columns. The holder div regains a shared `schema-holder` class and style.css caps it with `max-height: 500px; overflow-y: auto` (via max-height so small tables no longer get an oversized fixed-height scroll box) instead of the dead `height: 300px` block, which no longer matched after the div class was renamed. The duplicate `id="schema-table"` becomes a class. #4811: stop appending the table description to the page title and instead surface the `tables.` knowl via KNOWL_INC in collection.html (read-only content for everyone, edit link for logged-in knowl editors). Verified: sage -python -m pytest lmfdb/api/test_api.py (8 passed, incl. two new tests); pyflakes clean on api.py and test_api.py; test-client render of nf_fields, ec_curvedata, mf_hecke_traces, lat_lattices, gps_groups and char_dirichlet shows a populated Example column, the description knowl above the schema, and a title without the parenthetical; datapages still render with no Example column; /style.css serves 200. Co-Authored-By: Claude Fable 5 --- lmfdb/api/api.py | 30 ++++++++++++++++++++++++++--- lmfdb/api/templates/apidata.html | 8 -------- lmfdb/api/templates/apischema.html | 14 ++++++++++++-- lmfdb/api/templates/collection.html | 11 +++-------- lmfdb/api/test_api.py | 22 +++++++++++++++++++++ lmfdb/templates/style.css | 16 +++++++++++++++ 6 files changed, 80 insertions(+), 21 deletions(-) diff --git a/lmfdb/api/api.py b/lmfdb/api/api.py index 832f5ba8a2..2600ece319 100644 --- a/lmfdb/api/api.py +++ b/lmfdb/api/api.py @@ -33,6 +33,21 @@ def pretty_document(rec, sep=", ", id=True): return "{" + sep.join("'%s': %s" % attr for attr in attrs) + "}" +def schema_example(value, typ, max_len=100): + """ + String representation of one value from a random row, truncated + for display in the Example column of the schema table. + """ + if value is None: + return "" + if typ == "bytea" or isinstance(value, buffer): + return "[binary data]" + value = str(quote_string(value)) + if len(value) > max_len: + value = value[:max_len] + "..." + return value + + def hidden_collection(c): """ hide some collections from the main page (still available via direct requests) @@ -351,9 +366,17 @@ def apierror(msg, flash_extras=[], code=404, table=True): title = "Database - " + location bc = [("Database", url_for(".index")), (table,)] query_unquote = unquote(data["query"]) - description = coll.description() - if description: - title += " (%s)" % description + # The table description (the tables. knowl) is displayed by + # collection.html rather than being appended to the title + try: + example = coll.random(projection=2) + except Exception: + example = None + if example is None: + examples = {} + else: + examples = {table: {col: schema_example(example.get(col), typ) + for col, typ in coll.col_type.items()}} search_schema = [(col, coll.col_type[col]) for col in sorted(coll.search_cols)] extra_schema = [(col, coll.col_type[col]) @@ -362,6 +385,7 @@ def apierror(msg, flash_extras=[], code=404, table=True): title=title, search_schema={table: search_schema}, extra_schema={table: extra_schema}, + examples=examples, single_object=single_object, query_unquote=query_unquote, url_args=url_args, diff --git a/lmfdb/api/templates/apidata.html b/lmfdb/api/templates/apidata.html index cbaf9ab1fb..cfebdb1795 100644 --- a/lmfdb/api/templates/apidata.html +++ b/lmfdb/api/templates/apidata.html @@ -4,14 +4,6 @@
diff --git a/lmfdb/api/templates/apischema.html b/lmfdb/api/templates/apischema.html index 4b199d8ab5..e0a5fc675c 100644 --- a/lmfdb/api/templates/apischema.html +++ b/lmfdb/api/templates/apischema.html @@ -1,15 +1,22 @@ Show schema -
+
+ {% if example is not none %} + + {% endif %} {% for col, typ in search_schema[table] %} + {% if example is not none %} + + {% endif %} {% endfor %} {% if extra_schema %} @@ -17,6 +24,9 @@ + {% if example is not none %} + + {% endif %} {% endfor %} {% endif %} diff --git a/lmfdb/api/templates/collection.html b/lmfdb/api/templates/collection.html index b7a2ee52b3..16ebd1bbd6 100644 --- a/lmfdb/api/templates/collection.html +++ b/lmfdb/api/templates/collection.html @@ -4,14 +4,6 @@
@@ -27,6 +19,9 @@
Query: {{ query_unquote }}
+
+ {{ KNOWL_INC('tables.'+table) }} +
{% include "apischema.html" %}
diff --git a/lmfdb/api/test_api.py b/lmfdb/api/test_api.py index 44af9d883c..dbc6a1b9d4 100644 --- a/lmfdb/api/test_api.py +++ b/lmfdb/api/test_api.py @@ -74,3 +74,25 @@ def test_api_usage(self): if '11a1' in query: assert '"lmfdb_label": "11.a2"' in data assert '"jinv": [\n -122023936,\n 161051\n ]' in data + + def test_api_schema_display(self): + r""" + Check the schema display on table pages: an Example column filled + from a random row, and the table description no longer in the title + """ + for tbl in ['mf_hecke_traces', 'nf_fields']: # one small, one big table + data = self.tc.get("/api/{}".format(tbl), follow_redirects=True).get_data(as_text=True) + assert "
" in data + assert 'class="schema-example"' in data + assert 'class="schema-holder {}-schema-holder"'.format(tbl) in data + # the table description is displayed as a knowl, not in the title + assert "Database - {} (".format(tbl) not in data + + def test_api_schema_on_datapage(self): + r""" + Check that the schema display on underlying data pages still works + (with no Example column there) + """ + data = self.tc.get("/EllipticCurve/Q/data/11.a1", follow_redirects=True).get_data(as_text=True) + assert 'class="schema-holder ec_curvedata-schema-holder"' in data + assert "<th>Example</th>" not in data diff --git a/lmfdb/templates/style.css b/lmfdb/templates/style.css index 66c407dc89..0a82540af2 100644 --- a/lmfdb/templates/style.css +++ b/lmfdb/templates/style.css @@ -702,6 +702,22 @@ table.ntdata tr.toplined { border-top: 2px solid {{ color.table_ntdata_border }}; } +/* schema display on API and underlying data pages */ +div.schema-holder { + max-height: 500px; + width: 97%; + overflow-y: auto; +} +table.schema-table { + width: 95%; +} +table.schema-table td.schema-example { + font-family: monospace; + white-space: normal; + overflow-wrap: anywhere; + max-width: 40em; +} + table.ntdata th { text-align: left; padding-right: 7px; From eae95853e57097689da94986f54c5096329d84f0 Mon Sep 17 00:00:00 2001 From: David Roe <roed.math@gmail.com> Date: Sun, 19 Jul 2026 15:59:31 -0400 Subject: [PATCH 2/4] Address review: sample full projection for schema Example column (LMFDB#4812) The /api/<table> schema Example column was sampled with coll.random(projection=2). Switch to projection=3, the same full mask (id + all search + all extra columns) the datapage query already uses, so the Example column is sampled consistently and every row shown in the schema table can be populated. Strengthen test_api_schema_display: instead of only asserting that schema-example cells exist, assert that the Example cell for the never-null search column nf_fields.degree carries an actual value, so a future change that blanks search-column examples is caught. Verified: sage -python -m pytest lmfdb/api/test_api.py -> 8 passed; pyflakes clean; test-client renders of /api/nf_fields, /api/ec_curvedata and /api/maass_newforms show nonblank Example cells for search columns (degree, conductor, conrey_index) and the extra column (maass_newforms.coefficients). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- lmfdb/api/api.py | 5 ++++- lmfdb/api/test_api.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lmfdb/api/api.py b/lmfdb/api/api.py index 2600ece319..9a0bc22eb4 100644 --- a/lmfdb/api/api.py +++ b/lmfdb/api/api.py @@ -369,7 +369,10 @@ def apierror(msg, flash_extras=[], code=404, table=True): # The table description (the tables.<name> knowl) is displayed by # collection.html rather than being appended to the title try: - example = coll.random(projection=2) + # projection=3 selects every search and extra column (plus id), + # the same full mask the datapage query below uses, so the Example + # column can be populated for every row shown in the schema table. + example = coll.random(projection=3) except Exception: example = None if example is None: diff --git a/lmfdb/api/test_api.py b/lmfdb/api/test_api.py index dbc6a1b9d4..e79408a40c 100644 --- a/lmfdb/api/test_api.py +++ b/lmfdb/api/test_api.py @@ -1,3 +1,5 @@ +import re + from lmfdb.tests import LmfdbTest class ApiTest(LmfdbTest): @@ -87,6 +89,15 @@ def test_api_schema_display(self): assert 'class="schema-holder {}-schema-holder"'.format(tbl) in data # the table description is displayed as a knowl, not in the title assert "<title>Database - {} (".format(tbl) not in data + # The random row must be sampled with a projection that includes the + # search columns, otherwise the Example cells for search columns render + # blank. degree is a search column of nf_fields that is never null, so + # its Example cell must carry an actual value, not an empty string. + data = self.tc.get("/api/nf_fields", follow_redirects=True).get_data(as_text=True) + m = re.search(r'columns\.nf_fields\.degree\b.*?' + r'<td class="schema-example">(.*?)</td>', data, re.S) + assert m is not None, "degree row not found in nf_fields schema table" + assert m.group(1).strip() != "", "Example cell for search column 'degree' is blank" def test_api_schema_on_datapage(self): r""" From ee561c5915e9f9872356f5f4fb51422fe835c14b Mon Sep 17 00:00:00 2001 From: David Roe <roed.math@gmail.com> Date: Wed, 5 Aug 2026 00:04:01 -0400 Subject: [PATCH 3/4] Address review: make the table description title editable and shown (LMFDB#4811) The tables.<table> knowl was included on /api/<table> without a heading, so the page lost the description text that used to sit in its title without gaining the header-backed, editable knowl the issue asks for. Three things stood in the way, all fixed here. Knowl.__init__ overwrote every type 2 title with a generated string, so a custom title saved on a tables.<table> knowl was discarded on load. Generated titles are what we want for columns.<table>.<column>, so keep those; for a table description, use the stored title and only fall back to "Table <table>" when the record has none. The defunct marker is still appended, but not a second time when a title saved while the table was defunct already carries it. knowl-edit.html showed every type 2 title as read-only text plus a hidden input, and offered a "Postgres column type" row that is None for a table. Split the two cases apart by source (a column description records its table, a table description has none): a table description now gets a labelled, editable title input and no column type row, while a column description is unchanged. KNOWL_INC emitted no heading when called without an explicit title, with the knowl's own title left commented out. Add an opt-in show_title argument that renders it, which leaves the other 42 call sites alone and takes the title from the same Knowl object as the content, so the two cannot come from different versions. collection.html now calls it with htag='h2', show_title=True. A missing knowl is untouched: anonymous users still see nothing rather than an empty heading, and authenticated users still get the create link. Tests: test_api_table_description asserts the heading is present, is derived from the knowl rather than hard coded, and precedes both the description and the schema controls; DescriptionKnowlTest covers the model (custom title kept, generated fallback, defunct marker not doubled, column titles still generated) and renders knowl-edit.html for both kinds to check the editable/read-only split and the column type row. All four fail on the previous head. Also fixed the stale assertion in test_api_schema_display, which looked for a <title> prefix that never matches. Verified: sage -python -m pytest lmfdb/api/test_api.py lmfdb/tests/test_dynamic_knowls.py -> 22 passed; pyflakes clean; a test-client render of /api/artin_old2new_labels (a table with no description knowl) shows an empty description div and no bogus heading. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- lmfdb/api/templates/collection.html | 2 +- lmfdb/api/test_api.py | 27 +++++++++- lmfdb/knowledge/knowl.py | 11 +++-- lmfdb/knowledge/templates/knowl-edit.html | 19 +++++-- lmfdb/templates/knowl-defs.html | 8 +-- lmfdb/tests/test_dynamic_knowls.py | 60 +++++++++++++++++++++++ 6 files changed, 113 insertions(+), 14 deletions(-) diff --git a/lmfdb/api/templates/collection.html b/lmfdb/api/templates/collection.html index 16ebd1bbd6..297d86839d 100644 --- a/lmfdb/api/templates/collection.html +++ b/lmfdb/api/templates/collection.html @@ -20,7 +20,7 @@ Query: <code><a href="{{ query }}">{{ query_unquote }}</a></code> </div> <div class="table-description"> - {{ KNOWL_INC('tables.'+table) }} + {{ KNOWL_INC('tables.'+table, htag='h2', show_title=True) }} </div> <div> {% include "apischema.html" %} diff --git a/lmfdb/api/test_api.py b/lmfdb/api/test_api.py index e79408a40c..e256a506ba 100644 --- a/lmfdb/api/test_api.py +++ b/lmfdb/api/test_api.py @@ -88,7 +88,7 @@ def test_api_schema_display(self): assert 'class="schema-example"' in data assert 'class="schema-holder {}-schema-holder"'.format(tbl) in data # the table description is displayed as a knowl, not in the title - assert "<title>Database - {} (".format(tbl) not in data + assert "Database - {} (".format(tbl) not in data # The random row must be sampled with a projection that includes the # search columns, otherwise the Example cells for search columns render # blank. degree is a search column of nf_fields that is never null, so @@ -99,6 +99,31 @@ def test_api_schema_display(self): assert m is not None, "degree row not found in nf_fields schema table" assert m.group(1).strip() != "", "Example cell for search column 'degree' is blank" + def test_api_table_description(self): + r""" + Check that the tables.<table> knowl is shown with its own title as a + heading above the schema, rather than folded into the page title + """ + from markupsafe import escape + from lmfdb.knowledge.knowl import Knowl + from lmfdb.knowledge.main import md, md_preprocess + + tbl = 'nf_fields' + # The title and content live in the knowl database, so we ask for them + # rather than hard coding them here. + with self.app.test_request_context(): + self.app.preprocess_request() + knowl = Knowl('tables.{}'.format(tbl)) + assert knowl.exists(), "no tables.{} knowl to test against".format(tbl) + heading = "<h2>{}</h2>".format(escape(knowl.title)) + content = md.convert(md_preprocess(knowl.content)) + + data = self.tc.get("/api/{}".format(tbl), follow_redirects=True).get_data(as_text=True) + assert heading in data, "table description heading missing" + assert content in data, "table description content missing" + # the heading introduces the description, and both precede the schema + assert data.index(heading) < data.index(content) < data.index("{}-schema-shower".format(tbl)) + def test_api_schema_on_datapage(self): r""" Check that the schema display on underlying data pages still works diff --git a/lmfdb/knowledge/knowl.py b/lmfdb/knowledge/knowl.py index e72983f672..605e94cc44 100644 --- a/lmfdb/knowledge/knowl.py +++ b/lmfdb/knowledge/knowl.py @@ -1100,19 +1100,20 @@ def __init__(self, ID, template_kwargs=None, data=None, editing=False, showing=F self.type, self.source, self.source_name = extract_typ(ID) if self.type == 2: pieces = ID.split(".") - # Ignore the title passed in if len(pieces) == 3: - # Column + # Column: the title is generated, so ignore the title passed in self.title = f"Column {pieces[2]} of table {pieces[1]}" if pieces[1] in db.tablenames: self.coltype = db[pieces[1]].col_type.get(pieces[2], "DEFUNCT") else: self.coltype = "DEFUNCT" elif len(pieces) == 2: - # Table - self.title = f"Table {pieces[1]}" + # Table: unlike a column description, the title is editable, + # so it is only generated for records that don't have one yet self.coltype = None - if pieces[1] not in db.tablenames: + if not self.title: + self.title = f"Table {pieces[1]}" + if pieces[1] not in db.tablenames and not self.title.endswith(" (DEFUNCT)"): self.title += " (DEFUNCT)" if showing: diff --git a/lmfdb/knowledge/templates/knowl-edit.html b/lmfdb/knowledge/templates/knowl-edit.html index a76a2e25d7..3b22913663 100644 --- a/lmfdb/knowledge/templates/knowl-edit.html +++ b/lmfdb/knowledge/templates/knowl-edit.html @@ -20,6 +20,13 @@ {% else %} {% from "knowl-defs.html" import knowlbar with context %} + {# Column descriptions have a generated, read-only title and a Postgres + column type; table descriptions have neither, and their title is edited + like a normal knowl's. They are told apart by the source, which is the + table name for a column description and none for a table description. #} + {% set column_description = (k.type == 2 and k.source is not none) %} + {% set table_description = (k.type == 2 and k.source is none) %} + {% set renamable = (k.type == 0 and k.exists() and user_is_admin and k.source is none) %} {% set renaming_other_to_this = (k.type == 0 and k.source is not none and k.id != k.source) %} {% set renaming_this_to_other = (k.type == 0 and k.id == k.source) %} @@ -81,14 +88,20 @@ </tr> {% endif %} {# renamable #} <tr> - <td>{{KNOWL("doc.knowl.description",title="Description")}}</td> - {% if k.type == 2 %} + <td> + {%- if table_description -%} + Title + {%- else -%} + {{KNOWL("doc.knowl.description",title="Description")}} + {%- endif -%} + </td> + {% if column_description %} <td>{{ k.title }}<input name="title" id="ktitle" value="{{ k.title }}" type="hidden" /></td> {% else %} <td><input size="40" name="title" id="ktitle" value="{{ k.title }}" /></td> {% endif %} </tr> - {% if k.type == 2 %} + {% if column_description %} <tr> <td>Postgres column type</td> <td>{{ k.coltype }}</td> diff --git a/lmfdb/templates/knowl-defs.html b/lmfdb/templates/knowl-defs.html index 82229593db..d284143097 100644 --- a/lmfdb/templates/knowl-defs.html +++ b/lmfdb/templates/knowl-defs.html @@ -55,13 +55,13 @@ {%- endwith %} {%- endmacro %} -{% macro KNOWL_INC( knowlid, title=none, htag="h1", show_missing=False, backupid=none) -%} +{% macro KNOWL_INC( knowlid, title=none, htag="h1", show_missing=False, backupid=none, show_title=False) -%} {% with knowl = Knowl(knowlid) -%} {% if knowl.exists() -%} - {% if title is none -%} - {# <{{htag}}>{{knowl.title}}</{{htag}}> #} - {%- else -%} + {% if title is not none -%} <{{htag}}>{{title}}</{{htag}}> + {%- elif show_title -%} + <{{htag}}>{{knowl.title}}</{{htag}}> {%- endif %} {{knowl.content|render_knowl|safe }} {% if user_is_authenticated -%} diff --git a/lmfdb/tests/test_dynamic_knowls.py b/lmfdb/tests/test_dynamic_knowls.py index d8440914af..3d70cdd18c 100644 --- a/lmfdb/tests/test_dynamic_knowls.py +++ b/lmfdb/tests/test_dynamic_knowls.py @@ -1,5 +1,9 @@ +from flask import render_template +from markupsafe import escape + from lmfdb.tests import LmfdbTest +from lmfdb.knowledge.knowl import Knowl from lmfdb.utils.datetime_utils import utc_now_naive class DynamicKnowlTest(LmfdbTest): @@ -75,3 +79,59 @@ def test_prod_knowl_sync(self): # The timestamps and counts should be the same assert dev_cnt == prod_cnt and dev_t == prod_t + + +class DescriptionKnowlTest(LmfdbTest): + """ + These tests check that a table description knowl has a title that editors + can set, while a column description knowl keeps its generated title. + """ + + def test_table_title_is_kept(self): + k = Knowl("tables.nf_fields", + data={"title": "Number field data", "content": "", "status": 0}) + assert k.title == "Number field data" + + def test_table_title_is_generated_when_absent(self): + k = Knowl("tables.nf_fields", data={"title": "", "content": "", "status": 0}) + assert k.title == "Table nf_fields" + + def test_defunct_table_is_marked_once(self): + k = Knowl("tables.not_a_table", + data={"title": "Number field data", "content": "", "status": 0}) + assert k.title == "Number field data (DEFUNCT)" + # a title saved while the table was defunct already carries the marker + again = Knowl("tables.not_a_table", + data={"title": k.title, "content": "", "status": 0}) + assert again.title == "Number field data (DEFUNCT)" + + def test_column_title_is_generated(self): + k = Knowl("columns.nf_fields.degree", + data={"title": "Number field data", "content": "", "status": 0}) + assert k.title == "Column degree of table nf_fields" + assert k.coltype == self.db.nf_fields.col_type["degree"] + + def test_editor_title_row(self): + r""" + The editor offers a table description an editable title and a column + description a read-only one; only a column has a Postgres column type. + """ + def edit_page(ID): + # the template uses endpoints relative to the knowledge blueprint, + # so it has to be rendered from the edit page's own request context + with self.app.test_request_context("/knowledge/edit/" + ID): + self.app.preprocess_request() + k = Knowl(ID, editing=True) + return k, render_template("knowl-edit.html", k=k, title="", bread=[]) + + table_knowl, table = edit_page("tables.nf_fields") + column_knowl, column = edit_page("columns.nf_fields.degree") + + assert '<td>Title</td>' in table + assert '<input size="40" name="title" id="ktitle" value="{}" />'.format( + escape(table_knowl.title)) in table + assert "Postgres column type" not in table + + assert '<input name="title" id="ktitle" value="{}" type="hidden" />'.format( + escape(column_knowl.title)) in column + assert "Postgres column type" in column From 16b27c218144d5209a0db21ce40431cbaceda13a Mon Sep 17 00:00:00 2001 From: David Roe <roed.math@gmail.com> Date: Wed, 5 Aug 2026 01:05:07 -0400 Subject: [PATCH 4/4] Preserve the table description title on content-only updates (LMFDB#4811) Making the title editable exposed a way to lose it. Whenever a table description title becomes something an editor chose rather than a string we regenerate, every writer has to carry it, and set_table_description did not: it built its Knowl from content and defines alone, so the new constructor supplied the "Table <table>" fallback and KnowlBackend.save persisted that as the title. most_recent merges authors, not the title. The effect was that db.<table>.description("...") -- a content-only update, typically from a data-loading script -- would silently reset a title an editor had set. Fetch the old record before building the data dict, which set_table_description already did for the authors, and pass its title through. A table description with no record yet still gets the generated fallback. set_column_description needs no such change: column titles are always regenerated from the id, so there is nothing there to lose. Tests: test_content_update_keeps_table_title mocks out the database write (get_knowl, save and db.login are patched, so nothing touches the read-only connection) and asserts the knowl handed to save keeps the old title while taking the new content; test_content_update_generates_ missing_table_title covers the no-previous-record case. The first fails on ee561c5 with 'Table nf_fields' == 'Number field data'. Verified: sage -python -m pytest -q lmfdb/tests/test_dynamic_knowls.py lmfdb/api/test_api.py -> 25 passed; pyflakes clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --- lmfdb/knowledge/knowl.py | 10 +++++--- lmfdb/tests/test_dynamic_knowls.py | 38 +++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/lmfdb/knowledge/knowl.py b/lmfdb/knowledge/knowl.py index 605e94cc44..8ad71d8b0a 100644 --- a/lmfdb/knowledge/knowl.py +++ b/lmfdb/knowledge/knowl.py @@ -526,14 +526,18 @@ def get_table_description(self, table): def set_table_description(self, table, description): uid = db.login() kid = f"tables.{table}" + old = self.get_knowl(kid, beta=True) + if old is None: + old = {'authors': []} data = { 'content': description, 'defines': table, + # Unlike a column description, the title here is set by editors, + # so carry it over: this updates the content only, and a missing + # title would be saved as the generated fallback, discarding it. + 'title': old.get('title', ''), } kwl = Knowl(kid, data=data) - old = self.get_knowl(kid, beta=True) - if old is None: - old = {'authors': []} self.save(kwl, uid, most_recent=old) def drop_table(self, table): diff --git a/lmfdb/tests/test_dynamic_knowls.py b/lmfdb/tests/test_dynamic_knowls.py index 3d70cdd18c..cb6ab5946e 100644 --- a/lmfdb/tests/test_dynamic_knowls.py +++ b/lmfdb/tests/test_dynamic_knowls.py @@ -1,9 +1,11 @@ +from unittest.mock import patch + from flask import render_template from markupsafe import escape from lmfdb.tests import LmfdbTest -from lmfdb.knowledge.knowl import Knowl +from lmfdb.knowledge.knowl import Knowl, knowldb from lmfdb.utils.datetime_utils import utc_now_naive class DynamicKnowlTest(LmfdbTest): @@ -135,3 +137,37 @@ def edit_page(ID): assert '<input name="title" id="ktitle" value="{}" type="hidden" />'.format( escape(column_knowl.title)) in column assert "Postgres column type" in column + + def _set_table_description(self, table, description, old): + r""" + Run knowldb.set_table_description with the database write mocked out, + and return the knowl it would have saved. + """ + saved = [] + + def fake_save(knowl, who, most_recent=None, minor=False): + saved.append(knowl) + + with patch.object(knowldb, "get_knowl", return_value=old), \ + patch.object(knowldb, "save", side_effect=fake_save), \ + patch.object(self.db, "login", return_value="tester"): + knowldb.set_table_description(table, description) + + assert len(saved) == 1 + return saved[0] + + def test_content_update_keeps_table_title(self): + r""" + db.<table>.description(...) updates the content, so it must carry the + title over rather than saving the generated fallback in its place. + """ + old = {"authors": ["editor"], "title": "Number field data", + "content": "Number fields", "status": 0} + kwl = self._set_table_description("nf_fields", "Fields of finite degree", old) + assert kwl.title == "Number field data" + assert kwl.content == "Fields of finite degree" + + def test_content_update_generates_missing_table_title(self): + # a table description that does not exist yet still gets the fallback + kwl = self._set_table_description("nf_fields", "Number fields", None) + assert kwl.title == "Table nf_fields"
Column TypeExample
{{KNOWL('columns.'+table+'.'+col, col)}} {{typ}}{{example.get(col, '')}}
{{KNOWL('columns.'+table+'.'+col, col)}} {{typ}}{{example.get(col, '')}}
Example