Compare commits

..
16 Commits
Author SHA1 Message Date
Herculino Trotta d500bef481 ci(translations): drop force push to protected main
Branch protection rejects force pushes to main (GH006), so the
translation commit never landed. The push is a fast-forward anyway.
2026-09-06 16:53:57 -03:00
eitchtee f280fcb172 chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2026-09-06 19:42:46 +00:00
Herculino Trotta 6c808eff38 Merge pull request #593 from eitchtee/dependabot/uv/djangorestframework-3.17.2
build(deps): bump djangorestframework from 3.17.1 to 3.17.2
2026-09-06 16:41:50 -03:00
Herculino Trotta 1690a153f9 Merge pull request #594 from eitchtee/dependabot/npm_and_yarn/frontend/browserslist-4.28.8
build(deps): bump browserslist from 4.28.2 to 4.28.8 in /frontend
2026-09-06 16:41:41 -03:00
Herculino Trotta 7116176c78 Merge pull request #595 from eitchtee/dependabot/uv/mistune-3.3.3
build(deps): bump mistune from 3.3.0 to 3.3.3
2026-09-06 16:41:15 -03:00
Herculino Trotta d9c9cbe7c3 Merge pull request #592 from hackking007/fix/owner-scoped-transaction-rule-endpoints
fix: BOLA on transaction-rule endpoints via get_owned_object_or_403
2026-09-06 16:41:00 -03:00
dependabot[bot] 11b03474c1 build(deps): bump mistune from 3.3.0 to 3.3.3
Bumps [mistune](https://github.com/lepture/mistune) from 3.3.0 to 3.3.3.
- [Release notes](https://github.com/lepture/mistune/releases)
- [Changelog](https://github.com/lepture/mistune/blob/main/docs/changes.rst)
- [Commits](https://github.com/lepture/mistune/compare/v3.3.0...v3.3.3)

---
updated-dependencies:
- dependency-name: mistune
  dependency-version: 3.3.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-02 16:45:07 +00:00
Herculino Trotta e2f26b3629 fix(sharing): enforce object-level authorization outside the rules app
The rules endpoints were one instance of a pattern repeated across every
SharedObject-backed app. Route the rest through the same helper.

DCA entries were the worst case and are strictly wider than the reported
rules bug: DCAEntry has an unscoped default manager, so filtering by
strategy__id alone reached entries on strategies the caller could not see
at all. No public or shared strategy was needed -- only a guessable
integer. strategy_entry_add/edit/delete now resolve through the parent
strategy with via="strategy".

Every SharedObject delete view carried an inverted condition:

    if obj.owner != request.user and request.user in obj.shared_with.all():
        obj.shared_with.remove(request.user)
    else:
        obj.delete()

An object its owner had made public matched neither branch's intent and
fell through to delete(), so any authenticated user could destroy it.
Confirmed reachable for accounts, account groups, categories, tags,
entities and DCA strategies. The owner now deletes, a shared user revokes
only their own access, and anyone else gets a 403.

The API viewsets had no object-level check at all. DjangoModelPermissions
gated them shut for ordinary users, who hold no model permissions, so this
was not reachable in a default install -- but the check belongs there
regardless, and a user granted change_account in the admin could write any
visible account. SharedObjectPermission adds it for the SharedObject
viewsets, leaving reads to SharedObjectManager.

account_toggle_untracked only flips the calling user's own row in the
untracked_by m2m, so it takes READ rather than EDIT.

Refs GHSA-83g9-vjqf-2j5q
2026-09-01 23:15:41 -03:00
dependabot[bot] 10c8fcfb97 build(deps): bump browserslist from 4.28.2 to 4.28.8 in /frontend
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.28.2 to 4.28.8.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.28.2...4.28.8)

---
updated-dependencies:
- dependency-name: browserslist
  dependency-version: 4.28.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-02 01:27:54 +00:00
Herculino Trotta 039ad225d3 test(rules): cover object-level authorization for transaction rules
Regression tests for GHSA-83g9-vjqf-2j5q, one per endpoint the advisory
named plus the delete and view paths found alongside them: a non-owner
gets 403 on every mutation of a public or shared rule, and the object is
asserted unchanged afterwards.

Also covers the parts that are easy to regress in the other direction:
shared users keep read access, a shared user deleting only revokes their
own access, unowned rules stay claimable, and children of invisible rules
answer 404 rather than 403.

SharedObjectPredicateParityTests asserts is_visible_to agrees with
SharedObjectManager across every owner/visibility/shared combination.
The manager builds a Q and the predicate tests an instance, so they
cannot share an implementation and can otherwise drift apart.
2026-09-01 21:43:13 -03:00
Herculino Trotta 18d4ab7d11 fix(rules): enforce object-level authorization on rule endpoints
SharedObjectManager scopes querysets to what a user may see, which
includes other people's public and shared-with-them objects. Several
mutating rule endpoints treated that visibility as permission to write.

Generalise get_owned_object_or_403 into get_shared_object_or_error, which
takes an explicit access level instead of inferring one:

- READ requires the object to be visible; denial is 404 so the response
  does not confirm that an id exists.
- EDIT requires ownership; denial is 403, but only after the visibility
  check, so 403 never leaks the existence of an invisible object. This
  matters for TransactionRuleAction, whose manager is unscoped.

The previous owner_path resolved to a User and discarded the object, so
it could not express visibility at all. via= now points at the governing
SharedObject and is resolved with a plain getattr, so an unresolvable
path raises instead of silently granting access.

is_visible_to/is_editable_by replace is_accessible_by, which was never
called and tested visibility == "shared", a value that does not exist in
Visibility.

Also fixes two further holes in the same module:

- transaction_rule_delete fell through to delete() whenever the caller
  was not in shared_with, so any user could delete a public rule. Now
  only the owner deletes; a shared user revokes their own access.
- transaction_rule_view was read-only but is now explicitly READ, so
  rules shared with a user stay viewable.

The activate/deactivate control is hidden for rules the user does not
own, instead of rendering a button that always fails.

Refs GHSA-83g9-vjqf-2j5q
2026-09-01 21:25:46 -03:00
dependabot[bot] c64a363126 build(deps): bump djangorestframework from 3.17.1 to 3.17.2
Bumps [djangorestframework](https://github.com/encode/django-rest-framework) from 3.17.1 to 3.17.2.
- [Release notes](https://github.com/encode/django-rest-framework/releases)
- [Commits](https://github.com/encode/django-rest-framework/compare/3.17.1...3.17.2)

---
updated-dependencies:
- dependency-name: djangorestframework
  dependency-version: 3.17.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-02 00:14:06 +00:00
Moshe Levi 68a9286ce5 Fix BOLA on transaction-rule endpoints via get_owned_object_or_403
Add apps.common.functions.get_owned_object_or_403, an ownership-enforcing
variant of get_object_or_404, and apply it across every rules handler that
resolved a TransactionRule / (UpdateOrCreate)TransactionRuleAction from a URL id
without an owner check. Mirrors the check in transaction_rule_edit
(obj.owner and obj.owner != request.user); objects with no owner remain
accessible, preserving existing behaviour. Nested ownership (actions owned via
their parent rule) is handled with owner_path='rule.owner'.
2026-08-31 20:14:40 +03:00
Herculino Trotta 1357688e7b Merge pull request #591 from eitchtee/dependabot/uv/sqlparse-0.6.0
build(deps): bump sqlparse from 0.5.5 to 0.6.0
2026-08-30 15:13:33 -03:00
Herculino Trotta ba1f421ad3 Merge pull request #590 from eitchtee/weblate
Translations update from Weblate
2026-08-30 15:13:24 -03:00
dependabot[bot] 7933f1c316 build(deps): bump sqlparse from 0.5.5 to 0.6.0
Bumps [sqlparse](https://github.com/andialbrecht/sqlparse) from 0.5.5 to 0.6.0.
- [Changelog](https://github.com/andialbrecht/sqlparse/blob/master/CHANGELOG)
- [Commits](https://github.com/andialbrecht/sqlparse/compare/0.5.5...0.6.0)

---
updated-dependencies:
- dependency-name: sqlparse
  dependency-version: 0.6.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 21:31:28 +00:00
40 changed files with 2566 additions and 1223 deletions
-1
View File
@@ -65,7 +65,6 @@ jobs:
if: steps.check_changes.outputs.changes_detected == 'true'
uses: stefanzweifel/git-auto-commit-action@v5
with:
push_options: --force
commit_message: |
chore(locale): update translation files
+18 -31
View File
@@ -1,13 +1,19 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from apps.accounts.forms import AccountGroupForm
from apps.accounts.models import AccountGroup
from apps.common.decorators.htmx import only_htmx
from apps.common.functions.permissions import (
EDIT,
READ,
get_shared_object_or_error,
)
from apps.common.models import SharedObject
from apps.common.forms import SharedObjectForm
@@ -63,17 +69,7 @@ def account_group_add(request, **kwargs):
@login_required
@require_http_methods(["GET", "POST"])
def account_group_edit(request, pk):
account_group = get_object_or_404(AccountGroup, id=pk)
if account_group.owner and account_group.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
account_group = get_shared_object_or_error(AccountGroup, request, id=pk, level=EDIT)
if request.method == "POST":
form = AccountGroupForm(request.POST, instance=account_group)
@@ -101,17 +97,18 @@ def account_group_edit(request, pk):
@login_required
@require_http_methods(["DELETE"])
def account_group_delete(request, pk):
account_group = get_object_or_404(AccountGroup, id=pk)
account_group = get_shared_object_or_error(AccountGroup, request, id=pk, level=READ)
if (
account_group.owner != request.user
and request.user in account_group.shared_with.all()
):
if account_group.is_editable_by(request.user):
account_group.delete()
messages.success(request, _("Account Group deleted successfully"))
elif account_group.shared_with.filter(pk=request.user.pk).exists():
# Someone else's object shared with us: we can drop our own access
# to it, but never delete it.
account_group.shared_with.remove(request.user)
messages.success(request, _("Item no longer shared with you"))
else:
account_group.delete()
messages.success(request, _("Account Group deleted successfully"))
raise PermissionDenied
return HttpResponse(
status=204,
@@ -125,7 +122,7 @@ def account_group_delete(request, pk):
@login_required
@require_http_methods(["GET"])
def account_group_take_ownership(request, pk):
account_group = get_object_or_404(AccountGroup, id=pk)
account_group = get_shared_object_or_error(AccountGroup, request, id=pk, level=EDIT)
if not account_group.owner:
account_group.owner = request.user
@@ -146,17 +143,7 @@ def account_group_take_ownership(request, pk):
@login_required
@require_http_methods(["GET", "POST"])
def account_group_share(request, pk):
obj = get_object_or_404(AccountGroup, id=pk)
if obj.owner and obj.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
obj = get_shared_object_or_error(AccountGroup, request, id=pk, level=EDIT)
if request.method == "POST":
form = SharedObjectForm(request.POST, instance=obj, user=request.user)
+21 -28
View File
@@ -1,13 +1,19 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from apps.accounts.forms import AccountForm
from apps.accounts.models import Account
from apps.common.decorators.htmx import only_htmx
from apps.common.functions.permissions import (
EDIT,
READ,
get_shared_object_or_error,
)
from apps.common.models import SharedObject
from apps.common.forms import SharedObjectForm
@@ -63,16 +69,7 @@ def account_add(request, **kwargs):
@login_required
@require_http_methods(["GET", "POST"])
def account_edit(request, pk):
account = get_object_or_404(Account, id=pk)
if account.owner and account.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
account = get_shared_object_or_error(Account, request, id=pk, level=EDIT)
if request.method == "POST":
form = AccountForm(request.POST, instance=account)
@@ -100,17 +97,7 @@ def account_edit(request, pk):
@login_required
@require_http_methods(["GET", "POST"])
def account_share(request, pk):
obj = get_object_or_404(Account, id=pk)
if obj.owner and obj.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
obj = get_shared_object_or_error(Account, request, id=pk, level=EDIT)
if request.method == "POST":
form = SharedObjectForm(request.POST, instance=obj, user=request.user)
@@ -138,14 +125,18 @@ def account_share(request, pk):
@login_required
@require_http_methods(["DELETE"])
def account_delete(request, pk):
account = get_object_or_404(Account, id=pk)
account = get_shared_object_or_error(Account, request, id=pk, level=READ)
if account.owner != request.user and request.user in account.shared_with.all():
if account.is_editable_by(request.user):
account.delete()
messages.success(request, _("Account deleted successfully"))
elif account.shared_with.filter(pk=request.user.pk).exists():
# Someone else's object shared with us: we can drop our own access
# to it, but never delete it.
account.shared_with.remove(request.user)
messages.success(request, _("Item no longer shared with you"))
else:
account.delete()
messages.success(request, _("Account deleted successfully"))
raise PermissionDenied
return HttpResponse(
status=204,
@@ -159,7 +150,9 @@ def account_delete(request, pk):
@login_required
@require_http_methods(["GET"])
def account_toggle_untracked(request, pk):
account = get_object_or_404(Account, id=pk)
# Only flips the calling user's own row in untracked_by, so visibility --
# not ownership -- is the right bar here.
account = get_shared_object_or_error(Account, request, id=pk, level=READ)
if account.is_untracked_by():
account.untracked_by.remove(request.user)
messages.success(request, _("Account is now tracked"))
@@ -179,7 +172,7 @@ def account_toggle_untracked(request, pk):
@login_required
@require_http_methods(["GET"])
def account_take_ownership(request, pk):
account = get_object_or_404(Account, id=pk)
account = get_shared_object_or_error(Account, request, id=pk, level=EDIT)
if not account.owner:
account.owner = request.user
+39 -1
View File
@@ -1,4 +1,8 @@
from rest_framework.permissions import BasePermission
from rest_framework.permissions import (
SAFE_METHODS,
BasePermission,
DjangoModelPermissions,
)
from django.conf import settings
@@ -8,3 +12,37 @@ class NotInDemoMode(BasePermission):
return False
else:
return True
class SharedObjectPermission(BasePermission):
"""Object-level ownership check for SharedObject-backed viewsets.
DjangoModelPermissions is model-level: a user holding ``change_account``
may write any object the viewset's queryset returns, and for SharedObject
that queryset includes other people's public and shared-with-them objects.
Sharing grants read access only, so writes are restricted to the owner
here as well.
Set ``shared_object_via`` on the viewset when the governing SharedObject is
reached through a relation (e.g. ``"strategy"`` for a DCA entry).
"""
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
guard = obj
via = getattr(view, "shared_object_via", None)
for attr in via.split(".") if via else []:
guard = getattr(guard, attr)
return guard.is_editable_by(request.user)
#: Default permissions plus the object-level ownership check. Assigning
#: ``permission_classes`` replaces the defaults, so they are repeated here.
SHARED_OBJECT_PERMISSIONS = [
NotInDemoMode,
DjangoModelPermissions,
SharedObjectPermission,
]
+1
View File
@@ -3,3 +3,4 @@ from .test_imports import *
from .test_accounts import *
from .test_data_isolation import *
from .test_shared_access import *
from .test_object_permissions import *
@@ -0,0 +1,171 @@
"""Object-level ownership on the SharedObject API viewsets.
DjangoModelPermissions is model-level: a user holding ``change_account`` could
write any object the viewset's queryset returned, and for SharedObject that
queryset includes other people's public and shared-with-them objects. Ordinary
users hold no model permissions, so this was not reachable for them, but the
object-level check was missing entirely.
Reads are unaffected -- they stay governed by SharedObjectManager.
"""
from datetime import date
from decimal import Decimal
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.test import TestCase, override_settings
from rest_framework import status
from rest_framework.test import APIClient
from apps.accounts.models import Account
from apps.currencies.models import Currency
from apps.dca.models import DCAEntry, DCAStrategy
from apps.transactions.models import TransactionCategory
@override_settings(
STORAGES={
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"
},
},
WHITENOISE_AUTOREFRESH=True,
DEMO=False,
)
class SharedObjectAPIPermissionTests(TestCase):
"""The attacker here deliberately HOLDS the Django model permissions.
Without them DjangoModelPermissions already answers 403 and the
object-level check is never consulted, so the test would pass whether or
not it exists.
"""
def setUp(self):
User = get_user_model()
self.owner = User.objects.create_user(
email="owner@test.com", password="testpass123"
)
self.attacker = User.objects.create_user(
email="attacker@test.com", password="testpass123"
)
self.attacker.user_permissions.set(
Permission.objects.filter(
codename__in=[
"add_account",
"change_account",
"delete_account",
"add_transactioncategory",
"change_transactioncategory",
"delete_transactioncategory",
"add_dcaentry",
"change_dcaentry",
"delete_dcaentry",
]
)
)
# Permissions are cached on the user instance.
self.attacker = User.objects.get(pk=self.attacker.pk)
self.currency = Currency.objects.create(
code="USD", name="US Dollar", decimal_places=2
)
self.api = APIClient()
self.api.force_authenticate(user=self.attacker)
def test_cannot_modify_public_account(self):
account = Account.all_objects.create(
name="Public account",
currency=self.currency,
owner=self.owner,
visibility="public",
)
response = self.api.patch(
f"/api/accounts/{account.id}/", {"name": "HIJACKED"}, format="json"
)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
account.refresh_from_db()
self.assertEqual(account.name, "Public account")
def test_cannot_delete_account_shared_with_them(self):
account = Account.all_objects.create(
name="Shared account",
currency=self.currency,
owner=self.owner,
visibility="private",
)
account.shared_with.add(self.attacker)
response = self.api.delete(f"/api/accounts/{account.id}/")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertTrue(Account.all_objects.filter(pk=account.pk).exists())
def test_cannot_delete_public_category(self):
category = TransactionCategory.all_objects.create(
name="Public category", owner=self.owner, visibility="public"
)
response = self.api.delete(f"/api/categories/{category.id}/")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertTrue(TransactionCategory.all_objects.filter(pk=category.pk).exists())
def test_cannot_delete_entry_on_public_strategy(self):
strategy = DCAStrategy.all_objects.create(
name="Public strategy",
owner=self.owner,
visibility="public",
target_currency=self.currency,
payment_currency=self.currency,
)
entry = DCAEntry.objects.create(
strategy=strategy,
date=date(2025, 1, 1),
amount_paid=Decimal("100"),
amount_received=Decimal("1"),
)
response = self.api.delete(f"/api/dca/entries/{entry.id}/")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertTrue(DCAEntry.objects.filter(pk=entry.pk).exists())
def test_reads_of_shared_objects_still_work(self):
account = Account.all_objects.create(
name="Public account",
currency=self.currency,
owner=self.owner,
visibility="public",
)
response = self.api.get(f"/api/accounts/{account.id}/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_owner_can_still_write_their_own_objects(self):
owner_client = APIClient()
self.owner.user_permissions.set(
Permission.objects.filter(codename__in=["change_account", "delete_account"])
)
owner = get_user_model().objects.get(pk=self.owner.pk)
owner_client.force_authenticate(user=owner)
account = Account.all_objects.create(
name="Own account",
currency=self.currency,
owner=owner,
visibility="private",
)
response = owner_client.patch(
f"/api/accounts/{account.id}/", {"name": "Renamed"}, format="json"
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
account.refresh_from_db()
self.assertEqual(account.name, "Renamed")
+3
View File
@@ -6,6 +6,7 @@ from rest_framework.response import Response
from apps.accounts.models import AccountGroup, Account
from apps.accounts.services import get_account_balance
from apps.api.permissions import SHARED_OBJECT_PERMISSIONS
from apps.api.serializers import (
AccountGroupSerializer,
AccountSerializer,
@@ -16,6 +17,7 @@ from apps.api.serializers import (
class AccountGroupViewSet(viewsets.ModelViewSet):
"""ViewSet for managing account groups."""
permission_classes = SHARED_OBJECT_PERMISSIONS
queryset = AccountGroup.objects.all()
serializer_class = AccountGroupSerializer
filterset_fields = {
@@ -40,6 +42,7 @@ class AccountGroupViewSet(viewsets.ModelViewSet):
class AccountViewSet(viewsets.ModelViewSet):
"""ViewSet for managing accounts."""
permission_classes = SHARED_OBJECT_PERMISSIONS
queryset = Account.objects.all()
serializer_class = AccountSerializer
filterset_fields = {
+4
View File
@@ -2,10 +2,12 @@ from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from apps.dca.models import DCAStrategy, DCAEntry
from apps.api.permissions import SHARED_OBJECT_PERMISSIONS
from apps.api.serializers import DCAStrategySerializer, DCAEntrySerializer
class DCAStrategyViewSet(viewsets.ModelViewSet):
permission_classes = SHARED_OBJECT_PERMISSIONS
queryset = DCAStrategy.objects.all()
serializer_class = DCAStrategySerializer
filterset_fields = {
@@ -43,6 +45,8 @@ class DCAStrategyViewSet(viewsets.ModelViewSet):
class DCAEntryViewSet(viewsets.ModelViewSet):
permission_classes = SHARED_OBJECT_PERMISSIONS
shared_object_via = "strategy"
queryset = DCAEntry.objects.all()
serializer_class = DCAEntrySerializer
filterset_fields = {
+4
View File
@@ -19,6 +19,7 @@ from apps.transactions.models import (
RecurringTransaction,
)
from apps.rules.signals import transaction_updated, transaction_created
from apps.api.permissions import SHARED_OBJECT_PERMISSIONS
class TransactionViewSet(viewsets.ModelViewSet):
@@ -68,6 +69,7 @@ class TransactionViewSet(viewsets.ModelViewSet):
class TransactionCategoryViewSet(viewsets.ModelViewSet):
permission_classes = SHARED_OBJECT_PERMISSIONS
queryset = TransactionCategory.objects.all()
serializer_class = TransactionCategorySerializer
filterset_fields = {
@@ -85,6 +87,7 @@ class TransactionCategoryViewSet(viewsets.ModelViewSet):
class TransactionTagViewSet(viewsets.ModelViewSet):
permission_classes = SHARED_OBJECT_PERMISSIONS
queryset = TransactionTag.objects.all()
serializer_class = TransactionTagSerializer
filterset_fields = {
@@ -101,6 +104,7 @@ class TransactionTagViewSet(viewsets.ModelViewSet):
class TransactionEntityViewSet(viewsets.ModelViewSet):
permission_classes = SHARED_OBJECT_PERMISSIONS
queryset = TransactionEntity.objects.all()
serializer_class = TransactionEntitySerializer
filterset_fields = {
+58
View File
@@ -0,0 +1,58 @@
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.shortcuts import get_object_or_404
READ = "read"
EDIT = "edit"
def get_shared_object_or_error(klass, request, *, level=EDIT, via=None, **kwargs):
"""Fetch an object like ``get_object_or_404`` while enforcing access control.
``SharedObjectManager`` scopes querysets to what a user may *see*, which is
not the same as what they may *change*. Views that resolve an object from a
URL id must state which of the two they need, otherwise a shared or public
object becomes writable by anyone who can see it.
``level`` selects the check applied to the governing ``SharedObject``:
``READ``
The object must be visible to the user. Denial raises :class:`Http404`
so the response does not confirm that the id exists.
``EDIT``
The object must be owned by the user. Denial raises
:class:`~django.core.exceptions.PermissionDenied` (HTTP 403), which the
frontend surfaces as an "Access Denied" dialog. An object the user
cannot even see raises :class:`Http404` instead, so 403 never confirms
the existence of an object they were not allowed to know about.
Objects with no owner stay accessible to everyone, preserving the existing
behaviour for legacy/unowned objects.
``via`` is a dotted path to the ``SharedObject`` that governs access, for
models owned through a relation, e.g. a rule action governed by its parent
rule::
get_shared_object_or_error(
TransactionRuleAction, request, id=pk, level=EDIT, via="rule"
)
The path is resolved with a plain ``getattr``, so a path that does not
resolve raises ``AttributeError`` rather than silently granting access.
"""
obj = get_object_or_404(klass, **kwargs)
guard = obj
for attr in via.split(".") if via else []:
guard = getattr(guard, attr)
if level not in (READ, EDIT):
raise ValueError(f"Unknown access level: {level!r}")
if not guard.is_visible_to(request.user):
raise Http404
if level == EDIT and not guard.is_editable_by(request.user):
raise PermissionDenied
return obj
+29 -7
View File
@@ -58,13 +58,35 @@ class SharedObject(models.Model):
models.Index(fields=["visibility"]),
]
def is_accessible_by(self, user):
"""Check if a user can access this object"""
return (
self.visibility == "public"
or self.owner == user
or (self.visibility == "shared" and user in self.shared_with.all())
)
# NOTE: these two predicates must stay in sync with the ``Q`` objects built
# by ``SharedObjectManager.get_queryset`` above. The manager filters at the
# queryset level and these check a single instance, so they cannot share an
# implementation; ``SharedObjectPredicateParityTests`` asserts they agree.
def is_visible_to(self, user):
"""Whether ``user`` may read this object.
Mirrors ``SharedObjectManager``: public objects, objects with no owner,
the owner's own objects, and objects explicitly shared with the user.
"""
if self.owner is None or self.visibility == "public":
return True
if not user or not user.is_authenticated:
return False
return self.owner_id == user.pk or self.shared_with.filter(pk=user.pk).exists()
def is_editable_by(self, user):
"""Whether ``user`` may mutate this object.
Sharing grants read access only; mutation stays with the owner. Objects
with no owner remain editable by everyone, preserving the behaviour of
legacy/unowned objects.
"""
if self.owner is None:
return True
return bool(user and user.is_authenticated and self.owner_id == user.pk)
def save(self, *args, **kwargs):
if not self.pk and not self.owner:
+269
View File
@@ -0,0 +1,269 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.test import RequestFactory, TestCase, override_settings
from apps.common.functions.permissions import (
EDIT,
READ,
get_shared_object_or_error,
)
from apps.common.middleware.thread_local import delete_current_user, write_current_user
from apps.rules.models import TransactionRule, TransactionRuleAction
@override_settings(
STORAGES={
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"
},
},
WHITENOISE_AUTOREFRESH=True,
)
class SharedObjectPredicateTests(TestCase):
"""Unit tests for is_visible_to / is_editable_by on SharedObject."""
def setUp(self):
User = get_user_model()
self.owner = User.objects.create_user(
email="owner@test.com", password="testpass123"
)
self.shared_user = User.objects.create_user(
email="shared@test.com", password="testpass123"
)
self.stranger = User.objects.create_user(
email="stranger@test.com", password="testpass123"
)
def _rule(self, **kwargs):
kwargs.setdefault("name", "Rule")
kwargs.setdefault("trigger", "True")
return TransactionRule.all_objects.create(**kwargs)
def test_owner_can_read_and_edit_own_private_rule(self):
rule = self._rule(owner=self.owner, visibility="private")
self.assertTrue(rule.is_visible_to(self.owner))
self.assertTrue(rule.is_editable_by(self.owner))
def test_private_rule_is_invisible_to_stranger(self):
rule = self._rule(owner=self.owner, visibility="private")
self.assertFalse(rule.is_visible_to(self.stranger))
self.assertFalse(rule.is_editable_by(self.stranger))
def test_shared_rule_is_readable_but_not_editable(self):
rule = self._rule(owner=self.owner, visibility="private")
rule.shared_with.add(self.shared_user)
self.assertTrue(rule.is_visible_to(self.shared_user))
self.assertFalse(rule.is_editable_by(self.shared_user))
def test_public_rule_is_readable_but_not_editable(self):
rule = self._rule(owner=self.owner, visibility="public")
self.assertTrue(rule.is_visible_to(self.stranger))
self.assertFalse(rule.is_editable_by(self.stranger))
def test_unowned_rule_stays_readable_and_editable_by_everyone(self):
rule = self._rule(owner=None, visibility="private")
self.assertTrue(rule.is_visible_to(self.stranger))
self.assertTrue(rule.is_editable_by(self.stranger))
def test_anonymous_user_gets_no_access_to_owned_rules(self):
rule = self._rule(owner=self.owner, visibility="private")
self.assertFalse(rule.is_visible_to(AnonymousUser()))
self.assertFalse(rule.is_editable_by(AnonymousUser()))
@override_settings(
STORAGES={
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"
},
},
WHITENOISE_AUTOREFRESH=True,
)
class SharedObjectPredicateParityTests(TestCase):
"""is_visible_to must agree with what SharedObjectManager returns.
The manager filters at the queryset level and the predicate checks a single
instance, so the two cannot share an implementation. This asserts they do
not drift apart.
"""
def setUp(self):
User = get_user_model()
self.owner = User.objects.create_user(
email="owner@test.com", password="testpass123"
)
self.other = User.objects.create_user(
email="other@test.com", password="testpass123"
)
self.addCleanup(self._clear_current_user)
def _clear_current_user(self):
try:
delete_current_user()
except AttributeError:
pass
def test_manager_and_predicate_agree_over_every_combination(self):
combinations = []
for owner in (self.owner, self.other, None):
for visibility in ("private", "public"):
for shared in (True, False):
rule = TransactionRule.all_objects.create(
name=f"{owner}-{visibility}-{shared}",
trigger="True",
owner=owner,
visibility=visibility,
)
if shared:
rule.shared_with.add(self.owner)
combinations.append(rule)
write_current_user(self.owner)
visible_ids = set(TransactionRule.objects.values_list("id", flat=True))
for rule in combinations:
with self.subTest(rule=rule.name):
self.assertEqual(
rule.id in visible_ids,
rule.is_visible_to(self.owner),
f"manager and is_visible_to disagree for {rule.name}",
)
@override_settings(
STORAGES={
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"
},
},
WHITENOISE_AUTOREFRESH=True,
)
class GetSharedObjectOrErrorTests(TestCase):
def setUp(self):
User = get_user_model()
self.owner = User.objects.create_user(
email="owner@test.com", password="testpass123"
)
self.stranger = User.objects.create_user(
email="stranger@test.com", password="testpass123"
)
self.factory = RequestFactory()
self.public_rule = TransactionRule.all_objects.create(
name="Public", trigger="True", owner=self.owner, visibility="public"
)
self.private_rule = TransactionRule.all_objects.create(
name="Private", trigger="True", owner=self.owner, visibility="private"
)
self.public_action = TransactionRuleAction.objects.create(
rule=self.public_rule, field="notes", value="x"
)
self.private_action = TransactionRuleAction.objects.create(
rule=self.private_rule, field="notes", value="x"
)
self.addCleanup(self._clear_current_user)
def _clear_current_user(self):
try:
delete_current_user()
except AttributeError:
pass
def _request(self, user):
request = self.factory.get("/")
request.user = user
write_current_user(user)
return request
def test_read_allows_visible_object(self):
request = self._request(self.stranger)
rule = get_shared_object_or_error(
TransactionRule, request, id=self.public_rule.id, level=READ
)
self.assertEqual(rule, self.public_rule)
def test_edit_denies_visible_but_unowned_object_with_403(self):
request = self._request(self.stranger)
with self.assertRaises(PermissionDenied):
get_shared_object_or_error(
TransactionRule, request, id=self.public_rule.id, level=EDIT
)
def test_edit_allows_owner(self):
request = self._request(self.owner)
rule = get_shared_object_or_error(
TransactionRule, request, id=self.public_rule.id, level=EDIT
)
self.assertEqual(rule, self.public_rule)
def test_invisible_object_raises_404_not_403(self):
"""403 must not confirm the existence of an object the user cannot see."""
request = self._request(self.stranger)
with self.assertRaises(Http404):
get_shared_object_or_error(
TransactionRule, request, id=self.private_rule.id, level=EDIT
)
def test_via_traverses_to_the_governing_object(self):
request = self._request(self.stranger)
with self.assertRaises(PermissionDenied):
get_shared_object_or_error(
TransactionRuleAction,
request,
id=self.public_action.id,
level=EDIT,
via="rule",
)
def test_via_hides_children_of_invisible_parents_behind_404(self):
"""TransactionRuleAction has an unscoped manager, so the id is reachable."""
request = self._request(self.stranger)
with self.assertRaises(Http404):
get_shared_object_or_error(
TransactionRuleAction,
request,
id=self.private_action.id,
level=EDIT,
via="rule",
)
def test_unresolvable_via_path_fails_closed(self):
"""A typo'd path must raise, never silently grant access."""
request = self._request(self.stranger)
with self.assertRaises(AttributeError):
get_shared_object_or_error(
TransactionRuleAction,
request,
id=self.public_action.id,
level=EDIT,
via="rulee",
)
def test_unknown_level_is_rejected(self):
request = self._request(self.owner)
with self.assertRaises(ValueError):
get_shared_object_or_error(
TransactionRule, request, id=self.public_rule.id, level="write"
)
@@ -0,0 +1,161 @@
"""Delete semantics for every SharedObject-backed model.
Each of these views carried the same inverted condition::
if obj.owner != request.user and request.user in obj.shared_with.all():
obj.shared_with.remove(request.user) # unshare
else:
obj.delete() # <- public objects landed here
so an object its owner had made public could be destroyed by any authenticated
user. The rule is: the owner deletes, a shared user revokes only their own
access, and nobody else may do either.
"""
from django.contrib.auth import get_user_model
from django.test import TestCase, override_settings
from django.urls import reverse
from apps.accounts.models import Account, AccountGroup
from apps.currencies.models import Currency
from apps.dca.models import DCAStrategy
from apps.rules.models import TransactionRule
from apps.transactions.models import (
TransactionCategory,
TransactionEntity,
TransactionTag,
)
HTMX = {"HTTP_HX_REQUEST": "true"}
@override_settings(
STORAGES={
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"
},
},
WHITENOISE_AUTOREFRESH=True,
DEMO=False,
)
class SharedObjectDeletionTests(TestCase):
def setUp(self):
User = get_user_model()
self.owner = User.objects.create_user(
email="owner@test.com", password="testpass123"
)
self.shared_user = User.objects.create_user(
email="shared@test.com", password="testpass123"
)
self.stranger = User.objects.create_user(
email="stranger@test.com", password="testpass123"
)
self.currency = Currency.objects.create(
code="USD", name="US Dollar", decimal_places=2
)
def cases(self):
"""(label, model, delete url name, url kwarg, extra create kwargs)."""
return [
(
"account",
Account,
"account_delete",
"pk",
{"currency": self.currency},
),
("account group", AccountGroup, "account_group_delete", "pk", {}),
(
"category",
TransactionCategory,
"category_delete",
"category_id",
{},
),
("tag", TransactionTag, "tag_delete", "tag_id", {}),
("entity", TransactionEntity, "entity_delete", "entity_id", {}),
(
"rule",
TransactionRule,
"transaction_rule_delete",
"transaction_rule_id",
{"trigger": "True"},
),
(
"dca strategy",
DCAStrategy,
"dca_strategy_delete",
"strategy_id",
{
"target_currency": self.currency,
"payment_currency": self.currency,
},
),
]
def make(self, model, visibility, extra):
return model.all_objects.create(
name="Target", owner=self.owner, visibility=visibility, **extra
)
def delete(self, url_name, kwarg, obj):
return self.client.delete(reverse(url_name, kwargs={kwarg: obj.id}), **HTMX)
def test_stranger_cannot_delete_a_public_object(self):
for label, model, url_name, kwarg, extra in self.cases():
with self.subTest(model=label):
obj = self.make(model, "public", extra)
self.client.force_login(self.stranger)
response = self.delete(url_name, kwarg, obj)
self.assertEqual(response.status_code, 403, label)
self.assertTrue(
model.all_objects.filter(pk=obj.pk).exists(),
f"{label} was deleted by a non-owner",
)
def test_shared_user_deleting_only_revokes_their_own_access(self):
for label, model, url_name, kwarg, extra in self.cases():
with self.subTest(model=label):
obj = self.make(model, "private", extra)
obj.shared_with.add(self.shared_user)
self.client.force_login(self.shared_user)
response = self.delete(url_name, kwarg, obj)
self.assertEqual(response.status_code, 204, label)
self.assertTrue(
model.all_objects.filter(pk=obj.pk).exists(),
f"{label} was deleted by a shared user",
)
self.assertNotIn(self.shared_user, obj.shared_with.all())
def test_owner_can_delete(self):
for label, model, url_name, kwarg, extra in self.cases():
with self.subTest(model=label):
obj = self.make(model, "private", extra)
self.client.force_login(self.owner)
response = self.delete(url_name, kwarg, obj)
self.assertEqual(response.status_code, 204, label)
self.assertFalse(
model.all_objects.filter(pk=obj.pk).exists(),
f"{label} was not deleted by its owner",
)
def test_unowned_objects_stay_deletable(self):
"""Legacy objects with no owner are editable by everyone by design."""
for label, model, url_name, kwarg, extra in self.cases():
with self.subTest(model=label):
obj = model.all_objects.create(
name="Legacy", owner=None, visibility="private", **extra
)
self.client.force_login(self.stranger)
response = self.delete(url_name, kwarg, obj)
self.assertEqual(response.status_code, 204, label)
self.assertFalse(model.all_objects.filter(pk=obj.pk).exists(), label)
-3
View File
@@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.
View File
+248
View File
@@ -0,0 +1,248 @@
"""Object-level authorization tests for the DCA views.
DCAEntry has an unscoped default manager, so filtering an entry by
``strategy__id`` alone reached entries belonging to strategies the caller could
not see at all -- a strictly wider hole than the one reported for rules in
GHSA-83g9-vjqf-2j5q, since it needs no public or shared strategy.
"""
from datetime import date
from decimal import Decimal
from django.contrib.auth import get_user_model
from django.test import TestCase, override_settings
from django.urls import reverse
from apps.currencies.models import Currency
from apps.dca.models import DCAEntry, DCAStrategy
HTMX = {"HTTP_HX_REQUEST": "true"}
@override_settings(
STORAGES={
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"
},
},
WHITENOISE_AUTOREFRESH=True,
DEMO=False,
)
class DCAObjectPermissionTests(TestCase):
def setUp(self):
User = get_user_model()
self.owner = User.objects.create_user(
email="owner@test.com", password="testpass123"
)
self.shared_user = User.objects.create_user(
email="shared@test.com", password="testpass123"
)
self.stranger = User.objects.create_user(
email="stranger@test.com", password="testpass123"
)
self.currency = Currency.objects.create(
code="USD", name="US Dollar", decimal_places=2
)
self.private_strategy = self._strategy("Private", visibility="private")
self.public_strategy = self._strategy("Public", visibility="public")
self.shared_strategy = self._strategy("Shared", visibility="private")
self.shared_strategy.shared_with.add(self.shared_user)
self.private_entry = self._entry(self.private_strategy)
self.public_entry = self._entry(self.public_strategy)
self.shared_entry = self._entry(self.shared_strategy)
def _strategy(self, name, visibility):
return DCAStrategy.all_objects.create(
name=name,
owner=self.owner,
visibility=visibility,
target_currency=self.currency,
payment_currency=self.currency,
)
def _entry(self, strategy):
return DCAEntry.objects.create(
strategy=strategy,
date=date(2025, 1, 1),
amount_paid=Decimal("100"),
amount_received=Decimal("1"),
)
# ------------------------------------------------------------------
# entries on an invisible strategy: must not even confirm they exist
# ------------------------------------------------------------------
def test_stranger_cannot_delete_entry_on_private_strategy(self):
self.client.force_login(self.stranger)
response = self.client.delete(
reverse(
"dca_entry_delete",
kwargs={
"strategy_id": self.private_strategy.id,
"entry_id": self.private_entry.id,
},
),
**HTMX,
)
self.assertEqual(response.status_code, 404)
self.assertTrue(DCAEntry.objects.filter(pk=self.private_entry.pk).exists())
def test_stranger_cannot_open_entry_edit_form_on_private_strategy(self):
self.client.force_login(self.stranger)
response = self.client.get(
reverse(
"dca_entry_edit",
kwargs={
"strategy_id": self.private_strategy.id,
"entry_id": self.private_entry.id,
},
),
**HTMX,
)
self.assertEqual(response.status_code, 404)
def test_stranger_cannot_edit_entry_on_private_strategy(self):
self.client.force_login(self.stranger)
response = self.client.post(
reverse(
"dca_entry_edit",
kwargs={
"strategy_id": self.private_strategy.id,
"entry_id": self.private_entry.id,
},
),
data={
"date": "2030-01-01",
"amount_paid": "999",
"amount_received": "999",
},
**HTMX,
)
self.assertEqual(response.status_code, 404)
self.private_entry.refresh_from_db()
self.assertEqual(self.private_entry.amount_paid, Decimal("100"))
# ------------------------------------------------------------------
# entries on a visible-but-unowned strategy: 403, not 404
# ------------------------------------------------------------------
def test_stranger_cannot_delete_entry_on_public_strategy(self):
self.client.force_login(self.stranger)
response = self.client.delete(
reverse(
"dca_entry_delete",
kwargs={
"strategy_id": self.public_strategy.id,
"entry_id": self.public_entry.id,
},
),
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.assertTrue(DCAEntry.objects.filter(pk=self.public_entry.pk).exists())
def test_shared_user_cannot_add_entry_to_shared_strategy(self):
self.client.force_login(self.shared_user)
response = self.client.post(
reverse("dca_entry_add", kwargs={"strategy_id": self.shared_strategy.id}),
data={
"date": "2030-01-01",
"amount_paid": "5",
"amount_received": "5",
},
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.assertEqual(self.shared_strategy.entries.count(), 1)
def test_owner_can_still_manage_own_entries(self):
self.client.force_login(self.owner)
response = self.client.delete(
reverse(
"dca_entry_delete",
kwargs={
"strategy_id": self.private_strategy.id,
"entry_id": self.private_entry.id,
},
),
**HTMX,
)
self.assertEqual(response.status_code, 204)
self.assertFalse(DCAEntry.objects.filter(pk=self.private_entry.pk).exists())
# ------------------------------------------------------------------
# reads stay open to shared users
# ------------------------------------------------------------------
def test_shared_user_can_still_view_strategy_detail(self):
self.client.force_login(self.shared_user)
response = self.client.get(
reverse(
"dca_strategy_detail", kwargs={"strategy_id": self.shared_strategy.id}
),
**HTMX,
)
self.assertEqual(response.status_code, 200)
# ------------------------------------------------------------------
# strategy delete: the same inversion the rules views had
# ------------------------------------------------------------------
def test_stranger_cannot_delete_public_strategy(self):
self.client.force_login(self.stranger)
response = self.client.delete(
reverse(
"dca_strategy_delete", kwargs={"strategy_id": self.public_strategy.id}
),
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.assertTrue(
DCAStrategy.all_objects.filter(pk=self.public_strategy.pk).exists()
)
def test_shared_user_deleting_only_revokes_their_own_access(self):
self.client.force_login(self.shared_user)
response = self.client.delete(
reverse(
"dca_strategy_delete", kwargs={"strategy_id": self.shared_strategy.id}
),
**HTMX,
)
self.assertEqual(response.status_code, 204)
self.assertTrue(
DCAStrategy.all_objects.filter(pk=self.shared_strategy.pk).exists()
)
self.assertNotIn(self.shared_user, self.shared_strategy.shared_with.all())
def test_owner_can_delete_own_strategy(self):
self.client.force_login(self.owner)
response = self.client.delete(
reverse(
"dca_strategy_delete", kwargs={"strategy_id": self.public_strategy.id}
),
**HTMX,
)
self.assertEqual(response.status_code, 204)
self.assertFalse(
DCAStrategy.all_objects.filter(pk=self.public_strategy.pk).exists()
)
+49 -36
View File
@@ -1,13 +1,19 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.db.models import Sum, Avg
from django.db.models.functions import TruncMonth
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from apps.common.decorators.htmx import only_htmx
from apps.common.functions.permissions import (
EDIT,
READ,
get_shared_object_or_error,
)
from apps.dca.forms import DCAEntryForm, DCAStrategyForm
from apps.dca.models import DCAStrategy, DCAEntry
from apps.common.models import SharedObject
@@ -56,17 +62,9 @@ def strategy_add(request):
@only_htmx
@login_required
def strategy_edit(request, strategy_id):
dca_strategy = get_object_or_404(DCAStrategy, id=strategy_id)
if dca_strategy.owner and dca_strategy.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
dca_strategy = get_shared_object_or_error(
DCAStrategy, request, id=strategy_id, level=EDIT
)
if request.method == "POST":
form = DCAStrategyForm(request.POST, instance=dca_strategy)
@@ -94,17 +92,20 @@ def strategy_edit(request, strategy_id):
@login_required
@require_http_methods(["DELETE"])
def strategy_delete(request, strategy_id):
dca_strategy = get_object_or_404(DCAStrategy, id=strategy_id)
dca_strategy = get_shared_object_or_error(
DCAStrategy, request, id=strategy_id, level=READ
)
if (
dca_strategy.owner != request.user
and request.user in dca_strategy.shared_with.all()
):
if dca_strategy.is_editable_by(request.user):
dca_strategy.delete()
messages.success(request, _("DCA strategy deleted successfully"))
elif dca_strategy.shared_with.filter(pk=request.user.pk).exists():
# Someone else's object shared with us: we can drop our own access
# to it, but never delete it.
dca_strategy.shared_with.remove(request.user)
messages.success(request, _("Item no longer shared with you"))
else:
dca_strategy.delete()
messages.success(request, _("DCA strategy deleted successfully"))
raise PermissionDenied
return HttpResponse(
status=204,
@@ -118,7 +119,9 @@ def strategy_delete(request, strategy_id):
@login_required
@require_http_methods(["GET"])
def strategy_take_ownership(request, strategy_id):
dca_strategy = get_object_or_404(DCAStrategy, id=strategy_id)
dca_strategy = get_shared_object_or_error(
DCAStrategy, request, id=strategy_id, level=EDIT
)
if not dca_strategy.owner:
dca_strategy.owner = request.user
@@ -139,17 +142,7 @@ def strategy_take_ownership(request, strategy_id):
@login_required
@require_http_methods(["GET", "POST"])
def strategy_share(request, pk):
obj = get_object_or_404(DCAStrategy, id=pk)
if obj.owner and obj.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
obj = get_shared_object_or_error(DCAStrategy, request, id=pk, level=EDIT)
if request.method == "POST":
form = SharedObjectForm(request.POST, instance=obj, user=request.user)
@@ -175,7 +168,9 @@ def strategy_share(request, pk):
@login_required
def strategy_detail_index(request, strategy_id):
strategy = get_object_or_404(DCAStrategy, id=strategy_id)
strategy = get_shared_object_or_error(
DCAStrategy, request, id=strategy_id, level=READ
)
return render(
request,
@@ -187,7 +182,9 @@ def strategy_detail_index(request, strategy_id):
@only_htmx
@login_required
def strategy_detail(request, strategy_id):
strategy = get_object_or_404(DCAStrategy, id=strategy_id)
strategy = get_shared_object_or_error(
DCAStrategy, request, id=strategy_id, level=READ
)
entries = strategy.entries.all()
# Calculate monthly aggregates
@@ -229,7 +226,9 @@ def strategy_detail(request, strategy_id):
@only_htmx
@login_required
def strategy_entry_add(request, strategy_id):
strategy = get_object_or_404(DCAStrategy, id=strategy_id)
strategy = get_shared_object_or_error(
DCAStrategy, request, id=strategy_id, level=EDIT
)
if request.method == "POST":
form = DCAEntryForm(request.POST, strategy=strategy)
if form.is_valid():
@@ -255,7 +254,14 @@ def strategy_entry_add(request, strategy_id):
@only_htmx
@login_required
def strategy_entry_edit(request, strategy_id, entry_id):
dca_entry = get_object_or_404(DCAEntry, id=entry_id, strategy__id=strategy_id)
dca_entry = get_shared_object_or_error(
DCAEntry,
request,
id=entry_id,
strategy__id=strategy_id,
level=EDIT,
via="strategy",
)
if request.method == "POST":
form = DCAEntryForm(request.POST, instance=dca_entry)
@@ -283,7 +289,14 @@ def strategy_entry_edit(request, strategy_id, entry_id):
@login_required
@require_http_methods(["DELETE"])
def strategy_entry_delete(request, entry_id, strategy_id):
dca_entry = get_object_or_404(DCAEntry, id=entry_id, strategy__id=strategy_id)
dca_entry = get_shared_object_or_error(
DCAEntry,
request,
id=entry_id,
strategy__id=strategy_id,
level=EDIT,
via="strategy",
)
dca_entry.delete()
@@ -0,0 +1,473 @@
"""Object-level authorization tests for the transaction-rule endpoints.
Regression coverage for GHSA-83g9-vjqf-2j5q: SharedObjectManager scopes rules to
what a user may *see*, which included other people's public and shared-with-them
rules. Several mutating endpoints treated that visibility as permission to write.
"""
from django.contrib.auth import get_user_model
from django.test import TestCase, override_settings
from django.urls import reverse
from apps.rules.models import (
TransactionRule,
TransactionRuleAction,
UpdateOrCreateTransactionRuleAction,
)
HTMX = {"HTTP_HX_REQUEST": "true"}
@override_settings(
STORAGES={
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"
},
},
WHITENOISE_AUTOREFRESH=True,
DEMO=False,
)
class TransactionRuleObjectPermissionTests(TestCase):
def setUp(self):
User = get_user_model()
self.owner = User.objects.create_user(
email="owner@test.com", password="testpass123"
)
self.shared_user = User.objects.create_user(
email="shared@test.com", password="testpass123"
)
self.stranger = User.objects.create_user(
email="stranger@test.com", password="testpass123"
)
# Public: visible to everyone through SharedObjectManager.
self.public_rule = TransactionRule.all_objects.create(
name="Public rule",
trigger="True",
owner=self.owner,
visibility="public",
active=True,
)
# Private but shared with shared_user: visible to them, not to stranger.
self.shared_rule = TransactionRule.all_objects.create(
name="Shared rule",
trigger="True",
owner=self.owner,
visibility="private",
active=True,
)
self.shared_rule.shared_with.add(self.shared_user)
# Private and unshared: invisible to everyone but the owner.
self.private_rule = TransactionRule.all_objects.create(
name="Private rule",
trigger="True",
owner=self.owner,
visibility="private",
active=True,
)
self.public_action = TransactionRuleAction.objects.create(
rule=self.public_rule, field="notes", value="owned by owner"
)
self.private_action = TransactionRuleAction.objects.create(
rule=self.private_rule, field="notes", value="owned by owner"
)
self.public_linked_action = UpdateOrCreateTransactionRuleAction.objects.create(
rule=self.public_rule
)
self.private_linked_action = UpdateOrCreateTransactionRuleAction.objects.create(
rule=self.private_rule
)
def login(self, user):
self.client.force_login(user)
# ------------------------------------------------------------------
# toggle-active
# ------------------------------------------------------------------
def test_stranger_cannot_toggle_public_rule(self):
self.login(self.stranger)
response = self.client.get(
reverse(
"transaction_rule_toggle_activity",
kwargs={"transaction_rule_id": self.public_rule.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.public_rule.refresh_from_db()
self.assertTrue(self.public_rule.active)
def test_shared_user_cannot_toggle_shared_rule(self):
self.login(self.shared_user)
response = self.client.get(
reverse(
"transaction_rule_toggle_activity",
kwargs={"transaction_rule_id": self.shared_rule.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.shared_rule.refresh_from_db()
self.assertTrue(self.shared_rule.active)
def test_owner_can_toggle_own_rule(self):
self.login(self.owner)
response = self.client.get(
reverse(
"transaction_rule_toggle_activity",
kwargs={"transaction_rule_id": self.public_rule.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 204)
self.public_rule.refresh_from_db()
self.assertFalse(self.public_rule.active)
# ------------------------------------------------------------------
# rule actions
# ------------------------------------------------------------------
def test_stranger_cannot_add_action_to_public_rule(self):
self.login(self.stranger)
response = self.client.post(
reverse(
"transaction_rule_action_add",
kwargs={"transaction_rule_id": self.public_rule.id},
),
data={"field": "notes", "value": "injected", "order": 0},
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.assertFalse(
TransactionRuleAction.objects.filter(value="injected").exists()
)
def test_stranger_cannot_edit_action_on_public_rule(self):
self.login(self.stranger)
response = self.client.post(
reverse(
"transaction_rule_action_edit",
kwargs={"transaction_rule_action_id": self.public_action.id},
),
data={"field": "notes", "value": "rewritten", "order": 0},
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.public_action.refresh_from_db()
self.assertEqual(self.public_action.value, "owned by owner")
def test_stranger_cannot_delete_action_on_public_rule(self):
self.login(self.stranger)
response = self.client.delete(
reverse(
"transaction_rule_action_delete",
kwargs={"transaction_rule_action_id": self.public_action.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.assertTrue(
TransactionRuleAction.objects.filter(pk=self.public_action.pk).exists()
)
def test_stranger_cannot_delete_action_on_invisible_rule(self):
"""The child manager is unscoped, so the id is reachable by guessing.
The parent rule is invisible to the stranger, so the response must be a
404 rather than a 403 that confirms the action exists.
"""
self.login(self.stranger)
response = self.client.delete(
reverse(
"transaction_rule_action_delete",
kwargs={"transaction_rule_action_id": self.private_action.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 404)
self.assertTrue(
TransactionRuleAction.objects.filter(pk=self.private_action.pk).exists()
)
def test_owner_can_delete_own_action(self):
self.login(self.owner)
response = self.client.delete(
reverse(
"transaction_rule_action_delete",
kwargs={"transaction_rule_action_id": self.public_action.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 204)
self.assertFalse(
TransactionRuleAction.objects.filter(pk=self.public_action.pk).exists()
)
# ------------------------------------------------------------------
# update-or-create rule actions
# ------------------------------------------------------------------
def test_stranger_cannot_add_linked_action_to_public_rule(self):
self.login(self.stranger)
response = self.client.post(
reverse(
"update_or_create_transaction_rule_action_add",
kwargs={"transaction_rule_id": self.public_rule.id},
),
data={},
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.assertEqual(
UpdateOrCreateTransactionRuleAction.objects.filter(
rule=self.public_rule
).count(),
1,
)
def test_stranger_cannot_edit_linked_action_on_public_rule(self):
self.login(self.stranger)
response = self.client.post(
reverse(
"update_or_create_transaction_rule_action_edit",
kwargs={"pk": self.public_linked_action.id},
),
data={"filter": "injected"},
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.public_linked_action.refresh_from_db()
self.assertEqual(self.public_linked_action.filter, "")
def test_stranger_cannot_delete_linked_action_on_public_rule(self):
self.login(self.stranger)
response = self.client.delete(
reverse(
"update_or_create_transaction_rule_action_delete",
kwargs={"pk": self.public_linked_action.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.assertTrue(
UpdateOrCreateTransactionRuleAction.objects.filter(
pk=self.public_linked_action.pk
).exists()
)
def test_stranger_cannot_delete_linked_action_on_invisible_rule(self):
self.login(self.stranger)
response = self.client.delete(
reverse(
"update_or_create_transaction_rule_action_delete",
kwargs={"pk": self.private_linked_action.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 404)
self.assertTrue(
UpdateOrCreateTransactionRuleAction.objects.filter(
pk=self.private_linked_action.pk
).exists()
)
# ------------------------------------------------------------------
# edit / share / dry-run
# ------------------------------------------------------------------
def test_stranger_cannot_edit_public_rule(self):
self.login(self.stranger)
response = self.client.post(
reverse(
"transaction_rule_edit",
kwargs={"transaction_rule_id": self.public_rule.id},
),
data={"name": "hijacked", "trigger": "True", "order": 0},
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.public_rule.refresh_from_db()
self.assertEqual(self.public_rule.name, "Public rule")
def test_stranger_cannot_change_sharing_of_public_rule(self):
self.login(self.stranger)
response = self.client.post(
reverse(
"transaction_rule_share_settings", kwargs={"pk": self.public_rule.id}
),
data={"visibility": "private"},
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.public_rule.refresh_from_db()
self.assertEqual(self.public_rule.visibility, "public")
def test_stranger_cannot_dry_run_public_rule(self):
self.login(self.stranger)
response = self.client.get(
reverse(
"transaction_rule_dry_run_created", kwargs={"pk": self.public_rule.id}
),
**HTMX,
)
self.assertEqual(response.status_code, 403)
# ------------------------------------------------------------------
# delete: sharing may be revoked, ownership may not be overridden
# ------------------------------------------------------------------
def test_stranger_cannot_delete_public_rule(self):
"""The original condition fell through to delete() for public rules."""
self.login(self.stranger)
response = self.client.delete(
reverse(
"transaction_rule_delete",
kwargs={"transaction_rule_id": self.public_rule.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.assertTrue(
TransactionRule.all_objects.filter(pk=self.public_rule.pk).exists()
)
def test_shared_user_deleting_only_revokes_their_own_access(self):
self.login(self.shared_user)
response = self.client.delete(
reverse(
"transaction_rule_delete",
kwargs={"transaction_rule_id": self.shared_rule.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 204)
self.assertTrue(
TransactionRule.all_objects.filter(pk=self.shared_rule.pk).exists()
)
self.assertNotIn(self.shared_user, self.shared_rule.shared_with.all())
def test_owner_can_delete_own_rule(self):
self.login(self.owner)
response = self.client.delete(
reverse(
"transaction_rule_delete",
kwargs={"transaction_rule_id": self.public_rule.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 204)
self.assertFalse(
TransactionRule.all_objects.filter(pk=self.public_rule.pk).exists()
)
# ------------------------------------------------------------------
# reads must keep working for shared users
# ------------------------------------------------------------------
def test_shared_user_can_still_view_shared_rule(self):
self.login(self.shared_user)
response = self.client.get(
reverse(
"transaction_rule_view",
kwargs={"transaction_rule_id": self.shared_rule.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 200)
def test_stranger_can_view_public_rule(self):
self.login(self.stranger)
response = self.client.get(
reverse(
"transaction_rule_view",
kwargs={"transaction_rule_id": self.public_rule.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 200)
def test_stranger_cannot_view_private_rule(self):
self.login(self.stranger)
response = self.client.get(
reverse(
"transaction_rule_view",
kwargs={"transaction_rule_id": self.private_rule.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 404)
# ------------------------------------------------------------------
# take ownership stays available for unowned rules only
# ------------------------------------------------------------------
def test_take_ownership_of_unowned_rule_still_works(self):
unowned = TransactionRule.all_objects.create(
name="Legacy rule", trigger="True", owner=None, visibility="private"
)
self.login(self.stranger)
response = self.client.get(
reverse(
"transaction_rule_take_ownership",
kwargs={"transaction_rule_id": unowned.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 204)
unowned.refresh_from_db()
self.assertEqual(unowned.owner, self.stranger)
def test_cannot_take_ownership_of_someone_elses_public_rule(self):
self.login(self.stranger)
response = self.client.get(
reverse(
"transaction_rule_take_ownership",
kwargs={"transaction_rule_id": self.public_rule.id},
),
**HTMX,
)
self.assertEqual(response.status_code, 403)
self.public_rule.refresh_from_db()
self.assertEqual(self.public_rule.owner, self.owner)
+58 -47
View File
@@ -4,13 +4,19 @@ from copy import deepcopy
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.shortcuts import render, redirect
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from apps.common.decorators.htmx import only_htmx
from apps.common.functions.permissions import (
EDIT,
READ,
get_shared_object_or_error,
)
from apps.rules.forms import (
TransactionRuleForm,
TransactionRuleActionForm,
@@ -62,7 +68,9 @@ def rules_list(request):
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def transaction_rule_toggle_activity(request, transaction_rule_id, **kwargs):
transaction_rule = get_object_or_404(TransactionRule, id=transaction_rule_id)
transaction_rule = get_shared_object_or_error(
TransactionRule, request, id=transaction_rule_id, level=EDIT
)
current_active = transaction_rule.active
transaction_rule.active = not current_active
transaction_rule.save(update_fields=["active"])
@@ -112,17 +120,9 @@ def transaction_rule_add(request, **kwargs):
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def transaction_rule_edit(request, transaction_rule_id):
transaction_rule = get_object_or_404(TransactionRule, id=transaction_rule_id)
if transaction_rule.owner and transaction_rule.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
transaction_rule = get_shared_object_or_error(
TransactionRule, request, id=transaction_rule_id, level=EDIT
)
if request.method == "POST":
form = TransactionRuleForm(request.POST, instance=transaction_rule)
@@ -151,7 +151,9 @@ def transaction_rule_edit(request, transaction_rule_id):
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def transaction_rule_view(request, transaction_rule_id):
transaction_rule = get_object_or_404(TransactionRule, id=transaction_rule_id)
transaction_rule = get_shared_object_or_error(
TransactionRule, request, id=transaction_rule_id, level=READ
)
edit_actions = transaction_rule.transaction_actions.all()
update_or_create_actions = (
@@ -175,17 +177,20 @@ def transaction_rule_view(request, transaction_rule_id):
@disabled_on_demo
@require_http_methods(["DELETE"])
def transaction_rule_delete(request, transaction_rule_id):
transaction_rule = get_object_or_404(TransactionRule, id=transaction_rule_id)
transaction_rule = get_shared_object_or_error(
TransactionRule, request, id=transaction_rule_id, level=READ
)
if (
transaction_rule.owner != request.user
and request.user in transaction_rule.shared_with.all()
):
if transaction_rule.is_editable_by(request.user):
transaction_rule.delete()
messages.success(request, _("Rule deleted successfully"))
elif transaction_rule.shared_with.filter(pk=request.user.pk).exists():
# Someone else's rule shared with us: we can drop our own access to it,
# but never delete it.
transaction_rule.shared_with.remove(request.user)
messages.success(request, _("Item no longer shared with you"))
else:
transaction_rule.delete()
messages.success(request, _("Rule deleted successfully"))
raise PermissionDenied
return HttpResponse(
status=204,
@@ -200,7 +205,9 @@ def transaction_rule_delete(request, transaction_rule_id):
@disabled_on_demo
@require_http_methods(["GET"])
def transaction_rule_take_ownership(request, transaction_rule_id):
transaction_rule = get_object_or_404(TransactionRule, id=transaction_rule_id)
transaction_rule = get_shared_object_or_error(
TransactionRule, request, id=transaction_rule_id, level=EDIT
)
if not transaction_rule.owner:
transaction_rule.owner = request.user
@@ -222,17 +229,7 @@ def transaction_rule_take_ownership(request, transaction_rule_id):
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def transaction_rule_share(request, pk):
obj = get_object_or_404(TransactionRule, id=pk)
if obj.owner and obj.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
obj = get_shared_object_or_error(TransactionRule, request, id=pk, level=EDIT)
if request.method == "POST":
form = SharedObjectForm(request.POST, instance=obj, user=request.user)
@@ -261,7 +258,9 @@ def transaction_rule_share(request, pk):
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def transaction_rule_action_add(request, transaction_rule_id):
transaction_rule = get_object_or_404(TransactionRule, id=transaction_rule_id)
transaction_rule = get_shared_object_or_error(
TransactionRule, request, id=transaction_rule_id, level=EDIT
)
if request.method == "POST":
form = TransactionRuleActionForm(request.POST, rule=transaction_rule)
@@ -289,12 +288,14 @@ def transaction_rule_action_add(request, transaction_rule_id):
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def transaction_rule_action_edit(request, transaction_rule_action_id):
transaction_rule_action = get_object_or_404(
TransactionRuleAction, id=transaction_rule_action_id
)
transaction_rule = get_object_or_404(
TransactionRule, id=transaction_rule_action.rule.id
transaction_rule_action = get_shared_object_or_error(
TransactionRuleAction,
request,
id=transaction_rule_action_id,
level=EDIT,
via="rule",
)
transaction_rule = transaction_rule_action.rule
if request.method == "POST":
form = TransactionRuleActionForm(
@@ -327,8 +328,12 @@ def transaction_rule_action_edit(request, transaction_rule_action_id):
@disabled_on_demo
@require_http_methods(["DELETE"])
def transaction_rule_action_delete(request, transaction_rule_action_id):
transaction_rule_action = get_object_or_404(
TransactionRuleAction, id=transaction_rule_action_id
transaction_rule_action = get_shared_object_or_error(
TransactionRuleAction,
request,
id=transaction_rule_action_id,
level=EDIT,
via="rule",
)
transaction_rule_action.delete()
@@ -348,7 +353,9 @@ def transaction_rule_action_delete(request, transaction_rule_action_id):
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def update_or_create_transaction_rule_action_add(request, transaction_rule_id):
transaction_rule = get_object_or_404(TransactionRule, id=transaction_rule_id)
transaction_rule = get_shared_object_or_error(
TransactionRule, request, id=transaction_rule_id, level=EDIT
)
if request.method == "POST":
form = UpdateOrCreateTransactionRuleActionForm(
@@ -380,7 +387,9 @@ def update_or_create_transaction_rule_action_add(request, transaction_rule_id):
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def update_or_create_transaction_rule_action_edit(request, pk):
linked_action = get_object_or_404(UpdateOrCreateTransactionRuleAction, id=pk)
linked_action = get_shared_object_or_error(
UpdateOrCreateTransactionRuleAction, request, id=pk, level=EDIT, via="rule"
)
transaction_rule = linked_action.rule
if request.method == "POST":
@@ -415,7 +424,9 @@ def update_or_create_transaction_rule_action_edit(request, pk):
@disabled_on_demo
@require_http_methods(["DELETE"])
def update_or_create_transaction_rule_action_delete(request, pk):
linked_action = get_object_or_404(UpdateOrCreateTransactionRuleAction, id=pk)
linked_action = get_shared_object_or_error(
UpdateOrCreateTransactionRuleAction, request, id=pk, level=EDIT, via="rule"
)
linked_action.delete()
@@ -436,7 +447,7 @@ def update_or_create_transaction_rule_action_delete(request, pk):
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def dry_run_rule_created(request, pk):
rule = get_object_or_404(TransactionRule, id=pk)
rule = get_shared_object_or_error(TransactionRule, request, id=pk, level=EDIT)
logs = None
results = None
@@ -481,7 +492,7 @@ def dry_run_rule_created(request, pk):
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def dry_run_rule_deleted(request, pk):
rule = get_object_or_404(TransactionRule, id=pk)
rule = get_shared_object_or_error(TransactionRule, request, id=pk, level=EDIT)
logs = None
results = None
@@ -526,7 +537,7 @@ def dry_run_rule_deleted(request, pk):
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def dry_run_rule_updated(request, pk):
rule = get_object_or_404(TransactionRule, id=pk)
rule = get_shared_object_or_error(TransactionRule, request, id=pk, level=EDIT)
logs = None
results = None
+24 -28
View File
@@ -1,11 +1,17 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from apps.common.decorators.htmx import only_htmx
from apps.common.functions.permissions import (
EDIT,
READ,
get_shared_object_or_error,
)
from apps.transactions.forms import TransactionCategoryForm
from apps.transactions.models import TransactionCategory
from apps.common.models import SharedObject
@@ -85,17 +91,9 @@ def category_add(request, **kwargs):
@login_required
@require_http_methods(["GET", "POST"])
def category_edit(request, category_id):
category = get_object_or_404(TransactionCategory, id=category_id)
if category.owner and category.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
category = get_shared_object_or_error(
TransactionCategory, request, id=category_id, level=EDIT
)
if request.method == "POST":
form = TransactionCategoryForm(request.POST, instance=category)
@@ -123,17 +121,7 @@ def category_edit(request, category_id):
@login_required
@require_http_methods(["GET", "POST"])
def category_share(request, pk):
obj = get_object_or_404(TransactionCategory, id=pk)
if obj.owner and obj.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
obj = get_shared_object_or_error(TransactionCategory, request, id=pk, level=EDIT)
if request.method == "POST":
form = SharedObjectForm(request.POST, instance=obj, user=request.user)
@@ -161,14 +149,20 @@ def category_share(request, pk):
@login_required
@require_http_methods(["DELETE"])
def category_delete(request, category_id):
category = get_object_or_404(TransactionCategory, id=category_id)
category = get_shared_object_or_error(
TransactionCategory, request, id=category_id, level=READ
)
if category.owner != request.user and request.user in category.shared_with.all():
if category.is_editable_by(request.user):
category.delete()
messages.success(request, _("Category deleted successfully"))
elif category.shared_with.filter(pk=request.user.pk).exists():
# Someone else's object shared with us: we can drop our own access
# to it, but never delete it.
category.shared_with.remove(request.user)
messages.success(request, _("Item no longer shared with you"))
else:
category.delete()
messages.success(request, _("Category deleted successfully"))
raise PermissionDenied
return HttpResponse(
status=204,
@@ -182,7 +176,9 @@ def category_delete(request, category_id):
@login_required
@require_http_methods(["GET"])
def category_take_ownership(request, category_id):
category = get_object_or_404(TransactionCategory, id=category_id)
category = get_shared_object_or_error(
TransactionCategory, request, id=category_id, level=EDIT
)
if not category.owner:
category.owner = request.user
+24 -28
View File
@@ -1,11 +1,17 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from apps.common.decorators.htmx import only_htmx
from apps.common.functions.permissions import (
EDIT,
READ,
get_shared_object_or_error,
)
from apps.transactions.forms import TransactionEntityForm
from apps.transactions.models import TransactionEntity
from apps.common.models import SharedObject
@@ -85,17 +91,9 @@ def entity_add(request, **kwargs):
@login_required
@require_http_methods(["GET", "POST"])
def entity_edit(request, entity_id):
entity = get_object_or_404(TransactionEntity, id=entity_id)
if entity.owner and entity.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
entity = get_shared_object_or_error(
TransactionEntity, request, id=entity_id, level=EDIT
)
if request.method == "POST":
form = TransactionEntityForm(request.POST, instance=entity)
@@ -123,14 +121,20 @@ def entity_edit(request, entity_id):
@login_required
@require_http_methods(["DELETE"])
def entity_delete(request, entity_id):
entity = get_object_or_404(TransactionEntity, id=entity_id)
entity = get_shared_object_or_error(
TransactionEntity, request, id=entity_id, level=READ
)
if entity.owner != request.user and request.user in entity.shared_with.all():
if entity.is_editable_by(request.user):
entity.delete()
messages.success(request, _("Entity deleted successfully"))
elif entity.shared_with.filter(pk=request.user.pk).exists():
# Someone else's object shared with us: we can drop our own access
# to it, but never delete it.
entity.shared_with.remove(request.user)
messages.success(request, _("Item no longer shared with you"))
else:
entity.delete()
messages.success(request, _("Entity deleted successfully"))
raise PermissionDenied
return HttpResponse(
status=204,
@@ -144,7 +148,9 @@ def entity_delete(request, entity_id):
@login_required
@require_http_methods(["GET"])
def entity_take_ownership(request, entity_id):
entity = get_object_or_404(TransactionEntity, id=entity_id)
entity = get_shared_object_or_error(
TransactionEntity, request, id=entity_id, level=EDIT
)
if not entity.owner:
entity.owner = request.user
@@ -165,17 +171,7 @@ def entity_take_ownership(request, entity_id):
@login_required
@require_http_methods(["GET", "POST"])
def entity_share(request, pk):
obj = get_object_or_404(TransactionEntity, id=pk)
if obj.owner and obj.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
obj = get_shared_object_or_error(TransactionEntity, request, id=pk, level=EDIT)
if request.method == "POST":
form = SharedObjectForm(request.POST, instance=obj, user=request.user)
+18 -28
View File
@@ -1,11 +1,17 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from apps.common.decorators.htmx import only_htmx
from apps.common.functions.permissions import (
EDIT,
READ,
get_shared_object_or_error,
)
from apps.transactions.forms import TransactionTagForm
from apps.transactions.models import TransactionTag
from apps.common.models import SharedObject
@@ -85,17 +91,7 @@ def tag_add(request, **kwargs):
@login_required
@require_http_methods(["GET", "POST"])
def tag_edit(request, tag_id):
tag = get_object_or_404(TransactionTag, id=tag_id)
if tag.owner and tag.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
tag = get_shared_object_or_error(TransactionTag, request, id=tag_id, level=EDIT)
if request.method == "POST":
form = TransactionTagForm(request.POST, instance=tag)
@@ -123,14 +119,18 @@ def tag_edit(request, tag_id):
@login_required
@require_http_methods(["DELETE"])
def tag_delete(request, tag_id):
tag = get_object_or_404(TransactionTag, id=tag_id)
tag = get_shared_object_or_error(TransactionTag, request, id=tag_id, level=READ)
if tag.owner != request.user and request.user in tag.shared_with.all():
if tag.is_editable_by(request.user):
tag.delete()
messages.success(request, _("Tag deleted successfully"))
elif tag.shared_with.filter(pk=request.user.pk).exists():
# Someone else's object shared with us: we can drop our own access
# to it, but never delete it.
tag.shared_with.remove(request.user)
messages.success(request, _("Item no longer shared with you"))
else:
tag.delete()
messages.success(request, _("Tag deleted successfully"))
raise PermissionDenied
return HttpResponse(
status=204,
@@ -144,7 +144,7 @@ def tag_delete(request, tag_id):
@login_required
@require_http_methods(["GET"])
def tag_take_ownership(request, tag_id):
tag = get_object_or_404(TransactionTag, id=tag_id)
tag = get_shared_object_or_error(TransactionTag, request, id=tag_id, level=EDIT)
if not tag.owner:
tag.owner = request.user
@@ -165,17 +165,7 @@ def tag_take_ownership(request, tag_id):
@login_required
@require_http_methods(["GET", "POST"])
def tag_share(request, pk):
obj = get_object_or_404(TransactionTag, id=pk)
if obj.owner and obj.owner != request.user:
messages.error(request, _("Only the owner can edit this"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
obj = get_shared_object_or_error(TransactionTag, request, id=pk, level=EDIT)
if request.method == "POST":
form = SharedObjectForm(request.POST, instance=obj, user=request.user)
+57 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: 2026-08-10 12:57+0000\n"
"Last-Translator: GitEbu <ebu@buol.ch>\n"
"Language-Team: German <https://translations.herculino.com/projects/wygiwyh/"
@@ -195,68 +195,56 @@ msgstr ""
"Die Umrechnungs-Währung darf nicht mit der Haupt-Währung des Kontos "
"übereinstimmen."
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr "Kontengruppe erfolgreich hinzugefügt"
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr "Nur der Besitzer kann dies bearbeiten"
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr "Kontengruppe erfolgreich aktualisiert"
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr "Einheit wird nicht länger mit dir geteilt"
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr "Kontengruppe erfolgreich gelöscht"
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr "Einheit wird nicht länger mit dir geteilt"
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr "Besitzeigenschaft erfolgreich übernommen"
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr "Konfiguration erfolgreich gespeichert"
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr "Konto erfolgreich hinzugefügt"
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr "Konto erfolgreich aktualisiert"
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr "Konto erfolgreich gelöscht"
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr "Konto wird verfolgt"
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr "Konto wird nicht mehr gefolgt"
@@ -804,27 +792,27 @@ msgstr "DCA-Eintrag"
msgid "DCA Entries"
msgstr "DCA-Einträge"
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr "DCA-Strategie erfolgreich hinzugefügt"
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr "DCA-Strategie erfolgreich aktualisiert"
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr "DCA-Strategie erfolgreich gelöscht"
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr "Eintrag erfolgreich hinzugefügt"
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr "Eintrag erfolgreich aktualisiert"
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr "Eintrag erfolgreich gelöscht"
@@ -1301,15 +1289,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr "Transaktions-Aktion aktualisieren oder erstellen"
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr "Regel erfolgreich deaktiviert"
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr "Regel erfolgreich aktiviert"
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr "Regel erfolgreich hinzugefügt"
@@ -1317,31 +1305,31 @@ msgstr "Regel erfolgreich hinzugefügt"
msgid "Rule updated successfully"
msgstr "Regel erfolgreich aktualisiert"
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr "Regel erfolgreich gelöscht"
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr "Aktion erfolgreich aktualisiert"
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr "Aktion erfolgreich gelöscht"
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr ""
"\"Transaktions-Aktualisierung oder -Erstellung\"-Aktion erfolgreich "
"hinzugefügt"
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr ""
"\"Transaktions-Aktualisierung oder -Erstellung\"-Aktion erfolgreich "
"aktualisiert"
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr ""
"\"Transaktions-Aktualisierung oder -Erstellung\"-Aktion erfolgreich gelöscht"
@@ -1773,27 +1761,27 @@ msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] "%(count)s Transaktion erfolgreich dupliziert"
msgstr[1] "%(count)s Transaktionen erfolgreich dupliziert"
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr "Kategorie erfolgreich hinzugefügt"
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr "Kategorie erfolgreich aktualisiert"
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr "Kategorie erfolgreich gelöscht"
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr "Entität erfolgreich hinzugefügt"
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr "Entität erfolgreich aktualisiert"
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr "Entität erfolgreich gelöscht"
@@ -1856,15 +1844,15 @@ msgstr "Wiederkehrende Transaktion erfolgreich abgeschlossen"
msgid "Recurring Transaction deleted successfully"
msgstr "Wiederkehrende Transaktion erfolgreich gelöscht"
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr "Tag erfolgreich hinzugefügt"
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr "Tag erfolgreich aktualisiert"
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr "Tag erfolgreich gelöscht"
@@ -3626,15 +3614,21 @@ msgstr "Keine wiederkehrenden Transaktionen"
msgid "View"
msgstr "Ansicht"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr "Deaktivieren"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr "Aktivieren"
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
#, fuzzy
#| msgid "Only the owner can edit this"
msgid "Only the owner can change this"
msgstr "Nur der Besitzer kann dies bearbeiten"
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr "Keine Regeln"
+55 -63
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -189,68 +189,56 @@ msgstr ""
msgid "Exchange currency cannot be the same as the account's main currency."
msgstr ""
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr ""
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr ""
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr ""
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr ""
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr ""
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr ""
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr ""
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr ""
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr ""
@@ -780,27 +768,27 @@ msgstr ""
msgid "DCA Entries"
msgstr ""
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr ""
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr ""
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr ""
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr ""
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr ""
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr ""
@@ -1269,15 +1257,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr ""
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr ""
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr ""
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr ""
@@ -1285,27 +1273,27 @@ msgstr ""
msgid "Rule updated successfully"
msgstr ""
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr ""
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr ""
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr ""
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr ""
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr ""
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr ""
@@ -1716,27 +1704,27 @@ msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] ""
msgstr[1] ""
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr ""
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr ""
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr ""
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr ""
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr ""
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr ""
@@ -1799,15 +1787,15 @@ msgstr ""
msgid "Recurring Transaction deleted successfully"
msgstr ""
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr ""
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr ""
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr ""
@@ -3487,15 +3475,19 @@ msgstr ""
msgid "View"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr ""
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
msgid "Only the owner can change this"
msgstr ""
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr ""
+57 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: 2026-07-10 04:57+0000\n"
"Last-Translator: Juan David Afanador <juanafanador07@gmail.com>\n"
"Language-Team: Spanish <https://translations.herculino.com/projects/wygiwyh/"
@@ -193,68 +193,56 @@ msgstr ""
"La moneda de cambio no puede ser la misma que la moneda principal de la "
"cuenta."
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr "Grupo de Cuenta añadido exitosamente"
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr "Solo el dueño puede editar esto"
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr "Grupo de Cuenta actualizado exitosamente"
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr "El artículo que ya no se comparte contigo"
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr "Grupo de Cuenta eliminado exitosamente"
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr "El artículo que ya no se comparte contigo"
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr "Propiedad tomada exitosamete"
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr "Configuración guardada exitosamente"
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr "Cuenta agregada exitosamente"
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr "Cuenta actualizada exitosamente"
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr "Cuenta borrada exitosamente"
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr "La cuenta ahora está siendo seguida"
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr "La cuenta ya no está siendo seguida"
@@ -801,27 +789,27 @@ msgstr "Entrada DCA"
msgid "DCA Entries"
msgstr "Entradas DCA"
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr "Estrategia DCA agregada con éxito"
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr "Estrategia DCA actualizada con éxito"
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr "Estrategia DCA eliminada con éxito"
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr "Entrada agregada con éxito"
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr "Entrada actualizada con éxito"
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr "Entrada eliminada con éxito"
@@ -1293,15 +1281,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr "Actualizar o crear acción de transacción"
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr "Regla desactivada con éxito"
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr "Regla activada con éxito"
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr "Regla agregada con éxito"
@@ -1309,27 +1297,27 @@ msgstr "Regla agregada con éxito"
msgid "Rule updated successfully"
msgstr "Regla actualizada con éxito"
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr "Regla eliminada con éxito"
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr "Acción actualizada con éxito"
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr "Acción eliminada con éxito"
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr "Acción de Actualizar o Crear Transacción agregada con éxito"
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr "Acción de Actualizar o Crear Transacción actualizada con éxito"
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr "Acción de Actualizar o Crear Transacción eliminada con éxito"
@@ -1750,27 +1738,27 @@ msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] "%(count)s transacción duplicada con éxito"
msgstr[1] "%(count)s transacciones duplicadas con éxito"
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr "Categoría agregada con éxito"
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr "Categoría actualizada con éxito"
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr "Categoría eliminada con éxito"
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr "Entidad agregada con éxito"
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr "Entidad actualizada con éxito"
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr "Entidad eliminada con éxito"
@@ -1833,15 +1821,15 @@ msgstr "Transacción Recurrente terminada exitosamente"
msgid "Recurring Transaction deleted successfully"
msgstr "Transacción Recurrente borrada exitosamente"
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr "Etiqueta agregada con éxito"
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr "Etiqueta actualizada con éxito"
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr "Etiqueta eliminada con éxito"
@@ -3561,15 +3549,21 @@ msgstr "Sin transacciones recurrentes"
msgid "View"
msgstr "Ver"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr "Desactivar"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr "Activar"
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
#, fuzzy
#| msgid "Only the owner can edit this"
msgid "Only the owner can change this"
msgstr "Solo el dueño puede editar esto"
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr "Sin reglas"
+57 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: 2026-08-23 16:57+0000\n"
"Last-Translator: renardspark <remibonnard@laposte.net>\n"
"Language-Team: French <https://translations.herculino.com/projects/wygiwyh/"
@@ -195,68 +195,56 @@ msgstr ""
"La devise de change ne peut pas être identique à la devise principal du "
"compte."
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr "Groupe de compte ajouté avec succès"
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr "Seul le propriétaire peut modifier ceci"
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr "Groupe de compte mis à jour avec succès"
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr "Cette objet n'est plus partagé avec vous"
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr "Groupe de compte supprimé avec succès"
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr "Cette objet n'est plus partagé avec vous"
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr "Propriété acquise avec succès"
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr "Configuration sauvegardé avec succès"
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr "Compte ajouté avec succès"
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr "Compte mis à jour avec succès"
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr "Compte supprimé avec succès"
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr "Le compte est maintenant suivi"
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr "Le compte n'est plus suivi"
@@ -802,27 +790,27 @@ msgstr "Entrée DCA"
msgid "DCA Entries"
msgstr "Entrées DCA"
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr "Stratégie DCA ajouté avec succès"
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr "Stratégie DCA mise à jour avec succès"
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr "Stratégie DCA supprimé avec succès"
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr "Entrée ajoutée avec succès"
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr "Entrée mise à jour avec succès"
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr "Entrée supprimée avec succès"
@@ -1295,15 +1283,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr "Mettre à jour ou créer une action de transaction"
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr "Règle désactivée avec succès"
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr "Règle activée avec succès"
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr "Règle ajoutée avec succès"
@@ -1311,28 +1299,28 @@ msgstr "Règle ajoutée avec succès"
msgid "Rule updated successfully"
msgstr "Règle mis à jour avec succès"
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr "Règle supprimée avec succès"
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr "Action mis à jour avec succès"
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr "Action supprimée avec succès"
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr "Mis à jour ou Création de Transaction ajouté avec succès"
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr ""
"Mis à jour ou Création de l'action de Transaction mis à jour avec succès"
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr ""
"Mis à jour ou Création de l'action de Transaction supprimée avec succès"
@@ -1754,27 +1742,27 @@ msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] "%(count)s transaction dupliquée avec succès"
msgstr[1] "%(count)s transactions dupliquées avec succès"
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr "Catégorie ajoutée avec succès"
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr "La catégorie a été mise à jour avec succès"
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr "Catégorie supprimée avec succès"
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr "Entité ajoutée avec succès"
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr "Entité mise à jour avec succès"
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr "Entité supprimée avec succès"
@@ -1837,15 +1825,15 @@ msgstr "Transaction récurrente terminée avec succès"
msgid "Recurring Transaction deleted successfully"
msgstr "Transaction récurrente supprimée avec succès"
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr "Etiquette ajoutée avec succès"
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr "Etiquette mise à jour avec succès"
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr "Etiquette supprimée avec succès"
@@ -3566,15 +3554,21 @@ msgstr "Aucune transaction récurrente"
msgid "View"
msgstr "Vue"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr "Désactiver"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr "Activer"
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
#, fuzzy
#| msgid "Only the owner can edit this"
msgid "Only the owner can change this"
msgstr "Seul le propriétaire peut modifier ceci"
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr "Aucune règle"
+57 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: 2026-01-10 03:09+0000\n"
"Last-Translator: Herculino Trotta <netotrotta@gmail.com>\n"
"Language-Team: Hungarian <https://translations.herculino.com/projects/"
@@ -194,68 +194,56 @@ msgstr "Számlák"
msgid "Exchange currency cannot be the same as the account's main currency."
msgstr "A váltási pénznem nem lehet azonos a számla fő pénznemével."
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr "Számlacsoport sikeresen hozzáadva"
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr "Csak a tulajdonos szerkesztheti ezt"
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr "A számlacsoport sikeresen módosítva lett"
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr "A tétel már nincs megosztva veled"
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr "A számlacsoport sikeresen törlésre került"
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr "A tétel már nincs megosztva veled"
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr "A tulajdonjog sikeresen átvéve"
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr "A beállítás mentésre került"
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr "A számla hozzáadásra került"
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr "A számla módosítása sikeres volt"
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr "A számla törlésre került"
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr "A számla mostantól követve van"
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr "A számla mostantól nincs követve"
@@ -786,27 +774,27 @@ msgstr ""
msgid "DCA Entries"
msgstr ""
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr ""
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr ""
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr ""
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr ""
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr ""
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr ""
@@ -1275,15 +1263,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr ""
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr ""
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr ""
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr ""
@@ -1291,27 +1279,27 @@ msgstr ""
msgid "Rule updated successfully"
msgstr ""
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr ""
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr ""
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr ""
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr ""
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr ""
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr ""
@@ -1730,27 +1718,27 @@ msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] ""
msgstr[1] ""
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr ""
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr ""
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr ""
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr ""
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr ""
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr ""
@@ -1813,15 +1801,15 @@ msgstr ""
msgid "Recurring Transaction deleted successfully"
msgstr ""
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr ""
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr ""
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr ""
@@ -3525,15 +3513,21 @@ msgstr ""
msgid "View"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr ""
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
#, fuzzy
#| msgid "Only the owner can edit this"
msgid "Only the owner can change this"
msgstr "Csak a tulajdonos szerkesztheti ezt"
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr ""
+55 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -188,68 +188,56 @@ msgstr ""
msgid "Exchange currency cannot be the same as the account's main currency."
msgstr ""
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr ""
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr ""
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr ""
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr ""
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr ""
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr ""
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr ""
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr ""
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr ""
@@ -779,27 +767,27 @@ msgstr ""
msgid "DCA Entries"
msgstr ""
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr ""
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr ""
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr ""
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr ""
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr ""
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr ""
@@ -1268,15 +1256,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr ""
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr ""
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr ""
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr ""
@@ -1284,27 +1272,27 @@ msgstr ""
msgid "Rule updated successfully"
msgstr ""
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr ""
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr ""
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr ""
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr ""
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr ""
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr ""
@@ -1715,27 +1703,27 @@ msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] ""
msgstr[1] ""
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr ""
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr ""
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr ""
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr ""
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr ""
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr ""
@@ -1798,15 +1786,15 @@ msgstr ""
msgid "Recurring Transaction deleted successfully"
msgstr ""
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr ""
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr ""
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr ""
@@ -3485,15 +3473,19 @@ msgstr ""
msgid "View"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr ""
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
msgid "Only the owner can change this"
msgstr ""
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr ""
+57 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: 2025-12-28 22:24+0000\n"
"Last-Translator: icovada <federico.tabbo@networktocode.com>\n"
"Language-Team: Italian <https://translations.herculino.com/projects/wygiwyh/"
@@ -195,68 +195,56 @@ msgstr ""
"La valuta di cambio non può essere la stessa della valuta principale del "
"conto."
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr "Gruppo conti aggiunto con successo"
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr "Solo il proprietario può modificare questo elemento"
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr "Gruppo conti aggiornato con successo"
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr "Lelemento non è più condiviso con te"
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr "Gruppo conti eliminato con successo"
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr "Lelemento non è più condiviso con te"
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr "Titolarità acquisita con successo"
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr "Configurazione salvata con successo"
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr "Conto aggiunto con successo"
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr "Conto aggiornato con successo"
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr "Conto eliminato con successo"
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr "Il conto è ora tracciato"
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr "Il conto non è più tracciato"
@@ -802,27 +790,27 @@ msgstr "Rata del PAC"
msgid "DCA Entries"
msgstr "Rate del PAC"
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr "Piano d'Accumulo aggiunto con successo"
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr "Piano d'Accumulo aggiornato con successo"
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr "Piano d'Accumulo eliminato con successo"
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr "Voce aggiunta con successo"
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr "Voce aggiornata con successo"
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr "Voce eliminata con successo"
@@ -1296,15 +1284,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr "Aggiorna o crea un'azione di transazione"
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr "Regola disattivata con successo"
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr "Regola attivata con successo"
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr "Regola aggiunta con successo"
@@ -1312,27 +1300,27 @@ msgstr "Regola aggiunta con successo"
msgid "Rule updated successfully"
msgstr "Regola aggiornata con successo"
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr "Regola eliminata con successo"
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr "Azione aggiornata con successo"
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr "Azione eliminata con successo"
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr "Azione Aggiornamento o Creazione transazione aggiunta correttamente"
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr "Azione Aggiornamento o Creazione transazione aggiornata correttamente"
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr "Azione Aggiornamento o Creazione transazione eliminata correttamente"
@@ -1763,27 +1751,27 @@ msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] "%(count)s transazione duplicata con successo"
msgstr[1] "%(count)s transazioni duplicate con successo"
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr "Categoria aggiunta con successo"
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr "Categoria aggiornata con successo"
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr "Categoria eliminata con successo"
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr "Beneficiario aggiunto con successo"
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr "Beneficiario aggiornato con successo"
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr "Beneficiario eliminato con successo"
@@ -1846,15 +1834,15 @@ msgstr "Transazione ricorrente completata con successo"
msgid "Recurring Transaction deleted successfully"
msgstr "Transazione ricorrente eliminata correttamente"
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr "Tag aggiunto con successo"
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr "Tag aggiornato con successo"
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr "Tag rimosso con successo"
@@ -3613,15 +3601,21 @@ msgstr "Nessuna transazione ricorrente"
msgid "View"
msgstr "Vista"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr "Disattiva"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr "Attiva"
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
#, fuzzy
#| msgid "Only the owner can edit this"
msgid "Only the owner can change this"
msgstr "Solo il proprietario può modificare questo elemento"
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr "Nessuna regola"
+57 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: 2026-08-17 15:57+0000\n"
"Last-Translator: Dimitri Decrock <dj.flashpower@gmail.com>\n"
"Language-Team: Dutch <https://translations.herculino.com/projects/wygiwyh/"
@@ -195,68 +195,56 @@ msgid "Exchange currency cannot be the same as the account's main currency."
msgstr ""
"Eenheid wisselgeld kan niet dezelfde zijn als de munteenheid van de rekening."
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr "Rekeninggroep succesvol toegevoegd"
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr "Alleen de eigenaar kan dit bewerken"
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr "Rekeninggroep succesvol bijgewerkt"
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr "Item is niet langer met jouw gedeeld"
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr "Rekeninggroep succesvol verwijderd"
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr "Item is niet langer met jouw gedeeld"
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr "Eigendom succesvol genomen"
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr "Configuratie succesvol opgeslagen"
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr "Rekening succesvol toegevoegd"
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr "Rekening succesvol bijgewerkt"
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr "Rekening succesvol verwijderd"
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr "Account wordt nu bijgehouden"
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr "Account wordt nu niet meer bijgehouden"
@@ -803,27 +791,27 @@ msgstr "DCA Instap"
msgid "DCA Entries"
msgstr "DCA Idems"
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr "Strategie voor DCA succesvol toegevoegd"
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr "Strategie voor DCA succesvol bijgewerkt"
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr "Strategie voor DCA succesvol verwijderd"
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr "Item succesvol toegevoegd"
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr "Invoer succesvol bijgewerkt"
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr "Invoer succesvol verwijderd"
@@ -1296,15 +1284,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr "Bewerk of maak verrichtingsregel actie"
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr "Regel succesvol uitgeschakeld"
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr "Regel succesvol ingeschakeld"
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr "Regel succesvol toegevoegd"
@@ -1312,27 +1300,27 @@ msgstr "Regel succesvol toegevoegd"
msgid "Rule updated successfully"
msgstr "Regel succesvol bijgewerkt"
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr "Regel succesvol verwijderd"
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr "Actie succesvol bijgewerkt"
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr "Actie succesvol verwijderd"
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr "Verrichting Bijwerken Of Maken succesvol toegevoegd"
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr "Verrichting Bijwerken Of Maken succesvol bijgewerkt"
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr "Verrichting Bijwerken Of Maken succesvol verwijderd"
@@ -1751,27 +1739,27 @@ msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] "%(count)s verrichting succesvol gedupliceerd"
msgstr[1] "%(count)s verrichtingen succesvol gedupliceerd"
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr "Categorie succesvol toegevoegd"
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr "Categorie succesvol bijgewerkt"
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr "Categorie succesvol verwijderd"
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr "Bedrijf succesvol toegevoegd"
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr "Bedrijf succesvol bijgewerkt"
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr "Bedrijf succesvol verwijderd"
@@ -1834,15 +1822,15 @@ msgstr "Terugkerende Verrichting succesvol voltooid"
msgid "Recurring Transaction deleted successfully"
msgstr "Terugkerende Verrichting succesvol verwijderd"
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr "Label succesvol toegevoegd"
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr "Label succesvol bijgewerkt"
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr "Label succesvol verwijderd"
@@ -3547,15 +3535,21 @@ msgstr "Geen terugkerende verrichtingen"
msgid "View"
msgstr "Toon"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr "Uitschakelen"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr "Inschakelen"
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
#, fuzzy
#| msgid "Only the owner can edit this"
msgid "Only the owner can change this"
msgstr "Alleen de eigenaar kan dit bewerken"
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr "Geen regels"
+57 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: 2026-06-08 07:57+0000\n"
"Last-Translator: Pawel Augustyn <pawelaugustyn15@gmail.com>\n"
"Language-Team: Polish <https://translations.herculino.com/projects/wygiwyh/"
@@ -195,68 +195,56 @@ msgstr "Konta"
msgid "Exchange currency cannot be the same as the account's main currency."
msgstr "Waluta do wymiany nie może być taka sama jak waluta konta."
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr "Grupa kont dodana pomyślnie"
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr "Tylko właściciel może to edytować"
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr "Zaktualizowano grupę kont"
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr "Ten wpis nie jest już dostępny dla Ciebie"
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr "Usunięto grupę kont"
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr "Ten wpis nie jest już dostępny dla Ciebie"
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr "Własność przejęta pomyślnie"
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr "Pomyślnie zapisano konfigurację"
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr "Konto dodane pomyślnie"
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr "Konto zaktualizowane"
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr "Usunięto konto"
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr "Konto jest teraz śledzone"
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr "Przestano śledzić konto"
@@ -807,27 +795,27 @@ msgstr ""
msgid "DCA Entries"
msgstr ""
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr ""
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr ""
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr ""
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr "Pomyślnie dodano wpis"
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr "Pomyślnie zaktualizowano wpis"
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr "Pomyślnie usunięto wpis"
@@ -1300,15 +1288,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr "Zaktualizuj lub utwórz akcję transakcji"
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr "Reguła zdezaktywowana"
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr "Reguła aktywowana"
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr "Dodano regułę"
@@ -1316,27 +1304,27 @@ msgstr "Dodano regułę"
msgid "Rule updated successfully"
msgstr "Zaktualizowano regułę"
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr "Usunięto regułę"
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr "Zaktualizowano akcję"
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr "Usunięto akcję"
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr "Akcja stworzenia lub aktualizacji transakcji dodana pomyślnie"
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr "Akcja stworzenia lub aktualizacji transakcji zmodyfikowana"
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr "Usunięto akcję stworzenia lub aktualizacji transakcji"
@@ -1764,27 +1752,27 @@ msgstr[0] "Zduplikowano %(count)s transakcję"
msgstr[1] "Zduplikowano %(count)s transakcje"
msgstr[2] "Zduplikowano %(count)s transakcji"
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr "Dodano kategorię"
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr "Zaktualizowano kategorię"
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr "Usunięto kategorię"
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr "Dodano encję"
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr "Zaktualizowano encję"
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr "Usunięto encję"
@@ -1847,15 +1835,15 @@ msgstr "Zakończono zlecenie stałe"
msgid "Recurring Transaction deleted successfully"
msgstr "Usunięto zlecenie stałe"
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr "Dodano tag"
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr "Zaktualizowano tag"
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr "Usunięto tag"
@@ -3568,15 +3556,21 @@ msgstr "Brak zleceń stałych"
msgid "View"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr ""
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
#, fuzzy
#| msgid "Only the owner can edit this"
msgid "Only the owner can change this"
msgstr "Tylko właściciel może to edytować"
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr ""
+57 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: 2026-08-16 22:03+0000\n"
"Last-Translator: Herculino Trotta <netotrotta@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translations.herculino.com/"
@@ -193,68 +193,56 @@ msgstr "Contas"
msgid "Exchange currency cannot be the same as the account's main currency."
msgstr "A moeda de câmbio não pode ser a mesma que a moeda principal da conta."
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr "Grupo de Conta adicionado com sucesso"
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr "Somente o proprietário pode editar isso"
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr "Grupo de Conta atualizado com sucesso"
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr "Item não está mais compartilhado com você"
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr "Grupo de Conta apagado com sucesso"
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr "Item não está mais compartilhado com você"
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr "Propriedade assumida com sucesso"
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr "Configuração salva com sucesso"
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr "Conta adicionado com sucesso"
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr "Conta atualizada com sucesso"
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr "Conta apagada com sucesso"
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr "A conta agora está sendo acompanhada"
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr "A conta agora não está mais sendo acompanhada"
@@ -801,27 +789,27 @@ msgstr "Entrada CMP"
msgid "DCA Entries"
msgstr "Entradas CMP"
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr "Estratégia CMP adicionada com sucesso"
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr "Estratégia CMP atualizada com sucesso"
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr "Estratégia CMP apagada com sucesso"
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr "Entrada adicionada com sucesso"
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr "Entrada atualizada com sucesso"
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr "Entrada apagada com sucesso"
@@ -1294,15 +1282,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr "Ação de atualizar ou criar transação"
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr "Regra desativada com sucesso"
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr "Regra ativada com sucesso"
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr "Regra adicionada com sucesso"
@@ -1310,27 +1298,27 @@ msgstr "Regra adicionada com sucesso"
msgid "Rule updated successfully"
msgstr "Regra atualizada com sucesso"
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr "Regra apagada com sucesso"
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr "Ação atualizada com sucesso"
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr "Ação apagada com sucesso"
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr "Ação Atualizar ou Criar Transação adicionada com sucesso"
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr "Ação Atualizar ou Criar Transação atualizada com sucesso"
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr "Ação Atualizar ou Criar Transação apagada com sucesso"
@@ -1748,27 +1736,27 @@ msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] "%(count)s transação duplicada com sucesso"
msgstr[1] "%(count)s transações duplicadas com sucesso"
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr "Categoria adicionada com sucesso"
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr "Categoria atualizada com sucesso"
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr "Categoria apagada com sucesso"
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr "Entidade adicionada com sucesso"
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr "Entidade atualizada com sucesso"
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr "Entidade apagada com sucesso"
@@ -1831,15 +1819,15 @@ msgstr "Transação Recorrente finalizada com sucesso"
msgid "Recurring Transaction deleted successfully"
msgstr "Transação Recorrente apagada com sucesso"
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr "Tag adicionada com sucesso"
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr "Tag atualizada com sucesso"
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr "Tag apagada com sucesso"
@@ -3541,15 +3529,21 @@ msgstr "Nenhuma transação recorrente"
msgid "View"
msgstr "Visualizar"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr "Desativar"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr "Ativar"
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
#, fuzzy
#| msgid "Only the owner can edit this"
msgid "Only the owner can change this"
msgstr "Somente o proprietário pode editar isso"
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr "Nenhuma regra"
+57 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: 2026-05-01 07:24+0000\n"
"Last-Translator: masttera <mail.masttera@gmail.com>\n"
"Language-Team: Russian <https://translations.herculino.com/projects/wygiwyh/"
@@ -195,68 +195,56 @@ msgstr "Счета"
msgid "Exchange currency cannot be the same as the account's main currency."
msgstr "Обменная валюта не может совпадать с основной валютой счета."
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr "Группа счетов успешно добавлена"
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr "Редактировать это может только владелец"
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr "Группа счетов успешно обновлена"
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr "Информация больше не предоставляется вам"
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr "Группа счетов успешно удалена"
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr "Информация больше не предоставляется вам"
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr "Владелец успешно присвоен"
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr "Настройки успешно сохранены"
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr "Счет успешно добавлен"
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr "Счет успешно обновлен"
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr "Счет успешно удален"
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr "Счет теперь отслеживается"
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr "Счет теперь не отслеживается"
@@ -811,27 +799,27 @@ msgstr ""
msgid "DCA Entries"
msgstr "Записи DCA"
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr "Стратегия DCA успешно добавлена"
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr "Стратегия DCA успешно обновлена"
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr "Стратегия DCA успешно удалена"
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr "Запись успешно добавлена"
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr "Запись успешно обновлена"
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr "Запись успешно удалена"
@@ -1302,15 +1290,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr "Операция обновления или создания транзакции"
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr "Правило успешно деактивировано"
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr "Правило успешно активировано"
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr "Правило успешно добавлено"
@@ -1318,27 +1306,27 @@ msgstr "Правило успешно добавлено"
msgid "Rule updated successfully"
msgstr "Правило успешно обновлено"
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr "Правило успешно удалено"
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr "Действие успешно обновлено"
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr "Действие успешно удалено"
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr "Действие «Обновить или создать транзакцию» успешно добавлено"
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr "Операция обновления или создания транзакции успешно завершена"
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr "Операция обновления или создания транзакции успешно удалена"
@@ -1764,27 +1752,27 @@ msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] ""
msgstr[1] ""
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr "Категория успешно добавлена"
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr "Категория успешно обновлена"
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr "Категория успешно удалена"
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr "Объект успешно добавлен"
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr "Объект обновлен успешно"
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr "Объект успешно удален"
@@ -1847,15 +1835,15 @@ msgstr "Повторяющаяся транзакция успешно заве
msgid "Recurring Transaction deleted successfully"
msgstr "Повторяющаяся транзакция успешно удалена"
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr "Тег успешно добавлен"
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr "Тег успешно обновлен"
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr "Тег успешно удален"
@@ -3566,15 +3554,21 @@ msgstr "Нет повторяющихся транзакций"
msgid "View"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr "Отключить"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr "включить"
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
#, fuzzy
#| msgid "Only the owner can edit this"
msgid "Only the owner can change this"
msgstr "Редактировать это может только владелец"
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr "Нет правил"
+55 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: 2025-04-14 06:16+0000\n"
"Last-Translator: Emil <emil.bjorkroth@gmail.com>\n"
"Language-Team: Swedish <https://translations.herculino.com/projects/wygiwyh/"
@@ -190,68 +190,56 @@ msgstr ""
msgid "Exchange currency cannot be the same as the account's main currency."
msgstr ""
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr ""
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr ""
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr ""
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr ""
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr ""
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr ""
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr ""
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr ""
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr ""
@@ -781,27 +769,27 @@ msgstr ""
msgid "DCA Entries"
msgstr ""
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr ""
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr ""
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr ""
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr ""
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr ""
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr ""
@@ -1270,15 +1258,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr ""
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr ""
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr ""
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr ""
@@ -1286,27 +1274,27 @@ msgstr ""
msgid "Rule updated successfully"
msgstr ""
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr ""
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr ""
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr ""
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr ""
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr ""
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr ""
@@ -1717,27 +1705,27 @@ msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] ""
msgstr[1] ""
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr ""
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr ""
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr ""
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr ""
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr ""
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr ""
@@ -1800,15 +1788,15 @@ msgstr ""
msgid "Recurring Transaction deleted successfully"
msgstr ""
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr ""
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr ""
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr ""
@@ -3490,15 +3478,19 @@ msgstr ""
msgid "View"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr ""
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
msgid "Only the owner can change this"
msgstr ""
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr ""
+55 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: 2026-01-18 19:24+0000\n"
"Last-Translator: Ebrahim Tayabali <ebrahimhakimuddin@gmail.com>\n"
"Language-Team: Swahili <https://translations.herculino.com/projects/wygiwyh/"
@@ -194,68 +194,56 @@ msgstr ""
msgid "Exchange currency cannot be the same as the account's main currency."
msgstr ""
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr ""
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr ""
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr ""
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr ""
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr ""
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr ""
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr ""
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr ""
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr ""
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr ""
@@ -785,27 +773,27 @@ msgstr ""
msgid "DCA Entries"
msgstr ""
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr ""
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr ""
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr ""
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr ""
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr ""
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr ""
@@ -1274,15 +1262,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr ""
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr ""
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr ""
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr ""
@@ -1290,27 +1278,27 @@ msgstr ""
msgid "Rule updated successfully"
msgstr ""
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr ""
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr ""
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr ""
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr ""
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr ""
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr ""
@@ -1721,27 +1709,27 @@ msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] ""
msgstr[1] ""
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr ""
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr ""
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr ""
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr ""
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr ""
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr ""
@@ -1804,15 +1792,15 @@ msgstr ""
msgid "Recurring Transaction deleted successfully"
msgstr ""
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr ""
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr ""
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr ""
@@ -3498,15 +3486,19 @@ msgstr ""
msgid "View"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr ""
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
msgid "Only the owner can change this"
msgstr ""
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr ""
+57 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: 2025-11-01 01:17+0000\n"
"Last-Translator: mlystopad <mlystopadt@gmail.com>\n"
"Language-Team: Ukrainian <https://translations.herculino.com/projects/"
@@ -195,68 +195,56 @@ msgstr "Рахунки"
msgid "Exchange currency cannot be the same as the account's main currency."
msgstr "Валюта обміну не може збігатися з основною валютою рахунку."
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr "Групу рахунків успішно додано"
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr "Тільки власник може редагувати його"
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr "Групу рахунків успішно оновлено"
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr "Елемент більше не доступний для вас"
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr "Групу рахунків успішно видалено"
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr "Елемент більше не доступний для вас"
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr "Успішно прийнято право власності"
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr "Конфігурацію успішно збережено"
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr "Рахунок додано успішно"
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr "Рахунок успішно оновлено"
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr "Рахунок успішно видалено"
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr "Рахунок теперь відстежується"
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr "Рахунок більше не відстежується"
@@ -811,27 +799,27 @@ msgstr ""
msgid "DCA Entries"
msgstr ""
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr ""
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr ""
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr ""
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr "Запис успішно додано"
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr "Запис успішно оновлено"
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr "Запис успішно видалено"
@@ -1302,15 +1290,15 @@ msgstr ""
msgid "Update or create transaction action"
msgstr ""
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr ""
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr ""
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr ""
@@ -1318,27 +1306,27 @@ msgstr ""
msgid "Rule updated successfully"
msgstr ""
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr ""
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr ""
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr ""
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr ""
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr ""
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr ""
@@ -1759,27 +1747,27 @@ msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] ""
msgstr[1] ""
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr ""
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr ""
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr ""
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr ""
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr ""
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr ""
@@ -1844,15 +1832,15 @@ msgstr ""
msgid "Recurring Transaction deleted successfully"
msgstr ""
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr ""
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr ""
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr ""
@@ -3566,15 +3554,21 @@ msgstr ""
msgid "View"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr ""
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr ""
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
#, fuzzy
#| msgid "Only the owner can edit this"
msgid "Only the owner can change this"
msgstr "Тільки власник може редагувати його"
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr ""
+57 -63
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-16 04:46+0000\n"
"POT-Creation-Date: 2026-09-06 19:42+0000\n"
"PO-Revision-Date: 2025-10-08 16:17+0000\n"
"Last-Translator: doody <doodykimo@gmail.com>\n"
"Language-Team: Chinese (Traditional Han script) <https://translations."
@@ -190,68 +190,56 @@ msgstr "帳戶"
msgid "Exchange currency cannot be the same as the account's main currency."
msgstr "目標貨幣不能跟原始貨幣相同。"
#: apps/accounts/views/account_groups.py:44
#: apps/accounts/views/account_groups.py:50
msgid "Account Group added successfully"
msgstr "成功新增帳戶組"
#: apps/accounts/views/account_groups.py:69
#: apps/accounts/views/account_groups.py:152 apps/accounts/views/accounts.py:68
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:62
#: apps/dca/views.py:145 apps/rules/views.py:118 apps/rules/views.py:228
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
#: apps/transactions/views/entities.py:171 apps/transactions/views/tags.py:91
#: apps/transactions/views/tags.py:171
msgid "Only the owner can edit this"
msgstr "只有擁有者可以編輯這個項目"
#: apps/accounts/views/account_groups.py:82
#: apps/accounts/views/account_groups.py:78
msgid "Account Group updated successfully"
msgstr "成功更新帳戶組"
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:104
#: apps/rules/views.py:185 apps/transactions/views/categories.py:168
#: apps/transactions/views/entities.py:130 apps/transactions/views/tags.py:130
msgid "Item no longer shared with you"
msgstr "這個項目不再與你分享"
#: apps/accounts/views/account_groups.py:114
#: apps/accounts/views/account_groups.py:104
msgid "Account Group deleted successfully"
msgstr "成功刪除帳戶組"
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:189 apps/dca/views.py:128
#: apps/rules/views.py:210 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 apps/transactions/views/tags.py:154
#: apps/accounts/views/account_groups.py:109
#: apps/accounts/views/accounts.py:137 apps/dca/views.py:106
#: apps/rules/views.py:191 apps/transactions/views/categories.py:163
#: apps/transactions/views/entities.py:135 apps/transactions/views/tags.py:131
msgid "Item no longer shared with you"
msgstr "這個項目不再與你分享"
#: apps/accounts/views/account_groups.py:132
#: apps/accounts/views/accounts.py:182 apps/dca/views.py:131
#: apps/rules/views.py:217 apps/transactions/views/categories.py:188
#: apps/transactions/views/entities.py:160 apps/transactions/views/tags.py:154
msgid "Ownership taken successfully"
msgstr "成功取得所有權"
#: apps/accounts/views/account_groups.py:165
#: apps/accounts/views/accounts.py:119 apps/dca/views.py:158
#: apps/rules/views.py:241 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
#: apps/accounts/views/account_groups.py:152
#: apps/accounts/views/accounts.py:106 apps/dca/views.py:151
#: apps/rules/views.py:238 apps/transactions/views/categories.py:130
#: apps/transactions/views/entities.py:180 apps/transactions/views/tags.py:174
msgid "Configuration saved successfully"
msgstr "成功儲存設定"
#: apps/accounts/views/accounts.py:44
#: apps/accounts/views/accounts.py:50
msgid "Account added successfully"
msgstr "成功新增帳戶"
#: apps/accounts/views/accounts.py:81
#: apps/accounts/views/accounts.py:78
msgid "Account updated successfully"
msgstr "成功更新帳戶"
#: apps/accounts/views/accounts.py:148
#: apps/accounts/views/accounts.py:132
msgid "Account deleted successfully"
msgstr "成功刪除帳戶"
#: apps/accounts/views/accounts.py:165
#: apps/accounts/views/accounts.py:158
msgid "Account is now tracked"
msgstr "帳戶已追蹤"
#: apps/accounts/views/accounts.py:168
#: apps/accounts/views/accounts.py:161
msgid "Account is now untracked"
msgstr "取消追蹤帳戶"
@@ -781,27 +769,27 @@ msgstr "定期定額投入"
msgid "DCA Entries"
msgstr "定期定額投入"
#: apps/dca/views.py:38
#: apps/dca/views.py:44
msgid "DCA Strategy added successfully"
msgstr "成功新增定期定額策略"
#: apps/dca/views.py:75
#: apps/dca/views.py:73
msgid "DCA Strategy updated successfully"
msgstr "成功更新定期定額策略"
#: apps/dca/views.py:107
#: apps/dca/views.py:101
msgid "DCA strategy deleted successfully"
msgstr "成功刪除定期定額策略"
#: apps/dca/views.py:237
#: apps/dca/views.py:236
msgid "Entry added successfully"
msgstr "成功新增投入"
#: apps/dca/views.py:264
#: apps/dca/views.py:270
msgid "Entry updated successfully"
msgstr "成功更新投入"
#: apps/dca/views.py:290
#: apps/dca/views.py:303
msgid "Entry deleted successfully"
msgstr "成功刪除投入"
@@ -1270,15 +1258,15 @@ msgstr "決定是否啟用或停用規則執行的通用表達式,結果應該
msgid "Update or create transaction action"
msgstr "更新或建立交易行為"
#: apps/rules/views.py:71
#: apps/rules/views.py:79
msgid "Rule deactivated successfully"
msgstr "成功停用規則"
#: apps/rules/views.py:73
#: apps/rules/views.py:81
msgid "Rule activated successfully"
msgstr "成功啟用規則"
#: apps/rules/views.py:92
#: apps/rules/views.py:100
msgid "Rule added successfully"
msgstr "成功新增規則"
@@ -1286,27 +1274,27 @@ msgstr "成功新增規則"
msgid "Rule updated successfully"
msgstr "成功更新規則"
#: apps/rules/views.py:188
#: apps/rules/views.py:186
msgid "Rule deleted successfully"
msgstr "成功刪除規則"
#: apps/rules/views.py:305
#: apps/rules/views.py:306
msgid "Action updated successfully"
msgstr "成功更新行為"
#: apps/rules/views.py:336
#: apps/rules/views.py:341
msgid "Action deleted successfully"
msgstr "成功刪除行為"
#: apps/rules/views.py:360
#: apps/rules/views.py:367
msgid "Update or Create Transaction action added successfully"
msgstr "成功新增「更新或建立交易行為」"
#: apps/rules/views.py:393
#: apps/rules/views.py:402
msgid "Update or Create Transaction action updated successfully"
msgstr "成功更新「更新或建立交易行為」"
#: apps/rules/views.py:423
#: apps/rules/views.py:434
msgid "Update or Create Transaction action deleted successfully"
msgstr "成功刪除「更新或建立交易行為」"
@@ -1724,27 +1712,27 @@ msgid "%(count)s transaction duplicated successfully"
msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] "成功複製%(count)s筆交易"
#: apps/transactions/views/categories.py:66
#: apps/transactions/views/categories.py:72
msgid "Category added successfully"
msgstr "成功新增分類"
#: apps/transactions/views/categories.py:104
#: apps/transactions/views/categories.py:102
msgid "Category updated successfully"
msgstr "成功更新分類"
#: apps/transactions/views/categories.py:171
#: apps/transactions/views/categories.py:158
msgid "Category deleted successfully"
msgstr "成功刪除分類"
#: apps/transactions/views/entities.py:66
#: apps/transactions/views/entities.py:72
msgid "Entity added successfully"
msgstr "成功新增實體"
#: apps/transactions/views/entities.py:104
#: apps/transactions/views/entities.py:102
msgid "Entity updated successfully"
msgstr "成功更新實體"
#: apps/transactions/views/entities.py:133
#: apps/transactions/views/entities.py:130
msgid "Entity deleted successfully"
msgstr "成功刪除實體"
@@ -1807,15 +1795,15 @@ msgstr "成功完成定期扣款"
msgid "Recurring Transaction deleted successfully"
msgstr "成功刪除定期扣款"
#: apps/transactions/views/tags.py:66
#: apps/transactions/views/tags.py:72
msgid "Tag added successfully"
msgstr "成功新增標籤"
#: apps/transactions/views/tags.py:104
#: apps/transactions/views/tags.py:100
msgid "Tag updated successfully"
msgstr "成功更新標籤"
#: apps/transactions/views/tags.py:133
#: apps/transactions/views/tags.py:126
msgid "Tag deleted successfully"
msgstr "成功刪除標籤"
@@ -3551,15 +3539,21 @@ msgstr "沒有定期扣款交易"
msgid "View"
msgstr "檢視"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Deactivate"
msgstr "停用"
#: templates/rules/fragments/list.html:70
#: templates/rules/fragments/list.html:71
msgid "Activate"
msgstr "啟用"
#: templates/rules/fragments/list.html:89
#: templates/rules/fragments/list.html:77
#, fuzzy
#| msgid "Only the owner can edit this"
msgid "Only the owner can change this"
msgstr "只有擁有者可以編輯這個項目"
#: templates/rules/fragments/list.html:96
msgid "No rules"
msgstr "沒有規則"
+15 -8
View File
@@ -64,14 +64,21 @@
</div>
</td>
<td class="table-col-auto">
<a class="no-underline cursor-pointer"
role="button"
data-tippy-content="
{% if rule.active %}{% translate "Deactivate" %}{% else %}{% translate "Activate" %}{% endif %}"
hx-get="{% url 'transaction_rule_toggle_activity' transaction_rule_id=rule.id %}">
{% if rule.active %}<i class="fa-solid fa-toggle-on text-success"></i>{% else %}
<i class="fa-solid fa-toggle-off text-error"></i>{% endif %}
</a>
{% if not rule.owner or user == rule.owner %}
<a class="no-underline cursor-pointer"
role="button"
data-tippy-content="
{% if rule.active %}{% translate "Deactivate" %}{% else %}{% translate "Activate" %}{% endif %}"
hx-get="{% url 'transaction_rule_toggle_activity' transaction_rule_id=rule.id %}">
{% if rule.active %}<i class="fa-solid fa-toggle-on text-success"></i>{% else %}
<i class="fa-solid fa-toggle-off text-error"></i>{% endif %}
</a>
{% else %}
<span data-tippy-content="{% translate "Only the owner can change this" %}">
{% if rule.active %}<i class="fa-solid fa-toggle-on text-success opacity-50"></i>{% else %}
<i class="fa-solid fa-toggle-off text-error opacity-50"></i>{% endif %}
</span>
{% endif %}
</td>
<td class="table-col-auto text-center">
<div>{{ rule.order }}</div>
+23 -23
View File
@@ -1157,9 +1157,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.10.34",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.34.tgz",
"integrity": "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw==",
"version": "2.11.20",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz",
"integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==",
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.cjs"
@@ -1188,9 +1188,9 @@
}
},
"node_modules/browserslist": {
"version": "4.28.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
"version": "4.28.8",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",
"integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==",
"funding": [
{
"type": "opencollective",
@@ -1207,11 +1207,11 @@
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
"electron-to-chromium": "^1.5.328",
"node-releases": "^2.0.36",
"update-browserslist-db": "^1.2.3"
"baseline-browser-mapping": "^2.11.12",
"caniuse-lite": "^1.0.30001809",
"electron-to-chromium": "^1.5.402",
"node-releases": "^2.0.53",
"update-browserslist-db": "^1.3.0"
},
"bin": {
"browserslist": "cli.js"
@@ -1221,9 +1221,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001797",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz",
"integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==",
"version": "1.0.30001810",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
"integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
"funding": [
{
"type": "opencollective",
@@ -1339,9 +1339,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.368",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.368.tgz",
"integrity": "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==",
"version": "1.5.420",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz",
"integrity": "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA==",
"license": "ISC"
},
"node_modules/enhanced-resolve": {
@@ -1812,9 +1812,9 @@
"optional": true
},
"node_modules/node-releases": {
"version": "2.0.47",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz",
"integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==",
"version": "2.0.54",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz",
"integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -2069,9 +2069,9 @@
}
},
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz",
"integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==",
"funding": [
{
"type": "opencollective",
Generated
+9 -9
View File
@@ -580,14 +580,14 @@ wheels = [
[[package]]
name = "djangorestframework"
version = "3.17.1"
version = "3.17.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ca/d7/c016e69fac19ff8afdc89db9d31d9ae43ae031e4d1993b20aca179b8301a/djangorestframework-3.17.1.tar.gz", hash = "sha256:a6def5f447fe78ff853bff1d47a3c59bf38f5434b031780b351b0c73a62db1a5", size = 905742, upload-time = "2026-03-24T16:58:33.705Z" }
sdist = { url = "https://files.pythonhosted.org/packages/3b/35/c96055e700fdff25da3a7b7756cfd1d4dc54f38b9bc6d6c5e19e3a0fdc20/djangorestframework-3.17.2.tar.gz", hash = "sha256:89ed713b6dc83e1539f214b7d10808ae19bb8511004beba886225da6d5c9dafa", size = 906683, upload-time = "2026-08-05T07:47:22.5Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/e1/2c516bdc83652b1a60c6119366ac2c0607b479ed05cd6093f916ca8928f8/djangorestframework-3.17.1-py3-none-any.whl", hash = "sha256:c3c74dd3e83a5a3efc37b3c18d92bd6f86a6791c7b7d4dff62bb068500e76457", size = 898844, upload-time = "2026-03-24T16:58:31.845Z" },
{ url = "https://files.pythonhosted.org/packages/a2/46/c14108e400b208c394325eb63fbae06c81341b6447fa1a6f9da718b17fe7/djangorestframework-3.17.2-py3-none-any.whl", hash = "sha256:cb0546a7415d5b46c04e0f4fe0a54b2109f4fdd5e83ca773c8c6183a6493d042", size = 899109, upload-time = "2026-08-05T07:47:20.853Z" },
]
[[package]]
@@ -709,11 +709,11 @@ wheels = [
[[package]]
name = "mistune"
version = "3.3.0"
version = "3.3.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/84/9c/1939635275ec7258e2b43b00dafabc36d89ad11aa7838d375dc1b0e561cb/mistune-3.3.0.tar.gz", hash = "sha256:3074ec4c61b384abe725128e4dcbb483f5a09cc4632012505cdee655d3a113b9", size = 110936, upload-time = "2026-06-21T13:11:39.458Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7b/a5/2dab368d6950e6808904dec98f54c7e726ee7be4a0c6afe00e6e011bd52d/mistune-3.3.3.tar.gz", hash = "sha256:c4c6c0c840b8637a2e9b8b6d607eb7c8f00888bf14c754409bcd339e848c2477", size = 115363, upload-time = "2026-07-09T06:18:05.268Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/76/b90f9d48d43fbd80a79a20d3eab2e5109859c7a56dc663b23187385898f3/mistune-3.3.0-py3-none-any.whl", hash = "sha256:a758e578acda49d8195f9a860b132dae2cf7bf409381393b1c4e6e489a65397b", size = 61250, upload-time = "2026-06-21T13:11:37.938Z" },
{ url = "https://files.pythonhosted.org/packages/89/70/b1e4737b84163db5bb1dfde6f216dbfbf32783330a9989c965e121172830/mistune-3.3.3-py3-none-any.whl", hash = "sha256:99de1585e42dcbd826faa9e11a202727a5e202e4e4722a4c69ac1ff615793dd7", size = 63569, upload-time = "2026-07-09T06:18:03.839Z" },
]
[[package]]
@@ -1532,11 +1532,11 @@ wheels = [
[[package]]
name = "sqlparse"
version = "0.5.5"
version = "0.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5f/d3/3f06a1006f2261d1342aefb3c71eed02f5d4ca5bdbecd86ebc12ad38306e/sqlparse-0.6.0.tar.gz", hash = "sha256:113c35c75365ab9cc9c7231d68c6428fb11c085fc8e9eb1ad659b7ddbf6cd2b9", size = 178477, upload-time = "2026-08-13T19:16:06.396Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" },
{ url = "https://files.pythonhosted.org/packages/d9/50/f00935da0ec7cbf325f8dc4f772ae46fbc7b672dd62876e73f0a94adda57/sqlparse-0.6.0-py3-none-any.whl", hash = "sha256:b861c0288ce2fa56209a9a6412d2e066ac664b3873b89c26c9d8415e8e32996f", size = 50070, upload-time = "2026-08-13T19:16:04.062Z" },
]
[[package]]