Simple Calendar 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!

 

 

TABLE OF CONTENTS

 

PYTHON FLASK CALENDAR

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

 

 

QUICK SETUP

The “usual stuff”:

  • Create a virtual environment virtualenv venv.
  • Activate it – venv/Scripts/activate (Windows) venv/bin/activate (Linux/Mac)
  • Install required libraries – pip install flask
  • For those who are new, the default Flask folders are –
    • static Public files (JS/CSS/images/videos/audio)
    • templates HTML pages

 

 

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">
    <input id="calBack" type="button" class="mi" value="&lt;">
    <select id="calMonth"></select>
    <input id="calYear" type="number">
    <input id="calNext" type="button" class="mi" value="&gt;">
  </div>
  <input id="calAdd" type="button" class="mi" 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">
  <div id="evtCX">X</div>
  <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="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("calBack").onclick = () => cal.pshift();
  document.getElementById("calNext").onclick = () => cal.pshift(1);
  document.getElementById("calAdd").onclick = () => cal.show();
  cal.hForm.onsubmit = () => cal.save();
  document.getElementById("evtCX").onclick = () => cal.hFormWrap.close();
  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 – THIS/NEXT MONTH

static/S4B_calendar.js
// (D) SHIFT CURRENT PERIOD BY 1 MONTH
pshift : forward => {
  cal.sMth = parseInt(cal.hMth.value);
  cal.sYear = parseInt(cal.hYear.value);
  if (forward) { cal.sMth++; } else { cal.sMth--; }
  if (cal.sMth > 12) { cal.sMth = 1; cal.sYear++; }
  if (cal.sMth < 1) { cal.sMth = 12; cal.sYear--; }
  cal.hMth.value = cal.sMth;
  cal.hYear.value = cal.sYear;
  cal.load();
},

A small helper function to drive the last/next month buttons… Pretty much just plus or minus one to the current month.

 

4E) JAVASCRIPT – LOAD & DRAW HTML CALENDAR

static/S4B_calendar.js
// (E) LOAD EVENTS
load : () => {
  // (E1) 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();
  });
},
 
// (F) DRAW CALENDAR
draw : () => {
  // (F1) 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 ;

  // (F2) DRAW CALENDAR ROWS & CELLS
  // (F2-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();

  // (F2-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++; }
  }

  // (F2-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 && i!=cal.sDIM) { rowNum++; rower(); }
    cellNum++;
  }
  
  // (F2-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);
    if (sd.getFullYear() < cal.sYear) { sd = 1; }
    else { sd = sd.getMonth()+1 < cal.sMth ? 1 : sd.getDate(); }
    if (ed.getFullYear() > cal.sYear) { ed = cal.sDIM; }
    else { ed = ed.getMonth()+1 > cal.sMth ? cal.sDIM : ed.getDate(); }

    // (F3-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).

 

4F) JAVASCRIPT – SHOW EVENT FORM

static/S4B_calendar.js
// (G) 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.show();
},

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.

 

4G) JAVASCRIPT – SAVE EVENT

static/S4B_calendar.js
// (H) SAVE EVENT
save : () => {
  // (H1) 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); }

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

  // (H3) SAVE
  cal.ajax("save", data, res => {
    if (res=="OK") {
      cal.hFormWrap.close();
      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.

 

4H) JAVASCRIPT – DELETE EVENT

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

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

 

DOWNLOAD & NOTES

Here is the download link to the example code, so you don’t have to copy-paste everything.

 

SORRY FOR THE ADS...

But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.

Buy Me A Coffee Code Boxx eBooks

 

EXAMPLE CODE DOWNLOAD

Click here for the source code on GitHub gist, just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

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!

2 thoughts on “Simple Calendar With Events In Python Flask (Free Download)”

Leave a Comment

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