I want to filter and sort in table.

I want to add filters and sorters like filter by amount, category and date. Sort by name, amount, category and date, both ascending and descending. Also, I want to be able to apply more than 1 filters. And sin sorting, if I first chose amount, then it’s first sorted by amount, then if I chose date, then entries with the same amount should be sorted by date. Also, can I do it dynamically or do I have to refresh the page everytime a filter is applied or removed. How should I go about this?

views.py:-

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views.generic import View, UpdateView, DeleteView
from .forms import ExpenseForm
from .models import Expense
from django.db.models import Sum
import datetime

# Create your views here.
class IndexView(View):
    template_name = 'expenses/index.html'

    def get(self, request):
        expense_form = ExpenseForm()
        expenses = Expense.objects.all().order_by('-date')
        total_expenses = Expense.objects.aggregate(Sum('amount'))

        today = datetime.date.today()
        # last year sum
        last_year = today - datetime.timedelta(days=365)
        yearly_sum = Expense.objects.filter(date__gt=last_year, user=request.user).aggregate(Sum('amount'))

        # last month sum
        last_month = today - datetime.timedelta(days=30)
        monthly_sum = Expense.objects.filter(date__gt=last_month, user=request.user).aggregate(Sum('amount'))

        # last week sum
        last_week = today - datetime.timedelta(days=7)
        weekly_sum = Expense.objects.filter(date__gt=last_week, user=request.user).aggregate(Sum('amount'))

        year_start = datetime.date(today.year, 1, 1)
        month_start = datetime.date(today.year, today.month, 1)
        week_start = today - datetime.timedelta(days=today.weekday())

        # sums from since starting of...
        this_year_sum = Expense.objects.filter(date__gt=year_start, user=request.user).aggregate(Sum('amount'))
        this_month_sum = Expense.objects.filter(date__gt=month_start, user=request.user).aggregate(Sum('amount'))
        this_week_sum = Expense.objects.filter(date__gt=week_start, user=request.user).aggregate(Sum('amount'))

        return render(request, self.template_name, {
            'expense_form': expense_form,
            'expenses': expenses,
            'total_expenses': total_expenses,
            'yearly_sum': yearly_sum,
            'monthly_sum': monthly_sum,
            'weekly_sum': weekly_sum,
            'this_year_sum': this_year_sum,
            'this_month_sum': this_month_sum,
            'this_week_sum': this_week_sum,
        })
       
    def post(self, request):
        expense_form = ExpenseForm(request.POST)
        if expense_form.is_valid():
            expense = expense_form.save(commit=False)
            expense.user = request.user
            expense.save()
            return redirect('index')
        expenses = Expense.objects.all()
        return render(request, self.template_name, {'expense_form': expense_form, 'expenses': expenses})

class ExpenseUpdateView(UpdateView):
    model = Expense
    fields = ['name', 'amount', 'category']
    template_name_suffix = '_update_form'
    success_url = reverse_lazy('index')

class ExpenseDeleteView(DeleteView):
    model = Expense
    success_url = reverse_lazy('index')

models.py:-

from django.db import models
from django.contrib.auth.models import User
from datetime import date
from django.db.models import Sum

# Create your models here.
class Expense(models.Model):
    name = models.CharField(max_length=200)
    amount = models.IntegerField()
    category = models.CharField(max_length=200)
    date = models.DateTimeField(auto_now_add=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='expenses')

    def __str__(self):
        return self.name

index.html:-

{% extends "base.html" %}
{% load humanize %}

{% block content %}
<h1 class="text-4xl mt-10 text-center">Best Place To Keep Track Of Your Money! 💰</h1>
<h2 class="font-bold text-gray-700 mt-10 px-10 -mb-10">Add Expense ➕</h2>
<form class="shadow-lg m-10 rounded-lg" method="post">
    {% csrf_token %}
    <div class="form-container pt-4 pb-8 px-10 flex items-center justify-center gap-10">
        {% for field in expense_form %}
        <div class="relative">
            <div>
                <div class="mb-1">
                    <label class="text-lg text-gray-500 font-bold" for="id_{{field.name}}">{{field.name | title}}</label>
                </div>
                <div class="border-2 border-zinc-400 focus:outline-none xl:w-80">
                    {{field}}
                </div>
            </div>
            <div class="absolute top-16 px-1 py-1 text-sm font-semibold text-red-600">
                {{field.errors}}
            </div>
        </div>
        {% endfor %}

        <div class="pt-8">
            <button class="bg-green-500 hover:bg-green-600 px-5 py-2 rounded text-white font-bold">Add</button>
        </div>
    </div>
