Skip to content

CMS Data Model & Rendering

This site documents the CMS template / page schema and the P1 data-driven rendering model for the custom CMS (epic AISD-36). It is the companion reference to the SQLAlchemy models in hq/sql_models/ -- the docs live next to the code they describe so the two stay in step.

It explains two things that work together:

  1. The data model -- the database tables that store a template, its blocks, its per-page descriptors, and the CSS that styles it.
  2. The rendering model -- how a single generic worker turns a row in the page_types table into a fully rendered web page, with no page-specific code.

Who this is for

  • Engineers working on the CMS schema, the cms-api service, or the rendering worker -- the Model API reference is generated straight from the SQLAlchemy docstrings, so it never drifts from the code.
  • Editors, product, and anyone non-technical who needs to understand how a page is described in data. The sources / bind / call / params explained page is written in plain English and walks real, seeded examples line by line.

The single source of truth: the models

There is no Alembic, no hand-written CREATE TABLE. The SQLAlchemy models in hq/sql_models/ are the single source of truth for the schema. The bootstrap builds the tables with Base.metadata.create_all(), so every column, foreign key, index, and unique constraint is declared in Python (template.py, core.py, lookup.py, ...) and emitted from that metadata.

The .sql files under hq/sql_models/ddl/ carry only the post-create steps that create_all cannot express -- guarded ALTERs for existing databases, the AFTER UPDATE history triggers, and the seed rows. Those seed rows are the real, shipped data this site quotes verbatim (the celebrity-v1 template's page descriptors live in 0015_page_types_and_css_version.sql).

What changed in P1

Before P1, the rendering worker carried hardcoded routing (router.ts) and one orchestrator file per page kind (pages/*.ts). P1 moves all of that into the database: a row in page_types now describes a page -- its routes, its layout, and the API calls that supply its data -- and the worker runs one generic loop over those descriptors:

match route → fetch sources → build → wrap layout

Adding or changing a page is now a data edit, not a code deploy.

Task lineage

  • AISD-36 -- the CMS epic (the SQL-model track that introduced these tables).
  • AISD-131 -- the per-repository documentation-portal epic. Phase 1 is content + a local strict build only; hosting and CI are Phase 2.
  • AISD-133 -- this documentation package (Phase 1 of AISD-131).
  • AISD-85 -- the template table group (templates, template_blocks, site_block_overrides).
  • AISD-126 -- the edit-tracking columns (edited_by / creator_type) and the history-mirror trigger pattern.
  • AISD P1.a / P1.b -- the templates.css / css_version cache-bust pointer and the page_types data-driven descriptors.

Where to go next

Page What it covers
Data model overview The four live tables, their history mirrors, the shared CMSBase columns, the cms_db schema, and the foreign-key relationships.
Templates & CSS in the DB The templates table, and why the full stylesheet lives in templates.css with a css_version cache-bust pointer.
Template blocks & overrides template_blocks and site_block_overrides, the element_type string key, and the HTML / settings resolution order.
Page types (data-driven pages) Every page_types column, the (template_id, type, variant) unique key, and the real seeded celebrity-v1 descriptors.
sources / bind / call / params explained The plain-English core: the generic render loop and a line-by-line walk of three real sources descriptors.
Model API reference Auto-generated class + column reference for hq.sql_models.template.