Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
ShoeBot Problems
#11
Don't know why you should.
I use standard 4 space indentation as spaces, never tabs
did you double click on the code to copy?
If not, try that.
Reply
#12
Here it is, everything seems to be running.
There may still be some bugs, I tested without finding any, but since you will do things differently,
you may still find some.

There are two ways to display a customer, and the same applied to delete.
  • 1. double click in left treeview
  • 2. Enter first and last name in data entry area. When you do this, if there is
              more than one customer with the same name, a list will pop up where you can select
              the proper record.

Note: Remaining items will be included at some point so that I can create a tutorial, but
don't wait for it.

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--
  • Delete unused or replaced methods --Done--
  • Add comments and user docs --Done--
  • Place data entry fields and dup tree in frames. --Done--
  • hide_dup_tree doesn't hide the dup tree. --Done--
  • Finish delete. --Done--
  • Make sure all necessary fields populated when adding or deleting customer. --Done--
  • Decide what to do with Text Box Future use. --Done--
  • Internal sequence numbers, and last_seq need to be re-ordered after delete. --Done--

To be done:
  • Re-arrange order of methods logically partially done.
  • Replace dictionary with sqlite3 database
  • Add zipcode file

code listings:
CustomerEditor.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


class CustomerEditor:
    def __init__(self, parent):
        """
        Initialization routine

        :param parent: type tkinter Tk()
        """
        self.parent = parent
        self.parent.title('Customer Editor')

        datapath = os.path.abspath('.')
        datapath = '{}\data'.format(datapath)
        if not os.path.exists(datapath):
            msf.MakeStateFiles()

        self.filename = os.path.abspath('data/customerFile.json')
        self.state_tuples_filename = os.path.abspath('data/StateTuples.json')
        self.state_dict_filename = os.path.abspath('data/States.json')

        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.customers = None
        self.cust_key = None
        self.cust_item = None
        self.selected_customer = None
        # call_after will contain the name of routine to be called after an event
        # If used, always remember to set back to None after call completed
        self.call_after = 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(self.state_tuples_filename) as state_tuples_file:
            self.states = json.load(state_tuples_file)

        with open(self.state_dict_filename) 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

    def run(self):
        """
        run the application

        :return: None
        """
        self.build_gui()
        self.load_tree()

    def build_gui(self):
        """
        all gui widget creation dispatched here

        :return: None
        """
        self.create_main_frame()
        self.create_left_frame()
        self.create_right_frame()
        self.create_data_entry_frame()
        self.create_dup_tree_frame()
        self.create_bottom_frame()
        self.create_tree()
        self.create_textbox()
        self.create_data_entry_fields()
        self.create_dup_tree()
        self.create_statusbar()
        self.parent.protocol("WM_DELETE_WINDOW", self.on_delete)

    def create_main_frame(self):
        """
        main frame creation (same size as parent window)
        container for all other widgets

        :return: None
        """
        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):
        """
        left frame creation -- created in left portion of main frame.
        holds the main treeview, and the text box

        :return: None
        """
        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):
        """
        right frame creation -- created in right portion of main frame.
        holds data entry fields and (usually hiden) duplicate selection tree.

        :return: None
        """
        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_data_entry_frame(self):
        self.data_entry_frame = tk.Frame(self.right_frame, bd=2, padx=4, pady=4, relief=tk.SUNKEN)
        self.data_entry_frame.grid(row=0, column=0, columnspan=7, sticky='ns')

    def create_dup_tree_frame(self):
        self.dup_tree_frame = tk.Frame(self.right_frame, bd=2, padx=4, pady=4, relief=tk.SUNKEN)

    def create_bottom_frame(self):
        """
        bottom frame creation -- created at bottom of main frame.
        holds status bar.

        :return: None
        """
        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):
        """
        creates main customer selection tree.

        :return: None
        """
        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.select_cust_from_tree)


    def create_textbox(self):
        """
        creates a text box in lower portion of left frame.
        Has not been uesd yet.

        :return:  None
        """
        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_data_entry_fields(self):
        """
        creates data entry fields on top portion of right frame

        :return: None
        """
        self.intruction = tk.Label(self.data_entry_frame, text='Enter Information Below:\n')
        self.intruction.grid(row=0, column=0, columnspan=7, sticky='w')

        self.first_name_l = tk.Label(self.data_entry_frame, text='First Name: ')
        self.first_name_e = tk.Entry(self.data_entry_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.data_entry_frame, text='Last Name: ')
        self.last_name_e = tk.Entry(self.data_entry_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.data_entry_frame, text='Address 1: ')
        self.address1_e = tk.Entry(self.data_entry_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.data_entry_frame, text='Address 2: ')
        self.address2_e = tk.Entry(self.data_entry_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.data_entry_frame, text='City: ')
        self.city_e = tk.Entry(self.data_entry_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.data_entry_frame, text='State: ')
        self.state_e = ttk.Combobox(self.data_entry_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.data_entry_frame, text='Zip Code: ')
        self.zipcode_e = tk.Entry(self.data_entry_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.data_entry_frame, text='Country: ')
        self.country_e = tk.Entry(self.data_entry_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.data_entry_frame, text='Shoe Size: ')
        self.shoe_size_e = tk.Entry(self.data_entry_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.data_entry_frame)
        self.spacer1.grid(row=9, rowspan=2, column=0)

        button_frame = tk.Frame(self.data_entry_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.display_selected)

        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_clear = tk.Button(button_frame, text='Cancel/Clear', bd=4, padx=4, pady=4, relief=tk.RAISED)
        self.cancel_clear.grid(row=0, column=2, sticky='w')
        self.cancel_clear.bind('<Button-1>', self.clear_transaction)

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

    def create_dup_tree(self):
        """
        creates a dup selection tree on bottom of right frame.
        Used to resolve selection of duplicate customers.

        :return:
        """
        self.dup_tree = ttk.Treeview(self.dup_tree_frame,
                                 height=self.tree_rows,
                                 padding=(2, 2, 2, 2),
                                 columns='Name',
                                 selectmode="extended",
                                 style='Treeview')
        self.dup_tree.heading('#0', text='Please choose correct customer', anchor='w')
        self.dup_tree.column('#0', stretch=tk.YES, width=400)

        self.dup_tree.tag_configure('monospace', font='courier')

        self.dw_treescrolly = tk.Scrollbar(self.dup_tree_frame, orient=tk.VERTICAL,
                                   command=self.dup_tree.yview)
        self.dw_treescrollx = tk.Scrollbar(self.dup_tree_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)
        self.dup_tree.grid(row=0, rowspan=self.dup_tree_rows, column=0, columnspan=6, sticky='nsew')
        self.dw_treescrolly.grid(row=0, rowspan=self.dup_tree_rows, column=6, sticky='ns')
        self.dw_treescrollx.grid(row=self.dup_tree_rows + 1, column=0, columnspan=6, sticky='ew')
        self.dup_tree.bind('<Double-1>', self.dup_customer_selected)

    def create_statusbar(self):
        """
        create Status bar in bottom frame.
        Not used yet, will use eventually.

        :return: None
        """
        self.sb = tk.Frame(self.bottom_frame)
        self.sb.grid(row=0, column=0, sticky='nsew')

    def hide_dup_tree(self):
        """
        hides the dup selection tree when not in use

        :return: None
        """
        self.clear_dup_tree()
        self.dup_tree_frame.grid_forget()
        self.parent.update_idletasks()

    def show_dup_tree(self):
        """
        shows the dup selection tree needed.

        :return: None
        """
        self.dup_tree_frame.grid(row=1, column=0, columnspan=7, sticky='ns')

    def load_tree(self):
        """
        loads the customer selection tree

        :return: None
        """
        self.clear_tree()
        keys = list(self.customers.keys())
        keys.sort()
        cur_index = 0
        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):
        """
        called to create customer dictionary key

        :return: self.customers dictionary key for selected name
        """
        return f'{self.last_name.get()}{self.first_name.get()}'

    def prep_cust(self):
        self.cust_key = None
        self.cust_item = None
        self.selected_customer = None

    def select_cust_from_tree(self, event):
        """
        Run on double click of self.tree item

        :param event:
        :return: None
        """
        self.prep_cust()
        curitem = self.tree.focus()
        idx = self.tree.index(curitem)
        tree_item_key = str(idx)
        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]
        self.display_customer()

    def customer_select(self, event):
        """
        Run on double click of self.tree item

        :param event:
        :return: None
        """
        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()
            idx = self.tree.index(curitem)
            tree_item_key = str(idx)
            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 'button' in name:
            self.cust_key = self.create_key()
            if self.cust_key in self.customers:
                self.selected_customer = self.customers[self.cust_key]
                if int(self.selected_customer['last_seq']) == 0:
                    self.selected_customer = self.selected_customer['0']
                    self.cust_item = '0'
                    self.display_customer()
                else:
                    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

    def display_selected(self, event):
        """
        Selection from display, check for dups and select from if there
        ae any. When customer selected, display results
        :return: None
        """
        self.selected_customer = self.customers[self.create_key()]
        self.select_customer_from_dups()

    def select_customer_from_dups(self):
        """
        Cretes a duplicate customer tree when resolution needed for customers
        with identical keys (and thus names). Shows all data so decision can be made.
        This is necessary as father and son may have same name and live in same house

        :return: None
        """
        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']
                self.finish_selection()
            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):
        """
        Entry point for data entry display button

        :param event:
        :return: None
        """
        curitem = self.dup_tree.focus()
        idx = self.dup_tree.index(curitem)
        dkey = str(idx)
        self.selected_customer = self.selected_customer[dkey]
        self.finish_selection()

    def finish_selection(self):
        """
        This part broken out to be used when only one customer is
        found. (doesn't have an event).
        :return: None
        """
        self.cust_item = self.selected_customer['seq']
        self.display_customer()
        self.hide_dup_tree()
        # Check call after, if not done, execute method stored within
        if self.call_after is not None:
            self.call_after()

    def display_customer(self):
        """
        Expects selected customer data in self.selected customer.
        Fills data entry fields with that customer data. Further
        action (delete specifically) can then be taken on customer.
        Customer cam be selected by double clicking on left tree, or
        by entering first and last name in data entry fields and then
        clicking the display button.

        :return: None
        """
        if self.selected_customer is not None:
            self.clear_entry()
            customer = self.selected_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 clear_entry(self, clear_name=True):
        """
        Clears all data entry fields

        :return: None
        """
        # setting clear_name to False will preserve contents of name fields
        if clear_name:
            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):
        """
        Clears all items from main selection tree

        :return: None
        """
        all_children = self.tree.get_children()
        for item in all_children:
            self.tree.delete(item)
        self.tree_items = {}

    def clear_dup_tree(self):
        """
        Clears all items from dup selection tree

        :return: None
        """
        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):
        """
        sets focus for main tree selection

        :param event: contains event info from originating widget.
        :return: None
        """
        event.widget.master.focus_set()

    def set_state(self, event):
        """
        Sets abbreviated state code, and country code from selected state.

        :param event:  contains event info from originating widget.
        :return: None
        """
        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 clear_transaction(self, event):
        """
        Clears data entry transaction

        :return: None
        """
        self.clear_entry()
        self.clear_tree()
        self.clear_dup_tree()
        self.load_tree()

    def save_data(self):
        """
        Saves self.customers to disk in json format

        :return: None
        """
        with open(self.filename, 'w') as fo:
            json.dump(self.customers, fo)

    def update_seq(self, key, ):
        """
        updates sequence number for new customer.

        :param key: self.customers key consisting of last and first name
        :return: New sequence number.
        """
        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
        return(new_seq)

    def customer_has_requied_fields(self):
        """
        Check that all required entry fields are populated for customer

        :return: boolean value, True if all fields populated.
        """
        retval = True
        fields = []
        fields.append(len(self.first_name.get()))
        fields.append(len(self.last_name.get()))
        fields.append(len(self.address1.get()))
        fields.append(len(self.city.get()))
        fields.append(len(self.state_abbr.get()))
        fields.append(len(self.zipcode.get()))
        for field in fields:
            if field == 0:
                retval = False
                break
        return retval

    def add_customer(self, event):
        """
        Add a new customer

        :return: None
        """
        # todo - add check to make sure required fields are populated
        key = self.create_key()
        seq = self.update_seq(key)
        new_customer = self.customers[key][seq] = {}
        if self.customer_has_requied_fields():
            new_customer['lname'] = self.last_name.get()
            new_customer['fname'] = self.first_name.get()
            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()
        else:
            tm.showerror('add_customer', 'Missing data')

    def delete_customer(self, event):
        """
        deletes the selected customer

        :param event:
        :return: None
        """

        # Clear all but name
        self.clear_entry(clear_name=False)
        self.clear_dup_tree()
        self.selected_customer = self.customers[self.create_key()]
        # Set routine to execute after dup select event
        self.call_after = self.finish_delete
        self.select_customer_from_dups()

    def finish_delete(self):
        # Reset call after -- If not done, displaying a customer mat set up a delete!
        self.call_after = None
        custmsg = ['Are you sure you want to delete\n{} {}'
                       .format(self.first_name.get(), self.last_name.get()),
                   '{}'.format(self.address1.get()),
                   '{}, {}'.format(self.city.get(), self.state_abbr.get())]
        tk.messagebox.askokcancel('remove_customer', "\n".join(custmsg))
        seq = self.selected_customer['seq']

        dkey = self.create_key()
        del self.customers[dkey][seq]
        self.reorder_seq(self.customers[dkey], dkey)

        self.save_data()

        self.clear_tree()
        self.clear_dup_tree()
        self.clear_entry()

        self.load_tree()

    def reorder_seq(self, customer, dkey):
        """
        Update self.customers seq no after delete.

        :param dkey: Contains key of deleted customer
        :param seq: Contains sequence number of deleted customer
        :return: None
        """
        # If customer doesn't exist, nothing to do, return
        new_entry = {}
        nseq = '0'
        for key, value in customer.items():
            if key == 'last_seq':
                continue
            new_entry['last_seq'] = nseq
            new_entry[nseq] = value
            new_entry[nseq]['seq'] = nseq
            nseq = str(int(nseq) + 1)
        del self.customers[dkey]
        self.customers[dkey] = new_entry

    def on_delete(self):
        """
        Graceful exit from application

        :return: None
        """
        self.parent.destroy()

