Skip to content

RFC: Calendar backend abstraction to support multiple protocols (CalDAV, EWS, Graph API, Google Workspace, others) #43

@Moustique82

Description

@Moustique82

Context
Many organizations already have calendar infrastructure in place — Exchange on-premises, Exchange Online / Microsoft 365, Google Workspace — that does not speak CalDAV natively. These organizations often struggle with room and resource booking: fragmented tooling, no unified interface, complex integrations.

Calendars has the potential to become the go-to solution for meeting and resource scheduling — but only if it can connect to the calendar backends organizations already run, rather than requiring a full migration to CalDAV.

Observation
The codebase already applies a clean backend abstraction pattern for entitlements (EntitlementsBackend with swappable LocalBackend and DeployCenterBackend). The calendar protocol layer does not yet have an equivalent abstraction.

CalDAV/SabreDAV is currently the only backend, wired in four specific places:

  • CalDAVHTTPClient / CalDAVClient in caldav_service.py
  • CalDAVProxyView in viewsets_caldav.py
  • provision_default_calendar / delete_user_caldav_data signals in signals.py
  • ResourceService in resource_service.py

Everything else — Organization, User, Channel, entitlements, OIDC, calendar_invitation_service — is already fully protocol-agnostic and would be inherited for free by any alternative backend.

Proposal
Introduce a CalendarBackend abstract base class following the same pattern as EntitlementsBackend:
class CalendarBackend(ABC):

@abstractmethod
def create_calendar(self, user, name, **kwargs) -> str: ...

@abstractmethod
def delete_calendar(self, user, calendar_path) -> None: ...

@abstractmethod
def create_event(self, user, calendar_path, event_data) -> str: ...

@abstractmethod
def get_events(self, user, calendar_path, start, end) -> list: ...

@abstractmethod
def find_event_by_uid(self, user, uid) -> tuple: ...

@abstractmethod
def update_attendee_partstat(self, data, email, partstat) -> str: ...

@abstractmethod
def get_free_busy(self, user, emails, start, end) -> dict: ...

@abstractmethod
def provision_resource(self, user, name, resource_type) -> dict: ...

@abstractmethod
def delete_resource(self, user, resource_id) -> None: ...

...

CalDAVBackend would be the default implementation — zero behavior change for existing deployments.

Concrete examples
Exchange on-premises exposes EWS (Exchange Web Services), not CalDAV. A native EWSBackend would handle room booking, free/busy, recurring events and resource provisioning directly against Exchange — without the double-invitation and sync issues a CalDAV bridge introduces.

Exchange Online / Microsoft 365 exposes the Graph API. A GraphAPIBackend would allow organizations on M365 to use Calendars as their room booking interface without migrating their calendar infrastructure.

The same abstraction would also enable a MockBackend for testing without a live calendar server — a practical benefit regardless of which backends are eventually implemented.

Relationship to "active sync with external calendars"
I noticed this item in your roadmap. Active sync is a valid approach for read access and calendar visibility, but it has limitations for room booking specifically: conflict detection requires real-time availability, and bidirectional sync introduces double-invitation issues and reconciliation complexity.

A native backend abstraction would complement sync — sync for personal calendar visibility, native backend for resource booking where real-time accuracy matters.

Questions

  1. Is protocol-agnosticism in scope for Calendars, or is CalDAV a deliberate permanent constraint?
  2. Are you open to this direction? Happy to discuss the approach before going further.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions