diff --git a/app/apps/accounts/migrations/0016_account_untracked_by.py b/app/apps/accounts/migrations/0016_account_untracked_by.py new file mode 100644 index 0000000..ca3b75a --- /dev/null +++ b/app/apps/accounts/migrations/0016_account_untracked_by.py @@ -0,0 +1,20 @@ +# Generated by Django 5.2.4 on 2025-08-09 05:52 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0015_alter_account_owner_alter_account_shared_with_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='account', + name='untracked_by', + field=models.ManyToManyField(blank=True, related_name='untracked_accounts', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/app/apps/accounts/models.py b/app/apps/accounts/models.py index e6efbc8..55c064c 100644 --- a/app/apps/accounts/models.py +++ b/app/apps/accounts/models.py @@ -1,11 +1,11 @@ from django.conf import settings from django.core.exceptions import ValidationError from django.db import models -from django.db.models import Q from django.utils.translation import gettext_lazy as _ -from apps.transactions.models import Transaction +from apps.common.middleware.thread_local import get_current_user from apps.common.models import SharedObject, SharedObjectManager +from apps.transactions.models import Transaction class AccountGroup(SharedObject): @@ -62,6 +62,11 @@ class Account(SharedObject): verbose_name=_("Archived"), help_text=_("Archived accounts don't show up nor count towards your net worth"), ) + untracked_by = models.ManyToManyField( + settings.AUTH_USER_MODEL, + blank=True, + related_name="untracked_accounts", + ) objects = SharedObjectManager() all_objects = models.Manager() # Unfiltered manager @@ -75,6 +80,10 @@ class Account(SharedObject): def __str__(self): return self.name + def is_untracked_by(self): + user = get_current_user() + return self.untracked_by.filter(pk=user.pk).exists() + def clean(self): super().clean() if self.exchange_currency == self.currency: diff --git a/app/apps/accounts/urls.py b/app/apps/accounts/urls.py index 699b830..ffe2781 100644 --- a/app/apps/accounts/urls.py +++ b/app/apps/accounts/urls.py @@ -31,6 +31,11 @@ urlpatterns = [ views.account_take_ownership, name="account_take_ownership", ), + path( + "account//toggle-untracked/", + views.account_toggle_untracked, + name="account_toggle_untracked", + ), path("account-groups/", views.account_groups_index, name="account_groups_index"), path("account-groups/list/", views.account_groups_list, name="account_groups_list"), path("account-groups/add/", views.account_group_add, name="account_group_add"), diff --git a/app/apps/accounts/views/accounts.py b/app/apps/accounts/views/accounts.py index 53b70ae..d45c746 100644 --- a/app/apps/accounts/views/accounts.py +++ b/app/apps/accounts/views/accounts.py @@ -155,6 +155,26 @@ def account_delete(request, pk): ) +@only_htmx +@login_required +@require_http_methods(["GET"]) +def account_toggle_untracked(request, pk): + account = get_object_or_404(Account, id=pk) + if account.is_untracked_by(): + account.untracked_by.remove(request.user) + messages.success(request, _("Account is now tracked")) + else: + account.untracked_by.add(request.user) + messages.success(request, _("Account is now untracked")) + + return HttpResponse( + status=204, + headers={ + "HX-Trigger": "updated", + }, + ) + + @only_htmx @login_required @require_http_methods(["GET"]) diff --git a/app/apps/insights/utils/transactions.py b/app/apps/insights/utils/transactions.py index 5b919d9..fbfbc0a 100644 --- a/app/apps/insights/utils/transactions.py +++ b/app/apps/insights/utils/transactions.py @@ -13,7 +13,9 @@ from apps.insights.forms import ( ) -def get_transactions(request, include_unpaid=True, include_silent=False): +def get_transactions( + request, include_unpaid=True, include_silent=False, include_untracked_accounts=False +): transactions = Transaction.objects.all() filter_type = request.GET.get("type", None) @@ -95,4 +97,9 @@ def get_transactions(request, include_unpaid=True, include_silent=False): Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True) ) + if not include_untracked_accounts: + transactions = transactions.exclude( + account__in=request.user.untracked_accounts.all() + ) + return transactions diff --git a/app/apps/insights/views.py b/app/apps/insights/views.py index 5ebbcfe..4788922 100644 --- a/app/apps/insights/views.py +++ b/app/apps/insights/views.py @@ -74,7 +74,7 @@ def index(request): def sankey_by_account(request): # Get filtered transactions - transactions = get_transactions(request) + transactions = get_transactions(request, include_untracked_accounts=True) # Generate Sankey data sankey_data = generate_sankey_data_by_account(transactions) @@ -239,10 +239,14 @@ def late_transactions(request): @login_required @require_http_methods(["GET"]) def emergency_fund(request): - transactions_currency_queryset = Transaction.objects.filter( - is_paid=True, account__is_archived=False, account__is_asset=False - ).order_by( - "account__currency__name", + transactions_currency_queryset = ( + Transaction.objects.filter( + is_paid=True, account__is_archived=False, account__is_asset=False + ) + .exclude(account__in=request.user.untracked_accounts.all()) + .order_by( + "account__currency__name", + ) ) currency_net_worth = calculate_currency_totals( transactions_queryset=transactions_currency_queryset, ignore_empty=False @@ -262,6 +266,7 @@ def emergency_fund(request): category__mute=False, mute=False, ) + .exclude(account__in=request.user.untracked_accounts.all()) .values("reference_date", "account__currency") .annotate(monthly_total=Sum("amount")) ) diff --git a/app/apps/monthly_overview/views.py b/app/apps/monthly_overview/views.py index 8624201..8ed59db 100644 --- a/app/apps/monthly_overview/views.py +++ b/app/apps/monthly_overview/views.py @@ -107,9 +107,15 @@ def transactions_list(request, month: int, year: int): @require_http_methods(["GET"]) def monthly_summary(request, month: int, year: int): # Base queryset with all required filters - base_queryset = Transaction.objects.filter( - reference_date__year=year, reference_date__month=month, account__is_asset=False - ).exclude(Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True)) + base_queryset = ( + Transaction.objects.filter( + reference_date__year=year, + reference_date__month=month, + account__is_asset=False, + ) + .exclude(Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True)) + .exclude(account__in=request.user.untracked_accounts.all()) + ) data = calculate_currency_totals(base_queryset, ignore_empty=True) percentages = calculate_percentage_distribution(data) @@ -165,10 +171,14 @@ def monthly_account_summary(request, month: int, year: int): @require_http_methods(["GET"]) def monthly_currency_summary(request, month: int, year: int): # Base queryset with all required filters - base_queryset = Transaction.objects.filter( - reference_date__year=year, - reference_date__month=month, - ).exclude(Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True)) + base_queryset = ( + Transaction.objects.filter( + reference_date__year=year, + reference_date__month=month, + ) + .exclude(Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True)) + .exclude(account__in=request.user.untracked_accounts.all()) + ) currency_data = calculate_currency_totals(base_queryset.all(), ignore_empty=True) currency_percentages = calculate_percentage_distribution(currency_data) diff --git a/app/apps/net_worth/views.py b/app/apps/net_worth/views.py index 07bbbfb..4e3362a 100644 --- a/app/apps/net_worth/views.py +++ b/app/apps/net_worth/views.py @@ -27,10 +27,12 @@ def net_worth(request): view_type = request.session.get("networth_view_type", "current") if view_type == "current": - transactions_currency_queryset = Transaction.objects.filter( - is_paid=True, account__is_archived=False - ).order_by( - "account__currency__name", + transactions_currency_queryset = ( + Transaction.objects.filter(is_paid=True, account__is_archived=False) + .order_by( + "account__currency__name", + ) + .exclude(account__in=request.user.untracked_accounts.all()) ) transactions_account_queryset = Transaction.objects.filter( is_paid=True, account__is_archived=False @@ -39,10 +41,12 @@ def net_worth(request): "account__name", ) else: - transactions_currency_queryset = Transaction.objects.filter( - account__is_archived=False - ).order_by( - "account__currency__name", + transactions_currency_queryset = ( + Transaction.objects.filter(account__is_archived=False) + .order_by( + "account__currency__name", + ) + .exclude(account__in=request.user.untracked_accounts.all()) ) transactions_account_queryset = Transaction.objects.filter( account__is_archived=False diff --git a/app/apps/transactions/views/transactions.py b/app/apps/transactions/views/transactions.py index 496e4ce..286b835 100644 --- a/app/apps/transactions/views/transactions.py +++ b/app/apps/transactions/views/transactions.py @@ -589,7 +589,10 @@ def transaction_all_currency_summary(request): f = TransactionsFilter(request.GET, queryset=transactions) - currency_data = calculate_currency_totals(f.qs.all(), ignore_empty=True) + currency_data = calculate_currency_totals( + f.qs.exclude(account__in=request.user.untracked_accounts.all()), + ignore_empty=True, + ) currency_percentages = calculate_percentage_distribution(currency_data) context = { diff --git a/app/apps/yearly_overview/views.py b/app/apps/yearly_overview/views.py index 881e9df..1444476 100644 --- a/app/apps/yearly_overview/views.py +++ b/app/apps/yearly_overview/views.py @@ -95,6 +95,7 @@ def yearly_overview_by_currency(request, year: int): transactions = ( Transaction.objects.filter(**filter_params) .exclude(Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True)) + .exclude(account__in=request.user.untracked_accounts.all()) .order_by("account__currency__name") ) diff --git a/app/templates/accounts/fragments/list.html b/app/templates/accounts/fragments/list.html index 207c761..f5ef5ed 100644 --- a/app/templates/accounts/fragments/list.html +++ b/app/templates/accounts/fragments/list.html @@ -71,6 +71,17 @@ hx-get="{% url 'account_share_settings' pk=account.id %}"> {% endif %} + + {% if account.is_untracked_by %} + + {% else %} + + {% endif %} + {{ account.name }} diff --git a/app/templates/cotton/transaction/item.html b/app/templates/cotton/transaction/item.html index e48d609..d182af4 100644 --- a/app/templates/cotton/transaction/item.html +++ b/app/templates/cotton/transaction/item.html @@ -33,7 +33,7 @@ {% endif %} -
+
{# Date#}
@@ -91,7 +91,7 @@ {% endwith %}
-