Day 15: Phase 2 Capstone — OOP-Based Task Manager
🐍 Day 15: Phase 2 Capstone — OOP-Based Task Manager
1. Learning Objectives
By the end of Day 15, you will be able to:
- Design and implement a complete object‑oriented application from scratch
- Combine inheritance, encapsulation, and polymorphism into a real system
- Integrate file persistence (JSON) so data survives between runs
- Apply exception handling to make the app robust
- Structure a Python project with modules and packages
- Write clean, maintainable OOP code that can be extended
2. Concept Explanation
2.1 The Big Picture — What We're Building
Today you'll build a Command‑Line Task Manager — a to‑do application that supports multiple task types, deadlines, priorities, and persistent storage. This project synthesises everything from Days 8–14.
Core features:
- Add tasks of different types (simple task, deadline task, recurring task)
- Mark tasks as completed
- List tasks filtered by status or priority
- Save tasks to a JSON file and load them on startup
- Full error handling and input validation
2.2 OOP Design — From Concept to Code
Before writing a single line of code, think about your class hierarchy:
Task (base class)
├── SimpleTask
├── DeadlineTask
└── RecurringTask
Base class Task:
- Attributes:
title,description,priority,completed - Methods:
mark_completed(),__str__(),to_dict(),from_dict()
Inheritance & Polymorphism: Each subclass overrides __str__() and adds its own behaviour. The TaskManager can work with any task type through the base class interface.
Composition: A TaskManager class has a list of tasks, not is a list. This keeps responsibilities separate.
2.3 Project Structure
We'll organise the code into modules:
task_manager/
├── main.py # Entry point, menu loop
├── models/
│ ├── __init__.py
│ ├── task.py # Base Task class
│ └── variants.py # SimpleTask, DeadlineTask, RecurringTask
├── manager.py # TaskManager class (CRUD + persistence)
└── utils.py # Input validation helpers
2.4 Key Design Decisions
| Decision | Why |
|---|---|
| JSON for persistence | Human‑readable, easy to debug, built‑in json module |
to_dict() / from_dict() pattern | Makes serialisation clean and extensible |
| Class attribute for task type | Each subclass stores a string identifier ("simple", "deadline", "recurring") used for reconstruction |
@staticmethod for input validation | Keeps validation logic near the data it validates |
3. Code Examples — Building Blocks
Step 1: Base Task Class
# models/task.py
from datetime import datetime
class Task:
"""Base class for all task types."""
def __init__(self, title, description="", priority="medium"):
self.title = title
self.description = description
self.priority = priority
self.completed = False
self.created_at = datetime.now().isoformat()
def mark_completed(self):
self.completed = True
def __str__(self):
status = "✓" if self.completed else "○"
return f"[{status}] {self.title} ({self.priority})"
def to_dict(self):
"""Convert task to a dictionary for JSON serialisation."""
return {
"type": self.__class__.task_type,
"title": self.title,
"description": self.description,
"priority": self.priority,
"completed": self.completed,
"created_at": self.created_at,
}
@classmethod
def from_dict(cls, data):
"""Create a task from a dictionary (to be overridden by subclasses)."""
pass # Implemented in each subclass
Step 2: Subclasses
# models/variants.py
from .task import Task
class SimpleTask(Task):
task_type = "simple"
def __str__(self):
return f"[Simple] {super().__str__()}"
@classmethod
def from_dict(cls, data):
task = cls(data["title"], data["description"], data["priority"])
task.completed = data["completed"]
task.created_at = data["created_at"]
return task
class DeadlineTask(Task):
task_type = "deadline"
def __init__(self, title, description="", priority="medium", deadline=""):
super().__init__(title, description, priority)
self.deadline = deadline
def __str__(self):
return f"[Deadline: {self.deadline}] {super().__str__()}"
def to_dict(self):
data = super().to_dict()
data["deadline"] = self.deadline
return data
@classmethod
def from_dict(cls, data):
task = cls(data["title"], data["description"], data["priority"], data["deadline"])
task.completed = data["completed"]
task.created_at = data["created_at"]
return task
class RecurringTask(Task):
task_type = "recurring"
def __init__(self, title, description="", priority="medium", frequency="weekly"):
super().__init__(title, description, priority)
self.frequency = frequency
def __str__(self):
return f"[Recurring {self.frequency}] {super().__str__()}"
def to_dict(self):
data = super().to_dict()
data["frequency"] = self.frequency
return data
@classmethod
def from_dict(cls, data):
task = cls(data["title"], data["description"], data["priority"], data["frequency"])
task.completed = data["completed"]
task.created_at = data["created_at"]
return task
Step 3: Task Manager with Persistence
# manager.py
import json
from models.variants import SimpleTask, DeadlineTask, RecurringTask
TASK_CLASSES = {
"simple": SimpleTask,
"deadline": DeadlineTask,
"recurring": RecurringTask,
}
class TaskManager:
def __init__(self, filename="tasks.json"):
self.filename = filename
self.tasks = []
self.load()
def add_task(self, task):
self.tasks.append(task)
self.save()
def list_tasks(self, show_completed=True):
for i, task in enumerate(self.tasks, 1):
if not show_completed and task.completed:
continue
print(f"{i}. {task}")
def complete_task(self, index):
if 0 <= index < len(self.tasks):
self.tasks[index].mark_completed()
self.save()
else:
print("Invalid task number.")
def delete_task(self, index):
if 0 <= index < len(self.tasks):
removed = self.tasks.pop(index)
print(f"Deleted: {removed.title}")
self.save()
else:
print("Invalid task number.")
def save(self):
with open(self.filename, "w") as f:
json.dump([t.to_dict() for t in self.tasks], f, indent=2)
def load(self):
try:
with open(self.filename, "r") as f:
data = json.load(f)
self.tasks = [
TASK_CLASSES[item["type"]].from_dict(item)
for item in data
]
except FileNotFoundError:
self.tasks = []
4. Hands-On Exercises
Exercise 1: Build the Task Base Class
Create the Task class with the attributes and methods described above. Write a small test script to create a task, mark it completed, and print it.
Exercise 2: Implement a Subclass
Write the DeadlineTask subclass. Override __str__() and to_dict()/from_dict(). Test that an instance can be serialised to JSON and reconstructed correctly.
Exercise 3: JSON Persistence
Implement a simplified TaskManager that can:
- Add a task
- Save to a JSON file
- Load from a JSON file
- List all tasks Verify that tasks survive after restarting the program.
Exercise 4: Input Validation
Add exception handling in the manager: if the JSON file is corrupted or missing required keys, print a user‑friendly error instead of crashing.
Exercise 5: Polymorphic Display
Write a function display_tasks(tasks) that accepts a list of Task objects (any subclass) and prints them. Demonstrate with a list containing SimpleTask, DeadlineTask, and RecurringTask instances.
5. Applied Challenge Task 🏗️
Full Task Manager Application
Combine all the pieces into a working console application.
Core Requirements:
Menu‑driven interface with options:
=== TASK MANAGER === [1] Add Task [2] List All Tasks [3] List Pending Tasks [4] Complete Task [5] Delete Task [6] Save & QuitAdd task flow:
- Choose type: (S)imple, (D)eadline, (R)ecurring
- Enter title, description, priority
- For deadline: ask for date (YYYY‑MM‑DD)
- For recurring: ask for frequency (daily, weekly, monthly)
Input validations:
- Priority must be
low,medium, orhigh - Deadlines must be in valid date format
- Task numbers must be within range
- Priority must be
Persistence: All tasks are saved to
tasks.jsonwhen the user quits or after every modificationError handling: Graceful messages for file errors, invalid input, etc.
Stretch goals:
- Edit an existing task
- Search tasks by keyword
- Sort tasks by priority or deadline
- Add colour to the terminal output using
colorama - Write unit tests for the
TaskandTaskManagerclasses (preview of Day 22)
6. Brief Review Summary
| Concept | Application in Project |
|---|---|
| Inheritance | SimpleTask, DeadlineTask, RecurringTask inherit from Task |
| Encapsulation | Private helpers and properties protect internal state |
| Polymorphism | list_tasks() works with any Task subclass |
| Modules/Packages | Project split into models/, manager.py, utils.py |
| File I/O | JSON read/write for persistence |
| Exception handling | Corrupted files, missing keys, invalid inputs |
| Comprehensions | Useful for filtering tasks by status/priority |
7. Phase 2 Complete! 🎉
You have now mastered:
| Day | Topic |
|---|---|
| 8 | String Manipulation & Formatting |
| 9 | File Handling |
| 10 | Exception Handling & Debugging |
| 11 | List Comprehensions & Lambda Functions |
| 12 | Modules, Packages & Virtual Environments |
| 13 | Intro to OOP — Classes & Objects |
| 14 | OOP: Inheritance, Encapsulation, Polymorphism |
| 15 | Phase 2 Capstone Project |
Next stop: Phase 3 — Advanced Concepts (Days 16–23)
Day 16 kicks off with Iterators and Generators — memory‑efficient data processing and custom iteration.
🎯 Your Action Items for Day 15:
- ✅ Complete all 5 exercises
- ✅ Build the Full Task Manager application
- ✅ Experiment with adding new task types — see how easily the system extends
- ✅ Reflect on how OOP made this project more organised than procedural code would have
Comments
Post a Comment
Leave us your comments here...