3
0
Fork 0

fix: make master view auto-detect continuum versioning for model class

This commit is contained in:
Lance Edgar 2025-10-29 19:16:03 -05:00
parent f33448f64a
commit 910ddca96f
2 changed files with 52 additions and 21 deletions

View file

@ -350,14 +350,6 @@ class MasterView(View): # pylint: disable=too-many-public-methods
"configuring" - i.e. it should have a :meth:`configure()` view. "configuring" - i.e. it should have a :meth:`configure()` view.
Default value is ``False``. 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 .. attribute:: version_grid_columns
List of columns for the :meth:`view_versions()` view grid. 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_paginate_on_backend = True
rows_viewable = False rows_viewable = False
# versioning features
has_versions = False
# current action # current action
listing = False listing = False
creating = False creating = False
@ -914,12 +903,40 @@ class MasterView(View): # pylint: disable=too-many-public-methods
# version history 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 @classmethod
def get_model_version_class(cls): def get_model_version_class(cls):
""" """
Returns the version class for the master model class. 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 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`` be exposed for the current user. This will return ``True``
unless any of the following are ``False``: unless any of the following are ``False``:
* :attr:`has_versions` * :meth:`is_versioned()`
* :meth:`wuttjamaican:wuttjamaican.app.AppHandler.continuum_is_enabled()` * :meth:`wuttjamaican:wuttjamaican.app.AppHandler.continuum_is_enabled()`
* ``self.has_perm("versions")`` - cf. :meth:`has_perm()` * ``self.has_perm("versions")`` - cf. :meth:`has_perm()`
:returns: ``True`` if versioning should be exposed for current :returns: ``True`` if versioning should be exposed for current
user; else ``False``. user; else ``False``.
""" """
if not self.has_versions: if not self.is_versioned():
return False return False
if not self.app.continuum_is_enabled(): 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 ``/widgets/XXX/versions/`` where ``XXX`` represents the key/ID
for the record. for the record.
By default, this view is included only if :attr:`has_versions` By default, this view is included only if
is true. :meth:`is_versioned()` is true.
The default view logic will show a "grid" (table) with the The default view logic will show a "grid" (table) with the
record's version history. 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 key/ID for the record and YYY represents a
SQLAlchemy-Continuum ``transaction.id``. SQLAlchemy-Continuum ``transaction.id``.
By default, this view is included only if :attr:`has_versions` By default, this view is included only if
is true. :meth:`is_versioned()` is true.
The default view logic will display a "diff" table showing how The default view logic will display a "diff" table showing how
the record's values were changed within a transaction. the record's values were changed within a transaction.
@ -3618,7 +3635,7 @@ class MasterView(View): # pylint: disable=too-many-public-methods
) )
# version history # 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() instance_url_prefix = cls.get_instance_url_prefix()
config.add_wutta_permission( config.add_wutta_permission(
permission_prefix, permission_prefix,

View file

@ -1849,10 +1849,24 @@ class TestVersionedMasterView(VersionWebTestCase):
def make_view(self): def make_view(self):
return mod.MasterView(self.request) 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): def test_defaults(self):
model = self.app.model 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) mod.MasterView.defaults(self.pyramid_config)
def test_get_model_version_class(self): def test_get_model_version_class(self):
@ -1864,7 +1878,7 @@ class TestVersionedMasterView(VersionWebTestCase):
def test_should_expose_versions(self): def test_should_expose_versions(self):
model = self.app.model 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 # fully enabled for root user
with patch.object(self.request, "is_root", new=True): with patch.object(self.request, "is_root", new=True):