Simple Caldendar With Events In Python Flask (Free Download)

Welcome to a tutorial on how to create a simple calendar with events in Python Flask. So you want to create a calendar in Python Flask, only to find complicated ones all over the Internet? Well, here’s a quick step-by-step sharing of my own simple version – Read on!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

QUICK NOTES

  • Create a project folder, e.g. D:\cal, unzip the code inside this folder.
  • Navigate to the project folder in the command line cd D:\cal, create a virtual environment to not mess up your other projects.
    • virtualenv venv
    • Windows – venv\scripts\activate
    • Mac/Linux – venv/bin/activate
  • Get all the packages – pip install flask
  • Change mon : true in static/S4B_calendar.js if you want the week to start on Monday.
  • Run python S3_server.py and access http://localhost.
If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

SCREENSHOT

 

EXAMPLE CODE DOWNLOAD

Click here to download all the example source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

 

PYTHON FLASK CALENDAR

All right, let us now get into the details of how to create a simple calendar in Python Flask and SQLite.

 

STEP 1) THE DATABASE

1A) EVENTS TABLE

S1A_events.sql
CREATE TABLE events (
  id INTEGER,
  start DATETIME,
  end DATETIME,
  text TEXT NOT NULL,
  color TEXT NOT NULL,
  bg TEXT NOT NULL,
  PRIMARY KEY("id" AUTOINCREMENT)
);
CREATE INDEX idx_start ON events (start);
CREATE INDEX idx_end ON events (end);

First, we are going to need a database table to store the calendar event. Here is a simple one.

  • id Event ID, primary key.
  • start end The start and end dates of the event.
  • text Details of the event.
  • color bg Text and background color.

 

1B) CREATE THE DATABASE

S1B_create.sql
# (A) LOAD PACKAGES
import sqlite3, os
from sqlite3 import Error

# (B) DATABASE + SQL FILE
DBFILE = "events.db"
SQLFILE = "S1A_events.sql"

# (C) DELETE OLD DATABASE IF EXIST
if os.path.exists(DBFILE):
  os.remove(DBFILE)

# (D) IMPORT SQL
conn = sqlite3.connect(DBFILE)
with open(SQLFILE) as f:
  conn.executescript(f.read())
conn.commit()
conn.close()
print("Database created!")

This script pretty much just reads the above SQL file and creates the actual database file.

 

 

STEP 2) EVENTS LIBRARY

S2_lib.py
# (A) LOAD SQLITE MODULE
import sqlite3
from calendar import monthrange
DBFILE = "events.db"

# (B) SAVE EVENT
def save (start, end, txt, color, bg, id=None):
  # (B1) CONNECT
  conn = sqlite3.connect(DBFILE)
  cursor = conn.cursor()

  # (B2) DATA & SQL
  data = (start, end, txt, color, bg,)
  if id is None:
    sql = "INSERT INTO `events` (`start`, `end`, `text`, `color`, `bg`) VALUES (?,?,?,?,?)"
  else:
    sql = "UPDATE `events` SET `start`=?, `end`=?, `text`=?, `color`=?, `bg`=? WHERE `id`=?"
    data = data + (id,)

  # (B3) EXECUTE
  cursor.execute(sql, data)
  conn.commit()
  conn.close()
  return True

# (C) DELETE EVENT
def delete(id):
  # (C1) CONNECT
  conn = sqlite3.connect(DBFILE)
  cursor = conn.cursor()

  # (C2) EXECUTE
  cursor.execute("DELETE FROM `events` WHERE `id`=?", (id,))
  conn.commit()
  conn.close()
  return True

# (D) GET EVENTS
def get(month, year):
  # (D1) CONNECT
  conn = sqlite3.connect(DBFILE)
  cursor = conn.cursor()

  # (D2) DATE RANGE CALCULATIONS
  daysInMonth = str(monthrange(year, month)[1])
  month = month if month>10 else "0" + str(month)
  dateYM = str(year) + "-" + str(month) + "-"
  start = dateYM + "01 00:00:00"
  end = dateYM + daysInMonth + " 23:59:59"

  # (D3) GET EVENTS
  cursor.execute(
    "SELECT * FROM `events` WHERE ((`start` BETWEEN ? AND ?) OR (`end` BETWEEN ? AND ?) OR (`start` <= ? AND `end` >= ?))",
    (start, end, start, end, start, end)
  )
  rows = cursor.fetchall()
  if len(rows)==0:
    return None

  # s & e : start & end date
  # c & b : text & background color
  # t : event text
  data = {}
  for r in rows:
    data[r[0]] = {
      "s" : r[1], "e" : r[2],
      "c" : r[4], "b" : r[5],
      "t" : r[3]
    }
  return data

Next, we are going to need a library to work with the database. There are only 3 simple functions in this one:

  • save() Add or update an event.
  • delete() Delete an event.
  • get() Get all events for a given month/year.

 

 

STEP 3) FLASK SERVER

S3_server.py
# (A) INIT
# (A1) LOAD MODULES
import sys
import S2_lib as evt
from flask import Flask, request, render_template, make_response
 
# (A2) FLASK SETTINGS + INIT
HOST_NAME = "localhost"
HOST_PORT = 80
app = Flask(__name__)
# app.debug = True

# (B) ROUTES
# (B1) CALENDAR PAGE
@app.route("/", methods=["GET", "POST"])
def index():
  return render_template("S4A_calendar.html")

# (B2) ENDPOINT - GET EVENTS
@app.route("/get/", methods=["POST"])
def get():
  data = dict(request.form)
  events = evt.get(int(data["month"]), int(data["year"]))
  return "{}" if events is None else events

# (B3) ENDPOINT - SAVE EVENT
@app.route("/save/", methods=["POST"])
def save():
  data = dict(request.form)
  ok = evt.save(data["s"], data["e"], data["t"], data["c"], data["b"], data["id"] if "id" in data else None)
  msg = "OK" if ok else sys.last_value
  return make_response(msg, 200)

# (B4) ENDPOINT - DELETE EVENT
@app.route("/delete/", methods=["POST"])
def delete():
  data = dict(request.form)
  ok = evt.delete(data["id"])
  msg = "OK" if ok else sys.last_value
  return make_response(msg, 200)

# (C) START
if __name__ == "__main__":
  app.run(HOST_NAME, HOST_PORT)

Now for the Flask server itself.

  • / Serves the HTML calendar page.
  • /get/ An endpoint to get all events for a given month/year.
  • /save/ Add or update an event.
  • /delete/ Delete an event.

 

STEP 4) CALENDAR PAGE

4A) THE HTML

templates/S4A_calendar.html
<!-- (A) PERIOD SELECTOR -->
<div id="calHead">
  <div id="calPeriod">
    <select id="calMonth"></select>
    <input id="calYear" type="number">
  </div>
  <input id="calAdd" type="button" value="+">
</div>
 
<!-- (B) CALENDAR WRAPPER -->
<div id="calWrap">
  <div id="calDays"></div>
  <div id="calBody"></div>
</div>
 
<!-- (C) EVENT FORM -->
<dialog id="calForm"><form method="dialog">
  <h2 class="evt100">CALENDAR EVENT</h2>
  <div class="evt50">
    <label>Start</label>
    <input id="evtStart" type="datetime-local" required>
  </div>
  <div class="evt50">
    <label>End</label>
    <input id="evtEnd" type="datetime-local" required>
  </div>
  <div class="evt50">
    <label>Text Color</label>
    <input id="evtColor" type="color" value="#000000" required>
  </div>
  <div class="evt50">
    <label>Background Color</label>
    <input id="evtBG" type="color" value="#ffdbdb" required>
  </div>
  <div class="evt100">
    <label>Event</label>
    <input id="evtTxt" type="text" required>
  </div>
  <div class="evt100">
    <input type="hidden" id="evtID">
    <input type="button" id="evtCX" value="Cancel">
    <input type="button" id="evtDel" value="Delete">
    <input type="submit" id="evtSave" value="Save">
  </div>
</form></dialog>

Look no further, this is the only HTML page in the entire project.

  1. <div id="calHead"> The page header.
    • <div id="calPeriod"> Month and year selector.
    • <input id="calAdd"> Add a new event.
  2. <div id="calWrap"> Calendar wrapper.
    • <div id="calDays"> Day names (Monday to Sunday)
    • <div id="calBody"> The calendar itself.
  3. <dialog id="calForm"> A hidden popup event form.

 

 

