I'm trying to create a to-do list (TODO) application in Python using tkinter. The problem is that, when trying to remove data from JSON files using the 'delete' function, the code is not removing data from the cards.json file or the ids.json file.
main.py
import datetime
from random import randint
from tkinter import *
from tkinter import messagebox
from extras_without_gui import save_ids, load_ids, clear_terminal, load_cards, save_cards
clear_terminal()
ids_data = load_ids()
IDs = ids_data['IDs']
Cards = load_cards()
def save(id, title, description):
today_date = datetime.date.today()
card = {
f"{id}": {
"Title": title,
"Description": description,
"Tag": [],
"Creation_Date": today_date.strftime("%d-%m-%Y"),
"Creation_Time": datetime.datetime.now().strftime("%H:%M"),
"Last_Modification_Date": "00-00-0000",
"Last_Modification_Time": "00:00",
"ID": id
}
}
ids_data['IDs'].append(id)
save_ids(ids_data)
Cards.update(card)
save_cards(Cards)
def delete_task(self):
task_id = int(self.id_to_delete.get())
if task_id not in self.IDs:
messagebox.showerror("Error", "Task ID not found.")
return
delete_confirm = messagebox.askquestion("Delete Task", "Are you sure you want to delete this task?")
if delete_confirm == "yes":
Cards.pop(str(task_id), " ")
save_cards(Cards)
self.IDs.remove(str(task_id))
self.ids_data['IDs'] = self.IDs
save_ids(self.ids_data)
self.id_to_delete.delete(0, 'end')
messagebox.showinfo("Success", "Task deleted successfully.")
else:
self.id_to_delete.delete(0, 'end')
messagebox.showinfo("Success", "Task not deleted.")
class Application:
def __init__(self, master=None):
self.master = master
self.ids_data = load_ids()
self.IDs = self.ids_data['IDs']
self.screen()
def destroy_window(self):
try:
self.master.destroy()
except AttributeError:
pass
def screen(self):
self.destroy_window()
self.master = Tk()
self.master.title("TODO Python")
self.master.geometry("600x600+250+50")
screen_1 = Frame(self.master)
screen_1["pady"] = 10
screen_1.pack()
screen_2 = Frame(self.master)
screen_2["pady"] = 10
screen_2.pack()
screen_3 = Frame(self.master)
screen_3.pack()
screen_4 = Frame(self.master)
screen_4["pady"] = 100
screen_4.pack()
title = Label(screen_1, text='TODO Python')
title["font"] = ("Arial", 20, "bold")
title.pack()
create_button = Button(screen_2, text='Create', command=self.create_task)
create_button["font"] = ("Arial", 10, "bold")
create_button["fg"] = "black"
create_button["bg"] = "white"
create_button.pack()
delete_button = Button(screen_3, text='Delete', command=self.delete_screen)
delete_button["font"] = ("Arial", 10, "bold")
delete_button["fg"] = "black"
delete_button["bg"] = "white"
delete_button.pack()
exit_button = Button(screen_4, text='Exit', command=self.destroy_window)
exit_button["font"] = ("Arial", 10, "bold")
exit_button["fg"] = "black"
exit_button["bg"] = "white"
exit_button.pack()
def create_task(self):
today_date = datetime.date.today()
self.destroy_window()
self.master = Tk()
self.master.title("Create New Task")
self.master.geometry("600x600+250+50")
title = Label(self.master, text='Create New Task')
title["font"] = ("Arial", 20, "bold")
title.pack()
screen_1 = Frame(self.master)
screen_1["pady"] = 10
screen_1.pack()
screen_2 = Frame(self.master)
screen_2["pady"] = 10
screen_2.pack()
screen_3 = Frame(self.master)
screen_3["pady"] = 10
screen_3.pack()
screen_4 = Frame(self.master)
screen_4["pady"] = 10
screen_4.pack()
screen_5 = Frame(self.master)
screen_5["pady"] = 10
screen_5.pack()
screen_6 = Frame(self.master)
screen_6["pady"] = 10
screen_6.pack()
title_label = Label(screen_1, text='Task Title')
title_label["font"] = ("Arial", 10, "bold")
title_label.pack()
self.title_task = Entry(screen_1)
self.title_task["font"] = ("Arial", 10, "bold")
self.title_task["width"] = 50
self.title_task.pack()
description_label = Label(screen_2, text='Task Description')
description_label["font"] = ("Arial", 10, "bold")
description_label.pack()
self.description_task = Text(screen_2, width=50, height=10)
self.description_task["font"] = ("Arial", 10, "bold")
self.description_task.pack()
creation_date = Label(screen_3, text=f'Creation Date: {today_date.strftime("%d-%m-%Y")}')
creation_date["font"] = ("Arial", 10, "bold")
creation_date.pack()
creation_time = Label(screen_3, text=f'Creation Time: {datetime.datetime.now().strftime("%H:%M")}')
creation_time['font'] = ("Arial", 10, "bold")
creation_time.pack()
id = self.generate_new_id()
id_label = Label(screen_4, text=f'ID: {id}')
id_label["font"] = ("Arial", 10, "bold")
id_label.pack()
save_button = Button(screen_5, text='Save', command=self.save_data)
save_button["font"] = ("Arial", 10, "bold")
save_button["fg"] = "black"
save_button["bg"] = "white"
save_button["width"] = 6
save_button.pack()
back_button = Button(screen_6, text='Back', command=self.screen)
back_button["font"] = ("Arial", 10, "bold")
back_button["fg"] = "black"
back_button["bg"] = "white"
back_button["width"] = 6
back_button.pack()
def save_data(self):
title = self.title_task.get()
description = self.description_task.get("1.0", "end
-1c")
if len(title) == 0 or len(description) == 0:
messagebox.showerror("Error", "Please fill in all fields.")
return
id = self.generate_new_id()
save(id, title, description)
self.title_task.delete(0, 'end')
self.description_task.delete("1.0", "end")
self.screen()
@staticmethod
def generate_new_id():
id = randint(10000, 99999)
while id in IDs:
id = randint(10000, 99999)
return id
def delete_screen(self):
self.destroy_window()
self.master = Tk()
self.master.title("Delete Task")
self.master.geometry("600x600+250+50")
self.screen_delete_1 = Frame(self.master)
self.screen_delete_1["pady"] = 10
self.screen_delete_1.pack()
self.screen_delete_2 = Frame(self.master)
self.screen_delete_2["pady"] = 10
self.screen_delete_2.pack()
self.screen_delete_3 = Frame(self.master)
self.screen_delete_3["pady"] = 10
self.screen_delete_3.pack()
self.screen_delete_4 = Frame(self.master)
self.screen_delete_4["pady"] = 10
self.screen_delete_4.pack()
self.screen_delete_5 = Frame(self.master)
self.screen_delete_5.pack()
self.title = Label(self.screen_delete_1, text='Delete Task')
self.title["font"] = ("Arial", 20, "bold")
self.title.pack()
self.label_delete = Label(self.screen_delete_2, text='Task ID: ')
self.label_delete["font"] = ("Arial", 10, "bold")
self.label_delete.pack()
self.id_to_delete = Entry(self.screen_delete_3)
self.id_to_delete["font"] = ("Arial", 10, "bold")
self.id_to_delete["width"] = 50
self.id_to_delete.pack()
self.delete_button = Button(self.screen_delete_4, text='Delete', command=lambda: delete_task(self))
self.delete_button["font"] = ("Arial", 10, "bold")
self.delete_button["fg"] = "black"
self.delete_button["bg"] = "white"
self.delete_button["width"] = 6
self.delete_button.pack()
self.back_button = Button(self.screen_delete_5, text='Back', command=self.screen)
self.back_button["font"] = ("Arial", 10, "bold")
self.back_button["fg"] = "black"
self.back_button["bg"] = "white"
self.back_button["width"] = 6
self.back_button.pack()
def main():
root = Tk()
Application(root)
root.mainloop()
if __name__ == '__main__':
main()
extras.py
"""
Script to handle loading, saving, and managing card data.
This script includes functions for loading and saving card information from/to JSON files,
as well as functions for clearing the terminal and displaying card details.
"""
from json import load
from json import dump
from json import JSONDecodeError
import platform
from os import system
from os import path
from os import getcwd
def load_cards():
"""
Load card data from the JSON file.
Returns:
- cards (dict): Dictionary containing card data.
"""
try:
with open('Data/cards.json', 'r', encoding='utf-8') as cards_info:
cards = load(cards_info)
except JSONDecodeError:
print('JSON formatting error')
except FileNotFoundError:
print('File not found')
except Exception as error:
print(f'ERROR: {error}')
else:
return cards
def save_cards(card):
"""
Save card data to the JSON file.
Args:
- card (dict): Card data to be saved.
"""
file_path = path.join(getcwd(), 'Data', 'cards.json')
try:
with open(file_path, "w", encoding="utf-8") as cards_info:
dump(card, cards_info, indent=4)
except JSONDecodeError:
print('JSON formatting error')
except FileNotFoundError:
print('File not found')
except Exception as error:
print(f'ERROR: {error}')
def load_ids():
"""
Load IDs from the JSON file.
Returns:
- IDs (dict): Dictionary containing IDs.
"""
try:
with open('Data/ids.json', 'r', encoding='utf-8') as data:
IDs = load(data)
except JSONDecodeError:
print('JSON formatting error')
except FileNotFoundError:
print('File not found')
except Exception as error:
print(f'ERROR: {error}')
else:
return IDs
def save_ids(ids):
"""
Save IDs to the JSON file.
Args:
- ids (dict): IDs data to be saved.
"""
file_path = path.join(getcwd(), 'Data', 'ids.json')
try:
with open(file_path, 'w', encoding='utf-8') as data:
dump(ids, data, indent=4)
except JSONDecodeError:
print('JSON formatting error')
except FileNotFoundError:
print('File not found')
except Exception as error:
print(f'ERROR: {error}')
def clear_terminal():
"""
Clear the terminal based on the operating system.
"""
try:
os_name = str(platform.system())
try:
if os_name == "Linux" or os_name == "Darwin":
system("clear")
elif os_name == "Windows":
system("cls")
else:
print("Unknown system")
except Exception as e:
print(f'Error clearing terminal: {e}')
exit(1)
except Exception as os_error:
print(f'Error identifying system\nError: {os_error}')
def show_cards(cards):
"""
Show card details.
Args:
- Cards (dict): Dictionary containing card data.
"""
print('All cards: \n')
for key, card in cards.items():
for field, content in card.items():
if isinstance(content, list):
tags = ', '.join(content)
print(f'{field}: {tags}')
else:
print(f'{field}: {content}')
print('\n')
cards.json
{
"15728": {
"Title": "0",
"Description": "0",
"Tag": [],
"Creation_Date": "19-02-2024",
"Creation_Time": "19:38",
"Last_Modification_Date": "00-00-0000",
"Last_Modification_Time": "00:00",
"ID": 15728
}
}
ids.json
{
"IDs": [
15728
]
}
it must ask for the id and then with the id go to the ids.json file and remove it from the array and in cards.json remove the entire content of the presented id
When I try this code, removing a recently-added task fails with "Task ID not found.".
The problem is that you have two separate lists of IDs, one loaded here:
clear_terminal()
ids_data = load_ids()
IDs = ids_data['IDs']
Cards = load_cards()
and the other here:
class Application:
def __init__(self, master=None):
self.master = master
self.ids_data = load_ids()
self.IDs = self.ids_data['IDs']
self.screen()
Furthermore, the save
function changes the JSON files, but doesn't cause the IDs to be reloaded, so delete
can't find it.
To fix it, change save
to write to Application.IDs:
# Add the 'self' parameter.
def save(self, id, title, description):
# Add this line.
self.IDs.append(id)
today_date = datetime.date.today()
...
and the way you call it:
def save_data(self):
title = self.title_task.get()
description = self.description_task.get("1.0", "end-1c")
if len(title) == 0 or len(description) == 0:
messagebox.showerror("Error", "Please fill in all fields.")
return
id = self.generate_new_id()
##############
# Add the 'self' parameter here.
save(self, id, title, description)
##############
self.title_task.delete(0, 'end')
self.description_task.delete("1.0", "end")
self.screen()
And I'd highly recommend you moving the save
and load
functions inside the Application class, as methods, instead of separate functions. It's unusual to have a free floating function that takes a 'self' parameter.
Finally, there's another issue where delete_task
tries to remove a string ID, when they should be ints:
delete_confirm = messagebox.askquestion("Delete Task", "Are you sure you want to delete this task?")
if delete_confirm == "yes":
Cards.pop(str(task_id), " ")
save_cards(Cards)
# Change here:
self.IDs.remove(task_id)
--
Alternatively, you can also reload the list of IDs before each operation. As long as the list is not overwhelmingly long, it'd be pretty much instant, and avoid many errors and forms of data corruption.