Scaffold stk Blueprint
Create a complete blueprint for $ARGUMENTS in the stk framework.
Workflow
The scaffolder generates the full module structure. Run it first, then customize domain fields and logic.
uv run stk new <name>
<name> must be lowercase snake_case (e.g. blog_post, invoice_line). Reserved names (user, role, portal, public, session, admin, etc.) are rejected with a clear error.
The command generates:
stk/<name>/__init__.py, models.py, views.py
stk/templates/cms/<name>.html
- Patches
stk/app.py (import + register_blueprint)
- Patches
stk/static/js/navigation.js (nav entry)
- Generates AND applies the Alembic migration, then prints the page URL
Then:
- Customize
stk/<name>/models.py -- add/rename fields to fit your domain
- If you changed the model after scaffolding:
uv run stk db revision -m "update <name>" and review, then uv run stk db upgrade
(only needed for post-scaffold model edits; the initial migration is already applied. With --no-migrate, the first revision is manual)
- Verify:
uv run stk verify && uv run stk smoke
Customization reference
After scaffolding, the generated files follow these conventions. Only touch these when you need to go beyond the defaults.
Models (stk/<name>/models.py)
import dataclasses and use @dataclasses.dataclass decorator
- Import
Base from stk.extensions, NOT db
- Use
Column(Type) from sqlalchemy directly
- All relationships use
lazy="selectin"
- Include
to_dict() instance method
- Include
async from_dict(self, data) as instance method that mutates self (NOT a classmethod)
- Include
created_at = Column(DateTime, default=datetime.now, nullable=False)
Views (stk/<name>/views.py)
- ALL handlers are
async def
- Use
import orjson as json and return Response(json.dumps(data), content_type="application/json") for list endpoints
- Blueprint-level auth via
@bp.before_request with @auth_required("session") + @roles_required("admin")
- DB access via
await g.db_session.execute(...) or await g.db_session.get(...)
- Frontend sends
{item: {...}}, extract with json_data.get("item", {})
- Wrap mutations in try/except with
await g.db_session.rollback() on error
- Log mutations:
await Activity.register(current_user.id, "Action", data)
- Return
{"message": "..."} for create/update/delete responses
- Use
PER_PAGE = 25 constant
- Use
log = logging.getLogger(__name__) for error logging
Template (stk/templates/cms/<name>.html)
- Extend
layout.html
- Vue 3 Options API:
data(), methods, mounted(), NOT setup()
- Must include
mixins: [layoutMixin] and delimiters: config.delimiters
- Use
const vuetify = createVuetify(config.vuetifyConfig)
- Call
registerStkComponents(app) before app.use(vuetify).mount("#app")
- Use
v-data-table-server for lists
- Icons: Tabler Icons (
ti ti-*), e.g. ti ti-plus, ti ti-pencil, ti ti-trash, ti ti-x
- Pass server data via
<script type="application/json" id="...">{{ data|tojson|safe }}</script>
- Use
toRaw() from Vue when editing items: const {createApp, toRaw} = Vue
Do NOT:
- Use
db.Model, db.Column, db.select(), or any Flask-SQLAlchemy patterns
- Use
from flask import ... or from flask_security import ...
- Use sync handlers (every route must be
async def)
- Use
jsonify() (return dicts or Response objects)
- Use
lazy="dynamic" on relationships (breaks async)
- Forget
await on DB operations
- Use Vue Composition API (
setup(), ref(), reactive()) -- use Options API
- Use Material Design Icons -- use Tabler Icons (
ti ti-*)
- Send raw data in POST -- wrap in
{item: {...}}
- Forget
mixins: [layoutMixin] or registerStkComponents(app) in templates
1---2name: stk-blueprint3description: Scaffold a new stk blueprint with models, views, templates, and Alembic migration. Use when creating a new feature module, adding a new section to the app, or scaffolding a blueprint.4---56# Scaffold stk Blueprint78Create a complete blueprint for `$ARGUMENTS` in the stk framework.910## Workflow1112The scaffolder generates the full module structure. Run it first, then customize domain fields and logic.1314```bash15uv run stk new <name>16```1718`<name>` must be lowercase snake_case (e.g. `blog_post`, `invoice_line`). Reserved names (`user`, `role`, `portal`, `public`, `session`, `admin`, etc.) are rejected with a clear error.1920The command generates:21- `stk/<name>/__init__.py`, `models.py`, `views.py`22- `stk/templates/cms/<name>.html`23- Patches `stk/app.py` (import + `register_blueprint`)24- Patches `stk/static/js/navigation.js` (nav entry)25- Generates AND applies the Alembic migration, then prints the page URL2627Then:281. Customize `stk/<name>/models.py` -- add/rename fields to fit your domain292. If you changed the model after scaffolding: `uv run stk db revision -m "update <name>"` and review, then `uv run stk db upgrade`30 (only needed for post-scaffold model edits; the initial migration is already applied. With `--no-migrate`, the first revision is manual)313. Verify: `uv run stk verify && uv run stk smoke`3233## Customization reference3435After scaffolding, the generated files follow these conventions. Only touch these when you need to go beyond the defaults.3637### Models (`stk/<name>/models.py`)3839- `import dataclasses` and use `@dataclasses.dataclass` decorator40- Import `Base` from `stk.extensions`, NOT `db`41- Use `Column(Type)` from sqlalchemy directly42- All relationships use `lazy="selectin"`43- Include `to_dict()` instance method44- Include `async from_dict(self, data)` as **instance method** that mutates self (NOT a classmethod)45- Include `created_at = Column(DateTime, default=datetime.now, nullable=False)`4647### Views (`stk/<name>/views.py`)4849- ALL handlers are `async def`50- Use `import orjson as json` and return `Response(json.dumps(data), content_type="application/json")` for list endpoints51- Blueprint-level auth via `@bp.before_request` with `@auth_required("session")` + `@roles_required("admin")`52- DB access via `await g.db_session.execute(...)` or `await g.db_session.get(...)`53- Frontend sends `{item: {...}}`, extract with `json_data.get("item", {})`54- Wrap mutations in try/except with `await g.db_session.rollback()` on error55- Log mutations: `await Activity.register(current_user.id, "Action", data)`56- Return `{"message": "..."}` for create/update/delete responses57- Use `PER_PAGE = 25` constant58- Use `log = logging.getLogger(__name__)` for error logging5960### Template (`stk/templates/cms/<name>.html`)6162- Extend `layout.html`63- Vue 3 Options API: `data()`, `methods`, `mounted()`, NOT `setup()`64- Must include `mixins: [layoutMixin]` and `delimiters: config.delimiters`65- Use `const vuetify = createVuetify(config.vuetifyConfig)`66- Call `registerStkComponents(app)` before `app.use(vuetify).mount("#app")`67- Use `v-data-table-server` for lists68- Icons: Tabler Icons (`ti ti-*`), e.g. `ti ti-plus`, `ti ti-pencil`, `ti ti-trash`, `ti ti-x`69- Pass server data via `<script type="application/json" id="...">{{ data|tojson|safe }}</script>`70- Use `toRaw()` from Vue when editing items: `const {createApp, toRaw} = Vue`7172## Do NOT:73- Use `db.Model`, `db.Column`, `db.select()`, or any Flask-SQLAlchemy patterns74- Use `from flask import ...` or `from flask_security import ...`75- Use sync handlers (every route must be `async def`)76- Use `jsonify()` (return dicts or Response objects)77- Use `lazy="dynamic"` on relationships (breaks async)78- Forget `await` on DB operations79- Use Vue Composition API (`setup()`, `ref()`, `reactive()`) -- use Options API80- Use Material Design Icons -- use Tabler Icons (`ti ti-*`)81- Send raw data in POST -- wrap in `{item: {...}}`82- Forget `mixins: [layoutMixin]` or `registerStkComponents(app)` in templates