Widen settings.description to TEXT; run seeders in CI
The dualpath_single_machine setting description is 257 chars but settings.description was varchar(255). On strict MySQL 8 an over-length insert is a hard error 1406 (Data too long), so `flask seed settings` failed on a fresh install; older/relaxed MySQL truncated silently and hid it. Widen the column to TEXT (matches value, already TEXT) via core migration 7d26. CI only ran `flask db upgrade` + plugin install, never the seeders, so it missed this. Add a seed step to the migrations-mysql job so a seeded row that violates a column constraint fails CI on strict MySQL 8 instead of shipping.
This commit is contained in:
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -92,6 +92,15 @@ jobs:
|
|||||||
flask plugin install "$p"
|
flask plugin install "$p"
|
||||||
done
|
done
|
||||||
flask plugin upgrade-all
|
flask plugin upgrade-all
|
||||||
|
- name: Seed platform data (catches strict-mode data violations)
|
||||||
|
# Runs the real seeders on strict MySQL 8. A seeded row that exceeds a
|
||||||
|
# column width is a hard error 1406 here (not the silent truncation
|
||||||
|
# older/relaxed MySQL gives), so this is what catches over-length
|
||||||
|
# setting descriptions and similar before they reach a fresh box.
|
||||||
|
run: |
|
||||||
|
flask seed permissions
|
||||||
|
flask seed settings
|
||||||
|
flask seed reference-data
|
||||||
- name: Assert schema built + utf8mb4
|
- name: Assert schema built + utf8mb4
|
||||||
run: |
|
run: |
|
||||||
python - <<'PY'
|
python - <<'PY'
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ Refactor phases 0-5 landed; phase 6 (multi-site distribution readiness) largely
|
|||||||
- 966 tests, naming/style check green, Gitea Actions CI (backend + naming + frontend build + a migrations-mysql job that runs the real fresh upgrade on utf8mb4 MySQL 8)
|
- 966 tests, naming/style check green, Gitea Actions CI (backend + naming + frontend build + a migrations-mysql job that runs the real fresh upgrade on utf8mb4 MySQL 8)
|
||||||
- `__contract_version__` at 0.13.0 (0.12.0 added the mailer, 0.13.0 the User model, to the plugin surface) (product `__version__` 0.7.0, tags v0.5.0/v0.6.0/v0.7.0 - distinct series, ADR-007)
|
- `__contract_version__` at 0.13.0 (0.12.0 added the mailer, 0.13.0 the User model, to the plugin surface) (product `__version__` 0.7.0, tags v0.5.0/v0.6.0/v0.7.0 - distinct series, ADR-007)
|
||||||
- 12 bundled plugins all satisfy contract: computers, employees, geenforce, knowledgebase, machines, measuringtools, network, notifications, printers, slides, usb, warranty
|
- 12 bundled plugins all satisfy contract: computers, employees, geenforce, knowledgebase, machines, measuringtools, network, notifications, printers, slides, usb, warranty
|
||||||
- Core Alembic chain: baseline `68b3947ae14f` -> head `7d25_drop_redundant_indexes` (32 core migrations). Each plugin owns its own chain (ADR-008); deploy runs `flask db upgrade` then `flask plugin upgrade-all`. Reproducible + idempotent from empty (env.py relaxes session sql_mode so the chain runs on strict MySQL 8).
|
- Core Alembic chain: baseline `68b3947ae14f` -> head `7d26_settings_description_text` (33 core migrations). Each plugin owns its own chain (ADR-008); deploy runs `flask db upgrade` then `flask plugin upgrade-all`. Reproducible + idempotent from empty (env.py relaxes session sql_mode so the chain runs on strict MySQL 8).
|
||||||
- Legacy import: `docs/IMPORT-API.md` is the schema-agnostic import contract; `docs/IMPORT-ADOPTION.md` + `docs/PILOT-DEPLOY.md` cover adopting a site; `scripts/site_imports/wjf/` is the West Jefferson reference loader (all 15 stages, validated end-to-end including on a Windows + MySQL 8 VM).
|
- Legacy import: `docs/IMPORT-API.md` is the schema-agnostic import contract; `docs/IMPORT-ADOPTION.md` + `docs/PILOT-DEPLOY.md` cover adopting a site; `scripts/site_imports/wjf/` is the West Jefferson reference loader (all 15 stages, validated end-to-end including on a Windows + MySQL 8 VM).
|
||||||
- API is migration-complete: an admin PAT + docs/IMPORT-API.md let a script import the whole legacy DB (X-Import-Mode preserves timestamps).
|
- API is migration-complete: an admin PAT + docs/IMPORT-API.md let a script import the whole legacy DB (X-Import-Mode preserves timestamps).
|
||||||
- Pre-1.0 framework; sister sites should pin tight `core_version` ranges until contract reaches 1.0
|
- Pre-1.0 framework; sister sites should pin tight `core_version` ranges until contract reaches 1.0
|
||||||
@@ -135,4 +135,4 @@ Each plugin must have:
|
|||||||
- `migrations/FIX_LOCATIONONLY_EQUIPMENT_TYPES.md` - LocationOnly equipment type fix
|
- `migrations/FIX_LOCATIONONLY_EQUIPMENT_TYPES.md` - LocationOnly equipment type fix
|
||||||
- `migrations/PRODUCTION_MIGRATION_GUIDE.md` - production import methods
|
- `migrations/PRODUCTION_MIGRATION_GUIDE.md` - production import methods
|
||||||
- `migrations/rename_underscore_columns.sql` - one-time rename of snake_case columns to lowercase concatenated (per CONTRIBUTING.md)
|
- `migrations/rename_underscore_columns.sql` - one-time rename of snake_case columns to lowercase concatenated (per CONTRIBUTING.md)
|
||||||
- `migrations/versions/` - the core Alembic chain (baseline `68b3947ae14f` -> head `7d25_drop_redundant_indexes`). Run `flask db upgrade` to apply.
|
- `migrations/versions/` - the core Alembic chain (baseline `68b3947ae14f` -> head `7d26_settings_description_text`). Run `flask db upgrade` to apply.
|
||||||
|
|||||||
61
migrations/versions/7d26_settings_description_text.py
Normal file
61
migrations/versions/7d26_settings_description_text.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
"""Widen settings.description to TEXT (was varchar(255))
|
||||||
|
|
||||||
|
Seeded setting descriptions are explanatory sentences; some exceed 255 chars
|
||||||
|
(e.g. dualpath_single_machine). On strict MySQL 8 an over-length insert is a
|
||||||
|
hard error 1406, not a silent truncation, so `flask seed settings` failed on a
|
||||||
|
fresh box. TEXT removes the ceiling and matches value, which is already TEXT.
|
||||||
|
|
||||||
|
Idempotent guard on current column type; real downgrade back to varchar(255).
|
||||||
|
|
||||||
|
Revision ID: 7d26_settings_description_text
|
||||||
|
Revises: 7d25_drop_redundant_indexes
|
||||||
|
Create Date: 2026-07-17
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
revision = '7d26_settings_description_text'
|
||||||
|
down_revision = '7d25_drop_redundant_indexes'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def _description_type(insp):
|
||||||
|
for c in insp.get_columns('settings'):
|
||||||
|
if c['name'] == 'description':
|
||||||
|
return str(c['type']).upper()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
bind = op.get_bind()
|
||||||
|
insp = sa.inspect(bind)
|
||||||
|
|
||||||
|
if 'settings' not in insp.get_table_names():
|
||||||
|
return
|
||||||
|
coltype = _description_type(insp)
|
||||||
|
if coltype is None or 'TEXT' in coltype:
|
||||||
|
return
|
||||||
|
|
||||||
|
op.alter_column('settings', 'description',
|
||||||
|
existing_type=sa.String(length=255),
|
||||||
|
type_=sa.Text(),
|
||||||
|
existing_nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
bind = op.get_bind()
|
||||||
|
insp = sa.inspect(bind)
|
||||||
|
|
||||||
|
if 'settings' not in insp.get_table_names():
|
||||||
|
return
|
||||||
|
coltype = _description_type(insp)
|
||||||
|
if coltype is None or 'TEXT' not in coltype:
|
||||||
|
return
|
||||||
|
|
||||||
|
op.alter_column('settings', 'description',
|
||||||
|
existing_type=sa.Text(),
|
||||||
|
type_=sa.String(length=255),
|
||||||
|
existing_nullable=True)
|
||||||
@@ -26,7 +26,7 @@ class Setting(db.Model):
|
|||||||
value = db.Column(db.Text, nullable=True)
|
value = db.Column(db.Text, nullable=True)
|
||||||
valuetype = db.Column(db.String(20), default='string') # string, boolean, integer, json
|
valuetype = db.Column(db.String(20), default='string') # string, boolean, integer, json
|
||||||
category = db.Column(db.String(50), default='general') # For grouping in UI
|
category = db.Column(db.String(50), default='general') # For grouping in UI
|
||||||
description = db.Column(db.String(255), nullable=True)
|
description = db.Column(db.Text, nullable=True)
|
||||||
createddate = db.Column(db.DateTime, default=_utcnow)
|
createddate = db.Column(db.DateTime, default=_utcnow)
|
||||||
modifieddate = db.Column(db.DateTime, default=_utcnow, onupdate=_utcnow)
|
modifieddate = db.Column(db.DateTime, default=_utcnow, onupdate=_utcnow)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user