
This post was written back in the days where Heroku provided free hosting; do not expect any example projects or links to Heroku to work.
Some tricky configuration is required, and the existing Python-specific documentation is sparse and buggy (or simply not written for a production setting). My eventual solution involves using a Google Cloud Platform service account and a custom Heroku buildpack
Little Cabin (my final project for my recently completed General Assembly bootcamp), provides tools for extended families to securely share their vacation property’s logistics and memories. One key feature is allowing the in-app scheduling to sync with a Google Calendar, and though I assumed it would be a simple matter of hooking up to the Google Calendar API, it was by far the most difficult problem to solve.

To get myself started, I’ve followed along with my two previous blog posts:
I have also created a blog example repo on my GitHub that includes all of the steps in this entire tutorial, so feel free to clone that repo and have a look at the code. You’ll still need to do a lot of person configuration to get it working, including creating your own service account, google calendar, Heroku project, .env files and Heroku config vars, etc.
We will start by creating a new module (just a separate .py file) to contain all of the Calendar API access methods. Python automatically exports, but you must explicitly import them into any code that needs access.
In your main_app/ folder, create a file called calendar_API.py. In that blank file:
touch main_app/calendar_API.pydef test_calendar():
print("RUNNING TEST_CALENDAR()")
test_event1 = {"start": {"date": "2022-01-01"}, "end": {"date": "2022-01-07"}, "summary":"test event 1"}
test_event2 = {"start": {"date": "2022-02-01"}, "end": {"date": "2022-02-07"}, "summary":"test event 2"}
events = [test_event1, test_event2]
return events
In VSCode, open main_app/urls.py and add the following the following item to the urlpatterns[] array below the existing “home” route. This will give us two pages in total, our “Home” and our “Demo” which will demonstrate the connection to Google Calendar API
path('demo/', views.demo, name='demo'),In your views.py, import the module method at the top of the file using:
from .calendar_API import test_calendarNow add a second method which will display the demo template (later on we’ll fill in the code that actually does some work)
def demo(request):
results = test_calendar()
context = {"results": results}
return render(request, 'demo.html', context)
Create the html template that will serve as the HTML for eventually displaying the fetched results from the API:
touch main_app/templates/demo.htmlAdd the following to to the blank demo.html file in VSCode:
<!DOCTYPE html>
<html>
<head> </head>
<body>
<h1>Demo</h1>
<ul>
{% for result in results %}
<li>{{result.start.date}}{% if result.end.date %}-{% endif%}{{result.end.date}}: {{result.summary}}</li>
{% endfor %}
</ul>
</body>
</html>
Now, edit your home.html to include a link to this new demo.html. Add the following line after the <h1>Home</h1> line:
<a href="{% url 'demo' %}">Connect to Google Calendar</a>In your terminal, run the following commands to install needed packages and save those as project dependencies:
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlibpip3 freeze > requirements.txtLet’s make sure the plumbing is working before we add more logical complexity!
In your terminal, start the local server (make sure you’re in your activated env):
python3 manage.py runserverIn your browser
These instructions draw heavily on this amazing Stack Overflow answer
example/.env.# Google API
CAL_ID={{{THE CAL ID YOU COPY-PASTED FROM YOUR GOOGLE CALENDAR SETTINGS PAGE}}}
touch google-credentials.json{
"type": "service_account",
"project_id": "example",
"private_key_id": "BLAHBALH",
"private_key": "-----BEGIN PRIVATE KEY-----\nSUUUUUUPER------LONG-------STRING\n-----END PRIVATE KEY-----\n",
"client_email": "USERNAME@EXAMPLE.iam.gserviceaccount.com",
"client_id": "1234567890987654321",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/username%40example.iam.gserviceaccount.com"
}
.gitignore.gitignore file# this hidden folder contains your local, virtual environment
.env
# this hidden file contains sensitive keys and environmental config vars
# if you've named your primary project folder something other than
# 'example' please adjust the following line as needed
example/.env
# this token contains your sensitive google service account info
google-credentials.json
Please ensure both example/.env and the newly created google-credentials.json appear greyed out in VSCode

calendar_API.py, and replace the existing code with everything in the following block (basically replacing our test logging function with the real API calls)from decouple import config
from google.oauth2 import service_account
import googleapiclient.discovery
import datetime
CAL_ID = config('CAL_ID')
SCOPES = ['https://www.googleapis.com/auth/calendar']
SERVICE_ACCOUNT_FILE = './google-credentials.json'
def test_calendar():
print("RUNNING TEST_CALENDAR()")
credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
service = googleapiclient.discovery.build('calendar', 'v3', credentials=credentials)
# CREATE A NEW EVENT
new_event = {
'summary': "Ben Hammond Tech's Super Awesome Event",
'location': 'Denver, CO USA',
'description': 'https://benhammond.tech',
'start': {
'date': f"{datetime.date.today()}",
'timeZone': 'America/New_York',
},
'end': {
'date': f"{datetime.date.today() + datetime.timedelta(days=3)}",
'timeZone': 'America/New_York',
},
}
service.events().insert(calendarId=CAL_ID, body=new_event).execute()
print('Event created')
# GET ALL EXISTING EVENTS
events_result = service.events().list(calendarId=CAL_ID, maxResults=2500).execute()
events = events_result.get('items', [])
# LOG THEM ALL OUT IN DEV TOOLS CONSOLE
for e in events:
print(e)
return events
At this point, running the local server (make sure you’re in your activated env):
python3 manage.py run server
and viewing localhost:8000 in browser should display the same button as before. However, now clicking the button should trigger the series of API calls we added above:If that’s all working locally, the next big step is adjusting everything so that it works in production on Heroku.
These steps come mainly from this excellent DevDojo.com blog post
https://github.com/buyersight/heroku-google-application-credentials-buildpack into the text input box labeled “Enter Buildpack URL”.env file, along with some addition Heroku only entries.CAL_ID, and in the corresponding VALUE box, paste the string that occurs after the = in your local .env. In my case it looks like this fF43grgewefwFWeww1231233r23rwq@group.calendar.google.com. Make sure there are no quotation marks or spaces before and after the stringGOOGLE_APPLICATION_CREDENTIALS, and in the corresponding VALUE box paste google-credentials.jsonGOOGLE_CREDENTIALS, and in the corresponding VALUE box, paste the entire JSON object. You can literally copy all of the code that is now in your local google-credentials.json, starting with a { and ending with a }
git add .git commit -m "getting ready for deploy"git push heroku main (or master if you haven’t yet switched your local git to default to the main branch)Once you’re notified the deploy was successful, you can use the terminal to manually explore your project’s Heroku server and confirm that the credentials file was created automagically by the build pack and those config vars.
heroku run bash (wait a few seconds for it to log in)lsgoogle-credentials.json listed on the floor of your project. Yay!exit will bring you back onto your local machine
Head to your deployed Heroku site, and see if the button still works to populate / print out the Google Calendar events. If so, great job! If not, scope the last paragraph here for some potential debugging tricks.
As noted in the previous article Deploying Django to Heroku, it can be helpful to configure your Django project and Heroku to give more detailed error logging. Check out the instructions on this stackoverflow where they explain adding
DEBUG_PROPAGATE_EXCEPTIONS = Trueand aLOGGING = { ... }library to theirsettings.py
Sundial Photo by Marian Kroell on Unsplash