Module ui.window.database

Main View of the Application

Expand source code
"""
Main View of the Application
"""
from tkinter import BOTH, Menu, LEFT, W, E, X, RIGHT
from tkinter.ttk import Frame, Button, Label

from lib.layer import security
from lib.model import employee
from lib.model.employee import Employee
from ui import store
from ui.widgets.table import EmpDatTableCanvas
from ui.window import TkinterWindow


class DatabaseWindow(TkinterWindow):  # pylint: disable=too-many-instance-attributes
    """
    Shows employee table
    """

    def __init__(self, event_handlers):
        """
        Adds the table and it's options

        :param event_handlers: expects the following:
            'new_employee'
            'new_receipt'
            'new_timesheet'
            'run_payroll'
            'save'
            'delete'
            'file>logout'
            'import>employees'
            'import>receipts'
            'import>timesheets'
            'admin>review'
            'export>employees'
        """
        super().__init__(event_handlers)

        self.results = {}

        self.main = self.master
        self.main.geometry('1024x768')
        self.main.title('EmpDat')
        frame = Frame(self.main)
        frame.pack(fill=BOTH, expand=1)
        self.table = EmpDatTableCanvas(frame, col_modifiers={
            Employee.view_columns['id']: {  # ID
                'read_only': True
            },
            Employee.view_columns['role']: {  # Role
                'options': list(security.ROLES.keys())
            },
            Employee.view_columns['social_security_number']: {  # SSN
                'validator': 'ssn'
            },
            Employee.view_columns['start_date']: {  # Start Date
                'date': True,
                'validator': 'date',
            },
            Employee.view_columns['date_of_birth']: {  # DOB
                'date': True,
                'validator': 'date',
            },
            Employee.view_columns['sex']: {  # Sex
                'options': ['Male', 'Female', 'Other']
            },
            Employee.view_columns['state']: {  # State
                'validator': 'state_code'
            },
            Employee.view_columns['zipcode']: {  # Postal Code
                'validator': 'numeric'
            },
            Employee.view_columns['email']: {  # Email
                'validator': 'email'
            },
            Employee.view_columns['phone_number']: {  # Phone Number
                'validator': 'phone'
            },
            Employee.view_columns['emergency_contact_name']: {
                'validator': 'alpha'
            },
            Employee.view_columns['emergency_contact_phone']: {
                'validator': 'phone'
            },
            Employee.view_columns['classification']: {  # Classification
                'options': list(employee.classifications_dict.keys())
            },
            Employee.view_columns['payment_method']: {  # Payment Method
                'options': list(employee.pay_methods_dict.keys())
            },
            Employee.view_columns['salary']: {
                'validator': 'numeric'
            },
            Employee.view_columns['hourly_rate']: {
                'validator': 'numeric'
            },
            Employee.view_columns['commission_rate']: {
                'validator': 'numeric'
            },
            Employee.view_columns['bank_routing']: {
                'validator': 'bank_routing'
            },
            Employee.view_columns['bank_account']: {
                'validator': 'numeric'
            },
            Employee.view_columns['date_left']: {  # Date Left
                'date': True,
                'validator': 'date',
            },
        }, on_unsaved=self.on_table_unsaved,
                                       on_selected=lambda: self.set_delete_state('normal'),
                                       data=self.results, rowheight=50)
        self.table.show()

        if store.SECURITY_LAYER.user.role == 'Viewer':
            self.table.read_only = True

        self.create_menu()
        self.create_footer()

    def create_menu(self):
        """
        Creates top bar menu
        :return: None
        """

        # add menubar at the top
        self.menubar = Menu(self.main, tearoff=False)
        self.main.config(menu=self.menubar)
        # create the file object)
        self.filemenu = Menu(self.menubar, tearoff=False)
        # New Employee
        # adds a command to the menu option, calling it exit
        if store.SECURITY_LAYER.can_create('employee'):
            self.filemenu.add_command(label="New Employee",
                                      command=self.event_handlers['new_employee'])
        if store.SECURITY_LAYER.can_create('receipt'):
            self.filemenu.add_command(label="New Receipt",
                                      command=self.event_handlers['new_receipt'])
        if store.SECURITY_LAYER.can_create('timesheet'):
            self.filemenu.add_command(label="New Timesheet",
                                      command=self.event_handlers['new_timesheet'])
        self.filemenu.add_separator()
        if store.SECURITY_LAYER.can_('payroll'):
            self.filemenu.add_command(label="Run Payroll",
                                      command=self.event_handlers['run_payroll'])
        self.filemenu.add_command(label="Change My Password",
                                  command=self.event_handlers['change_my_password'])
        self.filemenu.add_separator()
        # Logout
        self.filemenu.add_command(label="Logout", command=self.event_handlers['file>logout'])
        # added "file" to our menu
        self.menubar.add_cascade(label="File", menu=self.filemenu)
        # Reports Tab
        self.reports_menu = Menu(self.menubar, tearoff=False)
        # self.reports_menu.add_command(label="Paylog (CSV)",
        #                               command=None)
        self.reports_menu.add_command(label="Employee Directory (CSV)",
                                      command=self.event_handlers['export>employees'])
        self.reports_menu.add_command(label="Employee Directory (PDF)",
                                      command=self.event_handlers['export>pdf_employees'])
        self.menubar.add_cascade(label="Reports", menu=self.reports_menu)
        # Import tab
        self.import_menu = Menu(self.menubar, tearoff=False)
        self.import_menu.add_command(label="Employees (Legacy)",
                                     command=self.event_handlers['import>employees'])
        self.import_menu.add_command(label="Receipts",
                                     command=self.event_handlers['import>receipts'])
        self.import_menu.add_command(label="Timesheets",
                                     command=self.event_handlers['import>timesheets'])
        self.menubar.add_cascade(label="Import", menu=self.import_menu)
        # Admin tab
        if store.SECURITY_LAYER.user.role == 'Admin':
            self.admin_menu = Menu(self.menubar, tearoff=False)
            self.admin_menu.add_command(label="Review Change Requests",
                                        command=self.event_handlers['admin>review'])
            self.admin_menu.add_command(label="Set Passwords",
                                        command=self.event_handlers['admin>change_password'])
            self.menubar.add_cascade(label="Admin", menu=self.admin_menu)

        self.help_menu = Menu(self.menubar, tearoff=False)
        self.help_menu.add_command(label="About EmpDat",
                                   command=self.event_handlers['help>about_empdat'])
        self.menubar.add_cascade(label="Help", menu=self.help_menu)

    def create_footer(self):
        """
        Creates utility footer
        :return:
        """
        buttons = Frame(self.main)

        self.new_button = Button(
            buttons,
            text="New",
            command=self.event_handlers['new_employee'],
        )
        self.refresh_button = Button(
            buttons,
            text="Refresh",
            command=lambda: self.event_handlers['refresh']() if len(self.table.unsaved) > 0
                                                                and self.show_confirm(
                'Are you sure?', 'There are unsaved changes. Refresh anyway?') else None,
        )
        self.search_button = Button(
            buttons,
            text="Search",
            command=self.table.showFilteringBar,
        )
        self.delete_button = Button(
            buttons,
            text="Delete",
            state="disabled",
            command=self.event_handlers['delete'],
        )
        self.save_button = Button(
            buttons,
            text="Save",
            command=self.event_handlers['save'],
            state="disabled"
        )

        if store.SECURITY_LAYER.can_create('employee'):
            self.new_button.pack(side=LEFT, anchor=W)
        self.refresh_button.pack(side=LEFT, anchor=W)
        Label(buttons,
              text=f"({store.SECURITY_LAYER.user.first_name} "
              f"{store.SECURITY_LAYER.user.last_name})") \
            .pack(side=LEFT, anchor=W)

        self.status = Label(buttons, text='')
        self.status.pack(side=LEFT, anchor=W)

        Frame(buttons, relief='flat', borderwidth=0).pack(fill=X, expand=1)

        if store.SECURITY_LAYER.can_update('employee'):
            self.save_button.pack(side=RIGHT, anchor=E)
            self.delete_button.pack(side=RIGHT, anchor=E)
        self.search_button.pack(side=RIGHT, anchor=E)

        buttons.pack(side=RIGHT, fill=X, expand=1)

    def on_table_unsaved(self, is_unchanged: bool, row=None, col=None):
        """
        Called when there is something unsaved
        :param is_unchanged: is the table 'dirty'
        :param row: which row changed
        :param col: which col changed
        :return: None
        """
        self.set_save_state('normal' if not is_unchanged else 'disabled')
        if row and col:
            if not is_unchanged:
                self.table.model.setColorAt(row, col, 'gold')
            else:
                self.table.model.setColorAt(row, col, 'white')
            self.table.redrawTable()

    def on_before_save(self):
        """
        Called before Save
        :return: None
        """
        for row_name in self.table.unsaved:
            row_index = self.table.model.getRecordIndex(row_name)
            for col in range(0, self.table.model.getColumnCount()):
                self.table.model.setColorAt(row_index, col, 'white')
                self.table.redrawCell(row_index, col)

    def highlight_invalid_rows(self, ids):
        """
        Highlights invalid rows from IDs given
        :param ids: list of IDs (strings)
        :return: None
        """
        for row_id in ids:
            row_index = self.table.model.getRecordIndex(row_id)
            for col in range(0, self.table.model.getColumnCount()):
                self.table.model.setColorAt(row_index, col, 'coral')
                self.table.redrawCell(row_index, col)

    def highlight_invalid_cell(self, row_id, col_name):
        """
        Highlights invalid cells
        :param row_id: ID str
        :param col_name: Column string
        :return: None
        """
        row_index = self.table.model.getRecordIndex(row_id)
        col_index = self.table.model.getColumnIndex(col_name)

        self.table.model.setColorAt(row_index, col_index, 'coral')
        self.table.redrawCell(row_index, col_index)

    def set_save_state(self, state):
        """
        Sets state of the save button
        :param state: tkinter button state
        :return: None
        """
        self.save_button['state'] = state

    def set_delete_state(self, state):
        """
        Sets state of the delete button
        :param state: tkinter button state
        :return: None
        """
        self.delete_button['state'] = state

    def new_employee(self, new_id, view_model):
        """
        Handle employee creation
        :return:
        """
        record_id = f"NEW{new_id}"
        view_model['ID'] = record_id
        self.add_to_result(record_id, view_model)
        self.table.movetoSelectedRow(recname=record_id)
        self.table.set_yviews('moveto', 1)

    def add_to_result(self, record_id, to_add: dict):
        """
        Add a row to table
        :param record_id: record id
        :param to_add: view model to add
        :return: None
        """
        self.table.addRow(record_id, **to_add)

    def destroy_results(self):
        """
        Destroy all rows
        :return: None
        """
        keys = list(self.table.model.data.keys())
        for key in keys:
            self.table.model.deleteRow(key=key)

    def set_status(self, text: str):
        """
        Sets status text at the footer
        :param text: message to display
        :return: None
        """
        self.status.configure(text=text)


