From d13f908da5dc6275a4c80074d77d2768208446c5 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 15 Dec 2025 16:13:55 -0600 Subject: [PATCH 1/4] feat: drop timezone, assume UTC for all datetime values in DB per changes in wuttjamaican --- pyproject.toml | 2 +- src/sideshow/batch/neworder.py | 3 +- .../versions/9f2ff39f4743_drop_time_zones.py | 531 ++++++++++++++++++ src/sideshow/db/model/customers.py | 7 +- src/sideshow/db/model/orders.py | 13 +- src/sideshow/db/model/products.py | 7 +- 6 files changed, 545 insertions(+), 18 deletions(-) create mode 100644 src/sideshow/db/alembic/versions/9f2ff39f4743_drop_time_zones.py diff --git a/pyproject.toml b/pyproject.toml index 6af181a..a5feec4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ license = {text = "GNU General Public License v3+"} requires-python = ">= 3.8" dependencies = [ "SQLAlchemy<2", # TODO: should allow 2.x - "WuttaWeb>=0.23.1", + "WuttaWeb>=0.24.0", ] [project.optional-dependencies] diff --git a/src/sideshow/batch/neworder.py b/src/sideshow/batch/neworder.py index 599ad73..2f428b4 100644 --- a/src/sideshow/batch/neworder.py +++ b/src/sideshow/batch/neworder.py @@ -25,7 +25,6 @@ New Order Batch Handler """ # pylint: disable=too-many-lines -import datetime import decimal from collections import OrderedDict @@ -921,7 +920,7 @@ class NewOrderBatchHandler(BatchHandler): # pylint: disable=too-many-public-met row.unit_price_quoted = None row.case_price_quoted = None if row.unit_price_sale is not None and ( - not row.sale_ends or row.sale_ends > datetime.datetime.now() + not row.sale_ends or row.sale_ends > self.app.make_utc() ): row.unit_price_quoted = row.unit_price_sale else: diff --git a/src/sideshow/db/alembic/versions/9f2ff39f4743_drop_time_zones.py b/src/sideshow/db/alembic/versions/9f2ff39f4743_drop_time_zones.py new file mode 100644 index 0000000..60fefaa --- /dev/null +++ b/src/sideshow/db/alembic/versions/9f2ff39f4743_drop_time_zones.py @@ -0,0 +1,531 @@ +"""drop time zones + +Revision ID: 9f2ff39f4743 +Revises: fd8a2527bd30 +Create Date: 2025-12-15 15:14:38.281566 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import wuttjamaican.db.util +from sqlalchemy.dialects import postgresql +from wuttjamaican.util import make_utc + +# revision identifiers, used by Alembic. +revision: str = "9f2ff39f4743" +down_revision: Union[str, None] = "fd8a2527bd30" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + + # sideshow_batch_neworder.created + op.add_column( + "sideshow_batch_neworder", + sa.Column("created_new", sa.DateTime(), nullable=True), + ) + sideshow_batch_neworder = sa.sql.table( + "sideshow_batch_neworder", + sa.sql.column("uuid"), + sa.sql.column("created"), + sa.sql.column("created_new"), + ) + cursor = op.get_bind().execute(sideshow_batch_neworder.select()) + for row in cursor.fetchall(): + op.get_bind().execute( + sideshow_batch_neworder.update() + .where(sideshow_batch_neworder.c.uuid == row.uuid) + .values({"created_new": make_utc(row.created)}) + ) + op.drop_column("sideshow_batch_neworder", "created") + op.alter_column( + "sideshow_batch_neworder", + "created_new", + new_column_name="created", + nullable=False, + existing_type=sa.DateTime(), + existing_nullable=True, + ) + + # sideshow_batch_neworder.executed + op.add_column( + "sideshow_batch_neworder", + sa.Column("executed_new", sa.DateTime(), nullable=True), + ) + sideshow_batch_neworder = sa.sql.table( + "sideshow_batch_neworder", + sa.sql.column("uuid"), + sa.sql.column("executed"), + sa.sql.column("executed_new"), + ) + cursor = op.get_bind().execute(sideshow_batch_neworder.select()) + for row in cursor.fetchall(): + if row.executed: + op.get_bind().execute( + sideshow_batch_neworder.update() + .where(sideshow_batch_neworder.c.uuid == row.uuid) + .values({"executed_new": make_utc(row.executed)}) + ) + op.drop_column("sideshow_batch_neworder", "executed") + op.alter_column( + "sideshow_batch_neworder", + "executed_new", + new_column_name="executed", + existing_type=sa.DateTime(), + existing_nullable=True, + ) + + # sideshow_batch_neworder_row.modified + op.add_column( + "sideshow_batch_neworder_row", + sa.Column("modified_new", sa.DateTime(), nullable=True), + ) + sideshow_batch_neworder_row = sa.sql.table( + "sideshow_batch_neworder_row", + sa.sql.column("uuid"), + sa.sql.column("modified"), + sa.sql.column("modified_new"), + ) + cursor = op.get_bind().execute(sideshow_batch_neworder_row.select()) + for row in cursor.fetchall(): + if row.modified: + op.get_bind().execute( + sideshow_batch_neworder_row.update() + .where(sideshow_batch_neworder_row.c.uuid == row.uuid) + .values({"modified_new": make_utc(row.modified)}) + ) + op.drop_column("sideshow_batch_neworder_row", "modified") + op.alter_column( + "sideshow_batch_neworder_row", + "modified_new", + new_column_name="modified", + existing_type=sa.DateTime(), + existing_nullable=True, + ) + + # sideshow_batch_neworder_row.sale_ends + op.add_column( + "sideshow_batch_neworder_row", + sa.Column("sale_ends_new", sa.DateTime(), nullable=True), + ) + sideshow_batch_neworder_row = sa.sql.table( + "sideshow_batch_neworder_row", + sa.sql.column("uuid"), + sa.sql.column("sale_ends"), + sa.sql.column("sale_ends_new"), + ) + cursor = op.get_bind().execute(sideshow_batch_neworder_row.select()) + for row in cursor.fetchall(): + if row.sale_ends: + op.get_bind().execute( + sideshow_batch_neworder_row.update() + .where(sideshow_batch_neworder_row.c.uuid == row.uuid) + .values({"sale_ends_new": make_utc(row.sale_ends)}) + ) + op.drop_column("sideshow_batch_neworder_row", "sale_ends") + op.alter_column( + "sideshow_batch_neworder_row", + "sale_ends_new", + new_column_name="sale_ends", + existing_type=sa.DateTime(), + existing_nullable=True, + ) + + # sideshow_customer_pending.created + op.add_column( + "sideshow_customer_pending", + sa.Column("created_new", sa.DateTime(), nullable=True), + ) + sideshow_customer_pending = sa.sql.table( + "sideshow_customer_pending", + sa.sql.column("uuid"), + sa.sql.column("created"), + sa.sql.column("created_new"), + ) + cursor = op.get_bind().execute(sideshow_customer_pending.select()) + for row in cursor.fetchall(): + op.get_bind().execute( + sideshow_customer_pending.update() + .where(sideshow_customer_pending.c.uuid == row.uuid) + .values({"created_new": make_utc(row.created)}) + ) + op.drop_column("sideshow_customer_pending", "created") + op.alter_column( + "sideshow_customer_pending", + "created_new", + new_column_name="created", + nullable=False, + existing_type=sa.DateTime(), + existing_nullable=True, + ) + + # sideshow_order.created + op.add_column( + "sideshow_order", + sa.Column("created_new", sa.DateTime(), nullable=True), + ) + sideshow_order = sa.sql.table( + "sideshow_order", + sa.sql.column("uuid"), + sa.sql.column("created"), + sa.sql.column("created_new"), + ) + cursor = op.get_bind().execute(sideshow_order.select()) + for row in cursor.fetchall(): + op.get_bind().execute( + sideshow_order.update() + .where(sideshow_order.c.uuid == row.uuid) + .values({"created_new": make_utc(row.created)}) + ) + op.drop_column("sideshow_order", "created") + op.alter_column( + "sideshow_order", + "created_new", + new_column_name="created", + nullable=False, + existing_type=sa.DateTime(), + existing_nullable=True, + ) + + # sideshow_order_item.sale_ends + op.add_column( + "sideshow_order_item", + sa.Column("sale_ends_new", sa.DateTime(), nullable=True), + ) + sideshow_order_item = sa.sql.table( + "sideshow_order_item", + sa.sql.column("uuid"), + sa.sql.column("sale_ends"), + sa.sql.column("sale_ends_new"), + ) + cursor = op.get_bind().execute(sideshow_order_item.select()) + for row in cursor.fetchall(): + if row.sale_ends: + op.get_bind().execute( + sideshow_order_item.update() + .where(sideshow_order_item.c.uuid == row.uuid) + .values({"sale_ends_new": make_utc(row.sale_ends)}) + ) + op.drop_column("sideshow_order_item", "sale_ends") + op.alter_column( + "sideshow_order_item", + "sale_ends_new", + new_column_name="sale_ends", + existing_type=sa.DateTime(), + existing_nullable=True, + ) + + # sideshow_order_item_event.occurred + op.add_column( + "sideshow_order_item_event", + sa.Column("occurred_new", sa.DateTime(), nullable=True), + ) + sideshow_order_item_event = sa.sql.table( + "sideshow_order_item_event", + sa.sql.column("uuid"), + sa.sql.column("occurred"), + sa.sql.column("occurred_new"), + ) + cursor = op.get_bind().execute(sideshow_order_item_event.select()) + for row in cursor.fetchall(): + op.get_bind().execute( + sideshow_order_item_event.update() + .where(sideshow_order_item_event.c.uuid == row.uuid) + .values({"occurred_new": make_utc(row.occurred)}) + ) + op.drop_column("sideshow_order_item_event", "occurred") + op.alter_column( + "sideshow_order_item_event", + "occurred_new", + new_column_name="occurred", + nullable=False, + existing_type=sa.DateTime(), + existing_nullable=True, + ) + + # sideshow_product_pending.created + op.add_column( + "sideshow_product_pending", + sa.Column("created_new", sa.DateTime(), nullable=True), + ) + sideshow_product_pending = sa.sql.table( + "sideshow_product_pending", + sa.sql.column("uuid"), + sa.sql.column("created"), + sa.sql.column("created_new"), + ) + cursor = op.get_bind().execute(sideshow_product_pending.select()) + for row in cursor.fetchall(): + op.get_bind().execute( + sideshow_product_pending.update() + .where(sideshow_product_pending.c.uuid == row.uuid) + .values({"created_new": make_utc(row.created)}) + ) + op.drop_column("sideshow_product_pending", "created") + op.alter_column( + "sideshow_product_pending", + "created_new", + new_column_name="created", + nullable=False, + existing_type=sa.DateTime(), + existing_nullable=True, + ) + + +def downgrade() -> None: + + # sideshow_product_pending.created + op.add_column( + "sideshow_product_pending", + sa.Column("created_old", sa.DateTime(timezone=True), nullable=True), + ) + sideshow_product_pending = sa.sql.table( + "sideshow_product_pending", + sa.sql.column("uuid"), + sa.sql.column("created"), + sa.sql.column("created_old"), + ) + cursor = op.get_bind().execute(sideshow_product_pending.select()) + for row in cursor.fetchall(): + op.get_bind().execute( + sideshow_product_pending.update() + .where(sideshow_product_pending.c.uuid == row.uuid) + .values({"created_old": row.created}) + ) + op.drop_column("sideshow_product_pending", "created") + op.alter_column( + "sideshow_product_pending", + "created_old", + new_column_name="created", + nullable=False, + existing_type=sa.DateTime(timezone=True), + existing_nullable=True, + ) + + # sideshow_order_item_event.occurred + op.add_column( + "sideshow_order_item_event", + sa.Column("occurred_old", sa.DateTime(timezone=True), nullable=True), + ) + sideshow_order_item_event = sa.sql.table( + "sideshow_order_item_event", + sa.sql.column("uuid"), + sa.sql.column("occurred"), + sa.sql.column("occurred_old"), + ) + cursor = op.get_bind().execute(sideshow_order_item_event.select()) + for row in cursor.fetchall(): + op.get_bind().execute( + sideshow_order_item_event.update() + .where(sideshow_order_item_event.c.uuid == row.uuid) + .values({"occurred_old": row.occurred}) + ) + op.drop_column("sideshow_order_item_event", "occurred") + op.alter_column( + "sideshow_order_item_event", + "occurred_old", + new_column_name="occurred", + nullable=False, + existing_type=sa.DateTime(timezone=True), + existing_nullable=True, + ) + + # sideshow_order_item.sale_ends + op.add_column( + "sideshow_order_item", + sa.Column("sale_ends_old", sa.DateTime(timezone=True), nullable=True), + ) + sideshow_order_item = sa.sql.table( + "sideshow_order_item", + sa.sql.column("uuid"), + sa.sql.column("sale_ends"), + sa.sql.column("sale_ends_old"), + ) + cursor = op.get_bind().execute(sideshow_order_item.select()) + for row in cursor.fetchall(): + if row.sale_ends: + op.get_bind().execute( + sideshow_order_item.update() + .where(sideshow_order_item.c.uuid == row.uuid) + .values({"sale_ends_old": row.sale_ends}) + ) + op.drop_column("sideshow_order_item", "sale_ends") + op.alter_column( + "sideshow_order_item", + "sale_ends_old", + new_column_name="sale_ends", + existing_type=sa.DateTime(timezone=True), + existing_nullable=True, + ) + + # sideshow_order.created + op.add_column( + "sideshow_order", + sa.Column("created_old", sa.DateTime(timezone=True), nullable=True), + ) + sideshow_order = sa.sql.table( + "sideshow_order", + sa.sql.column("uuid"), + sa.sql.column("created"), + sa.sql.column("created_old"), + ) + cursor = op.get_bind().execute(sideshow_order.select()) + for row in cursor.fetchall(): + op.get_bind().execute( + sideshow_order.update() + .where(sideshow_order.c.uuid == row.uuid) + .values({"created_old": row.created}) + ) + op.drop_column("sideshow_order", "created") + op.alter_column( + "sideshow_order", + "created_old", + new_column_name="created", + nullable=False, + existing_type=sa.DateTime(timezone=True), + existing_nullable=True, + ) + + # sideshow_customer_pending.created + op.add_column( + "sideshow_customer_pending", + sa.Column("created_old", sa.DateTime(timezone=True), nullable=True), + ) + sideshow_customer_pending = sa.sql.table( + "sideshow_customer_pending", + sa.sql.column("uuid"), + sa.sql.column("created"), + sa.sql.column("created_old"), + ) + cursor = op.get_bind().execute(sideshow_customer_pending.select()) + for row in cursor.fetchall(): + op.get_bind().execute( + sideshow_customer_pending.update() + .where(sideshow_customer_pending.c.uuid == row.uuid) + .values({"created_old": row.created}) + ) + op.drop_column("sideshow_customer_pending", "created") + op.alter_column( + "sideshow_customer_pending", + "created_old", + new_column_name="created", + nullable=False, + existing_type=sa.DateTime(timezone=True), + existing_nullable=True, + ) + + # sideshow_batch_neworder_row.sale_ends + op.add_column( + "sideshow_batch_neworder_row", + sa.Column("sale_ends_old", sa.DateTime(timezone=True), nullable=True), + ) + sideshow_batch_neworder_row = sa.sql.table( + "sideshow_batch_neworder_row", + sa.sql.column("uuid"), + sa.sql.column("sale_ends"), + sa.sql.column("sale_ends_old"), + ) + cursor = op.get_bind().execute(sideshow_batch_neworder_row.select()) + for row in cursor.fetchall(): + if row.sale_ends: + op.get_bind().execute( + sideshow_batch_neworder_row.update() + .where(sideshow_batch_neworder_row.c.uuid == row.uuid) + .values({"sale_ends_old": row.sale_ends}) + ) + op.drop_column("sideshow_batch_neworder_row", "sale_ends") + op.alter_column( + "sideshow_batch_neworder_row", + "sale_ends_old", + new_column_name="sale_ends", + existing_type=sa.DateTime(timezone=True), + existing_nullable=True, + ) + + # sideshow_batch_neworder_row.modified + op.add_column( + "sideshow_batch_neworder_row", + sa.Column("modified_old", sa.DateTime(timezone=True), nullable=True), + ) + sideshow_batch_neworder_row = sa.sql.table( + "sideshow_batch_neworder_row", + sa.sql.column("uuid"), + sa.sql.column("modified"), + sa.sql.column("modified_old"), + ) + cursor = op.get_bind().execute(sideshow_batch_neworder_row.select()) + for row in cursor.fetchall(): + if row.modified: + op.get_bind().execute( + sideshow_batch_neworder_row.update() + .where(sideshow_batch_neworder_row.c.uuid == row.uuid) + .values({"modified_old": row.modified}) + ) + op.drop_column("sideshow_batch_neworder_row", "modified") + op.alter_column( + "sideshow_batch_neworder_row", + "modified_old", + new_column_name="modified", + existing_type=sa.DateTime(timezone=True), + existing_nullable=True, + ) + + # sideshow_batch_neworder.executed + op.add_column( + "sideshow_batch_neworder", + sa.Column("executed_old", sa.DateTime(timezone=True), nullable=True), + ) + sideshow_batch_neworder = sa.sql.table( + "sideshow_batch_neworder", + sa.sql.column("uuid"), + sa.sql.column("executed"), + sa.sql.column("executed_old"), + ) + cursor = op.get_bind().execute(sideshow_batch_neworder.select()) + for row in cursor.fetchall(): + if row.executed: + op.get_bind().execute( + sideshow_batch_neworder.update() + .where(sideshow_batch_neworder.c.uuid == row.uuid) + .values({"executed_old": row.executed}) + ) + op.drop_column("sideshow_batch_neworder", "executed") + op.alter_column( + "sideshow_batch_neworder", + "executed_old", + new_column_name="executed", + existing_type=sa.DateTime(timezone=True), + existing_nullable=True, + ) + + # sideshow_batch_neworder.created + op.add_column( + "sideshow_batch_neworder", + sa.Column("created_old", sa.DateTime(timezone=True), nullable=True), + ) + sideshow_batch_neworder = sa.sql.table( + "sideshow_batch_neworder", + sa.sql.column("uuid"), + sa.sql.column("created"), + sa.sql.column("created_old"), + ) + cursor = op.get_bind().execute(sideshow_batch_neworder.select()) + for row in cursor.fetchall(): + op.get_bind().execute( + sideshow_batch_neworder.update() + .where(sideshow_batch_neworder.c.uuid == row.uuid) + .values({"created_old": row.created}) + ) + op.drop_column("sideshow_batch_neworder", "created") + op.alter_column( + "sideshow_batch_neworder", + "created_old", + new_column_name="created", + nullable=False, + existing_type=sa.DateTime(timezone=True), + existing_nullable=True, + ) diff --git a/src/sideshow/db/model/customers.py b/src/sideshow/db/model/customers.py index deb081c..7ef8024 100644 --- a/src/sideshow/db/model/customers.py +++ b/src/sideshow/db/model/customers.py @@ -24,12 +24,11 @@ Data models for Customers """ -import datetime - import sqlalchemy as sa from sqlalchemy import orm from wuttjamaican.db import model +from wuttjamaican.util import make_utc from sideshow.enum import PendingCustomerStatus @@ -175,9 +174,9 @@ class PendingCustomer( # pylint: disable=too-few-public-methods ) created = sa.Column( - sa.DateTime(timezone=True), + sa.DateTime(), nullable=False, - default=datetime.datetime.now, + default=make_utc, doc=""" Timestamp when the customer record was created. """, diff --git a/src/sideshow/db/model/orders.py b/src/sideshow/db/model/orders.py index ff99001..6ff7cfa 100644 --- a/src/sideshow/db/model/orders.py +++ b/src/sideshow/db/model/orders.py @@ -24,13 +24,12 @@ Data models for Orders """ -import datetime - import sqlalchemy as sa from sqlalchemy import orm from sqlalchemy.ext.orderinglist import ordering_list from wuttjamaican.db import model +from wuttjamaican.util import make_utc class OrderMixin: # pylint: disable=too-few-public-methods @@ -266,7 +265,7 @@ class OrderItemMixin: # pylint: disable=too-few-public-methods ) sale_ends = sa.Column( - sa.DateTime(timezone=True), + sa.DateTime(), nullable=True, doc=""" End date/time for the sale in effect, if any. @@ -403,9 +402,9 @@ class Order( # pylint: disable=too-few-public-methods,duplicate-code ) created = sa.Column( - sa.DateTime(timezone=True), + sa.DateTime(), nullable=False, - default=datetime.datetime.now, + default=make_utc, doc=""" Timestamp when the order was created. @@ -596,9 +595,9 @@ class OrderItemEvent(model.Base): # pylint: disable=too-few-public-methods ) occurred = sa.Column( - sa.DateTime(timezone=True), + sa.DateTime(), nullable=False, - default=datetime.datetime.now, + default=make_utc, doc=""" Date and time when the event occurred. """, diff --git a/src/sideshow/db/model/products.py b/src/sideshow/db/model/products.py index f549b1c..fc40ff3 100644 --- a/src/sideshow/db/model/products.py +++ b/src/sideshow/db/model/products.py @@ -24,12 +24,11 @@ Data models for Products """ -import datetime - import sqlalchemy as sa from sqlalchemy import orm from wuttjamaican.db import model +from wuttjamaican.util import make_utc from sideshow.enum import PendingProductStatus @@ -265,9 +264,9 @@ class PendingProduct( # pylint: disable=too-few-public-methods ) created = sa.Column( - sa.DateTime(timezone=True), + sa.DateTime(), nullable=False, - default=datetime.datetime.now, + default=make_utc, doc=""" Timestamp when the product record was created. """, From 38d247bac4147768ab66304a47218a51e7ebd4f5 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 15 Dec 2025 16:58:34 -0600 Subject: [PATCH 2/4] test: fix broken test for neworder batch view not sure how/when this test broke - it doesn't *seem* related to the timezone changes, but iirc nothing else changed recently --- tests/web/views/batch/test_neworder.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/web/views/batch/test_neworder.py b/tests/web/views/batch/test_neworder.py index 8a34b26..f4b5dd8 100644 --- a/tests/web/views/batch/test_neworder.py +++ b/tests/web/views/batch/test_neworder.py @@ -86,9 +86,19 @@ class TestNewOrderBatchView(WebTestCase): def test_configure_row_grid(self): model = self.app.model view = self.make_view() + handler = view.batch_handler + + user = model.User(username="fred") + self.session.add(user) + batch = handler.make_batch(self.session, created_by=user) + self.session.add(batch) + self.session.commit() + grid = view.make_grid(model_class=model.NewOrderBatchRow) self.assertNotIn("total_price", grid.renderers) - view.configure_row_grid(grid) + with patch.object(view, "Session", return_value=self.session): + with patch.object(self.request, "matchdict", new={"uuid": batch.uuid}): + view.configure_row_grid(grid) self.assertIn("total_price", grid.renderers) def test_get_xref_buttons(self): From c7c86e6e868fbed879981871e62fd980b643d3c7 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 15 Dec 2025 17:04:48 -0600 Subject: [PATCH 3/4] fix: define `get_row_parent()` for OrderView --- src/sideshow/web/views/orders.py | 5 +++++ tests/web/views/test_orders.py | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/sideshow/web/views/orders.py b/src/sideshow/web/views/orders.py index d38b728..71907a6 100644 --- a/src/sideshow/web/views/orders.py +++ b/src/sideshow/web/views/orders.py @@ -1025,6 +1025,11 @@ class OrderView(MasterView): # pylint: disable=too-many-public-methods session = self.Session() return session.query(model.OrderItem).filter(model.OrderItem.order == order) + def get_row_parent(self, row): # pylint: disable=empty-docstring + """ """ + item = row + return item.order + def configure_row_grid(self, grid): # pylint: disable=empty-docstring """ """ g = grid diff --git a/tests/web/views/test_orders.py b/tests/web/views/test_orders.py index 7e01411..598f418 100644 --- a/tests/web/views/test_orders.py +++ b/tests/web/views/test_orders.py @@ -1503,6 +1503,28 @@ class TestOrderView(WebTestCase): self.assertEqual(len(items), 1) self.assertEqual(items[0].product_scancode, "07430500132") + def test_get_row_parent(self): + model = self.app.model + enum = self.app.enum + view = self.make_view() + + user = model.User(username="barney") + self.session.add(user) + order = model.Order(order_id=42, created_by=user) + self.session.add(order) + self.session.flush() + item = model.OrderItem( + product_id="07430500132", + product_scancode="07430500132", + order_qty=1, + order_uom=enum.ORDER_UOM_UNIT, + status_code=enum.ORDER_ITEM_STATUS_INITIATED, + ) + order.items.append(item) + self.session.flush() + + self.assertIs(view.get_row_parent(item), order) + def test_configure_row_grid(self): model = self.app.model enum = self.app.enum From 572467d53e0d7ccd0cd70c096d2071d60491bd4c Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 15 Dec 2025 17:05:39 -0600 Subject: [PATCH 4/4] =?UTF-8?q?bump:=20version=200.7.1=20=E2=86=92=200.8.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 42 ++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46b7ed7..b6d31c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,45 @@ +## v0.8.0 (2025-12-15) + +### Feat + +- drop timezone, assume UTC for all datetime values in DB + +### Fix + +- define `get_row_parent()` for OrderView +- fix 'duplicate-code' for pylint +- consolidate some duplicated code +- bump minimum version for wuttaweb dependency +- fix 'abstract-method' for pylint +- fix 'arguments-differ' for pylint +- fix 'attribute-defined-outside-init' for pylint +- fix 'broad-exception-caught' for pylint +- fix 'consider-using-dict-comprehension' for pylint +- fix 'consider-using-f-string' for pylint +- fix 'consider-using-set-comprehension' for pylint +- fix 'empty-docstring' for pylint +- fix 'implicit-str-concat' for pylint +- fix 'inconsistent-return-statements' for pylint +- fix 'invalid-name' for pylint +- fix 'missing-class-docstring' and 'missing-function-docstring' for pylint +- fix 'no-else-return' for pylint +- fix 'no-member' for pylint +- fix 'no-self-argument' for pylint +- fix 'redefined-outer-name' for pylint +- fix 'singleton-comparison' for pylint +- fix 'too-few-public-methods' for pylint +- fix 'too-many-branches' for pylint +- fix 'too-many-lines' for pylint +- fix 'too-many-locals' for pylint +- fix 'too-many-arguments' for pylint +- fix 'too-many-public-methods' for pylint +- fix 'unnecessary-lambda-assignment' for pylint +- fix 'unused-argument' for pylint +- fix 'unused-import' for pylint +- fix 'unused-variable' for pylint +- fix 'wildcard-import' and 'unused-wildcard-import' for pylint +- format all code with black + ## v0.7.1 (2025-07-06) ### Fix diff --git a/pyproject.toml b/pyproject.toml index a5feec4..5160456 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "Sideshow" -version = "0.7.1" +version = "0.8.0" description = "Case/Special Order Tracker" readme = "README.md" authors = [