Week view flexibility: configurable day count, layout, and a CalDAV task list
Build and push server image / build-and-push (push) Successful in 52s

- Day count (2-10, was fixed at 7) -- 5 days trims the weekend clutter
  without losing the grid format.
- Layout choice: days side by side (original behavior) or stacked
  vertically as full agenda-style sections (reuses _draw_agenda_day,
  same approach _build_today_tomorrow already used for a fixed 2 days).
- Optional task list (CalDAV VTODO collections only -- a plain ICS
  subscription doesn't meaningfully have one) that takes the space of
  one day slot instead of adding an extra one. Same owner-controls-
  their-own-data permission split as calendar sources: only the
  calendar's owner can point a frame's task list at it, but anyone
  linked to the frame can clear it.

Browse-offset paging now moves by N days (was hardcoded to weeks),
identical to the old behavior when days=7. Changing the day count
resets the browse offset, same reasoning as changing views already did.
This commit is contained in:
2026-07-23 07:58:13 -04:00
parent 01b9e9f1d0
commit ce8525bee8
10 changed files with 436 additions and 25 deletions
+24
View File
@@ -96,6 +96,24 @@ def _calendar_users_for_frame(db: Session, frame_id: int, viewer_id: int | None)
return result
def _tasks_source_info(db: Session, frame: Frame) -> dict | None:
"""Whose CalDAV calendar this frame's week-view task list currently
pulls from, and its label -- for showing "using <name>'s Chores
list" to everyone linked, not just whoever set it. None if no
source is configured."""
if not frame.calendar_tasks_user_id or not frame.calendar_tasks_calendar_key:
return None
user = db.get(User, frame.calendar_tasks_user_id)
if user is None:
return None
label = frame.calendar_tasks_calendar_key
for c in (user.calendar_caldav_calendars or []):
if f"caldav:{c['href']}" == frame.calendar_tasks_calendar_key:
label = c.get("display_name") or label
break
return {"user_id": user.id, "display_name": user.display_name or user.username, "label": label}
@router.get("/frames/{frame_id}/config", response_class=HTMLResponse)
def frame_config_page(frame_id: int, request: Request, db: Session = Depends(get_db)):
return _frame_page(
@@ -115,6 +133,10 @@ WEEK_START_LABELS = {0: "Monday", 1: "Tuesday", 2: "Wednesday", 3: "Thursday",
@router.get("/frames/{frame_id}/calendar", response_class=HTMLResponse)
def frame_calendar_page(frame_id: int, request: Request, db: Session = Depends(get_db)):
viewer = current_user(request, db)
frame = db.get(Frame, frame_id)
viewer_task_calendars = []
if viewer is not None and frame is not None and can_view_frame(db, viewer, frame):
viewer_task_calendars = [c for c in _user_available_calendars(viewer) if c["key"].startswith("caldav:")]
return _frame_page(
request, db, frame_id, "frame_calendar.html", "calendar",
calendar_views=CALENDAR_VIEW_LABELS,
@@ -123,6 +145,8 @@ def frame_calendar_page(frame_id: int, request: Request, db: Session = Depends(g
calendar_color_labels=PALETTE_LABELS,
default_palette_rgb=DEFAULT_PALETTE_RGB,
palette_to_hex=palette_to_hex,
viewer_task_calendars=viewer_task_calendars,
tasks_source=_tasks_source_info(db, frame) if frame is not None else None,
)