diff --git a/expenses_manager/expenses/forms.py b/expenses_manager/expenses/forms.py
index 0af8c10..f711f7f 100644
--- a/expenses_manager/expenses/forms.py
+++ b/expenses_manager/expenses/forms.py
@@ -1,90 +1,101 @@
from django import forms
-from .models import Expense, Category, Income, Tag, Account, FuelEntry
+from .models import Expense, Category, Income, Tag, Account, FuelEntry, Goal
+
class ExpenseForm(forms.ModelForm):
-
+
def __init__(self, *args, **kwargs):
# Get user
- user = kwargs.pop('user', None)
-
+ user = kwargs.pop("user", None)
+
super().__init__(*args, **kwargs)
-
+
if user:
- self.fields['category'].queryset = Category.objects.filter(owner=user)
- self.fields['tags'].queryset = Tag.objects.filter(owner=user)
- self.fields['account'].queryset = Account.objects.filter(owner=user, active=True)
- self.fields['date'].input_formats = ['%Y-%m-%d']
-
+ self.fields["category"].queryset = Category.objects.filter(owner=user)
+ self.fields["tags"].queryset = Tag.objects.filter(owner=user)
+ self.fields["account"].queryset = Account.objects.filter(
+ owner=user, active=True
+ )
+ self.fields["date"].input_formats = ["%Y-%m-%d"]
+
class Meta:
model = Expense
fields = [
- 'date',
- 'amount',
- 'description',
- 'category',
- 'account',
- 'tags',
+ "date",
+ "amount",
+ "description",
+ "category",
+ "account",
+ "tags",
]
widgets = {
- 'date': forms.DateInput(
- format='%Y-%m-%d',
- attrs={'type': 'date'}),
- 'widget': forms.CheckboxSelectMultiple(),
+ "date": forms.DateInput(format="%Y-%m-%d", attrs={"type": "date"}),
+ "widget": forms.CheckboxSelectMultiple(),
}
+
class TagForm(forms.ModelForm):
class Meta:
model = Tag
- fields = ['name']
-
+ fields = ["name"]
+
+
class AccountForm(forms.ModelForm):
-
+
class Meta:
model = Account
- fields = ['name', 'initial_balance', 'active']
-
+ fields = ["name", "initial_balance", "active"]
+
class IncomeForm(forms.ModelForm):
class Meta:
model = Income
- fields = ['account', 'name', 'amount', 'date']
- widgets = {
- 'date': forms.DateInput(
- format='%Y-%m-%d',
- attrs={'type': 'date'})
- }
-
+ fields = ["account", "name", "amount", "date"]
+ widgets = {"date": forms.DateInput(format="%Y-%m-%d", attrs={"type": "date"})}
+
def __init__(self, *args, **kwargs):
- user = kwargs.pop('user')
+ user = kwargs.pop("user")
super().__init__(*args, **kwargs)
- self.fields['account'].queryset = Account.objects.filter(
+ self.fields["account"].queryset = Account.objects.filter(
owner=user,
active=True,
)
+
class FuelEntryForm(forms.Form):
# Expense fields
- date = forms.DateField(widget=forms.DateInput(
- format='%Y-%m-%d',
- attrs={'type': 'date'}))
+ date = forms.DateField(
+ widget=forms.DateInput(format="%Y-%m-%d", attrs={"type": "date"})
+ )
amount = forms.DecimalField(max_digits=10, decimal_places=2)
account = forms.ModelChoiceField(queryset=None)
-
+
# Specifics fuel fields
- odometer = forms.DecimalField(label='Current kilometers')
+ odometer = forms.DecimalField(label="Current kilometers")
liters = forms.DecimalField(max_digits=8, decimal_places=2)
-
-
+
def __init__(self, *args, **kwargs):
- user = kwargs.pop('user')
+ user = kwargs.pop("user")
super().__init__(*args, **kwargs)
if user:
- self.fields['account'].queryset = (
- user.accounts.filter(active=True)
- )
+ self.fields["account"].queryset = user.accounts.filter(active=True)
+
class CategoryForm(forms.ModelForm):
class Meta:
model = Category
- fields = ['name', 'parent']
\ No newline at end of file
+ fields = ["name", "parent"]
+
+class GoalForm(forms.ModelForm):
+ class Meta:
+ model = Goal
+ fields = ['name', 'target_amount', 'category']
+
+ def __init__(self, *args, **kwargs):
+ user = kwargs.pop('user')
+ super().__init__(*args, **kwargs)
+
+ self.fields['category'].queryset = (
+ user.categories.all()
+ )
\ No newline at end of file
diff --git a/expenses_manager/expenses/migrations/0008_goal.py b/expenses_manager/expenses/migrations/0008_goal.py
new file mode 100644
index 0000000..013a5e3
--- /dev/null
+++ b/expenses_manager/expenses/migrations/0008_goal.py
@@ -0,0 +1,27 @@
+# Generated by Django 5.2.10 on 2026-05-05 09:08
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('expenses', '0007_alter_category_options_and_more'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Goal',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100)),
+ ('target_amount', models.DecimalField(decimal_places=2, max_digits=12)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='expenses.category')),
+ ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/expenses_manager/expenses/models.py b/expenses_manager/expenses/models.py
index f7d1981..0c87a95 100644
--- a/expenses_manager/expenses/models.py
+++ b/expenses_manager/expenses/models.py
@@ -7,141 +7,127 @@ from django.db.models.fields import related
from django.db.models.functions import ExtractMonth
from django.utils.text import slugify
+
class Category(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(blank=True)
-
+
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="categories",
)
-
+
parent = models.ForeignKey(
- 'self',
+ "self",
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="children",
)
-
+
class Meta:
unique_together = ("name", "parent", "owner", "slug")
verbose_name_plural = "categories"
ordering = ["name"]
-
+
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.slug:
from django.utils.text import slugify
+
self.slug = slugify(self.name)
super().save(*args, **kwargs)
-
-class Account(models.Model):
+
+
+class Account(models.Model):
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="accounts",
)
-
- name = models.CharField(max_length=100)
- initial_balance = models.DecimalField(
- max_digits=12,
- decimal_places=2,
- default=0
- )
+
+ name = models.CharField(max_length=100)
+ initial_balance = models.DecimalField(max_digits=12, decimal_places=2, default=0)
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
-
+
class Meta:
- ordering = ['name']
-
+ ordering = ["name"]
+
def current_balance(self):
- expenses_total = (
- self.expenses.aggregate(total=Sum('amount'))['total']
- or Decimal('0')
- )
- income_total = (
- self.incomes.aggregate(total=Sum('amount'))['total']
- or Decimal('0')
+ expenses_total = self.expenses.aggregate(total=Sum("amount"))[
+ "total"
+ ] or Decimal("0")
+ income_total = self.incomes.aggregate(total=Sum("amount"))["total"] or Decimal(
+ "0"
)
return self.initial_balance + income_total - expenses_total
-
+
def monthly_balance(self, year=None):
year = year or date.today().year
-
+
incomes = (
- self.incomes
- .filter(date__year=year)
- .annotate(month=ExtractMonth('date'))
- .values('month')
- .annotate(total=Sum('amount'))
+ self.incomes.filter(date__year=year)
+ .annotate(month=ExtractMonth("date"))
+ .values("month")
+ .annotate(total=Sum("amount"))
)
-
+
expenses = (
- self.expenses
- .filter(date__year=year)
- .annotate(month=ExtractMonth('date'))
- .values('month')
- .annotate(total=Sum('amount'))
+ self.expenses.filter(date__year=year)
+ .annotate(month=ExtractMonth("date"))
+ .values("month")
+ .annotate(total=Sum("amount"))
)
-
- income_map = {i['month']: i['total'] for i in incomes}
- expenses_map = {e['month']: e['total'] for e in expenses}
-
+
+ income_map = {i["month"]: i["total"] for i in incomes}
+ expenses_map = {e["month"]: e["total"] for e in expenses}
+
balance = self.initial_balance
data = []
-
+
for month in range(1, 13):
- balance += income_map.get(month, Decimal('0'))
- balance -= expenses_map.get(month, Decimal('0'))
-
- data.append({
- 'month': month,
- 'balance': float(balance),
- })
+ balance += income_map.get(month, Decimal("0"))
+ balance -= expenses_map.get(month, Decimal("0"))
+
+ data.append(
+ {
+ "month": month,
+ "balance": float(balance),
+ }
+ )
return data
-
+
def balance_until(self, date):
- incomes_total = (
- self.incomes
- .filter(date__lte=date)
- .aggregate(total=Sum('amount'))['total'] or Decimal('0')
- )
-
- expenses_total = (
- self.expenses
- .filter(date__lte=date)
- .aggregate(total=Sum('amount'))['total'] or Decimal('0')
- )
-
+ incomes_total = self.incomes.filter(date__lte=date).aggregate(
+ total=Sum("amount")
+ )["total"] or Decimal("0")
+
+ expenses_total = self.expenses.filter(date__lte=date).aggregate(
+ total=Sum("amount")
+ )["total"] or Decimal("0")
+
return self.initial_balance + incomes_total - expenses_total
-
+
def monthly_net(self, year=None):
year = year or date.today().year
data = []
-
+
for month in range(1, 13):
- income = (
- self.incomes
- .filter(date__year=year, date__month=month)
- .aggregate(total=Sum('amount'))['total'] or Decimal('0')
- )
- expense = (
- self.expenses
- .filter(date__year=year, date__month=month)
- .aggregate(total=Sum('amount'))['total'] or Decimal('0')
- )
-
- data.append({
- 'month': month,
- 'net': float(income - expense)
- })
-
+ income = self.incomes.filter(date__year=year, date__month=month).aggregate(
+ total=Sum("amount")
+ )["total"] or Decimal("0")
+ expense = self.expenses.filter(
+ date__year=year, date__month=month
+ ).aggregate(total=Sum("amount"))["total"] or Decimal("0")
+
+ data.append({"month": month, "net": float(income - expense)})
+
return data
-
+
def __str__(self):
return self.name
@@ -153,108 +139,144 @@ class Tag(models.Model):
on_delete=models.CASCADE,
related_name="tags",
)
-
+
class Meta:
unique_together = ("name", "owner")
-
+
def __str__(self):
return self.name
-
+
class Expense(models.Model):
date = models.DateField()
amount = models.DecimalField(max_digits=10, decimal_places=2)
description = models.TextField(blank=True)
-
+
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="expenses",
)
-
+
category = models.ForeignKey(
Category,
on_delete=models.PROTECT,
related_name="expenses",
)
-
+
account = models.ForeignKey(
Account,
on_delete=models.PROTECT,
related_name="expenses",
)
-
+
tags = models.ManyToManyField(
Tag,
blank=True,
related_name="expenses",
)
-
+
created_at = models.DateField(auto_now_add=True)
-
+
class Meta:
ordering = ["-date"]
-
+
def __str__(self):
return "{} - {}".format(self.date, self.amount)
-
-
+
+
class Income(models.Model):
- owner = models.ForeignKey(
- settings.AUTH_USER_MODEL,
- on_delete=models.CASCADE
- )
+ owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
account = models.ForeignKey(
- Account,
- on_delete=models.CASCADE,
- related_name='incomes'
+ Account, on_delete=models.CASCADE, related_name="incomes"
)
-
+
name = models.CharField(max_length=150)
amount = models.DecimalField(max_digits=12, decimal_places=2)
date = models.DateField()
created_at = models.DateTimeField(auto_now_add=True)
-
+
class Meta:
- ordering = ['-date']
-
+ ordering = ["-date"]
+
def __str__(self):
- return f'{self.name} - {self.amount}'
-
+ return f"{self.name} - {self.amount}"
+
class FuelEntry(models.Model):
expense = models.OneToOneField(
- Expense,
- on_delete=models.CASCADE,
- related_name="fuel_data"
+ Expense, on_delete=models.CASCADE, related_name="fuel_data"
)
-
- odometer = models.PositiveIntegerField() # kilometers
+
+ odometer = models.PositiveIntegerField() # kilometers
liters = models.DecimalField(max_digits=8, decimal_places=2)
-
+
created_at = models.DateTimeField(auto_now_add=True)
-
+
class Meta:
- ordering = ['odometer']
+ ordering = ["odometer"]
def km_since_previous(self):
- previous = FuelEntry.objects.filter(
- expense__owner=self.expense.owner,
- odometer__lt=self.odometer
- ).order_by('-odometer').first()
-
+ previous = (
+ FuelEntry.objects.filter(
+ expense__owner=self.expense.owner, odometer__lt=self.odometer
+ )
+ .order_by("-odometer")
+ .first()
+ )
+
if previous:
return self.odometer - previous.odometer
-
+
return None
-
+
def price_per_liter(self):
if self.liters:
return self.expense.amount / self.liters
return 0
-
+
def consumption(self):
km = self.km_since_previous()
if km and km > 0:
return (self.liters / km) * 100
return None
+
+
+class Goal(models.Model):
+ owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
+ name = models.CharField(max_length=100)
+ target_amount = models.DecimalField(max_digits=12, decimal_places=2)
+ category = models.ForeignKey("Category", on_delete=models.CASCADE)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ def progress(self):
+ """
+ Calculate the accumulated spending for the goal category.
+ This method returns the sum of all expenses of the owner in the goal's category.
+
+ Returns:
+ Decimal: Total amount spent in the goal category, or Decimal('0') when there is no spending.
+
+ """
+ total = Expense.objects.filter(
+ owner=self.owner,
+ category=self.category,
+ ).aggregate(total=Sum("amount"))["total"]
+
+ return total or 0
+
+ def percentage(self):
+ """
+ Calculate the completion percentage of the goal.
+ This method returns how much of the target amount has been reached as a percentage.
+
+ Returns:
+ Decimal: Percentage of the goal that has been reached, or Decimal('0') when the target amount is zero.
+
+ """
+ if self.target_amount == 0:
+ return 0
+ return (self.progress() / self.target_amount) * 100
+
+ def __str__(self):
+ return self.name
diff --git a/expenses_manager/expenses/static/expenses/css/base.css b/expenses_manager/expenses/static/expenses/css/base.css
index a99bbb3..1130a12 100644
--- a/expenses_manager/expenses/static/expenses/css/base.css
+++ b/expenses_manager/expenses/static/expenses/css/base.css
@@ -310,4 +310,47 @@ tbody tr:hover {
.tag:hover {
background: #dbe3ee;
+}
+
+.dropdown {
+ position: relative;
+ cursor: pointer;
+ color: #e5e7eb;
+}
+
+.dropdown.active {
+ font-weight: bold;
+ border-bottom: 2px solid white;
+}
+
+.dropdown-toggle {
+ padding: 10px;
+ display: inline-block;
+}
+
+.dropdown-menu {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ background: white;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ min-width: 150px;
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
+}
+
+.dropdown-menu a {
+ display: block;
+ padding: 10px;
+ text-decoration: none;
+ color: #333;
+}
+
+.dropdown-menu a:hover {
+ background-color: #f5f5f5;
+}
+
+.dropdown:hover .dropdown-menu {
+ display: block;
}
\ No newline at end of file
diff --git a/expenses_manager/expenses/templates/expenses/base.html b/expenses_manager/expenses/templates/expenses/base.html
index 65a736c..1e6cb78 100644
--- a/expenses_manager/expenses/templates/expenses/base.html
+++ b/expenses_manager/expenses/templates/expenses/base.html
@@ -18,9 +18,18 @@
-
+
-
+
+
diff --git a/expenses_manager/expenses/templates/goals/form.html b/expenses_manager/expenses/templates/goals/form.html
new file mode 100644
index 0000000..0c54707
--- /dev/null
+++ b/expenses_manager/expenses/templates/goals/form.html
@@ -0,0 +1,20 @@
+{% extends "expenses/base.html" %}
+{% block title %}
+ Nuevo objetivo
+{% endblock %}
+
+{% block content %}
+
+ Nuevo objetivo
+
+
+
+
+ Volver
+{% endblock %}
\ No newline at end of file
diff --git a/expenses_manager/expenses/templates/goals/list.html b/expenses_manager/expenses/templates/goals/list.html
new file mode 100644
index 0000000..402b81b
--- /dev/null
+++ b/expenses_manager/expenses/templates/goals/list.html
@@ -0,0 +1,40 @@
+{% extends "expenses/base.html" %}
+
+{% load l10n %}
+
+{% block title %}
+ Objetivos
+{% endblock %}
+
+{% block content %}
+Objetivos
+
+➕ Nuevo objetivo
+
+
+
+ | Nombre |
+ Progreso |
+ |
+
+
+ {% for goal in goals %}
+
+ | {{ goal.name }} |
+
+ {{ goal.progress }} / {{ goal.target_amount }}
+
+
+ |
+
+
+ Editar
+ Eliminar
+ |
+
+ {% endfor %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/expenses_manager/expenses/urls.py b/expenses_manager/expenses/urls.py
index 7c85916..cce5358 100644
--- a/expenses_manager/expenses/urls.py
+++ b/expenses_manager/expenses/urls.py
@@ -24,4 +24,9 @@ urlpatterns = [
path('fuel/create/', views.fuel_create, name='fuel_create'),
path('fuel//edit/', views.fuel_edit, name='fuel_edit'),
path('categories/', views.category_list, name='category_list'),
+ path('settings/', views.settings_index, name='settings_index'),
+ path('goals/', views.goal_list, name='goal_list'),
+ path('goals/new/', views.goal_create, name='goal_create'),
+ path('goals//edit/', views.goal_edit, name='goal_edit'),
+ path('goals//delete/', views.goal_delete, name='goal_delete'),
]
\ No newline at end of file
diff --git a/expenses_manager/expenses/views.py b/expenses_manager/expenses/views.py
index cd06ce7..5b69948 100644
--- a/expenses_manager/expenses/views.py
+++ b/expenses_manager/expenses/views.py
@@ -2,8 +2,17 @@ from operator import truediv
from datetime import date, datetime
from django.contrib import messages
from django.template import context
-from .models import Account, Category, Expense, FuelEntry, Tag, Income
-from .forms import ExpenseForm, IncomeForm, TagForm, AccountForm, FuelEntryForm, CategoryForm
+from .models import Account, Category, Expense, FuelEntry, Tag, Income, Goal
+from .forms import (
+ ExpenseForm,
+ IncomeForm,
+ TagForm,
+ AccountForm,
+ FuelEntryForm,
+ CategoryForm,
+ GoalForm,
+)
+
# from dateutli.relativedelta import relativedelta
from django.db.models import Sum
@@ -16,26 +25,28 @@ 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'
+ 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:
@@ -43,128 +54,115 @@ def sub_months(year, month, n):
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]
- )
-
+ 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
-
+ 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()
- )
-
+
+ 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', {
- 'active_menu': 'home',
- '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],
- })
+ 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",
+ {
+ "active_menu": "home",
+ "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')
- )
-
+ 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'))
- account_id = _get_int(request.GET.get('account'))
-
- tag_ids = request.GET.getlist('tag')
+ year = _get_int(request.GET.get("year"))
+ month = _get_int(request.GET.get("month"))
+ category = _get_int(request.GET.get("category"))
+ account_id = _get_int(request.GET.get("account"))
+
+ tag_ids = request.GET.getlist("tag")
tag_ids = [int(t) for t in tag_ids]
-
+
if year:
expenses = expenses.filter(date__year=year)
-
+
if month:
expenses = expenses.filter(date__month=month)
-
+
if category:
expenses = expenses.filter(category_id=category)
-
+
if tag_ids:
expenses = expenses.filter(tags__id__in=tag_ids).distinct()
-
+
if account_id:
expenses = expenses.filter(account_id=account_id)
-
+
selected_tags = tag_ids or []
-
- expenses = expenses.order_by('-date')
-
- total_amount = expenses.aggregate(
- total=Sum('amount')
- )['total'] or 0
-
+
+ expenses = expenses.order_by("-date")
+
+ total_amount = expenses.aggregate(total=Sum("amount"))["total"] or 0
+
expense_count = expenses.count()
-
- category_count = (
- expenses.values('category')
- .distinct()
- .count()
- )
-
+
+ category_count = expenses.values("category").distinct().count()
+
# Pagination
paginator = Paginator(expenses, 10)
- page_number = request.GET.get('page')
- page_obj = paginator.get_page(page_number)
-
+ page_number = request.GET.get("page")
+ page_obj = paginator.get_page(page_number)
+
query_params = request.GET.copy()
- query_params.pop('page', None)
-
+ query_params.pop("page", None)
+
# tags with state
tags_with_state = []
-
+
for tag in Tag.objects.filter(owner=request.user):
if tag.id in selected_tags:
new_tags = [t for t in selected_tags if t != tag.id]
@@ -172,43 +170,43 @@ def expense_list(request):
else:
new_tags = selected_tags + [tag.id]
active = False
-
- query = '&'.join([f'tag={t}' for t in new_tags])
-
- tags_with_state.append({
- 'id': tag.id,
- 'name': tag.name,
- 'active': active,
- 'query': query,
- })
- advanced_filters_open = bool(
- category or selected_tags
- )
+ query = "&".join([f"tag={t}" for t in new_tags])
- return render (
+ tags_with_state.append(
+ {
+ "id": tag.id,
+ "name": tag.name,
+ "active": active,
+ "query": query,
+ }
+ )
+
+ advanced_filters_open = bool(category or selected_tags)
+
+ return render(
request,
- 'expenses/expense_list.html',
+ "expenses/expense_list.html",
{
- 'active_menu': 'expenses',
- '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,
- 'kpi_total': total_amount,
- 'kpi_count': expense_count,
- 'kpi_categories': category_count,
- 'selected_tags': selected_tags,
- 'tags': Tag.objects.filter(owner=request.user),
- 'tags_with_state': tags_with_state,
- 'accounts': Account.objects.filter(owner=request.user),
- 'selected_account': account_id,
- 'advanced_filters_open' : advanced_filters_open,
- 'query_params': query_params.urlencode(),
+ "active_menu": "expenses",
+ "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,
+ "kpi_total": total_amount,
+ "kpi_count": expense_count,
+ "kpi_categories": category_count,
+ "selected_tags": selected_tags,
+ "tags": Tag.objects.filter(owner=request.user),
+ "tags_with_state": tags_with_state,
+ "accounts": Account.objects.filter(owner=request.user),
+ "selected_account": account_id,
+ "advanced_filters_open": advanced_filters_open,
+ "query_params": query_params.urlencode(),
},
)
@@ -222,16 +220,16 @@ def expense_create(request):
expense.owner = request.user
expense.save()
form.save_m2m()
-
- messages.success(request, 'Gasto creado correctamente.')
- return redirect('expense_list')
+
+ messages.success(request, "Gasto creado correctamente.")
+ return redirect("expense_list")
else:
form = ExpenseForm(user=request.user)
-
+
return render(
request,
- 'expenses/expense_form.html',
- {'active_menu': 'expenses','form': form},
+ "expenses/expense_form.html",
+ {"active_menu": "expenses", "form": form},
)
@@ -245,24 +243,22 @@ def expense_edit(request, pk):
)
if request.method == "POST":
- form = ExpenseForm(
- request.POST or None,
- instance=expense,
- user=request.user)
-
+ form = ExpenseForm(request.POST or None, instance=expense, user=request.user)
+
if form.is_valid():
expense = form.save()
- messages.success(request, 'Gasto actualizado')
- return redirect('expense_list')
+ messages.success(request, "Gasto actualizado")
+ return redirect("expense_list")
else:
- form = ExpenseForm(instance=expense, user=request.user)
-
+ form = ExpenseForm(instance=expense, user=request.user)
+
return render(
request,
- 'expenses/expense_form.html', {
- 'active_menu': 'expenses',
- 'form': form,
- }
+ "expenses/expense_form.html",
+ {
+ "active_menu": "expenses",
+ "form": form,
+ },
)
@@ -273,101 +269,93 @@ def expense_delete(request, pk):
pk=pk,
owner=request.user,
)
-
- if request.method == 'POST':
+
+ if request.method == "POST":
expense.delete()
- messages.success(request, 'Gasto eliminado correctamente.')
- return redirect('expense_list')
-
+ messages.success(request, "Gasto eliminado correctamente.")
+ return redirect("expense_list")
+
return render(
request,
- 'expenses/expense_confirm_delete.html',
- {'active_menu': 'expenses','expense': expense},
+ "expenses/expense_confirm_delete.html",
+ {"active_menu": "expenses", "expense": expense},
)
@login_required
-def dashboard(request):
+def dashboard(request):
# ------------------
# Filters
# ------------------
- year = _get_int(request.GET.get('year'))
- month = _get_int(request.GET.get('month'))
- period = request.GET.get('period')
-
+ year = _get_int(request.GET.get("year"))
+ month = _get_int(request.GET.get("month"))
+ period = request.GET.get("period")
+
current_year = date.today().year
-
- account_id = _get_int(request.GET.get('account'))
+
+ account_id = _get_int(request.GET.get("account"))
accounts = Account.objects.filter(
owner=request.user,
active=True,
)
-
+
selected_year = year or current_year
- selected_month = month
-
+ selected_month = month
+
compare_enabled = request.GET.get("compare") == "1"
-
+
# ------------------
# Queryset base
# -----------------
expenses = Expense.objects.filter(owner=request.user)
-
+
selected_account_obj = None
kpi_balance = None
- if account_id:
- expenses = expenses.filter(account_id=account_id)
+ if account_id:
+ expenses = expenses.filter(account_id=account_id)
selected_account_obj = accounts.filter(id=account_id).first()
- kpi_balance = selected_account_obj.current_balance() if selected_account_obj else 0
- else:
- kpi_balance = sum(
- account.current_balance() for account in accounts
+ kpi_balance = (
+ selected_account_obj.current_balance() if selected_account_obj else 0
)
-
+ else:
+ kpi_balance = sum(account.current_balance() for account in accounts)
+
today = date.today()
-
- if period == 'this_month':
+
+ if period == "this_month":
selected_year = today.year
selected_month = today.month
-
- elif period == 'last_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':
+
+ 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
-
+ expenses_filtered = expenses_filtered.filter(date__month=selected_month)
+
+ total_amount = expenses_filtered.aggregate(total=Sum("amount"))["total"] or 0
+
expense_count = expenses_filtered.count()
-
- category_count = (
- expenses_filtered
- .values('category')
- .distinct()
- .count()
- )
+
+ category_count = expenses_filtered.values("category").distinct().count()
# ------------------
# Totals by category
# -----------------
by_category = (
- expenses_filtered
- .values('category__name')
- .annotate(total=Sum('amount'))
- .order_by('category__name')
+ expenses_filtered.values("category__name")
+ .annotate(total=Sum("amount"))
+ .order_by("category__name")
)
# ------------------
@@ -375,56 +363,51 @@ def dashboard(request):
# -----------------
by_month_qs = (
- expenses_filtered
- .annotate(month=ExtractMonth('date'))
- .values('month')
- .annotate(total=Sum('amount'))
+ 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
- }
-
+
+ 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),
+ "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)
+ expenses.annotate(year=ExtractYear("date"))
+ .values_list("year", flat=True)
.distinct()
- .order_by('year')
+ .order_by("year")
)
-
+
# ------------------
# Chart
# -----------------
chart_labels = months
- chart_totals = [row['total'] for row in by_month]
-
+ 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 account_id:
@@ -438,191 +421,174 @@ def dashboard(request):
else:
prev_year = selected_year
prev_month = selected_month - 1
-
+
previous_expenses = previous_expenses.filter(
- date__year=prev_year,
- date__month=prev_month
+ 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
- )
-
+ 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
-
+ percentage = (kpi_difference / previous_total) * 100
+
kpi_trend = None
if kpi_difference is not None:
if kpi_difference > 0:
- kpi_trend = 'up'
+ kpi_trend = "up"
elif kpi_difference < 0:
- kpi_trend = 'down'
+ kpi_trend = "down"
else:
- kpi_trend = 'equal'
+ 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'))
+ # ------------------
+ # 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
- }
-
+
+ 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
+ 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),
- })
-
+
+ category_comparison.append(
+ {
+ "category": category,
+ "current": current_total,
+ "previous": previous_total_cat,
+ "difference": difference,
+ "difference_abs": abs(difference),
+ }
+ )
+
# ------------------
# Previous expenses by category
# ------------------
accounts_charts = []
for account in accounts:
monthly_data = account.monthly_balance(selected_year)
- m_balance = [row['balance'] for row in monthly_data]
-
- accounts_charts.append({
- 'id': account.id,
- 'name': account.name,
- 'data': m_balance,
- 'current_balance': account.current_balance(),
- })
-
+ m_balance = [row["balance"] for row in monthly_data]
+
+ accounts_charts.append(
+ {
+ "id": account.id,
+ "name": account.name,
+ "data": m_balance,
+ "current_balance": account.current_balance(),
+ }
+ )
+
# Send the data to the dashboard
- return render(request, 'expenses/dashboard.html', {
- 'active_menu': 'dashboard',
- '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,
- 'accounts':accounts,
- 'selected_account': account_id,
- 'selected_account_obj': selected_account_obj,
- 'kpi_balance': kpi_balance,
- 'accounts_charts': accounts_charts,
- 'period':period,
- })
+ return render(
+ request,
+ "expenses/dashboard.html",
+ {
+ "active_menu": "dashboard",
+ "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,
+ "accounts": accounts,
+ "selected_account": account_id,
+ "selected_account_obj": selected_account_obj,
+ "kpi_balance": kpi_balance,
+ "accounts_charts": accounts_charts,
+ "period": period,
+ },
+ )
@login_required
def tag_list(request):
tags = Tag.objects.filter(owner=request.user)
-
+
return render(
- request,
- 'expenses/tag_list.html',
- {'active_menu': 'tags','tags':tags}
+ request, "expenses/tag_list.html", {"active_menu": "settings", "tags": tags}
)
@login_required
def tag_create(request):
- if request.method == 'POST':
+ if request.method == "POST":
form = TagForm(request.POST)
if form.is_valid():
tag = form.save(commit=False)
tag.owner = request.user
tag.save()
- messages.success(request, 'Etiqueta creada correctamente.')
- return redirect('tag_list')
+ messages.success(request, "Etiqueta creada correctamente.")
+ return redirect("tag_list")
else:
form = TagForm()
-
+
return render(
- request,
- 'expenses/tag_form.html',
- {'active_menu': 'tags','form': form}
+ request, "expenses/tag_form.html", {"active_menu": "settings", "form": form}
)
@login_required
def tag_edit(request, pk):
- tag = get_object_or_404(
- Tag,
- pk=pk,
- owner=request.user
- )
-
- if request.method == 'POST':
+ tag = get_object_or_404(Tag, pk=pk, owner=request.user)
+
+ if request.method == "POST":
form = TagForm(request.POST, instance=tag)
if form.is_valid():
form.save()
- messages.success(request, 'Etiqueta actualizada.')
- return redirect('tag_list')
+ messages.success(request, "Etiqueta actualizada.")
+ return redirect("tag_list")
else:
form = TagForm(instance=tag)
-
+
return render(
- request,
- 'expenses/tag_form.html',
- {'active_menu': 'tags','form': form}
+ request, "expenses/tag_form.html", {"active_menu": "settings", "form": form}
)
@login_required
def tag_delete(request, pk):
- tag = get_object_or_404(
- Tag,
- pk=pk,
- owner=request.user
- )
-
- if request.method == 'POST':
+ tag = get_object_or_404(Tag, pk=pk, owner=request.user)
+
+ if request.method == "POST":
tag.delete()
- messages.success(request, 'Etiqueta eliminada.')
- return redirect('tag_list')
-
+ messages.success(request, "Etiqueta eliminada.")
+ return redirect("tag_list")
+
return render(
request,
- 'expenses/tag_confirm_delete.html',
- {'active_menu': 'tags','tag': tag}
+ "expenses/tag_confirm_delete.html",
+ {"active_menu": "settings", "tag": tag},
)
@@ -631,200 +597,191 @@ def account_list(request):
accounts = Account.objects.filter(owner=request.user)
return render(
request,
- 'expenses/account_list.html',
- {'active_menu': 'accounts','accounts': accounts}
+ "expenses/account_list.html",
+ {"active_menu": "accounts", "accounts": accounts},
)
@login_required
def account_create(request):
- if request.method == 'POST':
+ if request.method == "POST":
form = AccountForm(request.POST)
if form.is_valid():
account = form.save(commit=False)
account.owner = request.user
account.save()
- messages.success(request, 'Cuenta creada correctamente.')
- return redirect('account_list')
+ messages.success(request, "Cuenta creada correctamente.")
+ return redirect("account_list")
else:
form = AccountForm()
-
+
return render(
- request,
- 'expenses/account_form.html',
- {'active_menu': 'accounts','form': form}
+ request, "expenses/account_form.html", {"active_menu": "accounts", "form": form}
)
-
+
+
@login_required
def account_edit(request, pk):
- account = get_object_or_404(
- Account,
- pk=pk,
- owner=request.user
- )
-
- if request.method == 'POST':
+ account = get_object_or_404(Account, pk=pk, owner=request.user)
+
+ if request.method == "POST":
form = AccountForm(request.POST, instance=account)
if form.is_valid():
form.save()
- messages.success(request, 'Cuenta actualizada.')
- return redirect('account_list')
+ messages.success(request, "Cuenta actualizada.")
+ return redirect("account_list")
else:
form = AccountForm(instance=account)
-
+
return render(
- request,
- 'expenses/account_form.html',
- {'active_menu': 'accounts','form': form}
+ request, "expenses/account_form.html", {"active_menu": "accounts", "form": form}
)
+
@login_required
def account_delete(request, pk):
account = get_object_or_404(Account, pk=pk, owner=request.user)
-
- if request.method == 'POST':
+
+ if request.method == "POST":
account.active = False
account.save()
- messages.success(request, 'Cuenta eliminada.')
- return redirect('account_list')
-
+ messages.success(request, "Cuenta eliminada.")
+ return redirect("account_list")
+
return render(
request,
- 'expenses/account_confirm_delete.html',
- {'active_menu': 'accounts','account':account}
+ "expenses/account_confirm_delete.html",
+ {"active_menu": "accounts", "account": account},
)
+
@login_required
def income_create(request):
- if request.method == 'POST':
+ if request.method == "POST":
form = IncomeForm(request.POST, user=request.user)
if form.is_valid():
income = form.save(commit=False)
income.owner = request.user
income.save()
- messages.success(request, 'Ingreso creado correctamente.')
- return redirect('income_list')
+ messages.success(request, "Ingreso creado correctamente.")
+ return redirect("income_list")
else:
form = IncomeForm(user=request.user)
-
+
return render(
- request,
- 'expenses/income_form.html',
- {'active_menu': 'incomes','form': form}
+ request, "expenses/income_form.html", {"active_menu": "incomes", "form": form}
)
+
@login_required
def income_list(request):
incomes = Income.objects.filter(owner=request.user)
-
+
return render(
request,
- 'expenses/income_list.html',
- {'active_menu': 'incomes','incomes': incomes}
+ "expenses/income_list.html",
+ {"active_menu": "incomes", "incomes": incomes},
)
+
@login_required
def income_edit(request, pk):
- income = get_object_or_404(
- Income,
- pk=pk,
- owner=request.user
- )
-
- if request.method == 'POST':
+ income = get_object_or_404(Income, pk=pk, owner=request.user)
+
+ if request.method == "POST":
form = IncomeForm(request.POST, instance=income, user=request.user)
if form.is_valid():
form.save()
- messages.success(request, 'Ingreso actualizado.')
- return redirect('income_list')
+ messages.success(request, "Ingreso actualizado.")
+ return redirect("income_list")
else:
form = IncomeForm(instance=income, user=request.user)
-
+
return render(
- request,
- 'expenses/income_form.html',
- {'active_menu': 'incomes','form': form}
+ request, "expenses/income_form.html", {"active_menu": "incomes", "form": form}
)
+
@login_required
def income_delete(request, pk):
income = get_object_or_404(Income, pk=pk, owner=request.user)
-
- if request.method == 'POST':
+
+ if request.method == "POST":
income.delete()
- messages.success(request, 'Ingreso eliminado.')
- return redirect('income_list')
-
+ messages.success(request, "Ingreso eliminado.")
+ return redirect("income_list")
+
return render(
request,
- 'expenses/income_confirm_delete.html',
- {'active_menu': 'incomes','income':income}
+ "expenses/income_confirm_delete.html",
+ {"active_menu": "incomes", "income": income},
)
+
@login_required
def fuel_create(request):
- if request.method == 'POST':
+ if request.method == "POST":
form = FuelEntryForm(request.POST, user=request.user)
-
+
if form.is_valid():
expense = Expense.objects.create(
owner=request.user,
- date=form.cleaned_data['date'],
- amount=form.cleaned_data['amount'],
- account=form.cleaned_data['account'],
- category=Category.objects.get(slug='gasolina'),
- description='Repostaje',
+ date=form.cleaned_data["date"],
+ amount=form.cleaned_data["amount"],
+ account=form.cleaned_data["account"],
+ category=Category.objects.get(slug="gasolina"),
+ description="Repostaje",
)
-
+
FuelEntry.objects.create(
expense=expense,
- odometer=form.cleaned_data['odometer'],
- liters=form.cleaned_data['liters'],
+ odometer=form.cleaned_data["odometer"],
+ liters=form.cleaned_data["liters"],
)
-
- return redirect('fuel_list')
-
+
+ return redirect("fuel_list")
+
else:
form = FuelEntryForm(user=request.user)
-
- return render(request, 'fuel/create.html',{
- 'active_menu': 'fuel',
- 'form': form
- })
+
+ return render(
+ request, "fuel/create.html", {"active_menu": "fuel", "form": form}
+ )
+
@login_required
def fuel_list(request):
- selected_year = request.GET.get('year')
+ selected_year = request.GET.get("year")
current_year = datetime.now().year
selected_year = int(selected_year) if selected_year else current_year
- fuels = FuelEntry.objects.filter(
- expense__owner=request.user,
- expense__date__year=selected_year
- ).select_related('expense').order_by('expense__date')
-
- monthly_expenses = (
- fuels
- .annotate(month=ExtractMonth('expense__date'))
- .values('month')
- .annotate(total=Sum('expense__amount'))
+ fuels = (
+ FuelEntry.objects.filter(
+ expense__owner=request.user, expense__date__year=selected_year
+ )
+ .select_related("expense")
+ .order_by("expense__date")
)
- month_map = {m['month']: float(m['total']) for m in monthly_expenses}
+ monthly_expenses = (
+ fuels.annotate(month=ExtractMonth("expense__date"))
+ .values("month")
+ .annotate(total=Sum("expense__amount"))
+ )
- monthly_data = [
- month_map.get(month, 0)
- for month in range(1, 13)
- ]
+ month_map = {m["month"]: float(m["total"]) for m in monthly_expenses}
+
+ monthly_data = [month_map.get(month, 0) for month in range(1, 13)]
year_list = (
FuelEntry.objects.filter(
- expense__owner=request.user,)
- .annotate(year=ExtractYear('expense__date'))
- .values_list('year', flat=True)
+ expense__owner=request.user,
+ )
+ .annotate(year=ExtractYear("expense__date"))
+ .values_list("year", flat=True)
.distinct()
- .order_by('year')
+ .order_by("year")
)
km_data = []
@@ -834,20 +791,23 @@ def fuel_list(request):
km = fuel.km_since_previous()
if km:
km_data.append(km)
- dates.append(fuel.expense.date.strftime('%Y-%m-%d'))
+ dates.append(fuel.expense.date.strftime("%Y-%m-%d"))
- return render( request, 'fuel/list.html',
+ return render(
+ request,
+ "fuel/list.html",
{
- 'active_menu': 'fuel',
- 'fuels': fuels,
- 'monthly_data': monthly_data,
- 'km_data': km_data,
- 'km_dates': dates,
- 'selected_year': selected_year,
- 'year_list': year_list,
- }
+ "active_menu": "fuel",
+ "fuels": fuels,
+ "monthly_data": monthly_data,
+ "km_data": km_data,
+ "km_dates": dates,
+ "selected_year": selected_year,
+ "year_list": year_list,
+ },
)
+
@login_required
def fuel_edit(request, pk):
expense = get_object_or_404(
@@ -855,70 +815,152 @@ def fuel_edit(request, pk):
pk=pk,
owner=request.user,
)
-
+
fuel = get_object_or_404(FuelEntry, expense=expense)
-
+
if request.method == "POST":
- form = FuelEntryForm(
- request.POST,
- user=request.user
- )
-
+ form = FuelEntryForm(request.POST, user=request.user)
+
if form.is_valid():
# Update expense
- expense.date = form.cleaned_data['date']
- expense.amount = form.cleaned_data['amount']
- expense.account = form.cleaned_data['account']
- expense.description = 'Repostaje'
+ expense.date = form.cleaned_data["date"]
+ expense.amount = form.cleaned_data["amount"]
+ expense.account = form.cleaned_data["account"]
+ expense.description = "Repostaje"
expense.save()
-
+
# Update FuelEntry
- fuel.odometer = form.cleaned_data['odometer']
- fuel.liters = form.cleaned_data['liters']
+ fuel.odometer = form.cleaned_data["odometer"]
+ fuel.liters = form.cleaned_data["liters"]
fuel.save()
-
- return redirect('expense_list')
- else:
+
+ return redirect("expense_list")
+ else:
fuel = expense.fuel_data
# Initialize manually
form = FuelEntryForm(
initial={
- 'date': expense.date,
- 'amount': expense.amount,
- 'account': expense.account,
- 'odometer': fuel.odometer,
- 'liters': fuel.liters
- },
- user=request.user
- )
-
+ "date": expense.date,
+ "amount": expense.amount,
+ "account": expense.account,
+ "odometer": fuel.odometer,
+ "liters": fuel.liters,
+ },
+ user=request.user,
+ )
+
return render(
- request, 'fuel/create.html', {
- 'active_menu': 'expenses',
- 'form': form,
- 'editing': True,
- }
+ request,
+ "fuel/create.html",
+ {
+ "active_menu": "expenses",
+ "form": form,
+ "editing": True,
+ },
)
+
@login_required
def category_list(request):
- categories = Category.objects.filter(
- owner=request.user
- )
-
+ categories = Category.objects.filter(owner=request.user)
+
if request.method == "POST":
form = CategoryForm(request.POST)
if form.is_valid():
category = form.save(commit=False)
category.owner = request.user
category.save()
- return redirect('category_list')
+ return redirect("category_list")
else:
form = CategoryForm()
-
- return render(request, 'categories/list.html', {
- 'active_menu': 'categories',
- 'categories': categories,
- 'form': form,
- })
-
\ No newline at end of file
+
+ return render(
+ request,
+ "categories/list.html",
+ {
+ "active_menu": "settings",
+ "categories": categories,
+ "form": form,
+ },
+ )
+
+
+@login_required
+def goal_list(request):
+ goals = Goal.objects.filter(owner=request.user)
+
+ return render(
+ request,
+ "goals/list.html",
+ {
+ "goals": goals,
+ "active_menu": "settings",
+ },
+ )
+
+
+@login_required
+def goal_create(request):
+ if request.method == "POST":
+ form = GoalForm(request.POST, user=request.user)
+
+ if form.is_valid():
+ goal = form.save(commit=False)
+ goal.owner = request.user
+ goal.save()
+
+ return redirect("goal_list")
+ else:
+ form = GoalForm(user=request.user)
+
+ return render(
+ request,
+ "goals/form.html",
+ {
+ "form": form,
+ "title": "Nuevo objetivo",
+ "active_menu": "settings",
+ },
+ )
+
+
+@login_required
+def goal_edit(request, pk):
+ goal = get_object_or_404(Goal,
+ pk=pk,
+ owner=request.user)
+
+ if request.method == "POST":
+ form = GoalForm(request.POST, instance=goal, user=request.user)
+
+ if form.is_valid():
+ form.save()
+ return redirect("goal_list")
+
+ else:
+ form = GoalForm(instance=goal, user=request.user)
+
+ return render(
+ request,
+ "goals/form.html",
+ {
+ "form": form,
+ "title": "Editar objetivo",
+ "active_menu": "settings",
+ },
+ )
+
+
+@login_required
+def goal_delete(request, pk):
+ goal = get_object_or_404(Goal,
+ pk=pk,
+ owner=request.user)
+
+ goal.delete()
+ return redirect("goal_list")
+
+
+@login_required
+def settings_index(request):
+ return render(request, "settings/index.html")