Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
ShoeBot Problems
#1
Hello, I am currently creating a shoe bot, and I'm having trouble adding more inputs for other fields. I already have name and shoesize working (Thanks to someone on here for helping me with that), but I was wondering how I could make more inputs, and export them to a separate file in this format:
{
"first_name": “”,
"last_name": “”,
"address": “”,
"apartment_suite_unit_floor_(optional)": "",
"city/town": “”,
"state": "",
"zipcode": "",
"phone_number": "",
"email": "",
"credit_card_number": "",
"expirationmonth": "",
"expirationyear": "",
"security_code": "",
}
Here is the python code so far:
import tkinter as tk
import os
import json
import tkinter.ttk as ttk
import tkinter.messagebox as tm
 
 
class ShoeBot:
    def __init__(self, parent, filename):
        self.roots = parent
        self.roots.title('AWD ShoeBot')
        # self.roots.geometry('400x200+10+10')
        self.treeheight = 19
        self.filename = os.path.abspath(filename)
        self.fp = None
        self.people = {}
        if os.path.isfile(self.filename):
            self.load_data()
        self.changes_made = False
        self.this_person = None
        self.name = tk.StringVar()
        self.shoe_size = tk.StringVar()
        '''
        HERE
        '''
        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_user_dialog()
        self.create_statusbar()
        self.roots.protocol("WM_DELETE_WINDOW", self.on_delete)
 
    def create_main_frame(self):
        self.main_frame = tk.Frame(self.roots, relief=tk.RAISED)
        self.main_frame.grid(row=0, column=0, sticky='nsew')
        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, relief=tk.SUNKEN)
        self.left_frame.grid(row=0, rowspan=self.treeheight, column=0, columnspan=2, sticky='ns')
 
    def create_right_frame(self):
        self.right_frame = tk.Frame(self.main_frame, relief=tk.SUNKEN)
        self.right_frame.grid(row=0, rowspan=self.treeheight, column=2, columnspan=10, sticky='ns')
 
    def create_bottom_frame(self):
        self.bottom_frame = tk.Frame(self.main_frame, relief=tk.SUNKEN)
        self.bottom_frame.grid(row=self.treeheight + 1, column=0, columnspan=11, sticky='ew')
 
    def create_tree(self):
        treestyle = ttk.Style()
        treestyle.configure('Treeview.Heading', foreground='white',
                            borderwidth=2, background='SteelBlue',
                            rowheight=self.treeheight,
                            height=3)
 
        self.tree = ttk.Treeview(self.left_frame,
                                 height=self.treeheight,
                                 padding=(2, 2, 2, 2),
                                 columns='Name',
                                 selectmode="extended")
 
        self.tree.column('#0', stretch=tk.YES, width=180)
        self.tree.tag_configure('monospace', font='courier')
        treescrolly = tk.Scrollbar(self.left_frame, orient=tk.VERTICAL,
                                   command=self.tree.yview)
        treescrolly.grid(row=0, rowspan=self.treeheight, column=1, sticky='ns')
 
        treescrollx = tk.Scrollbar(self.left_frame, orient=tk.HORIZONTAL,
                                   command=self.tree.xview)
        treescrollx.grid(row=self.treeheight + 1, column=0, columnspan=2, sticky='ew')
        self.tree.configure(yscroll=treescrolly)
        self.tree.configure(xscroll=treescrollx)
        self.tree.grid(row=0, rowspan=self.treeheight, column=0, sticky='nsew')
        self.tree.bind('<Double-1>', self.name_selected)
 
    def clear_entry(self):
        self.nameE.delete(0, 'end')
        self.shoeSizeE.delete(0, 'end')
        '''
        HERE
        '''
 
    def clear_tree(self):
        all_children = self.tree.get_children()
        for item in all_children:
            self.tree.delete(item)
 
    def load_tree(self):
        self.clear_tree()
        keys = list(self.people.keys())
        keys.sort()
        for key in keys:
            self.tree.insert('', 'end', text='{}'.format(key))
 
    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.nameL = tk.Label(self.right_frame, text='Name: ')
        self.shoeSizeL = tk.Label(self.right_frame, text='Shoe Size: ')
        '''
        HERE
        '''
 
        self.nameL.grid(row=1, column=0, sticky='w')
        self.shoeSizeL.grid(row=2, column=0, sticky='w')
        '''
        HERE
        '''
 
        self.nameE = tk.Entry(self.right_frame, textvariable=self.name)
        self.shoeSizeE = tk.Entry(self.right_frame, textvariable=self.shoe_size)
        '''
        HERE
        '''
        self.nameE.grid(row=1, column=1)
        self.shoeSizeE.grid(row=2, column=1)
        '''
        HERE
        '''
 
        signupButton = tk.Button(self.right_frame, text='Create', command=self.add_person)
        signupButton.grid(row=4, column=0, sticky='w')
 
        removeButton = tk.Button(self.right_frame, text='Delete', command=self.remove_person)
        removeButton.grid(row=4, column=1, sticky='w')
 
        self.spacer = tk.Label(self.right_frame, bd=0)
        self.spacer.grid(row=5, column=0)
 
    def create_statusbar(self):
        self.sb = tk.Frame(self.bottom_frame)
        self.sb.grid(row=0, column=0, sticky='nsew')
 
    def on_delete(self):
        if self.changes_made:
            self.save_data()
        self.roots.destroy()
 
    def load_data(self):
        with open(self.filename) as fp:
            self.people = json.load(fp)
 
    def save_data(self):
        with open(self.filename, 'w') as fo:
            json.dump(self.people, fo)
 
    def add_person(self):
 
        name = self.name.get()
        shoesize = self.shoe_size.get()
        '''
        HERE
        '''
        if len(name) == 0 or len(shoesize) == 0:
            '''
            HERE
            '''
            tm.showerror('Add Person', 'Missing data')
        else:
            self.people[name] = {'shoesize': shoesize}
            self.tree.insert('', 'end', text='{}'.format(name))
            self.changes_made = True
            self.clear_entry()
            msg = 'Added {}'.format(name)
            '''
            HERE
            '''
 
    def remove_person(self):
        name = self.name.get()
        if len(name) == 0:
            tm.showerror('Remove Person', 'No Name')
        else:
            del self.people[name]
            print(f'People: {self.people}')
            self.changes_made = True
            self.load_tree()
        self.clear_entry()
 
    def name_selected(self, event):
        curitem = self.tree.focus()
        nidx = self.tree.item(curitem)
        name = nidx['text']
        shoesize = self.people[name]['shoesize']
        print('You selected {} who wears size {} shoe'.format(name, shoesize))
        # do stuff with selected person
        '''
        HERE
        '''
        '''
        HERE
        '''
 
    def find_person(self):
        '''
        HERE
        '''
        try:
            person = self.name.get()
            self.this_person = self.people[person]
            print('found {} shoesize: {}'.format(self.this_person[name], self.this_person[name]['shoesize']))
        except:
            'Adding {} shoesize: {}'.format(self.name.get(), self.shoe_size.get())
            self.add_person()
            self.this_person = self.people[person]
        self.name.set('')
        self.shoe_size.set('')
        return self.this_person
 
 
