ITECH1400 - Foundations of Programming
Assignment - Money Manager
!!Get the Best Assignment Help Service for ITECH1400 Foundations of Programming Assignment!!
Assignment Overview
You are tasked with creating an application that uses a GUI that simulates a simple money manager. This tool is used to track all spending of an individual.
Documentation
Source Code:
moneymanager.py
class MoneyManager():
def __init__(self):
'''Constructor to set user_number to -555, pin_number to an empty string,
balance to 0.0, and transaction_list to an empty list.'''
self.user_number=-555
self.pin_number=""
self.balance=0.0
self.transaction_list=[]
def add_entry(self, amount, entry_type):
'''Function to add entry and amount to the tool. Raises an
exception if it receives a value for amount that cannot be cast to float. Raises an exception
if the entry_type is not valid - i.e. not food, rent, bills, entertainment or other'''
try:
if entry_type not in ["Bills", "Entertainment", "Food","Other", "Rent"]:
raise Exception("Entry type is not in the list")
entry_amount_float=float(amount)
if entry_amount_float>self.balance:
raise Exception("Entry Amount is more than the user balance")
self.balance = self.balance - entry_amount_float
self.transaction_list.append((entry_type,entry_amount_float))
except ValueError:
raise Exception("Entry Amount conversion is unsuccessful")
def deposit_funds(self, amount):
'''Function to deposit an amount to the user balance. Raises an
exception if it receives a value that cannot be cast to float. '''
try:
deposit_amount_float=float(amount)
self.balance = self.balance + deposit_amount_float
self.transaction_list.append(("Deposit",deposit_amount_float))
except ValueError:
raise Exception("Deposit Amount conversion is unsuccessful")
def get_transaction_string(self):
'''Function to create and return a string of the transaction list. Each transaction
consists of two lines - either the word "Deposit" or the entry type - food etc - on
the first line, and then the amount deposited or entry amount on the next line.'''
data_string = ""
for transaction_entry in self.transaction_list:
data_string += transaction_entry[0] + "\n"
data_string += str(transaction_entry[1]) + "\n"
return data_string
def save_to_file(self):
'''Function to overwrite the user text file with the current user
details. user number, pin number, and balance (in that
precise order) are the first three lines - there are then two lines
per transaction as outlined in the above 'get_transaction_string'
function.'''
filename=str(self.user_number) + ".txt"
with open(filename, "w") as out_file:
out_file.write(str(self.user_number)+"\n")
out_file.write(self.pin_number+"\n")
out_file.write(str(self.balance)+"\n")
out_file.write(self.get_transaction_string())
testmoneymanager.py
import unittest
from moneymanager import MoneyManager
class TestMoneyManager(unittest.TestCase):
def setUp(self):
# Create a test BankAccount object
self.user = MoneyManager()
# Provide it with some initial balance values
self.user.balance = 1000.0
def test_legal_deposit_works(self):
# Your code here to test that depsositing money using the account's
# 'deposit_funds' function adds the amount to the balance.
self.user.deposit_funds(400)
self.assertEqual(self.user.balance, 1400.0)
def test_illegal_deposit_raises_exception(self):
# Your code here to test that depositing an illegal value (like 'bananas'
# or such - something which is NOT a float) results in an exception being
# raised.
self.assertRaises(Exception, self.user.deposit_funds,'amount')
def test_legal_entry(self):
# Your code here to test that adding a new entry with a a legal amount subtracts the
# funds from the balance.
self.user.add_entry(200.0,'Rent')
self.assertEqual(self.user.balance, 800.0)
def test_illegal_entry_amount(self):
# Your code here to test that withdrawing an illegal amount (like 'bananas'
# or such - something which is NOT a float) raises a suitable exception.
self.assertRaises(Exception, self.user.add_entry,'amount','Other')
def test_illegal_entry_type(self):
# Your code here to test that adding an illegal entry type raises a
# suitable exception.
self.assertRaises(Exception, self.user.add_entry,900.0,'Loan')
def test_insufficient_funds_entry(self):
# Your code here to test that you can only spend funds which are available.
# For example, if you have a balance of 500.00 dollars then that is the maximum
# that can be spent. If you tried to spend 600.00 then a suitable exception
# should be raised and the withdrawal should NOT be applied to the user balance
# or the user's transaction list.
self.assertRaises(Exception, self.user.add_entry,2000,'Entertainment')
# Run the unit tests in the above test case
unittest.main()
main.py
import tkinter as tk
from tkinter import *
from tkinter import messagebox
from pylab import plot, show, xlabel, ylabel
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from collections import defaultdict
from pprint import pprint
import matplotlib.pyplot as plt
from moneymanager import MoneyManager
win = tk.Tk()
#Set window size here to '500 x 660'
win.geometry('500x660')
#Set the window title to 'FedUni Money Manager'
win.winfo_toplevel().title("FedUni Money Manager")
#The user number and associated variable
user_number_var = tk.StringVar()
#This is set as a default for ease of testing
user_number_var.set('123456')
user_number_entry = tk.Entry(win, textvariable=user_number_var)
user_number_entry.focus_set()
#The pin number entry and associated variables
pin_number_var = tk.StringVar()
#This is set as a default for ease of testing
pin_number_var.set('7890')
#Modify the following to display a series of * rather than the pin ie **** not 1234
user_pin_entry = tk.Entry(win, text='PIN Number', textvariable=pin_number_var, show='*')
#set the user file by default to an empty string
user_file = ''
# The balance label and associated variable
balance_var = tk.StringVar()
balance_var.set('Balance: $0.00')
balance_label = tk.Label(win, textvariable=balance_var)
# The Entry widget to accept a numerical value to deposit or withdraw
#amount_var = tk.StringVar()
tkVar=StringVar(win)
amount_entry = tk.Entry(win)
entry_type=tk.Entry(win)
# The transaction text widget holds text of the transactions
transaction_text_widget = tk.Text(win, height=10, width=48)
# The money manager object we will work with
user = MoneyManager()
# ---------- Button Handlers for Login Screen ----------
def clear_pin_entry(event):
'''Function to clear the PIN number entry when the Clear / Cancel button is clicked.'''
# Clear the pin number entry here
user_pin_entry.delete(0, tk.END)
def handle_pin_button(event):
'''Function to add the number of the button clicked to the PIN number entry.'''
# Limit to 4 chars in length
pin_entry_length=len(user_pin_entry.get())
if pin_entry_length<4:
# Set the new pin number on the pin_number_var
user_pin_entry.insert(pin_entry_length,event.widget["text"])
pin_number_var = user_pin_entry.get()
def log_in(event):
'''Function to log in to the banking system using a known user number and PIN.'''
global user
global pin_number_var
global user_file
global user_num_entry
# Create the filename from the entered account number with '.txt' on the end
filename = str(user_number_entry.get()) + ".txt"
# Try to open the user file for reading
try:
# Open the user file for reading
user_file=open(filename, "r")
# First line is user number
user.user_number=int(read_line_from_user_file())
# Second line is PIN number, raise exception if the PIN entered doesn't match account PIN read
user.pin_number=read_line_from_user_file()
if not (user_pin_entry.get() == user.pin_number):
raise Exception("PIN entered does not match user PIN")
# Read third - balance
user.balance = float(read_line_from_user_file())
# Section to read account transactions from file - start an infinite 'do-while' loop here
while True:
# Attempt to read a line from the account file, break if we've hit the end of the file.
line = read_line_from_user_file()
if not line:
break
trans_type=line
# If we read a line then it's the transaction type, so read the next line which will be the transaction amount.
trans_amount=float(read_line_from_user_file())
# and then create a tuple from both lines and add it to the account's transaction_list
user.transaction_list.append((trans_type,trans_amount))
# Close the file now we're finished with it
user_file.close()
# Catch exception if we couldn't open the file or PIN entered did not match account PIN
except Exception as ex:
# Show error messagebox and & reset MoneyManager object to default...
messagebox.showerror("Error", str(ex))
user = MoneyManager()
# ...also clear PIN entry and change focus to account number entry
user_pin_entry.delete(0, tk.END)
user_number_entry.focus_set()
# Got here without raising an exception? Then we can log in - so remove the widgets and display the user screen
else:
remove_all_widgets()
create_user_screen()
# ---------- Button Handlers for User Screen ----------
def save_and_log_out():
'''Function to overwrite the user file with the current state of
the user object (i.e. including any new transactions), remove
all widgets and display the login screen.'''
global user
# Save the account with any new transactions
user.save_to_file()
# Reset the money manager object
user = MoneyManager()
# Reset the account number and pin to blank
user_number_entry.delete(0, tk.END)
user_pin_entry.delete(0, tk.END)
# Remove all widgets and display the login screen again
remove_all_widgets()
create_login_screen()
def perform_deposit():
'''Function to add a deposit for the amount in the amount entry to the
user's transaction list.'''
global user
global amount_entry
global balance_label
global balance_var
# Try to increase the account balance and append the deposit to the account file
try:
# Get the cash amount to deposit. Note: We check legality inside account's deposit method
deposit_amount=amount_entry.get()
# Deposit funds
user.deposit_funds(deposit_amount)
# Update the transaction widget with the new transaction by calling account.get_transaction_string()
# Note: Configure the text widget to be state='normal' first, then delete contents, then instert new
# contents, and finally configure back to state='disabled' so it cannot be user edited.
transaction_text_widget.config(state='normal')
transaction_text_widget.delete(1.0,tk.END)
transaction_text_widget.insert(tk.INSERT, user.get_transaction_string())
transaction_text_widget.config(state='disabled')
# Change the balance label to reflect the new balance
balance_var.set("Balance :$" + str(user.balance))
# Clear the amount entry
amount_entry.delete(0, tk.END)
# Update the spending graph
plot_spending_graph()
# Catch and display exception as a 'showerror' messagebox with a title of 'Transaction Error' and the text of the exception
except Exception as exception:
messagebox.showerror("Transaction Error", str(exception))
def perform_transaction():
'''Function to add the entry the amount in the amount entry from the user balance and add an entry to the transaction list.'''
global user
global amount_entry
global balance_label
global balance_var
global entry_type
# Try to decrease the account balance and append the deposit to the account file
try:
# Get the cash amount to use. Note: We check legality inside account's withdraw_funds method
spending_amount=amount_entry.get()
# Get the type of entry that will be added ie rent etc
type_of_entry=tkVar.get()
# Add entry to the manger
user.add_entry(spending_amount,type_of_entry)
# Update the transaction widget with the new transaction by calling user.get_transaction_string()
# Note: Configure the text widget to be state='normal' first, then delete contents, then instert new
# contents, and finally configure back to state='disabled' so it cannot be user edited.
transaction_text_widget.config(state='normal')
transaction_text_widget.delete(1.0,tk.END)
transaction_text_widget.insert(tk.INSERT, user.get_transaction_string())
transaction_text_widget.config(state='disabled')
# Change the balance label to reflect the new balance
balance_var.set("Balance :$" + str(user.balance))
# Clear the amount entry
amount_entry.delete(0, tk.END)
# Update the graph
plot_spending_graph()
# Catch and display any returned exception as a messagebox 'showerror'
except Exception as exception:
messagebox.showerror("Tansaction Error", str(exception))
def remove_all_widgets():
'''Function to remove all the widgets from the window.'''
global win
for widget in win.winfo_children():
widget.grid_remove()
def read_line_from_user_file():
'''Function to read a line from the users file but not the last newline character.
Note: The user_file must be open to read from for this function to succeed.'''
global user_file
return user_file.readline()[0:-1]
def plot_spending_graph():
'''Function to plot the user spending here.'''
# YOUR CODE to generate the x and y lists here which will be plotted
x_values= ["Bills", "Entertainment", "Food","Other", "Rent"]
y_values=[0.0]*5
for index in range(len(x_values)):
for transaction_entry in user.transaction_list:
if x_values[index] == transaction_entry[0]:
y_values[index]+=transaction_entry[1]
#Your code to display the graph on the screen here - do this last
figure = Figure(figsize=(7,3), dpi=100)
figure.suptitle("Spending Graph")
sub_plot = figure.add_subplot(1,1,1)
sub_plot.bar(x_values, y_values, 0.6,color='brown')
sub_plot.grid()
figure_canvas = FigureCanvasTkAgg(figure, master=win)
figure_canvas.show()
figure_widget = figure_canvas.get_tk_widget()
figure_widget.grid(row=5, column=0, columnspan=5, sticky="nsew")
# ---------- UI Drawing Functions ----------
def create_login_screen():
'''Function to create the login screen.'''
# ----- Row 0 -----
# 'FedUni Money Manager' label here. Font size is 28.
tk.Label(win, text="FedUni Money Manager", font=("Times New Roman", 28)).grid(row=0, column=0, columnspan=3)
# ----- Row 1 -----
# Acount Number / Pin label here
tk.Label(win, text="User Number/PIN").grid(row=1,column = 0)
# Account number entry here
user_number_entry.grid(row=1, column=1, ipadx = 30, ipady = 20)
# Account pin entry here
user_pin_entry.grid(row=1, column=2, ipadx = 30, ipady = 20)
# ----- Row 2 -----
# Buttons 1, 2 and 3 here. Buttons are bound to 'handle_pin_button' function via '<Button-1>' event.
button_1 = tk.Button(win, text="1", width=23, height=8)
button_1.grid(row=2, column=0)
button_1.bind("<Button-1>", handle_pin_button)
button_2 = tk.Button(win, text="2", width=23, height=8)
button_2.grid(row=2, column=1)
button_2.bind("<Button-1>", handle_pin_button)
button_3 = tk.Button(win, text="3", width=23, height=8)
button_3.grid(row=2, column=2)
button_3.bind("<Button-1>", handle_pin_button)
# ----- Row 3 -----
# Buttons 4, 5 and 6 here. Buttons are bound to 'handle_pin_button' function via '<Button-1>' event.
button_4 = tk.Button(win, text="4", width=23, height=8)
button_4.grid(row=3, column=0)
button_4.bind("<Button-1>", handle_pin_button)
button_5 = tk.Button(win, text="5", width=23, height=8)
button_5.grid(row=3, column=1)
button_5.bind("<Button-1>", handle_pin_button)
button_6 = tk.Button(win, text="6", width=23, height=8)
button_6.grid(row=3, column=2)
button_6.bind("<Button-1>", handle_pin_button)
# ----- Row 4 -----
# Buttons 7, 8 and 9 here. Buttons are bound to 'handle_pin_button' function via '<Button-1>' event.
button_7 = tk.Button(win, text="7", width=23, height=8)
button_7.grid(row=4, column=0)
button_7.bind("<Button-1>", handle_pin_button)
button_8 = tk.Button(win, text="8", width=23, height=8)
button_8.grid(row=4, column=1)
button_8.bind("<Button-1>", handle_pin_button)
button_9 = tk.Button(win, text="9", width=23, height=8)
button_9.grid(row=4, column=2)
button_9.bind('<Button-1>', handle_pin_button)
# ----- Row 5 -----
# Cancel/Clear button here. 'bg' and 'activebackground' should be 'red'. But calls 'clear_pin_entry' function.
button_clear=tk .Button(win, text="Cancel/Clear", width=23, height=10, bg = "red", activebackground = "red")
button_clear.grid(row=5, column=0)
button_clear.bind("<Button-1>", clear_pin_entry)
# Button 0 here
button_0 = tk.Button(win, text="0", width=23, height=10)
button_0.grid(row=5, column=1)
button_0.bind("<Button-1>", handle_pin_button)
# Login button here. 'bg' and 'activebackground' should be 'green'). Button calls 'log_in' function.
button_login=tk.Button(win, text="Login", width=23, height=10, bg = "green", activebackground = "green")
button_login.grid(row=5, column=2)
button_login.bind("<Button-1>", log_in)
# ----- Set column & row weights -----
# Set column and row weights. There are 5 columns and 6 rows (0..4 and 0..5 respectively)
win.grid_columnconfigure(0, weight=1)
win.grid_columnconfigure(1, weight=1)
win.grid_columnconfigure(2, weight=1)
win.grid_rowconfigure(0, weight=1)
win.grid_rowconfigure(1, weight=1)
win.grid_rowconfigure(2, weight=1)
win.grid_rowconfigure(3, weight=1)
win.grid_rowconfigure(4, weight=1)
win.grid_rowconfigure(5, weight=1)
def create_user_screen():
'''Function to create the user screen.'''
global amount_text
global amount_label
global transaction_text_widget
global balance_var
# ----- Row 0 -----
# FedUni Banking label here. Font size should be 24.
tk.Label(win, text='FedUni Money Manager', font=("Times New Roman", 22)).grid(row=0, column=0, columnspan=5)
# ----- Row 1 -----
# Account number label here
tk.Label(win, text=("User Number:"+str(user.user_number))).grid(row=1, column=0)
# Balance label here
balance_label.grid(row=1, column=2)
balance_var.set("Balance: $" + str(user.balance))
# Log out button here
btn_log_out=tk.Button(win, text="Log Out", width=18, height = 3, command=save_and_log_out).grid(row=1, column=4)
# ----- Row 2 -----
# Amount label here
tk.Label(win, text="Amount($)").grid(row=2, column=0)
# Amount entry here
amount_entry.grid(row=2, column=2, ipadx = 10, ipady = 10)
# Deposit button here
btn_deposit=tk.Button(win, text="Deposit", width=18, height = 3, command=perform_deposit)
btn_deposit.grid(row=2, column=4)
# NOTE: Bind Deposit and Withdraw buttons via the command attribute to the relevant deposit and withdraw
# functions in this file. If we "BIND" these buttons then the button being pressed keeps looking as
# if it is still pressed if an exception is raised during the deposit or withdraw operation, which is
# offputting.
# ----- Row 3 -----
# Entry type label here
entry_label=tk.Label(win, text="Entry Type")
entry_label.grid(row=3, column=0)
# Entry drop list here
options=["Food", "Rent", "Bills", "Entertainment", "Other"]
entry_type_options=tk.OptionMenu(win,tkVar,*options)
tkVar.set(options[0])
entry_type_options.grid(row=3, column=2)
# Add entry button here
add_entry_btn=tk.Button(win, text='Add Entry', width=18, height = 3, command=perform_transaction)
add_entry_btn. grid(row=3, column=4)
# ----- Row 4 -----
# Declare scrollbar (text_scrollbar) here (BEFORE transaction text widget)
text_scrollbar = tk.Scrollbar(win)
# Add transaction Text widget and configure to be in 'disabled' mode so it cannot be edited.
transaction_text_widget.grid(row = 4, column=0, columnspan=5)
transaction_text_widget.config(state = 'disabled')
# Note: Set the yscrollcommand to be 'text_scrollbar.set' here so that it actually scrolls the Text widget
transaction_text_widget['yscrollcommand'] = text_scrollbar.set
# Note: When updating the transaction text widget it must be set back to 'normal mode' (i.e. state='normal') for it to be edited
transaction_text_widget.config(state='normal')
transaction_text_widget.delete(1.0,tk.END)
transaction_text_widget.insert(tk.INSERT, user.get_transaction_string())
transaction_text_widget.config(state='disabled')
# Now add the scrollbar and set it to change with the yview of the text widget
text_scrollbar.grid(row =4, column = 5, ipady = 50)
text_scrollbar.configure(command = transaction_text_widget.yview)
# ----- Row 5 - Graph -----
# Call plot_interest_graph() here to display the graph
plot_spending_graph()
# ----- Set column & row weights -----
# Set column and row weights here - there are 6 rows and 5 columns (numbered 0 through 4 not 1 through 5!)
win.grid_columnconfigure(0, weight=1)
win.grid_columnconfigure(1, weight=1)
win.grid_columnconfigure(2, weight=1)
win.grid_columnconfigure(3, weight=1)
win.grid_columnconfigure(4, weight=1)
win.grid_rowconfigure(0, weight=1)
win.grid_rowconfigure(1, weight=1)
win.grid_rowconfigure(2, weight=1)
win.grid_rowconfigure(3, weight=1)
win.grid_rowconfigure(4, weight=1)
win.grid_rowconfigure(5, weight=1)
# ---------- Display Login Screen & Start Main loop ----------
create_login_screen()
win.mainloop()
Output:
The assignment is broken up into the following main components:
1.) The ability to provide a login screen to prevent other people from viewing the information contained in the tool
2.) The ability to view the current balance funds, add purchases made and deposits into the balance
3.) The ability to save the transactions to a file so that you can log in, add transactions to the application, log out and then return to find all of the transactions still there - i.e. the data is persistent.
4.) The ability to display a graph of your spending broken down by type - i.e. food, bills, rent.
5.) A Test Case that ensures that your simple money manager works as required.
!!Avail High Quality ITECH1400 Foundations of Programming Assignment Help and Tutoring Services at Expertsminds!!