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:
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.