def main():
    """
    Main routine for testing

    :return: None
    """
    root = tk.Tk()
    sb = CustomerEditor(root)
    sb.run()
    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}')
Help doc:
Output:
Help on module CustomerEditor: NAME     CustomerEditor - # coding=utf-8 CLASSES     builtins.object         CustomerEditor          class CustomerEditor(builtins.object)      |  Methods defined here:      |        |  __init__(self, parent)      |      Initialization routine      |            |      :param parent: type tkinter Tk()      |        |  add_customer(self, event)      |      Add a new customer      |            |      :return: None      |        |  build_gui(self)      |      all gui widget creation dispatched here      |            |      :return: None      |        |  clear_dup_tree(self)      |      Clears all items from dup selection tree      |            |      :return: None      |        |  clear_entry(self, clear_name=True)      |      Clears all data entry fields      |            |      :return: None      |        |  clear_transaction(self, event)      |      Clears data entry transaction      |            |      :return: None      |        |  clear_tree(self)      |      Clears all items from main selection tree      |            |      :return: None      |        |  create_bottom_frame(self)      |      bottom frame creation -- created at bottom of main frame.      |      holds status bar.      |            |      :return: None      |        |  create_data_entry_fields(self)      |      creates data entry fields on top portion of right frame      |            |      :return: None      |        |  create_data_entry_frame(self)      |        |  create_dup_tree(self)      |      creates a dup selection tree on bottom of right frame.      |      Used to resolve selection of duplicate customers.      |            |      :return:      |        |  create_dup_tree_frame(self)      |        |  create_key(self)      |      called to create customer dictionary key      |            |      :return: self.customers dictionary key for selected name      |        |  create_left_frame(self)      |      left frame creation -- created in left portion of main frame.      |      holds the main treeview, and the text box      |            |      :return: None      |        |  create_main_frame(self)      |      main frame creation (same size as parent window)      |      container for all other widgets      |            |      :return: None      |        |  create_right_frame(self)      |      right frame creation -- created in right portion of main frame.      |      holds data entry fields and (usually hiden) duplicate selection tree.      |            |      :return: None      |        |  create_statusbar(self)      |      create Status bar in bottom frame.      |      Not used yet, will use eventually.      |            |      :return: None      |        |  create_textbox(self)      |      creates a text box in lower portion of left frame.      |      Has not been uesd yet.      |            |      :return:  None      |        |  create_tree(self)      |      creates main customer selection tree.      |            |      :return: None      |        |  customer_has_requied_fields(self)      |      Check that all required entry fields are populated for customer      |            |      :return: boolean value, True if all fields populated.      |        |  customer_select(self, event)      |      Run on double click of self.tree item      |            |      :param event:      |      :return: None      |        |  delete_customer(self, event)      |      deletes the selected customer      |            |      :param event:      |      :return: None      |        |  display_customer(self)      |      Expects selected customer data in self.selected customer.      |      Fills data entry fields with that customer data. Further      |      action (delete specifically) can then be taken on customer.      |      Customer cam be selected by double clicking on left tree, or      |      by entering first and last name in data entry fields and then      |      clicking the display button.      |            |      :return: None      |        |  display_selected(self, event)      |      Selection from display, check for dups and select from if there      |      ae any. When customer selected, display results      |      :return: None      |        |  dup_customer_selected(self, event)      |      Entry point for data entry display button      |            |      :param event:      |      :return: None      |        |  finish_delete(self)      |        |  finish_selection(self)      |      This part broken out to be used when only one customer is      |      found. (doesn't have an event).      |      :return: None      |        |  hide_dup_tree(self)      |      hides the dup selection tree when not in use      |            |      :return: None      |        |  load_tree(self)      |      loads the customer selection tree      |            |      :return: None      |        |  on_delete(self)      |      Graceful exit from application      |            |      :return: None      |        |  prep_cust(self)      |        |  reorder_seq(self, customer, dkey)      |      Update self.customers seq no after delete.      |            |      :param dkey: Contains key of deleted customer      |      :param seq: Contains sequence number of deleted customer      |      :return: None      |        |  run(self)      |      run the application      |            |      :return: None      |        |  save_data(self)      |      Saves self.customers to disk in json format      |            |      :return: None      |        |  select_cust_from_tree(self, event)      |      Run on double click of self.tree item      |            |      :param event:      |      :return: None      |        |  select_customer_from_dups(self)      |      Cretes a duplicate customer tree when resolution needed for customers      |      with identical keys (and thus names). Shows all data so decision can be made.      |      This is necessary as father and son may have same name and live in same house      |            |      :return: None      |        |  set_focus(self, event)      |      sets focus for main tree selection      |            |      :param event: contains event info from originating widget.      |      :return: None      |        |  set_state(self, event)      |      Sets abbreviated state code, and country code from selected state.      |            |      :param event:  contains event info from originating widget.      |      :return: None      |        |  show_dup_tree(self)      |      shows the dup selection tree needed.      |            |      :return: None      |        |  update_seq(self, key)      |      updates sequence number for new customer.      |            |      :param key: self.customers key consisting of last and first name      |      :return: New sequence number.      |        |  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      |        |  ----------------------------------------------------------------------      |  Data descriptors defined here:      |        |  __dict__      |      dictionary for instance variables (if defined)      |        |  __weakref__      |      list of weak references to the object (if defined) FUNCTIONS     main()         Main routine for testing                  :return: None FILE     ...\c\customereditor\src\customereditor.py
Have fun with it!

Re-posted code somehow lost indentation fiest time
Reply


Forum Jump:

User Panel Messages

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