From 041d4aa17de1ac3d053f8b3fe2d63385acd1ab5d Mon Sep 17 00:00:00 2001 From: JKuijperM Date: Tue, 5 May 2026 13:01:48 +0200 Subject: [PATCH] Created the goal functionality and the configuration tab with category, tags and goals --- expenses_manager/expenses/forms.py | 103 +- .../expenses/migrations/0008_goal.py | 27 + expenses_manager/expenses/models.py | 266 +++-- .../expenses/static/expenses/css/base.css | 43 + .../expenses/templates/expenses/base.html | 13 +- .../expenses/templates/goals/form.html | 20 + .../expenses/templates/goals/list.html | 40 + expenses_manager/expenses/urls.py | 5 + expenses_manager/expenses/views.py | 1024 +++++++++-------- 9 files changed, 880 insertions(+), 661 deletions(-) create mode 100644 expenses_manager/expenses/migrations/0008_goal.py create mode 100644 expenses_manager/expenses/templates/goals/form.html create mode 100644 expenses_manager/expenses/templates/goals/list.html 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 @@ Gastos Cuentas Ingresos - Etiquetas + Repostajes - Categorías + + 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 +

+ +
+ {% csrf_token %} + {{ form.as_p }} + +
+ + 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 + + + + + + + + + {% for goal in goals %} + + + + + + + {% endfor %} +
NombreProgreso
{{ goal.name }} + {{ goal.progress }} / {{ goal.target_amount }} + +
+
+
+
+ Editar + Eliminar +
+ +{% 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")