feat: basic support for displaying version history
this is not terribly feature-rich yet, just the basics
This commit is contained in:
parent
6d2eccd0ea
commit
f33448f64a
18 changed files with 1323 additions and 66 deletions
|
|
@ -31,12 +31,6 @@ class MockBatchHandler(BatchHandler):
|
|||
|
||||
class TestBatchMasterView(WebTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.setup_web()
|
||||
|
||||
# nb. create MockBatch, MockBatchRow
|
||||
model.Base.metadata.create_all(bind=self.session.bind)
|
||||
|
||||
def make_handler(self):
|
||||
return MockBatchHandler(self.config)
|
||||
|
||||
|
|
@ -51,7 +45,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
self.assertEqual(view.batch_handler, 42)
|
||||
|
||||
def test_get_fallback_templates(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
handler = self.make_handler()
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
|
|
@ -67,7 +61,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
|
||||
def test_render_to_response(self):
|
||||
model = self.app.model
|
||||
handler = MockBatchHandler(self.config)
|
||||
handler = self.make_handler()
|
||||
|
||||
user = model.User(username="barney")
|
||||
self.session.add(user)
|
||||
|
|
@ -87,7 +81,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
self.assertIs(context["batch_handler"], handler)
|
||||
|
||||
def test_configure_grid(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
handler = self.make_handler()
|
||||
with patch.multiple(mod.BatchMasterView, create=True, model_class=MockBatch):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
|
|
@ -98,7 +92,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
view.configure_grid(grid)
|
||||
|
||||
def test_render_batch_id(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
handler = self.make_handler()
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
|
|
@ -112,7 +106,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
self.assertIsNone(result)
|
||||
|
||||
def test_get_instance_title(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
handler = self.make_handler()
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
|
|
@ -127,7 +121,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
self.assertEqual(result, "00000043 runnin some numbers")
|
||||
|
||||
def test_configure_form(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
handler = self.make_handler()
|
||||
with patch.multiple(mod.BatchMasterView, create=True, model_class=MockBatch):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
|
|
@ -163,7 +157,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
view.configure_form(form)
|
||||
|
||||
def test_objectify(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
handler = self.make_handler()
|
||||
with patch.multiple(mod.BatchMasterView, create=True, model_class=MockBatch):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
|
|
@ -194,7 +188,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
|
||||
def test_redirect_after_create(self):
|
||||
self.pyramid_config.add_route("mock_batches.view", "/batch/mock/{uuid}")
|
||||
handler = MockBatchHandler(self.config)
|
||||
handler = self.make_handler()
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
|
|
@ -247,7 +241,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
|
||||
def test_populate_thread(self):
|
||||
model = self.app.model
|
||||
handler = MockBatchHandler(self.config)
|
||||
handler = self.make_handler()
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
|
|
@ -315,7 +309,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
def test_execute(self):
|
||||
self.pyramid_config.add_route("mock_batches.view", "/batch/mock/{uuid}")
|
||||
model = self.app.model
|
||||
handler = MockBatchHandler(self.config)
|
||||
handler = self.make_handler()
|
||||
|
||||
user = model.User(username="barney")
|
||||
self.session.add(user)
|
||||
|
|
@ -345,7 +339,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
self.assertTrue(self.request.session.peek_flash("error"))
|
||||
|
||||
def test_get_row_model_class(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
handler = self.make_handler()
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
|
|
@ -370,7 +364,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
self.assertIs(cls, MockBatchRow)
|
||||
|
||||
def test_get_row_grid_data(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
handler = self.make_handler()
|
||||
model = self.app.model
|
||||
|
||||
user = model.User(username="barney")
|
||||
|
|
@ -401,7 +395,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
self.assertEqual(data.count(), 1)
|
||||
|
||||
def test_configure_row_grid(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
handler = self.make_handler()
|
||||
model = self.app.model
|
||||
|
||||
user = model.User(username="barney")
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue