0027_ui_theme.py 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. """用户界面主题偏好:sys_user 加 ui_theme 列(契约1.20)。
  2. Revision ID: 0027_ui_theme
  3. Revises: 0026_borrow_permission
  4. Create Date: 2026-09-03
  5. 变更要点:
  6. 1. 主题为账号级漫游偏好:任意设备登录后跟随账号;blue 为缺省
  7. (存量用户与未设置路径统一回退蓝色);
  8. 2. CHECK 白名单 blue/red/green(convention 渲染名
  9. ck_sys_user_user_ui_theme,与模型 CheckConstraint(name="user_ui_theme") 一致);
  10. 3. 达梦兼容:探测统一走 SQLAlchemy inspector、约束名大小写不敏感比较,
  11. 删除以库中实际名执行(达梦渲染为大写);
  12. 4. 升级幂等:列/约束先探测,与 Flyway V12 并行执行互不冲突;
  13. 降级删列(偏好可丢失,无审计意义)。
  14. """
  15. from __future__ import annotations
  16. from collections.abc import Sequence
  17. import sqlalchemy as sa
  18. from alembic import op
  19. revision: str = "0027_ui_theme"
  20. down_revision: str | None = "0026_borrow_permission"
  21. branch_labels: str | Sequence[str] | None = None
  22. depends_on: str | Sequence[str] | None = None
  23. _TABLE = "sys_user"
  24. _COLUMN = "ui_theme"
  25. # naming convention 渲染后的最终约束名(ck_%(table)s_%(constraint_name)s)
  26. _CHECK_NAME = "ck_sys_user_user_ui_theme"
  27. def _columns() -> set[str]:
  28. inspector = sa.inspect(op.get_bind())
  29. return {column["name"] for column in inspector.get_columns(_TABLE)}
  30. def _check_exists() -> bool:
  31. existing = {
  32. item["name"].lower()
  33. for item in sa.inspect(op.get_bind()).get_check_constraints(_TABLE)
  34. }
  35. return _CHECK_NAME in existing
  36. def _drop_check() -> None:
  37. # 达梦约束名为大写,以库中实际名执行删除。
  38. for item in sa.inspect(op.get_bind()).get_check_constraints(_TABLE):
  39. if item["name"].lower() == _CHECK_NAME:
  40. op.get_bind().execute(
  41. sa.text(f"ALTER TABLE {_TABLE} DROP CONSTRAINT {item['name']}")
  42. )
  43. return
  44. def upgrade() -> None:
  45. if _COLUMN not in _columns():
  46. op.add_column(
  47. _TABLE,
  48. sa.Column(
  49. _COLUMN,
  50. sa.String(length=12),
  51. nullable=False,
  52. server_default=sa.text("'blue'"),
  53. ),
  54. )
  55. if not _check_exists():
  56. op.create_check_constraint(
  57. "user_ui_theme",
  58. _TABLE,
  59. "ui_theme IN ('blue', 'red', 'green')",
  60. )
  61. def downgrade() -> None:
  62. _drop_check()
  63. if _COLUMN in _columns():
  64. op.drop_column(_TABLE, _COLUMN)