Введение в проект

В этом проекте мы создадим приложение для управления списком задач (To-Do List) с графическим интерфейсом пользователя. Этот проект поможет закрепить знания по работе с файлами, объектно-ориентированным программированием, обработке событий и использованию внешних библиотек. Мы будем использовать библиотеку tkinter для создания интерфейса и json для хранения данных.

Что вы узнаете

Планирование проекта

Перед началом реализации определим основные компоненты приложения списка задач:

Функциональные требования
  • Графический интерфейс с полем ввода для новых задач
  • Список для отображения всех задач
  • Кнопки для добавления, удаления и отметки выполненных задач
  • Возможность сохранения задач в файл
  • Автоматическая загрузка задач при запуске приложения
  • Визуальное различие между выполненными и невыполненными задачами
  • Сортировка задач по статусу выполнения

Реализация приложения списка задач

Создадим класс TodoListApp, который будет содержать всю логику работы приложения:

Базовая реализация приложения
import tkinter as tk
from tkinter import messagebox, ttk
import json
import os
from datetime import datetime

class TodoListApp:
    def __init__(self):
        self.окно = tk.Tk()
        self.окно.title("Список задач")
        self.окно.geometry("500x600")
        self.окно.configure(bg="#f0f0f0")
        
        # Файл для хранения задач
        self.файл_задач = "tasks.json"
        
        # Список задач
        self.задачи = []
        
        # Загрузка задач из файла
        self.загрузить_задачи()
        
        self.создать_интерфейс()
        self.обновить_список()
    
    def создать_интерфейс(self):
        # Заголовок
        заголовок = tk.Label(
            self.окно,
            text="Список задач",
            font=("Arial", 20, "bold"),
            bg="#f0f0f0",
            fg="#333333"
        )
        заголовок.pack(pady=10)
        
        # Панель ввода
        панель_ввода = tk.Frame(self.окно, bg="#f0f0f0")
        панель_ввода.pack(pady=10, padx=20, fill="x")
        
        # Поле ввода
        self.поле_ввода = tk.Entry(
            панель_ввода,
            font=("Arial", 12),
            width=30
        )
        self.поле_ввода.pack(side="left", fill="x", expand=True)
        self.поле_ввода.bind("<Return>", lambda event: self.добавить_задачу())
        
        # Кнопка добавления
        кнопка_добавить = tk.Button(
            панель_ввода,
            text="Добавить",
            command=self.добавить_задачу,
            bg="#4CAF50",
            fg="white",
            font=("Arial", 10, "bold"),
            relief="flat"
        )
        кнопка_добавить.pack(side="right", padx=(10, 0))
        
        # Панель управления
        панель_управления = tk.Frame(self.окно, bg="#f0f0f0")
        панель_управления.pack(pady=5, padx=20, fill="x")
        
        # Кнопки управления
        кнопка_выполнить = tk.Button(
            панель_управления,
            text="Отметить выполненной",
            command=self.отметить_выполненной,
            bg="#2196F3",
            fg="white",
            font=("Arial", 9),
            relief="flat"
        )
        кнопка_выполнить.pack(side="left", padx=(0, 5))
        
        кнопка_удалить = tk.Button(
            панель_управления,
            text="Удалить",
            command=self.удалить_задачу,
            bg="#f44336",
            fg="white",
            font=("Arial", 9),
            relief="flat"
        )
        кнопка_удалить.pack(side="left", padx=(0, 5))
        
        кнопка_обновить = tk.Button(
            панель_управления,
            text="Обновить",
            command=self.обновить_список,
            bg="#FF9800",
            fg="white",
            font=("Arial", 9),
            relief="flat"
        )
        кнопка_обновить.pack(side="left")
        
        # Список задач
        фрейм_списка = tk.Frame(self.окно, bg="#f0f0f0")
        фрейм_списка.pack(pady=10, padx=20, fill="both", expand=True)
        
        # Scrollbar
        scrollbar = tk.Scrollbar(фрейм_списка)
        scrollbar.pack(side="right", fill="y")
        
        # Listbox для задач
        self.список_задач = tk.Listbox(
            фрейм_списка,
            font=("Arial", 11),
            yscrollcommand=scrollbar.set,
            selectbackground="#cce5ff",
            selectforeground="#000000",
            activestyle="none",
            height=15
        )
        self.список_задач.pack(side="left", fill="both", expand=True)
        scrollbar.config(command=self.список_задач.yview)
        
        # Привязка события двойного клика
        self.список_задач.bind("<Double-Button-1>", lambda event: self.отметить_выполненной())
    
    def добавить_задачу(self):
        задача = self.поле_ввода.get().strip()
        if задача:
            новая_задача = {
                "id": len(self.задачи) + 1,
                "описание": задача,
                "выполнена": False,
                "дата_создания": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            }
            self.задачи.append(новая_задача)
            self.поле_ввода.delete(0, tk.END)
            self.обновить_список()
            self.сохранить_задачи()
        else:
            messagebox.showwarning("Предупреждение", "Пожалуйста, введите описание задачи")
    
    def отметить_выполненной(self):
        выбранная_задача = self.список_задач.curselection()
        if выбранная_задача:
            индекс = выбранная_задача[0]
            # Найдем реальный индекс задачи (без учета визуального отображения)
            задача_для_обновления = None
            for задача in self.задачи:
                if not задача["выполнена"]:
                    if self.задачи.index(задача) == индекс:
                        задача_для_обновления = задача
                        break
            
            if задача_для_обновления:
                задача_для_обновления["выполнена"] = True
                self.обновить_список()
                self.сохранить_задачи()
            else:
                messagebox.showinfo("Информация", "Выбранная задача уже выполнена")
        else:
            messagebox.showwarning("Предупреждение", "Пожалуйста, выберите задачу для отметки")
    
    def удалить_задачу(self):
        выбранная_задача = self.список_задач.curselection()
        if выбранная_задача:
            индекс = выбранная_задача[0]
            # Найдем реальный индекс задачи для удаления
            задача_для_удаления = None
            счетчик = 0
            for задача in self.задачи:
                if not задача["выполнена"]:
                    if счетчик == индекс:
                        задача_для_удаления = задача
                        break
                    счетчик += 1
            
            if задача_для_удаления:
                self.задачи.remove(задача_для_удаления)
                self.обновить_список()
                self.сохранить_задачи()
        else:
            messagebox.showwarning("Предупреждение", "Пожалуйста, выберите задачу для удаления")
    
    def обновить_список(self):
        self.список_задач.delete(0, tk.END)
        
        # Сначала отображаем невыполненные задачи
        for задача in self.задачи:
            if not задача["выполнена"]:
                отображение = f"☐ {задача['описание']}"
                self.список_задач.insert(tk.END, отображение)
        
        # Затем отображаем выполненные задачи
        for задача in self.задачи:
            if задача["выполнена"]:
                отображение = f"☑ {задача['описание']} (Выполнена)"
                self.список_задач.insert(tk.END, отображение)
                # Изменяем цвет выполненных задач
                self.список_задач.itemconfig(tk.END, fg="#888888")
    
    def сохранить_задачи(self):
        try:
            with open(self.файл_задач, "w", encoding="utf-8") as файл:
                json.dump(self.задачи, файл, ensure_ascii=False, indent=2)
        except Exception as e:
            messagebox.showerror("Ошибка", f"Не удалось сохранить задачи: {str(e)}")
    
    def загрузить_задачи(self):
        if os.path.exists(self.файл_задач):
            try:
                with open(self.файл_задач, "r", encoding="utf-8") as файл:
                    self.задачи = json.load(файл)
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось загрузить задачи: {str(e)}")
                self.задачи = []
        else:
            self.задачи = []
    
    def запустить(self):
        self.окно.mainloop()

