Let a tasks widget merge multiple task lists, checkbox+color like calendar
Tasks widgets could only ever point at one CalDAV task list (a radio- button picker, owner-only). Now they merge any number of included task lists across every linked user, same checkbox-inclusion + optional pinned-color shape a calendar widget already has for its calendars -- FrameTaskList mirrors FrameCalendar exactly, down to the same owner- adds/anyone-mutes permission split (api_widget_task_list_select/ api_widget_task_list_color). Reused calendar_render._event_colors/ _draw_color_bar as-is for the per-task color bar -- a task dict's owner_display_name/color_index is exactly that function's single- source fallback shape. Also added an opt-in "show tasks completed in the last 24 hours" toggle (TaskWidgetConfig.show_completed): caldav_client.fetch_tasks now accepts a completed_since cutoff and returns completed VTODOs (with their completion time) instead of silently dropping them, and _draw_tasks gives a completed task a filled checkbox + muted text instead of the normal empty-box/due-date row. Migration 18 splits the single-source TaskWidgetConfig columns (added by 17, splitting tasks out of the calendar widget in the first place) into frame_task_lists, carrying forward each widget's existing single source as its first included list -- same shape migration 9 used carrying forward frame_calendars' old single opt-in. Verified live in the browser (desktop + mobile): the new "Included task lists" + "Recently completed" dialog sections, the show_completed toggle actually persisting through a real HTTP round-trip, and no regression in the calendar widget's own "Included calendars" dialog. Full suite (192 tests, including new merge_tasks/config_save/migration coverage) passes.
This commit is contained in:
@@ -18,6 +18,7 @@ from app.models import (
|
||||
Frame,
|
||||
FrameButtonAction,
|
||||
FrameCalendar,
|
||||
FrameTaskList,
|
||||
PhotoWidgetConfig,
|
||||
ServerSettings,
|
||||
TaskWidgetConfig,
|
||||
@@ -180,7 +181,7 @@ def test_migration_16_raw_sql_path_applies_to_an_existing_pre_widget_database(db
|
||||
_ensure_frame_calendars_rekeyed has a real frame_id-shaped table to
|
||||
migrate."""
|
||||
with db_module.engine.begin() as conn:
|
||||
for table in ("frame_button_actions", "whiteboard_widget_configs", "task_widget_configs",
|
||||
for table in ("frame_button_actions", "whiteboard_widget_configs", "task_widget_configs", "frame_task_lists",
|
||||
"calendar_widget_configs", "photo_widget_configs", "widgets"):
|
||||
conn.execute(text(f"DROP TABLE {table}"))
|
||||
conn.execute(text("DROP TABLE frame_calendars"))
|
||||
@@ -234,16 +235,22 @@ def test_migration_16_raw_sql_path_applies_to_an_existing_pre_widget_database(db
|
||||
assert config.queue == ["legacy-asset", "next-asset"]
|
||||
|
||||
|
||||
def test_migration_17_extracts_tasks_into_a_standalone_widget(db_session):
|
||||
"""Exercises _migration_17's actual data-extraction SQL (the real
|
||||
"existing widget-system database upgrading past this migration"
|
||||
scenario): a calendar_widget_configs row in its pre-17 shape (tasks_*
|
||||
columns still present, still holding a configured task source) should
|
||||
come out the other side as a sibling `tasks` widget carrying that
|
||||
source, with calendar_widget_configs no longer having tasks_*
|
||||
columns at all."""
|
||||
def test_migration_17_and_18_extract_tasks_into_a_standalone_multi_list_widget(db_session):
|
||||
"""Exercises _migration_17 and _migration_18's actual data-extraction
|
||||
SQL back to back (the real "existing widget-system database
|
||||
upgrading past both migrations" scenario, since both apply in the
|
||||
same run_migrations() call here): a calendar_widget_configs row in
|
||||
its pre-17 shape (tasks_* columns still present, still holding a
|
||||
configured task source) should come out the other side as a sibling
|
||||
`tasks` widget with that source carried forward as its first
|
||||
FrameTaskList row (migration 17's extraction, then migration 18's
|
||||
further extraction of the single-source TaskWidgetConfig it produces
|
||||
into FrameTaskList), with calendar_widget_configs no longer having
|
||||
tasks_* columns and TaskWidgetConfig no longer having user_id/
|
||||
calendar_key columns either."""
|
||||
with db_module.engine.begin() as conn:
|
||||
conn.execute(text("DROP TABLE task_widget_configs"))
|
||||
conn.execute(text("DROP TABLE frame_task_lists"))
|
||||
conn.execute(text("DROP TABLE calendar_widget_configs"))
|
||||
conn.execute(text(
|
||||
"CREATE TABLE calendar_widget_configs ("
|
||||
@@ -301,6 +308,9 @@ def test_migration_17_extracts_tasks_into_a_standalone_widget(db_session):
|
||||
|
||||
columns = {c["name"] for c in inspect(db_module.engine).get_columns("calendar_widget_configs")}
|
||||
assert not any(c.startswith("tasks_") for c in columns)
|
||||
task_cfg_columns = {c["name"] for c in inspect(db_module.engine).get_columns("task_widget_configs")}
|
||||
assert "user_id" not in task_cfg_columns and "calendar_key" not in task_cfg_columns
|
||||
assert "show_completed" in task_cfg_columns
|
||||
|
||||
task_widgets = db_session.scalars(
|
||||
select(Widget).where(Widget.frame_id == 1, Widget.widget_type == "tasks")
|
||||
@@ -309,10 +319,16 @@ def test_migration_17_extracts_tasks_into_a_standalone_widget(db_session):
|
||||
task_widget = task_widgets[0]
|
||||
|
||||
cfg = db_session.get(TaskWidgetConfig, task_widget.id)
|
||||
assert cfg.user_id == user_id
|
||||
assert cfg.calendar_key == "caldav:/some/tasks/"
|
||||
assert cfg.checked_at == 123.0
|
||||
assert cfg.cached == [{"summary": "Buy milk"}]
|
||||
assert cfg.show_completed is False
|
||||
|
||||
task_list = db_session.scalars(
|
||||
select(FrameTaskList).where(FrameTaskList.widget_id == task_widget.id)
|
||||
).one()
|
||||
assert task_list.user_id == user_id
|
||||
assert task_list.calendar_key == "caldav:/some/tasks/"
|
||||
assert task_list.included is True
|
||||
|
||||
# Auto-placed without overlapping the calendar widget it was split from.
|
||||
cal = db_session.get(Widget, calendar_widget_id)
|
||||
@@ -330,7 +346,7 @@ def test_frame_calendars_rekey_attaches_existing_rows_to_their_calendar_widget(d
|
||||
the widget backfill, not as a numbered migration racing ahead of
|
||||
it (see _ensure_frame_calendars_rekeyed's own docstring)."""
|
||||
with db_module.engine.begin() as conn:
|
||||
for table in ("frame_button_actions", "whiteboard_widget_configs", "task_widget_configs",
|
||||
for table in ("frame_button_actions", "whiteboard_widget_configs", "task_widget_configs", "frame_task_lists",
|
||||
"calendar_widget_configs", "photo_widget_configs", "widgets"):
|
||||
conn.execute(text(f"DROP TABLE {table}"))
|
||||
conn.execute(text("DROP TABLE frame_calendars"))
|
||||
|
||||
Reference in New Issue
Block a user