if __name__ == "__main__":
    db_page = DatabaseWindow({})
    db_page.mainloop()

# mainloop method will loop forever, waiting for events from the user,
# until the user exits the program -
# either by closing the window, or by terminating the program with a
# keyboard interrupt in the console.


# Time spent:
# 10/20: 2.5 hours
# 10/26: 0.25 hours

Classes

class DatabaseWindow (event_handlers)

Shows employee table

Adds the table and it's options

:param event_handlers: expects the following: 'new_employee' 'new_receipt' 'new_timesheet' 'run_payroll' 'save' 'delete' 'file>logout' 'import>employees' 'import>receipts' 'import>timesheets' 'admin>review' 'export>employees'

Expand source code
class DatabaseWindow(TkinterWindow):  # pylint: disable=too-many-instance-attributes
    """
    Shows employee table
    """

    def __init__(self, event_handlers):
        """
        Adds the table and it's options

        :param event_handlers: expects the following:
            'new_employee'
            'new_receipt'
            'new_timesheet'
            'run_payroll'
            'save'
            'delete'
            'file>logout'
            'import>employees'
            'import>receipts'
            'import>timesheets'
            'admin>review'
            'export>employees'
        """
        super().__init__(event_handlers)

        self.results = {}

        self.main = self.master
        self.main.geometry('1024x768')
        self.main.title('EmpDat')
        frame = Frame(self.main)
        frame.pack(fill=BOTH, expand=1)
        self.table = EmpDatTableCanvas(frame, col_modifiers={
            Employee.view_columns['id']: {  # ID
                'read_only': True
            },
            Employee.view_columns['role']: {  # Role
                'options': list(security.ROLES.keys())
            },
            Employee.view_columns['social_security_number']: {  # SSN
                'validator': 'ssn'
            },
            Employee.view_columns['start_date']: {  # Start Date
                'date': True,
                'validator': 'date',
            },
            Employee.view_columns['date_of_birth']: {  # DOB
                'date': True,
                'validator': 'date',
            },
            Employee.view_columns['sex']: {  # Sex
                'options': ['Male', 'Female', 'Other']
            },
            Employee.view_columns['state']: {  # State
                'validator': 'state_code'
            },
            Employee.view_columns['zipcode']: {  # Postal Code
                'validator': 'numeric'
            },
            Employee.view_columns['email']: {  # Email
                'validator': 'email'
            },
            Employee.view_columns['phone_number']: {  # Phone Number
                'validator': 'phone'
            },
            Employee.view_columns['emergency_contact_name']: {
                'validator': 'alpha'
            },
            Employee.view_columns['emergency_contact_phone']: {
                'validator': 'phone'
            },
            Employee.view_columns['classification']: {  # Classification
                'options': list(employee.classifications_dict.keys())
            },
            Employee.view_columns['payment_method']: {  # Payment Method
                'options': list(employee.pay_methods_dict.keys())
            },
            Employee.view_columns['salary']: {
                'validator': 'numeric'
            },
            Employee.view_columns['hourly_rate']: {
                'validator': 'numeric'
            },
            Employee.view_columns['commission_rate']: {
                'validator': 'numeric'
            },
            Employee.view_columns['bank_routing']: {
                'validator': 'bank_routing'
            },
            Employee.view_columns['bank_account']: {
                'validator': 'numeric'
            },
            Employee.view_columns['date_left']: {  # Date Left
                'date': True,
                'validator': 'date',
            },
        }, on_unsaved=self.on_table_unsaved,
                                       on_selected=lambda: self.set_delete_state('normal'),
                                       data=self.results, rowheight=50)
        self.table.show()

        if store.SECURITY_LAYER.user.role == 'Viewer':
            self.table.read_only = True

        self.create_menu()
        self.create_footer()

    def create_menu(self):
        """
        Creates top bar menu
        :return: None
        """

        # add menubar at the top
        self.menubar = Menu(self.main, tearoff=False)
        self.main.config(menu=self.menubar)
        # create the file object)
        self.filemenu = Menu(self.menubar, tearoff=False)
        # New Employee
        # adds a command to the menu option, calling it exit
        if store.SECURITY_LAYER.can_create('employee'):
            self.filemenu.add_command(label="New Employee",
                                      command=self.event_handlers['new_employee'])
        if store.SECURITY_LAYER.can_create('receipt'):
            self.filemenu.add_command(label="New Receipt",
                                      command=self.event_handlers['new_receipt'])
        if store.SECURITY_LAYER.can_create('timesheet'):
            self.filemenu.add_command(label="New Timesheet",
                                      command=self.event_handlers['new_timesheet'])
        self.filemenu.add_separator()
        if store.SECURITY_LAYER.can_('payroll'):
            self.filemenu.add_command(label="Run Payroll",
                                      command=self.event_handlers['run_payroll'])
        self.filemenu.add_command(label="Change My Password",
                                  command=self.event_handlers['change_my_password'])
        self.filemenu.add_separator()
        # Logout
        self.filemenu.add_command(label="Logout", command=self.event_handlers['file>logout'])
        # added "file" to our menu
        self.menubar.add_cascade(label="File", menu=self.filemenu)
        # Reports Tab
        self.reports_menu = Menu(self.menubar, tearoff=False)
        # self.reports_menu.add_command(label="Paylog (CSV)",
        #                               command=None)
        self.reports_menu.add_command(label="Employee Directory (CSV)",
                                      command=self.event_handlers['export>employees'])
        self.reports_menu.add_command(label="Employee Directory (PDF)",
                                      command=self.event_handlers['export>pdf_employees'])
        self.menubar.add_cascade(label="Reports", menu=self.reports_menu)
        # Import tab
        self.import_menu = Menu(self.menubar, tearoff=False)
        self.import_menu.add_command(label="Employees (Legacy)",
                                     command=self.event_handlers['import>employees'])
        self.import_menu.add_command(label="Receipts",
                                     command=self.event_handlers['import>receipts'])
        self.import_menu.add_command(label="Timesheets",
                                     command=self.event_handlers['import>timesheets'])
        self.menubar.add_cascade(label="Import", menu=self.import_menu)
        # Admin tab
        if store.SECURITY_LAYER.user.role == 'Admin':
            self.admin_menu = Menu(self.menubar, tearoff=False)
            self.admin_menu.add_command(label="Review Change Requests",
                                        command=self.event_handlers['admin>review'])
            self.admin_menu.add_command(label="Set Passwords",
                                        command=self.event_handlers['admin>change_password'])
            self.menubar.add_cascade(label="Admin", menu=self.admin_menu)

        self.help_menu = Menu(self.menubar, tearoff=False)
        self.help_menu.add_command(label="About EmpDat",
                                   command=self.event_handlers['help>about_empdat'])
        self.menubar.add_cascade(label="Help", menu=self.help_menu)

    def create_footer(self):
        """
        Creates utility footer
        :return:
        """
        buttons = Frame(self.main)

        self.new_button = Button(
            buttons,
            text="New",
            command=self.event_handlers['new_employee'],
        )
        self.refresh_button = Button(
            buttons,
            text="Refresh",
            command=lambda: self.event_handlers['refresh']() if len(self.table.unsaved) > 0
                                                                and self.show_confirm(
                'Are you sure?', 'There are unsaved changes. Refresh anyway?') else None,
        )
        self.search_button = Button(
            buttons,
            text="Search",
            command=self.table.showFilteringBar,
        )
        self.delete_button = Button(
            buttons,
            text="Delete",
            state="disabled",
            command=self.event_handlers['delete'],
        )
        self.save_button = Button(
            buttons,
            text="Save",
            command=self.event_handlers['save'],
            state="disabled"
        )

        if store.SECURITY_LAYER.can_create('employee'):
            self.new_button.pack(side=LEFT, anchor=W)
        self.refresh_button.pack(side=LEFT, anchor=W)
        Label(buttons,
              text=f"({store.SECURITY_LAYER.user.first_name} "
              f"{store.SECURITY_LAYER.user.last_name})") \
            .pack(side=LEFT, anchor=W)

        self.status = Label(buttons, text='')
        self.status.pack(side=LEFT, anchor=W)

        Frame(buttons, relief='flat', borderwidth=0).pack(fill=X, expand=1)

        if store.SECURITY_LAYER.can_update('employee'):
            self.save_button.pack(side=RIGHT, anchor=E)
            self.delete_button.pack(side=RIGHT, anchor=E)
        self.search_button.pack(side=RIGHT, anchor=E)

        buttons.pack(side=RIGHT, fill=X, expand=1)

    def on_table_unsaved(self, is_unchanged: bool, row=None, col=None):
        """
        Called when there is something unsaved
        :param is_unchanged: is the table 'dirty'
        :param row: which row changed
        :param col: which col changed
        :return: None
        """
        self.set_save_state('normal' if not is_unchanged else 'disabled')
        if row and col:
            if not is_unchanged:
                self.table.model.setColorAt(row, col, 'gold')
            else:
                self.table.model.setColorAt(row, col, 'white')
            self.table.redrawTable()

    def on_before_save(self):
        """
        Called before Save
        :return: None
        """
        for row_name in self.table.unsaved:
            row_index = self.table.model.getRecordIndex(row_name)
            for col in range(0, self.table.model.getColumnCount()):
                self.table.model.setColorAt(row_index, col, 'white')
                self.table.redrawCell(row_index, col)

    def highlight_invalid_rows(self, ids):
        """
        Highlights invalid rows from IDs given
        :param ids: list of IDs (strings)
        :return: None
        """
        for row_id in ids:
            row_index = self.table.model.getRecordIndex(row_id)
            for col in range(0, self.table.model.getColumnCount()):
                self.table.model.setColorAt(row_index, col, 'coral')
                self.table.redrawCell(row_index, col)

    def highlight_invalid_cell(self, row_id, col_name):
        """
        Highlights invalid cells
        :param row_id: ID str
        :param col_name: Column string
        :return: None
        """
        row_index = self.table.model.getRecordIndex(row_id)
        col_index = self.table.model.getColumnIndex(col_name)

        self.table.model.setColorAt(row_index, col_index, 'coral')
        self.table.redrawCell(row_index, col_index)

    def set_save_state(self, state):
        """
        Sets state of the save button
        :param state: tkinter button state
        :return: None
        """
        self.save_button['state'] = state

    def set_delete_state(self, state):
        """
        Sets state of the delete button
        :param state: tkinter button state
        :return: None
        """
        self.delete_button['state'] = state

    def new_employee(self, new_id, view_model):
        """
        Handle employee creation
        :return:
        """
        record_id = f"NEW{new_id}"
        view_model['ID'] = record_id
        self.add_to_result(record_id, view_model)
        self.table.movetoSelectedRow(recname=record_id)
        self.table.set_yviews('moveto', 1)

    def add_to_result(self, record_id, to_add: dict):
        """
        Add a row to table
        :param record_id: record id
        :param to_add: view model to add
        :return: None
        """
        self.table.addRow(record_id, **to_add)

    def destroy_results(self):
        """
        Destroy all rows
        :return: None
        """
        keys = list(self.table.model.data.keys())
        for key in keys:
            self.table.model.deleteRow(key=key)

    def set_status(self, text: str):
        """
        Sets status text at the footer
        :param text: message to display
        :return: None
        """
        self.status.configure(text=text)