# Запуск приложения
if __name__ == "__main__":
    приложение = TodoListApp()
    приложение.запустить()

Улучшенная версия приложения

Добавим дополнительные функции и улучшим интерфейс:

Расширенная реализация
import tkinter as tk
from tkinter import messagebox, ttk
import json
import os
from datetime import datetime

class AdvancedTodoListApp:
    def __init__(self):
        self.окно = tk.Tk()
        self.окно.title("Продвинутый Список задач")
        self.окно.geometry("600x700")
        self.окно.configure(bg="#f8f9fa")
        
        # Файл для хранения задач
        self.файл_задач = "advanced_tasks.json"
        
        # Список задач
        self.задачи = []
        
        # Загрузка задач из файла
        self.загрузить_задачи()
        
        self.создать_интерфейс()
        self.обновить_список()
    
    def создать_интерфейс(self):
        # Заголовок
        заголовок = tk.Label(
            self.окно,
            text="Продвинутый Список задач",
            font=("Arial", 22, "bold"),
            bg="#f8f9fa",
            fg="#2c3e50"
        )
        заголовок.pack(pady=(15, 10))
        
        # Статистика
        self.статистика = tk.Label(
            self.окно,
            text="",
            font=("Arial", 10),
            bg="#f8f9fa",
            fg="#7f8c8d"
        )
        self.статистика.pack(pady=(0, 10))
        self.обновить_статистику()
        
        # Панель ввода
        панель_ввода = tk.Frame(self.окно, bg="#f8f9fa")
        панель_ввода.pack(pady=10, padx=20, fill="x")
        
        # Поле ввода
        tk.Label(
            панель_ввода,
            text="Новая задача:",
            font=("Arial", 10),
            bg="#f8f9fa"
        ).pack(anchor="w")
        
        self.поле_ввода = tk.Entry(
            панель_ввода,
            font=("Arial", 12),
            width=40,
            relief="solid",
            bd=1
        )
        self.поле_ввода.pack(side="left", fill="x", expand=True, pady=(5, 0))
        self.поле_ввода.bind("<Return>", lambda event: self.добавить_задачу())
        
        # Кнопка добавления
        кнопка_добавить = tk.Button(
            панель_ввода,
            text="Добавить",
            command=self.добавить_задачу,
            bg="#27ae60",
            fg="white",
            font=("Arial", 10, "bold"),
            relief="flat",
            padx=15
        )
        кнопка_добавить.pack(side="right", padx=(10, 0), pady=(5, 0))
        
        # Панель фильтров
        панель_фильтров = tk.Frame(self.окно, bg="#f8f9fa")
        панель_фильтров.pack(pady=5, padx=20, fill="x")
        
        tk.Label(
            панель_фильтров,
            text="Фильтр:",
            font=("Arial", 10),
            bg="#f8f9fa"
        ).pack(side="left")
        
        self.переменная_фильтра = tk.StringVar(value="все")
        фильтры = [
            ("Все задачи", "все"),
            ("Активные", "активные"),
            ("Выполненные", "выполненные")
        ]
        
        for текст, значение in фильтры:
            радио = tk.Radiobutton(
                панель_фильтров,
                text=текст,
                variable=self.переменная_фильтра,
                value=значение,
                bg="#f8f9fa",
                command=self.обновить_список
            )
            радио.pack(side="left", padx=(10, 0))
        
        # Панель управления
        панель_управления = tk.Frame(self.окно, bg="#f8f9fa")
        панель_управления.pack(pady=10, padx=20, fill="x")
        
        # Кнопки управления
        кнопки = [
            ("Отметить выполненной", self.отметить_выполненной, "#3498db"),
            ("Удалить выбранную", self.удалить_задачу, "#e74c3c"),
            ("Удалить выполненные", self.удалить_выполненные, "#e67e22"),
            ("Очистить все", self.очистить_все, "#95a5a6")
        ]
        
        for текст, команда, цвет in кнопки:
            кнопка = tk.Button(
                панель_управления,
                text=текст,
                command=команда,
                bg=цвет,
                fg="white",
                font=("Arial", 9),
                relief="flat",
                padx=10
            )
            кнопка.pack(side="left", padx=(0, 5))
        
        # Список задач
        фрейм_списка = tk.Frame(self.окно, bg="#f8f9fa")
        фрейм_списка.pack(pady=10, padx=20, fill="both", expand=True)
        
        # Scrollbar
        scrollbar = tk.Scrollbar(фрейм_списка)
        scrollbar.pack(side="right", fill="y")
        
        # Listbox для задач с улучшенным оформлением
        self.список_задач = tk.Listbox(
            фрейм_списка,
            font=("Arial", 11),
            yscrollcommand=scrollbar.set,
            selectbackground="#d6eaf8",
            selectforeground="#2c3e50",
            activestyle="none",
            height=15,
            relief="solid",
            bd=1,
            highlightthickness=0
        )
        self.список_задач.pack(side="left", fill="both", expand=True)
        scrollbar.config(command=self.список_задач.yview)
        
        # Привязка событий
        self.список_задач.bind("<Double-Button-1>", lambda event: self.отметить_выполненной())
        self.список_задач.bind("<Delete>", lambda event: self.удалить_задачу())
    
    def добавить_задачу(self):
        задача = self.поле_ввода.get().strip()
        if задача:
            новая_задача = {
                "id": self.генерировать_id(),
                "описание": задача,
                "выполнена": False,
                "дата_создания": datetime.now().isoformat(),
                "дата_выполнения": None
            }
            self.задачи.append(новая_задача)
            self.поле_ввода.delete(0, tk.END)
            self.обновить_список()
            self.сохранить_задачи()
            self.обновить_статистику()
        else:
            messagebox.showwarning("Предупреждение", "Пожалуйста, введите описание задачи")
    
    def генерировать_id(self):
        if not self.задачи:
            return 1
        return max(задача["id"] for задача in self.задачи) + 1
    
    def отметить_выполненной(self):
        выбранная_задача = self.список_задач.curselection()
        if выбранная_задача:
            индекс = выбранная_задача[0]
            
            # Получаем задачу по визуальному индексу с учетом фильтра
            задача_для_обновления = self.получить_задачу_по_визуальному_индексу(индекс)
            
            if задача_для_обновления:
                if not задача_для_обновления["выполнена"]:
                    задача_для_обновления["выполнена"] = True
                    задача_для_обновления["дата_выполнения"] = datetime.now().isoformat()
                    self.обновить_список()
                    self.сохранить_задачи()
                    self.обновить_статистику()
                    messagebox.showinfo("Успех", "Задача отмечена как выполненная")
                else:
                    # Если задача уже выполнена, можем ее "восстановить"
                    if messagebox.askyesno("Подтверждение", "Задача уже выполнена. Отметить как невыполненная?"):
                        задача_для_обновления["выполнена"] = False
                        задача_для_обновления["дата_выполнения"] = None
                        self.обновить_список()
                        self.сохранить_задачи()
                        self.обновить_статистику()
            else:
                messagebox.showwarning("Предупреждение", "Не удалось найти выбранную задачу")
        else:
            messagebox.showwarning("Предупреждение", "Пожалуйста, выберите задачу для отметки")
    
    def получить_задачу_по_визуальному_индексу(self, визуальный_индекс):
        # Получаем отфильтрованный список задач
        отфильтрованные_задачи = self.получить_отфильтрованные_задачи()
        
        if 0 <= визуальный_индекс < len(отфильтрованные_задачи):
            return отфильтрованные_задачи[визуальный_индекс]
        return None
    
    def удалить_задачу(self):
        выбранная_задача = self.список_задач.curselection()
        if выбранная_задача:
            индекс = выбранная_задача[0]
            
            # Получаем задачу по визуальному индексу
            задача_для_удаления = self.получить_задачу_по_визуальному_индексу(индекс)
            
            if задача_для_удаления:
                if messagebox.askyesno("Подтверждение", f"Удалить задачу: '{задача_для_удаления['описание']}'?"):
                    self.задачи.remove(задача_для_удаления)
                    self.обновить_список()
                    self.сохранить_задачи()
                    self.обновить_статистику()
            else:
                messagebox.showwarning("Предупреждение", "Не удалось найти выбранную задачу")
        else:
            messagebox.showwarning("Предупреждение", "Пожалуйста, выберите задачу для удаления")
    
    def удалить_выполненные(self):
        выполненные_задачи = [задача for задача in self.задачи if задача["выполнена"]]
        
        if выполненные_задачи:
            if messagebox.askyesno("Подтверждение", f"Удалить {len(выполненные_задачи)} выполненных задач?"):
                for задача in выполненные_задачи:
                    self.задачи.remove(задача)
                self.обновить_список()
                self.сохранить_задачи()
                self.обновить_статистику()
                messagebox.showinfo("Успех", f"Удалено {len(выполненные_задачи)} задач")
        else:
            messagebox.showinfo("Информация", "Нет выполненных задач для удаления")
    
    def очистить_все(self):
        if self.задачи:
            if messagebox.askyesno("Подтверждение", "Удалить все задачи?"):
                self.задачи.clear()
                self.обновить_список()
                self.сохранить_задачи()
                self.обновить_статистику()
                messagebox.showinfo("Успех", "Все задачи удалены")
        else:
            messagebox.showinfo("Информация", "Список задач пуст")
    
    def получить_отфильтрованные_задачи(self):
        фильтр = self.переменная_фильтра.get()
        
        if фильтр == "активные":
            return [задача for задача in self.задачи if not задача["выполнена"]]
        elif фильтр == "выполненные":
            return [задача for задача in self.задачи if задача["выполнена"]]
        else:  # все
            return self.задачи
    
    def обновить_список(self):
        self.список_задач.delete(0, tk.END)
        
        # Получаем отфильтрованные задачи
        отфильтрованные_задачи = self.получить_отфильтрованные_задачи()
        
        # Отображаем задачи
        for задача in отфильтрованные_задачи:
            if задача["выполнена"]:
                отображение = f"☑ {задача['описание']} (Выполнена)"
                self.список_задач.insert(tk.END, отображение)
                # Изменяем цвет выполненных задач
                self.список_задач.itemconfig(tk.END, fg="#7f8c8d")
            else:
                # Добавляем дату создания для активных задач
                дата = datetime.fromisoformat(задача["дата_создания"]).strftime("%d.%m.%Y")
                отображение = f"☐ {задача['описание']} (Создана: {дата})"
                self.список_задач.insert(tk.END, отображение)
                self.список_задач.itemconfig(tk.END, fg="#2c3e50")
    
    def обновить_статистику(self):
        всего = len(self.задачи)
        выполненные = len([задача for задача in self.задачи if задача["выполнена"]])
        активные = всего - выполненные
        
        текст_статистики = f"Всего: {всего} | Активные: {активные} | Выполненные: {выполненные}"
        if всего > 0:
            процент = round((выполненные / всего) * 100)
            текст_статистики += f" | Выполнено: {процент}%"
        
        self.статистика.config(text=текст_статистики)
    
    def сохранить_задачи(self):
        try:
            with open(self.файл_задач, "w", encoding="utf-8") as файл:
                json.dump(self.задачи, файл, ensure_ascii=False, indent=2)
        except Exception as e:
            messagebox.showerror("Ошибка", f"Не удалось сохранить задачи: {str(e)}")
    
    def загрузить_задачи(self):
        if os.path.exists(self.файл_задач):
            try:
                with open(self.файл_задач, "r", encoding="utf-8") as файл:
                    self.задачи = json.load(файл)
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось загрузить задачи: {str(e)}")
                self.задачи = []
        else:
            self.задачи = []
    
    def запустить(self):
        self.окно.mainloop()

