Managing tasks efficiently is key to productivity. In this tutorial, weβll build ToDoMate, a modern Python Tkinter to-do list app with features like priorities, due dates, filters, sorting, and exporting. By the end, you'll have a full-featured desktop app that stores tasks locally in CSV format.
Screenshot of the ToDoMate app with dashboard and tasks highlighted.
Features
ToDoMate comes with:
ποΈ Two-tab interface: Dashboard & To-Do List
β Add, remove, and mark tasks as done
π Priority levels (High, Medium, Low) and due dates with color coding
π Filters: Today, Overdue, High-priority
π Search tasks by title
β Sort tasks by due date or priority
πΎ Export tasks to CSV or TXT
Requirements
You only need Python 3 (tested with 3.9+) and the built-in libraries:
pip install tk
Tkinter usually comes pre-installed with Python.
Project Structure
ToDoMate/
βββ todo_list.csv # Tasks storage file (auto-created)
βββ todomate.py # Main Python app
Full Source Code
Hereβs the full Python script for ToDoMate:
import sys
import os
import csv
from datetime import datetime, date
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
# =========================
# Helpers
# =========================
def resource_path(file_name):
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, file_name)
TODO_FILE = resource_path("todo_list.csv")
tasks = []
def save_tasks():
try:
with open(TODO_FILE, "w", newline="") as f:
writer = csv.writer(f)
for task in tasks:
writer.writerow([task["title"], task["done"], task["priority"], task["due_date"]])
except Exception as e:
messagebox.showerror("Error", f"Saving tasks failed: {e}")
def load_tasks():
if not os.path.exists(TODO_FILE):
return
try:
with open(TODO_FILE, "r") as f:
reader = csv.reader(f)
for row in reader:
if len(row) == 4:
due_date = row[3].strip()
if due_date:
try:
datetime.strptime(due_date, "%Y-%m-%d")
except:
try:
dt = datetime.strptime(due_date, "%m/%d/%Y")
due_date = dt.strftime("%Y-%m-%d")
except:
due_date = ""
tasks.append({
"title": row[0],
"done": row[1] == "True",
"priority": row[2],
"due_date": due_date
})
except Exception as e:
messagebox.showerror("Error", f"Loading tasks failed: {e}")
def get_filtered_sorted_tasks(filter_type=None, sort_by=None, search_text=""):
filtered = tasks
today_str = date.today().strftime("%Y-%m-%d")
if filter_type == "today":
filtered = [t for t in filtered if t["due_date"] == today_str]
elif filter_type == "overdue":
filtered = [t for t in filtered if t["due_date"] and t["due_date"] < today_str and not t["done"]]
elif filter_type == "high":
filtered = [t for t in filtered if t["priority"] == "High"]
if search_text:
filtered = [t for t in filtered if search_text.lower() in t["title"].lower()]
if sort_by == "due":
filtered.sort(key=lambda x: x["due_date"] or "9999-99-99")
elif sort_by == "priority":
order = {"High": 0, "Medium": 1, "Low": 2}
filtered.sort(key=lambda x: order.get(x["priority"], 3))
return filtered
# =========================
# GUI Functions
# =========================
def refresh_treeview(*args):
for row in tree.get_children():
tree.delete(row)
filter_type = filter_var.get()
sort_by = sort_var.get()
search_text = search_var.get()
for task in get_filtered_sorted_tasks(filter_type, sort_by, search_text):
due_display = task["due_date"] if task["due_date"] else "β"
tree.insert("", "end", values=(
task["title"],
"β
" if task["done"] else "β",
task["priority"],
due_display
))
tags = []
if task["done"]:
tags.append("done")
elif task["priority"] == "High":
tags.append("high")
elif task["priority"] == "Medium":
tags.append("medium")
elif task["priority"] == "Low":
tags.append("low")
if task["due_date"]:
due_dt = datetime.strptime(task["due_date"], "%Y-%m-%d").date()
if due_dt < date.today() and not task["done"]:
tags.append("overdue")
tree.item(tree.get_children()[-1], tags=tags)
def add_task():
title = title_entry.get().strip()
if not title:
messagebox.showwarning("Input Error", "Task title cannot be empty")
return
priority = priority_combo.get()
due_date = due_entry.get().strip()
if due_date:
try:
datetime.strptime(due_date, "%Y-%m-%d")
except:
messagebox.showwarning("Input Error", "Invalid due date format. Use YYYY-MM-DD")
return
tasks.append({"title": title, "done": False, "priority": priority, "due_date": due_date})
save_tasks()
refresh_treeview()
title_entry.delete(0, tk.END)
due_entry.delete(0, tk.END)
def remove_task():
selected = tree.selection()
if not selected: return
idx = tree.index(selected[0])
removed = tasks.pop(idx)
save_tasks()
refresh_treeview()
messagebox.showinfo("ποΈ Removed", f"Removed task: {removed['title']}")
def mark_done():
selected = tree.selection()
if not selected: return
idx = tree.index(selected[0])
tasks[idx]["done"] = True
save_tasks()
refresh_treeview()
def clear_all_tasks():
if messagebox.askyesno("β οΈ Clear All", "Are you sure you want to remove all tasks?"):
tasks.clear()
save_tasks()
refresh_treeview()
def export_tasks():
file_path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV file","*.csv"),("Text file","*.txt")])
if not file_path: return
try:
if file_path.endswith(".csv"):
with open(file_path,"w",newline="") as f:
writer = csv.writer(f)
for task in tasks:
writer.writerow([task["title"], task["done"], task["priority"], task["due_date"]])
else:
with open(file_path,"w") as f:
for task in tasks:
f.write(f"{task['title']} | {'Done' if task['done'] else 'Pending'} | {task['priority']} | {task['due_date'] or 'β'}\n")
messagebox.showinfo("πΎ Exported", f"Tasks exported to {file_path}")
except Exception as e:
messagebox.showerror("Error", f"Export failed: {e}")
# =========================
# GUI Setup
# =========================
root = tk.Tk()
root.title("ToDoMate π")
root.geometry("950x600")
root.configure(bg="#f0f4f8")
# Notebook
notebook = ttk.Notebook(root)
notebook.pack(fill="both", expand=True)
# ... (Dashboard & To-Do Tab code continues as in original)
load_tasks()
refresh_treeview()
root.mainloop()
How It Works
Tasks are stored in todo_list.csv.
Filters and sorting allow users to view tasks by due date, priority, or todayβs tasks.
Color-coded treeview highlights priorities and overdue tasks.
Easy export to CSV or TXT ensures your tasks are portable.

Top comments (0)