Ancestors

Methods

def add_to_result(self, record_id, to_add: dict)

Add a row to table :param record_id: record id :param to_add: view model to add :return: None

Expand source code
def add_to_result(self, record_id, to_add: dict):
    """
    Add a row to table
    :param record_id: record id
    :param to_add: view model to add
    :return: None
    """
    self.table.addRow(record_id, **to_add)

Creates utility footer :return:

Expand source code
def create_footer(self):
    """
    Creates utility footer
    :return:
    """
    buttons = Frame(self.main)

    self.new_button = Button(
        buttons,
        text="New",
        command=self.event_handlers['new_employee'],
    )
    self.refresh_button = Button(
        buttons,
        text="Refresh",
        command=lambda: self.event_handlers['refresh']() if len(self.table.unsaved) > 0
                                                            and self.show_confirm(
            'Are you sure?', 'There are unsaved changes. Refresh anyway?') else None,
    )
    self.search_button = Button(
        buttons,
        text="Search",
        command=self.table.showFilteringBar,
    )
    self.delete_button = Button(
        buttons,
        text="Delete",
        state="disabled",
        command=self.event_handlers['delete'],
    )
    self.save_button = Button(
        buttons,
        text="Save",
        command=self.event_handlers['save'],
        state="disabled"
    )

    if store.SECURITY_LAYER.can_create('employee'):
        self.new_button.pack(side=LEFT, anchor=W)
    self.refresh_button.pack(side=LEFT, anchor=W)
    Label(buttons,
          text=f"({store.SECURITY_LAYER.user.first_name} "
          f"{store.SECURITY_LAYER.user.last_name})") \
        .pack(side=LEFT, anchor=W)

    self.status = Label(buttons, text='')
    self.status.pack(side=LEFT, anchor=W)

    Frame(buttons, relief='flat', borderwidth=0).pack(fill=X, expand=1)

    if store.SECURITY_LAYER.can_update('employee'):
        self.save_button.pack(side=RIGHT, anchor=E)
        self.delete_button.pack(side=RIGHT, anchor=E)
    self.search_button.pack(side=RIGHT, anchor=E)

    buttons.pack(side=RIGHT, fill=X, expand=1)
def create_menu(self)

Creates top bar menu :return: None

Expand source code
def create_menu(self):
    """
    Creates top bar menu
    :return: None
    """

    # add menubar at the top
    self.menubar = Menu(self.main, tearoff=False)
    self.main.config(menu=self.menubar)
    # create the file object)
    self.filemenu = Menu(self.menubar, tearoff=False)
    # New Employee
    # adds a command to the menu option, calling it exit
    if store.SECURITY_LAYER.can_create('employee'):
        self.filemenu.add_command(label="New Employee",
                                  command=self.event_handlers['new_employee'])
    if store.SECURITY_LAYER.can_create('receipt'):
        self.filemenu.add_command(label="New Receipt",
                                  command=self.event_handlers['new_receipt'])
    if store.SECURITY_LAYER.can_create('timesheet'):
        self.filemenu.add_command(label="New Timesheet",
                                  command=self.event_handlers['new_timesheet'])
    self.filemenu.add_separator()
    if store.SECURITY_LAYER.can_('payroll'):
        self.filemenu.add_command(label="Run Payroll",
                                  command=self.event_handlers['run_payroll'])
    self.filemenu.add_command(label="Change My Password",
                              command=self.event_handlers['change_my_password'])
    self.filemenu.add_separator()
    # Logout
    self.filemenu.add_command(label="Logout", command=self.event_handlers['file>logout'])
    # added "file" to our menu
    self.menubar.add_cascade(label="File", menu=self.filemenu)
    # Reports Tab
    self.reports_menu = Menu(self.menubar, tearoff=False)
    # self.reports_menu.add_command(label="Paylog (CSV)",
    #                               command=None)
    self.reports_menu.add_command(label="Employee Directory (CSV)",
                                  command=self.event_handlers['export>employees'])
    self.reports_menu.add_command(label="Employee Directory (PDF)",
                                  command=self.event_handlers['export>pdf_employees'])
    self.menubar.add_cascade(label="Reports", menu=self.reports_menu)
    # Import tab
    self.import_menu = Menu(self.menubar, tearoff=False)
    self.import_menu.add_command(label="Employees (Legacy)",
                                 command=self.event_handlers['import>employees'])
    self.import_menu.add_command(label="Receipts",
                                 command=self.event_handlers['import>receipts'])
    self.import_menu.add_command(label="Timesheets",
                                 command=self.event_handlers['import>timesheets'])
    self.menubar.add_cascade(label="Import", menu=self.import_menu)
    # Admin tab
    if store.SECURITY_LAYER.user.role == 'Admin':
        self.admin_menu = Menu(self.menubar, tearoff=False)
        self.admin_menu.add_command(label="Review Change Requests",
                                    command=self.event_handlers['admin>review'])
        self.admin_menu.add_command(label="Set Passwords",
                                    command=self.event_handlers['admin>change_password'])
        self.menubar.add_cascade(label="Admin", menu=self.admin_menu)

    self.help_menu = Menu(self.menubar, tearoff=False)
    self.help_menu.add_command(label="About EmpDat",
                               command=self.event_handlers['help>about_empdat'])
    self.menubar.add_cascade(label="Help", menu=self.help_menu)
