Skip to content

feat: add bulk modification support for calendar groups#26

Merged
hugobessa merged 1 commit intofeat/calendar-groups-and-slotsfrom
feat/calendar-groups-and-slots-pt6
Apr 27, 2026
Merged

feat: add bulk modification support for calendar groups#26
hugobessa merged 1 commit intofeat/calendar-groups-and-slotsfrom
feat/calendar-groups-and-slots-pt6

Conversation

@hugobessa
Copy link
Copy Markdown
Contributor

PR 6 — CalendarGroup follow-ups: permission delegation, bulk-modification parity, batched find_bookable_slots

Summary

Closes out three of the four follow-ups queued for PR 6 in dev-plans/2026-04-20-CALENDAR_GROUP_PLAN.md. Each item is small and independent enough to ship alongside the others without stepping on anything that came before. Option A (bundle-based persistence for non-primary selections) remains deferred — the plan recommends holding on it unless Option B proves insufficient in production.

What's in

  1. CalendarPermissionService.can_manage_calendar_group + CalendarGroupPermission delegates to it.
  2. *_with_bulk_modifications parity for Calendar and CalendarGroup querysets, plus the corresponding flag on CalendarGroupService.check_group_availability.
  3. Batched find_bookable_slots — replaces the per-candidate DB round-trip with a precomputed in-memory scan.

What's deferred

  • Option A (bundle-based persistence) — left as a separate ticket. The plan's own recommendation: start with Option B, migrate only if bundles' unified RSVP/sync semantics turn out to be worth the refactor.
  • Org-admin override on can_manage_calendar_group — blocked on a prerequisite (no org-admin role exists on OrganizationMembership today). Docstring flags where the override would slot in.

Details

1. can_manage_calendar_group

New method on CalendarPermissionService:

def can_manage_calendar_group(self, user: User, group: CalendarGroup) -> bool:
    """True iff `user` owns at least one calendar inside the group's slot
    pools (scoped to the group's organization)."""

CalendarGroupPermission now injects the service via DI and delegates has_object_permission to it, so the manage-group rule has a single implementation. A safe fallback path preserves the inline ownership query if DI ever fails to wire, so tests and scripts that instantiate the permission directly still work.

2. *_with_bulk_modifications parity

CalendarQuerySet and CalendarGroupQuerySet each gained a _with_bulk_modifications variant sharing the existing implementation via a with_bulk_modifications: bool helper. The bulk-aware path swaps annotate_recurring_occurrences_on_date_range for annotate_recurring_occurrences_with_bulk_modifications_on_date_range on events, so continuation occurrences from bulk-modified recurring series count against availability.

Exposed via:

  • Calendar.objects.only_calendars_available_in_ranges_with_bulk_modifications(ranges)
  • CalendarGroup.objects.only_groups_bookable_in_ranges_with_bulk_modifications(ranges)
  • CalendarGroupService.check_group_availability(..., with_bulk_modifications=True)

3. Batched find_bookable_slots

CalendarGroupService.find_bookable_slots was a Python loop that issued one availability query per candidate window. A 6-hour search at a 15-minute step made 24 round-trips for a read-only operation.

The new implementation:

  • Fetches slot pools and required_count once.
  • Splits pool calendars into managed / unmanaged.
  • Fetches AvailableTime spans (managed) and CalendarEvent + BlockedTime spans (unmanaged) once for the whole search window, with recurring-occurrence expansion.
  • Iterates candidate windows in Python against the in-memory spans.

Also takes with_bulk_modifications: bool = False for parity with check_group_availability.

Query-count delta for a 6-hour window at 15-min step (24 candidates):

Before After
~24 DB round-trips ≤8 (measured: 6)

A regression guard pins the bound with django_assert_max_num_queries(8).


Files touched

Test plan

  • only_groups_bookable_in_ranges_with_bulk_modifications smoke test (no bulk mods → same result as non-bulk variant).
  • check_group_availability(with_bulk_modifications=True) returns the same per-slot availability when no bulk modifications exist.
  • Batched find_bookable_slots matches prior behavior in the all-available happy path.
  • Batched find_bookable_slots excludes candidate windows overlapping an unmanaged calendar's blocking event.
  • find_bookable_slots returns [] when any slot pool is empty.
  • Query-count regression: find_bookable_slots over 24 candidates issues <=8 queries.
  • can_manage_calendar_group: true for an owner of any pool calendar, false for strangers, scoped to the group's organization.
  • CalendarGroupPermission delegates to CalendarPermissionService.can_manage_calendar_group when DI is wired, and falls back cleanly when it isn't.
  • Full calendar_integration/tests/ + public_api/tests/ suites pass — 905 tests (up from 894).

Rollout notes

  • No migrations.
  • No API changes to public surfaces: the new with_bulk_modifications flag defaults to False, so existing call-sites are unaffected.
  • CalendarGroupPermission callers that construct the class directly (outside DI) keep working via the documented fallback path.

Follow-ups

  • Option A (bundle-based persistence for non-primary selections) — open in a dedicated ticket once Option B shows friction in production.
  • Org-admin role on OrganizationMembership — once introduced, extend can_manage_calendar_group with an admin override (single call-site change).

@hugobessa hugobessa force-pushed the feat/calendar-groups-and-slots-pt5 branch from 5b80386 to a580f72 Compare April 21, 2026 17:03
@hugobessa hugobessa force-pushed the feat/calendar-groups-and-slots-pt6 branch from ad1d220 to 6ad6d52 Compare April 21, 2026 17:03
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 21, 2026

Codecov Report

❌ Patch coverage is 88.42105% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.61%. Comparing base (a580f72) to head (6ad6d52).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...dar_integration/services/calendar_group_service.py 86.36% 4 Missing and 5 partials ⚠️
calendar_integration/managers.py 50.00% 2 Missing ⚠️
Additional details and impacted files
@@                          Coverage Diff                           @@
##           feat/calendar-groups-and-slots-pt5      #26      +/-   ##
======================================================================
+ Coverage                               81.54%   81.61%   +0.06%     
======================================================================
  Files                                     157      157              
  Lines                                    9994    10085      +91     
  Branches                                 1080     1101      +21     
======================================================================
+ Hits                                     8150     8231      +81     
- Misses                                   1406     1411       +5     
- Partials                                  438      443       +5     
Flag Coverage Δ
unittests 81.61% <88.42%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@hugobessa hugobessa changed the base branch from feat/calendar-groups-and-slots-pt5 to feat/calendar-groups-and-slots April 27, 2026 04:21
@hugobessa hugobessa merged commit d1695eb into feat/calendar-groups-and-slots Apr 27, 2026
1 check passed
@hugobessa hugobessa deleted the feat/calendar-groups-and-slots-pt6 branch April 27, 2026 04:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants