How to build a scheduling application using Python and Flask

Do you want to create your own custom scheduling application? Here’s an extensive guide for you including Virtual Calendars

How to build a scheduling application using Python and Flask

Let’s imagine that we own a gym equipment rental business. People can reserve our gym equipment for an hour, and we need a scheduling system to handle these reservations. Using Nylas and the Nylas Python SDK, we can create a Python and Flask scheduling system where people can log in using their Google account, select a time slot, and receive a confirmation email with an event attached.

With the Nylas Calendar API, we can easily support creating new calendars and events. Here’s how:

Is your system ready?

If you already have the Nylas Python SDK installed and your Ruby environment configured, continue reading this blog.

Otherwise, I recommend reading the post How to Send Emails with the Nylas Python SDK to understand the setup basics.

What are we going to talk about?

What our application will look like

When we launch our webpage, we need a way for people to log into their Google accounts:

Sign up into the Python and Flask scheduling app

When pressing the button, they will be redirected to this login screen:

Log using Gmail or another provider

If you want to use a different provider, you will need to configure an app for each of them:

Choose your account

We choose the account we want to use and enter our password:

Application not verified

We must continue, as our application is still in the testing phase:

Give access and permissions for our python and flask scheduling application

We’ll need to access the calendar, so we need to click continue, although we can confirm that we’re just being asked for 4 services related to the calendar and nothing else.

Once logged in, we will be greeted and presented with the available spots for that particular day.

Our scheduling application running on Python and Flask

A new event will be scheduled once we click on one.

Selecting an available spot

We will receive a confirmation email and an event on our calendar:

Reservation made

Now, we see that the selected time slot is no longer available:

Available spots left

We can only reserve one spot per day, so trying to reserve another will result in an error message:

No more available spots

Creating a virtual calendar

While we can use our default calendar, it would be better to have one specially made for this project. And even better, we can use a virtual calendar. A virtual calendar allows us to assign a calendar to an object or a person without an existing calendar account.

Read how to do it in the blog post A Small Guide to Virtual Calendars

Updating our .env file

As we have created a virtual calendar, we need to add extra information to our .env file:

export CLIENT_ID=<CLIENT_ID>
export CLIENT_SECRET=<CLIENT_SECRET>
export ACCESS_TOKEN=<ACCESS_TOKEN>
export ACCOUNT_ID=<ACCOUNT_ID>
export CALENDAR_ID=<VIRTUAL_CALENDAR_ID>
export VIRTUAL_TOKEN=<VIRTUAL_ACCOUNT_ACCESS_TOKEN>

Creating a Nylas App and a Google Cloud Platform project

If you haven’t created a Nylas app yet, follow these steps:

Create a new app for our scheduling application
  • The application has been successfully created:
Dashboard overview
  • We need to press on Connect an email account:
Dashboard accounts
  • Enter your email address and press Connect:
Connect an account

You’re going to get this:

Error, but it's Ok

But no worries, it’s not your fault, or ours. It’s actually Google’s increased security. Simply, let’s move to the next step.

Creating a Google Project

To save you some time and to make this post shorter, we already have all the instructions you need to follow in this blog post How to Setup Your First Google API Project

Installing dependencies

As we want to create a Python web application, our best option is to use Flask, one of the most popular Micro Frameworks in the Python world. We might need to install some additional packages:

$ pip3 install flask
$ pip3 install Flask-Session
$ pip3 install python-dotenv

Once installed, we’re ready to go.

Creating the scheduling project

Now, we’re going to create our scheduling application with Python and Flask. We need to create a folder called GymEquipment with two folders called templates and static. Inside the GymEquipment folder, we’re going to create a file called GymSchedule.py with the following code:

# Load your env variables
from dotenv import load_dotenv

load_dotenv()

# Import your dependencies
from flask import Flask, render_template, request, redirect, url_for, session
from flask_session.__init__ import Session
from nylas import APIClient
import datetime
import os
from datetime import date