def main():
    root = tk.Tk()
    sb = ShoeBot(root, filename='PeopleFile.json')
    root.mainloop()
 
if __name__ == '__main__':
    main()
Thanks!
-MGallo
Reply
#2
What about the code i posted?? I't will allow you to have as many as you wish

And why the new post?
Reply
#3
(Sep-22-2017, 10:19 PM)Larz60+ Wrote: What about the code i posted?? I't will allow you to have as many as you wish

And why the new post?

I thought you said I was on my own now, and I'm having trouble adding new fields; I keep getting errors. Also, the program that we already made takes the values in the dictionary form as I showed above.
Reply
#4
Ok, I'll give you a start.
your list, which I assume is what you want to save in the file:

Since you are adding so much information, I would suggest that you also
include a customer id, otherwise you're going to run into problems when
someone has same name.

Now since this is the case, the treeview needs to show that customer number, name, and probably zipcode
in order to make the person unique.

{
'first_name': '',
'last_name':'',
'address': '',
'apartment_suite_unit_floor_(optional)':'',
'city/town': '',
'state': '',
'zipcode': ',
'phone_number': ',
'email': ',
'credit_card_number': '',
'expiration_month': '',
'expiration_year': '',
'security_code': '',
}
I'll do: 'first_name', 'last_name' and 'zipcode'

The rest will basically be a repeat of these.

Give me a while
Reply
#5
(Sep-23-2017, 10:42 AM)Larz60+ Wrote: I'll do: 'first_name', 'last_name' and 'zipcode'

The rest will basically be a repeat of these.

Give me a while

Thank you so much again! :D
Reply
#6
This is turning out to be a lot more than I bargained for.
I seldom do this sort of application writing on the forum, you just caught me
while in the mood to write code.