4B) JAVASCRIPT – PROPERTIES & HELPERS

static/S4B_calendar.js
var cal = {
  // (A) PROPERTIES
  // (A1) FLAGS & DATA
  mon : false, // monday first
  events : null, // events data for current month/year
  sMth : 0, // selected month
  sYear : 0, // selected year
  sDIM : 0, // number of days in selected month
  sF : 0, // first date of the selected month (yyyymmddhhmm)
  sL : 0, // last date of the selected month (yyyymmddhhmm)
  sFD : 0, // first day of the selected month (mon-sun)
  sLD : 0, // last day of the selected month (mon-sun)
  ready : 0, // to track loading
 
  // (A2) HTML ELEMENTS
  hMth : null, hYear : null, // month & year
  hCD : null, hCB : null, // calendar days & body
  hFormWrap : null, hForm : null, // event form
  hfID : null, hfStart : null, // event form fields
  hfEnd : null, hfTxt : null,
  hfColor : null, hfBG : null,
  hfDel : null,
 
  // (B) SUPPORT FUNCTION - AJAX FETCH
  ajax : (req, data, onload) => {
    // (B1) FORM DATA
    let form = new FormData();
    for (let [k,v] of Object.entries(data)) { form.append(k,v); }
 
    // (B2) FETCH
    fetch(req + "/", { method:"POST", body:form })
    .then(res => res.text())
    .then(txt => onload(txt))
    .catch(err => console.error(err));
  },
  // ...
};

The Javascript is massive and it starts with a load of flags, data, and a helper function… Not going to explain everything line by line, but the noteworthy:

  • var cal = {} Contains all the calendar mechanics.
  • mon : true/false  To set Monday or Sunday first.
  • cal.ajax() A helper function to do an AJAX call.

 

4C) JAVASCRIPT – INIT

static/S4B_calendar.js
// (C) INIT CALENDAR
init : () => {
  // (C1) GET HTML ELEMENTS
  cal.hMth = document.getElementById("calMonth");
  cal.hYear = document.getElementById("calYear");
  cal.hCD = document.getElementById("calDays");
  cal.hCB = document.getElementById("calBody");
  cal.hFormWrap = document.getElementById("calForm");
  cal.hForm = cal.hFormWrap.querySelector("form");
  cal.hfID = document.getElementById("evtID");
  cal.hfStart = document.getElementById("evtStart");
  cal.hfEnd = document.getElementById("evtEnd");
  cal.hfTxt = document.getElementById("evtTxt");
  cal.hfColor = document.getElementById("evtColor");
  cal.hfBG = document.getElementById("evtBG");
  cal.hfDel = document.getElementById("evtDel");

  // (C2) MONTH & YEAR SELECTOR
  let now = new Date(), nowMth = now.getMonth() + 1;
  for (let [i,n] of Object.entries({
    1 : "January", 2 : "Febuary", 3 : "March", 4 : "April",
    5 : "May", 6 : "June", 7 : "July", 8 : "August",
    9 : "September", 10 : "October", 11 : "November", 12 : "December"
  })) {
    let opt = document.createElement("option");
    opt.value = i;
    opt.innerHTML = n;
    if (i==nowMth) { opt.selected = true; }
    cal.hMth.appendChild(opt);
  }
  cal.hYear.value = parseInt(now.getFullYear());

  // (C3) ATTACH CONTROLS
  cal.hMth.onchange = cal.load;
  cal.hYear.onchange = cal.load;
  document.getElementById("calAdd").onclick = () => cal.show();
  cal.hForm.onsubmit = () => cal.save();
  document.getElementById("evtCX").onclick = () => cal.hFormWrap.open = false;
  cal.hfDel.onclick = cal.del;

  // (C4) DRAW DAY NAMES
  let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
  if (cal.mon) { days.push("Sun"); } else { days.unshift("Sun"); }
  for (let d of days) {
    let cell = document.createElement("div");
    cell.className = "calCell";
    cell.innerHTML = d;
    cal.hCD.appendChild(cell);
  }

  // (C5) LOAD & DRAW CALENDAR
  cal.load();
},
// ...
window.onload = cal.init;

cal.init() runs on page load. Looks intimidating, but all it does is:

  • Get the HTML elements.
  • Setup the month and year selector.
  • Setup the day names.
  • “Enable” the calendar interface.

 

 

4D) JAVASCRIPT – LOAD & DRAW HTML CALENDAR

static/S4B_calendar.js
// (D) LOAD EVENTS
load : () => {
  // (D1) SET SELECTED PERIOD
  cal.sMth = parseInt(cal.hMth.value);
  cal.sYear = parseInt(cal.hYear.value);
  cal.sDIM = new Date(cal.sYear, cal.sMth, 0).getDate();
  cal.sFD = new Date(cal.sYear, cal.sMth-1, 1).getDay();
  cal.sLD = new Date(cal.sYear, cal.sMth-1, cal.sDIM).getDay();
  let m = cal.sMth;
  if (m < 10) { m = "0" + m; } cal.sF = parseInt(String(cal.sYear) + String(m) + "010000"); cal.sL = parseInt(String(cal.sYear) + String(m) + String(cal.sDIM) + "2359"); // (D2) AJAX GET EVENTS cal.ajax("get", { month : cal.sMth, year : cal.sYear }, evt => {
    cal.events = JSON.parse(evt);
    cal.draw();
  });
},
 
// (E) DRAW CALENDAR
draw : () => {
  // (E1) CALCULATE DAY MONTH YEAR
  // note - jan is 0 & dec is 11 in js
  // note - sun is 0 & sat is 6 in js
  let now = new Date(), // current date
      nowMth = now.getMonth()+1, // current month
      nowYear = parseInt(now.getFullYear()), // current year
      nowDay = cal.sMth==nowMth && cal.sYear==nowYear ? now.getDate() : null ;

  // (E2) DRAW CALENDAR ROWS & CELLS
  // (E2-1) INIT + HELPER FUNCTIONS
  let rowA, rowB, rowC, rowMap = {}, rowNum = 1,
      cell, cellNum = 1,
  rower = () => {
    rowA = document.createElement("div");
    rowB = document.createElement("div");
    rowC = document.createElement("div");
    rowA.className = "calRow";
    rowA.id = "calRow" + rowNum;
    rowB.className = "calRowHead";
    rowC.className = "calRowBack";
    cal.hCB.appendChild(rowA);
    rowA.appendChild(rowB);
    rowA.appendChild(rowC);
  },
  celler = day => {
    cell = document.createElement("div");
    cell.className = "calCell";
    if (day) { cell.innerHTML = day; }
    rowB.appendChild(cell);
    cell = document.createElement("div");
    cell.className = "calCell";
    if (day===undefined) { cell.classList.add("calBlank"); }
    if (day!==undefined && day==nowDay) { cell.classList.add("calToday"); }
    rowC.appendChild(cell);
  };
  cal.hCB.innerHTML = ""; rower();

  // (E2-2) BLANK CELLS BEFORE START OF MONTH
  if (cal.mon && cal.sFD != 1) {
    let blanks = cal.sFD==0 ? 7 : cal.sFD ;
    for (let i=1; i<blanks; i++) { celler(); cellNum++; }
  }
  if (!cal.mon && cal.sFD != 0) {
    for (let i=0; i<cal.sFD; i++) { celler(); cellNum++; }
  }

  // (E2-3) DAYS OF THE MONTH
  for (let i=1; i<=cal.sDIM; i++) {
    rowMap[i] = { r : rowNum, c : cellNum };
    celler(i);
    if (cellNum%7==0) { rowNum++; rower(); }
    cellNum++;
  }
  
  // (E2-4) BLANK CELLS AFTER END OF MONTH
  if (cal.mon && cal.sLD != 0) {
    let blanks = cal.sLD==6 ? 1 : 7-cal.sLD;
    for (let i=0; i<blanks; i++) { celler(); cellNum++; }
  }
  if (!cal.mon && cal.sLD != 6) {
    let blanks = cal.sLD==0 ? 6 : 6-cal.sLD;
    for (let i=0; i<blanks; i++) { celler(); cellNum++; } } // (E3) DRAW EVENTS if (Object.keys(cal.events).length > 0) { for (let [id,evt] of Object.entries(cal.events)) {
    // (E3-1) EVENT START & END DAY
    let sd = new Date(evt.s), ed = new Date(evt.e);
    sd = sd.getMonth()+1 < cal.sMth ? 1 : sd.getDate(); ed = ed.getMonth()+1 > cal.sMth ? cal.sDIM : ed.getDate();

    // (E3-2) "MAP" ONTO HTML CALENDAR
    cell = {}; rowNum = 0;
    for (let i=sd; i<=ed; i++) { if (rowNum!=rowMap[i]["r"]) { cell[rowMap[i]["r"]] = { s:rowMap[i]["c"], e:0 }; rowNum = rowMap[i]["r"]; } if (cell[rowNum]) { cell[rowNum]["e"] = rowMap[i]["c"]; } } // (E3-3) DRAW HTML EVENT ROW for (let [r,c] of Object.entries(cell)) { let o = c.s - 1 - ((r-1) * 7), // event cell offset w = c.e - c.s + 1; // event cell width rowA = document.getElementById("calRow"+r); rowB = document.createElement("div"); rowB.className = "calRowEvt"; rowB.innerHTML = cal.events[id]["t"]; rowB.style.color = cal.events[id]["c"]; rowB.style.backgroundColor = cal.events[id]["b"]; rowB.classList.add("w"+w); if (o!=0) { rowB.classList.add("o"+o); } rowB.onclick = () => cal.show(id);
      rowA.appendChild(rowB);
    }
  }}
},
  • cal.load() Fetch events from the database.
  • cal.draw() Draw the HTML calendar (and the events on it).

 

