Skip to content

Commit 76925ec

Browse files
authored
Examples on how to connect to Google - with references from the doc
Fixes #119 Piggy-backed in a fixup for pull request #507 - I had forgotten to add the documentation file examples.rst #510
1 parent fb5da3e commit 76925ec

5 files changed

Lines changed: 195 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0
1616

1717
### Added
1818

19+
* Documentation has been through a major overhaul.
20+
* Added some information on how to connect to Google in the doc and examples.
1921
* `event.component` is now an alias for `event.icalendar_component`.
2022
* `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
2123
* It can read from environment (including environment variable for reading from test config and for locating the config file).

docs/source/about.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ CalDAV URLs can be quite confusing, some software requires the URL to the calend
229229
namespace.
230230

231231
* Google - new api: see https://developers.google.com/calendar/caldav/v2/guide.
232-
Please comment in https://github.com/python-caldav/caldav/issues/119 if you manage to connect to Google calendar.
232+
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.
233233

234234
* 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``.
235235

docs/source/examples.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
==========
3+
Examples
4+
==========
5+
6+
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.
7+
8+
View the examples on `github <https://github.com/python-caldav/caldav/tree/master/examples>`_
9+
10+
Files currently there:
11+
12+
* ``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.
13+
* ``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.
14+
* ``scheduling_examples.py`` - This is NOT tested, it may or may not work! (I should look into this soon)
15+
* ``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.
16+
* ``google-flask.py`` some flask application reading content from a Google calendar. Contributed by @seidnerj, not tested by the caldav maintainer.
17+
* ``google-django`` - some python code utilizing django allauth library to authenticate towards a Google calendar. Contributed by Abe Hanoka.

