Python Django Expense Tracker – Say Goodbye to Financial Stress

Effectively managing expenses play a crucial role in personal finance. By diligently tracking expenses, individuals can make informed financial choices and adhere to their budgets. Employing an Expense Tracker Web Application simplifies the process of monitoring expenses and effectively managing financial matters.

This project focuses on developing an Expense Tracker Django Web Application that empowers users to create profiles and track their expenses based on the categories they specified during profile creation.

About Python Django Expense Tracker Project

The objective of this project is to guide you in building a web application enabling users to establish profiles, input expenses, and monitor their spending according to the categories they specified during profile creation.

Prerequisite for Expense Tracker using Python Django

Prior to commencing this project, it is recommended to possess fundamental knowledge of Django Framework, HTML, CSS, JavaScript, and Bootstrap.

You should also have Python, Django, and a code editor installed on your system.

Download Python Django Expense Tracker Project

Please download the source code of Python Django Expense Tracker Project from the following link: Django Expense Tracker Project Code

Steps to create Expense Tracker using Python Django

Following are the steps for developing the Expense Tracker :

Step 1: Setting up the Django Project

Step 2: Creating the Expense Tracker App

Step 3: Creating the Models

Step 4: Creating the Views

Step 5: Creating the Forms

Step 6: Creating the Templates

Step 7: Migrating the Database

Step 8: Running the Server

Step 1: Setting up the Django Project

a. Install Django using pip:
In order to create a Django project, it is necessary to have Django installed. If you haven’t installed it already, please access your terminal or command prompt and execute the following command:

pip install Django

b. Create a new Django project using the following command:

django-admin startproject expense_tracker

c. Change your current directory to the newly created project directory using the following command:

cd expense_tracker

Step 2: Creating the Expense Tracker App

a. Create a new Django app called ‘budget’ using the following command:

python manage.py startapp budget

b. Add ‘budget’ to your project’s installed apps by opening the ‘settings.py’ file located in the project directory and adding ‘budget’ to the INSTALLED_APPS list.

# Application definition
INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   'budget',
]

c. Create a file called ‘urls.py’ in the ‘budget’ app directory and open it, then add the following code to it:

# Description: This file contains the urls for the budget app


# Importing libraries
from django.contrib import admin
from django.urls import path,include
from .import views


# Defining the url patterns
urlpatterns = [
   path('',views.project_list,name = 'list'),
   path('add', views.ProjectCreateView.as_view(),name ='add'),
   path('<slug:project_slug>',views.project_detail,name='detail'),
]

The urls.py file defines the URL patterns for the app. The first pattern maps the root URL to the project_list function. The second pattern maps the add URL to the ProjectCreateView class. The third pattern maps a URL with a project slug to the project_detail function.

Step 3: Creating the Models

a. Open the ‘models.py’ file located in the ‘budget’ app directory.

b. Define the models for the Expense Tracker app as shown below.

# Importing the necessary libraries
from django.db import models
from django.utils.text import slugify


#-------------------------- Models --------------------------#


# Creating the Project model to store the project details like name, slug, budget
class Project(models.Model):
  
   # Defining the fields of the Project model
   name = models.CharField(max_length=100)
   slug = models.SlugField(max_length=100,unique=True,blank=True)
   budget = models.IntegerField()
  
   # Defining the save method to create the slug
   def save(self, *args,**kwargs):
       self.slug = slugify(self.name)
       super(Project,self).save(*args,**kwargs)
      
   # Defining the budget_left method to calculate the budget left
   def budget_left(self):
       expense_list = Expense.objects.filter(project= self)
       total_expense_amount =0
       for expense in expense_list:
           total_expense_amount += expense.amount


       return self.budget- total_expense_amount
  
   # Defining the total_transactions method to calculate the total number of transactions
   def total_transactions(self):
       expense_list = Expense.objects.filter(project= self)
       return len(expense_list)


# Creating the Category model to store the category details like name, project
class Category(models.Model):
   # Defining the fields of the Category model
   project = models.ForeignKey(Project, on_delete=models.CASCADE)
   name = models.CharField(max_length=100)


# Creating the Expense model to store the expense details like project, title, amount, category
class Expense(models.Model):
   # Defining the fields of the Expense model
   project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='expenses')
   title = models.CharField(max_length=100)
   amount = models.DecimalField( max_digits=8, decimal_places=2)
   category = models.ForeignKey(Category, on_delete=models.CASCADE)
  
   # Defining the Meta class to order the expenses by amount
   class Meta:
       ordering = ('-amount',)

The models.py file defines the Project, Category, and Expense models used by the app. Project and Category are related by a foreign key relationship, and Expense is related to both Project and Category using foreign keys. Project has a budget_left method that calculates the amount of budget remaining after subtracting all expenses, and a total_transactions method that counts the total number of expenses associated with the project.

Step 4: Creating the Views

a. Open the ‘views.py’ file located in the ‘budget’ app directory.
b. Define the views for the Expense Tracker app as shown below.

# Importing the required libraries
from django.shortcuts import render,get_object_or_404
from django.http import HttpResponseRedirect, HttpResponse
from .models import Project, Category, Expense
from django.views.generic import CreateView
from django.utils.text import slugify
from .forms import ExpenseForm
import json


#------------------------ Views ------------------------#


# View for the project list page
# It will return the list of projects
def project_list(request):
  
   # Getting the list of projects
   project_list = Project.objects.all()
  
   # Returning the project list page
   return render(request,'budget/project-list.html',{'project_list':project_list})


# View for the project detail page
# It will return the details of the project and the list of expenses
def project_detail(request, project_slug):
  
   # Getting the project object
   project = get_object_or_404(Project,slug=project_slug)
  
   # Checking the request method
   # If the request method is GET, then it will return the project detail page
   if request.method == 'GET':
       category_list = Category.objects.filter(project=project)
       return render(request,'budget/project-detail.html',{'project':project, 'expense_list': project.expenses.all(), 'category_list':category_list})
  
   # If the request method is POST, then it will create a new expense
   elif request.method == 'POST':
       # Getting the form data and checking if it is valid
       form = ExpenseForm(request.POST)
       if form.is_valid():
          
           # Getting the form data if it is valid
           title = form.cleaned_data['title']
           amount = form.cleaned_data['amount']
           category_name = form.cleaned_data['category']
           category= get_object_or_404(Category, project = project, name= category_name)
          
           # Creating the expense object and saving it
           Expense.objects.create(
               project=project,
               title=title,
               amount=amount,
               category=category
           ).save()
          
   # If the request method is DELETE, then it will delete the expense
   elif request.method == 'DELETE':
      
       # Getting the expense id from the request body and deleting the expense
       id = json.loads(request.body)['id']
       expense = get_object_or_404(Expense,id =id)
       expense.delete()
       return HttpResponse('')
  
   # Redirecting to the project detail page
   return HttpResponseRedirect(project_slug)


# View for the add project page
# It will create a new project
class ProjectCreateView(CreateView):
  
   # Defining the model, template and fields
   model = Project
   template_name = 'budget/add-project.html'
   fields = {'name','budget'}
  
   # Defining the form_valid method to create the categories
   def form_valid(self, form):
       self.object = form.save(commit=False)
       self.object.save()


       categories = self.request.POST['categoriesString'].split(',')
      
       # Creating the categories and saving them
       for category in categories:
           Category.objects.create(
               project = Project.objects.get(id=self.object.id),
               name = category
           ).save()
       return HttpResponseRedirect(self.get_success_url())
  
   # Defining the get_success_url method to redirect to the project detail page
   def get_success_url(self):
       return slugify(self.request.POST['name'])

The views.py file defines several functions that handle different HTTP requests made by the user.

  • The project_list function fetches all Project objects from the database and transfers them to the project-list.html template to be rendered.
  • The project_detail function takes a project_slug as a parameter, retrieves the corresponding Project object from the database using get_object_or_404, and returns a rendered template with the project information, expense list, and category list.When the request method is GET, the function proceeds to render the project-detail.html template along with the required data. If the method is POST, it extracts the Expense form data and creates a new Expense object associated with the corresponding Project and Category object before saving it to the database. If the method is DELETE, it extracts the id from the request body and deletes the corresponding Expense object from the database.
  • The ProjectCreateView class handles the creation of new Project objects. It extends Django’s CreateView class, which provides built-in functionality for creating new objects. Also, the form_valid method is overridden to create new Category objects associated with the Project. It extracts the categories from the categoriesString field in the form data, creates new Category objects for each one, and saves them to the database.

Step 5: Creating the Forms

a. Create a file named ‘forms.py’ file in the ‘budget’ app directory.
b. Open the ‘forms.py’ file
c. Define the form for adding expenses as shown below.

# Importing required libraries
from django import forms


# Creating a form class for the expense
class ExpenseForm(forms.Form):
  
   # Defining the fields for the form
   title = forms.CharField()
   amount = forms.IntegerField()
   category = forms.CharField()
  
   # Defining the widgets for the form
   widget = {
       'title': forms.TextInput(attrs={'class': 'input-group form-control form-control-lg'}),
       'amount': forms.TextInput(attrs={'class': 'input-group form-control form-control-lg'}), 
   }

The forms.py file defines the ExpenseForm form used to create new Expense objects. It has fields for the title, amount, and category of the expense, and includes widgets for styling the input fields.

Step 6: Creating the Templates

a. Create a new directory called ‘templates’ in the ‘budget’ app directory.
b. Create HTML templates for each view.

base.html : It is the base html file, using this we will use the bootstrap in all the other html files, and eliminate the rewriting of the code.

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>{% block title %}{% endblock title %}</title>
   <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
   <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
   <script src="https://code.jquery.com/jquery-3.6.0.slim.min.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.js"></script>
   <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />


</head>
<body>
   {% block content %}
  
   {% endblock content %}
   <style>


       body {
           background: linear-gradient(180deg, #ebf4f5, #b5c6e0);
           background-size: 100% 100%;
           background-repeat: no-repeat;
           background-attachment: fixed;
           background-position: center;
           background-size: cover;
       }
      
   </style>
</body>
</html>

add-project.html: This html file will provide the view to add new user profile to the system.

{% extends 'budget/base.html' %}
{% block title %}
Add Project
{% endblock title %}
{% block content %}
<br>
  <div class="container">
   <div class="row justify-content-center">
     <div class="col-md-8 col-lg-6">
       <h1 class="text-center mb-5">Add Project</h1>
       <form action="" method="POST" class="p-4 rounded-3 bg-gradient">
         {% csrf_token %}
         {% for field in form %}
           <div class="mb-3">
               <label for="{{ field.id_for_label }}" class="form-label h6">{{ field.label }}</label>
               <input type="text" class="form-control form-control-lg" id="{{ field.id_for_label }}" name="{{ field.name }}" >
           </div>
         {% endfor %}
         <div class="mb-3">
           <br>
           <br>
           <label for="categoryInput" class="form-label h6">Expense Categories (hit enter after every category)</label>
           <div class="input-group">
             <input type="text" class="form-control form-control-lg" id="categoryInput" placeholder="Enter category" aria-describedby="addCategoryBtn">
             <button type="button" class="btn btn-primary btn-lg" id="addCategoryBtn">Add</button>
           </div>
         </div>
         <input type="hidden" name="categoriesString" id="categoriesString">
         <div class="d-flex flex-wrap mb-3" id="categoriesContainer">
         </div>
         <button type="submit" class="btn btn-primary btn-lg w-100">Start Project</button>
       </form>
     </div>
   </div>
 </div>
  <script>
   const categoryInput = document.getElementById('categoryInput');
   const categoriesContainer = document.getElementById('categoriesContainer');
   const categoriesString = document.getElementById('categoriesString');
    const addCategory = () => {
     const category = categoryInput.value.trim();
     if (!category) return;
     const categoryBadge = `<h4><span class="badge bg-success me-2">${category}<span class="ms-2" style="cursor: pointer;" onclick="removeCategory(this)"><i class="fa-solid fa-xmark"></i></span></span></h4>`;
     categoriesContainer.insertAdjacentHTML('beforeend', categoryBadge);
     categoriesString.value = [...categoriesContainer.children].map(c => c.innerText.trim()).join(',');
     categoryInput.value = '';
   }
    const removeCategory = (category) => {
     category.parentElement.remove();
     categoriesString.value = [...categoriesContainer.children].map(c => c.innerText.trim()).join(',');
   }
    categoryInput.addEventListener('keydown', (event) => {
     if (event.key === 'Enter') {
       event.preventDefault();
       addCategory();
     }
   });
    const addCategoryBtn = document.getElementById('addCategoryBtn');
   addCategoryBtn.addEventListener('click', addCategory);
 </script>
 {% endblock content %}

project-list.html: This html file will show all the available user profile on the web app.

{% extends 'budget/base.html' %}


{% block title %}
Create Finance
{% endblock title %}
{% block content %}
<br>
<div class="container">
   <h1 class="text-center mb-5">My Accounts</h1>
   <div class="row row-cols-1 row-cols-md-3 g-4">
     {% for project in project_list %}
     <div class="col">
       <div class="card h-100">
         <div class="card-body">
           <h5 class="card-title">{{ project.name }}</h5>
           <a href="{% url 'detail' project.slug %}" class="btn btn-primary">Visit</a>
         </div>
       </div>
     </div>
     {% empty %}
     <div class="col">
       <div class="card h-100 bg-light text-center">
         <div class="card-body">
           <h3 class="card-title text-muted">Sorry, You don't have created any Finance History, yet.</h3>
         </div>
       </div>
     </div>
     {% endfor %}
     <div class="col">
       <div class="btn btn-lg card h-100 bg-success text-center">
         <div class="card-body">
           <a href="{% url 'add' %}" class="btn-dark bg-success" style="text-decoration: none;">
               <i class="fa-solid fa-circle-plus"></i><h3 class="card-title text-light">Create New Finance</h3>
           </a>
         </div>
       </div>
     </div>
   </div>
 </div>
  <style>
   body {
     background-color: #f8f9fa;
   }
  
   .btncreate {
     margin-top: 2rem;
     margin-bottom: 2rem;
     width: 100%;
   }
 </style>
{% endblock content %}

project-detail.html: Output{% extends ‘budget/base.html’ %}

{% load static %}
{% block title %}
Finance List
{% endblock title %}
{% block content %}
<br>
<h3 class="text-center font-weight-bold mb-4">Welcome back, {{ user.username }}</h3>
<div class="container">
   <div class="row mb-4">
       <div class="col-md-4">
           <div class="card bg-primary text-white">
               <div class="card-body">
                   <h6 class="card-title font-weight-bold mb-3">Total Budget</h6>
                   <h1 class="card-text font-weight-bold mb-0">₹{{ project.budget }}</h1>
               </div>
           </div>
       </div>
       <div class="col-md-4">
           <div class="card bg-success text-white">
               <div class="card-body">
                   <h6 class="card-title font-weight-bold mb-3">Budget Left</h6>
                   {% if project.budget_left > 0 %}
                   <h1 class="card-text font-weight-bold mb-0">₹{{ project.budget_left }}</h1> 
                   {% elif project.budget_left == 0 %}
                   <h1 class="card-text font-weight-bold mb-0">₹{{ project.budget_left }}</h1>
                   {% else %}
                   <h1 class="card-text font-weight-bold mb-0">₹{{ project.budget_left }}</h1>
                   {% endif %}
               </div>
           </div>
       </div>
       <div class="col-md-4">
           <div class="card bg-secondary text-white">
               <div class="card-body">
                   <h6 class="card-title font-weight-bold mb-3">Total Transactions</h6>
                   <h1 class="card-text font-weight-bold mb-0">{{ project.total_transactions }}</h1>
               </div>
           </div>
       </div>
   </div>


   <center>
   <div class="row mb-4">
       <div class="col-md-12">
           <button class="btn btn-success btn-lg float-right modal-trigger" href="#expenseModal" data-toggle="modal" data-target="#expenseModal">
               <i class="fas fa-plus-circle mr-2"></i>
               Add Expense
           </button>
       </div>
   </div>
   </center>
<center>
   <div class="column">
       <div class="col-md-10">
           <table class="table table-striped table-hover">
               <thead>
                   <tr>
                       <th>Title</th>
                       <th>Amount</th>
                       <th>Category</th>
                       <th>Action</th>
                   </tr>
               </thead>
               <tbody>
                   {% for expense in expense_list %}
                   <tr id="{{ expense.id }}">
                       <td>{{ expense.title }}</td>
                       <td>₹{{ expense.amount }}</td>
                       <td>{{ expense.category.name }}</td>
                       <td>
                           <button class="btn btn-sm btn-danger" onclick="deleteExpense(this)" data-id="{{ expense.id }}">
                               <i class="fas fa-trash-alt"></i>
                               Delete
                           </button>
                       </td>
                   </tr>
                   {% endfor %}
               </tbody>
           </table>
       </div>
   </div>
</div>
</center>


<div id="expenseModal" class="modal fade">
   <div class="modal-dialog">
       <div class="modal-content">
           <div class="modal-header">
               <h4 class="modal-title font-weight-bold">Add Expense</h4>
               <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
           </div>
           <div class="modal-body">
               <form action="" method="POST">
                   {% csrf_token %}
                   <div class="mb-3">
                       <label for="title" class="form-label">Title</label>
                       <input type="text" class="form-control" id="title" name="title">
                   </div>
                   <div class="mb-3">
                       <label for="amount" class="form-label">Amount</label>
                       <input type="text" class="form-control" id="amount" name="amount">
                   </div>
                   <div class="mb-3">
                       <label for="category" class="form-label">Category</label>
                       <select id="category" class="form-select" name="category">
                           {% for category in category_list %}
                           <option value="{{ category.name }}">{{ category.name }}</option>
                           {% endfor %}
                       </select>
                   </div>
                   <button type="submit" class="btn btn-primary">Add</button>
               </form>
           </div>
       </div>
   </div>
</div>


<script>
   var elem = document.querySelector('.modal')
   var instanace = M.Modal.init(elem)


   var elem = document.querySelector('select')
   var instanace = M.FormSelect.init(elem)


   function deleteExpense(e){


       let id = e.dataset.id
       let row = document.getElementById(id)
       row.remove()
      
       fetch('',{
           method:'DELETE',
           headers : {
               'X-CSRFToken' : '{{ csrf_token }}'
           },
           body:JSON.stringify({
               'id' :id
           }),
           credentials : 'same-origin',
       })
   }
</script>
{% endblock content %}

Step 7: Migrating the Database

a. Execute the following command to create all the required database tables:

python manage.py migrate

Step 8: Running the Server

a. Start the Django development server by running the following command:

python manage.py runserver

b. Open a web browser and navigate to the following URL:

http://127.0.0.1:8000/

Output:

expense tracker output

django expense tracker app project output

django expense tracker output

django expense tracker project output

Summary:

Congratulations, you have successfully created an Expense Tracker Django Web Application that allows users to create profiles, add expenses, and track their expenses based on categories they entered while creating their profile. You can now use this application to manage your finances more efficiently. Nevertheless, this is just the beginning. You have the opportunity to incorporate additional functionalities based on your requirements, such as generating expense reports. The possibilities are limitless, and we trust that this project will assist you in initiating your Expense Tracker Web Application. Always remember to continue learning and enhancing your skills as a developer.