# Create the app
app = Flask(__name__)
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)

# Initialize your Nylas API client
nylas = APIClient(
    os.environ.get("CLIENT_ID"),
    os.environ.get("CLIENT_SECRET"),
    os.environ.get("ACCESS_TOKEN"),
)

# OAuth Nylas API client
api = APIClient(
    os.environ.get("CLIENT_ID"),
    os.environ.get("CLIENT_SECRET"),
)

# Virtual API Client
virtual = APIClient(
    os.environ.get("CLIENT_ID"),
    os.environ.get("CLIENT_SECRET"),
    os.environ.get("VIRTUAL_TOKEN")
)

# Call the authorization page
@app.route("/login/nylas/authorized", methods=["GET"])
def authorized():
    if session["email_address"] == None:
        code = api.token_for_code(request.args.get("code"))
        client = APIClient(
            os.environ.get("CLIENT_ID"), os.environ.get("CLIENT_SECRET"), code
        )
        account = client.account
        session["email_address"] = account.email_address
        session["participant"] = account.name
        session["access_token"] = code
        return redirect(url_for("login"))

# Remove auth access
@app.route("/remove", methods=["GET"])
def remove():
    api.revoke_token()
    session["email_address"] = None
    session["participant"] = None
    session["access_token"] = None
    return redirect("/")


# This the landing page
@app.route("/", methods=["GET"])
def index():
    session["email_address"] = None
    return render_template("welcome.html")


# Main page with form
@app.route("/login", methods=["GET"])
def login():
    if session["email_address"] == None:
        url = api.authentication_url(
            redirect_uri="http://localhost:5000/login/nylas/authorized",
            scopes=["calendar"],
            login_hint="devrel@nylas.com",
            state="mycustomstate",
        )
        return redirect(url)
    else:
        # Organizers emails
        email = ["nylas_gym_calendar"]

        duration = 60
        interval = 60
        schedules = []
        time_slots = []
        # Get today’s date
        today = date.today()
        # We rent equipment from 10am to 3pm
        start_time = int(
            datetime.datetime(today.year, today.month, today.day, 10, 0, 0).strftime("%s")
        )
        end_time = int(
            datetime.datetime(today.year, today.month, today.day, 15, 0, 0).strftime("%s")
        )
        # Determine availability of organizers
        virtual_events = virtual.events.where(calendar_id = os.environ.get('VIRTUAL_CALENDAR'), 
                                              starts_after = start_time, ends_before = end_time)
		
        # Find available times
        for event in virtual_events:
            time_slots.append({"object": "time_slot", "status": "busy",
                                         "start_time": event["when"]["start_time"],
                                         "end_time": event["when"]["end_time"]})

        # Determine free/busy
        free_busy = [{"object": "free_busy","email": "nylas_gym_calendar","time_slots": time_slots}]
 
		# Check for availability
        availability = virtual.availability(email, duration, interval, start_time, end_time, 
                                            free_busy = free_busy)
 
         # Find available times
        for available in availability["time_slots"]:
            ts = datetime.datetime.fromtimestamp(available["start_time"]).strftime(
                "%H:%M:%S"
            )
            te = datetime.datetime.fromtimestamp(available["end_time"]).strftime(
                "%H:%M:%S"
            )
            schedules.insert(len(schedules), [ts, te])

        return render_template(
            "main.html", schedules=schedules, participant=session["participant"]
        )


