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