# Запуск приложения
if __name__ == "__main__":
    приложение = AdvancedTodoListApp()
    приложение.запустить()

Практическое применение и расширение

Рассмотрим дополнительные возможности для расширения функциональности приложения:

Дополнительные функции
# Добавление приоритетов задач
class TaskWithPriority:
    def __init__(self, описание, приоритет="средний"):
        self.id = self.generate_id()
        self.описание = описание
        self.приоритет = приоритет  # "низкий", "средний", "высокий"
        self.выполнена = False
        self.дата_создания = datetime.now()
        self.дата_выполнения = None
    
    def generate_id(self):
        return int(datetime.now().timestamp() * 1000000) % 1000000
    
    def get_priority_color(self):
        colors = {"низкий": "#27ae60", "средний": "#f39c12", "высокий": "#e74c3c"}
        return colors.get(self.приоритет, "#7f8c8d")
    
    def __str__(self):
        status = "☑" if self.выполнена else "☐"
        return f"{status} {self.описание} [{self.приоритет}]"

# Класс для категорий задач
class TaskCategory:
    def __init__(self, название):
        self.название = название
        self.задачи = []
    
    def add_task(self, задача):
        self.задачи.append(задача)
    
    def get_active_tasks(self):
        return [задача for задача in self.задачи if not задача.выполнена]
    
    def get_completed_tasks(self):
        return [задача for задача in self.задачи if задача.выполнена]

