expenses_manager/expenses_manager/expenses/views.py

432 lines
11 KiB
Python

from datetime import date
from .models import Category, Expense
from .forms import ExpenseForm
# from dateutli.relativedelta import relativedelta
from django.db.models import Sum
from django.contrib.auth import login
from django.core.paginator import Paginator
from django.utils.ipv6 import is_valid_ipv6_address
from django.db.models.functions import ExtractMonth, ExtractYear
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, render, redirect
MONTHS = {
1:'ENERO',
2:'FEBRERO',
3:'MARZO',
4:'ABRIL',
5:'MAYO',
6:'JUNIO',
7:'JULIO',
8:'AGOSTO',
9:'SEPTIEMBRE',
10:'OCTUBRE',
11:'NOVIEMBRE',
12:'DICIEMBRE'
}
def _get_int(value):
try:
return int(value)
except (TypeError, ValueError):
return None
def sub_months(year, month, n):
month -= n
while month <= 0:
month += 12
year -= 1
return year, month
@login_required
def home(request):
expenses = Expense.objects.filter(owner=request.user)
# Last expenses
last_expenses = (
expenses
.select_related('category')
.order_by('-date')[:5]
)
# Simple KPIs (current month)
today = date.today()
month_expenses = expenses.filter(
date__year=today.year,
date__month=today.month
)
kpi_total = month_expenses.aggregate(
total=Sum('amount')
)['total'] or 0
kpi_count = month_expenses.count()
kpi_categories = (
month_expenses
.values('category')
.distinct()
.count()
)
six_months = []
for i in range(5, -1, -1):
y, m = sub_months(today.year, today.month, i)
six_months.append((y, m))
mini_data = []
for y, m in six_months:
total = expenses.filter(
date__year=y,
date__month=m
).aggregate(total=Sum('amount'))['total'] or 0
mini_data.append({
'label': f'{m}/{y}',
'total': float(total),
})
return render(request, 'expenses/home.html', {
'last_expenses': last_expenses,
'kpi_total': kpi_total,
'kpi_count': kpi_count,
'kpi_categories': kpi_categories,
'mini_chart_labels': [x['label'] for x in mini_data],
'mini_chart_data': [x['total'] for x in mini_data],
})
@login_required
def expense_list(request):
expenses = Expense.objects.filter(owner=request.user)
categories = Category.objects.filter(owner=request.user)
year_list = (
Expense.objects.filter(owner=request.user)
.dates('date', 'year')
)
months = list(range(1, 13))
# Filters
year = _get_int(request.GET.get('year'))
month = _get_int(request.GET.get('month'))
category = _get_int(request.GET.get('category'))
if year:
expenses = expenses.filter(date__year=year)
if month:
expenses = expenses.filter(date__month=month)
if category:
expenses = expenses.filter(category_id=category)
expenses = expenses.order_by('-date')
# Pagination
paginator = Paginator(expenses, 10)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render (
request,
'expenses/expense_list.html',
{
'expenses': page_obj,
'page_obj': page_obj,
'selected_year': year,
'selected_month': month,
'selected_category': category,
'categories': categories,
'year_list': [y.year for y in year_list],
'months': months,
},
)
@login_required
def expense_create(request):
if request.method == "POST":
form = ExpenseForm(request.POST, user=request.user)
if form.is_valid():
expense = form.save(commit=False)
expense.owner = request.user
expense.save()
form.save_m2m()
return redirect('expense_list')
else:
form = ExpenseForm(user=request.user)
return render(
request,
'expenses/expense_form.html',
{'form': form},
)
@login_required
def expense_edit(request, pk):
expense = get_object_or_404(
Expense,
pk=pk,
owner=request.user,
)
if request.method == "POST":
form = ExpenseForm(request.POST or None, instance=expense, user=request.user)
if form.is_valid():
expense = form.save()
return redirect('expense_list')
else:
form = ExpenseForm(instance=expense, user=request.user)
return render(
request,
'expenses/expense_form.html',
{'form': form}
)
@login_required
def expense_delete(request, pk):
expense = get_object_or_404(
Expense,
pk=pk,
owner=request.user,
)
if request.method == 'POST':
expense.delete()
return redirect('expense_list')
return render(
request,
'expenses/expense_confirm_delete.html',
{'expense': expense},
)
@login_required
def dashboard(request):
# ------------------
# Filters
# ------------------
year = _get_int(request.GET.get('year'))
month = _get_int(request.GET.get('month'))
period = request.GET.get('period')
current_year = date.today().year
selected_year = year or current_year
selected_month = month
compare_enabled = request.GET.get("compare") == "1"
# ------------------
# Queryset base
# -----------------
expenses = Expense.objects.filter(owner=request.user)
today = date.today()
if period == 'this_month':
selected_year = today.year
selected_month = today.month
elif period == 'last_month':
if today.month == 1:
selected_year = today.year - 1
selected_month = 12
else:
selected_year = today.year
selected_month = today.month
elif period == 'this_year':
selected_year = today.year
selected_month = None
expenses_filtered = expenses.filter(date__year=selected_year)
if selected_month:
expenses_filtered = expenses_filtered.filter(date__month=selected_month)
total_amount = expenses_filtered.aggregate(
total=Sum('amount')
)['total'] or 0
expense_count = expenses.count()
category_count = (
expenses
.values('category')
.distinct()
.count()
)
# ------------------
# Totals by category
# -----------------
by_category = (
expenses_filtered
.values('category__name')
.annotate(total=Sum('amount'))
.order_by('category__name')
)
# ------------------
# Totals by month
# -----------------
by_month_qs = (
expenses_filtered
.annotate(month=ExtractMonth('date'))
.values('month')
.annotate(total=Sum('amount'))
)
month_totals = {
row['month']: float(row['total'])
for row in by_month_qs
}
months = list(range(1, 13))
by_month = [
{
'month': m,
'total': month_totals.get(m, 0),
}
for m in months
]
# ------------------
# Availables years
# -----------------
year_list = (
expenses
.annotate(year=ExtractYear('date'))
.values_list('year', flat=True)
.distinct()
.order_by('year')
)
# ------------------
# Chart
# -----------------
chart_labels = months
chart_totals = [row['total'] for row in by_month]
# ------------------
# Compare period
# -----------------
previous_total = None
kpi_difference = None
percentage = None
category_comparison = None
kpi_trend = None
kpi_difference_abs = None
if compare_enabled:
previous_expenses = Expense.objects.filter(owner=request.user)
if selected_month:
# Monthly compare
if selected_month == 1:
prev_year = selected_year - 1
prev_month = 12
else:
prev_year = selected_year
prev_month = selected_month - 1
previous_expenses = previous_expenses.filter(
date__year=prev_year,
date__month=prev_month
)
else:
# Anual compare
prev_year = selected_year - 1
previous_expenses = previous_expenses.filter(
date__year=prev_year
)
previous_total = (
previous_expenses.aggregate(total=Sum('amount'))['total'] or 0
)
kpi_difference = total_amount - previous_total
if previous_total:
percentage = (kpi_difference / previous_total) * 100
kpi_trend = None
if kpi_difference is not None:
if kpi_difference > 0:
kpi_trend = 'up'
elif kpi_difference < 0:
kpi_trend = 'down'
else:
kpi_trend = 'equal'
kpi_difference_abs = abs(kpi_difference) if kpi_difference is not None else None
# ------------------
# Previous expenses by category
# ------------------
previous_by_category = (
previous_expenses
.values('category__name')
.annotate(total=Sum('amount'))
)
current_map = {
row['category__name']: row['total']
for row in by_category
}
previous_map = {
row['category__name']: row['total']
for row in previous_by_category
}
all_categories = set(current_map.keys()) | set(previous_map.keys())
category_comparison = []
for category in all_categories:
current_total = current_map.get(category, 0)
previous_total_cat = previous_map.get(category, 0)
difference = current_total - previous_total_cat
category_comparison.append({
'category': category,
'current': current_total,
'previous': previous_total_cat,
'difference': difference,
'difference_abs': abs(difference),
})
# Send the data to the dashboard
return render(request, 'expenses/dashboard.html', {
'by_category': by_category,
'by_month': by_month,
'chart_labels': chart_labels,
'chart_data': chart_totals,
'year_list': year_list,
'months': months,
'selected_year': selected_year,
'selected_month': selected_month,
'kpi_total': total_amount,
'kpi_count': expense_count,
'kpi_categories': category_count,
'compare_enabled': compare_enabled,
'kpi_previous_total': previous_total,
'kpi_difference': kpi_difference,
'kpi_difference_abs': kpi_difference_abs,
'kpi_percentage': percentage,
'category_comparison': category_comparison,
'kpi_trend': kpi_trend,
})