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
DOWNLOAD & NOTES
Here is the download link to the example code, so you don’t have to copy-paste everything.
EXAMPLE CODE DOWNLOAD
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.
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
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
- Create a virtual environment
virtualenv venv
and 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
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
# (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 reads the above SQL file and creates the actual database file.
STEP 2) EVENTS LIBRARY
# (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
# (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
<!-- (A) PERIOD SELECTOR -->
<div id="calHead">
<div id="calPeriod">
<input id="calToday" type="button" value="★">
<input id="calBack" type="button" class="mi" value="<">
<select id="calMonth"></select>
<input id="calYear" type="number">
<input id="calNext" type="button" class="mi" value=">">
</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.
<div id="calHead">
The page header.<div id="calPeriod">
Month and year selector.<input id="calAdd">
Add a new event.
<div id="calWrap">
Calendar wrapper.<div id="calDays">
Day names (Monday to Sunday)<div id="calBody">
The calendar itself.
<dialog id="calForm">
A hidden popup event form.
4B) JAVASCRIPT – PROPERTIES & HELPERS
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,
// (A3) HELPER FUNCTION - TRANSITION
transit : swap => {
if (document.startViewTransition) { document.startViewTransition(swap); }
else { swap(); }
},
// (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 starts with a load of flags, data, and helper functions. 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
// (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.transit(() => 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 – PERIOD SHIFT
// (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();
},
// (E) JUMP TO TODAY
today : () => {
let now = new Date(), ny = now.getFullYear(), nm = now.getMonth()+1;
if (ny!=cal.sYear || (ny==cal.sYear && nm!=cal.sMth)) {
cal.hMth.value = nm;
cal.hYear.value = ny;
cal.load();
}
},
cal.pshift()
A small helper function to drive the last/next month buttons. It simply adds/minus one to the current month.cal.today()
Jumps back to the current month/year.
4E) JAVASCRIPT – LOAD & DRAW HTML CALENDAR
// (F) LOAD EVENTS
load : () => {
// (F1) 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();
});
},
// (G) DRAW CALENDAR
draw : () => {
// (G1) 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 ;
// (G2) DRAW CALENDAR ROWS & CELLS
// (G2-1) INIT + HELPER FUNCTIONS
let rowA, rowB, rowC, rowMap = {}, rowNum = 1, cell, cellNum = 1,
// (G2-2) HELPER - DRAW A NEW ROW
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);
},
// (G2-3) HELPER - DRAW A NEW CELL
celler = day => {
cell = document.createElement("div");
cell.className = "calCell";
if (day) {
cell.innerHTML = day;
cell.classList.add("calCellDay");
cell.onclick = () => {
cal.show();
let d = +day, m = +cal.hMth.value,
s = `${cal.hYear.value}-${String(m<10 ? "0"+m : m)}-${String(d<10 ? "0"+d : d)}T00:00:00`;
cal.hfStart.value = s;
cal.hfEnd.value = s;
};
}
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);
};
// (G2-4) RESET CALENDAR
cal.hCB.innerHTML = ""; rower();
// (G2-5) 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++; }
}
// (G2-6) 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++;
}
// (G2-7) 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++; }
}
// (G3) DRAW EVENTS
if (Object.keys(cal.events).length > 0) { for (let [id,evt] of Object.entries(cal.events)) {
// (G3-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(); }
// (G3-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"]; }
}
// (G3-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
// (H) 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.transit(() => 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
// (I) SAVE EVENT
save : () => {
// (I1) 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); }
// (I2) DATE CHECK
if (new Date(data.s) > new Date(data.e)) {
alert("Start date cannot be later than end date!");
return false;
}
// (I3) SAVE
cal.ajax("save", data, res => {
if (res=="OK") {
cal.transit(() => 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
// (J) DELETE EVENT
del : () => { if (confirm("Delete Event?")) {
cal.ajax("delete", { id : parseInt(cal.hfID.value) }, res => {
if (res=="OK") {
cal.transit(() => cal.hFormWrap.close());
cal.load();
} else { alert(res); }
});
}}
Finally, yet another self-explanatory function – Delete a selected event.
EXTRAS
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 the haters start banging on their keyboards, we are using SQLite here only for 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
- Python Flask
- Offline PWA Calendar – Code Boxx
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!
i run this project it start perfectly when i submit my event it dosent work show some html error
https://code-boxx.com/faq#notwork