This will take a bit more work, and I'm doing it between other tasks.
Had to expand it in order to make it work correctly, had I known that you wanted
all the other fields, and persistence, I would have gone about it  differently.

one thing that is always a problem is selecting customers with the same name, city, state and
even the same home ... (Father and son for example).
To compensate for this, i added a sequence number.
This also creates a delete problem, for example which John Smith to delete.
The data structure has changed because of this, it's now a dictionary, with a hash key, and a
list of people matching that hash code.
I'll explain more later, but here's what you are going to have to do after I post the code:
  • change from storage in json file to a database, I think sqlite3 is quite adequate for this application.
  • add remaining fields. All you have to do is examine how the address field was entered, and follow
    the pattern for the remaining fields.
  • examine the code and understand how it works.
  • You can get someone to do the work for you if you don't want to do it yourself, but you will have
  • to post that request in the jobs section, and may have to offer some sort of compensation.
  • You can still ask for advise on specific questions, but don't expect someone to answer 10
    questions at one time.
  • if you find that don't understand certain concepts, ask and I or someone else will at least point you to a tutorial
    or other source of information, And may very well answer it directly.

I'll probably post the finished code tomorrow (not a promise, football takes precedence)
but here's a peek at what this is looking like:
   

click to enlarge
Reply
#7
Gracias
Reply
#8
This was posted on the original thread (that's only on thread should be created for the same topic, got confused)
So this is a duplicate post:

From 'Window not appearing #15' at https://python-forum.io/Thread-Window-No...ing?page=2'
------------------------------- duplicate -------------------------------
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
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()
Here's a list of what reamains:

Remains to do (in order of priority:
 
  • Change Name Selected, all it should do is select the item being clicked and diaplay the details.

  • if Only first and last name selected from entry fields, fill dup list with everyone that has
      that name, and allow to finish selection from dup tree.

  • Finish delete.

  • Problem when starting from entry box: - Thinks it is selecting from dlete tree
          before populated

  • if last and first name only entered, fill dup list for final selection.

  • Fix problem with country not being populated if wrong state chosen and then correct chosen
    (after tkinter messgebox display)

  • Make sure all necessary fields populated when adding or deleting customer.

  • re-arrange order of methods logically

  • Decide what to do with Text Box

  • replace dictionary with sqlite3 database

  • Add zipcode file

  • add comments

  • add user docs

I may not do all this, but at least it's on the list.
I will turn this into a general customer maintenance program tutorial
so others may benefit form it. For that, I'll probably do a wxpython version as well
as it has a much cleaner and modern look.
Reply
#9
New update:
I'm going to take a week break from this as I am getting
tired of looking at it.

Still a few items to do:

Completed Tasks:
  • Change Name Selected, all it should do is select the item being clicked and diaplay the details. --Done--
  • if Only first and last name selected from entry fields, fill dup list with everyone that has  that name, and allow to finish selection from  dup tree. --Done--
  • if last and first name only entered, fill dup list for final selection. --Done--
  • Problem when starting from entry box: - Thinks it is selecting from delete tree before populated
  • Fix problem with country not being populated if wrong state chosen and then correct chosen (after tkinter messgebox display) -- Done--
  • Treeview not loading at start -- Done--

To be done:
  • hide_dup_tree doesn't hide the dup tree.
  • Finish delete.
  • Make sure all necessary fields populated when adding or deleting customer.
  • re-arrange order of methods logically
  • Decide what to do with Text Box
  • replace dictionary with sqlite3 database
  • Add zipcode file
  • add comments - Started -
  • add user docs
  • Delete unused or replaced methods

ShoeBot.py
# coding=utf-8
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.tree_items = {}
        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/customerFile.json')
        self.fp = None
        self.customers = None
        self.cust_key = None
        self.cust_item = None
        self.selected_customer = None

        if os.path.isfile(self.filename):
            with open(self.filename) as customer_file:
                self.customers = json.load(customer_file)
        else:
            self.customers = {}

        self.changes_made = False
        self.this_customer = None

        with open('data/StateTuples.json') as state_tuples_file:
            self.states = json.load(state_tuples_file)

        with open('data/States.json') as state_dict_file:
            self.state_dict = json.load(state_dict_file)

        self.selected_customer = 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_dup_tree()
        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=7, 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='customer, 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.customer_select)


    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', 'Reserved for future use:\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, columnspan=7, sticky='w')

        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=5, column=6, sticky='w')
        self.zipcode_e.grid(row=5, column=7, 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)

        button_frame = tk.Frame(self.right_frame, bd=4, padx=4, pady=4, relief=tk.SUNKEN)
        button_frame.grid(row=11, column=0, columnspan=4, sticky='nsew')

        self.display_button = tk.Button(button_frame, text='Display', bd=4, padx=4, pady=4, relief=tk.RAISED)
        self.display_button.grid(row=0, column=0, sticky='w')
        self.display_button.bind('<Button-1>', self.customer_select)

        self.create_button = tk.Button(button_frame, text='Create', bd=4, padx=4, pady=4, relief=tk.RAISED)
        self.create_button.grid(row=0, column=1, sticky='w')
        self.create_button.bind('<Button-1>', self.add_customer)

        self.cancel_button = tk.Button(button_frame, text='Cancel', bd=4, padx=4, pady=4, relief=tk.RAISED)
        self.cancel_button.grid(row=0, column=2, sticky='w')
        self.cancel_button.bind('<Button-1>', self.cancel)

        self.remove_button = tk.Button(button_frame, text='Delete', bd=4, padx=4, pady=4, relief=tk.RAISED)
        self.remove_button.grid(row=0, column=3, sticky='w')
        self.remove_button.bind('<Button-1>', self.remove_customer)

    def create_dup_tree(self):
        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=400)

        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.clear_dup_tree()
        self.dup_tree.forget()
        self.dw_treescrolly.forget()
        self.dw_treescrollx.forget()
        self.dup_tree.unbind('<Double-1>')
        self.parent.update_idletasks()

    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.dup_customer_selected)

    def load_tree(self):
        # todo -- This one ok
        self.clear_tree()
        keys = list(self.customers.keys())
        keys.sort()
        cur_index = 1
        for key in keys:
            cust_key = key
            group = self.customers[key]
            for ckey, customer in group.items():
                if ckey.isnumeric():
                    ptext = '{} {} - {} {} {}, {} {} {}'\
                        .format(customer['fname'], customer['lname'], customer['address1'], customer['address2'],
                                customer['city'], customer['state_name'], customer['zip'], customer['country'])
                    self.tree.insert('', cur_index, text=ptext)
                    self.update_tree_items(ckey, cust_key, cur_index, customer)
                    cur_index += 1

    def update_tree_items(self, key, cust_key, cur_index, customer):
        """
        Keeps copy of all customers dictionary keys used to fill treeview.
        Used as a cross reference when selecting or deleting entries
        :param key: <class 'str'> this is the self.customers dictionry key
        :param cust_key: <class 'str'> This is the item in the self.customers[key] entry of a the tree entry
        :param cur_index: <class 'int'> this is the tree row index
        :param customer: <class 'dict'> type dict: this is the customer detail
        :return: None
        """
        idx = str(cur_index)
        self.tree_items[idx] = {}
        self.tree_items[idx]['cust_key'] = cust_key
        self.tree_items[idx]['cust_entry'] = key
        self.tree_items[idx]['fname'] = customer['fname']
        self.tree_items[idx]['lname'] = customer['lname']
        self.tree_items[idx]['address1'] = customer['address1']
        self.tree_items[idx]['address2'] = customer['address2']
        self.tree_items[idx]['city'] = customer['city']
        self.tree_items[idx]['state_name'] = customer['state_name']
        self.tree_items[idx]['zip'] = customer['zip']
        self.tree_items[idx]['country'] = customer['country']


    def create_key(self, fname, lname):
        return f'{lname}{fname}'

    def customer_select(self, event):
        """
        Run on double click of self.tree item
        :param event:
        :return:
        """
        name = str(event.widget).split(".")[-1]
        name = name[1:]
        self.cust_key = None
        self.cust_item = None
        self.selected_customer = None
        if name == 'treeview':
            curitem = self.tree.focus()
            nidx = self.tree.item(curitem)
            # The following is a hack, curitem has a value like I002, we want '2'
            tree_item_key = str(int(curitem[1:]))
            self.cust_key = self.tree_items[tree_item_key]['cust_key']
            self.cust_item = self.tree_items[tree_item_key]['cust_entry']
            self.selected_customer = self.customers[self.cust_key][self.cust_item]
        elif name == 'button':
            lname = self.last_name.get()
            fname = self.first_name.get()
            self.cust_key = self.create_key(fname, lname)
            self.selected_customer = self.customers[self.cust_key]
            if self.cust_key in self.customers:
                self.selected_customer = self.customers[self.cust_key]
                self.select_customer_from_dups()
            else:
                self.cust_key =None
                tm.showerror('customer_select', 'Customer not found, check first and last name')
                return None
        else:
            self.cust_key = None
            tm.showerror('customer_select', 'Unknown event name: {}'.format(name))
            return None
        self.display_customer()


    def select_customer_from_dups(self):
        if self.selected_customer is not None:
            self.clear_dup_tree()
            last_seq = int(self.selected_customer['last_seq'])
            if last_seq == 0:
                self.selected_customer = self.selected_customer['0']
            else:
                for n in range(last_seq + 1):
                    cid = str(n)
                    customer = self.selected_customer[cid]
                    ptext = '{} {} - {} {} {}, {} {} {}'\
                        .format(customer['fname'], customer['lname'], customer['address1'], customer['address2'],
                                customer['city'], customer['state_name'], customer['zip'], customer['country'])
                    self.dup_tree.insert('', 'end', text=ptext)
                self.show_dup_tree()
        else:
            self.cust_key = None
            tm.showerror('select_customer_from_dups', 'No selected customer')
            return None

    def dup_customer_selected(self, event):
        curitem = self.dup_tree.focus()
        # The following is a hack, curitem has a value like I002, we want '2'
        dkey = str(int(curitem[1:]) - 1)

        self.selected_customer = self.selected_customer[dkey]
        self.display_customer()
        self.hide_dup_tree()

    def display_customer(self):
        if self.selected_customer is not None:
            self.clear_entry()
            customer = self.selected_customer
            print(f'customer: {customer}')
            self.first_name.set(customer['fname'])
            self.last_name.set(customer['lname'])
            self.address1.set(customer['address1'])
            self.address2.set(customer['address2'])
            self.city.set(customer['city'])
            self.state_name.set(customer['state_name'])
            self.state_abbr.set(customer['state_abbr'])
            self.country.set(customer['country'])
            self.shoe_size.set(customer['shoesize'])
            self.zipcode.set(customer['zip'])
            self.parent.update_idletasks()

    def add_to_dups(self, customer):
        # 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(customer['seq'], customer['fname'], customer['lname'], customer['address1'], customer['city'],
                    customer['state_abbr'], customer['shoesize'])
        self.dup_tree.insert('', 'end', text=ptext)

    def remove_customer(self, event):
        # todo - populate dup entry tree
        # todo use new indexing
        # todo - determine how this method was called, from clicking customer 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.customers[key]

        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)
        self.tree_items = {}

    def clear_dup_tree(self):
        all_children = self.dup_tree.get_children()
        for item in all_children:
            self.dup_tree.delete(item)
        self.tree_items = {}

    def set_focus(self, event):
        event.widget.master.focus_set()

    def set_state(self, event):
        widget = event.widget
        state_name = widget.get()

        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.customers, fo)

    def update_seq(self, key):
        new_seq = None
        if key in self.customers:
            seq_no = int(self.customers[key]['last_seq'])
            new_seq = str(int(seq_no) + 1)
        else:
            new_seq = '0'
            self.customers[key] = {}
        self.customers[key]['last_seq'] = new_seq
        # self.customers[key]['last_seq'] = str(int(new_seq)+1)
        return(new_seq)

    def add_customer(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)
        seq = self.update_seq(key)
        new_customer = self.customers[key][seq] = {}
        new_customer['lname'] = lname
        new_customer['fname'] = fname
        new_customer['seq'] = seq
        new_customer['address1'] = self.address1.get()
        new_customer['address2'] = self.address2.get()
        new_customer['city'] = self.city.get()
        new_customer['state_name'] = self.state_name.get()
        new_customer['state_abbr'] =self.state_abbr.get()
        new_customer['zip'] = self.zipcode.get()
        new_customer['country'] = self.country.get()
        new_customer['shoesize'] = self.shoe_size.get()

        self.save_data()
        self.load_tree()
        self.clear_entry()

    def on_delete(self):
        self.parent.destroy()

def main():
    root = tk.Tk()
    ShoeBot(root)
    root.mainloop()

if __name__ == '__main__':
    main()
MakeStateFile.py
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}')
Reply
#10
Wow, I cannot thank you enough, you obviously put an extreme amount of work into this!
However, I am getting an indentation error for some reason:
Error:
Traceback (most recent call last): File "/Users/me/Desktop/ShoeBot/ShoeBot.py", line 5, in <module> import MakeStateFile as msf File "/Users/me/Desktop/ShoeBot/MakeStateFile.py", line 128 datapath = os.path.abspath('.') ^ IndentationError: unexpected indent
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020