Многие новички, которые только начинают свою карьеру в программировании, не совсем понимают что такое CRUD. CRUD (Create, Read, Update, Delete) - термин, исходя из обычного перевода (Создавать, читать, обновлять, удалять), обозначает четыре базовые операции для управления данными в системах. Я хочу, попытаться объяснить на простом примере что же это такое и как с ним можно работать.
Сегодня мы напишем классический десктопный менеджер задач с графическим интерфейсом. Это отличный способ изучить и закрепить:
основы ООП (создадим класс приложения)
работу с JSON для хранения данных
создание интерфейса на Tkinter
обработку событий (нажатие кнопок, клики)
Готовый код займёт не так много строк, но после этого вы сможете закрепить свои знания, а кто-то изучить для себя что то новое.
Что мы будем делать
Наше приложение будет уметь четыре вещи (CRUD — Create, Read, Update, Delete):
Элемент интерфейса | Что делает |
|---|---|
Поле ввода + кнопка «Добавить» | Добавляет новую задачу |
Список задач (Listbox) | Показывает все задачи с их статусом |
Кнопка «Выполнено» | Отмечает выбранную задачу как выполненную |
Кнопка «Удалить» | Удаляет выбранную задачу |
Данные будем хранить в JSON-файле. Это проще, чем настраивать SQLite, и отлично подходит для такой задачи.
Вот как это будет выглядеть в работе:
Главное окно с полем ввода и списком задач.
Добавляем задачу — она появляется в списке.
Выделяем задачу, нажимаем «Выполнено» — она отмечается галочкой (или меняет цвет).
Выделяем задачу, нажимаем «Удалить» — она исчезает.
Минималистично и полностью независимо от браузера.

