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
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 just 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="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,
// (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
// (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
// (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
// (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
// (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
// (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
// (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
- 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