This is kinda long and I don’t really expect anyone to look at it, but if they do and have any comments, critiques, questions that’d be great. I wrote this little python (3) program to take a csv file dowload from a woocommerce/wordpress store that I’m using for kroshipkin so I could change the prices of a bunch of items (the T-shirts) all at once. It has a GUI using tk/tcl (tkinter). You can search for a subset of items and then change them. Then save it back into a csv file to reupload to the store. I’ll add onto this as needed. I tried to make it like modular or something. That’s why I made the save function a callback that is sent in with the data because maybe some other code would save the result somewhere else.
#! /usr/bin/python
import tkinter as tk
import os, csv, copy
from collections import OrderedDict
_filename = 'temp'
class Product_manager(tk.Frame):
def __init__(self, root, **kwargs):
tk.Frame.__init__(self, root)
self.close_button = tk.Button(root, text="Goodbye", command=root.destroy)
self.close_button.pack()
class Popup():
def __init__(self, products=None, save_callback=None):
self.products = copy.deepcopy(products)
self.input_products = copy.deepcopy(products)
self.save_callback = save_callback
#lay the items out on the popup window
win = tk.Toplevel()
win.geometry("700x400")
win.title("title of popup")
self.canvas = tk.Canvas(win, width=100, height=200, borderwidth=0,
background='#ffffff')
self.frame = tk.Frame(self.canvas, background='#ffffff')
self.vsb = tk.Scrollbar(self.canvas, orient='vertical',
command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
tk.Button(win, text='filter', borderwidth='1', relief='solid',
command=self.filter_products).pack(side='top', fill='x')
self.filter_entry = tk.Entry(win, width="50")
self.filter_entry.pack()
tk.Button(win, text="Set price for all", borderwidth='1', relief='solid',
command=self.set_price).pack(side='top', fill='x')
self.price_entry = tk.Entry(win, width="50")
self.price_entry.pack()
tk.Button(win, text="Save", borderwidth='1', relief='solid',
command=self.save).pack(side='top', fill='x')
self.vsb.pack(side='right', fill='y')
self.canvas.pack(side='left', fill='both', expand=True)
self.canvas.create_window((4,4), window=self.frame, anchor='nw',
tags='self.frame')
self.frame.bind('<Configure>', self.onFrameConfigure)
self.populate()
def populate(self):
row = 0
for key, product in self.products.items():
tk.Label(self.frame, text='%s' % row, width=3, borderwidth='1',
relief='solid').grid(row=row, column=0)
self.t = f"Name of product is {product['Name']}"
tk.Label(self.frame, text=self.t).grid(row=row, column=1, sticky='w')
self.t = f"Price is {product['Regular price']}"
tk.Label(self.frame, text=self.t).grid(row=row, column=2, sticky='w')
row += 1
def onFrameConfigure(self, event):
'''Reset the scroll region to encompass the innter frame'''
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
def set_price(self):
updated_products = {}
for key, product in self.products.items():
product['Regular price'] = self.price_entry.get()
updated_products[key] = product
self.products = updated_products
self.redraw_popup()
def filter_products(self):
new_products = {}
for key, product in self.products.items():
if self.filter_entry.get() in product['Name']:
new_products[key] = product
self.products = new_products
self.redraw_popup()
def redraw_popup(self):
for item in self.frame.winfo_children():
item.destroy()
self.populate()
def save(self):
self.save_callback(self)
def save_callback(Products_obj):
'''
I will probably add tags for whether I'm replacing or updating
the whole file and perhaps do that in other functions, but for
now this is all the functionality I need
'''
#save/replace file
#construct the replacement dictionary
replacement_products = []
for key, product in Products_obj.input_products.items():
this_product = {}
try:
this_product = copy.deepcopy(Products_obj.products[key])
except:
this_product = copy.deepcopy(Products_obj.input_products[key])
replacement_products.append(this_product)
#save it as the file
with open(_filename, 'w', encoding='UTF-8-sig') as csvfile:
writer = csv.DictWriter(csvfile, replacement_products[0].keys())
writer.writeheader()
for p in replacement_products:
writer.writerow(p)
def option_callback(*args):
#open the popup to make changes to the selected file
global _filename
_filename = option.get()
products = get_products(_filename)
Popup(products, save_callback)
def get_products(filename):
with open(filename, 'r', encoding='UTF-8-sig') as csvfile:
dict_reader = csv.DictReader(csvfile)
products = {}
for row in dict_reader:
product = OrderedDict()
for key, val in row.items():
product[key] = val
products[int(product['ID'])] = product
return products
if __name__ == '__main__':
root = tk.Tk()
root.geometry("400x200")
root.title("Kroshopkin Product Manager")
#get a list of csv files in the directory
file_list = os.listdir('.')
csv_list = []
for file in file_list:
file_name, file_extension = os.path.splitext(file)
if file_extension == '.csv':
csv_list.append(file)
option = tk.StringVar(root)
option.set(csv_list[0])
x = tk.OptionMenu(root, option, *csv_list)
x.pack()
#execute callback upon selection of file
option.trace("w", option_callback)
#open root window
Product_manager(root, csv_list=csv_list)
root.mainloop()
Here’s a sample of what the csv file looks like
ID,Type,SKU,Name,Published,Is featured?,Visibility in catalog,Short description,Description,Date sale price starts,Date sale price ends,Tax status,Tax class,In stock?,Stock,Low stock amount,Backorders allowed?,Sold individually?,Weight (oz),Length (in),Width (in),Height (in),Allow customer reviews?,Purchase note,Sale price,Regular price,Categories,Tags,Shipping class,Images,Download limit,Download expiry days,Parent,Grouped products,Upsells,Cross-sells,External URL,Button text,Position,Meta: _wcsquare_disable_sync,Attribute 1 name,Attribute 1 value(s),Attribute 1 visible,Attribute 1 global,Attribute 2 name,Attribute 2 value(s),Attribute 2 visible,Attribute 2 global
10,simple,,Gently Used Bridge,1,0,visible,"This is an older bridge. It is in good condition, but sale is As-Is with no guarantee. Shipping is not included.",Used bridge.,,,taxable,,0,0,,0,0,,,,,1,,,99.99,Infrastructure,,,http://kroshopkin.com/wp-content/uploads/2019/11/bridge.jpg,,,,,,,,,0,no,,,,,,,,
32,simple,,Solar Panel,1,0,visible,"Solar Panel 140watts (used)
\n
\nThis item is for sale by a private party, not a worker cooperative as there is no real worker here. At least there's no boss, so close enough for a demo item. This is here as a test, but the item is really for sale!","Product is located in the Los Angeles area (South Bay) to be picked up or possibly will be delivered if convenient.
\n
\n<img class=""alignnone size-full wp-image-34"" src=""http://kroshopkin.com/wp-content/uploads/2019/12/solarnameplate.jpg"" alt="""" width=""600"" height=""800"" />
\n
\n ",,,none,zero-rate,1,,,0,0,,,,,1,,,44.44,Electronics,,,http://kroshopkin.com/wp-content/uploads/2019/12/solarpanel.jpg,,,,,,,,,0,no,,,,,,,,
35,simple,,Well wishes,1,0,visible,"One well wish, intended for testing.","Purchase this and we at Kroshopkin.com will wish you well. That's it. I'm really setting this up as a test, but if you do go ahead and purchase these wishes, that's what you'll get. No promises that wishes will come true.",,,taxable,,1,,,0,0,,,,,1,,,0.50,Uncategorized,,,,,,,,,,,,0,no,,,,,,,,