Структура проекта
Нам понадобится всего один файл - app.py. Вся логика будет внутри. План такой:
Определим константы (имя файла для хранения задач).
Напишем функции для загрузки и сохранения задач (почти как в консольной версии).
Создадим класс приложения TodoApp, который создаёт окно и виджеты, управляет отображением списка, обрабатывает действия пользователя.
Шаг 1. Загрузка и сохранение задач (модель данных)
Начнём с самого важного — как хранить задачи. Каждая задача — это словарь с тремя полями:
id - уникальный номер.
text - описание задачи.
completed - выполнена или нет (True/False).
Все задачи будем хранить в списке. А весь список — в JSON-файле tasks.json
import json import os TASKS_FILE = "tasks.json" def load_tasks(): """Загружает задачи из 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): """Сохраняет задачи в 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): """Возвращает следующий доступный ID для новой задачи""" if not tasks: return 1 return max(task["id"] for task in tasks) + 1
os.path.exists проверяет, существует ли файл. Если нет — возвращаем пустой список.
json.load читает данные из файла и превращает JSON обратно в список/словари Python.
json.dump делает обратное: превращает Python-объект в JSON и сохраняет.
ensure_ascii=False нужен, чтобы русский текст сохранялся нормально, а не в виде \u043f...
indent=2 делает файл читаемым для человека (с отступами).
Шаг 2. Создаём главное окно на Tkinter
Теперь переходим к самому интересному - графическому интерфейсу. Tkinter идёт в комплекте с Python, так что ничего дополнительно устанавливать не нужно, что для новичка очень огромный полюс.
Создадим класс TodoApp, который будет управлять всем приложением.
import tkinter as tk from tkinter import messagebox class TodoApp: def __init__(self, root): self.root = root self.root.title("Менеджер задач") self.root.geometry("500x400") self.root.resizable(False, False) # Загружаем задачи self.tasks = load_tasks() # Создаём элементы интерфейса self.create_widgets() # Обновляем список задач self.refresh_task_list() def create_widgets(self): """Создаёт все виджеты окна""" # Верхняя панель: поле ввода + кнопка добавления 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()) # Добавление по Enter self.add_button = tk.Button(top_frame, text="➕ Добавить", command=self.add_task) self.add_button.pack(side=tk.RIGHT) # Список задач 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_frame = tk.Frame(self.root) bottom_frame.pack(pady=10, padx=10, fill=tk.X) self.done_button = tk.Button(bottom_frame, text="✅ Выполнено", command=self.mark_done) self.done_button.pack(side=tk.LEFT, padx=5) self.delete_button = tk.Button(bottom_frame, text="🗑 Удалить", command=self.delete_task) self.delete_button.pack(side=tk.LEFT, padx=5) # Кнопка выхода self.exit_button = tk.Button(bottom_frame, text="Выход", command=self.root.quit) self.exit_button.pack(side=tk.RIGHT, padx=5) def refresh_task_list(self): """Обновляет отображение списка задач""" 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) # Остальные методы (add_task, mark_done, delete_task) будут здесь
Пояснения для новичков:
tk.Tk() - главное окно приложения.
Frame - контейнер для группировки виджетов.
Entry - поле ввода текста.
Listbox - список, в котором отображаются задачи.
Scrollbar - полоса прокрутки для списка.
bind("", ...) - позволяет добавлять задачу нажатием Enter.
refresh_task_list - перерисовывает список на основе текущих данных.
Шаг 3. Реализуем команды CRUD в GUI
Теперь добавим в класс методы, которые выполняют действия с задачами и обновляют интерфейс.
class TodoApp: # ... (предыдущий код) ... def add_task(self): """Добавляет новую задачу""" text = self.entry.get().strip() if not text: messagebox.showwarning("Предупреждение", "Введите текст задачи") 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("Успех", f"Задача добавлена (ID: {next_id})") def mark_done(self): """Отмечает выбранную задачу как выполненную""" selection = self.task_listbox.curselection() if not selection: messagebox.showwarning("Предупреждение", "Выберите задачу") 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("Информация", "Задача уже выполнена") else: task["completed"] = True save_tasks(self.tasks) self.refresh_task_list() messagebox.showinfo("Успех", f"Задача #{task_id} отмечена как выполненная") return def delete_task(self): """Удаляет выбранную задачу""" selection = self.task_listbox.curselection() if not selection: messagebox.showwarning("Предупреждение", "Выберите задачу") return index = selection[0] task_id = self.tasks[index]["id"] task_text = self.tasks[index]["text"] # Подтверждение удаления if messagebox.askyesno("Подтверждение", f"Удалить задачу #{task_id} \"{task_text}\"?"): del self.tasks[index] save_tasks(self.tasks) self.refresh_task_list() messagebox.showinfo("Успех", f"Задача #{task_id} удалена")
Важные моменты:
curselection() - возвращает индекс выделенного элемента в Listbox.
Мы всегда работаем с актуальным списком self.tasks, а после каждого изменения сохраняем его в файл и обновляем интерфейс.
messagebox - удобный способ показывать уведомления и подтверждения.
Шаг 4. Собираем всё вместе
А теперь самое важное, объединим всё в одном файле и добавим запуск приложения.
import json import os import tkinter as tk from tkinter import messagebox # Модель данных (работа с JSON) TASKS_FILE = "tasks.json" def load_tasks(): 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): with open(TASKS_FILE, "w", encoding="utf-8") as f: json.dump(tasks, f, ensure_ascii=False, indent=2) def get_next_id(tasks): if not tasks: return 1 return max(task["id"] for task in tasks) + 1 # Класс приложения class TodoApp: def __init__(self, root): self.root = root self.root.title("Менеджер задач") self.root.geometry("500x400") self.root.resizable(False, False) self.tasks = load_tasks() self.create_widgets() self.refresh_task_list() def create_widgets(self): # Верхняя панель 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="➕ Добавить", command=self.add_task) self.add_button.pack(side=tk.RIGHT) # Список с прокруткой 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_frame = tk.Frame(self.root) bottom_frame.pack(pady=10, padx=10, fill=tk.X) self.done_button = tk.Button(bottom_frame, text="✅ Выполнено", command=self.mark_done) self.done_button.pack(side=tk.LEFT, padx=5) self.delete_button = tk.Button(bottom_frame, text="🗑 Удалить", command=self.delete_task) self.delete_button.pack(side=tk.LEFT, padx=5) self.exit_button = tk.Button(bottom_frame, text="Выход", command=self.root.quit) self.exit_button.pack(side=tk.RIGHT, padx=5) def refresh_task_list(self): 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) def add_task(self): text = self.entry.get().strip() if not text: messagebox.showwarning("Предупреждение", "Введите текст задачи") 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("Успех", f"Задача добавлена (ID: {next_id})") def mark_done(self): selection = self.task_listbox.curselection() if not selection: messagebox.showwarning("Предупреждение", "Выберите задачу") 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("Информация", "Задача уже выполнена") else: task["completed"] = True save_tasks(self.tasks) self.refresh_task_list() messagebox.showinfo("Успех", f"Задача #{task_id} отмечена как выполненная") return def delete_task(self): selection = self.task_listbox.curselection() if not selection: messagebox.showwarning("Предупреждение", "Выберите задачу") return index = selection[0] task_id = self.tasks[index]["id"] task_text = self.tasks[index]["text"] if messagebox.askyesno("Подтверждение", f"Удалить задачу #{task_id} \"{task_text}\"?"): del self.tasks[index] save_tasks(self.tasks) self.refresh_task_list() messagebox.showinfo("Успех", f"Задача #{task_id} удалена") # Запуск приложения if __name__ == "__main__": root = tk.Tk() app = TodoApp(root) root.mainloop()
И вуаля!
У вас простое приложение, но с функциями CRUD (Create, Read, Update, Delete).
В следующий раз вспомним, а кто то изучит:
Редактирование - кнопка «Редактировать».
Цветовая индикация - выделять выполненные задачи зелёным, а важные — красным.
Сортировка - выводить невыполненные задачи выше.
Дата создания - показывать дату в списке.
Приоритеты - выпадающий список при добавлении: высокий, средний, низкий.
Фильтр - чекбоксы «Показать все/активные/выполненные».
Поиск - поле для поиска задач по тексту.
Экспорт - кнопка «Сохранить как CSV» или «Экспорт в PDF».