examples/google-django.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""
2+
Contributed by Abe Hanoka in https://github.com/python-caldav/caldav/issues/119#issuecomment-2652650972
3+
4+
> 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"
5+
6+
> Make sure your Google OAuth configuration includes the CalDAV scope: https://www.googleapis.com/auth/calendar
7+
8+
> 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.
9+
10+
> For the allauth setup, I followed this guide: https://stackoverflow.com/questions/51575127/use-google-api-with-a-token-django-allauth
11+
12+
This code is not tested by the caldav library maintainer.
13+
"""
14+
from allauth.socialaccount.models import SocialApp
15+
from allauth.socialaccount.models import SocialToken
16+
from google.oauth2.credentials import Credentials
17+
18+
from caldav import DAVClient
19+
from caldav.requests import HTTPBearerAuth
20+
21+
22+
def get_google_credentials(user):
23+
"""Get Google OAuth2 credentials from django-allauth"""
24+
token = SocialToken.objects.filter(
25+
account__user=user,
26+
account__provider="google",
27+
).first()
28+
29+
if not token:
30+
raise Exception("No Google account connected")
31+
32+
google = SocialApp.objects.get(provider="google")
33+
return Credentials(
34+
token=token.token,
35+
refresh_token=token.token_secret,
36+
token_uri="https://oauth2.googleapis.com/token",
37+
client_id=google.client_id,
38+
client_secret=google.secret,
39+
)
40+
41+
42+
def sync_calendar(user, calendar_id):
43+
"""Sync with Google Calendar using CalDAV"""
44+
# Get credentials from django-allauth
45+
credentials = get_google_credentials(user)
46+
47+
# Set up CalDAV client with OAuth token
48+
client = DAVClient(
49+
url=f"https://apidata.googleusercontent.com/caldav/v2/{calendar_id}/events",
50+
auth=HTTPBearerAuth(credentials.token),
51+
)
52+
53+
# Access calendar
54+
principal = client.principal()
55+
calendar = principal.calendars()[0]
56+
57+
# Now you can work with events
58+
events = calendar.events()
59+
# ...etc

examples/google-flask.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Code contributed by github user seidnerj in
4+
https://github.com/python-caldav/caldav/issues/119#issuecomment-2561980368
5+
6+
This code has not been tested by the maintainer of the caldav library.
7+
"""
8+
import os
9+
10+
from flask import Flask
11+
from flask import jsonify
12+
from flask import Response
13+
from google.oauth2.credentials import Credentials
14+
15+
from caldav import DAVClient
16+
from caldav.requests import HTTPBearerAuth
17+
18+
19+
app = Flask(__name__)
20+
21+
# Constants
22+
CREDENTIALS_FILE = "credentials.json"
23+
TOKEN_FILE = "token.json"
24+
CALDAV_URL_TEMPLATE = (
25+
"https://apidata.googleusercontent.com/caldav/v2/{calendar_id}/user"
26+
)
27+
28+
# Cache to store calendar summaries and IDs
29+
CALENDAR_CACHE = {}
30+
31+
32+
def get_google_credentials():
33+
"""
34+
Load or refresh Google OAuth2 credentials.
35+
"""
36+
37+
if os.path.exists(TOKEN_FILE):
38+
creds = Credentials.from_authorized_user_file(TOKEN_FILE)
39+
else:
40+
from google_auth_oauthlib.flow import InstalledAppFlow
41+
42+
flow = InstalledAppFlow.from_client_secrets_file(
43+
CREDENTIALS_FILE, scopes=["https://www.googleapis.com/auth/calendar"]
44+
)
45+
creds = flow.run_local_server(port=0)
46+
47+
# save credentials for future use
48+
with open(TOKEN_FILE, "w") as token:
49+
token.write(creds.to_json())
50+
51+
return creds
52+
53+
54+
def get_calendar_list():
55+
"""
56+
Fetch the list of calendars and store them in the cache.
57+
"""
58+
59+
creds = get_google_credentials()
60+
from googleapiclient.discovery import build
61+
62+
service = build("calendar", "v3", credentials=creds)
63+
64+
calendars = service.calendarList().list().execute()
65+
for calendar in calendars.get("items", []):
66+
CALENDAR_CACHE[calendar["summary"]] = calendar["id"]
67+
68+
69+
@app.route("/calendars", methods=["GET"])
70+
def list_calendars():
71+
"""
72+
Endpoint to list all available calendars.
73+
"""
74+
if not CALENDAR_CACHE:
75+
get_calendar_list()
76+
77+
return jsonify({"calendars": list(CALENDAR_CACHE.keys())})
78+
79+
80+
@app.route("/calendar/<calendar_name>.ics", methods=["GET"])
81+
def serve_calendar_ics(calendar_name):
82+
"""
83+
Endpoint to serve .ics data for a specific calendar.
84+
"""
85+
if calendar_name not in CALENDAR_CACHE:
86+
return jsonify({"error": "Calendar not found"}), 404
87+
88+
calendar_id = CALENDAR_CACHE[calendar_name]
89+
creds = get_google_credentials()
90+
access_token = creds.token
91+
92+
try:
93+
calendar_url = CALDAV_URL_TEMPLATE.format(calendar_id=calendar_id)
94+
95+
# connect to the calendar using CalDAV
96+
client = DAVClient(url=calendar_url, auth=HTTPBearerAuth(access_token))
97+
principal = client.principal()
98+
calendars = principal.calendars()
99+
100+
# fetch events from the first calendar (usually the only one)
101+
calendar = calendars[0]
102+
ics_data = ""
103+
for event in calendar.events():
104+
ics_data += event.data
105+
106+
# serve the calendar as an ICS file
107+
return Response(ics_data, mimetype="text/calendar")
108+
except Exception as ex:
109+
return jsonify({"error": str(ex)}), 500
110+
111+
112+
# requirements: flask, caldav, google-auth, google-auth-oauthlib, google-api-python-client
113+
if __name__ == "__main__":
114+
# preload calendar list on server start
115+
get_calendar_list()
116+
app.run(host="0.0.0.0", port=5000)

0 commit comments

Comments
 (0)