From 3e7aa1fa0b91457d721bfed09241ba4484c01cdb Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 22 Dec 2025 17:35:07 -0600 Subject: [PATCH] feat: add basic views for Alembic Migrations, Dashboard can see all "known" migration revisions (per alembic config), and migrate the DB arbitrarily via alembic upgrade/downgrade --- docs/api/wuttaweb.views.alembic.rst | 6 + docs/conf.py | 1 + docs/index.rst | 1 + src/wuttaweb/forms/widgets.py | 57 ++- src/wuttaweb/templates/alembic/dashboard.mako | 220 ++++++++++ .../templates/alembic/migrations/index.mako | 25 ++ src/wuttaweb/templates/appinfo/index.mako | 8 + src/wuttaweb/templates/tables/index.mako | 25 ++ src/wuttaweb/templates/upgrades/index.mako | 14 + src/wuttaweb/views/alembic.py | 379 ++++++++++++++++++ src/wuttaweb/views/common.py | 6 +- src/wuttaweb/views/essential.py | 22 +- tests/forms/test_widgets.py | 95 +++++ tests/views/test_alembic.py | 297 ++++++++++++++ tests/views/test_tables.py | 3 - 15 files changed, 1151 insertions(+), 8 deletions(-) create mode 100644 docs/api/wuttaweb.views.alembic.rst create mode 100644 src/wuttaweb/templates/alembic/dashboard.mako create mode 100644 src/wuttaweb/templates/alembic/migrations/index.mako create mode 100644 src/wuttaweb/templates/tables/index.mako create mode 100644 src/wuttaweb/templates/upgrades/index.mako create mode 100644 src/wuttaweb/views/alembic.py create mode 100644 tests/views/test_alembic.py diff --git a/docs/api/wuttaweb.views.alembic.rst b/docs/api/wuttaweb.views.alembic.rst new file mode 100644 index 0000000..ce82a7a --- /dev/null +++ b/docs/api/wuttaweb.views.alembic.rst @@ -0,0 +1,6 @@ + +``wuttaweb.views.alembic`` +========================== + +.. automodule:: wuttaweb.views.alembic + :members: diff --git a/docs/conf.py b/docs/conf.py index 6bcd169..7465596 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,6 +28,7 @@ templates_path = ["_templates"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] intersphinx_mapping = { + "alembic": ("https://alembic.sqlalchemy.org/en/latest/", None), "colander": ("https://docs.pylonsproject.org/projects/colander/en/latest/", None), "deform": ("https://docs.pylonsproject.org/projects/deform/en/latest/", None), "fanstatic": ("https://www.fanstatic.org/en/latest/", None), diff --git a/docs/index.rst b/docs/index.rst index 450476e..f910cc9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -58,6 +58,7 @@ the narrative docs are pretty scant. That will eventually change. api/wuttaweb.subscribers api/wuttaweb.util api/wuttaweb.views + api/wuttaweb.views.alembic api/wuttaweb.views.auth api/wuttaweb.views.base api/wuttaweb.views.batch diff --git a/src/wuttaweb/forms/widgets.py b/src/wuttaweb/forms/widgets.py index 0ef6d87..13331b3 100644 --- a/src/wuttaweb/forms/widgets.py +++ b/src/wuttaweb/forms/widgets.py @@ -60,7 +60,7 @@ from deform.widget import ( # pylint: disable=unused-import DateTimeInputWidget, MoneyInputWidget, ) -from webhelpers2.html import HTML +from webhelpers2.html import HTML, tags from wuttjamaican.conf import parse_list @@ -537,3 +537,58 @@ class BatchIdWidget(Widget): # pylint: disable=abstract-method batch_id = int(cstruct) return f"{batch_id:08d}" + + +class AlembicRevisionWidget(Widget): # pylint: disable=missing-class-docstring + """ + Widget to show an Alembic revision identifier, with link to view + the revision. + """ + + def __init__(self, request, *args, **kwargs): + super().__init__(*args, **kwargs) + self.request = request + + def serialize(self, field, cstruct, **kw): # pylint: disable=empty-docstring + """ """ + if not cstruct: + return colander.null + + return tags.link_to( + cstruct, self.request.route_url("alembic.migrations.view", revision=cstruct) + ) + + def deserialize(self, field, pstruct): # pylint: disable=empty-docstring + """ """ + raise NotImplementedError + + +class AlembicRevisionsWidget(Widget): + """ + Widget to show list of Alembic revision identifiers, with links to + view each revision. + """ + + def __init__(self, request, *args, **kwargs): + super().__init__(*args, **kwargs) + self.request = request + self.config = self.request.wutta_config + + def serialize(self, field, cstruct, **kw): # pylint: disable=empty-docstring + """ """ + if not cstruct: + return colander.null + + revisions = [] + for rev in self.config.parse_list(cstruct): + revisions.append( + tags.link_to( + rev, self.request.route_url("alembic.migrations.view", revision=rev) + ) + ) + + return ", ".join(revisions) + + def deserialize(self, field, pstruct): # pylint: disable=empty-docstring + """ """ + raise NotImplementedError diff --git a/src/wuttaweb/templates/alembic/dashboard.mako b/src/wuttaweb/templates/alembic/dashboard.mako new file mode 100644 index 0000000..34ce1a7 --- /dev/null +++ b/src/wuttaweb/templates/alembic/dashboard.mako @@ -0,0 +1,220 @@ +## -*- coding: utf-8; -*- +<%inherit file="/page.mako" /> + +<%def name="page_content()"> +
+ +
+
+ + + {{ script.dir }} + + + + {{ script.env_py_location }} + + + +
    +
  • + {{ path }} +
  • +
+
+ +
+ +
+
+ % if request.has_perm("tables.list"): + + % endif +
+
+ % if request.has_perm("alembic.migrations.list"): + + % endif +
+ % if request.has_perm("alembic.migrate"): +
+ + Migrate Database + +
+ % endif +
+
+ +
+

Script Heads

+ + + + {{ props.row.branch_labels }} + + + {{ props.row.doc }} + + + {{ props.row.is_current ? "Yes" : "No" }} + + + + + + + + + {{ props.row.path }} + + + +
+

Database Heads

+ + + + {{ props.row.branch_labels }} + + + {{ props.row.doc }} + + + + + + + + + {{ props.row.path }} + + + + % if request.has_perm("alembic.migrate"): + <${b}-modal has-modal-card + :active.sync="migrateShowDialog"> + + + % endif + + + +<%def name="modify_vue_vars()"> + ${parent.modify_vue_vars()} + + diff --git a/src/wuttaweb/templates/alembic/migrations/index.mako b/src/wuttaweb/templates/alembic/migrations/index.mako new file mode 100644 index 0000000..b3cbd79 --- /dev/null +++ b/src/wuttaweb/templates/alembic/migrations/index.mako @@ -0,0 +1,25 @@ +## -*- coding: utf-8; -*- +<%inherit file="/master/index.mako" /> + +<%def name="page_content()"> +
+ + % if request.has_perm("alembic.dashboard"): + + % endif + + % if request.has_perm("tables.list"): + + % endif + +
+ ${parent.page_content()} + diff --git a/src/wuttaweb/templates/appinfo/index.mako b/src/wuttaweb/templates/appinfo/index.mako index 45974b6..554be2f 100644 --- a/src/wuttaweb/templates/appinfo/index.mako +++ b/src/wuttaweb/templates/appinfo/index.mako @@ -42,6 +42,14 @@ once /> % endif + % if request.has_perm("alembic.dashboard"): + + % endif +