Skip to content

Scheduler

oldmangan edited this page Oct 8, 2026 · 1 revision

A calendar of appointments: day, week, work week, month and agenda views, where people create, move and resize items by mouse, touch or keyboard. The app (its BFF) stays the source of truth: changes apply at once, the app saves them, and puts an item back if the server says no.

Added in #67. The design and decisions are in Spec: Scheduler.

import datetime as dt
from wapyt import ScheduleItem, SchedulerConfig

sched = self.add_scheduler("main", SchedulerConfig(view="week", items=[
    ScheduleItem("standup", "Stand-up", dt.datetime(2026, 10, 8, 9, 30), dt.datetime(2026, 10, 8, 9, 45), color="aqua"),
    ScheduleItem("offsite", "Offsite", dt.date(2026, 10, 12), dt.date(2026, 10, 14), all_day=True, color="green"),
]))

sched.on_range_change(lambda r: sched.set_items(load_items(r["start"], r["end"])))
sched.on_create(lambda p: open_new_item_form(p["start"], p["end"], p["all_day"]))
sched.on_open(lambda p: open_item_form(p["item"]))

def moved(p):
    if not save_item_times(p["id"], p["start"], p["end"]):
        sched.revert(p["id"])                       # back where it was
        sched.set_item_error(p["id"], "That room is booked")
sched.on_move(moved)
sched.on_resize(moved)

Views

View Shows
day One day as a column of time slots, with an all-day row on top
week Seven days (from week_start, Monday by default)
work_week Monday to Friday
month Six weeks of days; a busy day shows "+3 more", which lists the day
agenda The next agenda_days (14) as a list grouped by day

The toolbar has Today, previous and next, the range as a title, and the view switch. A mini month beside the views jumps to any date; it hides when the scheduler is narrower than 640px. Clicking a day number opens that day.

Items

ScheduleItem(id, title, start, end, all_day=False, color=None, editable=True, description=None, series_id=None, data=None).

  • Times are naive local times: plain datetimes with no time zone (or ISO strings), shown as given. Convert to the viewer's local time before handing items over. All-day items use dates, and their end is the day after the last day (a one-day item on the 12th ends on the 13th).
  • color: blue, orange, aqua, yellow, magenta, green, violet, red (each with a light and a dark version), or a hex colour.
  • Overlapping items sit side by side. An item that runs past midnight continues on the next day.
  • editable=False locks an item: no moving, resizing or deleting. SchedulerConfig(read_only=True) locks everything.
  • Recurring items arrive already expanded, one item per occurrence; series_id groups them. The scheduler doesn't compute recurrence rules.

Creating, moving and resizing

Action Mouse / touch Keyboard Event
Create Drag across empty slots, or double-click one (a day, in month or the all-day row: an all-day item) Arrow to a slot, Enter on_create {start, end, all_day}
Move Drag the item to another time or day Arrows, then Enter on_move {id, start, end, all_day, old, item}
Resize Drag its bottom edge Shift+↑/↓, then Enter on_resize (same payload)
Open Click Enter on_open {id, item}
Delete (the app's own button or menu) Delete on_delete {id, item}
  • Times snap to slot_minutes (30), and a label shows the times while dragging. Escape cancels a drag; dragging near an edge scrolls.
  • Creating only reports the chosen slot. Add the item with add_item once it exists (after the person fills in a form, say).
  • Moves and resizes apply straight away and report the new and old times. If the server refuses, revert(id) puts the item back, and set_item_error(id, message) marks it (cleared by its next good change).
  • Delete only asks: call remove_item(id) once it's gone.
  • With the keyboard, arrows show the item at its new place (dashed) until Enter confirms or Escape cancels. The time grid and the month grid are one Tab stop each; items are in the Tab order. Moves are read out to screen readers.

Loading and changing items

on_range_change fires with {start, end, view} whenever the visible range changes, so the app can load just those items (get_range() for the first load). set_items, add_item, update_item(id, title=..., start=..., ...), remove_item, get_items and get_item keep the view current without reloading it.

iCal and printing

  • export_ics(filename="schedule.ics") downloads the loaded items as an iCalendar file any calendar app imports. Times are written as local ("floating") times, like the scheduler shows them. wapyt.scheduler.to_ics(items) builds the same text in Python, for a server-side export.
  • print() prints just the scheduler: the current view without its buttons, the whole day (not only the scrolled part), in black on white.

Options

SchedulerConfig
view, views The starting view, and which views the switch offers
date The date to show (today by default)
week_start 0 Sunday to 6 Saturday (1, Monday)
day_start, day_end, scroll_to Hours shown (0–24) and the hour it opens at (8)
slot_minutes, slot_height Grid step (5, 10, 15, 20, 30 or 60) and its height in pixels (22)
time_format, locale "12" or "24"; a language tag for day and month names
now_indicator A line at the current time (on)
min_item_minutes Shortest an item can be resized to (one slot)
mini_calendar, agenda_days, height, title The mini month (on), the agenda's length (14), a fixed height, the accessible name

Not yet

A resource timeline (rows per person or room), drag-to-create across several days in month, and time zones per item. See the spec.

Clone this wiki locally