</form>

<div class="container px-10 my-5 rounded-lg">
    <h1 class="text-4xl my-10 text-center">💵 Expenses 💵</h1>
    <div class="shadow-lg p-5">
        {% if expenses %}
        <div class="expense-header text-xl flex flex-wrap px-20 font-bold">
            <span>Name</span>
            <span>Amount</span>
            <span>Category</span>
            <span>Date</span>
            <span>Edit</span>
            <span>Delete</span>
        </div>
        <hr class="mt-8">
        {% else %}
        <h1 class="text-4xl text-center">Add Expenses to show here...</h1>
        {% endif %}
        
        {% for expense in expenses %}
        <div class="expense-row flex flex-wrap px-20 py-5">
            <span>{{expense.name}}</span>
            <span>₹ {{expense.amount | intcomma}}</span>
            <span>{{expense.category}}</span>
            <span>{{expense.date | date:"d M, Y"}}</span>
            <span>
                <div class="flex justify-center">
                    <a class="flex justify-content p-1" href="{% url 'edit' expense.id %}">
                        <svg class="stroke-indigo-800 stroke-2 w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
                        stroke-width="1.5" stroke="currentColor">
                        <path stroke-linecap="round" stroke-linejoin="round"
                        d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
                        </svg>
                    </a>
                </div>
            </span>
            <span>
                <form method="POST" action="{% url 'delete' expense.id %}">
                    {% csrf_token %}
                    <button class="cursor-pointer p-1" type="submit">
                        <svg class="stroke-red-600 stroke-2 w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
                        stroke="currentColor">
                        <path stroke-linecap="round" stroke-linejoin="round"
                        d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
                    </svg>
                </button>
            </form>
            </span>
        </div>
        {% endfor %}

        <div class="px-60 mt-10">
            <span class="mx-2 font-bold">Total: </span>
            <span class="font-bold text-green-600 text-2xl">₹ {{total_expenses.amount__sum | intcomma}}</span>
        </div>
    </div>
    
    
    <h1 class="text-4xl mt-20 mb-5 text-center">📜 Totals 🧾</h1>
    <div class="container">
        <div class="flex justify-center">
            <div class="shadow-lg rounded-lg p-10 m-10 w-1/3">
                <h2 class="font-bold text-gray-500">Last Year (365 days)</h2>
                <h2 class="font-bold text-green-600 text-2xl">
                    ₹ {{yearly_sum.amount__sum | intcomma}}
                </h2>
            </div>
            
            <div class="shadow-lg rounded-lg p-10 m-10 w-1/3">
                <h2 class="font-bold text-gray-500">Last Month (30 days)</h2>
                <h2 class="font-bold text-green-600 text-2xl">
                    ₹ {{weekly_sum.amount__sum | intcomma}}
                </h2>
            </div>
    
            <div class="shadow-lg rounded-lg p-10 m-10 w-1/3">
                <h2 class="font-bold text-gray-500">Last Week (7 days)</h2>
                <h2 class="font-bold text-green-600 text-2xl">
                    ₹ {{monthly_sum.amount__sum | intcomma}}
                </h2>
            </div>
        </div>
    </div>

    <div class="container">
        <div class="flex justify-center">
            <div class="shadow-lg rounded-lg p-10 m-10 w-1/3">
                <h2 class="font-bold text-gray-500">This Year</h2>
                <h2 class="font-bold text-green-600 text-2xl">
                    ₹ {{this_year_sum.amount__sum | intcomma}}
                </h2>
            </div>
            
            <div class="shadow-lg rounded-lg p-10 m-10 w-1/3">
                <h2 class="font-bold text-gray-500">This Month</h2>
                <h2 class="font-bold text-green-600 text-2xl">
                    ₹ {{this_week_sum.amount__sum | intcomma}}
                </h2>
            </div>
    
            <div class="shadow-lg rounded-lg p-10 m-10 w-1/3">
                <h2 class="font-bold text-gray-500">This Week</h2>
                <h2 class="font-bold text-green-600 text-2xl">
                    ₹ {{this_month_sum.amount__sum | intcomma}}
                </h2>
            </div>
        </div>
    </div>
</div>

{% endblock content %}

You can do it either way.

If you do this via page refresh, you might be able to take advantage of the django-filter package to handle some of this. But based upon what you’re describing, you’re still going to end up doing a fair amount of work on this in your code.

If you want to do this dynamically in the browser, then that means using JavaScript. We use the jQuery Datatables module to present tabular data in a sortable & searchable UI.