Praktyczny przewodnik CRUD: Tworzymy menedżer zadań w Pythonie z Tkinter
CRUD (Create, Read, Update, Delete) to fundamentalny wzorzec do pracy z danymi. W tym przewodniku zaimplementujemy go w praktyce, tworząc aplikację desktopową do zarządzania zadaniami za pomocą Pythona, Tkinter i JSON do przechowywania danych. Projekt jest odpowiedni dla programistów, którzy chcą utrwalić podstawy OOP, pracę z plikami i budowanie interfejsów graficznych.
Architektura aplikacji i przygotowanie środowiska
Nasza aplikacja będzie składać się z jednego pliku app.py. Do przechowywania danych wybrano format JSON, jako najprostszy i najbardziej przejrzysty dla takich zadań. Każde zadanie jest reprezentowane jako słownik z polami: id (unikalny identyfikator), text (opis) i completed (status wykonania). Wszystkie zadania są przechowywane na liście, która jest serializowana do pliku tasks.json.
Przed rozpoczęciem upewnij się, że masz zainstalowany Python 3.6 lub nowszy. Tkinter jest częścią standardowej biblioteki, więc dodatkowa instalacja nie jest wymagana. Utwórz nowy katalog dla projektu i otwórz go w swoim IDE.
Implementacja modelu danych i pracy z JSON
Najpierw zaimplementujemy funkcje do ładowania i zapisywania danych. To podstawa operacji CRUD Read i Update.
import json
import os
TASKS_FILE = "tasks.json"
def load_tasks():
"""Ładuje listę zadań z pliku JSON."""
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):
"""Zapisuje listę zadań do pliku JSON."""
with open(TASKS_FILE, "w", encoding="utf-8") as f:
json.dump(tasks, f, ensure_ascii=False, indent=2)
def get_next_id(tasks):
"""Generuje następny unikalny ID dla nowego zadania."""
if not tasks:
return 1
return max(task["id"] for task in tasks) + 1
Kluczowe aspekty tej implementacji:
- Funkcja
load_tasksobsługuje przypadki braku lub uszkodzenia pliku. - Parametr
ensure_ascii=Falsewjson.dumpzapewnia poprawne zapisywanie znaków diakrytycznych. indent=2formatuje JSON dla czytelności.
Budowanie interfejsu graficznego z Tkinter
Stworzymy klasę TodoApp, która będzie zarządzać głównym oknem i widżetami. Użyjemy Tkinter do zbudowania interfejsu, składającego się z:
- Pola wprowadzania dla nowego zadania.
- Listy (Listbox) do wyświetlania wszystkich zadań.
- Przycisków sterowania: dodawanie, oznaczanie jako wykonane, usuwanie.
import tkinter as tk
from tkinter import messagebox
class TodoApp:
def __init__(self, root):
self.root = root
self.root.title("Menedżer zadań")
self.root.geometry("500x400")
self.root.resizable(False, False)
self.tasks = load_tasks()
self.create_widgets()
self.refresh_task_list()
def create_widgets(self):
"""Inicjalizuje wszystkie elementy interfejsu."""
# Górny panel: pole wprowadzania i przycisk dodawania
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="➕ Dodaj", command=self.add_task)
self.add_button.pack(side=tk.RIGHT)
# Lista zadań z przewijaniem
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)
# Dolny panel: przyciski akcji
bottom_frame = tk.Frame(self.root)
bottom_frame.pack(pady=10, padx=10, fill=tk.X)
self.done_button = tk.Button(bottom_frame, text="✅ Wykonano", command=self.mark_done)
self.done_button.pack(side=tk.LEFT, padx=5)
self.delete_button = tk.Button(bottom_frame, text="🗑 Usuń", command=self.delete_task)
self.delete_button.pack(side=tk.LEFT, padx=5)
self.exit_button = tk.Button(bottom_frame, text="Wyjście", command=self.root.quit)
self.exit_button.pack(side=tk.RIGHT, padx=5)
def refresh_task_list(self):
"""Aktualizuje wyświetlanie listy zadań w interfejsie."""
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)
Metoda refresh_task_list implementuje operację Read, wyświetlając aktualny stan danych. Symbole ✓ i ○ wizualizują status wykonania.
Implementacja operacji CRUD w interfejsie
Dodajmy do klasy metody, które obsługują działania użytkownika, wykonując odpowiednie operacje CRUD.
Create: dodawanie nowego zadania
def add_task(self):
"""Dodaje nowe zadanie (Create)."""
text = self.entry.get().strip()
if not text:
messagebox.showwarning("Ostrzeżenie", "Wprowadź tekst zadania")
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("Sukces", f"Zadanie dodane (ID: {next_id})")
Update: oznaczanie zadania jako wykonanego
def mark_done(self):
"""Oznacza wybrane zadanie jako wykonane (Update)."""
selection = self.task_listbox.curselection()
if not selection:
messagebox.showwarning("Ostrzeżenie", "Wybierz zadanie")
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("Informacja", "Zadanie już wykonane")
else:
task["completed"] = True
save_tasks(self.tasks)
self.refresh_task_list()
messagebox.showinfo("Sukces", f"Zadanie #{task_id} oznaczone jako wykonane")
return
Delete: usuwanie zadania
def delete_task(self):
"""Usuwa wybrane zadanie (Delete)."""
selection = self.task_listbox.curselection()
if not selection:
messagebox.showwarning("Ostrzeżenie", "Wybierz zadanie")
return
index = selection[0]
task_id = self.tasks[index]["id"]
task_text = self.tasks[index]["text"]
if messagebox.askyesno("Potwierdzenie", f"Usunąć zadanie #{task_id} \"{task_text}\"?"):
del self.tasks[index]
save_tasks(self.tasks)
self.refresh_task_list()
messagebox.showinfo("Sukces", f"Zadanie #{task_id} usunięte")
Każda metoda zawiera walidację wprowadzania, zmianę danych w pamięci, zapis do pliku i aktualizację interfejsu. Użycie messagebox poprawia UX, zapewniając informację zwrotną.
Kompilacja i uruchomienie aplikacji
Aby zakończyć projekt, połączymy wszystkie komponenty w jednym pliku i dodamy punkt wejścia.
if __name__ == "__main__":
root = tk.Tk()
app = TodoApp(root)
root.mainloop()
Uruchom skrypt poleceniem python app.py. Aplikacja otworzy okno, gdzie będziesz mógł przetestować wszystkie operacje CRUD. Dane będą zapisywane między sesjami w pliku tasks.json.
Co jest ważne
- CRUD to podstawa: Wzorzec Create, Read, Update, Delete jest fundamentalny dla większości aplikacji pracujących z danymi.
- JSON do przechowywania: Użycie plików JSON to prosty i efektywny sposób przechowywania danych dla małych projektów, które nie wymagają pełnej bazy danych.
- Tkinter dla GUI: Biblioteka Tkinter pozwala szybko tworzyć wieloplatformowe interfejsy desktopowe bez zewnętrznych zależności.
- Struktura kodu: Jasne podzielenie na model danych (praca z JSON), widok (Tkinter) i logikę aplikacji (klasa TodoApp) poprawia utrzymywalność kodu.
- Obsługa błędów: Implementacja zawiera sprawdzanie poprawności wprowadzania i obsługę wyjątków (np. uszkodzony plik JSON).
Dalszy rozwój projektu
Tego menedżera zadań można rozszerzyć w następujący sposób:
- Dodanie kategorii zadań z możliwością filtrowania.
- Implementacja terminów z użyciem modułu
datetimei kolorową sygnalizacją. - Eksport danych do formatów CSV lub Excel do raportowania.
- Integracja z bazami danych (SQLite, PostgreSQL) dla bardziej niezawodnego przechowywania.
- Stworzenie wersji webowej z użyciem Flask lub Django, co pozwoli przećwiczyć te same operacje CRUD w innym kontekście.
Takie podejście pokazuje, jak podstawowe koncepcje programowania są stosowane w rzeczywistych projektach, zapewniając solidną podstawę do nauki bardziej złożonych tematów.
— Editorial Team
Brak komentarzy.