# Create the event
@app.route("/schedule'", methods=["GET"])
def schedule():
    link = ""
    registered = False

    # Check if participant has been already registered
    today = date.today()
    # We get the details from the chosen time and create the start and end times
    start_time = int(
        datetime.datetime(today.year, today.month, today.day, 9, 0, 0).strftime("%s")
    )
    end_time = int(
        datetime.datetime(today.year, today.month, today.day, 16, 0, 0).strftime("%s")
    )

    # Check if participant has been already registered for the day
    events = virtual.events.where(calendar_id=os.environ.get("VIRTUAL_CALENDAR"), starts_after = start_time, ends_before = end_time)
    for event in events:
        for participant in event.participants:
            if participant["email"] == session["email_address"]:
                registered = True

    if registered == False:
        # Get time parameters
        ts = request.args["start_date"]
        te = request.args["end_date"]
        link = ts + " to " + te
        today = date.today()
        # We get the details from the chosen time and create the start and end times
        start_time = int(
            datetime.datetime(
                today.year,
                today.month,
                today.day,
                int(ts[0:2]),
                int(ts[3:5]),
                int(ts[6:8]),
            ).strftime("%s")
        )
        end_time = int(
            datetime.datetime(
                today.year,
                today.month,
                today.day,
                int(te[0:2]),
                int(te[3:5]),
                int(te[6:8]),
            ).strftime("%s")
        )
        # We create the event details
        event = virtual.events.create()
        event.calendar_id = os.environ.get("VIRTUAL_CALENDAR")
        event.title = "Nylas' Gym Equipment Reservation"
        event.location = "Nylas' Gym"
        event.description = f"You have reserve Gym Equipment from {ts} to {te}"
        event.when = {"start_time": start_time, "end_time": end_time}
        event.participants = [
            {"name": session["participant"], "email": session["email_address"]}
        ]
        # Notify participants via email
        event.save()

        # Generate the Internet Calendar Scheduling
        ics = event.generate_ics()
        # Write the file
        f = open("invite.ics", "w")
        f.write(ics)
        f.close()
        # Open the file as an attachment
        attachment = open("invite.ics", "rb")
        attach = nylas.files.create()
        attach.filename = 'invite.ics'
        attach.stream = attachment        
        # Send email with attachment included
        draft = nylas.drafts.create()
        draft.subject = "Nylas\' Gym Equipment Reservation"
        draft.body = f"You have reserve Gym Equipment from {ts} to {te}"
        draft.to = [{"name": session["participant"], "email": session["email_address"]}]
        draft.attach(attach)
        # Send your email!
        draft.send()
        attachment.close()
        if os.path.exists("invite.ics"):
            os.remove("invite.ics")

    # Call page to confirm reservation
    return render_template("scheduled.html", link=link, registered=registered)


# Run our application
if __name__ == "__main__":
    app.run()

On the templates folder, create four files, we’re going to start with base.html:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<!-- Call the TailwindCSS library -->	
	<script src="https://cdn.tailwindcss.com"></script>
	<title>Nylas' Gym Equipment</title>
</head>
<body>
{% block content %}  
{% endblock %}	
</body>
</html>

Then the file welcome.html:

{% extends 'base.html' %}

{% block content %}
<div class="bg-[#315acb] border-green-600 border-b p-4 m-4 rounded grid place-items-center">
	<h1 class="text-4xl text-white">Welcome to Nylas' Gym Equipment</h1><br><br>
	<img src="static/Nyla_Space.png"><br><br><br>
	<a href="/login"><img src="static/Google.png" alt="Sign in"></a>
</div>
{% endblock %}

Now main.html:

{% extends 'base.html' %}

