Well, this has been quite an undertaking:
I said I would post last night, but there was too many loose ends.
There are still several methods here that don't work correctly and
it may take a while longer to complete.
Right now, about the only thing that seems to be solid is the add function
deletes are almost working, but not there yet.
I thought I'd post anyway so that you can start to get an idea of what's what.
As I stated before, the self.people dictionary will need to be replaced by a relational
database, suggest sqlite3. This will eliminate the json file. As is, so long as you have a reasonable
amount of memory it will hold quite a few people, before memory runs out.
the first module must be named 'MakeStateFile.py' as it is imported by the main script
I said I would post last night, but there was too many loose ends.
There are still several methods here that don't work correctly and
it may take a while longer to complete.
Right now, about the only thing that seems to be solid is the add function
deletes are almost working, but not there yet.
I thought I'd post anyway so that you can start to get an idea of what's what.
As I stated before, the self.people dictionary will need to be replaced by a relational
database, suggest sqlite3. This will eliminate the json file. As is, so long as you have a reasonable
amount of memory it will hold quite a few people, before memory runs out.
the first module must be named 'MakeStateFile.py' as it is imported by the main script
import json import os class MakeStateFiles: def __init__(self): self.states = { 'USA': { 'Alabama': 'AL', 'Alaska': 'AK', 'Arizona': 'AZ', 'Arkansas': 'AR', 'California': 'CA', 'Colorado': 'CO', 'Connecticut': 'CT', 'Delaware': 'DE', 'District of Columbia': 'DC', 'Florida': 'FL', 'Georgia': 'GA', 'Hawaii': 'HI', 'Idaho': 'ID', 'Illinois': 'IL', 'Indiana': 'IN', 'Iowa': 'IA', 'Kentucky': 'KY', 'Kansas': 'KS', 'Louisiana': 'LA', 'Maine': 'ME', 'Maryland': 'MD', 'Massachusetts': 'MA', 'Michigan': 'MI', 'Mississippi': 'MS', 'Missouri': 'MO', 'Montana': 'MT', 'Nebraska': 'NE', 'Nevada': 'NV', 'New Hampshire': 'NH', 'New Jersey': 'NJ', 'New Mexico': 'NM', 'New York': 'NY', 'North Carolina': 'NC', 'North Dakota': 'ND', 'Ohio': 'OH', 'Oklahoma': 'OK', 'Oregon': 'OR', 'Pennsylvania': 'PA', 'Rhode Island': 'RI', 'South Carolina': 'SC', 'South Dakota': 'SD', 'Tennessee': 'TN', 'Texas': 'TX', 'Utah': 'UT', 'Vermont': 'VT', 'Virginia': 'VA', 'Washington': 'WA', 'West Virginia': 'WV', 'Wisconsin': 'WI', 'Wyoming': 'WY' }, 'Canada': { 'Alberta': 'AB', 'British Columbia': 'BC', 'Manitoba': 'MB', 'New Brunswick': 'NB', 'Newfoundland': 'NF', 'Northwest Territory': 'NT', 'Nova Scotia': 'NS', 'Nunavut': 'NU', 'Ontario': 'ON', 'Prince Edward Island': 'PE', 'Quebec': 'QC', 'Saskatchewan': 'SK', 'Yukon Territory': 'YT' }, 'Mexico': { 'Aguascalientes': 'AG', 'Baja California': 'BJ', 'Baja California Sur': 'BS', 'Campeche': 'CP', 'Chiapas': 'CH', 'Chihuahua': 'CI', 'Coahuila': 'CU', 'Colima': 'CL', 'Distrito Federal': 'DF', 'Durango': 'DG', 'Guanajuato': 'GJ', 'Guerrero': 'GR', 'Hidalgo': 'HG', 'Jalisco': 'JA', 'Mexico': 'EM', 'Michoacan': 'MH', 'Morelos': 'MR', 'Nayarit': 'NA', 'Nuevo': 'Leon NL', 'Oaxaca': 'OA', 'Puebla': 'PU', 'Queretaro': 'QA', 'Quintana': 'Roo QR', 'San Luis Potosi': 'SL', 'Sinaloa': 'SI', 'Sonora': 'SO', 'Tabasco': 'TA', 'Tamaulipas': 'TM', 'Tlaxcala': 'TL', 'Veracruz': 'VZ', 'Yucatan': 'YC', 'Zacatecas': 'ZT', } } self.stup = ('USA','Alabama','Alaska','Arizona','Arkansas','California','Colorado', 'Connecticut','Delaware','District of Columbia', 'Florida','Georgia','Hawaii', 'Idaho','Illinois','Indiana','Iowa','Kentucky','Kansas','Louisiana','Maine', 'Maryland','Massachusetts','Michigan','Mississippi','Missouri','Montana', 'Nebraska','Nevada','New Hampshire','New Jersey','New Mexico','New York', 'North Carolina','North Dakota','Ohio','Oklahoma','Oregon','Pennsylvania', 'Rhode Island','South Carolina','South Dakota','Tennessee','Texas','Utah', 'Vermont','Virginia','Washington','West Virginia','Wisconsin','Wyoming', ' ','Canada', ' ', 'Alberta','British Columbia','Manitoba','New Brunswick', 'Newfoundland','Northwest Territory','Nova Scotia','Nunavut','Ontario', 'Prince Edward Island','Quebec','Saskatchewan','Yukon Territory',' ','Mexico', ' ','Aguascalientes','Baja California', 'Baja California Sur','Campeche', 'Chiapas','Chihuahua','Coahuila','Colima','Distrito Federal', 'Durango', 'Guanajuato','Guerrero','Hidalgo','Jalisco', 'Mexico',' ', 'Michoacan', 'Morelos','Nayarit','Nuevo','Oaxaca','Puebla','Queretaro','Quintana', 'San Luis Potosi','Sinaloa','Sonora', 'Tabasco','Tamaulipas','Tlaxcala', 'Veracruz','Yucatan','Zacatecas') datapath = os.path.abspath('.') datapath = '{}\data'.format(datapath) if not os.path.exists(datapath): os.makedirs(datapath) with open('data/States.json', 'w') as state_file: json.dump(self.states, state_file) with open('data/StateTuples.json', 'w') as state_tuples: json.dump(self.stup, state_tuples) if __name__ == '__main__': MakeStateFiles() state_dict = {} with open('data/States.json', 'r') as f: state_dict = json.load(f) # Test dictionary for key, value in state_dict.items(): for state_name, state_abbr in value.items(): print(f'country: {key}, state_abbr: {state_abbr} state_name: {state_name}')the main program can be named whatever you like:
import tkinter as tk import tkinter.ttk as ttk import tkinter.messagebox as tm import MakeStateFile as msf import os import json import re class ShoeBot: def __init__(self, parent): self.parent = parent self.parent.title('AWD ShoeBot') datapath = os.path.abspath('.') datapath = '{}\data'.format(datapath) if not os.path.exists(datapath): msf.MakeStateFiles() self.tree_rows = 18 self.left_bottom = self.tree_rows + 2 self.dup_tree_rows = 10 self.textbox_rows = 14 self.treestyle = ttk.Style() self.treestyle.configure('Treeview', foreground='white', borderwidth=2, background='SteelBlue', rowheight=self.tree_rows, height=self.tree_rows) self.filename = os.path.abspath('data/PeopleFile.json') self.fp = None self.people = None self.person_found = None if os.path.isfile(self.filename): with open(self.filename) as people_file: self.people = json.load(people_file) else: self.people = {} self.changes_made = False self.this_person = None with open('data/StateTuples.json') as state_tuples_file: self.states = json.load(state_tuples_file) print(f'self.states: {self.states}') with open('data/States.json') as state_dict_file: self.state_dict = json.load(state_dict_file) print(f'self.state_dict: {self.state_dict}') self.selected_person = None self.last_name = tk.StringVar() self.first_name = tk.StringVar() self.address1 = tk.StringVar() self.address2 = tk.StringVar() self.city = tk.StringVar() self.state_name = tk.StringVar() self.state_abbr = tk.StringVar() self.country = tk.StringVar() self.zipcode = tk.StringVar() self.shoe_size = tk.StringVar() self.this_item = None self.build_gui() self.load_tree() def build_gui(self): self.create_main_frame() self.create_left_frame() self.create_right_frame() self.create_bottom_frame() self.create_tree() self.create_textbox() self.create_user_dialog() self.create_statusbar() self.parent.protocol("WM_DELETE_WINDOW", self.on_delete) def create_main_frame(self): self.main_frame = tk.Frame(self.parent, bd=2, padx=4, pady=4, relief=tk.RAISED) self.main_frame.grid(row=0, column=0, sticky='nseww') self.main_frame.columnconfigure(0, weight=1) self.main_frame.rowconfigure(0, weight=1) self.main_frame.pack(pady=5, padx=5) def create_left_frame(self): self.left_frame = tk.Frame(self.main_frame, bd=2, padx=4, pady=4, relief=tk.SUNKEN) self.left_frame.grid(row=0, rowspan=self.tree_rows, column=0, columnspan=2, sticky='ns') def create_right_frame(self): self.right_frame = tk.Frame(self.main_frame, bd=2, padx=4, pady=4, relief=tk.SUNKEN) self.right_frame.grid(row=0, rowspan=self.tree_rows, column=2, columnspan=10, sticky='ns') def create_bottom_frame(self): self.bottom_frame = tk.Frame(self.main_frame, bd=2, padx=4, pady=4, relief=tk.SUNKEN) self.bottom_frame.grid(row=self.tree_rows + 1, column=0, columnspan=11, sticky='ew') def create_tree(self): self.tree = ttk.Treeview(self.left_frame, height=self.tree_rows, padding=(2, 2, 2, 2), columns=1, selectmode="extended", style='Treeview') self.tree.heading('#0', text='Person, Name, address', anchor='w') self.tree.column('#0', stretch=tk.NO, width=400) self.tree.tag_configure('monospace', font='courier') treescrolly = tk.Scrollbar(self.left_frame, orient=tk.VERTICAL, command=self.tree.yview) treescrollx = tk.Scrollbar(self.left_frame, orient=tk.HORIZONTAL, command=self.tree.xview) self.tree.grid(row=0, rowspan=self.tree_rows, column=0, sticky='nsew') treescrolly.grid(row=0, rowspan=self.tree_rows, column=1, sticky='ns') treescrollx.grid(row=self.tree_rows + 1, column=0, columnspan=2, sticky='ew') self.tree.configure(yscroll=treescrolly) self.tree.configure(xscroll=treescrollx) self.tree.bind('<Double-1>', self.name_selected) def create_textbox(self): self.textbox = tk.Text(self.left_frame, bd=2, bg='#CEF6EC', relief=tk.RAISED, width=20, height=self.textbox_rows) txscrolly = tk.Scrollbar(self.left_frame, orient=tk.VERTICAL, command=self.textbox.yview) txscrollx = tk.Scrollbar(self.left_frame, orient=tk.HORIZONTAL, command=self.textbox.xview) txscrollx.config(command=self.textbox.xview) txscrolly.config(command=self.textbox.yview) self.textbox.grid(row=self.left_bottom, rowspan=self.textbox_rows, column=0, padx=2, pady=2, sticky='nsew') txscrolly.grid(row=self.left_bottom, rowspan=self.textbox_rows, column=1, sticky='ns') txscrollx.grid(row=self.left_bottom + self.textbox_rows, column=0, columnspan=2, sticky='ew') self.textbox.insert('end', 'Test message:\n') def create_user_dialog(self): self.intruction = tk.Label(self.right_frame, text='Enter Information Below:\n') self.intruction.grid(row=0, column=0, sticky='nse') self.first_name_l = tk.Label(self.right_frame, text='First Name: ') self.first_name_e = tk.Entry(self.right_frame, textvariable=self.first_name) self.first_name_l.grid(row=1, column=0, sticky='w') self.first_name_e.grid(row=1, column=1, columnspan=8, sticky='ew') self.last_name_l = tk.Label(self.right_frame, text='Last Name: ') self.last_name_e = tk.Entry(self.right_frame, textvariable=self.last_name) self.last_name_l.grid(row=2, column=0, sticky='w') self.last_name_e.grid(row=2, column=1, columnspan=8, sticky='ew') self.address1_l = tk.Label(self.right_frame, text='Address 1: ') self.address1_e = tk.Entry(self.right_frame, textvariable=self.address1) self.address1_l.grid(row=3, column=0, sticky='w') self.address1_e.grid(row=3, column=1, columnspan=8, sticky='ew') self.address2_l = tk.Label(self.right_frame, text='Address 2: ') self.address2_e = tk.Entry(self.right_frame, textvariable=self.address2) self.address2_l.grid(row=4, column=0, sticky='w') self.address2_e.grid(row=4, column=1, columnspan=8, sticky='ew') self.city_l = tk.Label(self.right_frame, text='City: ') self.city_e = tk.Entry(self.right_frame, textvariable=self.city) self.city_l.grid(row=5, column=0, sticky='w') self.city_e.grid(row=5, column=1, columnspan=2, sticky='ew') self.state_l = tk.Label(self.right_frame, text='State: ') self.state_e = ttk.Combobox(self.right_frame, textvariable=self.state_name) self.state_e['values'] = self.states self.state_l.grid(row=5, column=3, sticky='w') self.state_e.grid(row=5, column=4, columnspan=2, sticky='ew') self.state_e.bind("<<ComboboxSelected>>", self.set_state) self.state_e.bind("<FocusIn>", self.set_focus) self.zipcode_l = tk.Label(self.right_frame, text='Zip Code: ') self.zipcode_e = tk.Entry(self.right_frame, textvariable=self.zipcode) self.zipcode_l.grid(row=6, column=0, sticky='w') self.zipcode_e.grid(row=6, column=1, sticky='w') self.country_l = tk.Label(self.right_frame, text='Country: ') self.country_e = tk.Entry(self.right_frame, textvariable=self.country) self.country_l.grid(row=7, column=0, sticky='w') self.country_e.grid(row=7, column=1, sticky='w') self.country_e.configure(state='readonly') self.shoe_size_l = tk.Label(self.right_frame, text='Shoe Size: ') self.shoe_size_e = tk.Entry(self.right_frame, textvariable=self.shoe_size) self.shoe_size_l.grid(row=8, column=0, sticky='w') self.shoe_size_e.grid(row=8, column=1, sticky='w') self.spacer1 = ttk.Separator(self.right_frame) self.spacer1.grid(row=9, rowspan=2, column=0) create_button = tk.Button(self.right_frame, text='Create', command=self.add_person) create_button.grid(row=11, column=0, sticky='w') cancel_button = tk.Button(self.right_frame, text='Cancel', command=self.cancel) cancel_button.grid(row=11, column=1, sticky='w') remove_button = tk.Button(self.right_frame, text='Delete') remove_button.grid(row=11, column=2, sticky='w') remove_button.bind('<Button-1>', self.name_selected) self.spacer2 = ttk.Separator(self.right_frame) self.spacer2.grid(row=12, rowspan=2, column=0) self.dup_tree = ttk.Treeview(self.right_frame, height=self.tree_rows, padding=(2, 2, 2, 2), columns='Name', selectmode="extended", style='Treeview') self.dup_tree.heading('#0', text='Duplicate Name Selection') self.dup_tree.column('#0', stretch=tk.YES, width=200) self.dup_tree.tag_configure('monospace', font='courier') self.dw_treescrolly = tk.Scrollbar(self.right_frame, orient=tk.VERTICAL, command=self.dup_tree.yview) self.dw_treescrollx = tk.Scrollbar(self.right_frame, orient=tk.HORIZONTAL, command=self.dup_tree.xview) self.dup_tree.configure(yscroll=self.dw_treescrolly) self.dup_tree.configure(xscroll=self.dw_treescrollx) def hide_dup_tree(self): self.dup_tree.forget() self.dw_treescrolly.forget() self.dw_treescrollx.forget() self.dup_tree.unbind('<Double-1>') def show_dup_tree(self): self.dup_tree.grid(row=14, rowspan=self.dup_tree_rows, column=0, columnspan=6, sticky='nsew') self.dw_treescrolly.grid(row=14, rowspan=self.dup_tree_rows, column=6, sticky='ns') self.dw_treescrollx.grid(row=self.dup_tree_rows + 14, column=0, columnspan=6, sticky='ew') self.dup_tree.bind('<Double-1>', self.person_selected) def create_key(self, fname, lname): return f'{lname}{fname}' def name_selected(self, event): # todo - new indexing origin = str(event.widget) widget = event.widget fromtree = False address1 = '' if '!treeview' in origin: curitem = self.tree.focus() nidx = self.tree.item(curitem) entry = nidx['text'] # hack to get address from Treeview element # If anything is added to the Treeview elements, the following code will have to be changed address1 = entry[(entry.index('street address:') + 16):] # look for address hone in on proper dictionary entry fromtree = True else: curitem = self.button.focus() nidx = self.tree.item(curitem) entry = nidx['text'] fields = re.split('[, \W]+', entry) fname = fields[0] lname = fields[1] key = self.create_key(fname, lname) people = self.people[key] print(f'people: {people}') self.selected_person = None last_seq = int(people['last_seq']) if last_seq > 0: cur_seq = 0 while(cur_seq <= last_seq): person = people[str(cur_seq)] if fromtree: if person['address1'] != address1: self.add_to_dups(person) else: self.add_to_dups(person) cur_seq += 1 self.show_dup_tree() else: self.selected_person = people['0'] # self.display_person(selected_person) # else: # tm.showerror('name_selected', '{} {} not found'.format(fname, lname)) def person_selected(): pass def select_person_from_dups(self, event): curitem = self.dup_tree.focus() nidx = self.tree.item(curitem) entry = nidx['text'] key = self.create_key(entry['fname'], entry['lname']) # return self.people[key] def display_person(self, person): self.clear_entry() self.first_name.set(person['fname']) self.last_name.set(person['lname']) self.address1.set(person['address1']) self.address2.set(person['address2']) self.city.set(person['city']) self.state_name.set(person['state_name']) self.state_abbr.set(person['state_abbr']) self.country.set(person['state_name']) self.shoe_size.set(person['shoesize']) self.zipcode.set(person['zip']) self.parent.update_idletasks() def add_to_dups(self, person): # Give enough information to make delete decision # shoesize because father & son may have same name, address, and hopefully different shoe size ptext = 'seq: {}, {} {}, {}, {}, {} -- shoe size: {}'\ .format(person['seq'], person['fname'], person['lname'], person['address1'], person['city'], person['state_abbr'], person['shoesize']) self.dup_tree.insert('', 'end', text=ptext) def remove_person(self, event): # todo - populate dup entry tree # todo use new indexing # todo - determine how this method was called, from clicking person in Treeview, or # todo - from text entry (first & Last Name). If later, need to name curitem = self.dup_tree.focus() nidx = self.dup_tree.item(curitem) entry = nidx['text'] fields = entry.split() fname = fields[0] lname = fields[1] key = self.create_key(fname, lname) this_item = self.people[key] print(f'key: {key}, this_item: {this_item}') self.clear_entry() self.clear_dup_tree() def clear_entry(self): self.first_name_e.delete(0, 'end') self.last_name_e.delete(0, 'end') self.address1_e.delete(0, 'end') self.address2_e.delete(0, 'end') self.city_e.delete(0, 'end') self.state = None self.state_e.set('') self.zipcode_e.delete(0, 'end') self.country_e.delete(0, 'end') self.shoe_size_e.delete(0, 'end') def clear_tree(self): all_children = self.tree.get_children() for item in all_children: self.tree.delete(item) def load_tree(self): # todo -- This one ok self.clear_tree() keys = list(self.people.keys()) keys.sort() for key in keys: group = self.people[key] for key, person in group.items(): if key.isnumeric(): ptext = '{} {}, street address: {}'\ .format(person['fname'], person['lname'], person['address1']) self.tree.insert('', 'end', text=ptext) def set_focus(self, event): event.widget.master.focus_set() def set_state(self, event): widget = event.widget state_name = widget.get() print(f'state_name: {state_name}') us = self.state_dict['USA'] canada = self.state_dict['Canada'] mexico = self.state_dict['Mexico'] self.state_name.set(state_name) if state_name in us: self.state_abbr.set(us[state_name]) self.country.set('USA') elif state_name in canada: self.country.set('Canada') self.state_abbr.set(canada[state_name]) elif state_name in mexico: self.country.set('Mexico') self.state_abbr.set(mexico[state_name]) else: tm.showerror('set_state', 'State {} Not found'.format(state_name)) self.state_name = None self.country = None def cancel(self): self.clear_entry() def create_statusbar(self): self.sb = tk.Frame(self.bottom_frame) self.sb.grid(row=0, column=0, sticky='nsew') def save_data(self): with open(self.filename, 'w') as fo: json.dump(self.people, fo) def update_seq(self, key): new_seq = None if key in self.people: seq_no = int(self.people[key]['last_seq']) new_seq = str(int(seq_no) + 1) else: new_seq = '0' self.people[key] = {} self.people[key]['last_seq'] = new_seq # self.people[key]['last_seq'] = str(int(new_seq)+1) return(new_seq) def add_person(self): # todo - add check to make sure required fields are populated # todo use new indexing new_entry = False lname = self.last_name.get() fname = self.first_name.get() key = self.create_key(fname, lname) print(f'Add key: {key}') seq = self.update_seq(key) print(seq) new_person = self.people[key][seq] = {} new_person['lname'] = lname new_person['fname'] = fname new_person['seq'] = seq new_person['address1'] = self.address1.get() new_person['address2'] = self.address2.get() new_person['city'] = self.city.get() new_person['state_name'] = self.state_name.get() new_person['state_abbr'] =self.state_abbr.get() new_person['zip'] = self.zipcode.get() new_person['country'] = self.country.get() new_person['shoesize'] = self.shoe_size.get() print(f'self.people 2: {self.people}') self.save_data() self.load_tree() # ival = '{} {} , addr1: {}'.format(fname, lname, seq_no, new_person['address1']) # self.tree.insert('', 'end', text=ival) self.clear_entry() def clear_dup_tree(self): map(self.dup_tree.delete, self.dup_tree.get_children()) def on_delete(self): self.parent.destroy() def main(): root = tk.Tk() ShoeBot(root) root.mainloop() if __name__ == '__main__': main()