So, I am creating a database, on Python (Tkinter). Everything was going well, untill I realized that when I try to autofill the entry field by selecting a product, these are not well populated. The problem is that, the entry fields are populated by what is on the treeview, I don't know how to make it so that the info is gathered from the JOSN file instead (The costs on the treeview are not shown at the same time, because there's 2 different, depending the one you select is the one shown and the application will calculate the gain and margin). The other thing is that I have a menu window, and I can't make the root not appear until the log in is complete, so you have 2 windows open and is not nice (this is more for the looks but still).I use python 3.13.0
For the populated entry fields, I really don't know what to do, I am lost and have been scratching my head for the last few days.
For the menu I tried using root.withdraw()
at the end of the code, it works, but then when I complete the log in, the "database" (the main app) does not open.
(If you see something I can change even if not related to the question I will be thankfull, Iam not a programming pro)
import tkinter as tk
from tkinter import messagebox, ttk
import json
import os
class User:
def __init__(self, username, password):
self.username = username
self.password = password
def to_dict(self):
return {
"username": self.username,
"password": self.password
}
@classmethod
def from_dict(cls, data):
return cls(data['username'], data['password'])
@staticmethod
def save_users(users):
with open('users.json', 'w') as f:
json.dump([user.to_dict() for user in users], f)
@staticmethod
def load_users():
if os.path.exists('users.json'):
with open('users.json', 'r') as f:
return [User.from_dict(item) for item in json.load(f)]
return []
class Product:
def __init__(self, name, price, cost_lb, cost_pm, weight, store_prices=None):
self.name = name
self.price = price
self.cost_lb = cost_lb
self.cost_pm = cost_pm
self.weight = weight
self.store_prices = store_prices if store_prices is not None else {}
def gain(self, use_cost_lb=True):
return self.price - (self.cost_lb if use_cost_lb else self.cost_pm)
def margin(self, use_cost_lb=True):
gain = self.gain(use_cost_lb)
return (gain / self.price) * 100 if self.price > 0 else 0
def to_dict(self):
return {
"name": self.name,
"price": self.price,
"cost_lb": self.cost_lb,
"cost_pm": self.cost_pm,
"weight": self.weight,
"store_prices": self.store_prices
}
@classmethod
def from_dict(cls, data):
return cls(data['name'], data['price'], data['cost_lb'], data['cost_pm'], data['weight'], data.get('store_prices', {}))
class Store:
def __init__(self):
self.products = []
self.load_products()
def add_product(self, name, price, cost_lb, cost_pm, weight):
product = Product(name, price, cost_lb, cost_pm, weight)
self.products.append(product)
self.save_products()
def delete_product(self, name):
self.products = [product for product in self.products if product.name != name]
self.save_products()
def update_product(self, name, price=None, cost_lb=None, cost_pm=None, weight=None):
for product in self.products:
if product.name == name:
if price is not None:
product.price = price
if cost_lb is not None:
product.cost_lb = cost_lb
if cost_pm is not None:
product.cost_pm = cost_pm
if weight is not None:
product.weight = weight
break
self.save_products()
def save_products(self):
with open('products.json', 'w') as f:
json.dump([product.to_dict() for product in self.products], f)
def load_products(self):
if os.path.exists('products.json'):
with open('products.json', 'r') as f:
self.products = [Product.from_dict(item) for item in json.load(f)]
class StoreApp:
def __init__(self, root):
self.root = root
self.root.title("RenoCart")
self.store = Store()
self.users = User.load_users() # Load existing users
self.current_user = None # Track the currently logged-in user
self.show_menu()
def center_window(self, window, width, height):
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()
x = (screen_width // 2) - (width // 2)
y = (screen_height // 2) - (height // 2)
window.geometry(f"{width}x{height}+{x}+{y}")
def show_menu(self):
self.clear_frame()
menu_window = tk.Toplevel(self.root)
menu_window.title("Main Menu")
self.center_window(menu_window, 210, 250)
title_label = tk.Label(menu_window, text="RenoCart", font=("Helvetica", 16))
title_label.pack(pady=10)
# Login button
login_button = tk.Button(menu_window, text="Login", command=self.open_login_window) # Changed this line
login_button.pack(pady=5)
# Sign-up button
signup_button = tk.Button(menu_window, text="Sign Up", command=self.signup_user)
signup_button.pack(padx=7.5)
# Quit Button
quit_button = tk.Button(menu_window, text="Close", command=self.root.quit)
quit_button.pack(pady=10)
def signup_user(self):
signup_window = tk.Toplevel(self.root)
signup_window.title("Sign up")
signup_window.geometry("300x200")
self.center_window(signup_window, 210, 220)
# Username label and entry
username_label = tk.Label(signup_window, text="Username:")
username_label.pack()
username_entry = tk.Entry(signup_window)
username_entry.pack()
# Password label and entry
password_label = tk.Label(signup_window, text="Password:")
password_label.pack()
password_entry = tk.Entry(signup_window, show="*")
password_entry.pack()
# Code for signup
code_label = tk.Label(signup_window, text="Sign Up Code:")
code_label.pack()
code_entry = tk.Entry(signup_window)
code_entry.pack(pady=5)
# Sign Up button
signup_button = tk.Button(signup_window, text="Sign Up", command=lambda: self.signup(username_entry.get(), password_entry.get(), code_entry.get()))
signup_button.pack(pady=10)
# Back to menu button
back_button = tk.Button(signup_window, text="Back", command=self.show_menu)
back_button.pack()
def signup (self, username, password, code):
required_code = "1234" # The required code for sign-up
if code != required_code:
tk.messagebox.showerror("Error", "Invalid sign-up code.")
return
if any(user.username == username for user in self.users):
tk.messagebox.showerror("Error", "Username already exists.")
return
new_user = User(username, password)
self.users.append(new_user)
User.save_users(self.users)
tk.messagebox.showinfo("Success", "User signed up successfully!")
def clear_frame(self):
# Remove all widgets from the frame
for widget in self.root.winfo_children():
widget.destroy()
def open_login_window(self):
login_window = tk.Toplevel(self.root)
login_window.title("Login")
login_window.geometry("300x200")
self.center_window(login_window, 210, 220)
# Username Entry
username_label = tk.Label(login_window, text="Username:")
username_label.pack(pady=5)
username_entry = tk.Entry(login_window)
username_entry.pack(pady=5)
# Password Entry
password_label = tk.Label(login_window, text="Password:")
password_label.pack(pady=5)
password_entry = tk.Entry(login_window, show="*")
password_entry.pack(pady=5)
# Login Button
login_button = tk.Button(login_window, text="Login", command=lambda: self.login_user(username_entry.get(), password_entry.get(), login_window))
login_button.pack(pady=10)
# Cancel Button
cancel_button = tk.Button(login_window, text="Cancel", command=login_window.destroy)
cancel_button.pack(pady=5)
def login_user(self, username, password, login_window):
users = User.load_users() # Load users from the JSON file
for user in users:
if user.username == username and user.password == password:
tk.messagebox.showinfo("Success", "Login successful!")
login_window.destroy() # Close the login window
self.access_app() # Proceed to the next screen
return
tk.messagebox.showerror("Error", "Invalid username or password.")
def access_app(self):
self.clear_frame()
self.create_widgets()
def create_widgets(self):
# Input
self.name_var = tk.StringVar()
self.price_var = tk.DoubleVar()
self.cost_lb_var = tk.DoubleVar()
self.cost_pm_var = tk.DoubleVar()
self.weight_var = tk.DoubleVar()
self.cost_type = tk.StringVar(value="L&B")
self.search_var = tk.StringVar()
self.weight_unit = tk.StringVar(value="lbs")
tk.Label(self.root, text="Name:").grid(row=0, column=0)
tk.Entry(self.root, textvariable=self.name_var).grid(row=0, column=1)
tk.Button(self.root, text="Add", command=self.add_product).grid(row=0, column=2)
tk.Label(self.root, text="Price:").grid(row=1, column=0)
tk.Entry(self.root, textvariable=self.price_var).grid(row=1, column=1)
tk.Button(self.root, text="Delete", command=self.delete_product).grid(row=1, column=2)
tk.Label(self.root, text="L&B Cost:").grid(row=2, column=0)
tk.Entry(self.root, textvariable=self.cost_lb_var).grid(row=2, column=1)
tk.Button(self.root, text="Update", command=self.update_product).grid(row=2, column=2)
tk.Label(self.root, text="Pont-Masson Cost:").grid(row=3, column=0)
tk.Entry(self.root, textvariable=self.cost_pm_var).grid(row=3, column=1)
tk.Label(self.root, text="Weight (lbs):").grid(row=4, column=0)
tk.Entry(self.root, textvariable=self.weight_var).grid(row=4, column=1)
# Weight unit selection
tk.Label(self.root, text="Weight Unit:").grid(row=4, column=2)
tk.OptionMenu(self.root, self.weight_unit, "lbs", "kg").grid(row=4, column=3)
# Cost type selection
tk.Label(self.root, text="Select Cost Type for Margin:").grid(row=5, column=0, columnspan=2)
tk.OptionMenu(self.root, self.cost_type, "L&B", "Pont-Masson").grid(row=6, column=0, columnspan=2)
# Search
tk.Label(self.root, text="Search:").grid(row=7, column=0)
tk.Entry(self.root, textvariable=self.search_var).grid(row=7, column=1)
tk.Button(self.root, text="Search", command=self.search_products).grid(row=7, column=2)
# Refresh button
tk.Button(self.root, text="Refresh", command=self.show_products).grid(row=8, column=0, columnspan=3, sticky="w")
# Clear button
tk.Button(self.root, text="Clear", command=self.clear_selection).grid(row=3, column=2)
# Compare Prices button
tk.Button(self.root, text="Compare Prices", command=self.open_price_comparison).grid(row=8, column=3)
# Treeview for displaying products
self.tree = ttk.Treeview(self.root, columns=("Name", "Cost", "Price", "Gain", "Margin", "Weight"), show='headings')
self.tree.heading("Name", text="Name")
self.tree.heading("Cost", text="Cost")
self.tree.heading("Price", text="Price")
self.tree.heading("Gain", text="Gain")
self.tree.heading("Margin", text="Margin (%)")
self.tree.heading("Weight", text="Weight")
# Column centering
for col in ("Cost", "Price", "Gain", "Margin", "Weight"):
self.tree.column(col, anchor='center')
self.tree.grid(row=9, column=0, columnspan=5)
self.tree.bind("<<TreeviewSelect>>", self.on_product_select) # Bind selection event
# Show products
self.show_products()
def add_product(self):
name = self.name_var.get()
try:
price = self.price_var.get()
cost_lb = self.cost_lb_var.get()
cost_pm = self.cost_pm_var.get()
weight = self.weight_var.get()
if not name:
raise ValueError("Product name cannot be empty.")
if price < 0 or cost_lb < 0 or cost_pm < 0 or weight < 0:
raise ValueError("Price and costs must be non-negative.")
self.store.add_product(name, price, cost_lb, cost_pm, weight)
self.clear_entries()
self.show_products()
except ValueError as e:
messagebox.showerror("Error", str(e))
def delete_product(self):
selected_item = self.tree.selection()
if not selected_item:
messagebox.showwarning("Select Product", "Please select a product to delete.")
return
item_id = selected_item[0]
product_name = self.tree.item(item_id, "values")[0]
confirm_delete = messagebox.askyesno("Confirm Delete", f"Are you sure you want to delete '{product_name}'?")
if confirm_delete:
self.store.delete_product(product_name)
messagebox.showinfo("Success", f"Product '{product_name}' deleted.")
self.clear_entries()
self.show_products()
def update_product(self):
selected_item = self.tree.selection()
if not selected_item:
messagebox.showwarning("Select Product", "Please select a product to update.")
return
item_id = selected_item[0]
product_name = self.tree.item(item_id, "values")[0]
price = self.price_var.get() or None
cost_lb = self.cost_lb_var.get() or None
cost_pm = self.cost_pm_var.get() or None
weight = self.weight_var.get() or None
self.store.update_product(product_name, price, cost_lb, cost_pm, weight)
messagebox.showinfo("Success", f"Product '{product_name}' updated.")
self.clear_entries()
self.show_products()
def show_products(self):
for item in self.tree.get_children():
self.tree.delete(item)
for product in self.store.products:
use_cost_lb = self.cost_type.get() == "L&B"
cost = product.cost_lb if use_cost_lb else product.cost_pm
gain = product.gain(use_cost_lb)
margin = product.margin(use_cost_lb)
# Convert weight based on selection
weight_display = product.weight
if self.weight_unit.get() == "kg":
weight_display = weight_display * 0.453592 # Convert lbs to kg
self.tree.insert("", "end", values=(
product.name,
f"${cost:.2f}",
f"${product.price:.2f}",
f"${gain:.2f}",
f"{margin:.2f}%",
f"{weight_display:.2f} {self.weight_unit.get()}"
))
def search_products(self):
search_term = self.search_var.get().lower()
for item in self.tree.get_children():
self.tree.delete(item)
for product in self.store.products:
if search_term in product.name.lower():
use_cost_lb = self.cost_type.get() == "L&B"
cost = product.cost_lb if use_cost_lb else product.cost_pm
gain = product.gain(use_cost_lb)
margin = product.margin(use_cost_lb)
weight_display = product.weight
if self.weight_unit.get() == "kg":
weight_display = weight_display * 0.453592
self.tree.insert("", "end", values=(
product.name,
f"${cost:.2f}",
f"${product.price:.2f}",
f"${gain:.2f}",
f"{margin:.2f}%",
f"{weight_display:.2f} {self.weight_unit.get()}"
))
def clear_entries(self):
self.name_var.set("")
self.price_var.set(0)
self.cost_lb_var.set(0)
self.cost_pm_var.set(0)
self.weight_var.set(0)
def clear_selection(self):
self.tree.selection_remove(self.tree.selection()) # Deselect any selected item
self.clear_entries()
def on_product_select(self, event):
selected_item = self.tree.selection()
if not selected_item:
return
item_id = selected_item[0]
product_data = self.tree.item(item_id, "values")
# Order of values in product_data is (Name, Cost, Price, Gain, Margin, Weight)
self.name_var.set(product_data[0]) # Set product name
self.cost_lb_var.set(float(product_data[1][1:])) # L&B Cost (remove '$' sign)
self.price_var.set(float(product_data[2][1:])) # Price (remove '$' sign)
self.cost_pm_var.set(float(product_data[1][1:])) # Pont-Masson Cost (remove '$' sign)
self.weight_var.set(float(product_data[5].split()[0])) # Weight (extract value)
def open_price_comparison(self):
selected_item = self.tree.selection()
if not selected_item:
messagebox.showwarning("Select Product", "Please select a product to compare prices.")
return
item_id = selected_item[0]
product_name = self.tree.item(item_id, "values")[0]
product = next((p for p in self.store.products if p.name == product_name), None)
if product:
PriceComparisonWindow(self.root, product)
class PriceComparisonWindow:
def __init__(self, parent, product):
self.top = tk.Toplevel(parent)
self.top.title(f"Price Comparison for {product.name}")
self.product = product
# Store labels and entry fields for each store
self.store_vars = {
"Rona": tk.DoubleVar(value=self.product.store_prices.get("Rona", 0)),
"Home Depot": tk.DoubleVar(value=self.product.store_prices.get("Home Depot", 0)),
"Patrick Morin": tk.DoubleVar(value=self.product.store_prices.get("Patrick Morin", 0)),
"BMR": tk.DoubleVar(value=self.product.store_prices.get("BMR", 0)),
}
tk.Label(self.top, text="Store Prices").grid(row=0, column=0, columnspan=2)
for idx, (store, var) in enumerate(self.store_vars.items()):
tk.Label(self.top, text=store).grid(row=idx + 1, column=0)
entry = tk.Entry(self.top, textvariable=var)
entry.grid(row=idx + 1, column=1)
entry.bind("<KeyRelease>", self.update_price_info) # Update on key release
tk.Button(self.top, text="Save", command=self.save_prices).grid(row=len(self.store_vars) + 1, columnspan=2)
# Labels for comparison results
self.comparison_labels = {}
for idx, store in enumerate(self.store_vars.keys()):
label = tk.Label(self.top, text="")
label.grid(row=len(self.store_vars) + 2 + idx, columnspan=2)
self.comparison_labels[store] = label
# Label for cheapest store
self.cheapest_store_label = tk.Label(self.top, text="", font=('Arial', 10, 'bold'))
self.cheapest_store_label.grid(row=len(self.store_vars) + len(self.comparison_labels) + 2, columnspan=2)
# Label for most expensive store
self.most_expensive_store_label = tk.Label(self.top, text="", font=('Arial', 10, 'bold'))
self.most_expensive_store_label.grid(row=len(self.store_vars) + len(self.comparison_labels) + 3, columnspan=2)
self.update_price_info()
def save_prices(self):
for store, var in self.store_vars.items():
self.product.store_prices[store] = var.get()
messagebox.showinfo("Success", "Prices saved successfully.")
self.update_price_info()
def update_price_info(self, event=None):
prices = {store: var.get() for store, var in self.store_vars.items()}
# Update comparison labels
for store, price in prices.items():
if self.product.price < price:
comparison_text = f"Your price is Lower than {store}"
elif self.product.price > price:
comparison_text = f"Your price is Higher than {store}"
else:
comparison_text = f"Your price is the Same as {store}"
self.comparison_labels[store].config(text=comparison_text)
# Calculate the cheapest store
cheapest_store = min(prices, key=prices.get)
cheapest_price = prices[cheapest_store]
self.cheapest_store_label.config(text=f"Cheapest Store: {cheapest_store} - ${cheapest_price:.2f}")
# Calculate the most expensive store
most_expensive_store = max(prices, key=prices.get)
most_expensive_price = prices[most_expensive_store]
self.most_expensive_store_label.config(text=f"Most Expensive Store: {most_expensive_store} - ${most_expensive_price:.2f}")
if __name__ == "__main__":
root = tk.Tk()
app = StoreApp(root)
root.mainloop()
Two window problem is caused by the library architecture. Library automatically creates root window and everything built top of it. If you remove it, close it all gui applications will be likely to make unexpected behaviours. So you can make a frame inside root window, populate it with the login screen elements. After user logged in, delete or clear frame and pass through second phase.
Your other problem's solution is here.
this function gets data from json file of given product.
def get_product_data_db(self, product_name):
with open("products.json","r") as f:
text = f.read()
product_list = json.loads(text)
for product in product_list:
if(product["name"] == product_name):
return product
and your on_product_select function should be refined as this.
def on_product_select(self, event):
selected_item = self.tree.selection()
if not selected_item:
return
item_id = selected_item[0]
selected_product = self.tree.item(item_id, "values")
product_data = self.get_product_data_db(selected_product[0])
self.name_var.set(product_data["name"])
self.cost_lb_var.set(product_data["cost_lb"])
self.price_var.set(product_data["price"])
self.cost_pm_var.set(product_data["cost_pm"])
self.weight_var.set(product_data["weight"])
So you get data from folder and assign them individually.