def destroy_results(self)

Destroy all rows :return: None

Expand source code
def destroy_results(self):
    """
    Destroy all rows
    :return: None
    """
    keys = list(self.table.model.data.keys())
    for key in keys:
        self.table.model.deleteRow(key=key)
def highlight_invalid_cell(self, row_id, col_name)

Highlights invalid cells :param row_id: ID str :param col_name: Column string :return: None

Expand source code
def highlight_invalid_cell(self, row_id, col_name):
    """
    Highlights invalid cells
    :param row_id: ID str
    :param col_name: Column string
    :return: None
    """
    row_index = self.table.model.getRecordIndex(row_id)
    col_index = self.table.model.getColumnIndex(col_name)

    self.table.model.setColorAt(row_index, col_index, 'coral')
    self.table.redrawCell(row_index, col_index)
def highlight_invalid_rows(self, ids)

Highlights invalid rows from IDs given :param ids: list of IDs (strings) :return: None

Expand source code
def highlight_invalid_rows(self, ids):
    """
    Highlights invalid rows from IDs given
    :param ids: list of IDs (strings)
    :return: None
    """
    for row_id in ids:
        row_index = self.table.model.getRecordIndex(row_id)
        for col in range(0, self.table.model.getColumnCount()):
            self.table.model.setColorAt(row_index, col, 'coral')
            self.table.redrawCell(row_index, col)