# Пример использования категорий
категории = {
    "Работа": TaskCategory("Работа"),
    "Личное": TaskCategory("Личное"),
    "Учеба": TaskCategory("Учеба")
}

# Добавление задач в категории
категории["Работа"].add_task(TaskWithPriority("Подготовить отчет", "высокий"))
категории["Личное"].add_task(TaskWithPriority("Купить продукты", "средний"))
категории["Учеба"].add_task(TaskWithPriority("Выполнить домашнее задание", "высокий"))

# Отображение задач по категориям
for название, категория in категории.items():
    print(f"\nКатегория: {название}")
    for задача in категория.задачи:
        print(f"  {задача}")

Упражнения

Упражнение 1: Добавление срока выполнения задач

Модифицируйте приложение списка задач, добавив возможность устанавливать срок выполнения для каждой задачи. Реализуйте визуальное отображение просроченных задач.

import tkinter as tk
from tkinter import messagebox, ttk
from datetime import datetime, timedelta
import json

class TaskWithDeadline:
    def __init__(self, описание, срок_выполнения=None):
        self.id = int(datetime.now().timestamp() * 1000000) % 1000000
        self.описание = описание
        self.выполнена = False
        self.дата_создания = datetime.now().isoformat()
        self.дата_выполнения = None
        self.срок_выполнения = срок_выполнения  # ISO формат даты
    
    def is_overdue(self):
        if self.выполнена or not self.срок_выполнения:
            return False
        return datetime.now() > datetime.fromisoformat(self.срок_выполнения)
    
    def days_until_deadline(self):
        if not self.срок_выполнения:
            return None
        if self.выполнена:
            return "Выполнена"
        
        срок = datetime.fromisoformat(self.срок_выполнения)
        разница = срок - datetime.now()
        return разница.days

