Let’s Build A Simple Expense Tracker


If you’ve ever tried building a budget, you know how difficult it can be to get an expense tracker that does what you want it to do. But how about building one yourself? Let’s learn Python basics by creating a simple expense tracker that you can actually use.




Defining Requirements of our Expense Tracking App

When I came up with this idea, I wanted to create an app that’s more than just another command-line terminal app. We’ve built a few of those (like the simple To-Do List using Python). For this one, I wanted to use some GUI, so I decided we’ll be importing the Tkinter library to have some usable UI elements.

Libraries allow us to reuse code. There’s usually a library for most things you might want to do in Python. Importing them avoids rewriting all the code they contain from scratch.

In this app, we’re going to implement:

  • Expense entry
  • Defining a budget
  • Comparing our expenses to our budget
  • View spending history

At the end of this, we’ll have a decent idea of how Python works and be able to complete our first GUI-based Python app.


Setting Up Our Project

You should check to ensure that your device has Python installed by checking the Python version. I’ve already covered how to link my favorite IDE (Visual Studio) to Python. Once you’ve gotten through the overhead of installing Python on your device and getting it updated to the current version, we can start by creating a new project.

I chose Python for this project because it’s one of the easiest languages for beginners. Let’s dive into building our expense tracker!

Building the Main Application Window

Since we’re going to be moving away from the terminal in this application, we’ll instead need to set up Tkinter to build out our application’s graphical user interface (GUI). UI design in Python can be complex, so I’ll keep it simple and just tell you what the code I’m giving you does. Here’s how my UI window is going to be defined. We’ll start by importing Tkinter:


import tkinter as tk
from tkinter import ttk, messagebox, simpledialog

Tkinter includes all the functions we need to create a basic UI. You’ll notice that we’re also importing Ttk, a theming engine inside Tkinter. This allows us to control how our UI looks if we wish. We’re also going to use messagebox and simpledialog to prompt the user for the initial budget setting. We also want to be able to save monthly data in case we want to access it in the future, so we’ll also add at the top of our file:

import json
from datetime import datetime

Aside from being very close to my own name, JSON is actually quite cool as a serialization system. Luckily, it comes standard with Python, so we can import it directly. The datetime import helps us to keep our dates formatted properly across the whole application.

Now that we’ve figured out the imports let’s actually build the window:

def create_widgets(self):
    
    main_frame = ttk.Frame(self.root, padding="10")
    main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

    
    input_frame = ttk.LabelFrame(main_frame, text="Add Expense", padding="10")
    input_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5, pady=5)

    ttk.Label(input_frame, text="Date (YYYY-MM-DD):").grid(row=0, column=0, sticky=tk.W)
    self.date_entry = ttk.Entry(input_frame)
    self.date_entry.grid(row=0, column=1, padx=5, pady=2)

    ttk.Label(input_frame, text="Category:").grid(row=1, column=0, sticky=tk.W)
    self.category_combobox = ttk.Combobox(input_frame, values=self.categories)
    self.category_combobox.grid(row=1, column=1, padx=5, pady=2)

    ttk.Label(input_frame, text="Amount:").grid(row=2, column=0, sticky=tk.W)
    self.amount_entry = ttk.Entry(input_frame)
    self.amount_entry.grid(row=2, column=1, padx=5, pady=2)

    ttk.Button(input_frame, text="Add Expense", command=self.add_expense).grid(row=3, column=0, columnspan=2, pady=5)

    
    category_frame = ttk.LabelFrame(main_frame, text="Manage Categories", padding="10")
    category_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5, pady=5)

    self.new_category_entry = ttk.Entry(category_frame)
    self.new_category_entry.grid(row=0, column=0, padx=5, pady=2)
    ttk.Button(category_frame, text="Add Category", command=self.add_category).grid(row=0, column=1, padx=5, pady=2)

These entries are just styling our window. If you like, you can play around with the numbers and see what you get since fiddling around with them is harmless (aside from making the final window look terrible). We also need to display frame and the save button, and configure the grid so that it looks nice:


 
 display_frame = ttk.LabelFrame(main_frame, text="Expenses and Budget", padding="10")
 display_frame.grid(row=0, column=1, rowspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5, pady=5)

 self.expense_tree = ttk.Treeview(display_frame, columns=('Date', 'Category', 'Amount'), show='headings')
 self.expense_tree.heading('Date', text='Date')
 self.expense_tree.heading('Category', text='Category')
 self.expense_tree.heading('Amount', text='Amount')
 self.expense_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

 scrollbar = ttk.Scrollbar(display_frame, orient=tk.VERTICAL, command=self.expense_tree.yview)
 scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
 self.expense_tree.configure(yscrollcommand=scrollbar.set)

 self.budget_label = ttk.Label(display_frame, text=f"Monthly Budget: ${self.monthly_budget:.2f}")
 self.budget_label.grid(row=1, column=0, sticky=tk.W, pady=2)

 self.total_expenses_label = ttk.Label(display_frame, text="Total Expenses: $0.00")
 self.total_expenses_label.grid(row=2, column=0, sticky=tk.W, pady=2)

 self.remaining_budget_label = ttk.Label(display_frame, text=f"Remaining Budget: ${self.monthly_budget:.2f}")
 self.remaining_budget_label.grid(row=3, column=0, sticky=tk.W, pady=2)

 
 ttk.Button(display_frame, text="Save Data", command=self.save_data).grid(row=4, column=0, pady=10)

 
 self.root.columnconfigure(0, weight=1)
 self.root.rowconfigure(0, weight=1)
 main_frame.columnconfigure(1, weight=1)
 main_frame.rowconfigure(0, weight=1)
 display_frame.columnconfigure(0, weight=1)
 display_frame.rowconfigure(0, weight=1)

As you can see, the UI takes a lot of coding to get right. However, since you’re planning to learn Python basics, understanding UI design is a part of the whole. Now that we’ve got the UI up and running, let’s get working on the functionality.

Defining Categories and Adding Expenses

To give our app a bit of “body,” I predefined a few expense categories. I included those as headers along with the rest of our definitions:

class ExpenseTrackerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Expense Tracker & Budget Planner")
        self.root.geometry("1100x500")

        self.expenses = []
        self.categories = ["Rent", "Food", "Entertainment", "Car", "Credit Cards"]
        
        self.monthly_budget = self.get_initial_budget()
        
        self.create_widgets()

    def get_initial_budget(self):
        while True:
            budget = simpledialog.askfloat("Monthly Budget", "Enter your monthly budget:", minvalue=0.01)
            if budget is not None:
                return budget
            else:
                if messagebox.askyesno("No Budget", "You haven't entered a budget. Do you want to exit?"):
                    self.root.quit()
                    return 0

Definitions like these should be at the top of our program. To that end, this snippet should come before the widget creation code we have already developed.

The get_initial_budget(self) function creates a popup at the start of the app’s run to collect data determining the amount of money you have available. Failing to enter a value causes the app to shut down immediately.


We also need functionality to add expenses and update categories with new, custom ones. Luckily, this is also simple to implement:

def add_expense(self):
    try:
        date = self.date_entry.get()
        category = self.category_combobox.get()
        amount = float(self.amount_entry.get())

        self.expenses.append({
            'date': date,
            'category': category,
            'amount': amount
        })

        self.update_expense_list()
        self.update_budget_info()
        self.clear_input_fields()
    except ValueError:
        messagebox.showerror("Error", "Invalid input. Please check your entries.")

def add_category(self):
    new_category = self.new_category_entry.get().strip()
    if new_category and new_category not in self.categories:
        self.categories.append(new_category)
        self.category_combobox['values'] = self.categories
        self.new_category_entry.delete(0, tk.END)
        messagebox.showinfo("Category Added", f"'{new_category}' has been added to the categories.")
    else:
        messagebox.showerror("Error", "Invalid category name or category already exists.")

To round out our basic functionality, we’ll need functions to update the expense list when we enter a new expense. We’ll need a helper function to clear the combo box after we’ve entered the data. And finally, we’ll need to calculate how much of our budget we still have available. This simple code snippet will allow us to do just that:

def update_expense_list(self):
    for item in self.expense_tree.get_children():
        self.expense_tree.delete(item)
    for expense in self.expenses:
        self.expense_tree.insert('', 'end', values=(
            expense['date'],
            expense['category'],
            f"${expense['amount']:.2f}"
        ))

def update_budget_info(self):
    total_expenses = sum(expense['amount'] for expense in self.expenses)
    remaining_budget = self.monthly_budget - total_expenses

    self.total_expenses_label.config(text=f"Total Expenses: ${total_expenses:.2f}")
    self.remaining_budget_label.config(text=f"Remaining Budget: ${remaining_budget:.2f}")

def clear_input_fields(self):
    self.date_entry.delete(0, tk.END)
    self.category_combobox.set('')
    self.amount_entry.delete(0, tk.END)

So, after we enter all this data, how do we ensure we don’t lose it? I’ve implemented a simple save function using the JSON format we imported earlier:

def save_data(self):
    data = {
        'budget': self.monthly_budget,
        'categories': self.categories,
        'expenses': self.expenses
    }
    
    
    current_date = datetime.now().strftime("%Y-%m")
    default_filename = f"expense_data_{current_date}.json"
    
    file_path = filedialog.asksaveasfilename(
        defaultextension=".json",
        filetypes=[("JSON files", "*.json")],
        initialfile=default_filename
    )
    
    if file_path:
        with open(file_path, 'w') as f:
            json.dump(data, f, indent=2)
        messagebox.showinfo("Save Successful", f"Data saved to {file_path}")

This function serializes the data and saves it in a particular file location with the current date appended to the filename. The final thing we have to do is put together the main() function loop:

if __name__ == "__main__":
    root = tk.Tk()
    app = ExpenseTrackerApp(root)
    root.mainloop()

That should allow our expense tracker to run without any issues. Congratulations on completing your first GUI-based application in Python!


Additions, Shortcomings, and Customization

After completing this, you understand a little bit about how Tkinter works and how you can use it for GUI creation. If you know a bit of CSS or some graphic design, you should be able to figure out how to use Tkinter quickly. If you’d like a few challenges, you could try these things next:

  • Build or use a new theme for your app.
  • Create a “load” function and button for it. I specifically left out the load function to give you more room to explore.
  • Explore how you could do a graphically-based breakdown of your budget as a pie chart.

The final application should currently look something like this:

Basic Expense App


Where To Go From Here?

This development tutorial only gave you a broad overview of Tkinter, but if you’re planning on doing more GUI-based apps, you’ll have to get a better grasp of what it can do. RealPython can give you an idea of how to use Tkinter or any of the other available GUI frameworks or toolkits. Python is pretty easy to grasp at a base level, but there’s so much you can explore as you learn Python basics. The only thing limiting what you can create is your imagination.

If you’re getting stuck on the code and want my full Python script to compare to yours, it’s available on my GitHub.



Source link

Previous articleGet 3x more data with this 300GB Pixel 9 Pro offer
Next articleResearchers discover a simple way to make batteries last much longer