recreated json file handling as connector model, fixing #10 and introducing #8 and #7

This commit is contained in:
2025-04-28 00:18:22 +02:00
parent dc27197129
commit 9ea44933a6
3 changed files with 113 additions and 98 deletions

View File

@@ -8,6 +8,7 @@ from tkinter import ttk
import json import json
from config import Config from config import Config
from connector import JSONConnector
from windows import SettingsWindow, EditRecord, Window, show_error from windows import SettingsWindow, EditRecord, Window, show_error
@@ -35,13 +36,14 @@ class Application:
self.last_sort_field = "#3" self.last_sort_field = "#3"
self.filter_active = tk.BooleanVar(value=False) self.filter_active = tk.BooleanVar(value=False)
# model connector
self.model = JSONConnector()
# init paths to json and csv file # init paths to json and csv file
self.json_file_name = "brovski-adress-etiketten-verwaltung.json" self.json_file_name = "brovski-adress-etiketten-verwaltung.json"
self.csv_file_name = "brovski-adress-etiketten.csv" self.csv_file_name = "brovski-adress-etiketten.csv"
self.json_path = ""
self.csv_path = "" self.csv_path = ""
self.load_config() self.load_config()
self.json_file = os.path.join(self.json_path, self.json_file_name)
self.csv_file = os.path.join(self.csv_path, self.csv_file_name) self.csv_file = os.path.join(self.csv_path, self.csv_file_name)
# status bar content # status bar content
@@ -71,7 +73,8 @@ class Application:
tk.Button(top_frame, text="Delete", command=self.delete_record, width=button_width).pack(side=tk.LEFT) tk.Button(top_frame, text="Delete", command=self.delete_record, width=button_width).pack(side=tk.LEFT)
tk.Button(top_frame, text="Export CSV", command=self.export_csv, width=button_width).pack(side=tk.LEFT) tk.Button(top_frame, text="Export CSV", command=self.export_csv, width=button_width).pack(side=tk.LEFT)
tk.Button(top_frame, text="Toggle Aktiv", command=self.toggle_active, width=button_width).pack(side=tk.LEFT) tk.Button(top_frame, text="Toggle Aktiv", command=self.toggle_active, width=button_width).pack(side=tk.LEFT)
tk.Checkbutton(top_frame, text="Filter aktive", variable=self.filter_active, command=self.populate_table).pack(side=tk.LEFT) tk.Checkbutton(top_frame, text="Filter aktive", variable=self.filter_active, command=self.populate_table).pack(
side=tk.LEFT)
tk.Button(top_frame, text="Quit", command=self.on_close, width=button_width).pack(side=tk.RIGHT) tk.Button(top_frame, text="Quit", command=self.on_close, width=button_width).pack(side=tk.RIGHT)
tk.Button(top_frame, text="Settings", command=self.show_settings, width=button_width).pack(side=tk.RIGHT) tk.Button(top_frame, text="Settings", command=self.show_settings, width=button_width).pack(side=tk.RIGHT)
@@ -96,17 +99,14 @@ class Application:
# bottom status bar # bottom status bar
tk.Label(bottom_frame, textvariable=self.statusbar).pack(side=tk.LEFT) tk.Label(bottom_frame, textvariable=self.statusbar).pack(side=tk.LEFT)
self._load_json_file()
self.first_sort_after_start() self.first_sort_after_start()
self.root.mainloop() self.root.mainloop()
def load_config(self): def load_config(self):
try: try:
self.json_path = self.config.get("json", "path")
self.csv_path = self.config.get("csv", "path") self.csv_path = self.config.get("csv", "path")
if self.json_path == "" or self.csv_path == "": if self.csv_path == "":
raise ValueError("Empty JSON or CSV path") raise ValueError("Empty JSON or CSV path")
self.config_good = True self.config_good = True
except NoSectionError: except NoSectionError:
@@ -132,55 +132,56 @@ class Application:
settings.wait_window() settings.wait_window()
def insert_record(self): def insert_record(self):
values = [ values = {
"x", "aktiv": "x",
"Firma", "firma": "Firma",
"Name", "name": "Name",
"Strasse", "strasse": "Strasse",
"Plz/Ort", "plzort": "Plz/Ort"
] }
last_iid = self.table.get_children()[-1] self.model.create_new(values)
next_iid = int(last_iid) + 1 self.populate_table()
self.table.insert('', 'end', iid=next_iid, values=values)
self._save_json_file()
self.deselect_tree() self.deselect_tree()
def delete_record(self): def delete_record(self):
if self.current_record is None: if self.current_record is None:
return return
if len(self.table.selection()) > 1:
show_error(message_title="Mehrere Adressen ausgewählt",
message="Es können nur einzelne Adressen gelöscht werden",
parent=self.root)
return
if messagebox.askyesno( if messagebox.askyesno(
"Eintrag löschen?", "Eintrag löschen?",
"Willst du diesen Eintrag wirklich löschen?\nDies kann nicht rückgängig gemacht werden"): "Willst du diesen Eintrag wirklich löschen?\nDies kann nicht rückgängig gemacht werden"):
self.table.delete(self.current_record) print(type(self.current_record))
self.model.delete_by_id(self.current_record)
self.deselect_tree() self.deselect_tree()
self._save_json_file() self.populate_table()
def update_record(self, data: list): def update_record(self, record: dict):
if self.current_record is None: if self.current_record is None:
return return
values = {} self.model.update_record(record)
for idx, value in enumerate(data): self.populate_table()
values[str(idx)] = value.get()
for key, value in values.items():
self.table.set(self.current_record, key, value)
self._save_json_file()
def toggle_active(self): def toggle_active(self):
items = self.table.selection() selection = self.table.selection()
if len(items) == 0: if len(selection) == 0:
return return
for record_id in items: item_list = [int(x) for x in selection]
values = self.table.item(record_id, "values") for address in self.address_list:
active = values[0] record_id = address.get("record_id")
new_active = "x" if active == "" else "" if record_id in item_list:
self.table.set(record_id, "0", new_active) address["aktiv"] = "x" if address["aktiv"] == "" else ""
self.model.update_record(address)
self.table.set(record_id, "0", address["aktiv"])
self._save_json_file() self.update_status_bar()
def deselect_tree(self): def deselect_tree(self):
while len(self.table.selection()) > 0: while len(self.table.selection()) > 0:
@@ -188,7 +189,11 @@ class Application:
self.current_record = None self.current_record = None
def mouse_click(self, event): def mouse_click(self, event):
self.current_record = self.table.focus() id_string = self.table.focus()
if id_string is not None and id_string != "":
self.current_record = int(self.table.focus())
else:
self.current_record = None
region = self.table.identify("region", event.x, event.y) region = self.table.identify("region", event.x, event.y)
match region: match region:
case "heading": case "heading":
@@ -202,75 +207,70 @@ class Application:
def click_on_header(self, event): def click_on_header(self, event):
column = self.table.identify_column(event.x) column = self.table.identify_column(event.x)
print(f"col: {column}")
if self.last_sort_field == column: if self.last_sort_field == column:
self.sort_order = False if self.sort_order else True self.sort_order = False if self.sort_order else True
else: else:
self.sort_order = False self.sort_order = False
self.last_sort_field = column self.last_sort_field = column
self.address_list.sort(key=lambda x: (x[int(column[-1]) - 1]), reverse=self.sort_order)
# special case company sort match column:
if column == "#2": case "#1":
self.address_list.sort(key=lambda x: (x[1], x[2]), reverse=self.sort_order) field = "aktiv"
self.populate_table() case "#2":
field = "firma"
case "#3":
field = "name"
case "#4":
field = "strasse"
case "#5":
field = "plzort"
case _:
field = "name"
self.address_list = self.model.get_all_sorted_by(field, self.sort_order)
self.populate_table(reload=False)
def click_on_cell(self): def click_on_cell(self):
self.current_record = self.table.focus() self.current_record = int(self.table.focus())
values = self.table.item(self.current_record, "values") self.open_window_edit_records(self.current_record)
self.open_window_edit_records(values)
def _load_json_file(self):
try:
with open(self.json_file, "r", encoding="utf-8") as f:
self.address_list = json.load(f)
self.address_list.sort(key=lambda x: (x[0], x[1]))
except FileNotFoundError:
show_error(
message_title="Datei nicht gefunden",
message=f"{self.json_file_name} nicht gefunden, erstelle leere Datei unter {self.json_path}",
parent=self.root
)
self.address_list = [["", "firma", "name", "adresse", "plz/ort"]]
with open(self.json_file, "w", encoding="utf-8") as f:
json.dump(self.address_list, f)
self.populate_table()
def _save_json_file(self):
self.export_table_to_address_list()
try:
with open(self.json_file, "w", encoding="utf-8") as f:
json.dump(self.address_list, f, indent=4, sort_keys=True)
except FileNotFoundError:
show_error(
message_title="Unexpected Error: File not found?!",
message=f"{self.json_file_name} not found",
parent=self.root
)
def export_csv(self): def export_csv(self):
try: try:
with open(self.csv_file, "w", encoding="utf-8") as f: with open(self.csv_file, "w", encoding="utf-8") as f:
writer = csv.writer(f, delimiter=",") writer = csv.writer(f, delimiter=",")
for address in self.address_list: for address in self.address_list:
if address[0] != "x": if address["aktiv"] != "x":
continue continue
if address[1] == "": line = []
writer.writerow(address[2:]) if address["firma"] != "":
else: line.append(address["firma"])
writer.writerow(address[1:]) line.append(address["name"])
line.append(address["strasse"])
line.append(address["plzort"])
writer.writerow(line)
except FileNotFoundError: except FileNotFoundError:
show_error(message_title="Unexpected error", show_error(message_title="Unexpected error",
message=f"Could not write file {self.csv_file}", message=f"Could not write file {self.csv_file}",
parent=self.root parent=self.root
) )
def populate_table(self): def populate_table(self, reload=True):
if reload:
self.address_list = self.model.get_all()
if len(self.address_list) == 0:
return
self.delete_all_table_items() self.delete_all_table_items()
for index, item in enumerate(self.address_list): for address in self.address_list:
# skip inactive records if filter is true # skip inactive records if filter is true
if self.filter_active.get() and item[0] != "x": if self.filter_active.get() and address["aktiv"] != "x":
continue continue
self.table.insert('', 'end', iid=index, values=item) print(address)
self.table.insert('', 'end', iid=address["record_id"],
values=(address["aktiv"], address["firma"], address["name"], address["strasse"],
address["plzort"])
)
self.update_status_bar() self.update_status_bar()
def export_table_to_address_list(self): def export_table_to_address_list(self):
@@ -292,8 +292,8 @@ class Application:
window.grab_set() window.grab_set()
return window return window
def open_window_edit_records(self, data): def open_window_edit_records(self, record_id: int):
window = EditRecord(self, self.root, data) window = EditRecord(self, self.root, record_id)
window.wm_transient(self.root) window.wm_transient(self.root)
window.wait_visibility() window.wait_visibility()
window.grab_set() window.grab_set()
@@ -306,12 +306,12 @@ class Application:
self.length_address_list = len(self.address_list) self.length_address_list = len(self.address_list)
count = 0 count = 0
for address in self.address_list: for address in self.address_list:
if address[0] == "x": if address["aktiv"] == "x":
count += 1 count += 1
self.length_address_list_active = count self.length_address_list_active = count
def first_sort_after_start(self): def first_sort_after_start(self):
self.address_list.sort(key=lambda x: (x[1], x[2])) self.address_list.sort(key=lambda x: (x["firma"], x["name"]))
self.populate_table() self.populate_table()

