feat: add way to declare related versions for history in MasterView
This commit is contained in:
parent
205a1f7a65
commit
cba8e4774d
3 changed files with 121 additions and 7 deletions
|
|
@ -61,7 +61,7 @@ dependencies = [
|
||||||
|
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
continuum = ["Wutta-Continuum>=0.3.0"]
|
continuum = ["Wutta-Continuum>=0.3.3"]
|
||||||
docs = ["Sphinx", "furo", "sphinxcontrib-programoutput"]
|
docs = ["Sphinx", "furo", "sphinxcontrib-programoutput"]
|
||||||
tests = ["pylint", "pytest", "pytest-cov", "tox", "WebTest"]
|
tests = ["pylint", "pytest", "pytest-cov", "tox", "WebTest"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1248,8 +1248,11 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
||||||
|
|
||||||
Default query will locate SQLAlchemy-Continuum ``transaction``
|
Default query will locate SQLAlchemy-Continuum ``transaction``
|
||||||
records which are associated with versions of the given model
|
records which are associated with versions of the given model
|
||||||
instance. See also
|
instance. See also:
|
||||||
:func:`wutta-continuum:wutta_continuum.util.model_transaction_query()`.
|
|
||||||
|
* :meth:`get_version_joins()`
|
||||||
|
* :meth:`normalize_version_joins()`
|
||||||
|
* :func:`~wutta-continuum:wutta_continuum.util.model_transaction_query()`
|
||||||
|
|
||||||
:returns: :class:`~sqlalchemy:sqlalchemy.orm.Query` instance
|
:returns: :class:`~sqlalchemy:sqlalchemy.orm.Query` instance
|
||||||
"""
|
"""
|
||||||
|
|
@ -1260,9 +1263,67 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
||||||
|
|
||||||
model_class = self.get_model_class()
|
model_class = self.get_model_class()
|
||||||
txncls = continuum.transaction_class(model_class)
|
txncls = continuum.transaction_class(model_class)
|
||||||
query = model_transaction_query(instance)
|
query = model_transaction_query(instance, joins=self.normalize_version_joins())
|
||||||
return query.order_by(txncls.issued_at.desc())
|
return query.order_by(txncls.issued_at.desc())
|
||||||
|
|
||||||
|
def get_version_joins(self):
|
||||||
|
"""
|
||||||
|
Override this method to declare additional version tables
|
||||||
|
which should be joined when showing the overall revision
|
||||||
|
history for a given model instance.
|
||||||
|
|
||||||
|
Note that whatever this method returns, will be ran through
|
||||||
|
:meth:`normalize_version_joins()` before being passed along to
|
||||||
|
:func:`~wutta-continuum:wutta_continuum.util.model_transaction_query()`.
|
||||||
|
|
||||||
|
:returns: List of version joins info as described below.
|
||||||
|
|
||||||
|
In the simple scenario where an "extension" table is involved,
|
||||||
|
e.g. a ``UserExtension`` table::
|
||||||
|
|
||||||
|
def get_version_joins(self):
|
||||||
|
model = self.app.model
|
||||||
|
return super().get_version_joins() + [
|
||||||
|
model.UserExtension,
|
||||||
|
]
|
||||||
|
|
||||||
|
In the case where a secondary table is "related" to the main
|
||||||
|
model table, but not a standard extension (using the
|
||||||
|
``User.person`` relationship as example)::
|
||||||
|
|
||||||
|
def get_version_joins(self):
|
||||||
|
model = self.app.model
|
||||||
|
return super().get_version_joins() + [
|
||||||
|
(model.Person, "uuid", "person_uuid"),
|
||||||
|
]
|
||||||
|
|
||||||
|
See also :meth:`get_version_grid_data()`.
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
|
def normalize_version_joins(self):
|
||||||
|
"""
|
||||||
|
This method calls :meth:`get_version_joins()` and normalizes
|
||||||
|
the result, which will then get passed along to
|
||||||
|
:func:`~wutta-continuum:wutta_continuum.util.model_transaction_query()`.
|
||||||
|
|
||||||
|
Subclass should (generally) not override this, but instead
|
||||||
|
override :meth:`get_version_joins()`.
|
||||||
|
|
||||||
|
Each element in the return value (list) will be a 3-tuple
|
||||||
|
conforming to what is needed for the query function.
|
||||||
|
|
||||||
|
See also :meth:`get_version_grid_data()`.
|
||||||
|
|
||||||
|
:returns: List of version joins info.
|
||||||
|
"""
|
||||||
|
joins = []
|
||||||
|
for join in self.get_version_joins():
|
||||||
|
if not isinstance(join, tuple):
|
||||||
|
join = (join, "uuid", "uuid")
|
||||||
|
joins.append(join)
|
||||||
|
return joins
|
||||||
|
|
||||||
def configure_version_grid(self, g):
|
def configure_version_grid(self, g):
|
||||||
"""
|
"""
|
||||||
Configure the grid for the :meth:`view_versions()` view.
|
Configure the grid for the :meth:`view_versions()` view.
|
||||||
|
|
@ -1326,7 +1387,9 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
||||||
model_class = self.get_model_class()
|
model_class = self.get_model_class()
|
||||||
route_prefix = self.get_route_prefix()
|
route_prefix = self.get_route_prefix()
|
||||||
txncls = continuum.transaction_class(model_class)
|
txncls = continuum.transaction_class(model_class)
|
||||||
transactions = model_transaction_query(instance)
|
transactions = model_transaction_query(
|
||||||
|
instance, joins=self.normalize_version_joins()
|
||||||
|
)
|
||||||
|
|
||||||
txnid = self.request.matchdict["txnid"]
|
txnid = self.request.matchdict["txnid"]
|
||||||
txn = transactions.filter(txncls.id == txnid).first()
|
txn = transactions.filter(txncls.id == txnid).first()
|
||||||
|
|
@ -1408,15 +1471,34 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
||||||
|
|
||||||
:returns: List of version records.
|
:returns: List of version records.
|
||||||
"""
|
"""
|
||||||
|
import sqlalchemy_continuum as continuum # pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
session = self.Session()
|
session = self.Session()
|
||||||
vercls = self.get_model_version_class()
|
vercls = self.get_model_version_class()
|
||||||
return (
|
versions = []
|
||||||
|
|
||||||
|
# first get all versions for the model instance proper
|
||||||
|
versions.extend(
|
||||||
session.query(vercls)
|
session.query(vercls)
|
||||||
.filter(vercls.transaction == transaction)
|
.filter(vercls.transaction == transaction)
|
||||||
.filter(vercls.uuid == instance.uuid)
|
.filter(vercls.uuid == instance.uuid)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# then get all related versions, per declared joins
|
||||||
|
for child_class, foreign_attr, primary_attr in self.normalize_version_joins():
|
||||||
|
child_vercls = continuum.version_class(child_class)
|
||||||
|
versions.extend(
|
||||||
|
session.query(child_vercls)
|
||||||
|
.filter(child_vercls.transaction == transaction)
|
||||||
|
.filter(
|
||||||
|
getattr(child_vercls, foreign_attr)
|
||||||
|
== getattr(instance, primary_attr)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return versions
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
# autocomplete methods
|
# autocomplete methods
|
||||||
##############################
|
##############################
|
||||||
|
|
|
||||||
|
|
@ -2212,6 +2212,26 @@ class TestVersionedMasterView(VersionWebTestCase):
|
||||||
view = self.make_view()
|
view = self.make_view()
|
||||||
self.assertEqual(view.get_version_grid_columns(), ["issued_at", "user"])
|
self.assertEqual(view.get_version_grid_columns(), ["issued_at", "user"])
|
||||||
|
|
||||||
|
def test_get_version_joins(self):
|
||||||
|
view = self.make_view()
|
||||||
|
self.assertEqual(view.get_version_joins(), [])
|
||||||
|
|
||||||
|
def test_normalize_version_joins(self):
|
||||||
|
model = self.app.model
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
joins = [(model.Person, "uuid", "person_uuid")]
|
||||||
|
with patch.object(view, "get_version_joins", return_value=joins):
|
||||||
|
normal = view.normalize_version_joins()
|
||||||
|
self.assertEqual(normal, joins)
|
||||||
|
self.assertEqual(normal, [(model.Person, "uuid", "person_uuid")])
|
||||||
|
|
||||||
|
joins = [model.Person]
|
||||||
|
with patch.object(view, "get_version_joins", return_value=joins):
|
||||||
|
normal = view.normalize_version_joins()
|
||||||
|
self.assertNotEqual(normal, joins)
|
||||||
|
self.assertEqual(normal, [(model.Person, "uuid", "uuid")])
|
||||||
|
|
||||||
def test_get_version_grid_data(self):
|
def test_get_version_grid_data(self):
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
|
|
||||||
|
|
@ -2300,7 +2320,9 @@ class TestVersionedMasterView(VersionWebTestCase):
|
||||||
txncls = continuum.transaction_class(model.User)
|
txncls = continuum.transaction_class(model.User)
|
||||||
vercls = continuum.version_class(model.User)
|
vercls = continuum.version_class(model.User)
|
||||||
|
|
||||||
user = model.User(username="fred")
|
person = model.Person(full_name="Fred Flintstone")
|
||||||
|
self.session.add(person)
|
||||||
|
user = model.User(username="fred", person=person)
|
||||||
self.session.add(user)
|
self.session.add(user)
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
|
|
||||||
|
|
@ -2314,11 +2336,21 @@ class TestVersionedMasterView(VersionWebTestCase):
|
||||||
with patch.object(mod.MasterView, "model_class", new=model.User):
|
with patch.object(mod.MasterView, "model_class", new=model.User):
|
||||||
with patch.object(mod.MasterView, "Session", return_value=self.session):
|
with patch.object(mod.MasterView, "Session", return_value=self.session):
|
||||||
view = self.make_view()
|
view = self.make_view()
|
||||||
|
|
||||||
|
# just one version if no joins are specified
|
||||||
versions = view.get_relevant_versions(txn, user)
|
versions = view.get_relevant_versions(txn, user)
|
||||||
self.assertEqual(len(versions), 1)
|
self.assertEqual(len(versions), 1)
|
||||||
version = versions[0]
|
version = versions[0]
|
||||||
self.assertIsInstance(version, vercls)
|
self.assertIsInstance(version, vercls)
|
||||||
|
|
||||||
|
# but two versions if we specify join
|
||||||
|
joins = [(model.Person, "uuid", "person_uuid")]
|
||||||
|
with patch.object(view, "get_version_joins", return_value=joins):
|
||||||
|
versions = view.get_relevant_versions(txn, user)
|
||||||
|
self.assertEqual(len(versions), 2)
|
||||||
|
types = sorted([v.__class__.__name__ for v in versions])
|
||||||
|
self.assertEqual(types, ["PersonVersion", "UserVersion"])
|
||||||
|
|
||||||
def test_view_version(self):
|
def test_view_version(self):
|
||||||
import sqlalchemy_continuum as continuum
|
import sqlalchemy_continuum as continuum
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue