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:
@@ -13,6 +13,7 @@ from app.models import (
|
||||
CalendarWidgetConfig,
|
||||
Frame,
|
||||
FrameCalendar,
|
||||
TaskWidgetConfig,
|
||||
User,
|
||||
Widget,
|
||||
WhiteboardWidgetConfig,
|
||||
@@ -62,6 +63,16 @@ def _add_calendar_widget(db_session, frame: Frame) -> Widget:
|
||||
return widget
|
||||
|
||||
|
||||
def _add_tasks_widget(db_session, frame: Frame) -> Widget:
|
||||
widget = Widget(frame_id=frame.id, widget_type="tasks", x=0, y=0, w=2, h=2,
|
||||
sort_order=1, created_at=time.time())
|
||||
db_session.add(widget)
|
||||
db_session.flush()
|
||||
db_session.add(TaskWidgetConfig(widget_id=widget.id))
|
||||
db_session.commit()
|
||||
return widget
|
||||
|
||||
|
||||
# --- whiteboard-source ---
|
||||
|
||||
def test_whiteboard_source_owner_can_set_it(client, db_session):
|
||||
@@ -158,7 +169,7 @@ def test_whiteboard_source_400s_when_widget_is_not_a_whiteboard(client, db_sessi
|
||||
|
||||
def test_tasks_source_set_always_targets_the_caller(client, db_session):
|
||||
frame = _setup_two_linked_users(client, db_session)
|
||||
widget = _add_calendar_widget(db_session, frame)
|
||||
widget = _add_tasks_widget(db_session, frame)
|
||||
client.cookies.clear()
|
||||
login(client, "bob")
|
||||
|
||||
@@ -167,14 +178,14 @@ def test_tasks_source_set_always_targets_the_caller(client, db_session):
|
||||
assert resp.status_code == 200, resp.text
|
||||
|
||||
bob_row = db_session.query(User).filter_by(username="bob").one()
|
||||
cfg = db_session.get(CalendarWidgetConfig, widget.id)
|
||||
assert cfg.tasks_user_id == bob_row.id
|
||||
assert cfg.tasks_calendar_key == "caldav:/some/tasks/"
|
||||
cfg = db_session.get(TaskWidgetConfig, widget.id)
|
||||
assert cfg.user_id == bob_row.id
|
||||
assert cfg.calendar_key == "caldav:/some/tasks/"
|
||||
|
||||
|
||||
def test_tasks_source_anyone_linked_can_clear(client, db_session):
|
||||
frame = _setup_two_linked_users(client, db_session)
|
||||
widget = _add_calendar_widget(db_session, frame)
|
||||
widget = _add_tasks_widget(db_session, frame)
|
||||
client.post(f"/api/frames/1/widgets/{widget.id}/tasks-source",
|
||||
json={"calendar_key": "caldav:/alice/tasks/"}, headers=csrf_headers(client))
|
||||
|
||||
@@ -184,9 +195,9 @@ def test_tasks_source_anyone_linked_can_clear(client, db_session):
|
||||
headers=csrf_headers(client))
|
||||
assert resp.status_code == 200, resp.text
|
||||
|
||||
cfg = db_session.get(CalendarWidgetConfig, widget.id)
|
||||
assert cfg.tasks_user_id is None
|
||||
assert cfg.tasks_calendar_key is None
|
||||
cfg = db_session.get(TaskWidgetConfig, widget.id)
|
||||
assert cfg.user_id is None
|
||||
assert cfg.calendar_key is None
|
||||
|
||||
|
||||
def test_tasks_source_404s_for_a_widget_id_that_does_not_exist(client, db_session):
|
||||
@@ -196,7 +207,7 @@ def test_tasks_source_404s_for_a_widget_id_that_does_not_exist(client, db_sessio
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
def test_tasks_source_400s_when_widget_is_not_a_calendar(client, db_session):
|
||||
def test_tasks_source_400s_when_widget_is_not_tasks(client, db_session):
|
||||
frame = _setup_two_linked_users(client, db_session)
|
||||
photo_widget_id = _photo_widget_id(db_session, frame)
|
||||
resp = client.post(f"/api/frames/1/widgets/{photo_widget_id}/tasks-source",
|
||||
|
||||
Reference in New Issue
Block a user