# Пример использования
задача1 = TaskWithDeadline("Сделать домашку", (datetime.now() + timedelta(days=2)).isoformat())
задача2 = TaskWithDeadline("Сдать проект", (datetime.now() - timedelta(days=1)).isoformat())  # Просрочена

print(f"Задача 1 просрочена: {задача1.is_overdue()}")  # False
print(f"Дней до дедлайна задачи 1: {задача1.days_until_deadline()}")  # 2
print(f"Задача 2 просрочена: {задача2.is_overdue()}")  # True
print(f"Дней до дедлайна задачи 2: {задача2.days_until_deadline()}")  # -1
Упражнение 2: Экспорт задач в CSV

Добавьте функцию экспорта списка задач в CSV файл. Реализуйте возможность выбора формата экспорта (все задачи, только активные, только выполненные).

import csv
from datetime import datetime

def export_tasks_to_csv(задачи, имя_файла, тип_экспорта="все"):
    # Фильтрация задач в зависимости от типа экспорта
    if тип_экспорта == "активные":
        задачи_для_экспорта = [задача for задача in задачи if not задача["выполнена"]]
    elif тип_экспорта == "выполненные":
        задачи_для_экспорта = [задача for задача in задачи if задача["выполнена"]]
    else:  # все
        задачи_для_экспорта = задачи
    
    try:
        with open(имя_файла, 'w', newline='', encoding='utf-8') as csvfile:
            fieldnames = ['ID', 'Описание', 'Выполнена', 'Дата создания', 'Дата выполнения']
            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
            
            writer.writeheader()
            for задача in задачи_для_экспорта:
                writer.writerow({
                    'ID': задача["id"],
                    'Описание': задача["описание"],
                    'Выполнена': 'Да' if задача["выполнена"] else 'Нет',
                    'Дата создания': задача["дата_создания"],
                    'Дата выполнения': задача["дата_выполнения"] if задача["дата_выполнения"] else ''
                })
        
        print(f"Задачи успешно экспортированы в {имя_файла}")
    except Exception as e:
        print(f"Ошибка при экспорте: {e}")

