Source: calendar_integration/models.py —
Calendar(withcalendar_type=BUNDLE),ChildrenCalendarRelationship,CalendarEvent.bundle_calendar,CalendarEvent.bundle_primary_event,BlockedTime.bundle_calendar. Service:CalendarService.create_bundle_calendarand_create_bundle_eventin calendar_integration/services/calendar_service.py.
A bundle calendar is a Calendar with calendar_type=BUNDLE that
acts as a single bookable façade for a fixed set of underlying
calendars (its child calendars). When something is booked on the
bundle, the service creates the canonical event on a designated
primary child and propagates a representation (event or
BlockedTime) onto every other child.
Use a bundle when:
If any of those don't hold (you want to pick a physician from a pool, or the booking should be free of one canonical primary), prefer a CalendarGroup instead.
┌─────────────────────────────────────┐
BUNDLE Calendar │ ChildrenCalendarRelationship rows │
───────────────────────── │ bundle_calendar=<bundle> │
"Cardiology Procedure Suite" ──────▶ │ child_calendar=<Dr. Lee> │
│ is_primary=True │
├─────────────────────────────────────┤
│ child_calendar=<Cath Lab 1> │
│ is_primary=False │
├─────────────────────────────────────┤
│ child_calendar=<Cardiac Tech> │
│ is_primary=False │
└─────────────────────────────────────┘
ChildrenCalendarRelationship is the through table; exactly one row per
bundle has is_primary=True.
CalendarService._create_bundle_event walks roughly this path:
is_primary=True).
This is the calendar that owns the canonical event in its external
provider.BlockedTime).CalendarService.create_event
so all the normal side-effects run (provider sync, notifications,
permissions).INTERNAL, create a full CalendarEvent
representation linked back via bundle_primary_event.BlockedTime with
bundle_primary_event=<primary> and
bundle_calendar=<bundle> so the child shows as busy without
duplicating event details (and without polluting the upstream
provider calendar).A cardiac catheterisation requires the cardiologist, the cath lab, and a cardiac tech together every time. They never substitute.
"Cardiology Procedure Suite".Dr. Lee (personal, Google), Cath Lab 1 (resource,
internal), Cardiac Tech (personal, Microsoft).Dr. Lee (so the procedure shows up natively on her Google
calendar, with the lab + tech as invitees / co-busy).Booking a procedure on the bundle:
CalendarEvent on Dr. Lee's calendar (synced to Google,
attendees include the tech).BlockedTime on Cath Lab 1 (internal, but represented as
"busy" rather than as a full event the lab "owns").BlockedTime on the tech's calendar via Microsoft (since
Microsoft and Google are different providers, the cross-provider
invite isn't guaranteed and we want a local source of truth).A specific surgical configuration always requires:
Same shape: a bundle with four children, primary = the surgeon. Every booking blocks all four.
A weekly recurring group therapy session uses Therapist Maya + Group Room B together. The bundle sits behind a public scheduling URL that patients use to enrol — but enrolment never reassigns to a different therapist or room, so a fixed bundle is the right model.
Bundle events support recurrence: passing recurrence_rule to the
booking flow creates a recurring primary event and recurring
representations/blocked times on the children. Cancelling a single
occurrence (e.g. one week's group therapy because the therapist is
sick) flows through the standard recurrence-exception machinery on
the primary; the children's representations follow the primary.
Bundles answer "always book these N calendars together." They don't
answer "pick any one of these physicians and any one of these rooms."
That's the case CalendarGroup handles — see
calendar-groups.md. For new flows where the
caller picks calendars at booking time, prefer groups; bundles are the
right call when the membership is fixed and you want a single primary
calendar that owns the provider sync.