Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0

### Added

* Documentation has been through a major overhaul.
* Added some information on how to connect to Google in the doc and examples.
* `event.component` is now an alias for `event.icalendar_component`.
* `get_davclient` (earlier called `auto_conn`) is more complete now - https://github.com/python-caldav/caldav/pull/502 - https://github.com/python-caldav/caldav/issues/485 - https://github.com/python-caldav/caldav/pull/507
* It can read from environment (including environment variable for reading from test config and for locating the config file).
Expand Down
2 changes: 1 addition & 1 deletion docs/source/about.rst
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ CalDAV URLs can be quite confusing, some software requires the URL to the calend
namespace.

* Google - new api: see https://developers.google.com/calendar/caldav/v2/guide.
Please comment in https://github.com/python-caldav/caldav/issues/119 if you manage to connect to Google calendar.
There is some information in https://github.com/python-caldav/caldav/issues/119 on how to connect to Google, and there are two contributed `examples <examples.rst>`_ on how to obtain a bearer token and use it in the caldav lbirary.

* DAViCal: The caldav URL typically seems to be on the format ``https://your.server.example.com/caldav.php/``, though it depends on how the web server is configured. The primary calendars have URLs like ``https://your.server.example.com/caldav.php/donald/calendar`` and other calendars have names like ``https://your.server.example.com/caldav.php/donald/golfing_calendar``.

Expand Down
17 changes: 17 additions & 0 deletions docs/source/examples.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

==========
Examples
==========

There is an example directory in the source of the project, some of the examples is written by the maintainer, others are written by other contributors - with or without a bit of brush-up from the maintainer to make things a bit more in line with "the best current practices". I'm striving to make the examples redundant, the documentation should contain all you need to know - but it may be worth having a look into actual code if you're stuck.

View the examples on `github <https://github.com/python-caldav/caldav/tree/master/examples>`_

Files currently there:

* ``basic_usage_examples.py`` - written by the maintainer - contains all you need to know to do basic calendar interactions. Code is regularly tested towards the Radicale server in the unit tests.
* ``get_events_example.py`` - written by Krylov Alexandr, with lots of comments from the maintainer. Code is regularly tested towards the Radicale server in the unit tests.
* ``scheduling_examples.py`` - This is NOT tested, it may or may not work! (I should look into this soon)
* ``sync_examples.py`` - this is "pseudo-code", not intended to work, and hence not tested. I'm also planning on making a HOWTO on how to backup your calendar locally.
* ``google-flask.py`` some flask application reading content from a Google calendar. Contributed by @seidnerj, not tested by the caldav maintainer.
* ``google-django`` - some python code utilizing django allauth library to authenticate towards a Google calendar. Contributed by Abe Hanoka.
59 changes: 59 additions & 0 deletions examples/google-django.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
Contributed by Abe Hanoka in https://github.com/python-caldav/caldav/issues/119#issuecomment-2652650972

> I got this working using django-allauth. Here's a minimal working example that demonstrates how to connect to Google Calendar via CalDAV using OAuth tokens from django-allauth"

> Make sure your Google OAuth configuration includes the CalDAV scope: https://www.googleapis.com/auth/calendar

> This approach lets you leverage django-allauth's token management while using caldav for actual calendar operations. The calendar URL format is https://apidata.googleusercontent.com/caldav/v2/{calendar_id}/events.

> For the allauth setup, I followed this guide: https://stackoverflow.com/questions/51575127/use-google-api-with-a-token-django-allauth