View File

@@ -41,6 +41,11 @@ class JSONConnector(Connector):
with open(self.json_file, "r") as f: with open(self.json_file, "r") as f:
return json.load(f) return json.load(f)
def get_all_sorted_by(self, field: str, reverse=False) -> list:
with open(self.json_file, "r") as f:
data = json.load(f)
return sorted(data, key=lambda k: k[field], reverse=reverse)
def get_by_id(self, record_id: int) -> dict: def get_by_id(self, record_id: int) -> dict:
data = self.get_all() data = self.get_all()
for record in data: for record in data:

View File

@@ -3,6 +3,7 @@ from configparser import NoSectionError, NoOptionError
from tkinter import font, filedialog, messagebox from tkinter import font, filedialog, messagebox
from config import Config from config import Config
from connector import JSONConnector
def show_error(message_title: str, message: str, parent: tk.Tk | tk.Toplevel): def show_error(message_title: str, message: str, parent: tk.Tk | tk.Toplevel):
@@ -25,27 +26,28 @@ class Window(tk.Toplevel):
self._destroy_window() self._destroy_window()
def _destroy_window(self): def _destroy_window(self):
self.parent.populate_table()
self.root.update() self.root.update()
self.destroy() self.destroy()
class EditRecord(Window): class EditRecord(Window):
def __init__(self, parent, root: tk.Tk, data: tuple): def __init__(self, parent, root: tk.Tk, record_id: int):
super().__init__(parent, root) super().__init__(parent, root)
self.bind("<Return>", self._update) self.bind("<Return>", self._update)
self.data = data self.model = JSONConnector()
record = self.model.get_by_id(record_id)
button_width = 8 button_width = 8
self.title("Adresse bearbeiten") self.title("Adresse bearbeiten")
self.aktiv = tk.StringVar() self.record_id = tk.IntVar(value=record_id)
self.firma = tk.StringVar() self.aktiv = tk.StringVar(value=record.get("aktiv"))
self.name = tk.StringVar() self.firma = tk.StringVar(value=record.get("firma"))
self.strasse = tk.StringVar() self.name = tk.StringVar(value=record.get("name"))
self.plz_ort = tk.StringVar() self.strasse = tk.StringVar(value=record.get("strasse"))
self.plz_ort = tk.StringVar(value=record.get("plzort"))
self.field_list = [self.aktiv, self.firma, self.name, self.strasse, self.plz_ort]
for argument, field in zip(self.data, self.field_list):
field.set(argument)
edit_frame = tk.Frame(self) edit_frame = tk.Frame(self)
edit_frame.pack(side=tk.TOP, fill=tk.X, pady=20, padx=20) edit_frame.pack(side=tk.TOP, fill=tk.X, pady=20, padx=20)
@@ -71,7 +73,15 @@ class EditRecord(Window):
tk.Button(button_frame, text="Abbrechen", command=self.close_window, width=button_width).pack(side=tk.LEFT) tk.Button(button_frame, text="Abbrechen", command=self.close_window, width=button_width).pack(side=tk.LEFT)
def _update(self, event = None): def _update(self, event = None):
self.parent.update_record(self.field_list) new_record = {
"record_id": self.record_id.get(),
"aktiv": self.aktiv.get(),
"firma": self.firma.get(),
"name": self.name.get(),
"strasse": self.strasse.get(),
"plzort": self.plz_ort.get(),
}
self.model.update_record(new_record)
self.close_window() self.close_window()