diff --git a/expenses_manager/expenses/migrations/0011_alter_tag_options.py b/expenses_manager/expenses/migrations/0011_alter_tag_options.py new file mode 100644 index 0000000..70c4aa6 --- /dev/null +++ b/expenses_manager/expenses/migrations/0011_alter_tag_options.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.10 on 2026-09-09 11:54 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('expenses', '0010_goal_account_goal_include_subcategories_goal_kind_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='tag', + options={'ordering': ('name',)}, + ), + ] diff --git a/expenses_manager/expenses/migrations/0012_alter_account_options_alter_category_options_and_more.py b/expenses_manager/expenses/migrations/0012_alter_account_options_alter_category_options_and_more.py new file mode 100644 index 0000000..f113ab4 --- /dev/null +++ b/expenses_manager/expenses/migrations/0012_alter_account_options_alter_category_options_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 5.2.10 on 2026-09-09 12:59 + +import django.db.models.functions.text +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('expenses', '0011_alter_tag_options'), + ] + + operations = [ + migrations.AlterModelOptions( + name='account', + options={'ordering': [django.db.models.functions.text.Lower('name')]}, + ), + migrations.AlterModelOptions( + name='category', + options={'ordering': [django.db.models.functions.text.Lower('name')], 'verbose_name_plural': 'categories'}, + ), + migrations.AlterModelOptions( + name='tag', + options={'ordering': [django.db.models.functions.text.Lower('name')]}, + ), + ] diff --git a/expenses_manager/expenses/models.py b/expenses_manager/expenses/models.py index 50b1181..e6dc33b 100644 --- a/expenses_manager/expenses/models.py +++ b/expenses_manager/expenses/models.py @@ -5,7 +5,7 @@ from django.conf import settings from django.db.models import Sum from functools import cached_property from django.utils.text import slugify -from django.db.models.functions import ExtractMonth +from django.db.models.functions import ExtractMonth, Lower class Category(models.Model): @@ -29,7 +29,7 @@ class Category(models.Model): class Meta: unique_together = ("name", "parent", "owner", "slug") verbose_name_plural = "categories" - ordering = ["name"] + ordering = [Lower("name")] def __str__(self): return self.name @@ -71,7 +71,7 @@ class Account(models.Model): created_at = models.DateTimeField(auto_now_add=True) class Meta: - ordering = ["name"] + ordering = [Lower("name")] def current_balance(self): expenses_total = self.expenses.aggregate(total=Sum("amount"))[ @@ -193,6 +193,7 @@ class Tag(models.Model): class Meta: unique_together = ("name", "owner") + ordering = [Lower("name")] def __str__(self): return self.name diff --git a/expenses_manager/expenses/tests/test_tags.py b/expenses_manager/expenses/tests/test_tags.py index f915286..2bcdd29 100644 --- a/expenses_manager/expenses/tests/test_tags.py +++ b/expenses_manager/expenses/tests/test_tags.py @@ -21,3 +21,12 @@ def test_tag_list_annotates_expense_usage_count(auth_client, user, account, cate counts = {tag.name: tag.expense_count for tag in response.context["tags"]} assert counts == {"Usada": 2, "Sin usar": 0} + + +def test_tag_ordering_ignores_case(user): + for name in ["ZZ", "AA", "mk"]: + Tag.objects.create(owner=user, name=name) + + names = list(Tag.objects.filter(owner=user).values_list("name", flat=True)) + + assert names == ["AA", "mk", "ZZ"] diff --git a/expenses_manager/expenses/views.py b/expenses_manager/expenses/views.py index 906a9a1..4f73831 100644 --- a/expenses_manager/expenses/views.py +++ b/expenses_manager/expenses/views.py @@ -16,7 +16,7 @@ from .forms import ( from django.core.paginator import Paginator from django.db.models import Sum, Count, ProtectedError -from django.db.models.functions import ExtractMonth, ExtractYear, ExtractDay +from django.db.models.functions import ExtractMonth, ExtractYear, ExtractDay, Lower from django.contrib.auth.decorators import login_required from django.utils.http import url_has_allowed_host_and_scheme @@ -64,7 +64,7 @@ def _category_tree(categories): for category in categories: by_parent.setdefault(category.parent_id, []).append(category) for children in by_parent.values(): - children.sort(key=lambda c: c.name) + children.sort(key=lambda c: c.name.lower()) rows = [] @@ -638,7 +638,7 @@ def tag_list(request): tags = ( Tag.objects.filter(owner=request.user) .annotate(expense_count=Count("expenses")) - .order_by("name") + .order_by(Lower("name")) ) return render( @@ -700,7 +700,7 @@ def tag_delete(request, pk): @login_required def account_list(request): - accounts = list(Account.objects.filter(owner=request.user).order_by("name")) + accounts = list(Account.objects.filter(owner=request.user).order_by(Lower("name"))) account_rows, _, _ = _account_balances(accounts) return render( request, @@ -1077,7 +1077,7 @@ def goal_list(request): else: selected_kind = "" - goals = goals.order_by("name") + goals = goals.order_by(Lower("name")) return render( request,