Compare commits

...

36 Commits

Author SHA1 Message Date
Herculino Trotta
b62122ed23 Merge pull request #225
fix(app): rename DEMO_MODE variable to DEMO for simplicity
2025-03-31 02:45:28 -03:00
Herculino Trotta
f74946cba7 fix(app): rename DEMO_MODE variable to DEMO for simplicity 2025-03-31 02:45:09 -03:00
Herculino Trotta
585652064a locale(Portuguese (Brazil)): update translation
Currently translated at 100.0% (617 of 617 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/pt_BR/
2025-03-31 05:38:18 +00:00
eitchtee
ea6f61d5e4 chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2025-03-31 05:30:21 +00:00
Herculino Trotta
e986f7d802 Merge pull request #224
feat: add demo mode and allow for automatic admin creation from env variables
2025-03-31 02:29:44 -03:00
Herculino Trotta
26b218ae51 feat(app): disable API when demo mode is enabled 2025-03-31 02:28:48 -03:00
Herculino Trotta
19f0bc1034 feat(app): show current user e-mail on user menu 2025-03-31 02:28:33 -03:00
Herculino Trotta
47d34f3c27 feat(app): add a demo mode 2025-03-31 02:14:00 -03:00
Herculino Trotta
046e02d506 feat(app): add environment variables to automatically create superuser on startup 2025-03-31 02:11:13 -03:00
valentin-p
92c7a29b6a locale(German): update translation
Currently translated at 99.8% (610 of 611 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/de/
2025-03-30 15:07:17 +00:00
valentin-p
d95e5f71cc locale((French)): added translation using Weblate 2025-03-30 13:40:51 +00:00
Herculino Trotta
992c518dab Merge pull request #222
fix(net-worth): non-used currencies showing up on charts
2025-03-23 01:35:14 -03:00
Herculino Trotta
29aa1c9d2b fix(net-worth): non-used currencies showing up on charts 2025-03-23 01:34:53 -03:00
Herculino Trotta
1b3b7a583d Merge pull request #220 from eitchtee/dependabot/pip/gunicorn-23.0.0
chore(deps): bump gunicorn from 22.0.0 to 23.0.0
2025-03-22 01:02:43 -03:00
dependabot[bot]
2d22f961ad chore(deps): bump gunicorn from 22.0.0 to 23.0.0
Bumps [gunicorn](https://github.com/benoitc/gunicorn) from 22.0.0 to 23.0.0.
- [Release notes](https://github.com/benoitc/gunicorn/releases)
- [Commits](https://github.com/benoitc/gunicorn/compare/22.0.0...23.0.0)

---
updated-dependencies:
- dependency-name: gunicorn
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-22 04:01:48 +00:00
Herculino Trotta
71551d7651 Merge pull request #219
fix(insights:category-explorer): category field not loading available categories correctly
2025-03-15 11:37:46 -03:00
Herculino Trotta
62d58d1be3 fix(insights:category-explorer): category field not loading available categories correctly 2025-03-15 11:37:28 -03:00
Herculino Trotta
21917437f2 Merge pull request #218
fix(tools:currency-converter): currency list displaying oldest result instead of newest
2025-03-13 22:18:21 -03:00
Herculino Trotta
59acb14d05 fix(tools:currency-converter): currency list displaying oldest result instead of newest 2025-03-13 22:18:02 -03:00
Dimitri Decrock
050f794f2b locale(Dutch): update translation
Currently translated at 100.0% (611 of 611 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/nl/
2025-03-13 07:05:35 +00:00
Schmitz Schmitz
a5958c0937 locale(German): update translation
Currently translated at 99.6% (609 of 611 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/de/
2025-03-10 09:05:35 +00:00
Herculino Trotta
ee73ada5ae Merge pull request #215
fix: missing selection when updating transactions in a transaction list
2025-03-09 20:22:18 -03:00
Herculino Trotta
736a116685 fix: missing selection when updating transactions in a transaction list 2025-03-09 20:21:48 -03:00
Herculino Trotta
6c03c7b4eb locale(Portuguese (Brazil)): update translation
Currently translated at 100.0% (611 of 611 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/pt_BR/
2025-03-09 23:10:59 +00:00
eitchtee
960e537709 chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2025-03-09 21:56:43 +00:00
Herculino Trotta
e32285ce75 Merge pull request #214
feat: alphabetically order most models by default
2025-03-09 18:56:10 -03:00
Herculino Trotta
73e8fdbf04 feat: alphabetically order most models by default
#207
2025-03-09 18:55:29 -03:00
Herculino Trotta
d4c15da051 Merge pull request #212
feat(monthly_overview): preserve filter between month changes
2025-03-09 18:46:38 -03:00
Herculino Trotta
187b3174d2 feat(monthly_overview): preserve filter between month changes
#208
2025-03-09 18:45:55 -03:00
eitchtee
c90ea7ef16 chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2025-03-09 20:55:47 +00:00
Herculino Trotta
54713ecfe2 Merge pull request #211
fix(transactions:transfer): remove required description field
2025-03-09 17:55:15 -03:00
Herculino Trotta
cf693aa0c3 fix(transactions:transfer): remove required description field
#209
2025-03-09 17:54:16 -03:00
eitchtee
3580f1b132 chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2025-03-09 20:51:39 +00:00
Herculino Trotta
febd9a8ae7 Merge pull request #210
feat(transactions): add option for removing Recurring/Installment descriptions and notes from generated transactions
2025-03-09 17:50:59 -03:00
Herculino Trotta
3809f82b60 feat(transactions): add option for removing Recurring/Installment descriptions and notes from generated transactions
#209
2025-03-09 17:50:27 -03:00
Dimitri Decrock
3c6b52462a locale(Dutch): update translation
Currently translated at 100.0% (609 of 609 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/nl/
2025-03-09 12:05:35 +00:00
42 changed files with 4394 additions and 790 deletions

View File

@@ -10,6 +10,11 @@ SECRET_KEY=<GENERATE A SAFE SECRET KEY AND PLACE IT HERE>
DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
OUTBOUND_PORT=9005
# Uncomment these variables to automatically create an admin account using these credentials on startup.
# After your first successfull login you can remove these variables from your file for safety reasons.
#ADMIN_EMAIL=<ENTER YOUR EMAIL>
#ADMIN_PASSWORD=<YOUR SAFE PASSWORD>
SQL_DATABASE=wygiwyh
SQL_USER=wygiwyh
SQL_PASSWORD=<INSERT A SAFE PASSWORD HERE>

View File

@@ -76,7 +76,7 @@ $ nano .env # or any other editor you want to use
# Run the app
$ docker compose up -d
# Create the first admin account
# Create the first admin account. This isn't required if you set the enviroment variables: ADMIN_EMAIL and ADMIN_PASSWORD.
$ docker compose exec -it web python manage.py createsuperuser
```
@@ -117,7 +117,7 @@ To create the first user, open the container's console using Unraid's UI, by cli
|-------------------------------|-------------|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| DJANGO_ALLOWED_HOSTS | string | localhost 127.0.0.1 | A list of space separated domains and IPs representing the host/domain names that WYGIWYH site can serve. [Click here](https://docs.djangoproject.com/en/5.1/ref/settings/#allowed-hosts) for more details |
| HTTPS_ENABLED | true\|false | false | Whether to use secure cookies. If this is set to true, the cookie will be marked as “secure”, which means browsers may ensure that the cookie is only sent under an HTTPS connection |
| URL | string | http://localhost http://127.0.0.1 | A list of space separated domains and IPs (with the protocol) representing the trusted origins for unsafe requests (e.g. POST). [Click here](https://docs.djangoproject.com/en/5.1/ref/settings/#csrf-trusted-origins ) for more details |
| URL | string | http://localhost http://127.0.0.1 | A list of space separated domains and IPs (with the protocol) representing the trusted origins for unsafe requests (e.g. POST). [Click here](https://docs.djangoproject.com/en/5.1/ref/settings/#csrf-trusted-origins ) for more details |
| SECRET_KEY | string | "" | This is used to provide cryptographic signing, and should be set to a unique, unpredictable value. |
| DEBUG | true\|false | false | Turns DEBUG mode on or off, this is useful to gather more data about possible errors you're having. Don't use in production. |
| SQL_DATABASE | string | None *required | The name of your postgres database |
@@ -129,6 +129,9 @@ To create the first user, open the container's console using Unraid's UI, by cli
| ENABLE_SOFT_DELETE | true\|false | false | Whether to enable transactions soft delete, if enabled, deleted transactions will remain in the database. Useful for imports and avoiding duplicate entries. |
| KEEP_DELETED_TRANSACTIONS_FOR | int | 365 | Time in days to keep soft deleted transactions for. If 0, will keep all transactions indefinitely. Only works if ENABLE_SOFT_DELETE is true. |
| TASK_WORKERS | int | 1 | How many workers to have for async tasks. One should be enough for most use cases |
| DEMO | true\|false | false | If demo mode is enabled. |
| ADMIN_EMAIL | string | None | Automatically creates an admin account with this email. Must have `ADMIN_PASSWORD` also set. |
| ADMIN_PASSWORD | string | None | Automatically creates an admin account with this password. Must have `ADMIN_EMAIL` also set. |
# How it works

View File

@@ -261,7 +261,10 @@ if DEBUG:
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.DjangoModelPermissions"],
"DEFAULT_PERMISSION_CLASSES": [
"apps.api.permissions.NotInDemoMode",
"rest_framework.permissions.DjangoModelPermissions",
],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
@@ -394,3 +397,4 @@ PWA_SERVICE_WORKER_PATH = BASE_DIR / "templates" / "pwa" / "serviceworker.js"
ENABLE_SOFT_DELETE = os.getenv("ENABLE_SOFT_DELETE", "false").lower() == "true"
KEEP_DELETED_TRANSACTIONS_FOR = int(os.getenv("KEEP_DELETED_ENTRIES_FOR", "365"))
APP_VERSION = os.getenv("APP_VERSION", "unknown")
DEMO = os.getenv("DEMO", "false").lower() == "true"

View File

@@ -0,0 +1,21 @@
# Generated by Django 5.1.7 on 2025-03-09 21:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0013_alter_account_visibility_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='account',
options={'ordering': ['name', 'id'], 'verbose_name': 'Account', 'verbose_name_plural': 'Accounts'},
),
migrations.AlterModelOptions(
name='accountgroup',
options={'ordering': ['name', 'id'], 'verbose_name': 'Account Group', 'verbose_name_plural': 'Account Groups'},
),
]

View File

@@ -19,6 +19,7 @@ class AccountGroup(SharedObject):
verbose_name_plural = _("Account Groups")
db_table = "account_groups"
unique_together = (("owner", "name"),)
ordering = ["name", "id"]
def __str__(self):
return self.name
@@ -69,6 +70,7 @@ class Account(SharedObject):
verbose_name = _("Account")
verbose_name_plural = _("Accounts")
unique_together = (("owner", "name"),)
ordering = ["name", "id"]
def __str__(self):
return self.name

View File

@@ -0,0 +1,10 @@
from rest_framework.permissions import BasePermission
from django.conf import settings
class NotInDemoMode(BasePermission):
def has_permission(self, request, view):
if settings.DEMO and not request.user.is_superuser:
return False
else:
return True

View File

@@ -0,0 +1,15 @@
from functools import wraps
from django.conf import settings
from django.core.exceptions import PermissionDenied
def disabled_on_demo(view):
@wraps(view)
def _view(request, *args, **kwargs):
if settings.DEMO and not request.user.is_superuser:
raise PermissionDenied
return view(request, *args, **kwargs)
return _view

View File

View File

@@ -0,0 +1,137 @@
import os
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth import get_user_model
from django.conf import settings
from django.db import IntegrityError
# Get the custom User model if defined, otherwise the default User model
User = get_user_model()
class Command(BaseCommand):
help = (
"Creates a superuser from environment variables (ADMIN_EMAIL, ADMIN_PASSWORD) "
"and optionally creates a demo user (demo@demo.com) if settings.DEMO is True."
)
def handle(self, *args, **options):
self.stdout.write("Starting user setup...")
# --- Create Superuser ---
admin_email = os.environ.get("ADMIN_EMAIL")
admin_password = os.environ.get("ADMIN_PASSWORD")
if admin_email and admin_password:
self.stdout.write(f"Attempting to create superuser: {admin_email}")
# Use email as username for simplicity, requires USERNAME_FIELD='email'
# or adapt if your USERNAME_FIELD is different.
# If USERNAME_FIELD is 'username', you might need ADMIN_USERNAME env var.
username_field = User.USERNAME_FIELD # Get the actual username field name
# Check if the user already exists by email or username
user_exists_kwargs = {"email": admin_email}
if username_field != "email":
# Assume username should also be the email if not explicitly provided
user_exists_kwargs[username_field] = admin_email
if User.objects.filter(**user_exists_kwargs).exists():
self.stdout.write(
self.style.WARNING(
f"Superuser with email '{admin_email}' (or corresponding username) already exists. Skipping creation."
)
)
else:
try:
create_kwargs = {
username_field: admin_email, # Use email as username by default
"email": admin_email,
"password": admin_password,
}
User.objects.create_superuser(**create_kwargs)
self.stdout.write(
self.style.SUCCESS(
f"Superuser '{admin_email}' created successfully."
)
)
except IntegrityError as e:
self.stdout.write(
self.style.ERROR(
f"Failed to create superuser '{admin_email}'. IntegrityError: {e}"
)
)
except Exception as e:
self.stdout.write(
self.style.ERROR(
f"An unexpected error occurred creating superuser '{admin_email}': {e}"
)
)
else:
self.stdout.write(
self.style.NOTICE(
"ADMIN_EMAIL or ADMIN_PASSWORD environment variables not set. Skipping superuser creation."
)
)
self.stdout.write("---") # Separator
# --- Create Demo User ---
# Use getattr to safely check for the DEMO setting, default to False if not present
create_demo_user = getattr(settings, "DEMO", False)
if create_demo_user:
demo_email = "demo@demo.com"
demo_password = (
"wygiwyhdemo" # Consider making this an env var too for security
)
demo_username = demo_email # Using email as username for consistency
self.stdout.write(
f"DEMO setting is True. Attempting to create demo user: {demo_email}"
)
username_field = User.USERNAME_FIELD # Get the actual username field name
# Check if the user already exists by email or username
user_exists_kwargs = {"email": demo_email}
if username_field != "email":
user_exists_kwargs[username_field] = demo_username
if User.objects.filter(**user_exists_kwargs).exists():
self.stdout.write(
self.style.WARNING(
f"Demo user with email '{demo_email}' (or corresponding username) already exists. Skipping creation."
)
)
else:
try:
create_kwargs = {
username_field: demo_username,
"email": demo_email,
"password": demo_password,
}
User.objects.create_user(**create_kwargs)
self.stdout.write(
self.style.SUCCESS(
f"Demo user '{demo_email}' created successfully."
)
)
except IntegrityError as e:
self.stdout.write(
self.style.ERROR(
f"Failed to create demo user '{demo_email}'. IntegrityError: {e}"
)
)
except Exception as e:
self.stdout.write(
self.style.ERROR(
f"An unexpected error occurred creating demo user '{demo_email}': {e}"
)
)
else:
self.stdout.write(
self.style.NOTICE(
"DEMO setting is not True (or not set). Skipping demo user creation."
)
)
self.stdout.write(self.style.SUCCESS("User setup command finished."))

View File

@@ -1,7 +1,9 @@
import logging
from asgiref.sync import sync_to_async
from django.conf import settings
from django.core import management
from django.db import DEFAULT_DB_ALIAS
from procrastinate import builtin_tasks
from procrastinate.contrib.django import app
@@ -40,3 +42,39 @@ async def remove_expired_sessions(timestamp=None):
"Error while executing 'remove_expired_sessions' task",
exc_info=True,
)
@app.periodic(cron="0 6 * * *")
@app.task(name="reset_demo_data")
def reset_demo_data():
"""
Wipes the database and loads fresh demo data if DEMO mode is active.
Runs daily at 6:00 AM.
"""
if not settings.DEMO:
return # Exit if not in demo mode
logger.info("Demo mode active. Starting daily data reset...")
try:
# 1. Flush the database (wipe all data)
logger.info("Flushing the database...")
# Using --noinput prevents prompts. Specify database if not default.
management.call_command(
"flush", "--noinput", database=DEFAULT_DB_ALIAS, verbosity=1
)
logger.info("Database flushed successfully.")
# 2. Load data from the fixture
fixture_name = "fixtures/demo_data.json"
logger.info(f"Loading data from fixture: {fixture_name}...")
management.call_command(
"loaddata", fixture_name, database=DEFAULT_DB_ALIAS, verbosity=1
)
logger.info(f"Data loaded successfully from {fixture_name}.")
logger.info("Daily demo data reset completed.")
except Exception as e:
logger.exception(f"Error during daily demo data reset: {e}")
raise

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.1.7 on 2025-03-09 21:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('currencies', '0013_alter_exchangerateservice_service_type'),
]
operations = [
migrations.AlterModelOptions(
name='currency',
options={'ordering': ['name', 'id'], 'verbose_name': 'Currency', 'verbose_name_plural': 'Currencies'},
),
]

View File

@@ -38,6 +38,7 @@ class Currency(models.Model):
class Meta:
verbose_name = _("Currency")
verbose_name_plural = _("Currencies")
ordering = ["name", "id"]
def clean(self):
super().clean()

View File

@@ -11,9 +11,11 @@ from apps.common.decorators.htmx import only_htmx
from apps.currencies.forms import ExchangeRateForm, ExchangeRateServiceForm
from apps.currencies.models import ExchangeRate, ExchangeRateService
from apps.currencies.tasks import manual_fetch_exchange_rates
from apps.common.decorators.demo import disabled_on_demo
@login_required
@disabled_on_demo
@require_http_methods(["GET"])
def exchange_rates_services_index(request):
return render(
@@ -24,6 +26,7 @@ def exchange_rates_services_index(request):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["GET"])
def exchange_rates_services_list(request):
services = ExchangeRateService.objects.all()
@@ -37,6 +40,7 @@ def exchange_rates_services_list(request):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def exchange_rate_service_add(request):
if request.method == "POST":
@@ -63,6 +67,7 @@ def exchange_rate_service_add(request):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def exchange_rate_service_edit(request, pk):
service = get_object_or_404(ExchangeRateService, id=pk)
@@ -91,6 +96,7 @@ def exchange_rate_service_edit(request, pk):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["DELETE"])
def exchange_rate_service_delete(request, pk):
service = get_object_or_404(ExchangeRateService, id=pk)
@@ -109,6 +115,7 @@ def exchange_rate_service_delete(request, pk):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["GET"])
def exchange_rate_service_force_fetch(request):
manual_fetch_exchange_rates.defer()

View File

@@ -41,11 +41,13 @@ from apps.export_app.resources.transactions import (
RecurringTransactionResource,
)
from apps.export_app.resources.users import UserResource
from apps.common.decorators.demo import disabled_on_demo
logger = logging.getLogger()
@login_required
@disabled_on_demo
@user_passes_test(lambda u: u.is_superuser)
@require_http_methods(["GET"])
def export_index(request):
@@ -53,6 +55,7 @@ def export_index(request):
@login_required
@disabled_on_demo
@user_passes_test(lambda u: u.is_superuser)
@require_http_methods(["GET", "POST"])
def export_form(request):
@@ -182,6 +185,7 @@ def export_form(request):
@only_htmx
@login_required
@disabled_on_demo
@user_passes_test(lambda u: u.is_superuser)
@require_http_methods(["GET", "POST"])
def import_form(request):

View File

@@ -13,9 +13,11 @@ from apps.import_app.forms import ImportRunFileUploadForm, ImportProfileForm
from apps.import_app.models import ImportRun, ImportProfile
from apps.import_app.services import PresetService
from apps.import_app.tasks import process_import
from apps.common.decorators.demo import disabled_on_demo
@login_required
@disabled_on_demo
@require_http_methods(["GET"])
def import_presets_list(request):
presets = PresetService.get_all_presets()
@@ -27,6 +29,7 @@ def import_presets_list(request):
@login_required
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def import_profile_index(request):
return render(
@@ -37,6 +40,7 @@ def import_profile_index(request):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def import_profile_list(request):
profiles = ImportProfile.objects.all()
@@ -50,6 +54,7 @@ def import_profile_list(request):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def import_profile_add(request):
message = request.POST.get("message", None)
@@ -85,6 +90,7 @@ def import_profile_add(request):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def import_profile_edit(request, profile_id):
profile = get_object_or_404(ImportProfile, id=profile_id)
@@ -114,6 +120,7 @@ def import_profile_edit(request, profile_id):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["DELETE"])
def import_profile_delete(request, profile_id):
profile = ImportProfile.objects.get(id=profile_id)
@@ -132,6 +139,7 @@ def import_profile_delete(request, profile_id):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def import_runs_list(request, profile_id):
profile = ImportProfile.objects.get(id=profile_id)
@@ -147,6 +155,7 @@ def import_runs_list(request, profile_id):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def import_run_log(request, profile_id, run_id):
run = ImportRun.objects.get(profile__id=profile_id, id=run_id)
@@ -160,6 +169,7 @@ def import_run_log(request, profile_id, run_id):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def import_run_add(request, profile_id):
profile = ImportProfile.objects.get(id=profile_id)
@@ -202,6 +212,7 @@ def import_run_add(request, profile_id):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["DELETE"])
def import_run_delete(request, profile_id, run_id):
run = ImportRun.objects.get(profile__id=profile_id, id=run_id)

View File

@@ -117,13 +117,15 @@ class CategoryForm(forms.Form):
required=False,
label=_("Category"),
empty_label=_("Uncategorized"),
queryset=TransactionCategory.objects.filter(active=True),
queryset=TransactionCategory.objects.all(),
widget=TomSelect(clear_button=True),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["category"].queryset = TransactionCategory.objects.all()
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.disable_csrf = True

View File

@@ -40,8 +40,8 @@ def get_currency_exchange_map(date=None) -> Dict[str, dict]:
date_diff=Func(Extract(F("date") - Value(date), "epoch"), function="ABS"),
effective_rate=F("rate"),
)
.order_by("from_currency", "to_currency", "date_diff")
.distinct("from_currency", "to_currency")
.order_by("from_currency", "to_currency", "-date_diff")
.distinct()
)
# Initialize the result dictionary

View File

@@ -2,25 +2,38 @@ from collections import OrderedDict, defaultdict
from decimal import Decimal
from dateutil.relativedelta import relativedelta
from django.db.models import Sum, Min, Max, Case, When, F, Value, DecimalField
from django.db.models import Sum, Min, Max, Case, When, F, Value, DecimalField, Q
from django.db.models.functions import TruncMonth
from django.template.defaultfilters import date as date_filter
from django.utils import timezone
from apps.accounts.models import Account
from apps.common.middleware.thread_local import get_current_user
from apps.currencies.models import Currency
from apps.transactions.models import Transaction
def calculate_historical_currency_net_worth(is_paid=True):
transactions_params = {**{k: v for k, v in [("is_paid", True)] if is_paid}}
def calculate_historical_currency_net_worth(queryset):
# Get all currencies and date range in a single query
aggregates = Transaction.objects.aggregate(
aggregates = queryset.aggregate(
min_date=Min("reference_date"),
max_date=Max("reference_date"),
)
currencies = list(Currency.objects.values_list("name", flat=True))
user = get_current_user()
currencies = list(
Currency.objects.filter(
Q(accounts__visibility="public")
| Q(accounts__owner=user)
| Q(accounts__shared_with=user)
| Q(accounts__visibility="private", accounts__owner=None),
accounts__is_archived=False,
accounts__isnull=False,
)
.values_list("name", flat=True)
.distinct()
)
if not aggregates.get("min_date"):
start_date = timezone.localdate(timezone.now())
@@ -34,8 +47,7 @@ def calculate_historical_currency_net_worth(is_paid=True):
# Calculate cumulative balances for each account, currency, and month
cumulative_balances = (
Transaction.objects.filter(**transactions_params)
.annotate(month=TruncMonth("reference_date"))
queryset.annotate(month=TruncMonth("reference_date"))
.values("account__currency__name", "month")
.annotate(
balance=Sum(
@@ -94,15 +106,14 @@ def calculate_historical_currency_net_worth(is_paid=True):
return historical_net_worth
def calculate_historical_account_balance(is_paid=True):
transactions_params = {**{k: v for k, v in [("is_paid", True)] if is_paid}}
def calculate_historical_account_balance(queryset):
# Get all accounts
accounts = Account.objects.filter(
is_archived=False,
)
# Get the date range
date_range = Transaction.objects.filter(**transactions_params).aggregate(
date_range = queryset.aggregate(
min_date=Min("reference_date"), max_date=Max("reference_date")
)
@@ -118,8 +129,7 @@ def calculate_historical_account_balance(is_paid=True):
# Calculate balances for each account and month
balances = (
Transaction.objects.filter(**transactions_params)
.annotate(month=TruncMonth("reference_date"))
queryset.annotate(month=TruncMonth("reference_date"))
.values("account", "month")
.annotate(
balance=Sum(

View File

@@ -38,7 +38,9 @@ def net_worth_current(request):
transactions_queryset=transactions_account_queryset
)
historical_currency_net_worth = calculate_historical_currency_net_worth()
historical_currency_net_worth = calculate_historical_currency_net_worth(
queryset=transactions_currency_queryset
)
labels = (
list(historical_currency_net_worth.keys())
@@ -71,7 +73,9 @@ def net_worth_current(request):
chart_data_currency_json = json.dumps(chart_data_currency, cls=DjangoJSONEncoder)
historical_account_balance = calculate_historical_account_balance()
historical_account_balance = calculate_historical_account_balance(
queryset=transactions_account_queryset
)
labels = (
list(historical_account_balance.keys()) if historical_account_balance else []
@@ -140,7 +144,7 @@ def net_worth_projected(request):
)
historical_currency_net_worth = calculate_historical_currency_net_worth(
is_paid=False
queryset=transactions_currency_queryset
)
labels = (
@@ -174,7 +178,9 @@ def net_worth_projected(request):
chart_data_currency_json = json.dumps(chart_data_currency, cls=DjangoJSONEncoder)
historical_account_balance = calculate_historical_account_balance(is_paid=False)
historical_account_balance = calculate_historical_account_balance(
queryset=transactions_account_queryset
)
labels = (
list(historical_account_balance.keys()) if historical_account_balance else []

View File

@@ -18,9 +18,11 @@ from apps.rules.models import (
)
from apps.common.models import SharedObject
from apps.common.forms import SharedObjectForm
from apps.common.decorators.demo import disabled_on_demo
@login_required
@disabled_on_demo
@require_http_methods(["GET"])
def rules_index(request):
return render(
@@ -31,6 +33,7 @@ def rules_index(request):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["GET"])
def rules_list(request):
transaction_rules = TransactionRule.objects.all().order_by("id")
@@ -43,6 +46,7 @@ def rules_list(request):
@only_htmx
@login_required
@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)
@@ -65,6 +69,7 @@ def transaction_rule_toggle_activity(request, transaction_rule_id, **kwargs):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def transaction_rule_add(request, **kwargs):
if request.method == "POST":
@@ -91,6 +96,7 @@ def transaction_rule_add(request, **kwargs):
@only_htmx
@login_required
@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)
@@ -129,6 +135,7 @@ def transaction_rule_edit(request, transaction_rule_id):
@only_htmx
@login_required
@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)
@@ -142,6 +149,7 @@ def transaction_rule_view(request, transaction_rule_id):
@only_htmx
@login_required
@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)
@@ -166,6 +174,7 @@ def transaction_rule_delete(request, transaction_rule_id):
@only_htmx
@login_required
@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)
@@ -187,6 +196,7 @@ def transaction_rule_take_ownership(request, transaction_rule_id):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["GET", "POST"])
def transaction_rule_share(request, pk):
obj = get_object_or_404(TransactionRule, id=pk)
@@ -225,6 +235,7 @@ def transaction_rule_share(request, pk):
@only_htmx
@login_required
@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)
@@ -252,6 +263,7 @@ def transaction_rule_action_add(request, transaction_rule_id):
@only_htmx
@login_required
@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(
@@ -289,6 +301,7 @@ def transaction_rule_action_edit(request, transaction_rule_action_id):
@only_htmx
@login_required
@disabled_on_demo
@require_http_methods(["DELETE"])
def transaction_rule_action_delete(request, transaction_rule_action_id):
transaction_rule_action = get_object_or_404(
@@ -309,6 +322,7 @@ def transaction_rule_action_delete(request, transaction_rule_action_id):
@only_htmx
@login_required
@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)
@@ -340,6 +354,7 @@ def update_or_create_transaction_rule_action_add(request, transaction_rule_id):
@only_htmx
@login_required
@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)
@@ -374,6 +389,7 @@ def update_or_create_transaction_rule_action_edit(request, pk):
@only_htmx
@login_required
@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)

View File

@@ -334,7 +334,9 @@ class TransferForm(forms.Form):
widget=AirMonthYearPickerInput(), label=_("Reference Date"), required=False
)
description = forms.CharField(max_length=500, label=_("Description"))
description = forms.CharField(
max_length=500, label=_("Description"), required=False
)
notes = forms.CharField(
required=False,
widget=forms.Textarea(
@@ -538,6 +540,8 @@ class InstallmentPlanForm(forms.ModelForm):
"notes",
"installment_start",
"entities",
"add_description_to_transaction",
"add_notes_to_transaction",
]
widgets = {
"account": TomSelect(),
@@ -593,7 +597,9 @@ class InstallmentPlanForm(forms.ModelForm):
css_class="form-row",
),
"description",
Switch("add_description_to_transaction"),
"notes",
Switch("add_notes_to_transaction"),
Row(
Column("number_of_installments", css_class="form-group col-md-6 mb-0"),
Column("installment_start", css_class="form-group col-md-6 mb-0"),
@@ -782,6 +788,7 @@ class RecurringTransactionForm(forms.ModelForm):
"type",
"amount",
"description",
"add_description_to_transaction",
"category",
"tags",
"start_date",
@@ -790,6 +797,7 @@ class RecurringTransactionForm(forms.ModelForm):
"recurrence_type",
"recurrence_interval",
"notes",
"add_notes_to_transaction",
"entities",
]
widgets = {
@@ -850,6 +858,7 @@ class RecurringTransactionForm(forms.ModelForm):
css_class="form-row",
),
"description",
Switch("add_description_to_transaction"),
"amount",
Row(
Column("category", css_class="form-group col-md-6 mb-0"),
@@ -857,6 +866,7 @@ class RecurringTransactionForm(forms.ModelForm):
css_class="form-row",
),
"notes",
Switch("add_notes_to_transaction"),
Row(
Column("start_date", css_class="form-group col-md-6 mb-0"),
Column("reference_date", css_class="form-group col-md-6 mb-0"),

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.1.7 on 2025-03-09 20:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('transactions', '0040_alter_transaction_unique_together_and_more'),
]
operations = [
migrations.AddField(
model_name='installmentplan',
name='add_description_to_transaction',
field=models.BooleanField(default=True, verbose_name='Add description to transactions'),
),
migrations.AddField(
model_name='installmentplan',
name='add_notes_to_transaction',
field=models.BooleanField(default=True, verbose_name='Add notes to transactions'),
),
migrations.AddField(
model_name='recurringtransaction',
name='add_description_to_transaction',
field=models.BooleanField(default=True, verbose_name='Add description to transactions'),
),
migrations.AddField(
model_name='recurringtransaction',
name='add_notes_to_transaction',
field=models.BooleanField(default=True, verbose_name='Add notes to transactions'),
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 5.1.7 on 2025-03-09 21:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('transactions', '0041_installmentplan_add_description_to_transaction_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='transactioncategory',
options={'ordering': ['name', 'id'], 'verbose_name': 'Transaction Category', 'verbose_name_plural': 'Transaction Categories'},
),
migrations.AlterModelOptions(
name='transactionentity',
options={'ordering': ['name', 'id'], 'verbose_name': 'Entity', 'verbose_name_plural': 'Entities'},
),
migrations.AlterModelOptions(
name='transactiontag',
options={'ordering': ['name', 'id'], 'verbose_name': 'Transaction Tags', 'verbose_name_plural': 'Transaction Tags'},
),
]

View File

@@ -213,6 +213,7 @@ class TransactionCategory(SharedObject):
verbose_name_plural = _("Transaction Categories")
db_table = "t_categories"
unique_together = (("owner", "name"),)
ordering = ["name", "id"]
def __str__(self):
return self.name
@@ -236,6 +237,7 @@ class TransactionTag(SharedObject):
verbose_name_plural = _("Transaction Tags")
db_table = "tags"
unique_together = (("owner", "name"),)
ordering = ["name", "id"]
def __str__(self):
return self.name
@@ -259,6 +261,7 @@ class TransactionEntity(SharedObject):
verbose_name_plural = _("Entities")
db_table = "entities"
unique_together = (("owner", "name"),)
ordering = ["name", "id"]
def __str__(self):
return self.name
@@ -493,6 +496,13 @@ class InstallmentPlan(models.Model):
notes = models.TextField(blank=True, verbose_name=_("Notes"))
add_description_to_transaction = models.BooleanField(
default=True, verbose_name=_("Add description to transactions")
)
add_notes_to_transaction = models.BooleanField(
default=True, verbose_name=_("Add notes to transactions")
)
all_objects = models.Manager() # Unfiltered manager
objects = GenericAccountOwnerManager() # Default filtered manager
@@ -557,11 +567,13 @@ class InstallmentPlan(models.Model):
is_paid=False,
reference_date=transaction_reference_date,
amount=self.installment_amount,
description=self.description,
description=(
self.description if self.add_description_to_transaction else ""
),
category=self.category,
installment_plan=self,
installment_id=i,
notes=self.notes,
notes=self.notes if self.add_notes_to_transaction else "",
)
new_transaction.tags.set(self.tags.all())
new_transaction.entities.set(self.entities.all())
@@ -594,9 +606,13 @@ class InstallmentPlan(models.Model):
existing_transaction.type = self.type
existing_transaction.date = transaction_date
existing_transaction.reference_date = transaction_reference_date
existing_transaction.description = self.description
existing_transaction.description = (
self.description if self.add_description_to_transaction else ""
)
existing_transaction.category = self.category
existing_transaction.notes = self.notes
existing_transaction.notes = (
self.notes if self.add_notes_to_transaction else ""
)
if (
not existing_transaction.is_paid
@@ -617,11 +633,13 @@ class InstallmentPlan(models.Model):
is_paid=False,
reference_date=transaction_reference_date,
amount=self.installment_amount,
description=self.description,
description=(
self.description if self.add_description_to_transaction else ""
),
category=self.category,
installment_plan=self,
installment_id=i,
notes=self.notes,
notes=self.notes if self.add_notes_to_transaction else "",
)
new_transaction.tags.set(self.tags.all())
new_transaction.entities.set(self.entities.all())
@@ -697,6 +715,13 @@ class RecurringTransaction(models.Model):
verbose_name=_("Last Generated Reference Date"), null=True, blank=True
)
add_description_to_transaction = models.BooleanField(
default=True, verbose_name=_("Add description to transactions")
)
add_notes_to_transaction = models.BooleanField(
default=True, verbose_name=_("Add notes to transactions")
)
all_objects = models.Manager() # Unfiltered manager
objects = GenericAccountOwnerManager() # Default filtered manager
@@ -743,11 +768,13 @@ class RecurringTransaction(models.Model):
date=date,
reference_date=reference_date.replace(day=1),
amount=self.amount,
description=self.description,
description=(
self.description if self.add_description_to_transaction else ""
),
category=self.category,
is_paid=False,
recurring_transaction=self,
notes=self.notes,
notes=self.notes if self.add_notes_to_transaction else "",
)
if self.tags.exists():
created_transaction.tags.set(self.tags.all())
@@ -821,9 +848,13 @@ class RecurringTransaction(models.Model):
for existing_transaction in unpaid_transactions:
# Update fields based on RecurringTransaction
existing_transaction.amount = self.amount
existing_transaction.description = self.description
existing_transaction.description = (
self.description if self.add_description_to_transaction else ""
)
existing_transaction.category = self.category
existing_transaction.notes = self.notes
existing_transaction.notes = (
self.notes if self.add_notes_to_transaction else ""
)
# Update many-to-many relationships
existing_transaction.tags.set(self.tags.all())

View File

@@ -0,0 +1,34 @@
[
{
"model": "users.user",
"pk": 1,
"fields": {
"password": "pbkdf2_sha256$870000$kUmqeqdenjc2yFS8qbniwS$7qOMXzKG+yFmezdjhptkwuMJlqlZnQHXgAnonWurpBk=",
"last_login": "2025-03-31T03:22:25Z",
"is_superuser": false,
"first_name": "Demo",
"last_name": "User",
"is_staff": false,
"is_active": true,
"date_joined": "2025-03-31T03:21:04Z",
"email": "demo@demo.com",
"groups": [],
"user_permissions": []
}
},
{
"model": "users.usersettings",
"pk": 1,
"fields": {
"user": 1,
"hide_amounts": false,
"mute_sounds": false,
"date_format": "SHORT_DATE_FORMAT",
"datetime_format": "SHORT_DATETIME_FORMAT",
"number_format": "AA",
"language": "auto",
"timezone": "auto",
"start_page": "MONTHLY_OVERVIEW"
}
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-09 04:55+0000\n"
"POT-Creation-Date: 2025-03-31 05:30+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"
@@ -27,9 +27,9 @@ msgstr ""
#: apps/currencies/forms.py:142 apps/dca/forms.py:49 apps/dca/forms.py:224
#: apps/import_app/forms.py:34 apps/rules/forms.py:51 apps/rules/forms.py:93
#: apps/rules/forms.py:365 apps/transactions/forms.py:202
#: apps/transactions/forms.py:269 apps/transactions/forms.py:623
#: apps/transactions/forms.py:666 apps/transactions/forms.py:698
#: apps/transactions/forms.py:733 apps/transactions/forms.py:881
#: apps/transactions/forms.py:269 apps/transactions/forms.py:629
#: apps/transactions/forms.py:672 apps/transactions/forms.py:704
#: apps/transactions/forms.py:739 apps/transactions/forms.py:891
msgid "Update"
msgstr ""
@@ -39,9 +39,9 @@ msgstr ""
#: apps/dca/forms.py:57 apps/dca/forms.py:232 apps/import_app/forms.py:42
#: apps/rules/forms.py:59 apps/rules/forms.py:101 apps/rules/forms.py:373
#: apps/transactions/forms.py:187 apps/transactions/forms.py:211
#: apps/transactions/forms.py:631 apps/transactions/forms.py:674
#: apps/transactions/forms.py:706 apps/transactions/forms.py:741
#: apps/transactions/forms.py:889
#: apps/transactions/forms.py:637 apps/transactions/forms.py:680
#: apps/transactions/forms.py:712 apps/transactions/forms.py:747
#: apps/transactions/forms.py:899
#: templates/account_groups/fragments/list.html:9
#: templates/accounts/fragments/list.html:9
#: templates/categories/fragments/list.html:9
@@ -72,9 +72,9 @@ msgstr ""
#: apps/insights/forms.py:118 apps/rules/forms.py:174 apps/rules/forms.py:189
#: apps/rules/models.py:38 apps/rules/models.py:286
#: apps/transactions/forms.py:40 apps/transactions/forms.py:303
#: apps/transactions/forms.py:310 apps/transactions/forms.py:508
#: apps/transactions/forms.py:765 apps/transactions/models.py:302
#: apps/transactions/models.py:485 apps/transactions/models.py:668
#: apps/transactions/forms.py:310 apps/transactions/forms.py:510
#: apps/transactions/forms.py:771 apps/transactions/models.py:305
#: apps/transactions/models.py:488 apps/transactions/models.py:686
#: templates/insights/fragments/category_overview/index.html:9
msgid "Category"
msgstr ""
@@ -84,18 +84,18 @@ msgstr ""
#: apps/rules/forms.py:177 apps/rules/forms.py:186 apps/rules/models.py:39
#: apps/rules/models.py:290 apps/transactions/filters.py:74
#: apps/transactions/forms.py:48 apps/transactions/forms.py:319
#: apps/transactions/forms.py:327 apps/transactions/forms.py:501
#: apps/transactions/forms.py:758 apps/transactions/models.py:308
#: apps/transactions/models.py:487 apps/transactions/models.py:672
#: apps/transactions/forms.py:327 apps/transactions/forms.py:503
#: apps/transactions/forms.py:764 apps/transactions/models.py:311
#: apps/transactions/models.py:490 apps/transactions/models.py:690
#: templates/includes/navbar.html:108 templates/tags/fragments/list.html:5
#: templates/tags/pages/index.html:4
msgid "Tags"
msgstr ""
#: apps/accounts/models.py:12 apps/accounts/models.py:28 apps/dca/models.py:13
#: apps/accounts/models.py:12 apps/accounts/models.py:29 apps/dca/models.py:13
#: apps/import_app/models.py:14 apps/rules/models.py:13
#: apps/transactions/models.py:198 apps/transactions/models.py:222
#: apps/transactions/models.py:245
#: apps/transactions/models.py:198 apps/transactions/models.py:223
#: apps/transactions/models.py:247
#: templates/account_groups/fragments/list.html:25
#: templates/accounts/fragments/list.html:25
#: templates/categories/fragments/table.html:16
@@ -110,7 +110,7 @@ msgstr ""
msgid "Name"
msgstr ""
#: apps/accounts/models.py:18 apps/accounts/models.py:32
#: apps/accounts/models.py:18 apps/accounts/models.py:33
msgid "Account Group"
msgstr ""
@@ -120,49 +120,49 @@ msgstr ""
msgid "Account Groups"
msgstr ""
#: apps/accounts/models.py:38 apps/currencies/models.py:39
#: apps/accounts/models.py:39 apps/currencies/models.py:39
#: templates/accounts/fragments/list.html:27
msgid "Currency"
msgstr ""
#: apps/accounts/models.py:44 apps/currencies/models.py:27
#: apps/accounts/models.py:45 apps/currencies/models.py:27
#: templates/accounts/fragments/list.html:28
msgid "Exchange Currency"
msgstr ""
#: apps/accounts/models.py:49 apps/currencies/models.py:32
#: apps/accounts/models.py:50 apps/currencies/models.py:32
msgid "Default currency for exchange calculations"
msgstr ""
#: apps/accounts/models.py:54
#: apps/accounts/models.py:55
msgid "Asset account"
msgstr ""
#: apps/accounts/models.py:56
#: apps/accounts/models.py:57
msgid ""
"Asset accounts count towards your Net Worth, but not towards your month."
msgstr ""
#: apps/accounts/models.py:61 templates/accounts/fragments/list.html:30
#: apps/accounts/models.py:62 templates/accounts/fragments/list.html:30
#: templates/categories/fragments/list.html:24
#: templates/entities/fragments/list.html:24
#: templates/tags/fragments/list.html:24
msgid "Archived"
msgstr ""
#: apps/accounts/models.py:62
#: apps/accounts/models.py:63
msgid "Archived accounts don't show up nor count towards your net worth"
msgstr ""
#: apps/accounts/models.py:69 apps/rules/forms.py:166 apps/rules/forms.py:179
#: apps/accounts/models.py:70 apps/rules/forms.py:166 apps/rules/forms.py:179
#: apps/rules/models.py:30 apps/rules/models.py:242
#: apps/transactions/forms.py:60 apps/transactions/forms.py:493
#: apps/transactions/forms.py:750 apps/transactions/models.py:275
#: apps/transactions/models.py:445 apps/transactions/models.py:650
#: apps/transactions/forms.py:60 apps/transactions/forms.py:495
#: apps/transactions/forms.py:756 apps/transactions/models.py:278
#: apps/transactions/models.py:448 apps/transactions/models.py:668
msgid "Account"
msgstr ""
#: apps/accounts/models.py:70 apps/export_app/forms.py:20
#: apps/accounts/models.py:71 apps/export_app/forms.py:20
#: apps/export_app/forms.py:132 apps/transactions/filters.py:53
#: templates/accounts/fragments/list.html:5
#: templates/accounts/pages/index.html:4 templates/includes/navbar.html:114
@@ -173,7 +173,7 @@ msgstr ""
msgid "Accounts"
msgstr ""
#: apps/accounts/models.py:82
#: apps/accounts/models.py:84
msgid "Exchange currency cannot be the same as the account's main currency."
msgstr ""
@@ -184,7 +184,7 @@ 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:63
#: apps/dca/views.py:146 apps/rules/views.py:99 apps/rules/views.py:195
#: apps/dca/views.py:146 apps/rules/views.py:105 apps/rules/views.py:205
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
@@ -199,7 +199,7 @@ msgstr ""
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:105
#: apps/rules/views.py:154 apps/transactions/views/categories.py:168
#: apps/rules/views.py:162 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 ""
@@ -210,14 +210,14 @@ msgstr ""
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:169 apps/dca/views.py:129
#: apps/rules/views.py:178 apps/transactions/views/categories.py:192
#: apps/rules/views.py:187 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 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:159
#: apps/rules/views.py:208 apps/transactions/views/categories.py:142
#: apps/rules/views.py:218 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
msgid "Configuration saved successfully"
msgstr ""
@@ -442,7 +442,7 @@ msgstr ""
#: apps/currencies/forms.py:69 apps/dca/models.py:158 apps/rules/forms.py:169
#: apps/rules/forms.py:182 apps/rules/models.py:33 apps/rules/models.py:254
#: apps/transactions/forms.py:64 apps/transactions/forms.py:331
#: apps/transactions/models.py:285
#: apps/transactions/models.py:288
#: templates/dca/fragments/strategy/details.html:52
#: templates/exchange_rates/fragments/table.html:10
#: templates/exchange_rates_services/fragments/table.html:10
@@ -472,59 +472,59 @@ msgstr ""
msgid "Currencies"
msgstr ""
#: apps/currencies/models.py:48
#: apps/currencies/models.py:49
msgid "Currency cannot have itself as exchange currency."
msgstr ""
#: apps/currencies/models.py:59
#: apps/currencies/models.py:60
msgid "From Currency"
msgstr ""
#: apps/currencies/models.py:65
#: apps/currencies/models.py:66
msgid "To Currency"
msgstr ""
#: apps/currencies/models.py:68 apps/currencies/models.py:73
#: apps/currencies/models.py:69 apps/currencies/models.py:74
msgid "Exchange Rate"
msgstr ""
#: apps/currencies/models.py:70
#: apps/currencies/models.py:71
msgid "Date and Time"
msgstr ""
#: apps/currencies/models.py:74 apps/export_app/forms.py:68
#: apps/currencies/models.py:75 apps/export_app/forms.py:68
#: apps/export_app/forms.py:145 templates/exchange_rates/fragments/list.html:6
#: templates/exchange_rates/pages/index.html:4
#: templates/includes/navbar.html:126
msgid "Exchange Rates"
msgstr ""
#: apps/currencies/models.py:86
#: apps/currencies/models.py:87
msgid "From and To currencies cannot be the same."
msgstr ""
#: apps/currencies/models.py:101
#: apps/currencies/models.py:102
msgid "On"
msgstr ""
#: apps/currencies/models.py:102
#: apps/currencies/models.py:103
msgid "Every X hours"
msgstr ""
#: apps/currencies/models.py:103
#: apps/currencies/models.py:104
msgid "Not on"
msgstr ""
#: apps/currencies/models.py:105
#: apps/currencies/models.py:106
msgid "Service Name"
msgstr ""
#: apps/currencies/models.py:107
#: apps/currencies/models.py:108
msgid "Service Type"
msgstr ""
#: apps/currencies/models.py:109 apps/transactions/models.py:202
#: apps/transactions/models.py:225 apps/transactions/models.py:248
#: apps/currencies/models.py:110 apps/transactions/models.py:202
#: apps/transactions/models.py:226 apps/transactions/models.py:250
#: templates/categories/fragments/list.html:21
#: templates/entities/fragments/list.html:21
#: templates/recurring_transactions/fragments/list.html:21
@@ -532,69 +532,69 @@ msgstr ""
msgid "Active"
msgstr ""
#: apps/currencies/models.py:114
#: apps/currencies/models.py:115
msgid "API Key"
msgstr ""
#: apps/currencies/models.py:115
#: apps/currencies/models.py:116
msgid "API key for the service (if required)"
msgstr ""
#: apps/currencies/models.py:120
#: apps/currencies/models.py:121
msgid "Interval Type"
msgstr ""
#: apps/currencies/models.py:124
#: apps/currencies/models.py:125
msgid "Interval"
msgstr ""
#: apps/currencies/models.py:127
#: apps/currencies/models.py:128
msgid "Last Successful Fetch"
msgstr ""
#: apps/currencies/models.py:132
#: apps/currencies/models.py:133
msgid "Target Currencies"
msgstr ""
#: apps/currencies/models.py:134
#: apps/currencies/models.py:135
msgid ""
"Select currencies to fetch exchange rates for. Rates will be fetched for "
"each currency against their set exchange currency."
msgstr ""
#: apps/currencies/models.py:142
#: apps/currencies/models.py:143
msgid "Target Accounts"
msgstr ""
#: apps/currencies/models.py:144
#: apps/currencies/models.py:145
msgid ""
"Select accounts to fetch exchange rates for. Rates will be fetched for each "
"account's currency against their set exchange currency."
msgstr ""
#: apps/currencies/models.py:151
#: apps/currencies/models.py:152
msgid "Exchange Rate Service"
msgstr ""
#: apps/currencies/models.py:152
#: apps/currencies/models.py:153
msgid "Exchange Rate Services"
msgstr ""
#: apps/currencies/models.py:204
#: apps/currencies/models.py:205
msgid "'Every X hours' interval type requires a positive integer."
msgstr ""
#: apps/currencies/models.py:213
#: apps/currencies/models.py:214
msgid "'Every X hours' interval must be between 1 and 24."
msgstr ""
#: apps/currencies/models.py:227
#: apps/currencies/models.py:228
msgid ""
"Invalid hour format. Use comma-separated hours (0-23) and/or ranges (e.g., "
"'1-5,8,10-12')."
msgstr ""
#: apps/currencies/models.py:238
#: apps/currencies/models.py:239
msgid ""
"Invalid format. Please check the requirements for your selected interval "
"type."
@@ -624,19 +624,19 @@ msgstr ""
msgid "Exchange rate deleted successfully"
msgstr ""
#: apps/currencies/views/exchange_rates_services.py:46
#: apps/currencies/views/exchange_rates_services.py:50
msgid "Service added successfully"
msgstr ""
#: apps/currencies/views/exchange_rates_services.py:74
#: apps/currencies/views/exchange_rates_services.py:79
msgid "Service updated successfully"
msgstr ""
#: apps/currencies/views/exchange_rates_services.py:100
#: apps/currencies/views/exchange_rates_services.py:106
msgid "Service deleted successfully"
msgstr ""
#: apps/currencies/views/exchange_rates_services.py:115
#: apps/currencies/views/exchange_rates_services.py:122
msgid "Services queued successfully"
msgstr ""
@@ -673,7 +673,7 @@ msgstr ""
msgid "You must provide an account."
msgstr ""
#: apps/dca/forms.py:312 apps/transactions/forms.py:443
#: apps/dca/forms.py:312 apps/transactions/forms.py:445
msgid "From and To accounts must be different."
msgstr ""
@@ -692,8 +692,8 @@ msgstr ""
#: apps/dca/models.py:26 apps/dca/models.py:181 apps/rules/forms.py:173
#: apps/rules/forms.py:188 apps/rules/models.py:37 apps/rules/models.py:270
#: apps/transactions/forms.py:345 apps/transactions/models.py:298
#: apps/transactions/models.py:494 apps/transactions/models.py:678
#: apps/transactions/forms.py:347 apps/transactions/models.py:301
#: apps/transactions/models.py:497 apps/transactions/models.py:696
msgid "Notes"
msgstr ""
@@ -754,7 +754,7 @@ msgid "Users"
msgstr ""
#: apps/export_app/forms.py:32 apps/export_app/forms.py:137
#: apps/transactions/models.py:359 templates/includes/navbar.html:57
#: apps/transactions/models.py:362 templates/includes/navbar.html:57
#: templates/includes/navbar.html:104
#: templates/recurring_transactions/fragments/list_transactions.html:5
#: templates/recurring_transactions/fragments/table.html:37
@@ -771,23 +771,23 @@ msgstr ""
#: apps/export_app/forms.py:50 apps/export_app/forms.py:136
#: apps/rules/forms.py:178 apps/rules/forms.py:187 apps/rules/models.py:40
#: apps/rules/models.py:282 apps/transactions/filters.py:81
#: apps/transactions/forms.py:56 apps/transactions/forms.py:516
#: apps/transactions/forms.py:773 apps/transactions/models.py:259
#: apps/transactions/models.py:313 apps/transactions/models.py:490
#: apps/transactions/models.py:675 templates/entities/fragments/list.html:5
#: apps/transactions/forms.py:56 apps/transactions/forms.py:518
#: apps/transactions/forms.py:779 apps/transactions/models.py:261
#: apps/transactions/models.py:316 apps/transactions/models.py:493
#: apps/transactions/models.py:693 templates/entities/fragments/list.html:5
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:110
msgid "Entities"
msgstr ""
#: apps/export_app/forms.py:56 apps/export_app/forms.py:140
#: apps/transactions/models.py:705 templates/includes/navbar.html:74
#: apps/transactions/models.py:730 templates/includes/navbar.html:74
#: templates/recurring_transactions/fragments/list.html:5
#: templates/recurring_transactions/pages/index.html:4
msgid "Recurring Transactions"
msgstr ""
#: apps/export_app/forms.py:62 apps/export_app/forms.py:138
#: apps/transactions/models.py:501 templates/includes/navbar.html:72
#: apps/transactions/models.py:511 templates/includes/navbar.html:72
#: templates/installment_plans/fragments/list.html:5
#: templates/installment_plans/pages/index.html:4
msgid "Installment Plans"
@@ -851,15 +851,15 @@ msgstr ""
msgid "Please upload either a ZIP file or at least one CSV file"
msgstr ""
#: apps/export_app/views.py:174
#: apps/export_app/views.py:177
msgid "You have to select at least one export"
msgstr ""
#: apps/export_app/views.py:193
#: apps/export_app/views.py:197
msgid "Data restored successfully"
msgstr ""
#: apps/export_app/views.py:205
#: apps/export_app/views.py:209
msgid ""
"There was an error restoring your data. Check the logs for more details."
msgstr ""
@@ -918,23 +918,23 @@ msgstr ""
msgid "File name"
msgstr ""
#: apps/import_app/views.py:62
#: apps/import_app/views.py:67
msgid "Import Profile added successfully"
msgstr ""
#: apps/import_app/views.py:97
#: apps/import_app/views.py:103
msgid "Import Profile update successfully"
msgstr ""
#: apps/import_app/views.py:123
#: apps/import_app/views.py:130
msgid "Import Profile deleted successfully"
msgstr ""
#: apps/import_app/views.py:185
#: apps/import_app/views.py:195
msgid "Import Run queued successfully"
msgstr ""
#: apps/import_app/views.py:211
#: apps/import_app/views.py:222
msgid "Run deleted successfully"
msgstr ""
@@ -1022,14 +1022,14 @@ msgid "Operator"
msgstr ""
#: apps/rules/forms.py:167 apps/rules/forms.py:180 apps/rules/models.py:31
#: apps/rules/models.py:246 apps/transactions/models.py:282
#: apps/transactions/models.py:450 apps/transactions/models.py:656
#: apps/rules/models.py:246 apps/transactions/models.py:285
#: apps/transactions/models.py:453 apps/transactions/models.py:674
msgid "Type"
msgstr ""
#: apps/rules/forms.py:168 apps/rules/forms.py:181 apps/rules/models.py:32
#: apps/rules/models.py:250 apps/transactions/filters.py:23
#: apps/transactions/models.py:284 templates/cotton/transaction/item.html:21
#: apps/transactions/models.py:287 templates/cotton/transaction/item.html:21
#: templates/cotton/transaction/item.html:31
#: templates/transactions/widgets/paid_toggle_button.html:12
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:16
@@ -1038,32 +1038,32 @@ msgstr ""
#: apps/rules/forms.py:170 apps/rules/forms.py:183 apps/rules/models.py:34
#: apps/rules/models.py:258 apps/transactions/forms.py:68
#: apps/transactions/forms.py:334 apps/transactions/forms.py:522
#: apps/transactions/models.py:286 apps/transactions/models.py:468
#: apps/transactions/models.py:680
#: apps/transactions/forms.py:334 apps/transactions/forms.py:524
#: apps/transactions/models.py:289 apps/transactions/models.py:471
#: apps/transactions/models.py:698
msgid "Reference Date"
msgstr ""
#: apps/rules/forms.py:171 apps/rules/forms.py:184 apps/rules/models.py:35
#: apps/rules/models.py:262 apps/transactions/models.py:291
#: apps/transactions/models.py:661 templates/insights/fragments/sankey.html:95
#: apps/rules/models.py:262 apps/transactions/models.py:294
#: apps/transactions/models.py:679 templates/insights/fragments/sankey.html:95
msgid "Amount"
msgstr ""
#: apps/rules/forms.py:172 apps/rules/forms.py:185 apps/rules/models.py:14
#: apps/rules/models.py:36 apps/rules/models.py:266
#: apps/transactions/forms.py:337 apps/transactions/models.py:296
#: apps/transactions/models.py:452 apps/transactions/models.py:664
#: apps/transactions/forms.py:338 apps/transactions/models.py:299
#: apps/transactions/models.py:455 apps/transactions/models.py:682
msgid "Description"
msgstr ""
#: apps/rules/forms.py:175 apps/rules/forms.py:190 apps/rules/models.py:274
#: apps/transactions/models.py:335
#: apps/transactions/models.py:338
msgid "Internal Note"
msgstr ""
#: apps/rules/forms.py:176 apps/rules/forms.py:191 apps/rules/models.py:278
#: apps/transactions/models.py:337
#: apps/transactions/models.py:340
msgid "Internal ID"
msgstr ""
@@ -1149,43 +1149,43 @@ msgstr ""
msgid "Update or create transaction action"
msgstr ""
#: apps/rules/views.py:54
#: apps/rules/views.py:58
msgid "Rule deactivated successfully"
msgstr ""
#: apps/rules/views.py:56
#: apps/rules/views.py:60
msgid "Rule activated successfully"
msgstr ""
#: apps/rules/views.py:74
#: apps/rules/views.py:79
msgid "Rule added successfully"
msgstr ""
#: apps/rules/views.py:112
#: apps/rules/views.py:118
msgid "Rule updated successfully"
msgstr ""
#: apps/rules/views.py:157
#: apps/rules/views.py:165
msgid "Rule deleted successfully"
msgstr ""
#: apps/rules/views.py:270
#: apps/rules/views.py:282
msgid "Action updated successfully"
msgstr ""
#: apps/rules/views.py:300
#: apps/rules/views.py:313
msgid "Action deleted successfully"
msgstr ""
#: apps/rules/views.py:323
#: apps/rules/views.py:337
msgid "Update or Create Transaction action added successfully"
msgstr ""
#: apps/rules/views.py:355
#: apps/rules/views.py:370
msgid "Update or Create Transaction action updated successfully"
msgstr ""
#: apps/rules/views.py:384
#: apps/rules/views.py:400
msgid "Update or Create Transaction action deleted successfully"
msgstr ""
@@ -1236,28 +1236,28 @@ msgstr ""
msgid "To Amount"
msgstr ""
#: apps/transactions/forms.py:410
#: apps/transactions/forms.py:412
#: templates/cotton/ui/quick_transactions_buttons.html:40
msgid "Transfer"
msgstr ""
#: apps/transactions/forms.py:652
#: apps/transactions/forms.py:658
msgid "Tag name"
msgstr ""
#: apps/transactions/forms.py:684
#: apps/transactions/forms.py:690
msgid "Entity name"
msgstr ""
#: apps/transactions/forms.py:716
#: apps/transactions/forms.py:722
msgid "Category name"
msgstr ""
#: apps/transactions/forms.py:718
#: apps/transactions/forms.py:724
msgid "Muted categories won't count towards your monthly total"
msgstr ""
#: apps/transactions/forms.py:900
#: apps/transactions/forms.py:910
msgid "End date should be after the start date"
msgstr ""
@@ -1279,26 +1279,26 @@ msgstr ""
msgid "Transaction Categories"
msgstr ""
#: apps/transactions/models.py:227
#: apps/transactions/models.py:228
msgid ""
"Deactivated tags won't be able to be selected when creating new transactions"
msgstr ""
#: apps/transactions/models.py:235 apps/transactions/models.py:236
#: apps/transactions/models.py:236 apps/transactions/models.py:237
msgid "Transaction Tags"
msgstr ""
#: apps/transactions/models.py:250
#: apps/transactions/models.py:252
msgid ""
"Deactivated entities won't be able to be selected when creating new "
"transactions"
msgstr ""
#: apps/transactions/models.py:258
#: apps/transactions/models.py:260
msgid "Entity"
msgstr ""
#: apps/transactions/models.py:269
#: apps/transactions/models.py:272
#: templates/calendar_view/fragments/list.html:42
#: templates/calendar_view/fragments/list.html:44
#: templates/calendar_view/fragments/list.html:52
@@ -1309,7 +1309,7 @@ msgstr ""
msgid "Income"
msgstr ""
#: apps/transactions/models.py:270
#: apps/transactions/models.py:273
#: templates/calendar_view/fragments/list.html:46
#: templates/calendar_view/fragments/list.html:48
#: templates/calendar_view/fragments/list.html:56
@@ -1319,117 +1319,125 @@ msgstr ""
msgid "Expense"
msgstr ""
#: apps/transactions/models.py:324 apps/transactions/models.py:500
#: apps/transactions/models.py:327 apps/transactions/models.py:510
msgid "Installment Plan"
msgstr ""
#: apps/transactions/models.py:333 apps/transactions/models.py:704
#: apps/transactions/models.py:336 apps/transactions/models.py:729
msgid "Recurring Transaction"
msgstr ""
#: apps/transactions/models.py:341
#: apps/transactions/models.py:344
msgid "Deleted"
msgstr ""
#: apps/transactions/models.py:346
#: apps/transactions/models.py:349
msgid "Deleted At"
msgstr ""
#: apps/transactions/models.py:358
#: apps/transactions/models.py:361
msgid "Transaction"
msgstr ""
#: apps/transactions/models.py:430 templates/tags/fragments/table.html:71
#: apps/transactions/models.py:433 templates/tags/fragments/table.html:71
msgid "No tags"
msgstr ""
#: apps/transactions/models.py:431
#: apps/transactions/models.py:434
msgid "No category"
msgstr ""
#: apps/transactions/models.py:433
#: apps/transactions/models.py:436
msgid "No description"
msgstr ""
#: apps/transactions/models.py:439
#: apps/transactions/models.py:442
msgid "Yearly"
msgstr ""
#: apps/transactions/models.py:440 apps/users/models.py:26
#: apps/transactions/models.py:443 apps/users/models.py:26
#: templates/includes/navbar.html:26
msgid "Monthly"
msgstr ""
#: apps/transactions/models.py:441
#: apps/transactions/models.py:444
msgid "Weekly"
msgstr ""
#: apps/transactions/models.py:442
#: apps/transactions/models.py:445
msgid "Daily"
msgstr ""
#: apps/transactions/models.py:455
#: apps/transactions/models.py:458
msgid "Number of Installments"
msgstr ""
#: apps/transactions/models.py:460
#: apps/transactions/models.py:463
msgid "Installment Start"
msgstr ""
#: apps/transactions/models.py:461
#: apps/transactions/models.py:464
msgid "The installment number to start counting from"
msgstr ""
#: apps/transactions/models.py:466 apps/transactions/models.py:684
#: apps/transactions/models.py:469 apps/transactions/models.py:702
msgid "Start Date"
msgstr ""
#: apps/transactions/models.py:470 apps/transactions/models.py:685
#: apps/transactions/models.py:473 apps/transactions/models.py:703
msgid "End Date"
msgstr ""
#: apps/transactions/models.py:475
#: apps/transactions/models.py:478
msgid "Recurrence"
msgstr ""
#: apps/transactions/models.py:478
#: apps/transactions/models.py:481
msgid "Installment Amount"
msgstr ""
#: apps/transactions/models.py:643
#: apps/transactions/models.py:500 apps/transactions/models.py:719
msgid "Add description to transactions"
msgstr ""
#: apps/transactions/models.py:503 apps/transactions/models.py:722
msgid "Add notes to transactions"
msgstr ""
#: apps/transactions/models.py:661
msgid "day(s)"
msgstr ""
#: apps/transactions/models.py:644
#: apps/transactions/models.py:662
msgid "week(s)"
msgstr ""
#: apps/transactions/models.py:645
#: apps/transactions/models.py:663
msgid "month(s)"
msgstr ""
#: apps/transactions/models.py:646
#: apps/transactions/models.py:664
msgid "year(s)"
msgstr ""
#: apps/transactions/models.py:648
#: apps/transactions/models.py:666
#: templates/recurring_transactions/fragments/list.html:24
msgid "Paused"
msgstr ""
#: apps/transactions/models.py:687
#: apps/transactions/models.py:705
msgid "Recurrence Type"
msgstr ""
#: apps/transactions/models.py:690
#: apps/transactions/models.py:708
msgid "Recurrence Interval"
msgstr ""
#: apps/transactions/models.py:694
#: apps/transactions/models.py:712
msgid "Last Generated Date"
msgstr ""
#: apps/transactions/models.py:697
#: apps/transactions/models.py:715
msgid "Last Generated Reference Date"
msgstr ""
@@ -1606,11 +1614,11 @@ msgstr ""
msgid "Important dates"
msgstr ""
#: apps/users/forms.py:19 apps/users/models.py:13
#: apps/users/forms.py:19 apps/users/models.py:13 templates/users/login.html:19
msgid "E-mail"
msgstr ""
#: apps/users/forms.py:25
#: apps/users/forms.py:25 templates/users/login.html:20
msgid "Password"
msgstr ""
@@ -2476,23 +2484,32 @@ msgstr ""
msgid "Calculator"
msgstr ""
#: templates/includes/navbar/user_menu.html:12
#: templates/includes/navbar/user_menu.html:14
msgid "Settings"
msgstr ""
#: templates/includes/navbar/user_menu.html:39
#: templates/includes/navbar/user_menu.html:41
msgid "Clear cache"
msgstr ""
#: templates/includes/navbar/user_menu.html:43
#: templates/includes/navbar/user_menu.html:45
msgid "Logout"
msgstr ""
#: templates/includes/scripts/hyperscript/htmx_error_handler.html:5
#: templates/includes/scripts/hyperscript/htmx_error_handler.html:8
msgid "Access Denied"
msgstr ""
#: templates/includes/scripts/hyperscript/htmx_error_handler.html:9
msgid ""
"You do not have permission to perform this action or access this resource."
msgstr ""
#: templates/includes/scripts/hyperscript/htmx_error_handler.html:18
msgid "Something went wrong loading your data"
msgstr ""
#: templates/includes/scripts/hyperscript/htmx_error_handler.html:6
#: templates/includes/scripts/hyperscript/htmx_error_handler.html:19
msgid "Try reloading the page or check the console for more information."
msgstr ""
@@ -2659,6 +2676,14 @@ msgstr ""
msgid "No installment plans"
msgstr ""
#: templates/layouts/base.html:40
msgid "This is a demo!"
msgstr ""
#: templates/layouts/base.html:40
msgid "Any data you add here will be wiped in 24hrs or less"
msgstr ""
#: templates/mini_tools/currency_converter/currency_converter.html:58
msgid "Invert"
msgstr ""
@@ -2969,6 +2994,14 @@ msgstr ""
msgid "Show amounts"
msgstr ""
#: templates/users/login.html:17
msgid "Welcome to WYGIWYH's demo!"
msgstr ""
#: templates/users/login.html:18
msgid "Use the credentials below to login"
msgstr ""
#: templates/yearly_overview/pages/overview_by_account.html:7
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "Yearly Overview"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-09 04:55+0000\n"
"PO-Revision-Date: 2025-03-09 04:56+0000\n"
"POT-Creation-Date: 2025-03-31 05:30+0000\n"
"PO-Revision-Date: 2025-03-31 05:38+0000\n"
"Last-Translator: Herculino Trotta <netotrotta@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translations.herculino.com/"
"projects/wygiwyh/app/pt_BR/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.10.2\n"
"X-Generator: Weblate 5.10.4\n"
#: apps/accounts/forms.py:24
msgid "Group name"
@@ -28,9 +28,9 @@ msgstr "Nome do grupo"
#: apps/currencies/forms.py:142 apps/dca/forms.py:49 apps/dca/forms.py:224
#: apps/import_app/forms.py:34 apps/rules/forms.py:51 apps/rules/forms.py:93
#: apps/rules/forms.py:365 apps/transactions/forms.py:202
#: apps/transactions/forms.py:269 apps/transactions/forms.py:623
#: apps/transactions/forms.py:666 apps/transactions/forms.py:698
#: apps/transactions/forms.py:733 apps/transactions/forms.py:881
#: apps/transactions/forms.py:269 apps/transactions/forms.py:629
#: apps/transactions/forms.py:672 apps/transactions/forms.py:704
#: apps/transactions/forms.py:739 apps/transactions/forms.py:891
msgid "Update"
msgstr "Atualizar"
@@ -40,9 +40,9 @@ msgstr "Atualizar"
#: apps/dca/forms.py:57 apps/dca/forms.py:232 apps/import_app/forms.py:42
#: apps/rules/forms.py:59 apps/rules/forms.py:101 apps/rules/forms.py:373
#: apps/transactions/forms.py:187 apps/transactions/forms.py:211
#: apps/transactions/forms.py:631 apps/transactions/forms.py:674
#: apps/transactions/forms.py:706 apps/transactions/forms.py:741
#: apps/transactions/forms.py:889
#: apps/transactions/forms.py:637 apps/transactions/forms.py:680
#: apps/transactions/forms.py:712 apps/transactions/forms.py:747
#: apps/transactions/forms.py:899
#: templates/account_groups/fragments/list.html:9
#: templates/accounts/fragments/list.html:9
#: templates/categories/fragments/list.html:9
@@ -73,9 +73,9 @@ msgstr "Novo saldo"
#: apps/insights/forms.py:118 apps/rules/forms.py:174 apps/rules/forms.py:189
#: apps/rules/models.py:38 apps/rules/models.py:286
#: apps/transactions/forms.py:40 apps/transactions/forms.py:303
#: apps/transactions/forms.py:310 apps/transactions/forms.py:508
#: apps/transactions/forms.py:765 apps/transactions/models.py:302
#: apps/transactions/models.py:485 apps/transactions/models.py:668
#: apps/transactions/forms.py:310 apps/transactions/forms.py:510
#: apps/transactions/forms.py:771 apps/transactions/models.py:305
#: apps/transactions/models.py:488 apps/transactions/models.py:686
#: templates/insights/fragments/category_overview/index.html:9
msgid "Category"
msgstr "Categoria"
@@ -85,18 +85,18 @@ msgstr "Categoria"
#: apps/rules/forms.py:177 apps/rules/forms.py:186 apps/rules/models.py:39
#: apps/rules/models.py:290 apps/transactions/filters.py:74
#: apps/transactions/forms.py:48 apps/transactions/forms.py:319
#: apps/transactions/forms.py:327 apps/transactions/forms.py:501
#: apps/transactions/forms.py:758 apps/transactions/models.py:308
#: apps/transactions/models.py:487 apps/transactions/models.py:672
#: apps/transactions/forms.py:327 apps/transactions/forms.py:503
#: apps/transactions/forms.py:764 apps/transactions/models.py:311
#: apps/transactions/models.py:490 apps/transactions/models.py:690
#: templates/includes/navbar.html:108 templates/tags/fragments/list.html:5
#: templates/tags/pages/index.html:4
msgid "Tags"
msgstr "Tags"
#: apps/accounts/models.py:12 apps/accounts/models.py:28 apps/dca/models.py:13
#: apps/accounts/models.py:12 apps/accounts/models.py:29 apps/dca/models.py:13
#: apps/import_app/models.py:14 apps/rules/models.py:13
#: apps/transactions/models.py:198 apps/transactions/models.py:222
#: apps/transactions/models.py:245
#: apps/transactions/models.py:198 apps/transactions/models.py:223
#: apps/transactions/models.py:247
#: templates/account_groups/fragments/list.html:25
#: templates/accounts/fragments/list.html:25
#: templates/categories/fragments/table.html:16
@@ -111,7 +111,7 @@ msgstr "Tags"
msgid "Name"
msgstr "Nome"
#: apps/accounts/models.py:18 apps/accounts/models.py:32
#: apps/accounts/models.py:18 apps/accounts/models.py:33
msgid "Account Group"
msgstr "Grupo da Conta"
@@ -121,52 +121,52 @@ msgstr "Grupo da Conta"
msgid "Account Groups"
msgstr "Grupos da Conta"
#: apps/accounts/models.py:38 apps/currencies/models.py:39
#: apps/accounts/models.py:39 apps/currencies/models.py:39
#: templates/accounts/fragments/list.html:27
msgid "Currency"
msgstr "Moeda"
#: apps/accounts/models.py:44 apps/currencies/models.py:27
#: apps/accounts/models.py:45 apps/currencies/models.py:27
#: templates/accounts/fragments/list.html:28
msgid "Exchange Currency"
msgstr "Moeda de Câmbio"
#: apps/accounts/models.py:49 apps/currencies/models.py:32
#: apps/accounts/models.py:50 apps/currencies/models.py:32
msgid "Default currency for exchange calculations"
msgstr "Moeda padrão para os cálculos de câmbio"
#: apps/accounts/models.py:54
#: apps/accounts/models.py:55
msgid "Asset account"
msgstr "Conta de ativos"
#: apps/accounts/models.py:56
#: apps/accounts/models.py:57
msgid ""
"Asset accounts count towards your Net Worth, but not towards your month."
msgstr ""
"As contas de ativos contam para o seu patrimônio líquido, mas não para o seu "
"mês."
#: apps/accounts/models.py:61 templates/accounts/fragments/list.html:30
#: apps/accounts/models.py:62 templates/accounts/fragments/list.html:30
#: templates/categories/fragments/list.html:24
#: templates/entities/fragments/list.html:24
#: templates/tags/fragments/list.html:24
msgid "Archived"
msgstr "Arquivada"
#: apps/accounts/models.py:62
#: apps/accounts/models.py:63
msgid "Archived accounts don't show up nor count towards your net worth"
msgstr ""
"Contas arquivadas não aparecem nem contam para o seu patrimônio líquido"
#: apps/accounts/models.py:69 apps/rules/forms.py:166 apps/rules/forms.py:179
#: apps/accounts/models.py:70 apps/rules/forms.py:166 apps/rules/forms.py:179
#: apps/rules/models.py:30 apps/rules/models.py:242
#: apps/transactions/forms.py:60 apps/transactions/forms.py:493
#: apps/transactions/forms.py:750 apps/transactions/models.py:275
#: apps/transactions/models.py:445 apps/transactions/models.py:650
#: apps/transactions/forms.py:60 apps/transactions/forms.py:495
#: apps/transactions/forms.py:756 apps/transactions/models.py:278
#: apps/transactions/models.py:448 apps/transactions/models.py:668
msgid "Account"
msgstr "Conta"
#: apps/accounts/models.py:70 apps/export_app/forms.py:20
#: apps/accounts/models.py:71 apps/export_app/forms.py:20
#: apps/export_app/forms.py:132 apps/transactions/filters.py:53
#: templates/accounts/fragments/list.html:5
#: templates/accounts/pages/index.html:4 templates/includes/navbar.html:114
@@ -177,7 +177,7 @@ msgstr "Conta"
msgid "Accounts"
msgstr "Contas"
#: apps/accounts/models.py:82
#: apps/accounts/models.py:84
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."
@@ -188,7 +188,7 @@ 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:63
#: apps/dca/views.py:146 apps/rules/views.py:99 apps/rules/views.py:195
#: apps/dca/views.py:146 apps/rules/views.py:105 apps/rules/views.py:205
#: apps/transactions/views/categories.py:91
#: apps/transactions/views/categories.py:129
#: apps/transactions/views/entities.py:91
@@ -203,7 +203,7 @@ msgstr "Grupo de Conta atualizado com sucesso"
#: apps/accounts/views/account_groups.py:111
#: apps/accounts/views/accounts.py:145 apps/dca/views.py:105
#: apps/rules/views.py:154 apps/transactions/views/categories.py:168
#: apps/rules/views.py:162 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ê"
@@ -214,14 +214,14 @@ msgstr "Grupo de Conta apagado com sucesso"
#: apps/accounts/views/account_groups.py:135
#: apps/accounts/views/accounts.py:169 apps/dca/views.py:129
#: apps/rules/views.py:178 apps/transactions/views/categories.py:192
#: apps/rules/views.py:187 apps/transactions/views/categories.py:192
#: apps/transactions/views/entities.py:154 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:159
#: apps/rules/views.py:208 apps/transactions/views/categories.py:142
#: apps/rules/views.py:218 apps/transactions/views/categories.py:142
#: apps/transactions/views/entities.py:184 apps/transactions/views/tags.py:184
msgid "Configuration saved successfully"
msgstr "Configuração salva com sucesso"
@@ -451,7 +451,7 @@ msgstr "Sufixo"
#: apps/currencies/forms.py:69 apps/dca/models.py:158 apps/rules/forms.py:169
#: apps/rules/forms.py:182 apps/rules/models.py:33 apps/rules/models.py:254
#: apps/transactions/forms.py:64 apps/transactions/forms.py:331
#: apps/transactions/models.py:285
#: apps/transactions/models.py:288
#: templates/dca/fragments/strategy/details.html:52
#: templates/exchange_rates/fragments/table.html:10
#: templates/exchange_rates_services/fragments/table.html:10
@@ -481,59 +481,59 @@ msgstr "Casas Decimais"
msgid "Currencies"
msgstr "Moedas"
#: apps/currencies/models.py:48
#: apps/currencies/models.py:49
msgid "Currency cannot have itself as exchange currency."
msgstr "A moeda não pode ter a si mesma como moeda de câmbio."
#: apps/currencies/models.py:59
#: apps/currencies/models.py:60
msgid "From Currency"
msgstr "Moeda de origem"
#: apps/currencies/models.py:65
#: apps/currencies/models.py:66
msgid "To Currency"
msgstr "Moeda de destino"
#: apps/currencies/models.py:68 apps/currencies/models.py:73
#: apps/currencies/models.py:69 apps/currencies/models.py:74
msgid "Exchange Rate"
msgstr "Taxa de Câmbio"
#: apps/currencies/models.py:70
#: apps/currencies/models.py:71
msgid "Date and Time"
msgstr "Data e Tempo"
#: apps/currencies/models.py:74 apps/export_app/forms.py:68
#: apps/currencies/models.py:75 apps/export_app/forms.py:68
#: apps/export_app/forms.py:145 templates/exchange_rates/fragments/list.html:6
#: templates/exchange_rates/pages/index.html:4
#: templates/includes/navbar.html:126
msgid "Exchange Rates"
msgstr "Taxas de Câmbio"
#: apps/currencies/models.py:86
#: apps/currencies/models.py:87
msgid "From and To currencies cannot be the same."
msgstr "As moedas De e Para não podem ser as mesmas."
#: apps/currencies/models.py:101
#: apps/currencies/models.py:102
msgid "On"
msgstr "Em"
#: apps/currencies/models.py:102
#: apps/currencies/models.py:103
msgid "Every X hours"
msgstr "A cada X horas"
#: apps/currencies/models.py:103
#: apps/currencies/models.py:104
msgid "Not on"
msgstr "Não em"
#: apps/currencies/models.py:105
#: apps/currencies/models.py:106
msgid "Service Name"
msgstr "Nome do Serviço"
#: apps/currencies/models.py:107
#: apps/currencies/models.py:108
msgid "Service Type"
msgstr "Tipo de Serviço"
#: apps/currencies/models.py:109 apps/transactions/models.py:202
#: apps/transactions/models.py:225 apps/transactions/models.py:248
#: apps/currencies/models.py:110 apps/transactions/models.py:202
#: apps/transactions/models.py:226 apps/transactions/models.py:250
#: templates/categories/fragments/list.html:21
#: templates/entities/fragments/list.html:21
#: templates/recurring_transactions/fragments/list.html:21
@@ -541,31 +541,31 @@ msgstr "Tipo de Serviço"
msgid "Active"
msgstr "Ativo"
#: apps/currencies/models.py:114
#: apps/currencies/models.py:115
msgid "API Key"
msgstr "Chave de API"
#: apps/currencies/models.py:115
#: apps/currencies/models.py:116
msgid "API key for the service (if required)"
msgstr "Chave de API para o serviço (se necessário)"
#: apps/currencies/models.py:120
#: apps/currencies/models.py:121
msgid "Interval Type"
msgstr "Tipo de Intervalo"
#: apps/currencies/models.py:124
#: apps/currencies/models.py:125
msgid "Interval"
msgstr "Intervalo"
#: apps/currencies/models.py:127
#: apps/currencies/models.py:128
msgid "Last Successful Fetch"
msgstr "Última execução bem-sucedida"
#: apps/currencies/models.py:132
#: apps/currencies/models.py:133
msgid "Target Currencies"
msgstr "Moedas-alvo"
#: apps/currencies/models.py:134
#: apps/currencies/models.py:135
msgid ""
"Select currencies to fetch exchange rates for. Rates will be fetched for "
"each currency against their set exchange currency."
@@ -573,11 +573,11 @@ msgstr ""
"Selecione as moedas para as quais deseja obter as taxas de câmbio. As taxas "
"serão obtidas para cada moeda em relação à moeda de câmbio definida."
#: apps/currencies/models.py:142
#: apps/currencies/models.py:143
msgid "Target Accounts"
msgstr "Contas-alvo"
#: apps/currencies/models.py:144
#: apps/currencies/models.py:145
msgid ""
"Select accounts to fetch exchange rates for. Rates will be fetched for each "
"account's currency against their set exchange currency."
@@ -586,24 +586,24 @@ msgstr ""
"serão obtidas para a moeda de cada conta em relação à moeda de câmbio "
"definida."
#: apps/currencies/models.py:151
#: apps/currencies/models.py:152
msgid "Exchange Rate Service"
msgstr "Serviço de Taxa de Câmbio"
#: apps/currencies/models.py:152
#: apps/currencies/models.py:153
msgid "Exchange Rate Services"
msgstr "Serviços de Taxa de Câmbio"
#: apps/currencies/models.py:204
#: apps/currencies/models.py:205
msgid "'Every X hours' interval type requires a positive integer."
msgstr ""
"Intervalo do tipo 'A cada X horas' requerer um número inteiro positivo."
#: apps/currencies/models.py:213
#: apps/currencies/models.py:214
msgid "'Every X hours' interval must be between 1 and 24."
msgstr "Intervalo do tipo 'A cada X horas' requerer um número entre 1 e 24."
#: apps/currencies/models.py:227
#: apps/currencies/models.py:228
msgid ""
"Invalid hour format. Use comma-separated hours (0-23) and/or ranges (e.g., "
"'1-5,8,10-12')."
@@ -611,7 +611,7 @@ msgstr ""
"Formato inválido de hora. Use uma lista de horas separada por vírgulas "
"(0-23) e/ou uma faixa (ex.: '1-5,8,10-12')."
#: apps/currencies/models.py:238
#: apps/currencies/models.py:239
msgid ""
"Invalid format. Please check the requirements for your selected interval "
"type."
@@ -643,19 +643,19 @@ msgstr "Taxa de câmbio atualizada com sucesso"
msgid "Exchange rate deleted successfully"
msgstr "Taxa de câmbio apagada com sucesso"
#: apps/currencies/views/exchange_rates_services.py:46
#: apps/currencies/views/exchange_rates_services.py:50
msgid "Service added successfully"
msgstr "Serviço adicionado com sucesso"
#: apps/currencies/views/exchange_rates_services.py:74
#: apps/currencies/views/exchange_rates_services.py:79
msgid "Service updated successfully"
msgstr "Serviço atualizado com sucesso"
#: apps/currencies/views/exchange_rates_services.py:100
#: apps/currencies/views/exchange_rates_services.py:106
msgid "Service deleted successfully"
msgstr "Serviço apagado com sucesso"
#: apps/currencies/views/exchange_rates_services.py:115
#: apps/currencies/views/exchange_rates_services.py:122
msgid "Services queued successfully"
msgstr "Serviços marcados para execução com sucesso"
@@ -692,7 +692,7 @@ msgstr "Conectar transação"
msgid "You must provide an account."
msgstr "Você deve informar uma conta."
#: apps/dca/forms.py:312 apps/transactions/forms.py:443
#: apps/dca/forms.py:312 apps/transactions/forms.py:445
msgid "From and To accounts must be different."
msgstr "As contas De e Para devem ser diferentes."
@@ -711,8 +711,8 @@ msgstr "Moeda de pagamento"
#: apps/dca/models.py:26 apps/dca/models.py:181 apps/rules/forms.py:173
#: apps/rules/forms.py:188 apps/rules/models.py:37 apps/rules/models.py:270
#: apps/transactions/forms.py:345 apps/transactions/models.py:298
#: apps/transactions/models.py:494 apps/transactions/models.py:678
#: apps/transactions/forms.py:347 apps/transactions/models.py:301
#: apps/transactions/models.py:497 apps/transactions/models.py:696
msgid "Notes"
msgstr "Notas"
@@ -773,7 +773,7 @@ msgid "Users"
msgstr "Usuários"
#: apps/export_app/forms.py:32 apps/export_app/forms.py:137
#: apps/transactions/models.py:359 templates/includes/navbar.html:57
#: apps/transactions/models.py:362 templates/includes/navbar.html:57
#: templates/includes/navbar.html:104
#: templates/recurring_transactions/fragments/list_transactions.html:5
#: templates/recurring_transactions/fragments/table.html:37
@@ -790,23 +790,23 @@ msgstr "Categorias"
#: apps/export_app/forms.py:50 apps/export_app/forms.py:136
#: apps/rules/forms.py:178 apps/rules/forms.py:187 apps/rules/models.py:40
#: apps/rules/models.py:282 apps/transactions/filters.py:81
#: apps/transactions/forms.py:56 apps/transactions/forms.py:516
#: apps/transactions/forms.py:773 apps/transactions/models.py:259
#: apps/transactions/models.py:313 apps/transactions/models.py:490
#: apps/transactions/models.py:675 templates/entities/fragments/list.html:5
#: apps/transactions/forms.py:56 apps/transactions/forms.py:518
#: apps/transactions/forms.py:779 apps/transactions/models.py:261
#: apps/transactions/models.py:316 apps/transactions/models.py:493
#: apps/transactions/models.py:693 templates/entities/fragments/list.html:5
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:110
msgid "Entities"
msgstr "Entidades"
#: apps/export_app/forms.py:56 apps/export_app/forms.py:140
#: apps/transactions/models.py:705 templates/includes/navbar.html:74
#: apps/transactions/models.py:730 templates/includes/navbar.html:74
#: templates/recurring_transactions/fragments/list.html:5
#: templates/recurring_transactions/pages/index.html:4
msgid "Recurring Transactions"
msgstr "Transações Recorrentes"
#: apps/export_app/forms.py:62 apps/export_app/forms.py:138
#: apps/transactions/models.py:501 templates/includes/navbar.html:72
#: apps/transactions/models.py:511 templates/includes/navbar.html:72
#: templates/installment_plans/fragments/list.html:5
#: templates/installment_plans/pages/index.html:4
msgid "Installment Plans"
@@ -870,15 +870,15 @@ msgstr "Restaurar"
msgid "Please upload either a ZIP file or at least one CSV file"
msgstr "Carregue um arquivo ZIP ou pelo menos um arquivo CSV"
#: apps/export_app/views.py:174
#: apps/export_app/views.py:177
msgid "You have to select at least one export"
msgstr "É necessário selecionar pelo menos uma exportação"
#: apps/export_app/views.py:193
#: apps/export_app/views.py:197
msgid "Data restored successfully"
msgstr "Dados restaurados com sucesso"
#: apps/export_app/views.py:205
#: apps/export_app/views.py:209
msgid ""
"There was an error restoring your data. Check the logs for more details."
msgstr ""
@@ -939,23 +939,23 @@ msgstr "Status"
msgid "File name"
msgstr "Nome do Arquivo"
#: apps/import_app/views.py:62
#: apps/import_app/views.py:67
msgid "Import Profile added successfully"
msgstr "Perfil de Importação adicionado com sucesso"
#: apps/import_app/views.py:97
#: apps/import_app/views.py:103
msgid "Import Profile update successfully"
msgstr "Importação atualizada com sucesso"
#: apps/import_app/views.py:123
#: apps/import_app/views.py:130
msgid "Import Profile deleted successfully"
msgstr "Importação apagada com sucesso"
#: apps/import_app/views.py:185
#: apps/import_app/views.py:195
msgid "Import Run queued successfully"
msgstr "Importação adicionada à fila com sucesso"
#: apps/import_app/views.py:211
#: apps/import_app/views.py:222
msgid "Run deleted successfully"
msgstr "Importação apagada com sucesso"
@@ -1043,14 +1043,14 @@ msgid "Operator"
msgstr "Operador"
#: apps/rules/forms.py:167 apps/rules/forms.py:180 apps/rules/models.py:31
#: apps/rules/models.py:246 apps/transactions/models.py:282
#: apps/transactions/models.py:450 apps/transactions/models.py:656
#: apps/rules/models.py:246 apps/transactions/models.py:285
#: apps/transactions/models.py:453 apps/transactions/models.py:674
msgid "Type"
msgstr "Tipo"
#: apps/rules/forms.py:168 apps/rules/forms.py:181 apps/rules/models.py:32
#: apps/rules/models.py:250 apps/transactions/filters.py:23
#: apps/transactions/models.py:284 templates/cotton/transaction/item.html:21
#: apps/transactions/models.py:287 templates/cotton/transaction/item.html:21
#: templates/cotton/transaction/item.html:31
#: templates/transactions/widgets/paid_toggle_button.html:12
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:16
@@ -1059,32 +1059,32 @@ msgstr "Pago"
#: apps/rules/forms.py:170 apps/rules/forms.py:183 apps/rules/models.py:34
#: apps/rules/models.py:258 apps/transactions/forms.py:68
#: apps/transactions/forms.py:334 apps/transactions/forms.py:522
#: apps/transactions/models.py:286 apps/transactions/models.py:468
#: apps/transactions/models.py:680
#: apps/transactions/forms.py:334 apps/transactions/forms.py:524
#: apps/transactions/models.py:289 apps/transactions/models.py:471
#: apps/transactions/models.py:698
msgid "Reference Date"
msgstr "Data de Referência"
#: apps/rules/forms.py:171 apps/rules/forms.py:184 apps/rules/models.py:35
#: apps/rules/models.py:262 apps/transactions/models.py:291
#: apps/transactions/models.py:661 templates/insights/fragments/sankey.html:95
#: apps/rules/models.py:262 apps/transactions/models.py:294
#: apps/transactions/models.py:679 templates/insights/fragments/sankey.html:95
msgid "Amount"
msgstr "Quantia"
#: apps/rules/forms.py:172 apps/rules/forms.py:185 apps/rules/models.py:14
#: apps/rules/models.py:36 apps/rules/models.py:266
#: apps/transactions/forms.py:337 apps/transactions/models.py:296
#: apps/transactions/models.py:452 apps/transactions/models.py:664
#: apps/transactions/forms.py:338 apps/transactions/models.py:299
#: apps/transactions/models.py:455 apps/transactions/models.py:682
msgid "Description"
msgstr "Descrição"
#: apps/rules/forms.py:175 apps/rules/forms.py:190 apps/rules/models.py:274
#: apps/transactions/models.py:335
#: apps/transactions/models.py:338
msgid "Internal Note"
msgstr "Nota Interna"
#: apps/rules/forms.py:176 apps/rules/forms.py:191 apps/rules/models.py:278
#: apps/transactions/models.py:337
#: apps/transactions/models.py:340
msgid "Internal ID"
msgstr "ID Interna"
@@ -1172,43 +1172,43 @@ msgstr ""
msgid "Update or create transaction action"
msgstr "Ação de atualizar ou criar transação"
#: apps/rules/views.py:54
#: apps/rules/views.py:58
msgid "Rule deactivated successfully"
msgstr "Regra desativada com sucesso"
#: apps/rules/views.py:56
#: apps/rules/views.py:60
msgid "Rule activated successfully"
msgstr "Regra ativada com sucesso"
#: apps/rules/views.py:74
#: apps/rules/views.py:79
msgid "Rule added successfully"
msgstr "Regra adicionada com sucesso"
#: apps/rules/views.py:112
#: apps/rules/views.py:118
msgid "Rule updated successfully"
msgstr "Regra atualizada com sucesso"
#: apps/rules/views.py:157
#: apps/rules/views.py:165
msgid "Rule deleted successfully"
msgstr "Regra apagada com sucesso"
#: apps/rules/views.py:270
#: apps/rules/views.py:282
msgid "Action updated successfully"
msgstr "Ação atualizada com sucesso"
#: apps/rules/views.py:300
#: apps/rules/views.py:313
msgid "Action deleted successfully"
msgstr "Ação apagada com sucesso"
#: apps/rules/views.py:323
#: apps/rules/views.py:337
msgid "Update or Create Transaction action added successfully"
msgstr "Ação Atualizar ou Criar Transação adicionada com sucesso"
#: apps/rules/views.py:355
#: apps/rules/views.py:370
msgid "Update or Create Transaction action updated successfully"
msgstr "Ação Atualizar ou Criar Transação atualizada com sucesso"
#: apps/rules/views.py:384
#: apps/rules/views.py:400
msgid "Update or Create Transaction action deleted successfully"
msgstr "Ação Atualizar ou Criar Transação apagada com sucesso"
@@ -1259,28 +1259,28 @@ msgstr "Quantia de origem"
msgid "To Amount"
msgstr "Quantia de destino"
#: apps/transactions/forms.py:410
#: apps/transactions/forms.py:412
#: templates/cotton/ui/quick_transactions_buttons.html:40
msgid "Transfer"
msgstr "Transferir"
#: apps/transactions/forms.py:652
#: apps/transactions/forms.py:658
msgid "Tag name"
msgstr "Nome da Tag"
#: apps/transactions/forms.py:684
#: apps/transactions/forms.py:690
msgid "Entity name"
msgstr "Nome da entidade"
#: apps/transactions/forms.py:716
#: apps/transactions/forms.py:722
msgid "Category name"
msgstr "Nome da Categoria"
#: apps/transactions/forms.py:718
#: apps/transactions/forms.py:724
msgid "Muted categories won't count towards your monthly total"
msgstr "As categorias silenciadas não serão contabilizadas em seu total mensal"
#: apps/transactions/forms.py:900
#: apps/transactions/forms.py:910
msgid "End date should be after the start date"
msgstr "Data final deve ser após data inicial"
@@ -1304,17 +1304,17 @@ msgstr "Categoria da Transação"
msgid "Transaction Categories"
msgstr "Categorias da Trasanção"
#: apps/transactions/models.py:227
#: apps/transactions/models.py:228
msgid ""
"Deactivated tags won't be able to be selected when creating new transactions"
msgstr ""
"As tags desativadas não poderão ser selecionadas ao criar novas transações"
#: apps/transactions/models.py:235 apps/transactions/models.py:236
#: apps/transactions/models.py:236 apps/transactions/models.py:237
msgid "Transaction Tags"
msgstr "Tags da Transação"
#: apps/transactions/models.py:250
#: apps/transactions/models.py:252
msgid ""
"Deactivated entities won't be able to be selected when creating new "
"transactions"
@@ -1322,11 +1322,11 @@ msgstr ""
"As entidades desativadas não poderão ser selecionadas ao criar novas "
"transações"
#: apps/transactions/models.py:258
#: apps/transactions/models.py:260
msgid "Entity"
msgstr "Entidade"
#: apps/transactions/models.py:269
#: apps/transactions/models.py:272
#: templates/calendar_view/fragments/list.html:42
#: templates/calendar_view/fragments/list.html:44
#: templates/calendar_view/fragments/list.html:52
@@ -1337,7 +1337,7 @@ msgstr "Entidade"
msgid "Income"
msgstr "Renda"
#: apps/transactions/models.py:270
#: apps/transactions/models.py:273
#: templates/calendar_view/fragments/list.html:46
#: templates/calendar_view/fragments/list.html:48
#: templates/calendar_view/fragments/list.html:56
@@ -1347,117 +1347,125 @@ msgstr "Renda"
msgid "Expense"
msgstr "Despesa"
#: apps/transactions/models.py:324 apps/transactions/models.py:500
#: apps/transactions/models.py:327 apps/transactions/models.py:510
msgid "Installment Plan"
msgstr "Parcelamento"
#: apps/transactions/models.py:333 apps/transactions/models.py:704
#: apps/transactions/models.py:336 apps/transactions/models.py:729
msgid "Recurring Transaction"
msgstr "Transação Recorrente"
#: apps/transactions/models.py:341
#: apps/transactions/models.py:344
msgid "Deleted"
msgstr "Apagado"
#: apps/transactions/models.py:346
#: apps/transactions/models.py:349
msgid "Deleted At"
msgstr "Apagado Em"
#: apps/transactions/models.py:358
#: apps/transactions/models.py:361
msgid "Transaction"
msgstr "Transação"
#: apps/transactions/models.py:430 templates/tags/fragments/table.html:71
#: apps/transactions/models.py:433 templates/tags/fragments/table.html:71
msgid "No tags"
msgstr "Nenhuma tag"
#: apps/transactions/models.py:431
#: apps/transactions/models.py:434
msgid "No category"
msgstr "Sem categoria"
#: apps/transactions/models.py:433
#: apps/transactions/models.py:436
msgid "No description"
msgstr "Sem descrição"
#: apps/transactions/models.py:439
#: apps/transactions/models.py:442
msgid "Yearly"
msgstr "Anual"
#: apps/transactions/models.py:440 apps/users/models.py:26
#: apps/transactions/models.py:443 apps/users/models.py:26
#: templates/includes/navbar.html:26
msgid "Monthly"
msgstr "Mensal"
#: apps/transactions/models.py:441
#: apps/transactions/models.py:444
msgid "Weekly"
msgstr "Semanal"
#: apps/transactions/models.py:442
#: apps/transactions/models.py:445
msgid "Daily"
msgstr "Diária"
#: apps/transactions/models.py:455
#: apps/transactions/models.py:458
msgid "Number of Installments"
msgstr "Número de Parcelas"
#: apps/transactions/models.py:460
#: apps/transactions/models.py:463
msgid "Installment Start"
msgstr "Parcela inicial"
#: apps/transactions/models.py:461
#: apps/transactions/models.py:464
msgid "The installment number to start counting from"
msgstr "O número da parcela a partir do qual se inicia a contagem"
#: apps/transactions/models.py:466 apps/transactions/models.py:684
#: apps/transactions/models.py:469 apps/transactions/models.py:702
msgid "Start Date"
msgstr "Data de Início"
#: apps/transactions/models.py:470 apps/transactions/models.py:685
#: apps/transactions/models.py:473 apps/transactions/models.py:703
msgid "End Date"
msgstr "Data Final"
#: apps/transactions/models.py:475
#: apps/transactions/models.py:478
msgid "Recurrence"
msgstr "Recorrência"
#: apps/transactions/models.py:478
#: apps/transactions/models.py:481
msgid "Installment Amount"
msgstr "Valor da Parcela"
#: apps/transactions/models.py:643
#: apps/transactions/models.py:500 apps/transactions/models.py:719
msgid "Add description to transactions"
msgstr "Adicionar descrição às transações"
#: apps/transactions/models.py:503 apps/transactions/models.py:722
msgid "Add notes to transactions"
msgstr "Adicionar notas às transações"
#: apps/transactions/models.py:661
msgid "day(s)"
msgstr "dia(s)"
#: apps/transactions/models.py:644
#: apps/transactions/models.py:662
msgid "week(s)"
msgstr "semana(s)"
#: apps/transactions/models.py:645
#: apps/transactions/models.py:663
msgid "month(s)"
msgstr "mês(es)"
#: apps/transactions/models.py:646
#: apps/transactions/models.py:664
msgid "year(s)"
msgstr "ano(s)"
#: apps/transactions/models.py:648
#: apps/transactions/models.py:666
#: templates/recurring_transactions/fragments/list.html:24
msgid "Paused"
msgstr "Pausado"
#: apps/transactions/models.py:687
#: apps/transactions/models.py:705
msgid "Recurrence Type"
msgstr "Tipo de recorrência"
#: apps/transactions/models.py:690
#: apps/transactions/models.py:708
msgid "Recurrence Interval"
msgstr "Intervalo de recorrência"
#: apps/transactions/models.py:694
#: apps/transactions/models.py:712
msgid "Last Generated Date"
msgstr "Última data gerada"
#: apps/transactions/models.py:697
#: apps/transactions/models.py:715
msgid "Last Generated Reference Date"
msgstr "Última data de referência gerada"
@@ -1634,11 +1642,11 @@ msgstr "Permissões"
msgid "Important dates"
msgstr "Datas importantes"
#: apps/users/forms.py:19 apps/users/models.py:13
#: apps/users/forms.py:19 apps/users/models.py:13 templates/users/login.html:19
msgid "E-mail"
msgstr "E-mail"
#: apps/users/forms.py:25
#: apps/users/forms.py:25 templates/users/login.html:20
msgid "Password"
msgstr "Senha"
@@ -2507,23 +2515,32 @@ msgstr "Django Admin"
msgid "Calculator"
msgstr "Calculadora"
#: templates/includes/navbar/user_menu.html:12
#: templates/includes/navbar/user_menu.html:14
msgid "Settings"
msgstr "Configurações"
#: templates/includes/navbar/user_menu.html:39
#: templates/includes/navbar/user_menu.html:41
msgid "Clear cache"
msgstr "Limpar cache"
#: templates/includes/navbar/user_menu.html:43
#: templates/includes/navbar/user_menu.html:45
msgid "Logout"
msgstr "Sair"
#: templates/includes/scripts/hyperscript/htmx_error_handler.html:5
#: templates/includes/scripts/hyperscript/htmx_error_handler.html:8
msgid "Access Denied"
msgstr "Acesso Negado"
#: templates/includes/scripts/hyperscript/htmx_error_handler.html:9
msgid ""
"You do not have permission to perform this action or access this resource."
msgstr "Você não tem permissão para executar essa ação ou acessar esse recurso."
#: templates/includes/scripts/hyperscript/htmx_error_handler.html:18
msgid "Something went wrong loading your data"
msgstr "Algo deu errado ao carregar seus dados"
#: templates/includes/scripts/hyperscript/htmx_error_handler.html:6
#: templates/includes/scripts/hyperscript/htmx_error_handler.html:19
msgid "Try reloading the page or check the console for more information."
msgstr ""
"Tente recarregar a página ou verifique o console para obter mais informações."
@@ -2693,6 +2710,15 @@ msgstr "Isso excluirá o parcelamento e todas as transações associadas a ele"
msgid "No installment plans"
msgstr "Nenhum parcelamento"
#: templates/layouts/base.html:40
msgid "This is a demo!"
msgstr "Isto é uma demonstração!"
#: templates/layouts/base.html:40
msgid "Any data you add here will be wiped in 24hrs or less"
msgstr ""
"Todos os dados que você adicionar aqui serão apagados em 24 horas ou menos"
#: templates/mini_tools/currency_converter/currency_converter.html:58
msgid "Invert"
msgstr "Inverter"
@@ -3006,6 +3032,14 @@ msgstr "Reproduzir sons"
msgid "Show amounts"
msgstr "Mostrar valores"
#: templates/users/login.html:17
msgid "Welcome to WYGIWYH's demo!"
msgstr "Boas-vindas à demonstração do WYGIWYH!"
#: templates/users/login.html:18
msgid "Use the credentials below to login"
msgstr "Use as credenciais abaixo para fazer login"
#: templates/yearly_overview/pages/overview_by_account.html:7
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "Yearly Overview"

View File

@@ -5,7 +5,7 @@
{% block title %}{% translate 'Transactions on' %} {{ date|date:"SHORT_DATE_FORMAT" }}{% endblock %}
{% block body %}
<div hx-get="{% url 'calendar_transactions_list' day=date.day month=date.month year=date.year %}" hx-trigger="updated from:window" hx-vals='{"disable_selection": true}' hx-target="closest .offcanvas" class="show-loading" id="transactions-list">
<div hx-get="{% url 'calendar_transactions_list' day=date.day month=date.month year=date.year %}" hx-trigger="updated from:window" hx-target="closest .offcanvas" class="show-loading" id="transactions-list">
{% for transaction in transactions %}
<c-transaction.item :transaction="transaction"></c-transaction.item>
{% empty %}

View File

@@ -5,6 +5,8 @@
<i class="fa-solid fa-user"></i>
</a>
<ul class="dropdown-menu dropdown-menu-start dropdown-menu-lg-end">
<li class="dropdown-item-text">{{ user.email }}</li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item"
hx-get="{% url 'user_settings' %}"
hx-target="#generic-offcanvas"

View File

@@ -1,15 +1,30 @@
{% load i18n %}
<script type="text/hyperscript">
behavior htmx_error_handler
on htmx:responseError or htmx:afterRequest[detail.failed] or htmx:sendError queue none
call Swal.fire({title: '{% trans 'Something went wrong loading your data' %}',
text: '{% trans 'Try reloading the page or check the console for more information.' %}',
icon: 'error',
customClass: {
confirmButton: 'btn btn-primary'
},
buttonsStyling: true})
then log event
then halt the event
end
behavior htmx_error_handler
on htmx:responseError or htmx:afterRequest[detail.failed] or htmx:sendError queue none
-- Check if the event detail contains the xhr object and the status is 403
if event.detail.xhr.status == 403 then
call Swal.fire({
title: '{% trans "Access Denied" %}',
text: '{% trans "You do not have permission to perform this action or access this resource." %}',
icon: 'warning',
customClass: {
confirmButton: 'btn btn-warning' -- Optional: different button style
},
buttonsStyling: true
})
else
call Swal.fire({
title: '{% trans "Something went wrong loading your data" %}',
text: '{% trans "Try reloading the page or check the console for more information." %}',
icon: 'error',
customClass: {
confirmButton: 'btn btn-primary'
},
buttonsStyling: true
})
end
then log event
then halt the event
end
</script>

View File

@@ -5,7 +5,7 @@
{% block title %}{% translate 'Installments' %}{% endblock %}
{% block body %}
<div hx-get="{% url 'installment_plan_transactions' installment_plan_id=installment_plan.id %}" hx-trigger="updated from:window" hx-vals='{"disable_selection": true}' hx-target="closest .offcanvas" class="show-loading" id="transactions-list">
<div hx-get="{% url 'installment_plan_transactions' installment_plan_id=installment_plan.id %}" hx-trigger="updated from:window" hx-target="closest .offcanvas" class="show-loading" id="transactions-list">
{% for transaction in transactions %}
<c-transaction.item :transaction="transaction"></c-transaction.item>
{% endfor %}

View File

@@ -1,3 +1,4 @@
{% load settings %}
{% load pwa %}
{% load formats %}
{% load i18n %}
@@ -5,43 +6,53 @@
{% load webpack_loader %}
<!doctype html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
{% filter site_title %}
{% block title %}
{% endblock title %}
{% endfilter %}
</title>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
{% filter site_title %}
{% block title %}
{% endblock title %}
{% endfilter %}
</title>
{% include 'includes/head/favicons.html' %}
{% progressive_web_app_meta %}
{% include 'includes/head/favicons.html' %}
{% progressive_web_app_meta %}
{% include 'includes/styles.html' %}
{% block extra_styles %}{% endblock %}
{% include 'includes/scripts.html' %}
{% include 'includes/styles.html' %}
{% block extra_styles %}{% endblock %}
{% block extra_js_head %}{% endblock %}
</head>
<body class="font-monospace">
<div _="install hide_amounts
{% include 'includes/scripts.html' %}
{% block extra_js_head %}{% endblock %}
</head>
<body class="font-monospace">
<div _="install hide_amounts
install htmx_error_handler
{% block body_hyperscript %}{% endblock %}"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
{% include 'includes/navbar.html' %}
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
{% include 'includes/navbar.html' %}
<div id="content">
{% block content %}{% endblock %}
</div>
{% include 'includes/offcanvas.html' %}
{% include 'includes/toasts.html' %}
{% settings "DEMO" as demo_mode %}
{% if demo_mode %}
<div class="px-3 m-0" id="demo-mode-alert" hx-preserve>
<div class="alert alert-warning alert-dismissible fade show my-3" role="alert">
<strong>{% trans 'This is a demo!' %}</strong> {% trans 'Any data you add here will be wiped in 24hrs or less' %}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
{% endif %}
{% include 'includes/tools/calculator.html' %}
<div id="content">
{% block content %}{% endblock %}
</div>
{% block extra_js_body %}{% endblock %}
</body>
{% include 'includes/offcanvas.html' %}
{% include 'includes/toasts.html' %}
</div>
{% include 'includes/tools/calculator.html' %}
{% block extra_js_body %}{% endblock %}
</body>
</html>

View File

@@ -160,7 +160,7 @@
</div>
</div>
{# Filter transactions form#}
<div class="collapse" id="collapse-filter">
<div class="collapse" id="collapse-filter" hx-preserve>
<div class="card card-body">
<form _="on change or submit or search trigger updated on window end
install init_tom_select

View File

@@ -5,7 +5,7 @@
{% block title %}{% translate 'Transactions' %}{% endblock %}
{% block body %}
<div hx-get="{% url 'recurring_transaction_transactions' recurring_transaction_id=recurring_transaction.id %}" hx-trigger="updated from:window" hx-vals='{"disable_selection": true}' hx-target="closest .offcanvas" class="show-loading" id="transactions-list">
<div hx-get="{% url 'recurring_transaction_transactions' recurring_transaction_id=recurring_transaction.id %}" hx-trigger="updated from:window" hx-target="closest .offcanvas" class="show-loading" id="transactions-list">
{% for transaction in transactions %}
<c-transaction.item :transaction="transaction"></c-transaction.item>
{% endfor %}

View File

@@ -1,4 +1,6 @@
{% extends "layouts/base_auth.html" %}
{% load i18n %}
{% load settings %}
{% load crispy_forms_tags %}
{% block title %}Login{% endblock %}
@@ -7,15 +9,26 @@
<div>
<div class="container">
<div class="row vh-100 d-flex justify-content-center align-items-center">
<div class="col-md-6 col-xl-4 col-12">
<div class="card shadow-lg">
<div class="card-body">
<h2 class="card-title text-center mb-4">Login</h2>
{% crispy form %}
</div>
</div>
<div class="col-md-6 col-xl-4 col-12">
{% settings "DEMO" as demo_mode %}
{% if demo_mode %}
<div class="card shadow mb-3">
<div class="card-body">
<h1 class="h5 card-title text-center mb-4">{% trans "Welcome to WYGIWYH's demo!" %}</h1>
<p>{% trans 'Use the credentials below to login' %}:</p>
<p>{% trans 'E-mail' %}: <span class="badge text-bg-secondary user-select-all">demo@demo.com</span></p>
<p>{% trans 'Password' %}: <span class="badge text-bg-secondary user-select-all">wygiwyhdemo</span></p>
</div>
</div>
{% endif %}
<div class="card shadow-lg">
<div class="card-body">
<h1 class="h2 card-title text-center mb-4">Login</h1>
{% crispy form %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -11,4 +11,6 @@ python manage.py migrate
# Create flag file to signal migrations are complete
touch /tmp/migrations_complete
python manage.py setup_users
exec python manage.py runserver 0.0.0.0:8000

View File

@@ -13,4 +13,6 @@ python manage.py migrate
# Create flag file to signal migrations are complete
touch /tmp/migrations_complete
python manage.py setup_users
exec gunicorn WYGIWYH.wsgi:application --bind 0.0.0.0:8000 --timeout 600

View File

@@ -14,7 +14,7 @@ djangorestframework~=3.15.2
drf-spectacular~=0.27.2
django-import-export~=4.3.5
gunicorn==22.0.0
gunicorn==23.0.0
whitenoise[brotli]==6.6.0
watchfiles==0.24.0 # https://github.com/samuelcolvin/watchfiles