Google Calendar Integration with Django
Question:
Is there a fully fledged Django-based example of a Google Calendar integration? I was reading through Google’s example page but their link at the bottom is outdated.
I’m specifically struggeling with the refresh token as Google’s examples focus solely on how to get the access token. That’s what I have so far:
@staff_member_required
def authorize_access(request):
return redirect(get_flow(request).step1_get_authorize_url())
@staff_member_required
def oauth2_callback(request):
credentials = get_flow(request).step2_exchange(request.GET['code'])
store = get_store()
store.put(credentials)
credentials.set_store(store)
return redirect('...')
def get_flow(request):
flow = client.flow_from_clientsecrets(os.path.join(CREDENTIAL_DIR, CLIENT_SECRET_FILE),
SCOPES,
redirect_uri='%s://%s/google-calendar/oauth2-callback/' % (request.META['wsgi.url_scheme'], request.META['HTTP_HOST'],))
flow.params['access_type'] = 'offline'
flow.params['approval_prompt'] = 'force'
return flow
def get_store():
return oauth2client.file.Storage(os.path.join(CREDENTIAL_DIR, ACCESS_TOKEN_FILE))
def has_valid_api_credentials():
credentials = get_store().get()
return credentials is not None
def build_service():
credentials = get_store().get()
if not credentials:
return None
elif credentials.access_token_expired:
http = credentials.refresh(httplib2.Http())
http = get_store().get().authorize(http)
else:
http = credentials.authorize(httplib2.Http())
service = discovery.build('calendar', 'v3', http=http)
return service
def create_events(rental_request):
service = build_service()
event = service.events().insert(...).execute()
Answers:
Researching a lot of different approaches I found out that server-to-server authentication is what I wanted. This way no user has to explicitly give permissions and acquired auth-tokens don’t have to be renewed. Instead, using a service account, a server can make calls itself.
Before you can start coding, you have to setup such a service account and add it to your calendar that you want the service account to access. Google has written down the three steps to create an account here. Afterwards, go to https://calendar.google.com, locate on the left side of the screen the calendar you want to share with your new service account and click the triangle next to it. From the drop-down menu choose calendar settings. This takes you to a screen where you’ll find the calendar-ID which you’ll need later (so write it down) and also displays a tab at the top to share access to the calendar. As “person” insert the email address from the service account, give it the respective permissions and click save (if you don’t click save the service account won’t be added).
The code for this is actually pretty elegant:
import os
from datetime import timedelta
import datetime
import pytz
import httplib2
from googleapiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
service_account_email = '[email protected]'
CLIENT_SECRET_FILE = 'creds.p12'
SCOPES = 'https://www.googleapis.com/auth/calendar'
scopes = [SCOPES]
def build_service():
credentials = ServiceAccountCredentials.from_p12_keyfile(
service_account_email=service_account_email,
filename=CLIENT_SECRET_FILE,
scopes=SCOPES
)
http = credentials.authorize(httplib2.Http())
service = build('calendar', 'v3', http=http)
return service
def create_event():
service = build_service()
start_datetime = datetime.datetime.now(tz=pytz.utc)
event = service.events().insert(calendarId='<YOUR EMAIL HERE>@gmail.com', body={
'summary': 'Foo',
'description': 'Bar',
'start': {'dateTime': start_datetime.isoformat()},
'end': {'dateTime': (start_datetime + timedelta(minutes=15)).isoformat()},
}).execute()
print(event)
I’m using oauth2client version 2.2.0 (pip install oauth2client
).
I hope this answer helps 🙂
As this post was quite a while ago I wanted to share my 2020 version of it. Thanks for this post. Helped me a lot to achieve my goal.
import datetime
from datetime import timedelta
import pytz
from googleapiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
service_account_email = "INSERT_HERE"
SCOPES = ["https://www.googleapis.com/auth/calendar"]
credentials = ServiceAccountCredentials.from_json_keyfile_name(
filename="FILENAME.json", scopes=SCOPES
)
def build_service():
service = build("calendar", "v3", credentials=credentials)
return service
def create_event():
service = build_service()
start_datetime = datetime.datetime.now(tz=pytz.utc)
event = (
service.events()
.insert(
calendarId="[email protected]",
body={
"summary": "Foo",
"description": "Bar",
"start": {"dateTime": start_datetime.isoformat()},
"end": {
"dateTime": (start_datetime + timedelta(minutes=15)).isoformat()
},
},
)
.execute()
)
print(event)
create_event()
Just a note here: Although the code works, as per https://github.com/googleapis/google-auth-library-python/blob/7a8641a7f0718c0dce413436f23691e8590face1/docs/index.rst, oauth2client has been deprecated recently in favour of google-auth library – https://github.com/googleapis/google-auth-library-python/tree/edfe24602051969e32917e82bcedd2bace43e260
You can find the documentation of the new library here – https://google-auth.readthedocs.io/en/latest/user-guide.html
To use the new library, the code can be written as
import datetime
from datetime import timedelta
import pytz
from google.oauth2 import service_account
from googleapiclient.discovery import build
service_account_email = "[email protected]"
SCOPES = ["https://www.googleapis.com/auth/calendar"]
credentials = service_account.Credentials.from_service_account_file('google_calendar_credential.json')
scoped_credentials = credentials.with_scopes(SCOPES)
def build_service():
service = build("calendar", "v3", credentials=scoped_credentials)
return service
def create_event():
service = build_service()
start_datetime = datetime.datetime.now(tz=pytz.utc)
event = (
service.events()
.insert(
calendarId="primary",
body={
"summary": "Foo 2",
"description": "Bar",
"start": {"dateTime": start_datetime.isoformat()},
"end": {
"dateTime": (start_datetime + timedelta(minutes=15)).isoformat()
},
},
)
.execute()
)
print(event)
create_event()
As I do not have enough reputation to post this as comment, I am posting this as a separate post
2022
credits to @joey Coder(i would have added this as comment but its too long)
If you want your website or app to make events or calendars without have to use the Google accounts of the users you should use service accounts.
In https://console.cloud.google.com/ choose your project or start new one.
In the navigation menu choose "APIs & Services"
enable new APIs and then look up "calendar API", enable the API
Under "APIs & Services">"Credentials", select "Create Credentials" and click on "service account", fill in the desired name, and continue. Set role as owner(or other desired)(owner gives full access you you might want to switch to something less powerful). Click "Done"
This will redirect you to the credentials page.
Under the "Service accounts" click on the desired account(this will redirect you to the IAM & Admin panel)
Under the tab "Keys" click "ADD KEY" and select json, this will download a json file to your computer.
in the Calendar page in google
get and add the calendar ID to the admin panel under AgendaClients "CalendarId"
add the service account to the people shared as admin (make changes to events)
This is how it looks like in my django project:
signals.py
import datetime
import json
import os
from django.db.models.signals import post_delete, post_save
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from oauth2client.service_account import ServiceAccountCredentials
from .models import Event
# If modifying these scopes, delete the file token.json.
SCOPES = ["https://www.googleapis.com/auth/calendar"]
def get_service(refresh=False):
credentials = ServiceAccountCredentials.from_json_keyfile_dict(
json.loads(os.environ.get("client_secret")), scopes=SCOPES
)
# or if you have a file
# credentials = ServiceAccountCredentials.from_json_keyfile_name(
# filename="file.json", scopes=SCOPES
# )
service = build("calendar", "v3", credentials=credentials)
return service
def handle_event(sender, created, instance, **kwargs):
"""this function creates the events in the google agenda and updates them if changed in the website"""
service = get_service()
event = instance
if not event.end_date:
event.end_date = event.start_date
if not event.end_time and event.start_time:
event.end_time = event.start_time
elif not event.end_time:
event.end_time = datetime.datetime.min.time()
if not event.start_time:
event.start_time = datetime.datetime.min.time()
if event.end_date < event.start_date:
event.end_date, event.start_date = event.start_date, event.end_date
queryset = Event.objects.filter(
id=event.id
) # https://stackoverflow.com/questions/1555060/how-to-save-a-model-without-sending-a-signal
# this is used so that we can update the google event within this signal without reshooting this signal(signals shot every time an object is saved)
event = {
"summary": event.description,
"location": event.location or "",
"description": (event.description + " " + event.summary),
"start": {
"dateTime": datetime.datetime.combine(
event.start_date, event.start_time
).isoformat(),
"timeZone": "Europe/Amsterdam",
},
"end": {
"dateTime": datetime.datetime.combine(
event.end_date, event.end_time
).isoformat(),
"timeZone": "Europe/Amsterdam",
},
"recurrence": [],
"reminders": {},
}
if created or not instance.google_link:
try:
event = (
service.events()
.insert(
calendarId=os.environ.get("calendarId"),
body=event,
)
.execute()
)
queryset.update(google_link=event["id"])
except HttpError as error:
# print("An error occurred: %s" % error)
pass
else:
try:
event = (
service.events()
.update(
calendarId=os.environ.get("calendarId"),
body=event,
eventId=instance.google_link,
)
.execute()
)
queryset.update(google_link=event["id"])
except HttpError as error:
# print("An error occurred: %s" % error)
pass
# print("#############ADDED NEW #############")
def delete_event(sender, instance, **kwargs):
"""this function deletes an event from google agenda when deleted in the website"""
try:
service = get_service()
service.events().delete(
calendarId=os.environ.get("CalendarId"),
eventId=instance.google_link,
).execute()
except:
pass
post_save.connect(handle_event, sender=Event)
post_delete.connect(delete_event, sender=Event)
models.py
class Event(models.Model):
summary = models.CharField(max_length=50)
description = models.CharField(max_length=50, null=True, blank=True)
start_date = models.DateField()
start_time = models.TimeField(null=True, blank=True)
end_date = models.DateField(null=True, blank=True)
end_time = models.TimeField(null=True, blank=True)
location = models.CharField(max_length=50, null=True, blank=True)
google_link = models.CharField(max_length=150, null=True, blank=True)
# google link is used to edit events in google if you change them in your website
Feel free to ask any questions or point out anything
can you give me github link
and i am getting Service account info was not in the expected format, missing fields client_email, token_uri error
Is there a fully fledged Django-based example of a Google Calendar integration? I was reading through Google’s example page but their link at the bottom is outdated.
I’m specifically struggeling with the refresh token as Google’s examples focus solely on how to get the access token. That’s what I have so far:
@staff_member_required
def authorize_access(request):
return redirect(get_flow(request).step1_get_authorize_url())
@staff_member_required
def oauth2_callback(request):
credentials = get_flow(request).step2_exchange(request.GET['code'])
store = get_store()
store.put(credentials)
credentials.set_store(store)
return redirect('...')
def get_flow(request):
flow = client.flow_from_clientsecrets(os.path.join(CREDENTIAL_DIR, CLIENT_SECRET_FILE),
SCOPES,
redirect_uri='%s://%s/google-calendar/oauth2-callback/' % (request.META['wsgi.url_scheme'], request.META['HTTP_HOST'],))
flow.params['access_type'] = 'offline'
flow.params['approval_prompt'] = 'force'
return flow
def get_store():
return oauth2client.file.Storage(os.path.join(CREDENTIAL_DIR, ACCESS_TOKEN_FILE))
def has_valid_api_credentials():
credentials = get_store().get()
return credentials is not None
def build_service():
credentials = get_store().get()
if not credentials:
return None
elif credentials.access_token_expired:
http = credentials.refresh(httplib2.Http())
http = get_store().get().authorize(http)
else:
http = credentials.authorize(httplib2.Http())
service = discovery.build('calendar', 'v3', http=http)
return service
def create_events(rental_request):
service = build_service()
event = service.events().insert(...).execute()
Researching a lot of different approaches I found out that server-to-server authentication is what I wanted. This way no user has to explicitly give permissions and acquired auth-tokens don’t have to be renewed. Instead, using a service account, a server can make calls itself.
Before you can start coding, you have to setup such a service account and add it to your calendar that you want the service account to access. Google has written down the three steps to create an account here. Afterwards, go to https://calendar.google.com, locate on the left side of the screen the calendar you want to share with your new service account and click the triangle next to it. From the drop-down menu choose calendar settings. This takes you to a screen where you’ll find the calendar-ID which you’ll need later (so write it down) and also displays a tab at the top to share access to the calendar. As “person” insert the email address from the service account, give it the respective permissions and click save (if you don’t click save the service account won’t be added).
The code for this is actually pretty elegant:
import os
from datetime import timedelta
import datetime
import pytz
import httplib2
from googleapiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
service_account_email = '[email protected]'
CLIENT_SECRET_FILE = 'creds.p12'
SCOPES = 'https://www.googleapis.com/auth/calendar'
scopes = [SCOPES]
def build_service():
credentials = ServiceAccountCredentials.from_p12_keyfile(
service_account_email=service_account_email,
filename=CLIENT_SECRET_FILE,
scopes=SCOPES
)
http = credentials.authorize(httplib2.Http())
service = build('calendar', 'v3', http=http)
return service
def create_event():
service = build_service()
start_datetime = datetime.datetime.now(tz=pytz.utc)
event = service.events().insert(calendarId='<YOUR EMAIL HERE>@gmail.com', body={
'summary': 'Foo',
'description': 'Bar',
'start': {'dateTime': start_datetime.isoformat()},
'end': {'dateTime': (start_datetime + timedelta(minutes=15)).isoformat()},
}).execute()
print(event)
I’m using oauth2client version 2.2.0 (pip install oauth2client
).
I hope this answer helps 🙂
As this post was quite a while ago I wanted to share my 2020 version of it. Thanks for this post. Helped me a lot to achieve my goal.
import datetime
from datetime import timedelta
import pytz
from googleapiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
service_account_email = "INSERT_HERE"
SCOPES = ["https://www.googleapis.com/auth/calendar"]
credentials = ServiceAccountCredentials.from_json_keyfile_name(
filename="FILENAME.json", scopes=SCOPES
)
def build_service():
service = build("calendar", "v3", credentials=credentials)
return service
def create_event():
service = build_service()
start_datetime = datetime.datetime.now(tz=pytz.utc)
event = (
service.events()
.insert(
calendarId="[email protected]",
body={
"summary": "Foo",
"description": "Bar",
"start": {"dateTime": start_datetime.isoformat()},
"end": {
"dateTime": (start_datetime + timedelta(minutes=15)).isoformat()
},
},
)
.execute()
)
print(event)
create_event()
Just a note here: Although the code works, as per https://github.com/googleapis/google-auth-library-python/blob/7a8641a7f0718c0dce413436f23691e8590face1/docs/index.rst, oauth2client has been deprecated recently in favour of google-auth library – https://github.com/googleapis/google-auth-library-python/tree/edfe24602051969e32917e82bcedd2bace43e260
You can find the documentation of the new library here – https://google-auth.readthedocs.io/en/latest/user-guide.html
To use the new library, the code can be written as
import datetime
from datetime import timedelta
import pytz
from google.oauth2 import service_account
from googleapiclient.discovery import build
service_account_email = "[email protected]"
SCOPES = ["https://www.googleapis.com/auth/calendar"]
credentials = service_account.Credentials.from_service_account_file('google_calendar_credential.json')
scoped_credentials = credentials.with_scopes(SCOPES)
def build_service():
service = build("calendar", "v3", credentials=scoped_credentials)
return service
def create_event():
service = build_service()
start_datetime = datetime.datetime.now(tz=pytz.utc)
event = (
service.events()
.insert(
calendarId="primary",
body={
"summary": "Foo 2",
"description": "Bar",
"start": {"dateTime": start_datetime.isoformat()},
"end": {
"dateTime": (start_datetime + timedelta(minutes=15)).isoformat()
},
},
)
.execute()
)
print(event)
create_event()
As I do not have enough reputation to post this as comment, I am posting this as a separate post
2022
credits to @joey Coder(i would have added this as comment but its too long)
If you want your website or app to make events or calendars without have to use the Google accounts of the users you should use service accounts.
In https://console.cloud.google.com/ choose your project or start new one.
In the navigation menu choose "APIs & Services"
enable new APIs and then look up "calendar API", enable the API
Under "APIs & Services">"Credentials", select "Create Credentials" and click on "service account", fill in the desired name, and continue. Set role as owner(or other desired)(owner gives full access you you might want to switch to something less powerful). Click "Done"
This will redirect you to the credentials page.
Under the "Service accounts" click on the desired account(this will redirect you to the IAM & Admin panel)
Under the tab "Keys" click "ADD KEY" and select json, this will download a json file to your computer.
in the Calendar page in google
get and add the calendar ID to the admin panel under AgendaClients "CalendarId"
add the service account to the people shared as admin (make changes to events)
This is how it looks like in my django project:
signals.py
import datetime
import json
import os
from django.db.models.signals import post_delete, post_save
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from oauth2client.service_account import ServiceAccountCredentials
from .models import Event
# If modifying these scopes, delete the file token.json.
SCOPES = ["https://www.googleapis.com/auth/calendar"]
def get_service(refresh=False):
credentials = ServiceAccountCredentials.from_json_keyfile_dict(
json.loads(os.environ.get("client_secret")), scopes=SCOPES
)
# or if you have a file
# credentials = ServiceAccountCredentials.from_json_keyfile_name(
# filename="file.json", scopes=SCOPES
# )
service = build("calendar", "v3", credentials=credentials)
return service
def handle_event(sender, created, instance, **kwargs):
"""this function creates the events in the google agenda and updates them if changed in the website"""
service = get_service()
event = instance
if not event.end_date:
event.end_date = event.start_date
if not event.end_time and event.start_time:
event.end_time = event.start_time
elif not event.end_time:
event.end_time = datetime.datetime.min.time()
if not event.start_time:
event.start_time = datetime.datetime.min.time()
if event.end_date < event.start_date:
event.end_date, event.start_date = event.start_date, event.end_date
queryset = Event.objects.filter(
id=event.id
) # https://stackoverflow.com/questions/1555060/how-to-save-a-model-without-sending-a-signal
# this is used so that we can update the google event within this signal without reshooting this signal(signals shot every time an object is saved)
event = {
"summary": event.description,
"location": event.location or "",
"description": (event.description + " " + event.summary),
"start": {
"dateTime": datetime.datetime.combine(
event.start_date, event.start_time
).isoformat(),
"timeZone": "Europe/Amsterdam",
},
"end": {
"dateTime": datetime.datetime.combine(
event.end_date, event.end_time
).isoformat(),
"timeZone": "Europe/Amsterdam",
},
"recurrence": [],
"reminders": {},
}
if created or not instance.google_link:
try:
event = (
service.events()
.insert(
calendarId=os.environ.get("calendarId"),
body=event,
)
.execute()
)
queryset.update(google_link=event["id"])
except HttpError as error:
# print("An error occurred: %s" % error)
pass
else:
try:
event = (
service.events()
.update(
calendarId=os.environ.get("calendarId"),
body=event,
eventId=instance.google_link,
)
.execute()
)
queryset.update(google_link=event["id"])
except HttpError as error:
# print("An error occurred: %s" % error)
pass
# print("#############ADDED NEW #############")
def delete_event(sender, instance, **kwargs):
"""this function deletes an event from google agenda when deleted in the website"""
try:
service = get_service()
service.events().delete(
calendarId=os.environ.get("CalendarId"),
eventId=instance.google_link,
).execute()
except:
pass
post_save.connect(handle_event, sender=Event)
post_delete.connect(delete_event, sender=Event)
models.py
class Event(models.Model):
summary = models.CharField(max_length=50)
description = models.CharField(max_length=50, null=True, blank=True)
start_date = models.DateField()
start_time = models.TimeField(null=True, blank=True)
end_date = models.DateField(null=True, blank=True)
end_time = models.TimeField(null=True, blank=True)
location = models.CharField(max_length=50, null=True, blank=True)
google_link = models.CharField(max_length=150, null=True, blank=True)
# google link is used to edit events in google if you change them in your website
Feel free to ask any questions or point out anything
can you give me github link
and i am getting Service account info was not in the expected format, missing fields client_email, token_uri error