Split the tasks feature out of the calendar widget into its own widget type
Task lists used to be a week-view-only sub-feature bolted onto calendar widgets (CalendarWidgetConfig.tasks_*), so a task list could only exist tied to a calendar's view and only inside its footprint. Tasks are now a standalone widget type (TaskWidgetConfig, app/widgets/tasks.py) that can be placed and sized independently, same as photos/calendar/ whiteboard -- no separate "enabled" flag either, since being on the grid at all is the on/off switch, matching every other widget type. Migration 17 creates task_widget_configs, extracts any existing calendar widget's configured task source into a new sibling tasks widget (auto-placed in open grid space, source dropped+logged if truly none is left), then drops calendar_widget_configs' now-dead tasks_* columns in the same migration -- this project's usual same-migration- drop convention. Also handles the rarer case of a database jumping straight from before the widget system existed to after this split in one boot, via the legacy Frame.calendar_tasks_* columns. Verified live in the browser at desktop and mobile widths: adding a Tasks widget, its own dialog (task-list source picker + preview), and confirming the calendar widget's dialog no longer mentions tasks at all. Full test suite (180 tests, including new coverage for the widget render/actions, the migration's data-extraction path, and the permission-boundary shape for tasks-source) passes.
This commit is contained in:
@@ -36,10 +36,11 @@ Each module in this package exposes:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from . import calendar, photos, whiteboard
|
||||
from . import calendar, photos, tasks, whiteboard
|
||||
|
||||
WIDGET_TYPES = {
|
||||
"photos": photos,
|
||||
"calendar": calendar,
|
||||
"whiteboard": whiteboard,
|
||||
"tasks": tasks,
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ from ..db import widget_locked
|
||||
from ..models import CalendarWidgetConfig, Frame, Widget
|
||||
from ..routers.common import (
|
||||
get_or_refresh_calendar_events_for_widget,
|
||||
get_or_refresh_tasks_for_widget,
|
||||
get_or_refresh_weather_for_widget,
|
||||
)
|
||||
|
||||
@@ -47,16 +46,12 @@ def render(db: Session, frame: Frame, widget: Widget, target_w: int, target_h: i
|
||||
cfg = db.get(CalendarWidgetConfig, widget.id)
|
||||
events, fetch_summary = get_or_refresh_calendar_events_for_widget(db, frame, widget)
|
||||
weather_cities = get_or_refresh_weather_for_widget(db, frame, widget) if cfg.weather_enabled else None
|
||||
tasks = (
|
||||
get_or_refresh_tasks_for_widget(db, frame, widget)
|
||||
if (cfg.view == "week" and cfg.tasks_enabled) else None
|
||||
)
|
||||
|
||||
return _build(
|
||||
events, view=cfg.view, browse_offset=cfg.browse_offset, target_w=target_w, target_h=target_h,
|
||||
timezone=frame.timezone, fetch_summary=fetch_summary, week_start=cfg.week_start,
|
||||
palette_rgb=frame.palette_rgb, weather_cities=weather_cities, weather_units=cfg.weather_units,
|
||||
week_days=cfg.week_days, week_layout=cfg.week_layout, tasks=tasks,
|
||||
week_days=cfg.week_days, week_layout=cfg.week_layout,
|
||||
week_start_offset=cfg.week_start_offset,
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Tasks widget: a simple outstanding-task checklist in its own region --
|
||||
split out of the calendar widget's old week-view-only task list (see
|
||||
models.TaskWidgetConfig) so a task list can be placed and sized on its
|
||||
own, independent of any calendar's view/footprint.
|
||||
|
||||
No "enabled" concept and no button actions: the widget's mere presence
|
||||
on the grid is the on/off switch (same as every other widget type), and
|
||||
its cache refreshes on the same throttled schedule as weather -- nothing
|
||||
here to advance/back/force."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from PIL import Image
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..calendar_render import _build_tasks
|
||||
from ..models import Frame, TaskWidgetConfig, Widget
|
||||
from ..routers.common import get_or_refresh_tasks_for_widget
|
||||
from ._shared import placeholder_image
|
||||
|
||||
ACTION_LABELS: dict[str, str] = {}
|
||||
|
||||
|
||||
def render(db: Session, frame: Frame, widget: Widget, target_w: int, target_h: int,
|
||||
is_normal_wake: bool = True) -> Image.Image:
|
||||
"""is_normal_wake is unused here -- see app/widgets/photos.py's
|
||||
identical note; every widget type's render() shares one call
|
||||
signature regardless of which ones actually care."""
|
||||
cfg = db.get(TaskWidgetConfig, widget.id)
|
||||
if not cfg.calendar_key or not cfg.user_id:
|
||||
return placeholder_image(target_w, target_h, ["Tasks widget", "not configured yet"])
|
||||
tasks = get_or_refresh_tasks_for_widget(db, frame, widget)
|
||||
return _build_tasks(tasks, target_w, target_h)
|
||||
|
||||
|
||||
ACTIONS: dict = {}
|
||||
Reference in New Issue
Block a user