diff --git a/CHANGELOG.md b/CHANGELOG.md
index 47940ea..899001e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,20 +5,6 @@ All notable changes to wuttaweb will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
-## v0.29.0 (2026-03-04)
-
-### Feat
-
-- allow widget factory override for `ObjectRef` schema type
-- add way to declare related versions for history in MasterView
-
-### Fix
-
-- expose default grid pagesize in appinfo config
-- fix timezone edge case for `WuttaDateWidget`
-- sort roles by name when viewing user
-- make pylint happy
-
## v0.28.2 (2026-02-25)
### Fix
diff --git a/pyproject.toml b/pyproject.toml
index d22a6d7..5b7eb80 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
[project]
name = "WuttaWeb"
-version = "0.29.0"
+version = "0.28.2"
description = "Web App for Wutta Framework"
readme = "README.md"
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
@@ -61,7 +61,7 @@ dependencies = [
[project.optional-dependencies]
-continuum = ["Wutta-Continuum>=0.3.3"]
+continuum = ["Wutta-Continuum>=0.3.0"]
docs = ["Sphinx", "furo", "sphinxcontrib-programoutput"]
tests = ["pylint", "pytest", "pytest-cov", "tox", "WebTest"]
diff --git a/src/wuttaweb/forms/schema.py b/src/wuttaweb/forms/schema.py
index 20f957b..e002c0b 100644
--- a/src/wuttaweb/forms/schema.py
+++ b/src/wuttaweb/forms/schema.py
@@ -457,7 +457,6 @@ class ObjectRef(colander.SchemaType):
:returns: Instance of
:class:`~wuttaweb.forms.widgets.ObjectRefWidget`.
"""
- factory = kwargs.pop("factory", widgets.ObjectRefWidget)
if "values" not in kwargs:
query = self.get_query()
@@ -470,7 +469,7 @@ class ObjectRef(colander.SchemaType):
if "url" not in kwargs:
kwargs["url"] = self.get_object_url
- return factory(self.request, **kwargs)
+ return widgets.ObjectRefWidget(self.request, **kwargs)
def get_object_url(self, obj):
"""
diff --git a/src/wuttaweb/forms/widgets.py b/src/wuttaweb/forms/widgets.py
index ac65667..93be573 100644
--- a/src/wuttaweb/forms/widgets.py
+++ b/src/wuttaweb/forms/widgets.py
@@ -234,10 +234,7 @@ class WuttaDateWidget(DateInputWidget):
""" """
readonly = kw.get("readonly", self.readonly)
if readonly and cstruct:
- try:
- dt = datetime.date.fromisoformat(cstruct)
- except ValueError:
- dt = datetime.datetime.fromisoformat(cstruct)
+ dt = datetime.datetime.fromisoformat(cstruct)
return self.app.render_date(dt)
return super().serialize(field, cstruct, **kw)
diff --git a/src/wuttaweb/templates/appinfo/configure.mako b/src/wuttaweb/templates/appinfo/configure.mako
index 2cc4dc9..fc0d886 100644
--- a/src/wuttaweb/templates/appinfo/configure.mako
+++ b/src/wuttaweb/templates/appinfo/configure.mako
@@ -158,24 +158,6 @@
-
Grids
-
-
-
-
-
-
-
-
-
-
-
Web Libraries
@@ -366,8 +348,6 @@
ThisPageData.validators.push(ThisPage.methods.timezoneValidate)
- ThisPageData.gridPagesizeOptions = ${json.dumps(grid_pagesize_options)|n}
-
ThisPageData.weblibs = ${json.dumps(weblibs or [])|n}
ThisPageData.editWebLibraryShowDialog = false
diff --git a/src/wuttaweb/views/master.py b/src/wuttaweb/views/master.py
index 5f48e50..1f919c1 100644
--- a/src/wuttaweb/views/master.py
+++ b/src/wuttaweb/views/master.py
@@ -1248,11 +1248,8 @@ class MasterView(View): # pylint: disable=too-many-public-methods
Default query will locate SQLAlchemy-Continuum ``transaction``
records which are associated with versions of the given model
- instance. See also:
-
- * :meth:`get_version_joins()`
- * :meth:`normalize_version_joins()`
- * :func:`~wutta-continuum:wutta_continuum.util.model_transaction_query()`
+ instance. See also
+ :func:`wutta-continuum:wutta_continuum.util.model_transaction_query()`.
:returns: :class:`~sqlalchemy:sqlalchemy.orm.Query` instance
"""
@@ -1263,67 +1260,9 @@ class MasterView(View): # pylint: disable=too-many-public-methods
model_class = self.get_model_class()
txncls = continuum.transaction_class(model_class)
- query = model_transaction_query(instance, joins=self.normalize_version_joins())
+ query = model_transaction_query(instance)
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):
"""
Configure the grid for the :meth:`view_versions()` view.
@@ -1387,9 +1326,7 @@ class MasterView(View): # pylint: disable=too-many-public-methods
model_class = self.get_model_class()
route_prefix = self.get_route_prefix()
txncls = continuum.transaction_class(model_class)
- transactions = model_transaction_query(
- instance, joins=self.normalize_version_joins()
- )
+ transactions = model_transaction_query(instance)
txnid = self.request.matchdict["txnid"]
txn = transactions.filter(txncls.id == txnid).first()
@@ -1471,34 +1408,15 @@ class MasterView(View): # pylint: disable=too-many-public-methods
:returns: List of version records.
"""
- import sqlalchemy_continuum as continuum # pylint: disable=import-outside-toplevel
-
session = self.Session()
vercls = self.get_model_version_class()
- versions = []
-
- # first get all versions for the model instance proper
- versions.extend(
+ return (
session.query(vercls)
.filter(vercls.transaction == transaction)
.filter(vercls.uuid == instance.uuid)
.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
##############################
diff --git a/src/wuttaweb/views/settings.py b/src/wuttaweb/views/settings.py
index 04a529e..5b1e293 100644
--- a/src/wuttaweb/views/settings.py
+++ b/src/wuttaweb/views/settings.py
@@ -228,8 +228,6 @@ class AppInfoView(MasterView): # pylint: disable=abstract-method
{"name": f"{self.config.appname}.email.default.to"},
{"name": f"{self.config.appname}.email.feedback.subject"},
{"name": f"{self.config.appname}.email.feedback.to"},
- # grids
- {"name": "wuttaweb.grids.default_pagesize", "type": int},
]
def getval(key):
@@ -284,11 +282,6 @@ class AppInfoView(MasterView): # pylint: disable=abstract-method
handlers = [{"spec": spec} for spec in handlers]
context["menu_handlers"] = handlers
- # add pagesize options
- g = self.make_grid()
- context["grid_pagesize_options"] = g.get_pagesize_options()
- context["grid_pagesize_default"] = g.get_pagesize()
-
# add `weblibs` to context, based on config values
weblibs = self.get_weblibs()
for key in weblibs:
diff --git a/tests/forms/test_widgets.py b/tests/forms/test_widgets.py
index deb44ab..818c38a 100644
--- a/tests/forms/test_widgets.py
+++ b/tests/forms/test_widgets.py
@@ -139,9 +139,6 @@ class TestWuttaDateWidget(WebTestCase):
return mod.WuttaDateWidget(self.request, **kwargs)
def test_serialize(self):
- self.config.setdefault("wuttatest.timezone.default", "America/Los_Angeles")
- tzlocal = get_timezone_by_name("America/Los_Angeles")
-
node = colander.SchemaNode(colander.Date())
field = self.make_field(node)
@@ -159,9 +156,7 @@ class TestWuttaDateWidget(WebTestCase):
# now try again with datetime
widget = self.make_widget()
- # nb. local zone is Los_Angeles but this is presumed to be "naive UTC"
- # hence local date is 2025-01-14
- dt = datetime.datetime(2025, 1, 15, 4, 35)
+ dt = datetime.datetime(2025, 1, 15, 8, 35)
# editable widget has normal picker html
result = widget.serialize(field, str(dt))
@@ -169,7 +164,7 @@ class TestWuttaDateWidget(WebTestCase):
# readonly is rendered per app convention
result = widget.serialize(field, str(dt), readonly=True)
- self.assertEqual(result, "2025-01-14")
+ self.assertEqual(result, "2025-01-15")
class TestWuttaDateTimeWidget(WebTestCase):
diff --git a/tests/views/test_master.py b/tests/views/test_master.py
index 58d1c29..6050edf 100644
--- a/tests/views/test_master.py
+++ b/tests/views/test_master.py
@@ -1785,8 +1785,6 @@ class TestMasterView(WebTestCase):
kw = original_context(**kw)
kw["menu_handlers"] = []
kw["default_timezone"] = "UTC"
- kw["grid_pagesize_options"] = [10, 20, 50]
- kw["grid_pagesize_default"] = 20
return kw
with patch.object(view, "configure_get_context", new=get_context):
@@ -2214,26 +2212,6 @@ class TestVersionedMasterView(VersionWebTestCase):
view = self.make_view()
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):
model = self.app.model
@@ -2322,9 +2300,7 @@ class TestVersionedMasterView(VersionWebTestCase):
txncls = continuum.transaction_class(model.User)
vercls = continuum.version_class(model.User)
- person = model.Person(full_name="Fred Flintstone")
- self.session.add(person)
- user = model.User(username="fred", person=person)
+ user = model.User(username="fred")
self.session.add(user)
self.session.commit()
@@ -2338,21 +2314,11 @@ class TestVersionedMasterView(VersionWebTestCase):
with patch.object(mod.MasterView, "model_class", new=model.User):
with patch.object(mod.MasterView, "Session", return_value=self.session):
view = self.make_view()
-
- # just one version if no joins are specified
versions = view.get_relevant_versions(txn, user)
self.assertEqual(len(versions), 1)
version = versions[0]
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):
import sqlalchemy_continuum as continuum