Hands-On CRUD Guide: Build a Task Manager with Python and Tkinter
CRUD (Create, Read, Update, Delete) is a core pattern for data operations. In this guide, we'll put it into practice by building a desktop task manager using Python, Tkinter, and JSON for data storage. Perfect for developers looking to solidify OOP basics, file handling, and GUI development.
App Architecture and Setup
Our app will live in a single app.py file. We'll use JSON for data storage—simple and straightforward for this kind of project. Each task is a dictionary with id (unique identifier), text (description), and completed (status). All tasks are stored in a list, serialized to tasks.json.
Make sure you have Python 3.6 or later installed. Tkinter comes built-in, so no extra installs needed. Create a new project folder and open it in your IDE.
Data Model and JSON Handling
First, we'll implement functions for loading and saving data—the foundation for Read and Update CRUD operations.
import json
import os
TASKS_FILE = "tasks.json"
def load_tasks():
"""Loads tasks from JSON file."""
if not os.path.exists(TASKS_FILE):
return []
with open(TASKS_FILE, "r", encoding="utf-8") as f:
try:
return json.load(f)
except json.JSONDecodeError:
return []
def save_tasks(tasks):
"""Saves tasks list to JSON file."""
with open(TASKS_FILE, "w", encoding="utf-8") as f:
json.dump(tasks, f, ensure_ascii=False, indent=2)
def get_next_id(tasks):
"""Generates next unique ID for a new task."""
if not tasks:
return 1
return max(task["id"] for task in tasks) + 1
Key features:
load_taskshandles missing or corrupted files gracefully.ensure_ascii=Falseinjson.dumpensures proper Unicode support.indent=2keeps JSON readable.
Building the GUI with Tkinter
We'll create a TodoApp class to manage the main window and widgets. The interface includes:
- Input field for new tasks.
- Listbox to display all tasks.
- Control buttons: add, mark done, delete.
import tkinter as tk
from tkinter import messagebox
class TodoApp:
def __init__(self, root):
self.root = root
self.root.title("Task Manager")
self.root.geometry("500x400")
self.root.resizable(False, False)
self.tasks = load_tasks()
self.create_widgets()
self.refresh_task_list()
def create_widgets(self):
"""Sets up all UI elements."""
# Top panel: input and add button
top_frame = tk.Frame(self.root)
top_frame.pack(pady=10, padx=10, fill=tk.X)
self.entry = tk.Entry(top_frame, font=("Arial", 12))
self.entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
self.entry.bind("<Return>", lambda event: self.add_task())
self.add_button = tk.Button(top_frame, text="➕ Add", command=self.add_task)
self.add_button.pack(side=tk.RIGHT)
# Task list with scrollbar
list_frame = tk.Frame(self.root)
list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
scrollbar = tk.Scrollbar(list_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.task_listbox = tk.Listbox(
list_frame,
font=("Arial", 11),
yscrollcommand=scrollbar.set,
selectmode=tk.SINGLE,
height=15
)
self.task_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=self.task_listbox.yview)
# Bottom panel: action buttons
bottom_frame = tk.Frame(self.root)
bottom_frame.pack(pady=10, padx=10, fill=tk.X)
self.done_button = tk.Button(bottom_frame, text="✅ Done", command=self.mark_done)
self.done_button.pack(side=tk.LEFT, padx=5)
self.delete_button = tk.Button(bottom_frame, text="🗑 Delete", command=self.delete_task)
self.delete_button.pack(side=tk.LEFT, padx=5)
self.exit_button = tk.Button(bottom_frame, text="Exit", command=self.root.quit)
self.exit_button.pack(side=tk.RIGHT, padx=5)
def refresh_task_list(self):
"""Refreshes the task list display."""
self.task_listbox.delete(0, tk.END)
for task in self.tasks:
status = "✓" if task["completed"] else "○"
display_text = f"{status} {task['id']}. {task['text']}"
self.task_listbox.insert(tk.END, display_text)
refresh_task_list handles the Read operation, showing the current data state. Checkmarks (✓/○) visualize completion status.
Implementing CRUD Operations
Next, add methods to the class for user actions, each tied to a CRUD operation.
Create: Adding a New Task
def add_task(self):
"""Adds a new task (Create)."""
text = self.entry.get().strip()
if not text:
messagebox.showwarning("Warning", "Enter task text")
return
next_id = get_next_id(self.tasks)
new_task = {
"id": next_id,
"text": text,
"completed": False
}
self.tasks.append(new_task)
save_tasks(self.tasks)
self.entry.delete(0, tk.END)
self.refresh_task_list()
messagebox.showinfo("Success", f"Task added (ID: {next_id})")
Update: Marking a Task Done
def mark_done(self):
"""Marks selected task as done (Update)."""
selection = self.task_listbox.curselection()
if not selection:
messagebox.showwarning("Warning", "Select a task")
return
index = selection[0]
task_id = self.tasks[index]["id"]
for task in self.tasks:
if task["id"] == task_id:
if task["completed"]:
messagebox.showinfo("Info", "Task already done")
else:
task["completed"] = True
save_tasks(self.tasks)
self.refresh_task_list()
messagebox.showinfo("Success", f"Task #{task_id} marked done")
return
Delete: Removing a Task
def delete_task(self):
"""Deletes selected task (Delete)."""
selection = self.task_listbox.curselection()
if not selection:
messagebox.showwarning("Warning", "Select a task")
return
index = selection[0]
task_id = self.tasks[index]["id"]
task_text = self.tasks[index]["text"]
if messagebox.askyesno("Confirm", f"Delete task #{task_id} \"{task_text}\"?"):
del self.tasks[index]
save_tasks(self.tasks)
self.refresh_task_list()
messagebox.showinfo("Success", f"Task #{task_id} deleted")
Each method validates input, updates in-memory data, saves to file, and refreshes the UI. Messageboxes provide clear user feedback.
Assembling and Running the App
Tie it all together in one file with a main entry point.
if __name__ == "__main__":
root = tk.Tk()
app = TodoApp(root)
root.mainloop()
Run with python app.py. Test all CRUD ops—the app saves data to tasks.json between sessions.
Key Takeaways
- CRUD Fundamentals: Create, Read, Update, Delete powers most data-driven apps.
- JSON Storage: Ideal for small projects—no database needed.
- Tkinter GUI: Quick, cross-platform desktop UIs with zero dependencies.
- Clean Code Structure: Separates data model (JSON), view (Tkinter), and logic (TodoApp) for easy maintenance.
- Error Handling: Validates inputs and catches issues like bad JSON.
Next Steps
Expand your task manager:
- Task Categories with filtering.
- Deadlines using
datetimeand color-coding. - Export to CSV or Excel.
- Database Integration (SQLite, PostgreSQL).
- Web Version with Flask or Django.
This project shows how core programming concepts apply to real-world apps, building a strong foundation for advanced topics.
— Editorial Team
No comments yet.