This code is not tested by the caldav library maintainer.
"""
from allauth.socialaccount.models import SocialApp
from allauth.socialaccount.models import SocialToken
from google.oauth2.credentials import Credentials

from caldav import DAVClient
from caldav.requests import HTTPBearerAuth


def get_google_credentials(user):
"""Get Google OAuth2 credentials from django-allauth"""
token = SocialToken.objects.filter(
account__user=user,
account__provider="google",
).first()

if not token:
raise Exception("No Google account connected")

google = SocialApp.objects.get(provider="google")
return Credentials(
token=token.token,
refresh_token=token.token_secret,
token_uri="https://oauth2.googleapis.com/token",
client_id=google.client_id,
client_secret=google.secret,
)


def sync_calendar(user, calendar_id):
"""Sync with Google Calendar using CalDAV"""
# Get credentials from django-allauth
credentials = get_google_credentials(user)

# Set up CalDAV client with OAuth token
client = DAVClient(
url=f"https://apidata.googleusercontent.com/caldav/v2/{calendar_id}/events",
auth=HTTPBearerAuth(credentials.token),
)

# Access calendar
principal = client.principal()
calendar = principal.calendars()[0]

# Now you can work with events
events = calendar.events()
# ...etc
116 changes: 116 additions & 0 deletions examples/google-flask.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""
Code contributed by github user seidnerj in
https://github.com/python-caldav/caldav/issues/119#issuecomment-2561980368

This code has not been tested by the maintainer of the caldav library.
"""
import os

from flask import Flask
from flask import jsonify
from flask import Response
from google.oauth2.credentials import Credentials

from caldav import DAVClient
from caldav.requests import HTTPBearerAuth


app = Flask(__name__)

# Constants
CREDENTIALS_FILE = "credentials.json"
TOKEN_FILE = "token.json"
CALDAV_URL_TEMPLATE = (
"https://apidata.googleusercontent.com/caldav/v2/{calendar_id}/user"
)

# Cache to store calendar summaries and IDs
CALENDAR_CACHE = {}


def get_google_credentials():
"""
Load or refresh Google OAuth2 credentials.
"""

if os.path.exists(TOKEN_FILE):
creds = Credentials.from_authorized_user_file(TOKEN_FILE)
else:
from google_auth_oauthlib.flow import InstalledAppFlow

flow = InstalledAppFlow.from_client_secrets_file(
CREDENTIALS_FILE, scopes=["https://www.googleapis.com/auth/calendar"]
)
creds = flow.run_local_server(port=0)

# save credentials for future use
with open(TOKEN_FILE, "w") as token:
token.write(creds.to_json())

return creds


def get_calendar_list():
"""
Fetch the list of calendars and store them in the cache.
"""

creds = get_google_credentials()
from googleapiclient.discovery import build

service = build("calendar", "v3", credentials=creds)

calendars = service.calendarList().list().execute()
for calendar in calendars.get("items", []):
CALENDAR_CACHE[calendar["summary"]] = calendar["id"]


@app.route("/calendars", methods=["GET"])
def list_calendars():
"""
Endpoint to list all available calendars.
"""
if not CALENDAR_CACHE:
get_calendar_list()

return jsonify({"calendars": list(CALENDAR_CACHE.keys())})


@app.route("/calendar/<calendar_name>.ics", methods=["GET"])
def serve_calendar_ics(calendar_name):
"""
Endpoint to serve .ics data for a specific calendar.
"""
if calendar_name not in CALENDAR_CACHE:
return jsonify({"error": "Calendar not found"}), 404

calendar_id = CALENDAR_CACHE[calendar_name]
creds = get_google_credentials()
access_token = creds.token

try:
calendar_url = CALDAV_URL_TEMPLATE.format(calendar_id=calendar_id)

# connect to the calendar using CalDAV
client = DAVClient(url=calendar_url, auth=HTTPBearerAuth(access_token))
principal = client.principal()
calendars = principal.calendars()

# fetch events from the first calendar (usually the only one)
calendar = calendars[0]
ics_data = ""
for event in calendar.events():
ics_data += event.data

# serve the calendar as an ICS file
return Response(ics_data, mimetype="text/calendar")
except Exception as ex:
return jsonify({"error": str(ex)}), 500


# requirements: flask, caldav, google-auth, google-auth-oauthlib, google-api-python-client
if __name__ == "__main__":
# preload calendar list on server start
get_calendar_list()
app.run(host="0.0.0.0", port=5000)