# Пример использования
пример_задачи = [
    {
        "id": 1,
        "описание": "Купить молоко",
        "выполнена": False,
        "дата_создания": datetime.now().isoformat(),
        "дата_выполнения": None
    },
    {
        "id": 2,
        "описание": "Сделать домашку",
        "выполнена": True,
        "дата_создания": (datetime.now() - timedelta(days=1)).isoformat(),
        "дата_выполнения": datetime.now().isoformat()
    }
]

# Экспорт всех задач
export_tasks_to_csv(пример_задачи, "все_задачи.csv", "все")

# Экспорт только активных задач
export_tasks_to_csv(пример_задачи, "активные_задачи.csv", "активные")
Упражнение 3: Поиск по задачам

Реализуйте функцию поиска по задачам. Добавьте поле поиска в интерфейс и реализуйте алгоритм поиска по описанию задач.

def search_tasks(задачи, поисковый_запрос):
    """Поиск задач по описанию (регистронезависимый)"""
    if not поисковый_запрос:
        return задачи
    
    результаты = []
    поисковый_запрос = поисковый_запрос.lower().strip()
    
    for задача in задачи:
        if поисковый_запрос in задача["описание"].lower():
            результаты.append(задача)
    
    return результаты

def advanced_search(задачи, поисковый_запрос, поиск_по="описание"):
    """Расширенный поиск по разным полям"""
    if not поисковый_запрос:
        return задачи
    
    результаты = []
    поисковый_запрос = поисковый_запрос.lower().strip()
    
    for задача in задачи:
        if поиск_по == "описание" and поисковый_запрос in задача["описание"].lower():
            результаты.append(задача)
        elif поиск_по == "статус":
            статус = "выполнена" if задача["выполнена"] else "активна"
            if поисковый_запрос in статус:
                результаты.append(задача)
        elif поиск_по == "дата" and поисковый_запрос in задача["дата_создания"]:
            результаты.append(задача)
    
    return результаты

# Пример использования
задачи = [
    {
        "id": 1,
        "описание": "Купить молоко и хлеб",
        "выполнена": False,
        "дата_создания": "2023-10-15T10:30:00",
        "дата_выполнения": None
    },
    {
        "id": 2,
        "описание": "Сделать домашнее задание по математике",
        "выполнена": True,
        "дата_создания": "2023-10-14T14:20:00",
        "дата_выполнения": "2023-10-15T09:15:00"
    },
    {
        "id": 3,
        "описание": "Позвонить другу",
        "выполнена": False,
        "дата_создания": "2023-10-16T11:45:00",
        "дата_выполнения": None
    }
]

# Поиск по описанию
результат1 = search_tasks(задачи, "молоко")
print("Результаты поиска 'молоко':")
for задача in результат1:
    print(f"  - {задача['описание']}")

# Расширенный поиск по статусу
результат2 = advanced_search(задачи, "выполнена", "статус")
print("\nВыполненные задачи:")
for задача in результат2:
    print(f"  - {задача['описание']}")
Предыдущий урок Следующий урок