{% block content %}
<div class="bg-[#315acb] border-green-600 border-b p-4 m-4 rounded grid place-items-center">
	<h1 class="text-4xl text-white">Nylas' Gym Equipment</h1>
	<br>
	<p class="text-3xl text-center text-white">Welcome {{ participant }}</p><br>
	<p class="text-3xl text-center text-white">Choose a time that's convenient for you, so that we can reserve your gym equipment.</p><br><br>
{# We get the available times and display them as links #}
	{% for schedule in schedules %}
		{% set ts = schedule[0] %}
		{% set te = schedule[1] %}
		{% set link = ts + " to " + te %}
		<a href="{{url_for('schedule', start_date = ts, end_date = te)}}" class="text-blue-200"><b>{{link}}</b></a></br></br>
	{% endfor %}
	<a href="/remove" class="text-red-200"><b>Log Out</b></a>
</div>
{% endblock %}

And finally scheduled.html:

{% extends 'base.html' %}

{% block content %}
<div class="bg-[#315acb] border-green-600 border-b p-4 m-4 rounded grid place-items-center">
	{% if registered == False %}
		<h1 class="text-4xl text-white">You have schedule the usage of Nylas' Gym Equipment</h1><br>
		<p class="text-3xl text-center text-white">You will receive a confirmation for your  {{ link }} reservation.</p><br>
	{% else %}
		<h1 class="text-4xl text-white">You have already scheduled the usage of Nylas' Gym Equipment. Only one time per customer.</h1><br>
		<h1 class="text-4xl text-white">If you want to cancel or update your reservation, contact <b>Customer Care</b>.</h1><br>
	{% endif %}

	<p class="text-3xl text-center text-red">With love, Nylas.</p><br><br>
	<a href="/login" class="text-red-200"><b>Go back</b></a>
</div>
{% endblock %}

On the public folder, we need to images:

Sign with Google image
Google.png
Nyla Portrait
Nyla_Space.png

Installing ngrok

Ngrok is a globally distributed reverse proxy that allows our local applications to be exposed on the internet. We need to create a user and then install the client.

We can install it by using brew:

$ brew install ngrok/ngrok/ngrok

Then, follow the instructions on the webpage.

The first option is the installation:

Configuring ngrok

Once everything is ready, we can run it by typing the following on the terminal window:

$ ./ngrok http 5000
Running ngrok

We need to copy the Forwarding address, which will change every time we run ngrok.

Running the scheduling application

To run our application, we need to update our Nylas application. Head to the application settings page, choose Authentication and then type the following on the Add you callback input box:

http://localhost:5000/login/nylas/authorized

And also the address that you copied from ngrok:

https://xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx.ngrok.io
Configure Nylas and our python and flask scheduling app interaction

We just need to type the following on the terminal window:

$ python3 GymSchedule.py
Running our Flask Scheduling Application on Python

Our application will be running on port 5000 of localhost, so we just need to open our favourite browser and go to the following address:

http://localhost:5000

Everything should work as expected.

How to check for existing events?

Our application is done and working correctly, but, how do we check for existing events? As the virtual calendar doesn’t live on our calendar, this might be tricky, but luckily it is not.

If you have the Nylas CLI installed (if you don’t, here’s how Working with the Nylas CLI) then just run this:

$ nylas api events get --access_token <VIRTUAL_ACCESS_TOKEN>

Or you can run this script called ReadVirtual.py:

# Load your env variables
from dotenv import load_dotenv
load_dotenv()

# Import your dependencies
from nylas import APIClient
import os
import datetime
from datetime import date

# Initialize your Nylas API client
nylas = APIClient(
    os.environ.get("CLIENT_ID"),
    os.environ.get("CLIENT_SECRET"),
    os.environ.get("VIRTUAL_TOKEN")
)

virtual_events = nylas.events.where(calendar_id = os.environ.get('VIRTUAL_CALENDAR_ID'))
for event in virtual_events:
	print(event["title"])
	print(f"Start Time: {datetime.datetime.fromtimestamp(event['when']['start_time']).strftime('%H:%M:%S')}")
	print(f"End Time: {datetime.datetime.fromtimestamp(event['when']['end_time']).strftime('%H:%M:%S')}")
	#nylas.events.delete(event["id"])
	print("\n\n")

What’s next?

Now we have created an scheduling python and flask application, why don’t we explore and create more.

If you want to learn more about the Nylas Email API, please visit our documentation Nylas Calendar Overview.

You can sign up Nylas for free and start building!


You May Also Like

The Nylas mascot, Nyla, standing next to a scheduling UI
Introducing Nylas Scheduler v3: Experience the future of in-app scheduling
Transactional Email APIs vs Contextual Email APIs
Best email tracker
Find the best email tracker and elevate your app’s email game

Subscribe for our updates

Please enter your email address and receive the latest updates.