Compare commits
No commits in common. "a2eec6749166ca9530350550228e4a1ea8c8813c" and "2267146d0d4d5dc0d976a136cc83f247996eb3df" have entirely different histories.
a2eec67491
...
2267146d0d
@ -92,43 +92,10 @@ class CategoryForm(forms.ModelForm):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
queryset = Category.objects.filter(owner=user)
|
self.fields["parent"].queryset = Category.objects.filter(
|
||||||
|
owner=user,
|
||||||
if self.instance.pk:
|
|
||||||
queryset = queryset.exclude(
|
|
||||||
pk__in=self._self_and_descendant_ids(self.instance)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.fields["parent"].queryset = queryset
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _self_and_descendant_ids(category):
|
|
||||||
ids = [category.pk]
|
|
||||||
pending = [category.pk]
|
|
||||||
|
|
||||||
while pending:
|
|
||||||
children = list(
|
|
||||||
Category.objects.filter(parent_id__in=pending)
|
|
||||||
.exclude(pk__in=ids)
|
|
||||||
.values_list("pk", flat=True)
|
|
||||||
)
|
|
||||||
ids.extend(children)
|
|
||||||
pending = children
|
|
||||||
|
|
||||||
return ids
|
|
||||||
|
|
||||||
def clean_parent(self):
|
|
||||||
parent = self.cleaned_data.get("parent")
|
|
||||||
|
|
||||||
if parent and self.instance.pk:
|
|
||||||
if parent.pk in self._self_and_descendant_ids(self.instance):
|
|
||||||
raise forms.ValidationError(
|
|
||||||
"Una categoría no puede ser su propio padre ni depender "
|
|
||||||
"de una de sus subcategorías."
|
|
||||||
)
|
|
||||||
|
|
||||||
return parent
|
|
||||||
|
|
||||||
|
|
||||||
class GoalForm(forms.ModelForm):
|
class GoalForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@ -1,38 +0,0 @@
|
|||||||
{% extends "expenses/base.html" %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
Categorías
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h2>Eliminar categoría</h2>
|
|
||||||
|
|
||||||
<p>¿Seguro que quieres eliminar la categoría <strong>{{ category.name }}</strong>?</p>
|
|
||||||
|
|
||||||
{% if expense_count %}
|
|
||||||
<p class="form-errors">
|
|
||||||
Esta categoría tiene {{ expense_count }} gasto{{ expense_count|pluralize }} asociado{{ expense_count|pluralize }}
|
|
||||||
y no se puede eliminar. Reasigna esos gastos a otra categoría primero.
|
|
||||||
</p>
|
|
||||||
{% else %}
|
|
||||||
{% if children %}
|
|
||||||
<p class="form-errors">
|
|
||||||
Atención: también se eliminarán sus subcategorías:
|
|
||||||
{% for child in children %}<strong>{{child.name}}</strong>{% if not forloop.last %}, {% endif %}{% endfor %}.
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if goal_count %}
|
|
||||||
<p class="form-errors">
|
|
||||||
Atención: se eliminarán también {{ goal_count }} objetivo{{ goal_count|pluralize }} asociado{{ goal_count|pluralize }} a esta categoría.
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form.as_p }}
|
|
||||||
<button class="btn danger">Eliminar</button>
|
|
||||||
<a class="btn" href="{% url 'category_list' %}">Cancelar</a>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
{% extends "expenses/base.html" %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
Categorías
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h2>Editar categoría</h2>
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form.as_p }}
|
|
||||||
<button type="submit">Guardar</button>
|
|
||||||
<a class="btn" href="{% url 'category_list' %}">Cancelar</a>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@ -30,10 +30,6 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ category.name }}</td>
|
<td>{{ category.name }}</td>
|
||||||
<td>{% if category.parent %}{{ category.parent.name }}{% endif %}</td>
|
<td>{% if category.parent %}{{ category.parent.name }}{% endif %}</td>
|
||||||
<td class="table-actions">
|
|
||||||
<a href="{% url 'category_edit' category.id %}">Editar</a>
|
|
||||||
<a href="{% url 'category_delete' category.id %}" class="danger">Eliminar</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -24,8 +24,6 @@ urlpatterns = [
|
|||||||
path('fuel/create/', views.fuel_create, name='fuel_create'),
|
path('fuel/create/', views.fuel_create, name='fuel_create'),
|
||||||
path('fuel/<int:pk>/edit/', views.fuel_edit, name='fuel_edit'),
|
path('fuel/<int:pk>/edit/', views.fuel_edit, name='fuel_edit'),
|
||||||
path('categories/', views.category_list, name='category_list'),
|
path('categories/', views.category_list, name='category_list'),
|
||||||
path('categories/<int:pk>/edit/', views.category_edit, name='category_edit'),
|
|
||||||
path('categories/<int:pk>/delete/', views.category_delete, name='category_delete'),
|
|
||||||
path('settings/', views.settings_index, name='settings_index'),
|
path('settings/', views.settings_index, name='settings_index'),
|
||||||
path('goals/', views.goal_list, name='goal_list'),
|
path('goals/', views.goal_list, name='goal_list'),
|
||||||
path('goals/new/', views.goal_create, name='goal_create'),
|
path('goals/new/', views.goal_create, name='goal_create'),
|
||||||
|
|||||||
@ -13,8 +13,8 @@ from .forms import (
|
|||||||
GoalForm,
|
GoalForm,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from django.db.models import Sum
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db.models import Sum, ProtectedError
|
|
||||||
from django.db.models.functions import ExtractMonth, ExtractYear, ExtractDay
|
from django.db.models.functions import ExtractMonth, ExtractYear, ExtractDay
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
@ -859,60 +859,6 @@ def category_list(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def category_edit(request, pk):
|
|
||||||
category = get_object_or_404(Category, pk=pk, owner=request.user)
|
|
||||||
|
|
||||||
if request.method == "POST":
|
|
||||||
form = CategoryForm(request.POST, instance=category, user=request.user)
|
|
||||||
if form.is_valid():
|
|
||||||
form.save()
|
|
||||||
messages.success(request, "Categoría actualizada")
|
|
||||||
return redirect("category_list")
|
|
||||||
else:
|
|
||||||
form = CategoryForm(instance=category, user=request.user)
|
|
||||||
|
|
||||||
return render(
|
|
||||||
request,
|
|
||||||
"categories/form.html",
|
|
||||||
{
|
|
||||||
"active_menu": "settings",
|
|
||||||
"form":form,
|
|
||||||
"category": category,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def category_delete(request, pk):
|
|
||||||
category = get_object_or_404(Category, pk=pk, owner=request.user)
|
|
||||||
|
|
||||||
if request.method == "POST":
|
|
||||||
try:
|
|
||||||
category.delete()
|
|
||||||
except ProtectedError:
|
|
||||||
messages.error(
|
|
||||||
request,
|
|
||||||
f"No se puede eliminar «{category.name}»; tiene gastos asociados. "
|
|
||||||
"Reasigna esos gastos a otra categoría antes de borrarla."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
messages.success(request, "Categoría eliminada.")
|
|
||||||
return redirect("category_list")
|
|
||||||
|
|
||||||
return render(
|
|
||||||
request,
|
|
||||||
"categories/confirm_delete.html",
|
|
||||||
{
|
|
||||||
"active_menu": "settings",
|
|
||||||
"category": category,
|
|
||||||
"children": category.children.all(),
|
|
||||||
"expense_count": category.expenses.count(),
|
|
||||||
"goal_count": category.goal_set.count(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def goal_list(request):
|
def goal_list(request):
|
||||||
goals = Goal.objects.filter(owner=request.user)
|
goals = Goal.objects.filter(owner=request.user)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user