From 910ddca96fa53aa55ae20910a234de11dec38bdf Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 29 Oct 2025 19:16:03 -0500 Subject: [PATCH] fix: make master view auto-detect continuum versioning for model class --- src/wuttaweb/views/master.py | 55 +++++++++++++++++++++++------------- tests/views/test_master.py | 18 ++++++++++-- 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/wuttaweb/views/master.py b/src/wuttaweb/views/master.py index 607f6d8..0ca4823 100644 --- a/src/wuttaweb/views/master.py +++ b/src/wuttaweb/views/master.py @@ -350,14 +350,6 @@ class MasterView(View): # pylint: disable=too-many-public-methods "configuring" - i.e. it should have a :meth:`configure()` view. Default value is ``False``. - .. attribute:: has_versions - - Boolean indicating whether the master view should expose - version history for its data records - i.e. it should have a - :meth:`view_versions()` view. Default value is ``False``. - - See also :meth:`should_expose_versions()`. - .. attribute:: version_grid_columns List of columns for the :meth:`view_versions()` view grid. @@ -451,9 +443,6 @@ class MasterView(View): # pylint: disable=too-many-public-methods rows_paginate_on_backend = True rows_viewable = False - # versioning features - has_versions = False - # current action listing = False creating = False @@ -914,12 +903,40 @@ class MasterView(View): # pylint: disable=too-many-public-methods # version history methods ############################## + @classmethod + def is_versioned(cls): + """ + Returns boolean indicating whether the model class is + configured for SQLAlchemy-Continuum versioning. + + The default logic will directly inspect the model class, as + returned by :meth:`get_model_class()`. Or you can override by + setting the ``model_is_versioned`` attribute:: + + class WidgetView(MasterView): + model_class = Widget + model_is_versioned = False + + See also :meth:`should_expose_versions()`. + + :returns: ``True`` if the model class is versioned; else + ``False``. + """ + if hasattr(cls, "model_is_versioned"): + return cls.model_is_versioned + + model_class = cls.get_model_class() + if hasattr(model_class, "__versioned__"): + return True + + return False + @classmethod def get_model_version_class(cls): """ Returns the version class for the master model class. - Should only be relevant if :attr:`has_versions` is true. + Should only be relevant if :meth:`is_versioned()` is true. """ import sqlalchemy_continuum as continuum # pylint: disable=import-outside-toplevel @@ -931,14 +948,14 @@ class MasterView(View): # pylint: disable=too-many-public-methods be exposed for the current user. This will return ``True`` unless any of the following are ``False``: - * :attr:`has_versions` + * :meth:`is_versioned()` * :meth:`wuttjamaican:wuttjamaican.app.AppHandler.continuum_is_enabled()` * ``self.has_perm("versions")`` - cf. :meth:`has_perm()` :returns: ``True`` if versioning should be exposed for current user; else ``False``. """ - if not self.has_versions: + if not self.is_versioned(): return False if not self.app.continuum_is_enabled(): @@ -958,8 +975,8 @@ class MasterView(View): # pylint: disable=too-many-public-methods ``/widgets/XXX/versions/`` where ``XXX`` represents the key/ID for the record. - By default, this view is included only if :attr:`has_versions` - is true. + By default, this view is included only if + :meth:`is_versioned()` is true. The default view logic will show a "grid" (table) with the record's version history. @@ -1147,8 +1164,8 @@ class MasterView(View): # pylint: disable=too-many-public-methods key/ID for the record and YYY represents a SQLAlchemy-Continuum ``transaction.id``. - By default, this view is included only if :attr:`has_versions` - is true. + By default, this view is included only if + :meth:`is_versioned()` is true. The default view logic will display a "diff" table showing how the record's values were changed within a transaction. @@ -3618,7 +3635,7 @@ class MasterView(View): # pylint: disable=too-many-public-methods ) # version history - if cls.has_versions and app.continuum_is_enabled(): + if cls.is_versioned() and app.continuum_is_enabled(): instance_url_prefix = cls.get_instance_url_prefix() config.add_wutta_permission( permission_prefix, diff --git a/tests/views/test_master.py b/tests/views/test_master.py index 0a9bc1b..c02a431 100644 --- a/tests/views/test_master.py +++ b/tests/views/test_master.py @@ -1849,10 +1849,24 @@ class TestVersionedMasterView(VersionWebTestCase): def make_view(self): return mod.MasterView(self.request) + def test_is_versioned(self): + model = self.app.model + + with patch.object(mod.MasterView, "model_class", new=model.User): + + # User is versioned by default + self.assertTrue(mod.MasterView.is_versioned()) + + # but view can override w/ attr + with patch.object( + mod.MasterView, "model_is_versioned", new=False, create=True + ): + self.assertFalse(mod.MasterView.is_versioned()) + def test_defaults(self): model = self.app.model - with patch.multiple(mod.MasterView, model_class=model.User, has_versions=True): + with patch.object(mod.MasterView, "model_class", new=model.User): mod.MasterView.defaults(self.pyramid_config) def test_get_model_version_class(self): @@ -1864,7 +1878,7 @@ class TestVersionedMasterView(VersionWebTestCase): def test_should_expose_versions(self): model = self.app.model - with patch.multiple(mod.MasterView, model_class=model.User, has_versions=True): + with patch.object(mod.MasterView, "model_class", new=model.User): # fully enabled for root user with patch.object(self.request, "is_root", new=True):