3
0
Fork 0

feat: basic support for displaying version history

this is not terribly feature-rich yet, just the basics
This commit is contained in:
Lance Edgar 2025-10-29 18:32:35 -05:00
parent 6d2eccd0ea
commit f33448f64a
18 changed files with 1323 additions and 66 deletions

View file

@ -16,7 +16,8 @@ from wuttaweb.views import master as mod
from wuttaweb.views import View
from wuttaweb.progress import SessionProgress
from wuttaweb.subscribers import new_request_set_user
from wuttaweb.testing import WebTestCase
from wuttaweb.testing import WebTestCase, VersionWebTestCase
from wuttaweb.grids import Grid
class TestMasterView(WebTestCase):
@ -1841,3 +1842,247 @@ class TestMasterView(WebTestCase):
# class may specify
with patch.object(view, "rows_title", create=True, new="Mock Rows"):
self.assertEqual(view.get_rows_title(), "Mock Rows")
class TestVersionedMasterView(VersionWebTestCase):
def make_view(self):
return mod.MasterView(self.request)
def test_defaults(self):
model = self.app.model
with patch.multiple(mod.MasterView, model_class=model.User, has_versions=True):
mod.MasterView.defaults(self.pyramid_config)
def test_get_model_version_class(self):
model = self.app.model
with patch.object(mod.MasterView, "model_class", new=model.User):
view = self.make_view()
vercls = view.get_model_version_class()
self.assertEqual(vercls.__name__, "UserVersion")
def test_should_expose_versions(self):
model = self.app.model
with patch.multiple(mod.MasterView, model_class=model.User, has_versions=True):
# fully enabled for root user
with patch.object(self.request, "is_root", new=True):
view = self.make_view()
self.assertTrue(view.should_expose_versions())
# but not if user has no access
view = self.make_view()
self.assertFalse(view.should_expose_versions())
# again, works for root user
with patch.object(self.request, "is_root", new=True):
view = self.make_view()
self.assertTrue(view.should_expose_versions())
# but not if config disables versioning
with patch.object(view.app, "continuum_is_enabled", return_value=False):
self.assertFalse(view.should_expose_versions())
def test_get_version_grid_key(self):
model = self.app.model
with patch.object(mod.MasterView, "model_class", new=model.User):
# default
view = self.make_view()
self.assertEqual(view.get_version_grid_key(), "users.history")
# custom
with patch.object(
mod.MasterView,
"version_grid_key",
new="users_custom_history",
create=True,
):
view = self.make_view()
self.assertEqual(view.get_version_grid_key(), "users_custom_history")
def test_get_version_grid_columns(self):
model = self.app.model
with patch.object(mod.MasterView, "model_class", new=model.User):
# default
view = self.make_view()
self.assertEqual(
view.get_version_grid_columns(),
["id", "issued_at", "user", "remote_addr"],
)
# custom
with patch.object(
mod.MasterView,
"version_grid_columns",
new=["issued_at", "user"],
create=True,
):
view = self.make_view()
self.assertEqual(view.get_version_grid_columns(), ["issued_at", "user"])
def test_get_version_grid_data(self):
model = self.app.model
user = model.User(username="fred")
self.session.add(user)
self.session.commit()
user.username = "freddie"
self.session.commit()
with patch.object(mod.MasterView, "model_class", new=model.User):
view = self.make_view()
query = view.get_version_grid_data(user)
self.assertIsInstance(query, orm.Query)
transactions = query.all()
self.assertEqual(len(transactions), 2)
def test_configure_version_grid(self):
import sqlalchemy_continuum as continuum
model = self.app.model
txncls = continuum.transaction_class(model.User)
with patch.object(mod.MasterView, "model_class", new=model.User):
view = self.make_view()
# this is mostly just for coverage, but we at least can
# confirm something does change
grid = view.make_grid(model_class=txncls)
self.assertNotIn("issued_at", grid.linked_columns)
view.configure_version_grid(grid)
self.assertIn("issued_at", grid.linked_columns)
def test_make_version_grid(self):
model = self.app.model
user = model.User(username="fred")
self.session.add(user)
self.session.commit()
user.username = "freddie"
self.session.commit()
with patch.object(mod.MasterView, "model_class", new=model.User):
with patch.object(mod.MasterView, "Session", return_value=self.session):
with patch.dict(self.request.matchdict, uuid=user.uuid):
view = self.make_view()
grid = view.make_version_grid()
self.assertIsInstance(grid, Grid)
self.assertIsInstance(grid.data, orm.Query)
self.assertEqual(len(grid.data.all()), 2)
def test_view_versions(self):
self.pyramid_config.add_route("home", "/")
self.pyramid_config.add_route("login", "/auth/login")
self.pyramid_config.add_route("users", "/users/")
self.pyramid_config.add_route("users.view", "/users/{uuid}")
self.pyramid_config.add_route("users.version", "/users/{uuid}/versions/{txnid}")
model = self.app.model
user = model.User(username="fred")
self.session.add(user)
self.session.commit()
user.username = "freddie"
self.session.commit()
with patch.object(mod.MasterView, "model_class", new=model.User):
with patch.object(mod.MasterView, "Session", return_value=self.session):
with patch.dict(self.request.matchdict, uuid=user.uuid):
view = self.make_view()
# normal, full page
response = view.view_versions()
self.assertEqual(response.content_type, "text/html")
self.assertIn("<b-table", response.text)
# partial page
with patch.dict(self.request.params, partial="1"):
response = view.view_versions()
self.assertEqual(response.content_type, "application/json")
self.assertIn("data", response.json)
self.assertEqual(len(response.json["data"]), 2)
def test_get_relevant_versions(self):
import sqlalchemy_continuum as continuum
model = self.app.model
txncls = continuum.transaction_class(model.User)
vercls = continuum.version_class(model.User)
user = model.User(username="fred")
self.session.add(user)
self.session.commit()
txn = (
self.session.query(txncls)
.join(vercls, vercls.transaction_id == txncls.id)
.order_by(txncls.id)
.first()
)
with patch.object(mod.MasterView, "model_class", new=model.User):
with patch.object(mod.MasterView, "Session", return_value=self.session):
view = self.make_view()
versions = view.get_relevant_versions(txn, user)
self.assertEqual(len(versions), 1)
version = versions[0]
self.assertIsInstance(version, vercls)
def test_view_version(self):
import sqlalchemy_continuum as continuum
self.pyramid_config.add_route("home", "/")
self.pyramid_config.add_route("login", "/auth/login")
self.pyramid_config.add_route("users", "/users/")
self.pyramid_config.add_route("users.view", "/users/{uuid}")
self.pyramid_config.add_route("users.versions", "/users/{uuid}/versions/")
self.pyramid_config.add_route("users.version", "/users/{uuid}/versions/{txnid}")
model = self.app.model
txncls = continuum.transaction_class(model.User)
vercls = continuum.version_class(model.User)
user = model.User(username="fred")
self.session.add(user)
self.session.commit()
user.username = "freddie"
self.session.commit()
transactions = (
self.session.query(txncls)
.join(vercls, vercls.transaction_id == txncls.id)
.order_by(txncls.id)
.all()
)
self.assertEqual(len(transactions), 2)
with patch.object(mod.MasterView, "model_class", new=model.User):
with patch.object(mod.MasterView, "Session", return_value=self.session):
# invalid txnid
with patch.dict(self.request.matchdict, uuid=user.uuid, txnid=999999):
view = self.make_view()
self.assertRaises(HTTPNotFound, view.view_version)
# first txn
first = transactions[0]
with patch.dict(self.request.matchdict, uuid=user.uuid, txnid=first.id):
view = self.make_view()
response = view.view_version()
self.assertIn(
'<table class="table is-fullwidth is-bordered is-narrow">',
response.text,
)
# second txn
second = transactions[1]
with patch.dict(
self.request.matchdict, uuid=user.uuid, txnid=second.id
):
view = self.make_view()
response = view.view_version()
self.assertIn(
'<table class="table is-fullwidth is-bordered is-narrow">',
response.text,
)