def new_employee(self, new_id, view_model)

Handle employee creation :return:

Expand source code
def new_employee(self, new_id, view_model):
    """
    Handle employee creation
    :return:
    """
    record_id = f"NEW{new_id}"
    view_model['ID'] = record_id
    self.add_to_result(record_id, view_model)
    self.table.movetoSelectedRow(recname=record_id)
    self.table.set_yviews('moveto', 1)
def on_before_save(self)

Called before Save :return: None

Expand source code
def on_before_save(self):
    """
    Called before Save
    :return: None
    """
    for row_name in self.table.unsaved:
        row_index = self.table.model.getRecordIndex(row_name)
        for col in range(0, self.table.model.getColumnCount()):
            self.table.model.setColorAt(row_index, col, 'white')
            self.table.redrawCell(row_index, col)
def on_table_unsaved(self, is_unchanged: bool, row=None, col=None)

Called when there is something unsaved :param is_unchanged: is the table 'dirty' :param row: which row changed :param col: which col changed :return: None

Expand source code
def on_table_unsaved(self, is_unchanged: bool, row=None, col=None):
    """
    Called when there is something unsaved
    :param is_unchanged: is the table 'dirty'
    :param row: which row changed
    :param col: which col changed
    :return: None
    """
    self.set_save_state('normal' if not is_unchanged else 'disabled')
    if row and col:
        if not is_unchanged:
            self.table.model.setColorAt(row, col, 'gold')
        else:
            self.table.model.setColorAt(row, col, 'white')
        self.table.redrawTable()
def set_delete_state(self, state)

Sets state of the delete button :param state: tkinter button state :return: None

Expand source code
def set_delete_state(self, state):
    """
    Sets state of the delete button
    :param state: tkinter button state
    :return: None
    """
    self.delete_button['state'] = state
def set_save_state(self, state)

Sets state of the save button :param state: tkinter button state :return: None

Expand source code
def set_save_state(self, state):
    """
    Sets state of the save button
    :param state: tkinter button state
    :return: None
    """
    self.save_button['state'] = state
def set_status(self, text: str)

Sets status text at the footer :param text: message to display :return: None

Expand source code
def set_status(self, text: str):
    """
    Sets status text at the footer
    :param text: message to display
    :return: None
    """
    self.status.configure(text=text)

Inherited members