4E) JAVASCRIPT – SHOW EVENT FORM

static/S4B_calendar.js
// (F) SHOW EVENT FORM
show : id => {
  if (id) {
    cal.hfID.value = id;
    cal.hfStart.value = cal.events[id]["s"];
    cal.hfEnd.value = cal.events[id]["e"];
    cal.hfTxt.value = cal.events[id]["t"];
    cal.hfColor.value = cal.events[id]["c"];
    cal.hfBG.value = cal.events[id]["b"];
    cal.hfDel.style.display = "inline-block";
  } else {
    cal.hForm.reset();
    cal.hfID.value = "";
    cal.hfDel.style.display = "none";
  }
  cal.hFormWrap.open = true;
},

cal.show() is used to show the hidden popup event form.

  • id===NUMBER When the user clicks on an event to edit it. Set the form fields with the selected event and open the popup.
  • id===NOT NUMBER When the user clicks on “add event”. Reset the form and open the popup.

 

4F) JAVASCRIPT – SAVE EVENT

static/S4B_calendar.js
// (G) SAVE EVENT
save : () => {
  // (G1) COLLECT DATA
  // s & e : start & end date
  // c & b : text & background color
  // t : event text
  var data = {
    s : cal.hfStart.value.replace("T", " "),
    e : cal.hfEnd.value.replace("T", " "),
    t : cal.hfTxt.value,
    c : cal.hfColor.value,
    b : cal.hfBG.value
  };
  if (cal.hfID.value != "") { data.id = parseInt(cal.hfID.value); }

  // (G2) DATE CHECK
  if (new Date(data.s) > new Date(data.e)) {
    alert("Start date cannot be later than end date!");
    return false;
  }

  // (G3) SAVE
  cal.ajax("save", data, res => {
    if (res=="OK") {
      cal.hFormWrap.open = false;
      cal.load();
    } else { alert(res); }
  });
  return false;
},

This should be self-explanatory. Get the data from the HTML form, and send it to the server for saving.

 

4G) JAVASCRIPT – DELETE EVENT

static/S4B_calendar.js
// (H) DELETE EVENT
del : () => { if (confirm("Delete Event?")) {
  cal.ajax("delete", { id : parseInt(cal.hfID.value) }, res => {
    if (res=="OK") {
      cal.hFormWrap.open = false;
      cal.load();
    } else { alert(res); }
  });
}}

Finally, yet another self-explanatory function – Delete a selected event.

 

EXTRA BITS & LINKS

That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.

 

SQLITE IS NOT GREAT

Before haters start banging on their keyboards – We are using SQLite here out of convenience. While it is simple and easy for learning beginners, it is not great for live systems; It is lacking in performance, and cannot support distributed/cloud setups. So pick up a “better database” on your own – MYSQL, Oracle, POSTGRES, MSSQL, etc…

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

Leave a Comment

Your email address will not be published. Required fields are marked *