Compare commits

...

26 Commits
0.8.6 ... 0.9.1

Author SHA1 Message Date
Herculino Trotta
fee1db8660 Merge pull request #141
fix(automatic-exchange-rates): skipping hours due to minutes
2025-02-07 14:34:58 -03:00
Herculino Trotta
4f7fc1c9c8 fix(automatic-exchange-rates): skipping hours due to minutes 2025-02-07 14:34:38 -03:00
Herculino Trotta
f788709f97 Merge pull request #140
automatic exchange rates
2025-02-07 11:49:25 -03:00
Herculino Trotta
1a0de32ef8 locale: update locales 2025-02-07 11:46:57 -03:00
Herculino Trotta
8315adeb4a fix(automatic-exchange-rates): 1-24 should be 0-23 2025-02-07 11:46:33 -03:00
Herculino Trotta
5296820d46 refactor(automatic-exchange-rates): replace fetch_interval with fetch interval type and fetch interval 2025-02-07 11:40:37 -03:00
Herculino Trotta
d5f5053821 Merge pull request #139 from eitchtee/dev
feat: cleanup and format logs
2025-02-07 11:31:40 -03:00
Herculino Trotta
852ffd5634 feat: cleanup and format logs 2025-02-07 11:31:14 -03:00
Herculino Trotta
8cb3f51ea4 Merge pull request #138
feat: add TZ env var
2025-02-07 11:29:48 -03:00
Herculino Trotta
62bfaaa62a feat: add TZ env var 2025-02-07 11:29:28 -03:00
Herculino Trotta
dd1d4292d3 Merge pull request #137
automatic_exchange_rate
2025-02-06 21:48:29 -03:00
Herculino Trotta
93bb34166e feat(ui): auto-resize textareas when typing 2025-02-06 21:40:04 -03:00
Herculino Trotta
8f311d9924 Add Unraid setup details 2025-02-05 15:24:00 -03:00
Herculino Trotta
a5a9f838f5 Merge pull request #135
fix(docker:single): procrastinate starts before django
2025-02-05 10:52:47 -03:00
Herculino Trotta
6c17b3babb fix(docker:single): procrastinate starts before django 2025-02-05 10:52:21 -03:00
Herculino Trotta
d207760ae9 feat(currencies): add automatic exchange rate fetching
Closes #123
2025-02-05 10:16:04 -03:00
Herculino Trotta
996e0ee0eb Merge pull request #133
fix(transactions): transaction convert value doesn't take into account currency's exchange currency
2025-02-03 00:30:42 -03:00
Herculino Trotta
80edf557cb fix(transactions): transaction convert value doesn't take into account currency's exchange currency
account takes precedence
2025-02-03 00:30:26 -03:00
Herculino Trotta
2f3207b1f6 Merge pull request #132 from eitchtee/dev
refactor(currencies): remove currency's code reference in the UI
2025-02-03 00:28:53 -03:00
Herculino Trotta
7b95c806fb refactor(currencies): remove currency's code reference in the UI 2025-02-03 00:28:21 -03:00
Herculino Trotta
06e9383689 Merge pull request #131
refactor(currencies): make currency code non-unique and increase it's size
2025-02-03 00:27:31 -03:00
Herculino Trotta
56862cd025 refactor(currencies): make currency code non-unique and increase it's size 2025-02-03 00:27:11 -03:00
Herculino Trotta
35782cf14c Merge pull request #130
feat: internal code for automatic exchange rate fetching
2025-02-03 00:26:19 -03:00
Herculino Trotta
f7768c8658 feat: internal code for automatic exchange rate fetching 2025-02-03 00:26:00 -03:00
Herculino Trotta
7f8fe6a516 Merge pull request #129
fix: unable to display exchange projected income value
2025-02-03 00:20:15 -03:00
Herculino Trotta
aa8abe0e1c fix: unable to display exchange projected income value 2025-02-03 00:20:00 -03:00
49 changed files with 2191 additions and 424 deletions

View File

@@ -1,6 +1,8 @@
SERVER_NAME=wygiwyh_server
DB_NAME=wygiwyh_pg
TZ=UTC # Change to your timezone. This only affects some async tasks.
DEBUG=false
URL = https://...
HTTPS_ENABLED=true

View File

@@ -79,6 +79,9 @@ $ docker compose up -d
$ docker compose exec -it web python manage.py createsuperuser
```
> [!NOTE]
> If you're using Unraid, you don't need to follow these steps, use the app on the store. Make sure to read the [Unraid section](#unraid) and [Enviroment Variables](#enviroment-variables) for an explanation of all available variables
## Running locally
If you want to run WYGIWYH locally, on your env file:
@@ -105,6 +108,8 @@ All the required Dockerfiles are [here](https://github.com/eitchtee/WYGIWYH/tree
WYGIWYH is available on the Unraid Store. You'll need to provision your own postgres (version 15 or up) database.
To create the first user, open the container's console using Unraid's UI, by clicking on WYGIWYH icon on the Docker page and selecting `Console`, then type `python manage.py createsuperuser`, you'll them be prompted to input your e-mail and password.
## Enviroment Variables
| variable | type | default | explanation |

View File

@@ -0,0 +1,19 @@
import logging
class ProcrastinateFilter(logging.Filter):
# from https://github.com/madzak/python-json-logger/blob/master/src/pythonjsonlogger/jsonlogger.py#L19
_reserved_log_keys = frozenset(
"""args asctime created exc_info exc_text filename
funcName levelname levelno lineno module msecs message msg name pathname
process processName relativeCreated stack_info thread threadName""".split()
)
def filter(self, record: logging.LogRecord):
record.procrastinate = {}
for key, value in vars(record).items():
if not key.startswith("_") and key not in self._reserved_log_keys | {
"procrastinate"
}:
record.procrastinate[key] = value # type: ignore
return True

View File

View File

@@ -166,7 +166,7 @@ LANGUAGES = (
("pt-br", "Português (Brasil)"),
)
TIME_ZONE = "UTC"
TIME_ZONE = os.getenv("TZ", "UTC")
USE_I18N = True
@@ -278,28 +278,42 @@ if "procrastinate" in sys.argv:
"disable_existing_loggers": False,
"formatters": {
"procrastinate": {
"format": "%(asctime)s %(levelname)-7s %(name)s %(message)s"
"format": "[%(asctime)s] - %(levelname)s - %(name)s - %(message)s -> %(procrastinate)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
"standard": {
"format": "[%(asctime)s] - %(levelname)s - %(name)s - %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
},
"filters": {
"procrastinate": {
"()": "WYGIWYH.logs.ProcrastinateFilter.ProcrastinateFilter",
"name": "procrastinate",
},
},
"handlers": {
"procrastinate": {
"level": "DEBUG",
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "procrastinate",
"filters": ["procrastinate"],
},
"console": {
"class": "logging.StreamHandler",
"formatter": "standard",
"level": "INFO",
},
},
"loggers": {
"procrastinate": {
"handlers": ["procrastinate"],
"level": "INFO",
"propagate": False,
"propagate": True,
},
"root": {
"handlers": ["console"],
"handlers": None,
"level": "INFO",
"propagate": False,
},
},
}
@@ -308,24 +322,25 @@ else:
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"procrastinate": {
"format": "%(asctime)s %(levelname)-7s %(name)s %(message)s"
"standard": {
"format": "[%(asctime)s] - %(levelname)s - %(name)s - %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
},
"handlers": {
"procrastinate": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "procrastinate",
},
"console": {
"class": "logging.StreamHandler",
"formatter": "standard",
"level": "INFO",
},
"procrastinate": {
"level": "INFO",
"class": "logging.StreamHandler",
},
},
"loggers": {
"procrastinate": {
"handlers": None,
"level": "INFO",
"propagate": False,
},
"root": {

View File

@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
@app.periodic(cron="0 4 * * *")
@app.task(queueing_lock="remove_old_jobs", pass_context=True)
@app.task(queueing_lock="remove_old_jobs", pass_context=True, name="remove_old_jobs")
async def remove_old_jobs(context, timestamp):
try:
return await builtin_tasks.remove_old_jobs(
@@ -30,7 +30,7 @@ async def remove_old_jobs(context, timestamp):
@app.periodic(cron="0 6 1 * *")
@app.task(queueing_lock="remove_expired_sessions")
@app.task(queueing_lock="remove_expired_sessions", name="remove_expired_sessions")
async def remove_expired_sessions(timestamp=None):
"""Cleanup expired sessions by using Django management command."""
try:

View File

@@ -1,6 +1,6 @@
from django.contrib import admin
from apps.currencies.models import Currency, ExchangeRate
from apps.currencies.models import Currency, ExchangeRate, ExchangeRateService
@admin.register(Currency)
@@ -11,4 +11,19 @@ class CurrencyAdmin(admin.ModelAdmin):
return super().formfield_for_dbfield(db_field, request, **kwargs)
@admin.register(ExchangeRateService)
class ExchangeRateServiceAdmin(admin.ModelAdmin):
list_display = [
"name",
"service_type",
"is_active",
"interval_type",
"fetch_interval",
"last_fetch",
]
list_filter = ["is_active", "service_type"]
search_fields = ["name"]
filter_horizontal = ["target_currencies"]
admin.site.register(ExchangeRate)

View File

@@ -0,0 +1,30 @@
from abc import ABC, abstractmethod
from decimal import Decimal
from typing import List, Tuple, Optional
from django.db.models import QuerySet
from apps.currencies.models import Currency
class ExchangeRateProvider(ABC):
rates_inverted = False
def __init__(self, api_key: Optional[str] = None):
self.api_key = api_key
@abstractmethod
def get_rates(
self, target_currencies: QuerySet, exchange_currencies: set
) -> List[Tuple[Currency, Currency, Decimal]]:
"""Fetch exchange rates for multiple currency pairs"""
raise NotImplementedError("Subclasses must implement get_rates method")
@classmethod
def requires_api_key(cls) -> bool:
"""Return True if the service requires an API key"""
return True
@staticmethod
def invert_rate(rate: Decimal) -> Decimal:
"""Invert the given rate."""
return Decimal("1") / rate

View File

@@ -0,0 +1,223 @@
import logging
from datetime import timedelta
from django.db.models import QuerySet
from django.utils import timezone
from apps.currencies.exchange_rates.providers import (
SynthFinanceProvider,
CoinGeckoFreeProvider,
CoinGeckoProProvider,
)
from apps.currencies.models import ExchangeRateService, ExchangeRate, Currency
logger = logging.getLogger(__name__)
# Map service types to provider classes
PROVIDER_MAPPING = {
"synth_finance": SynthFinanceProvider,
"coingecko_free": CoinGeckoFreeProvider,
"coingecko_pro": CoinGeckoProProvider,
}
class ExchangeRateFetcher:
def _should_fetch_at_hour(service: ExchangeRateService, current_hour: int) -> bool:
"""Check if service should fetch rates at given hour based on interval type."""
try:
if service.interval_type == ExchangeRateService.IntervalType.NOT_ON:
blocked_hours = ExchangeRateService._parse_hour_ranges(
service.fetch_interval
)
should_fetch = current_hour not in blocked_hours
logger.info(
f"NOT_ON check for {service.name}: "
f"current_hour={current_hour}, "
f"blocked_hours={blocked_hours}, "
f"should_fetch={should_fetch}"
)
return should_fetch
if service.interval_type == ExchangeRateService.IntervalType.ON:
allowed_hours = ExchangeRateService._parse_hour_ranges(
service.fetch_interval
)
should_fetch = current_hour in allowed_hours
logger.info(
f"ON check for {service.name}: "
f"current_hour={current_hour}, "
f"allowed_hours={allowed_hours}, "
f"should_fetch={should_fetch}"
)
return should_fetch
if service.interval_type == ExchangeRateService.IntervalType.EVERY:
try:
interval_hours = int(service.fetch_interval)
if service.last_fetch is None:
return True
# Round down to nearest hour
now = timezone.now().replace(minute=0, second=0, microsecond=0)
last_fetch = service.last_fetch.replace(
minute=0, second=0, microsecond=0
)
hours_since_last = (now - last_fetch).total_seconds() / 3600
should_fetch = hours_since_last >= interval_hours
logger.info(
f"EVERY check for {service.name}: "
f"hours_since_last={hours_since_last:.1f}, "
f"interval={interval_hours}, "
f"should_fetch={should_fetch}"
)
return should_fetch
except ValueError:
logger.error(
f"Invalid EVERY interval format for {service.name}: "
f"expected single number, got '{service.fetch_interval}'"
)
return False
return False
except ValueError as e:
logger.error(f"Error parsing fetch_interval for {service.name}: {e}")
return False
@staticmethod
def fetch_due_rates(force: bool = False) -> None:
"""
Fetch rates for all services that are due for update.
Args:
force (bool): If True, fetches all active services regardless of their schedule.
"""
services = ExchangeRateService.objects.filter(is_active=True)
current_time = timezone.now().astimezone()
current_hour = current_time.hour
for service in services:
try:
if force:
logger.info(f"Force fetching rates for {service.name}")
ExchangeRateFetcher._fetch_service_rates(service)
continue
# Check if service should fetch based on interval type
if ExchangeRateFetcher._should_fetch_at_hour(service, current_hour):
logger.info(
f"Fetching rates for {service.name}. "
f"Last fetch: {service.last_fetch}, "
f"Interval type: {service.interval_type}, "
f"Current hour: {current_hour}"
)
ExchangeRateFetcher._fetch_service_rates(service)
else:
logger.debug(
f"Skipping {service.name}. "
f"Current hour: {current_hour}, "
f"Interval type: {service.interval_type}, "
f"Fetch interval: {service.fetch_interval}"
)
except Exception as e:
logger.error(f"Error checking fetch schedule for {service.name}: {e}")
@staticmethod
def _get_unique_currency_pairs(
service: ExchangeRateService,
) -> tuple[QuerySet, set]:
"""
Get unique currency pairs from both target_currencies and target_accounts
Returns a tuple of (target_currencies QuerySet, exchange_currencies set)
"""
# Get currencies from target_currencies
target_currencies = set(service.target_currencies.all())
# Add currencies from target_accounts
for account in service.target_accounts.all():
if account.currency and account.exchange_currency:
target_currencies.add(account.currency)
# Convert back to QuerySet for compatibility with existing code
target_currencies_qs = Currency.objects.filter(
id__in=[curr.id for curr in target_currencies]
)
# Get unique exchange currencies
exchange_currencies = set()
# From target_currencies
for currency in target_currencies:
if currency.exchange_currency:
exchange_currencies.add(currency.exchange_currency)
# From target_accounts
for account in service.target_accounts.all():
if account.exchange_currency:
exchange_currencies.add(account.exchange_currency)
return target_currencies_qs, exchange_currencies
@staticmethod
def _fetch_service_rates(service: ExchangeRateService) -> None:
"""Fetch rates for a specific service"""
try:
provider = service.get_provider()
# Check if API key is required but missing
if provider.requires_api_key() and not service.api_key:
logger.error(f"API key required but not provided for {service.name}")
return
# Get unique currency pairs from both sources
target_currencies, exchange_currencies = (
ExchangeRateFetcher._get_unique_currency_pairs(service)
)
# Skip if no currencies to process
if not target_currencies or not exchange_currencies:
logger.info(f"No currency pairs to process for service {service.name}")
return
rates = provider.get_rates(target_currencies, exchange_currencies)
# Track processed currency pairs to avoid duplicates
processed_pairs = set()
for from_currency, to_currency, rate in rates:
# Create a unique identifier for this currency pair
pair_key = (from_currency.id, to_currency.id)
if pair_key in processed_pairs:
continue
if provider.rates_inverted:
# If rates are inverted, we need to swap currencies
ExchangeRate.objects.create(
from_currency=to_currency,
to_currency=from_currency,
rate=rate,
date=timezone.now(),
)
processed_pairs.add((to_currency.id, from_currency.id))
else:
# If rates are not inverted, we can use them as is
ExchangeRate.objects.create(
from_currency=from_currency,
to_currency=to_currency,
rate=rate,
date=timezone.now(),
)
processed_pairs.add((from_currency.id, to_currency.id))
service.last_fetch = timezone.now()
service.save()
except Exception as e:
logger.error(f"Error fetching rates for {service.name}: {e}")

View File

@@ -0,0 +1,152 @@
import logging
import time
import requests
from decimal import Decimal
from typing import Tuple, List
from django.db.models import QuerySet
from apps.currencies.models import Currency
from apps.currencies.exchange_rates.base import ExchangeRateProvider
logger = logging.getLogger(__name__)
class SynthFinanceProvider(ExchangeRateProvider):
"""Implementation for Synth Finance API (synthfinance.com)"""
BASE_URL = "https://api.synthfinance.com/rates/live"
rates_inverted = False # SynthFinance returns non-inverted rates
def __init__(self, api_key: str = None):
super().__init__(api_key)
self.session = requests.Session()
self.session.headers.update({"Authorization": f"Bearer {self.api_key}"})
def get_rates(
self, target_currencies: QuerySet, exchange_currencies: set
) -> List[Tuple[Currency, Currency, Decimal]]:
results = []
currency_groups = {}
for currency in target_currencies:
if currency.exchange_currency in exchange_currencies:
group = currency_groups.setdefault(currency.exchange_currency.code, [])
group.append(currency)
for base_currency, currencies in currency_groups.items():
try:
to_currencies = ",".join(
currency.code
for currency in currencies
if currency.code != base_currency
)
response = self.session.get(
f"{self.BASE_URL}",
params={"from": base_currency, "to": to_currencies},
)
response.raise_for_status()
data = response.json()
rates = data["data"]["rates"]
for currency in currencies:
if currency.code == base_currency:
rate = Decimal("1")
else:
rate = Decimal(str(rates[currency.code]))
# Return the rate as is, without inversion
results.append((currency.exchange_currency, currency, rate))
credits_used = data["meta"]["credits_used"]
credits_remaining = data["meta"]["credits_remaining"]
logger.info(
f"Synth Finance API call: {credits_used} credits used, {credits_remaining} remaining"
)
except requests.RequestException as e:
logger.error(
f"Error fetching rates from Synth Finance API for base {base_currency}: {e}"
)
except KeyError as e:
logger.error(
f"Unexpected response structure from Synth Finance API for base {base_currency}: {e}"
)
except Exception as e:
logger.error(
f"Unexpected error processing Synth Finance data for base {base_currency}: {e}"
)
return results
class CoinGeckoFreeProvider(ExchangeRateProvider):
"""Implementation for CoinGecko Free API"""
BASE_URL = "https://api.coingecko.com/api/v3"
rates_inverted = True
def __init__(self, api_key: str):
super().__init__(api_key)
self.session = requests.Session()
self.session.headers.update({"x-cg-demo-api-key": api_key})
@classmethod
def requires_api_key(cls) -> bool:
return True
def get_rates(
self, target_currencies: QuerySet, exchange_currencies: set
) -> List[Tuple[Currency, Currency, Decimal]]:
results = []
all_currencies = set(currency.code.lower() for currency in target_currencies)
all_currencies.update(currency.code.lower() for currency in exchange_currencies)
try:
response = self.session.get(
f"{self.BASE_URL}/simple/price",
params={
"ids": ",".join(all_currencies),
"vs_currencies": ",".join(all_currencies),
},
)
response.raise_for_status()
rates_data = response.json()
for target_currency in target_currencies:
if target_currency.exchange_currency in exchange_currencies:
try:
rate = Decimal(
str(
rates_data[target_currency.code.lower()][
target_currency.exchange_currency.code.lower()
]
)
)
# The rate is already inverted, so we don't need to invert it again
results.append(
(target_currency.exchange_currency, target_currency, rate)
)
except KeyError:
logger.error(
f"Rate not found for {target_currency.code} or {target_currency.exchange_currency.code}"
)
except Exception as e:
logger.error(
f"Error calculating rate for {target_currency.code}: {e}"
)
time.sleep(1) # CoinGecko allows 10-30 calls/minute for free tier
except requests.RequestException as e:
logger.error(f"Error fetching rates from CoinGecko API: {e}")
return results
class CoinGeckoProProvider(CoinGeckoFreeProvider):
"""Implementation for CoinGecko Pro API"""
BASE_URL = "https://pro-api.coingecko.com/api/v3/simple/price"
rates_inverted = True
def __init__(self, api_key: str):
super().__init__(api_key)
self.session = requests.Session()
self.session.headers.update({"x-cg-pro-api-key": api_key})

View File

@@ -1,6 +1,7 @@
from crispy_bootstrap5.bootstrap5 import Switch
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout
from crispy_forms.layout import Layout, Row, Column
from django import forms
from django.forms import CharField
from django.utils.translation import gettext_lazy as _
@@ -9,7 +10,7 @@ from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.datepicker import AirDateTimePickerInput
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.tom_select import TomSelect
from apps.currencies.models import Currency, ExchangeRate
from apps.currencies.models import Currency, ExchangeRate, ExchangeRateService
class CurrencyForm(forms.ModelForm):
@@ -99,3 +100,54 @@ class ExchangeRateForm(forms.ModelForm):
),
),
)
class ExchangeRateServiceForm(forms.ModelForm):
class Meta:
model = ExchangeRateService
fields = [
"name",
"service_type",
"is_active",
"api_key",
"interval_type",
"fetch_interval",
"target_currencies",
"target_accounts",
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.form_method = "post"
self.helper.layout = Layout(
"name",
"service_type",
Switch("is_active"),
"api_key",
Row(
Column("interval_type", css_class="form-group col-md-6"),
Column("fetch_interval", css_class="form-group col-md-6"),
),
"target_currencies",
"target_accounts",
)
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
),
)

View File

@@ -0,0 +1,32 @@
# Generated by Django 5.1.5 on 2025-02-02 20:35
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('currencies', '0006_currency_exchange_currency'),
]
operations = [
migrations.CreateModel(
name='ExchangeRateService',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True, verbose_name='Service Name')),
('service_type', models.CharField(choices=[('synth_finance', 'Synth Finance'), ('coingecko', 'CoinGecko')], max_length=255, verbose_name='Service Type')),
('is_active', models.BooleanField(default=True, verbose_name='Active')),
('api_key', models.CharField(blank=True, help_text='API key for the service (if required)', max_length=255, null=True, verbose_name='API Key')),
('fetch_interval_hours', models.PositiveIntegerField(default=24, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Fetch Interval (hours)')),
('last_fetch', models.DateTimeField(blank=True, null=True, verbose_name='Last Successful Fetch')),
('target_currencies', models.ManyToManyField(help_text='Select currencies to fetch exchange rates for. Rates will be fetched for each currency against their exchange_currency.', related_name='exchange_services', to='currencies.currency', verbose_name='Target Currencies')),
],
options={
'verbose_name': 'Exchange Rate Service',
'verbose_name_plural': 'Exchange Rate Services',
'ordering': ['name'],
},
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.1.5 on 2025-02-03 01:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0008_alter_account_name'),
('currencies', '0007_exchangerateservice'),
]
operations = [
migrations.AddField(
model_name='exchangerateservice',
name='target_accounts',
field=models.ManyToManyField(help_text="Select accounts to fetch exchange rates for. Rates will be fetched for each account's currency against their set exchange currency.", related_name='exchange_services', to='accounts.account', verbose_name='Target Accounts'),
),
migrations.AlterField(
model_name='exchangerateservice',
name='target_currencies',
field=models.ManyToManyField(help_text='Select currencies to fetch exchange rates for. Rates will be fetched for each currency against their set exchange currency.', related_name='exchange_services', to='currencies.currency', verbose_name='Target Currencies'),
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.1.5 on 2025-02-03 01:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0008_alter_account_name'),
('currencies', '0008_exchangerateservice_target_accounts_and_more'),
]
operations = [
migrations.AlterField(
model_name='exchangerateservice',
name='target_accounts',
field=models.ManyToManyField(blank=True, help_text="Select accounts to fetch exchange rates for. Rates will be fetched for each account's currency against their set exchange currency.", related_name='exchange_services', to='accounts.account', verbose_name='Target Accounts'),
),
migrations.AlterField(
model_name='exchangerateservice',
name='target_currencies',
field=models.ManyToManyField(blank=True, help_text='Select currencies to fetch exchange rates for. Rates will be fetched for each currency against their set exchange currency.', related_name='exchange_services', to='currencies.currency', verbose_name='Target Currencies'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.5 on 2025-02-03 03:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('currencies', '0009_alter_exchangerateservice_target_accounts_and_more'),
]
operations = [
migrations.AlterField(
model_name='currency',
name='code',
field=models.CharField(max_length=255, verbose_name='Currency Code'),
),
]

View File

@@ -0,0 +1,32 @@
# Generated by Django 5.1.5 on 2025-02-07 02:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('currencies', '0010_alter_currency_code'),
]
operations = [
migrations.RemoveField(
model_name='exchangerateservice',
name='fetch_interval_hours',
),
migrations.AddField(
model_name='exchangerateservice',
name='fetch_interval',
field=models.CharField(default='24', max_length=1000, verbose_name='Interval'),
),
migrations.AddField(
model_name='exchangerateservice',
name='interval_type',
field=models.CharField(choices=[('on', 'On'), ('every', 'Every X hours'), ('not_on', 'Not on')], default='every', max_length=255, verbose_name='Interval Type'),
),
migrations.AlterField(
model_name='exchangerateservice',
name='service_type',
field=models.CharField(choices=[('synth_finance', 'Synth Finance'), ('coingecko_free', 'CoinGecko (Demo/Free)'), ('coingecko_pro', 'CoinGecko (Pro)')], max_length=255, verbose_name='Service Type'),
),
]

View File

@@ -1,11 +1,18 @@
import logging
from typing import Set
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.utils.translation import gettext_lazy as _
logger = logging.getLogger(__name__)
class Currency(models.Model):
code = models.CharField(max_length=10, unique=True, verbose_name=_("Currency Code"))
code = models.CharField(
max_length=255, unique=False, verbose_name=_("Currency Code")
)
name = models.CharField(max_length=50, verbose_name=_("Currency Name"), unique=True)
decimal_places = models.PositiveIntegerField(
default=2,
@@ -78,3 +85,155 @@ class ExchangeRate(models.Model):
raise ValidationError(
{"to_currency": _("From and To currencies cannot be the same.")}
)
class ExchangeRateService(models.Model):
"""Configuration for exchange rate services"""
class ServiceType(models.TextChoices):
SYNTH_FINANCE = "synth_finance", "Synth Finance"
COINGECKO_FREE = "coingecko_free", "CoinGecko (Demo/Free)"
COINGECKO_PRO = "coingecko_pro", "CoinGecko (Pro)"
class IntervalType(models.TextChoices):
ON = "on", _("On")
EVERY = "every", _("Every X hours")
NOT_ON = "not_on", _("Not on")
name = models.CharField(max_length=255, unique=True, verbose_name=_("Service Name"))
service_type = models.CharField(
max_length=255, choices=ServiceType.choices, verbose_name=_("Service Type")
)
is_active = models.BooleanField(default=True, verbose_name=_("Active"))
api_key = models.CharField(
max_length=255,
blank=True,
null=True,
verbose_name=_("API Key"),
help_text=_("API key for the service (if required)"),
)
interval_type = models.CharField(
max_length=255,
choices=IntervalType.choices,
verbose_name=_("Interval Type"),
default=IntervalType.EVERY,
)
fetch_interval = models.CharField(
max_length=1000, verbose_name=_("Interval"), default="24"
)
last_fetch = models.DateTimeField(
null=True, blank=True, verbose_name=_("Last Successful Fetch")
)
target_currencies = models.ManyToManyField(
Currency,
verbose_name=_("Target Currencies"),
help_text=_(
"Select currencies to fetch exchange rates for. Rates will be fetched for each currency against their set exchange currency."
),
related_name="exchange_services",
blank=True,
)
target_accounts = models.ManyToManyField(
"accounts.Account",
verbose_name=_("Target Accounts"),
help_text=_(
"Select accounts to fetch exchange rates for. Rates will be fetched for each account's currency against their set exchange currency."
),
related_name="exchange_services",
blank=True,
)
class Meta:
verbose_name = _("Exchange Rate Service")
verbose_name_plural = _("Exchange Rate Services")
ordering = ["name"]
def __str__(self):
return self.name
def get_provider(self):
from apps.currencies.exchange_rates.fetcher import PROVIDER_MAPPING
provider_class = PROVIDER_MAPPING[self.service_type]
return provider_class(self.api_key)
@staticmethod
def _parse_hour_ranges(interval_str: str) -> Set[int]:
"""
Parse hour ranges and individual hours from string.
Valid formats:
- Single hours: "1,5,9"
- Ranges: "1-5"
- Mixed: "1-5,8,10-12"
Returns set of hours.
"""
hours = set()
for part in interval_str.strip().split(","):
part = part.strip()
if "-" in part:
start, end = part.split("-")
start, end = int(start), int(end)
if not (0 <= start <= 23 and 0 <= end <= 23):
raise ValueError("Hours must be between 0 and 23")
if start > end:
raise ValueError(f"Invalid range: {start}-{end}")
hours.update(range(start, end + 1))
else:
hour = int(part)
if not 0 <= hour <= 23:
raise ValueError("Hours must be between 0 and 23")
hours.add(hour)
return hours
def clean(self):
super().clean()
try:
if self.interval_type == self.IntervalType.EVERY:
if not self.fetch_interval.isdigit():
raise ValidationError(
{
"fetch_interval": _(
"'Every X hours' interval type requires a positive integer."
)
}
)
hours = int(self.fetch_interval)
if hours < 0 or hours > 23:
raise ValidationError(
{
"fetch_interval": _(
"'Every X hours' interval must be between 0 and 23."
)
}
)
else:
try:
# Parse and validate hour ranges
hours = self._parse_hour_ranges(self.fetch_interval)
# Store in normalized format (optional)
self.fetch_interval = ",".join(str(h) for h in sorted(hours))
except ValueError as e:
raise ValidationError(
{
"fetch_interval": _(
"Invalid hour format. Use comma-separated hours (0-23) "
"and/or ranges (e.g., '1-5,8,10-12')."
)
}
)
except ValidationError:
raise
except Exception as e:
raise ValidationError(
{
"fetch_interval": _(
"Invalid format. Please check the requirements for your selected interval type."
)
}
)

View File

@@ -0,0 +1,30 @@
import logging
from procrastinate.contrib.django import app
from apps.currencies.exchange_rates.fetcher import ExchangeRateFetcher
logger = logging.getLogger(__name__)
@app.periodic(cron="0 * * * *") # Run every hour
@app.task(name="automatic_fetch_exchange_rates")
def automatic_fetch_exchange_rates(timestamp=None):
"""Fetch exchange rates for all due services"""
fetcher = ExchangeRateFetcher()
try:
fetcher.fetch_due_rates()
except Exception as e:
logger.error(e, exc_info=True)
@app.task(name="manual_fetch_exchange_rates")
def manual_fetch_exchange_rates(timestamp=None):
"""Fetch exchange rates for all due services"""
fetcher = ExchangeRateFetcher()
try:
fetcher.fetch_due_rates(force=True)
except Exception as e:
logger.error(e, exc_info=True)

View File

@@ -34,4 +34,34 @@ urlpatterns = [
views.exchange_rate_delete,
name="exchange_rate_delete",
),
path(
"automatic-exchange-rates/",
views.exchange_rates_services_index,
name="automatic_exchange_rates_index",
),
path(
"automatic-exchange-rates/list/",
views.exchange_rates_services_list,
name="automatic_exchange_rates_list",
),
path(
"automatic-exchange-rates/add/",
views.exchange_rate_service_add,
name="automatic_exchange_rate_add",
),
path(
"automatic-exchange-rates/force-fetch/",
views.exchange_rate_service_force_fetch,
name="automatic_exchange_rate_force_fetch",
),
path(
"automatic-exchange-rates/<int:pk>/edit/",
views.exchange_rate_service_edit,
name="automatic_exchange_rate_edit",
),
path(
"automatic-exchange-rates/<int:pk>/delete/",
views.exchange_rate_service_delete,
name="automatic_exchange_rate_delete",
),
]

View File

@@ -1,2 +1,3 @@
from .currencies import *
from .exchange_rates import *
from .exchange_rates_services import *

View File

@@ -27,17 +27,17 @@ def exchange_rates_index(request):
@require_http_methods(["GET"])
def exchange_rates_list(request):
pairings = (
ExchangeRate.objects.values("from_currency__code", "to_currency__code")
ExchangeRate.objects.values("from_currency__name", "to_currency__name")
.distinct()
.annotate(
pair=Concat(
"from_currency__code",
"from_currency__name",
Value(" x "),
"to_currency__code",
"to_currency__name",
output_field=CharField(),
)
)
.values_list("pair", "from_currency__code", "to_currency__code")
.values_list("pair", "from_currency__name", "to_currency__name")
)
return render(
@@ -56,7 +56,7 @@ def exchange_rates_list_pair(request):
if from_currency and to_currency:
exchange_rates = ExchangeRate.objects.filter(
from_currency__code=from_currency, to_currency__code=to_currency
from_currency__name=from_currency, to_currency__name=to_currency
).order_by("-date")
else:
exchange_rates = ExchangeRate.objects.all().order_by("-date")

View File

@@ -0,0 +1,122 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import CharField, Value
from django.db.models.functions import Concat
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from apps.common.decorators.htmx import only_htmx
from apps.currencies.forms import ExchangeRateForm, ExchangeRateServiceForm
from apps.currencies.models import ExchangeRate, ExchangeRateService
from apps.currencies.tasks import manual_fetch_exchange_rates
@login_required
@require_http_methods(["GET"])
def exchange_rates_services_index(request):
return render(
request,
"exchange_rates_services/pages/index.html",
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def exchange_rates_services_list(request):
services = ExchangeRateService.objects.all()
return render(
request,
"exchange_rates_services/fragments/list.html",
{"services": services},
)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def exchange_rate_service_add(request):
if request.method == "POST":
form = ExchangeRateServiceForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, _("Service added successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
else:
form = ExchangeRateServiceForm()
return render(
request,
"exchange_rates_services/fragments/add.html",
{"form": form},
)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def exchange_rate_service_edit(request, pk):
service = get_object_or_404(ExchangeRateService, id=pk)
if request.method == "POST":
form = ExchangeRateServiceForm(request.POST, instance=service)
if form.is_valid():
form.save()
messages.success(request, _("Service updated successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
else:
form = ExchangeRateServiceForm(instance=service)
return render(
request,
"exchange_rates_services/fragments/edit.html",
{"form": form, "service": service},
)
@only_htmx
@login_required
@require_http_methods(["DELETE"])
def exchange_rate_service_delete(request, pk):
service = get_object_or_404(ExchangeRateService, id=pk)
service.delete()
messages.success(request, _("Service deleted successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def exchange_rate_service_force_fetch(request):
manual_fetch_exchange_rates.defer()
messages.success(request, _("Services queued successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "toasts",
},
)

View File

@@ -9,7 +9,7 @@ from apps.import_app.services import ImportServiceV1
logger = logging.getLogger(__name__)
@app.task
@app.task(name="process_import")
def process_import(import_run_id: int, file_path: str):
try:
import_run = ImportRun.objects.get(id=import_run_id)

View File

@@ -18,7 +18,7 @@ from apps.transactions.models import (
logger = logging.getLogger(__name__)
@app.task
@app.task(name="check_for_transaction_rules")
def check_for_transaction_rules(
instance_id: int,
signal,

View File

@@ -255,6 +255,20 @@ class Transaction(models.Model):
"suffix": suffix,
"decimal_places": decimal_places,
}
elif self.account.currency.exchange_currency:
converted_amount, prefix, suffix, decimal_places = convert(
self.amount,
to_currency=self.account.currency.exchange_currency,
from_currency=self.account.currency,
date=self.date,
)
if converted_amount:
return {
"amount": converted_amount,
"prefix": prefix,
"suffix": suffix,
"decimal_places": decimal_places,
}
return None

View File

@@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
@app.periodic(cron="0 0 * * *")
@app.task
@app.task(name="generate_recurring_transactions")
def generate_recurring_transactions(timestamp=None):
try:
RecurringTransaction.generate_upcoming_transactions()
@@ -26,7 +26,7 @@ def generate_recurring_transactions(timestamp=None):
@app.periodic(cron="10 1 * * *")
@app.task
@app.task(name="cleanup_deleted_transactions")
def cleanup_deleted_transactions(timestamp=None):
with cachalot_disabled():
if settings.ENABLE_SOFT_DELETE and settings.KEEP_DELETED_TRANSACTIONS_FOR == 0:

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-01 22:04+0000\n"
"POT-Creation-Date: 2025-02-07 11:45-0300\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"
@@ -23,23 +23,24 @@ msgid "Group name"
msgstr ""
#: apps/accounts/forms.py:40 apps/accounts/forms.py:96
#: apps/currencies/forms.py:52 apps/currencies/forms.py:90 apps/dca/forms.py:41
#: apps/dca/forms.py:93 apps/import_app/forms.py:34 apps/rules/forms.py:45
#: apps/rules/forms.py:87 apps/transactions/forms.py:190
#: apps/transactions/forms.py:257 apps/transactions/forms.py:581
#: apps/transactions/forms.py:624 apps/transactions/forms.py:656
#: apps/transactions/forms.py:691 apps/transactions/forms.py:827
#: apps/currencies/forms.py:53 apps/currencies/forms.py:91
#: apps/currencies/forms.py:142 apps/dca/forms.py:41 apps/dca/forms.py:93
#: apps/import_app/forms.py:34 apps/rules/forms.py:45 apps/rules/forms.py:87
#: apps/transactions/forms.py:190 apps/transactions/forms.py:257
#: apps/transactions/forms.py:581 apps/transactions/forms.py:624
#: apps/transactions/forms.py:656 apps/transactions/forms.py:691
#: apps/transactions/forms.py:827
msgid "Update"
msgstr ""
#: apps/accounts/forms.py:48 apps/accounts/forms.py:104
#: apps/common/widgets/tom_select.py:12 apps/currencies/forms.py:60
#: apps/currencies/forms.py:98 apps/dca/forms.py:49 apps/dca/forms.py:102
#: apps/import_app/forms.py:42 apps/rules/forms.py:53 apps/rules/forms.py:95
#: apps/transactions/forms.py:174 apps/transactions/forms.py:199
#: apps/transactions/forms.py:589 apps/transactions/forms.py:632
#: apps/transactions/forms.py:664 apps/transactions/forms.py:699
#: apps/transactions/forms.py:835
#: apps/common/widgets/tom_select.py:12 apps/currencies/forms.py:61
#: apps/currencies/forms.py:99 apps/currencies/forms.py:150
#: apps/dca/forms.py:49 apps/dca/forms.py:102 apps/import_app/forms.py:42
#: apps/rules/forms.py:53 apps/rules/forms.py:95 apps/transactions/forms.py:174
#: apps/transactions/forms.py:199 apps/transactions/forms.py:589
#: apps/transactions/forms.py:632 apps/transactions/forms.py:664
#: apps/transactions/forms.py:699 apps/transactions/forms.py:835
#: templates/account_groups/fragments/list.html:9
#: templates/accounts/fragments/list.html:9
#: templates/categories/fragments/list.html:9
@@ -48,6 +49,7 @@ msgstr ""
#: templates/dca/fragments/strategy/list.html:9
#: templates/entities/fragments/list.html:9
#: templates/exchange_rates/fragments/list.html:10
#: templates/exchange_rates_services/fragments/list.html:10
#: templates/import_app/fragments/profiles/list.html:7
#: templates/import_app/fragments/profiles/list.html:10
#: templates/installment_plans/fragments/list.html:9
@@ -69,7 +71,7 @@ msgstr ""
#: apps/transactions/forms.py:39 apps/transactions/forms.py:291
#: apps/transactions/forms.py:298 apps/transactions/forms.py:478
#: apps/transactions/forms.py:723 apps/transactions/models.py:159
#: apps/transactions/models.py:314 apps/transactions/models.py:494
#: apps/transactions/models.py:328 apps/transactions/models.py:508
msgid "Category"
msgstr ""
@@ -77,8 +79,8 @@ msgstr ""
#: apps/transactions/filters.py:74 apps/transactions/forms.py:47
#: apps/transactions/forms.py:307 apps/transactions/forms.py:315
#: apps/transactions/forms.py:471 apps/transactions/forms.py:716
#: apps/transactions/models.py:165 apps/transactions/models.py:316
#: apps/transactions/models.py:498 templates/includes/navbar.html:105
#: apps/transactions/models.py:165 apps/transactions/models.py:330
#: apps/transactions/models.py:512 templates/includes/navbar.html:105
#: templates/tags/fragments/list.html:5 templates/tags/pages/index.html:4
msgid "Tags"
msgstr ""
@@ -92,6 +94,7 @@ msgstr ""
#: templates/categories/fragments/table.html:16
#: templates/currencies/fragments/list.html:26
#: templates/entities/fragments/table.html:16
#: templates/exchange_rates_services/fragments/list.html:32
#: templates/import_app/fragments/profiles/list.html:36
#: templates/installment_plans/fragments/table.html:16
#: templates/recurring_transactions/fragments/table.html:18
@@ -110,17 +113,17 @@ msgstr ""
msgid "Account Groups"
msgstr ""
#: apps/accounts/models.py:31 apps/currencies/models.py:32
#: apps/accounts/models.py:31 apps/currencies/models.py:39
#: templates/accounts/fragments/list.html:27
msgid "Currency"
msgstr ""
#: apps/accounts/models.py:37 apps/currencies/models.py:20
#: apps/accounts/models.py:37 apps/currencies/models.py:27
#: templates/accounts/fragments/list.html:28
msgid "Exchange Currency"
msgstr ""
#: apps/accounts/models.py:42 apps/currencies/models.py:25
#: apps/accounts/models.py:42 apps/currencies/models.py:32
msgid "Default currency for exchange calculations"
msgstr ""
@@ -147,7 +150,7 @@ msgstr ""
#: apps/accounts/models.py:59 apps/rules/models.py:19
#: apps/transactions/forms.py:59 apps/transactions/forms.py:463
#: apps/transactions/forms.py:708 apps/transactions/models.py:132
#: apps/transactions/models.py:274 apps/transactions/models.py:476
#: apps/transactions/models.py:288 apps/transactions/models.py:490
msgid "Account"
msgstr ""
@@ -345,35 +348,36 @@ msgstr ""
msgid "No results..."
msgstr ""
#: apps/currencies/forms.py:16 apps/currencies/models.py:15
#: apps/currencies/forms.py:17 apps/currencies/models.py:22
msgid "Prefix"
msgstr ""
#: apps/currencies/forms.py:17 apps/currencies/models.py:16
#: apps/currencies/forms.py:18 apps/currencies/models.py:23
msgid "Suffix"
msgstr ""
#: apps/currencies/forms.py:68 apps/dca/models.py:156 apps/rules/models.py:22
#: apps/currencies/forms.py:69 apps/dca/models.py:156 apps/rules/models.py:22
#: apps/transactions/forms.py:63 apps/transactions/forms.py:319
#: apps/transactions/models.py:142
#: templates/dca/fragments/strategy/details.html:52
#: templates/exchange_rates/fragments/table.html:10
#: templates/exchange_rates_services/fragments/table.html:10
msgid "Date"
msgstr ""
#: apps/currencies/models.py:8
#: apps/currencies/models.py:14
msgid "Currency Code"
msgstr ""
#: apps/currencies/models.py:9
#: apps/currencies/models.py:16
msgid "Currency Name"
msgstr ""
#: apps/currencies/models.py:13
#: apps/currencies/models.py:20
msgid "Decimal Places"
msgstr ""
#: apps/currencies/models.py:33 apps/transactions/filters.py:60
#: apps/currencies/models.py:40 apps/transactions/filters.py:60
#: templates/currencies/fragments/list.html:5
#: templates/currencies/pages/index.html:4 templates/includes/navbar.html:119
#: templates/includes/navbar.html:121
@@ -383,36 +387,133 @@ msgstr ""
msgid "Currencies"
msgstr ""
#: apps/currencies/models.py:41
#: apps/currencies/models.py:48
msgid "Currency cannot have itself as exchange currency."
msgstr ""
#: apps/currencies/models.py:52
#: apps/currencies/models.py:59
msgid "From Currency"
msgstr ""
#: apps/currencies/models.py:58
#: apps/currencies/models.py:65
msgid "To Currency"
msgstr ""
#: apps/currencies/models.py:61 apps/currencies/models.py:66
#: apps/currencies/models.py:68 apps/currencies/models.py:73
msgid "Exchange Rate"
msgstr ""
#: apps/currencies/models.py:63
#: apps/currencies/models.py:70
msgid "Date and Time"
msgstr ""
#: apps/currencies/models.py:67 templates/exchange_rates/fragments/list.html:6
#: apps/currencies/models.py:74 templates/exchange_rates/fragments/list.html:6
#: templates/exchange_rates/pages/index.html:4
#: templates/includes/navbar.html:123
msgid "Exchange Rates"
msgstr ""
#: apps/currencies/models.py:79
#: apps/currencies/models.py:86
msgid "From and To currencies cannot be the same."
msgstr ""
#: apps/currencies/models.py:99
msgid "On"
msgstr ""
#: apps/currencies/models.py:100
msgid "Every X hours"
msgstr ""
#: apps/currencies/models.py:101
msgid "Not on"
msgstr ""
#: apps/currencies/models.py:103
msgid "Service Name"
msgstr ""
#: apps/currencies/models.py:105
msgid "Service Type"
msgstr ""
#: apps/currencies/models.py:107 apps/transactions/models.py:71
#: apps/transactions/models.py:90 apps/transactions/models.py:109
#: templates/categories/fragments/list.html:21
#: templates/entities/fragments/list.html:21
#: templates/recurring_transactions/fragments/list.html:21
#: templates/tags/fragments/list.html:21
msgid "Active"
msgstr ""
#: apps/currencies/models.py:112
msgid "API Key"
msgstr ""
#: apps/currencies/models.py:113
msgid "API key for the service (if required)"
msgstr ""
#: apps/currencies/models.py:118
msgid "Interval Type"
msgstr ""
#: apps/currencies/models.py:122
msgid "Interval"
msgstr ""
#: apps/currencies/models.py:125
msgid "Last Successful Fetch"
msgstr ""
#: apps/currencies/models.py:130
msgid "Target Currencies"
msgstr ""
#: apps/currencies/models.py:132
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:140
msgid "Target Accounts"
msgstr ""
#: apps/currencies/models.py:142
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:149
msgid "Exchange Rate Service"
msgstr ""
#: apps/currencies/models.py:150
msgid "Exchange Rate Services"
msgstr ""
#: apps/currencies/models.py:202
msgid "'Every X hours' interval type requires a positive integer."
msgstr ""
#: apps/currencies/models.py:211
msgid "'Every X hours' interval must be between 0 and 23."
msgstr ""
#: apps/currencies/models.py:225
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:236
msgid ""
"Invalid format. Please check the requirements for your selected interval "
"type."
msgstr ""
#: apps/currencies/views/currencies.py:42
msgid "Currency added successfully"
msgstr ""
@@ -437,6 +538,22 @@ msgstr ""
msgid "Exchange rate deleted successfully"
msgstr ""
#: apps/currencies/views/exchange_rates_services.py:46
msgid "Service added successfully"
msgstr ""
#: apps/currencies/views/exchange_rates_services.py:74
msgid "Service updated successfully"
msgstr ""
#: apps/currencies/views/exchange_rates_services.py:100
msgid "Service deleted successfully"
msgstr ""
#: apps/currencies/views/exchange_rates_services.py:115
msgid "Services queued successfully"
msgstr ""
#: apps/dca/models.py:17
msgid "Target Currency"
msgstr ""
@@ -447,7 +564,7 @@ msgstr ""
#: apps/dca/models.py:27 apps/dca/models.py:179 apps/rules/models.py:26
#: apps/transactions/forms.py:333 apps/transactions/models.py:155
#: apps/transactions/models.py:323 apps/transactions/models.py:504
#: apps/transactions/models.py:337 apps/transactions/models.py:518
msgid "Notes"
msgstr ""
@@ -611,7 +728,7 @@ msgstr ""
#: apps/rules/models.py:10 apps/rules/models.py:25
#: apps/transactions/forms.py:325 apps/transactions/models.py:153
#: apps/transactions/models.py:281 apps/transactions/models.py:490
#: apps/transactions/models.py:295 apps/transactions/models.py:504
msgid "Description"
msgstr ""
@@ -620,7 +737,7 @@ msgid "Trigger"
msgstr ""
#: apps/rules/models.py:20 apps/transactions/models.py:139
#: apps/transactions/models.py:279 apps/transactions/models.py:482
#: apps/transactions/models.py:293 apps/transactions/models.py:496
msgid "Type"
msgstr ""
@@ -634,21 +751,21 @@ msgstr ""
#: apps/rules/models.py:23 apps/transactions/forms.py:66
#: apps/transactions/forms.py:322 apps/transactions/forms.py:492
#: apps/transactions/models.py:143 apps/transactions/models.py:297
#: apps/transactions/models.py:506
#: apps/transactions/models.py:143 apps/transactions/models.py:311
#: apps/transactions/models.py:520
msgid "Reference Date"
msgstr ""
#: apps/rules/models.py:24 apps/transactions/models.py:148
#: apps/transactions/models.py:487
#: apps/transactions/models.py:501
msgid "Amount"
msgstr ""
#: apps/rules/models.py:29 apps/transactions/filters.py:81
#: apps/transactions/forms.py:55 apps/transactions/forms.py:486
#: apps/transactions/forms.py:731 apps/transactions/models.py:117
#: apps/transactions/models.py:170 apps/transactions/models.py:319
#: apps/transactions/models.py:501 templates/entities/fragments/list.html:5
#: apps/transactions/models.py:170 apps/transactions/models.py:333
#: apps/transactions/models.py:515 templates/entities/fragments/list.html:5
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:107
msgid "Entities"
msgstr ""
@@ -786,14 +903,6 @@ msgstr ""
msgid "Mute"
msgstr ""
#: apps/transactions/models.py:71 apps/transactions/models.py:90
#: apps/transactions/models.py:109 templates/categories/fragments/list.html:21
#: templates/entities/fragments/list.html:21
#: templates/recurring_transactions/fragments/list.html:21
#: templates/tags/fragments/list.html:21
msgid "Active"
msgstr ""
#: apps/transactions/models.py:73
msgid ""
"Deactivated categories won't be able to be selected when creating new "
@@ -846,11 +955,11 @@ msgstr ""
msgid "Expense"
msgstr ""
#: apps/transactions/models.py:181 apps/transactions/models.py:326
#: apps/transactions/models.py:181 apps/transactions/models.py:340
msgid "Installment Plan"
msgstr ""
#: apps/transactions/models.py:190 apps/transactions/models.py:527
#: apps/transactions/models.py:190 apps/transactions/models.py:541
msgid "Recurring Transaction"
msgstr ""
@@ -882,95 +991,95 @@ msgstr ""
msgid "Transactions"
msgstr ""
#: apps/transactions/models.py:268
#: apps/transactions/models.py:282
msgid "Yearly"
msgstr ""
#: apps/transactions/models.py:269 apps/users/models.py:26
#: apps/transactions/models.py:283 apps/users/models.py:26
#: templates/includes/navbar.html:26
msgid "Monthly"
msgstr ""
#: apps/transactions/models.py:270
#: apps/transactions/models.py:284
msgid "Weekly"
msgstr ""
#: apps/transactions/models.py:271
#: apps/transactions/models.py:285
msgid "Daily"
msgstr ""
#: apps/transactions/models.py:284
#: apps/transactions/models.py:298
msgid "Number of Installments"
msgstr ""
#: apps/transactions/models.py:289
#: apps/transactions/models.py:303
msgid "Installment Start"
msgstr ""
#: apps/transactions/models.py:290
#: apps/transactions/models.py:304
msgid "The installment number to start counting from"
msgstr ""
#: apps/transactions/models.py:295 apps/transactions/models.py:510
#: apps/transactions/models.py:309 apps/transactions/models.py:524
msgid "Start Date"
msgstr ""
#: apps/transactions/models.py:299 apps/transactions/models.py:511
#: apps/transactions/models.py:313 apps/transactions/models.py:525
msgid "End Date"
msgstr ""
#: apps/transactions/models.py:304
#: apps/transactions/models.py:318
msgid "Recurrence"
msgstr ""
#: apps/transactions/models.py:307
#: apps/transactions/models.py:321
msgid "Installment Amount"
msgstr ""
#: apps/transactions/models.py:327 templates/includes/navbar.html:69
#: apps/transactions/models.py:341 templates/includes/navbar.html:69
#: templates/installment_plans/fragments/list.html:5
#: templates/installment_plans/pages/index.html:4
msgid "Installment Plans"
msgstr ""
#: apps/transactions/models.py:469
#: apps/transactions/models.py:483
msgid "day(s)"
msgstr ""
#: apps/transactions/models.py:470
#: apps/transactions/models.py:484
msgid "week(s)"
msgstr ""
#: apps/transactions/models.py:471
#: apps/transactions/models.py:485
msgid "month(s)"
msgstr ""
#: apps/transactions/models.py:472
#: apps/transactions/models.py:486
msgid "year(s)"
msgstr ""
#: apps/transactions/models.py:474
#: apps/transactions/models.py:488
#: templates/recurring_transactions/fragments/list.html:24
msgid "Paused"
msgstr ""
#: apps/transactions/models.py:513
#: apps/transactions/models.py:527
msgid "Recurrence Type"
msgstr ""
#: apps/transactions/models.py:516
#: apps/transactions/models.py:530
msgid "Recurrence Interval"
msgstr ""
#: apps/transactions/models.py:520
#: apps/transactions/models.py:534
msgid "Last Generated Date"
msgstr ""
#: apps/transactions/models.py:523
#: apps/transactions/models.py:537
msgid "Last Generated Reference Date"
msgstr ""
#: apps/transactions/models.py:528 templates/includes/navbar.html:71
#: apps/transactions/models.py:542 templates/includes/navbar.html:71
#: templates/recurring_transactions/fragments/list.html:5
#: templates/recurring_transactions/pages/index.html:4
msgid "Recurring Transactions"
@@ -1171,15 +1280,15 @@ msgstr ""
msgid "Default"
msgstr ""
#: apps/users/forms.py:91 apps/users/models.py:40
#: apps/users/forms.py:91 apps/users/models.py:41
msgid "Date Format"
msgstr ""
#: apps/users/forms.py:96 apps/users/models.py:45
#: apps/users/forms.py:96 apps/users/models.py:46
msgid "Datetime Format"
msgstr ""
#: apps/users/forms.py:102 apps/users/models.py:48
#: apps/users/forms.py:102 apps/users/models.py:49
msgid "Number Format"
msgstr ""
@@ -1195,51 +1304,55 @@ msgstr ""
msgid "Yearly by account"
msgstr ""
#: apps/users/models.py:29 templates/includes/navbar.html:40
msgid "Net Worth"
#: apps/users/models.py:29 templates/net_worth/net_worth.html:9
msgid "Current Net Worth"
msgstr ""
#: apps/users/models.py:30
#: apps/users/models.py:30 templates/net_worth/net_worth.html:9
msgid "Projected Net Worth"
msgstr ""
#: apps/users/models.py:31
msgid "All Transactions"
msgstr ""
#: apps/users/models.py:31 templates/includes/navbar.html:32
#: apps/users/models.py:32 templates/includes/navbar.html:32
msgid "Calendar"
msgstr ""
#: apps/users/models.py:53 apps/users/models.py:59
#: apps/users/models.py:54 apps/users/models.py:60
msgid "Auto"
msgstr ""
#: apps/users/models.py:55
#: apps/users/models.py:56
msgid "Language"
msgstr ""
#: apps/users/models.py:61
#: apps/users/models.py:62
msgid "Time Zone"
msgstr ""
#: apps/users/models.py:67
#: apps/users/models.py:68
msgid "Start page"
msgstr ""
#: apps/users/views.py:60
#: apps/users/views.py:62
msgid "Transaction amounts are now hidden"
msgstr ""
#: apps/users/views.py:63
#: apps/users/views.py:65
msgid "Transaction amounts are now displayed"
msgstr ""
#: apps/users/views.py:81
#: apps/users/views.py:83
msgid "Sounds are now muted"
msgstr ""
#: apps/users/views.py:84
#: apps/users/views.py:86
msgid "Sounds will now play"
msgstr ""
#: apps/users/views.py:100
#: apps/users/views.py:102
msgid "Your settings have been updated"
msgstr ""
@@ -1258,6 +1371,8 @@ msgstr ""
#: templates/dca/fragments/strategy/details.html:63
#: templates/entities/fragments/table.html:23
#: templates/exchange_rates/fragments/table.html:19
#: templates/exchange_rates_services/fragments/list.html:42
#: templates/exchange_rates_services/fragments/table.html:19
#: templates/import_app/fragments/profiles/list.html:44
#: templates/installment_plans/fragments/table.html:23
#: templates/recurring_transactions/fragments/table.html:25
@@ -1276,6 +1391,8 @@ msgstr ""
#: templates/dca/fragments/strategy/list.html:34
#: templates/entities/fragments/table.html:28
#: templates/exchange_rates/fragments/table.html:23
#: templates/exchange_rates_services/fragments/list.html:46
#: templates/exchange_rates_services/fragments/table.html:23
#: templates/import_app/fragments/profiles/list.html:48
#: templates/installment_plans/fragments/table.html:27
#: templates/recurring_transactions/fragments/table.html:29
@@ -1297,6 +1414,8 @@ msgstr ""
#: templates/dca/fragments/strategy/list.html:42
#: templates/entities/fragments/table.html:36
#: templates/exchange_rates/fragments/table.html:31
#: templates/exchange_rates_services/fragments/list.html:53
#: templates/exchange_rates_services/fragments/table.html:31
#: templates/import_app/fragments/profiles/list.html:69
#: templates/import_app/fragments/runs/list.html:102
#: templates/installment_plans/fragments/table.html:56
@@ -1320,6 +1439,8 @@ msgstr ""
#: templates/dca/fragments/strategy/list.html:46
#: templates/entities/fragments/table.html:40
#: templates/exchange_rates/fragments/table.html:36
#: templates/exchange_rates_services/fragments/list.html:57
#: templates/exchange_rates_services/fragments/table.html:36
#: templates/import_app/fragments/profiles/list.html:73
#: templates/import_app/fragments/runs/list.html:106
#: templates/installment_plans/fragments/table.html:48
@@ -1346,6 +1467,8 @@ msgstr ""
#: templates/dca/fragments/strategy/list.html:47
#: templates/entities/fragments/table.html:41
#: templates/exchange_rates/fragments/table.html:37
#: templates/exchange_rates_services/fragments/list.html:58
#: templates/exchange_rates_services/fragments/table.html:37
#: templates/import_app/fragments/profiles/list.html:74
#: templates/rules/fragments/list.html:49
#: templates/rules/fragments/transaction_rule/view.html:61
@@ -1363,6 +1486,8 @@ msgstr ""
#: templates/dca/fragments/strategy/list.html:48
#: templates/entities/fragments/table.html:42
#: templates/exchange_rates/fragments/table.html:38
#: templates/exchange_rates_services/fragments/list.html:59
#: templates/exchange_rates_services/fragments/table.html:38
#: templates/import_app/fragments/profiles/list.html:75
#: templates/import_app/fragments/runs/list.html:108
#: templates/installment_plans/fragments/table.html:62
@@ -1494,37 +1619,37 @@ msgid "Restore"
msgstr ""
#: templates/cotton/ui/account_card.html:15
#: templates/cotton/ui/currency_card.html:13
#: templates/cotton/ui/currency_card.html:10
msgid "projected income"
msgstr ""
#: templates/cotton/ui/account_card.html:37
#: templates/cotton/ui/currency_card.html:35
#: templates/cotton/ui/currency_card.html:32
msgid "projected expenses"
msgstr ""
#: templates/cotton/ui/account_card.html:61
#: templates/cotton/ui/currency_card.html:59
#: templates/cotton/ui/currency_card.html:56
msgid "projected total"
msgstr ""
#: templates/cotton/ui/account_card.html:86
#: templates/cotton/ui/currency_card.html:84
#: templates/cotton/ui/currency_card.html:81
msgid "current income"
msgstr ""
#: templates/cotton/ui/account_card.html:108
#: templates/cotton/ui/currency_card.html:106
#: templates/cotton/ui/currency_card.html:103
msgid "current expenses"
msgstr ""
#: templates/cotton/ui/account_card.html:130
#: templates/cotton/ui/currency_card.html:128
#: templates/cotton/ui/currency_card.html:125
msgid "current total"
msgstr ""
#: templates/cotton/ui/account_card.html:156
#: templates/cotton/ui/currency_card.html:154
#: templates/cotton/ui/currency_card.html:151
msgid "final total"
msgstr ""
@@ -1778,10 +1903,12 @@ msgid "No entities"
msgstr ""
#: templates/exchange_rates/fragments/add.html:5
#: templates/exchange_rates_services/fragments/add.html:5
msgid "Add exchange rate"
msgstr ""
#: templates/exchange_rates/fragments/edit.html:5
#: templates/exchange_rates_services/fragments/edit.html:5
msgid "Edit exchange rate"
msgstr ""
@@ -1794,22 +1921,60 @@ msgid "All"
msgstr ""
#: templates/exchange_rates/fragments/table.html:11
#: templates/exchange_rates_services/fragments/table.html:11
msgid "Pairing"
msgstr ""
#: templates/exchange_rates/fragments/table.html:12
#: templates/exchange_rates_services/fragments/table.html:12
msgid "Rate"
msgstr ""
#: templates/exchange_rates/fragments/table.html:51
#: templates/exchange_rates_services/fragments/table.html:51
msgid "No exchange rates"
msgstr ""
#: templates/exchange_rates/fragments/table.html:58
#: templates/exchange_rates_services/fragments/table.html:58
#: templates/transactions/fragments/list_all.html:47
msgid "Page navigation"
msgstr ""
#: templates/exchange_rates_services/fragments/list.html:6
#: templates/exchange_rates_services/pages/index.html:4
#: templates/includes/navbar.html:133
msgid "Automatic Exchange Rates"
msgstr ""
#: templates/exchange_rates_services/fragments/list.html:21
msgid "Fetch all"
msgstr ""
#: templates/exchange_rates_services/fragments/list.html:33
msgid "Service"
msgstr ""
#: templates/exchange_rates_services/fragments/list.html:34
msgid "Targeting"
msgstr ""
#: templates/exchange_rates_services/fragments/list.html:35
msgid "Last fetch"
msgstr ""
#: templates/exchange_rates_services/fragments/list.html:67
msgid "currencies"
msgstr ""
#: templates/exchange_rates_services/fragments/list.html:67
msgid "accounts"
msgstr ""
#: templates/exchange_rates_services/fragments/list.html:75
msgid "No services configured"
msgstr ""
#: templates/import_app/fragments/profiles/add.html:6
msgid "Add new import profile"
msgstr ""
@@ -1907,6 +2072,10 @@ msgstr ""
msgid "Overview"
msgstr ""
#: templates/includes/navbar.html:40
msgid "Net Worth"
msgstr ""
#: templates/includes/navbar.html:44
msgid "Current"
msgstr ""
@@ -1948,15 +2117,15 @@ msgstr ""
msgid "Rules"
msgstr ""
#: templates/includes/navbar.html:141
#: templates/includes/navbar.html:143
msgid "Only use this if you know what you're doing"
msgstr ""
#: templates/includes/navbar.html:142
#: templates/includes/navbar.html:144
msgid "Django Admin"
msgstr ""
#: templates/includes/navbar.html:151
#: templates/includes/navbar.html:153
msgid "Calculator"
msgstr ""
@@ -2121,14 +2290,6 @@ msgstr ""
msgid "Newest first"
msgstr ""
#: templates/net_worth/net_worth.html:9
msgid "Current Net Worth"
msgstr ""
#: templates/net_worth/net_worth.html:9
msgid "Projected Net Worth"
msgstr ""
#: templates/net_worth/net_worth.html:17
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "By currency"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-01 22:04+0000\n"
"POT-Creation-Date: 2025-02-07 11:45-0300\n"
"PO-Revision-Date: 2025-01-29 06:12+0100\n"
"Last-Translator: Dimitri Decrock <dimitri@fam-decrock.eu>\n"
"Language-Team: \n"
@@ -24,23 +24,24 @@ msgid "Group name"
msgstr "Groepsnaam"
#: apps/accounts/forms.py:40 apps/accounts/forms.py:96
#: apps/currencies/forms.py:52 apps/currencies/forms.py:90 apps/dca/forms.py:41
#: apps/dca/forms.py:93 apps/import_app/forms.py:34 apps/rules/forms.py:45
#: apps/rules/forms.py:87 apps/transactions/forms.py:190
#: apps/transactions/forms.py:257 apps/transactions/forms.py:581
#: apps/transactions/forms.py:624 apps/transactions/forms.py:656
#: apps/transactions/forms.py:691 apps/transactions/forms.py:827
#: apps/currencies/forms.py:53 apps/currencies/forms.py:91
#: apps/currencies/forms.py:142 apps/dca/forms.py:41 apps/dca/forms.py:93
#: apps/import_app/forms.py:34 apps/rules/forms.py:45 apps/rules/forms.py:87
#: apps/transactions/forms.py:190 apps/transactions/forms.py:257
#: apps/transactions/forms.py:581 apps/transactions/forms.py:624
#: apps/transactions/forms.py:656 apps/transactions/forms.py:691
#: apps/transactions/forms.py:827
msgid "Update"
msgstr "Bijwerken"
#: apps/accounts/forms.py:48 apps/accounts/forms.py:104
#: apps/common/widgets/tom_select.py:12 apps/currencies/forms.py:60
#: apps/currencies/forms.py:98 apps/dca/forms.py:49 apps/dca/forms.py:102
#: apps/import_app/forms.py:42 apps/rules/forms.py:53 apps/rules/forms.py:95
#: apps/transactions/forms.py:174 apps/transactions/forms.py:199
#: apps/transactions/forms.py:589 apps/transactions/forms.py:632
#: apps/transactions/forms.py:664 apps/transactions/forms.py:699
#: apps/transactions/forms.py:835
#: apps/common/widgets/tom_select.py:12 apps/currencies/forms.py:61
#: apps/currencies/forms.py:99 apps/currencies/forms.py:150
#: apps/dca/forms.py:49 apps/dca/forms.py:102 apps/import_app/forms.py:42
#: apps/rules/forms.py:53 apps/rules/forms.py:95 apps/transactions/forms.py:174
#: apps/transactions/forms.py:199 apps/transactions/forms.py:589
#: apps/transactions/forms.py:632 apps/transactions/forms.py:664
#: apps/transactions/forms.py:699 apps/transactions/forms.py:835
#: templates/account_groups/fragments/list.html:9
#: templates/accounts/fragments/list.html:9
#: templates/categories/fragments/list.html:9
@@ -49,6 +50,7 @@ msgstr "Bijwerken"
#: templates/dca/fragments/strategy/list.html:9
#: templates/entities/fragments/list.html:9
#: templates/exchange_rates/fragments/list.html:10
#: templates/exchange_rates_services/fragments/list.html:10
#: templates/import_app/fragments/profiles/list.html:7
#: templates/import_app/fragments/profiles/list.html:10
#: templates/installment_plans/fragments/list.html:9
@@ -70,7 +72,7 @@ msgstr "Nieuw saldo"
#: apps/transactions/forms.py:39 apps/transactions/forms.py:291
#: apps/transactions/forms.py:298 apps/transactions/forms.py:478
#: apps/transactions/forms.py:723 apps/transactions/models.py:159
#: apps/transactions/models.py:314 apps/transactions/models.py:494
#: apps/transactions/models.py:328 apps/transactions/models.py:508
msgid "Category"
msgstr "Categorie"
@@ -78,8 +80,8 @@ msgstr "Categorie"
#: apps/transactions/filters.py:74 apps/transactions/forms.py:47
#: apps/transactions/forms.py:307 apps/transactions/forms.py:315
#: apps/transactions/forms.py:471 apps/transactions/forms.py:716
#: apps/transactions/models.py:165 apps/transactions/models.py:316
#: apps/transactions/models.py:498 templates/includes/navbar.html:105
#: apps/transactions/models.py:165 apps/transactions/models.py:330
#: apps/transactions/models.py:512 templates/includes/navbar.html:105
#: templates/tags/fragments/list.html:5 templates/tags/pages/index.html:4
msgid "Tags"
msgstr "Labels"
@@ -93,6 +95,7 @@ msgstr "Labels"
#: templates/categories/fragments/table.html:16
#: templates/currencies/fragments/list.html:26
#: templates/entities/fragments/table.html:16
#: templates/exchange_rates_services/fragments/list.html:32
#: templates/import_app/fragments/profiles/list.html:36
#: templates/installment_plans/fragments/table.html:16
#: templates/recurring_transactions/fragments/table.html:18
@@ -111,17 +114,17 @@ msgstr "Accountgroep"
msgid "Account Groups"
msgstr "Accountgroepen"
#: apps/accounts/models.py:31 apps/currencies/models.py:32
#: apps/accounts/models.py:31 apps/currencies/models.py:39
#: templates/accounts/fragments/list.html:27
msgid "Currency"
msgstr "Munteenheid"
#: apps/accounts/models.py:37 apps/currencies/models.py:20
#: apps/accounts/models.py:37 apps/currencies/models.py:27
#: templates/accounts/fragments/list.html:28
msgid "Exchange Currency"
msgstr "Eenheid Wisselgeld"
#: apps/accounts/models.py:42 apps/currencies/models.py:25
#: apps/accounts/models.py:42 apps/currencies/models.py:32
msgid "Default currency for exchange calculations"
msgstr "Standaard munteenheid voor wisselberekeningen"
@@ -152,7 +155,7 @@ msgstr ""
#: apps/accounts/models.py:59 apps/rules/models.py:19
#: apps/transactions/forms.py:59 apps/transactions/forms.py:463
#: apps/transactions/forms.py:708 apps/transactions/models.py:132
#: apps/transactions/models.py:274 apps/transactions/models.py:476
#: apps/transactions/models.py:288 apps/transactions/models.py:490
msgid "Account"
msgstr "Rekening"
@@ -351,35 +354,36 @@ msgstr "Leegmaken"
msgid "No results..."
msgstr "Geen resultaten..."
#: apps/currencies/forms.py:16 apps/currencies/models.py:15
#: apps/currencies/forms.py:17 apps/currencies/models.py:22
msgid "Prefix"
msgstr "Voorvoegsel"
#: apps/currencies/forms.py:17 apps/currencies/models.py:16
#: apps/currencies/forms.py:18 apps/currencies/models.py:23
msgid "Suffix"
msgstr "Achtervoegsel"
#: apps/currencies/forms.py:68 apps/dca/models.py:156 apps/rules/models.py:22
#: apps/currencies/forms.py:69 apps/dca/models.py:156 apps/rules/models.py:22
#: apps/transactions/forms.py:63 apps/transactions/forms.py:319
#: apps/transactions/models.py:142
#: templates/dca/fragments/strategy/details.html:52
#: templates/exchange_rates/fragments/table.html:10
#: templates/exchange_rates_services/fragments/table.html:10
msgid "Date"
msgstr "Datum"
#: apps/currencies/models.py:8
#: apps/currencies/models.py:14
msgid "Currency Code"
msgstr "Munteenheids Code"
#: apps/currencies/models.py:9
#: apps/currencies/models.py:16
msgid "Currency Name"
msgstr "Munteenheids Naam"
#: apps/currencies/models.py:13
#: apps/currencies/models.py:20
msgid "Decimal Places"
msgstr "Cijfers na de komma"
#: apps/currencies/models.py:33 apps/transactions/filters.py:60
#: apps/currencies/models.py:40 apps/transactions/filters.py:60
#: templates/currencies/fragments/list.html:5
#: templates/currencies/pages/index.html:4 templates/includes/navbar.html:119
#: templates/includes/navbar.html:121
@@ -389,36 +393,149 @@ msgstr "Cijfers na de komma"
msgid "Currencies"
msgstr "Munteenheden"
#: apps/currencies/models.py:41
#: apps/currencies/models.py:48
msgid "Currency cannot have itself as exchange currency."
msgstr "Munteenheid kan zichzelf niet als ruilmiddel hebben."
#: apps/currencies/models.py:52
#: apps/currencies/models.py:59
msgid "From Currency"
msgstr "Van Munteenheid"
#: apps/currencies/models.py:58
#: apps/currencies/models.py:65
msgid "To Currency"
msgstr "Naar Munteenheid"
#: apps/currencies/models.py:61 apps/currencies/models.py:66
#: apps/currencies/models.py:68 apps/currencies/models.py:73
msgid "Exchange Rate"
msgstr "Wisselkoers"
#: apps/currencies/models.py:63
#: apps/currencies/models.py:70
msgid "Date and Time"
msgstr "Datum en Tijd"
#: apps/currencies/models.py:67 templates/exchange_rates/fragments/list.html:6
#: apps/currencies/models.py:74 templates/exchange_rates/fragments/list.html:6
#: templates/exchange_rates/pages/index.html:4
#: templates/includes/navbar.html:123
msgid "Exchange Rates"
msgstr "Wisselkoersen"
#: apps/currencies/models.py:79
#: apps/currencies/models.py:86
msgid "From and To currencies cannot be the same."
msgstr "Van en Naar munteenheid kunnen niet dezelfde zijn."
#: apps/currencies/models.py:99
msgid "On"
msgstr ""
#: apps/currencies/models.py:100
msgid "Every X hours"
msgstr ""
#: apps/currencies/models.py:101
msgid "Not on"
msgstr ""
#: apps/currencies/models.py:103
msgid "Service Name"
msgstr ""
#: apps/currencies/models.py:105
#, fuzzy
#| msgid "Recurrence Type"
msgid "Service Type"
msgstr "Type Terugkeerpatroon"
#: apps/currencies/models.py:107 apps/transactions/models.py:71
#: apps/transactions/models.py:90 apps/transactions/models.py:109
#: templates/categories/fragments/list.html:21
#: templates/entities/fragments/list.html:21
#: templates/recurring_transactions/fragments/list.html:21
#: templates/tags/fragments/list.html:21
msgid "Active"
msgstr "Actief"
#: apps/currencies/models.py:112
msgid "API Key"
msgstr ""
#: apps/currencies/models.py:113
msgid "API key for the service (if required)"
msgstr ""
#: apps/currencies/models.py:118
#, fuzzy
#| msgid "Internal Note"
msgid "Interval Type"
msgstr "Interne opmerking"
#: apps/currencies/models.py:122
#, fuzzy
#| msgid "Internal ID"
msgid "Interval"
msgstr "Interne ID"
#: apps/currencies/models.py:125
#, fuzzy
#| msgid "Successful Items"
msgid "Last Successful Fetch"
msgstr "Succesvolle Artikelen"
#: apps/currencies/models.py:130
#, fuzzy
#| msgid "Target Currency"
msgid "Target Currencies"
msgstr "Doel Munteenheid"
#: apps/currencies/models.py:132
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:140
#, fuzzy
#| msgid "To Account"
msgid "Target Accounts"
msgstr "Naar rekening"
#: apps/currencies/models.py:142
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:149
#, fuzzy
#| msgid "Exchange Rate"
msgid "Exchange Rate Service"
msgstr "Wisselkoers"
#: apps/currencies/models.py:150
#, fuzzy
#| msgid "Exchange Rates"
msgid "Exchange Rate Services"
msgstr "Wisselkoersen"
#: apps/currencies/models.py:202
msgid "'Every X hours' interval type requires a positive integer."
msgstr ""
#: apps/currencies/models.py:211
msgid "'Every X hours' interval must be between 0 and 23."
msgstr ""
#: apps/currencies/models.py:225
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:236
msgid ""
"Invalid format. Please check the requirements for your selected interval "
"type."
msgstr ""
#: apps/currencies/views/currencies.py:42
msgid "Currency added successfully"
msgstr "Munteenheid succesvol toegevoegd"
@@ -443,6 +560,30 @@ msgstr "Wisselkoers succesvol bijgewerkt"
msgid "Exchange rate deleted successfully"
msgstr "Wisselkoers succesvol verwijderd"
#: apps/currencies/views/exchange_rates_services.py:46
#, fuzzy
#| msgid "Rule added successfully"
msgid "Service added successfully"
msgstr "Regel succesvol toegevoegd"
#: apps/currencies/views/exchange_rates_services.py:74
#, fuzzy
#| msgid "Rule updated successfully"
msgid "Service updated successfully"
msgstr "Regel succesvol bijgewerkt"
#: apps/currencies/views/exchange_rates_services.py:100
#, fuzzy
#| msgid "Rule deleted successfully"
msgid "Service deleted successfully"
msgstr "Regel succesvol verwijderd"
#: apps/currencies/views/exchange_rates_services.py:115
#, fuzzy
#| msgid "Rule updated successfully"
msgid "Services queued successfully"
msgstr "Regel succesvol bijgewerkt"
#: apps/dca/models.py:17
msgid "Target Currency"
msgstr "Doel Munteenheid"
@@ -453,7 +594,7 @@ msgstr "Betaal Munteenheid"
#: apps/dca/models.py:27 apps/dca/models.py:179 apps/rules/models.py:26
#: apps/transactions/forms.py:333 apps/transactions/models.py:155
#: apps/transactions/models.py:323 apps/transactions/models.py:504
#: apps/transactions/models.py:337 apps/transactions/models.py:518
msgid "Notes"
msgstr "Opmerkingen"
@@ -617,7 +758,7 @@ msgstr "Een waarde voor dit veld bestaat al in de regel."
#: apps/rules/models.py:10 apps/rules/models.py:25
#: apps/transactions/forms.py:325 apps/transactions/models.py:153
#: apps/transactions/models.py:281 apps/transactions/models.py:490
#: apps/transactions/models.py:295 apps/transactions/models.py:504
msgid "Description"
msgstr "Beschrijving"
@@ -626,7 +767,7 @@ msgid "Trigger"
msgstr "Trigger"
#: apps/rules/models.py:20 apps/transactions/models.py:139
#: apps/transactions/models.py:279 apps/transactions/models.py:482
#: apps/transactions/models.py:293 apps/transactions/models.py:496
msgid "Type"
msgstr "Soort"
@@ -640,21 +781,21 @@ msgstr "Betaald"
#: apps/rules/models.py:23 apps/transactions/forms.py:66
#: apps/transactions/forms.py:322 apps/transactions/forms.py:492
#: apps/transactions/models.py:143 apps/transactions/models.py:297
#: apps/transactions/models.py:506
#: apps/transactions/models.py:143 apps/transactions/models.py:311
#: apps/transactions/models.py:520
msgid "Reference Date"
msgstr "Referentiedatum"
#: apps/rules/models.py:24 apps/transactions/models.py:148
#: apps/transactions/models.py:487
#: apps/transactions/models.py:501
msgid "Amount"
msgstr "Bedrag"
#: apps/rules/models.py:29 apps/transactions/filters.py:81
#: apps/transactions/forms.py:55 apps/transactions/forms.py:486
#: apps/transactions/forms.py:731 apps/transactions/models.py:117
#: apps/transactions/models.py:170 apps/transactions/models.py:319
#: apps/transactions/models.py:501 templates/entities/fragments/list.html:5
#: apps/transactions/models.py:170 apps/transactions/models.py:333
#: apps/transactions/models.py:515 templates/entities/fragments/list.html:5
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:107
msgid "Entities"
msgstr "Bedrijven"
@@ -792,14 +933,6 @@ msgstr "De einddatum moet na de begindatum vallen"
msgid "Mute"
msgstr "Gedempt"
#: apps/transactions/models.py:71 apps/transactions/models.py:90
#: apps/transactions/models.py:109 templates/categories/fragments/list.html:21
#: templates/entities/fragments/list.html:21
#: templates/recurring_transactions/fragments/list.html:21
#: templates/tags/fragments/list.html:21
msgid "Active"
msgstr "Actief"
#: apps/transactions/models.py:73
msgid ""
"Deactivated categories won't be able to be selected when creating new "
@@ -858,11 +991,11 @@ msgstr "Ontvangsten Transactie"
msgid "Expense"
msgstr "Uitgave Transactie"
#: apps/transactions/models.py:181 apps/transactions/models.py:326
#: apps/transactions/models.py:181 apps/transactions/models.py:340
msgid "Installment Plan"
msgstr "Afbetalingsplan"
#: apps/transactions/models.py:190 apps/transactions/models.py:527
#: apps/transactions/models.py:190 apps/transactions/models.py:541
msgid "Recurring Transaction"
msgstr "Terugkerende verrichting"
@@ -894,95 +1027,95 @@ msgstr "Verrichting"
msgid "Transactions"
msgstr "Verrichtingen"
#: apps/transactions/models.py:268
#: apps/transactions/models.py:282
msgid "Yearly"
msgstr "Jaarlijks"
#: apps/transactions/models.py:269 apps/users/models.py:26
#: apps/transactions/models.py:283 apps/users/models.py:26
#: templates/includes/navbar.html:26
msgid "Monthly"
msgstr "Maandelijks"
#: apps/transactions/models.py:270
#: apps/transactions/models.py:284
msgid "Weekly"
msgstr "Wekelijks"
#: apps/transactions/models.py:271
#: apps/transactions/models.py:285
msgid "Daily"
msgstr "Dagelijks"
#: apps/transactions/models.py:284
#: apps/transactions/models.py:298
msgid "Number of Installments"
msgstr "Aantal aflossingen"
#: apps/transactions/models.py:289
#: apps/transactions/models.py:303
msgid "Installment Start"
msgstr "Begin afbetaling"
#: apps/transactions/models.py:290
#: apps/transactions/models.py:304
msgid "The installment number to start counting from"
msgstr "Het nummer van de aflevering om mee te beginnen"
#: apps/transactions/models.py:295 apps/transactions/models.py:510
#: apps/transactions/models.py:309 apps/transactions/models.py:524
msgid "Start Date"
msgstr "Startdatum"
#: apps/transactions/models.py:299 apps/transactions/models.py:511
#: apps/transactions/models.py:313 apps/transactions/models.py:525
msgid "End Date"
msgstr "Einddatum"
#: apps/transactions/models.py:304
#: apps/transactions/models.py:318
msgid "Recurrence"
msgstr "Terugkeerpatroon"
#: apps/transactions/models.py:307
#: apps/transactions/models.py:321
msgid "Installment Amount"
msgstr "Termijnbedrag"
#: apps/transactions/models.py:327 templates/includes/navbar.html:69
#: apps/transactions/models.py:341 templates/includes/navbar.html:69
#: templates/installment_plans/fragments/list.html:5
#: templates/installment_plans/pages/index.html:4
msgid "Installment Plans"
msgstr "Afbetalingsplannen"
#: apps/transactions/models.py:469
#: apps/transactions/models.py:483
msgid "day(s)"
msgstr "dag(en)"
#: apps/transactions/models.py:470
#: apps/transactions/models.py:484
msgid "week(s)"
msgstr "we(e)k(en)"
#: apps/transactions/models.py:471
#: apps/transactions/models.py:485
msgid "month(s)"
msgstr "maand(en)"
#: apps/transactions/models.py:472
#: apps/transactions/models.py:486
msgid "year(s)"
msgstr "ja(a)r(en)"
#: apps/transactions/models.py:474
#: apps/transactions/models.py:488
#: templates/recurring_transactions/fragments/list.html:24
msgid "Paused"
msgstr "Gepauzeerd"
#: apps/transactions/models.py:513
#: apps/transactions/models.py:527
msgid "Recurrence Type"
msgstr "Type Terugkeerpatroon"
#: apps/transactions/models.py:516
#: apps/transactions/models.py:530
msgid "Recurrence Interval"
msgstr "Terugkeer Interval"
#: apps/transactions/models.py:520
#: apps/transactions/models.py:534
msgid "Last Generated Date"
msgstr "Laatste Gegenereerde Datum"
#: apps/transactions/models.py:523
#: apps/transactions/models.py:537
msgid "Last Generated Reference Date"
msgstr "Laatste Gegenereerde Referentiedatum"
#: apps/transactions/models.py:528 templates/includes/navbar.html:71
#: apps/transactions/models.py:542 templates/includes/navbar.html:71
#: templates/recurring_transactions/fragments/list.html:5
#: templates/recurring_transactions/pages/index.html:4
msgid "Recurring Transactions"
@@ -1187,15 +1320,15 @@ msgstr "Deze gebruiker is gedeactiveerd"
msgid "Default"
msgstr "Standaard"
#: apps/users/forms.py:91 apps/users/models.py:40
#: apps/users/forms.py:91 apps/users/models.py:41
msgid "Date Format"
msgstr "Datumnotatie"
#: apps/users/forms.py:96 apps/users/models.py:45
#: apps/users/forms.py:96 apps/users/models.py:46
msgid "Datetime Format"
msgstr "Tijdsnotatie"
#: apps/users/forms.py:102 apps/users/models.py:48
#: apps/users/forms.py:102 apps/users/models.py:49
msgid "Number Format"
msgstr "Schrijfwijze Nummers"
@@ -1211,51 +1344,55 @@ msgstr "Jaarlijks per munteenheid"
msgid "Yearly by account"
msgstr "Jaarlijks per rekening"
#: apps/users/models.py:29 templates/includes/navbar.html:40
msgid "Net Worth"
msgstr "Netto Waarde"
#: apps/users/models.py:29 templates/net_worth/net_worth.html:9
msgid "Current Net Worth"
msgstr "Huidige Nettowaarde"
#: apps/users/models.py:30
#: apps/users/models.py:30 templates/net_worth/net_worth.html:9
msgid "Projected Net Worth"
msgstr "Verwachte Nettowaarde"
#: apps/users/models.py:31
msgid "All Transactions"
msgstr "Alle Verrichtingen"
#: apps/users/models.py:31 templates/includes/navbar.html:32
#: apps/users/models.py:32 templates/includes/navbar.html:32
msgid "Calendar"
msgstr "Kalender"
#: apps/users/models.py:53 apps/users/models.py:59
#: apps/users/models.py:54 apps/users/models.py:60
msgid "Auto"
msgstr "Automatisch"
#: apps/users/models.py:55
#: apps/users/models.py:56
msgid "Language"
msgstr "Taal"
#: apps/users/models.py:61
#: apps/users/models.py:62
msgid "Time Zone"
msgstr "Tijdszone"
#: apps/users/models.py:67
#: apps/users/models.py:68
msgid "Start page"
msgstr "Startpagina"
#: apps/users/views.py:60
#: apps/users/views.py:62
msgid "Transaction amounts are now hidden"
msgstr "Verrichtingsbedragen worden nu verborgen"
#: apps/users/views.py:63
#: apps/users/views.py:65
msgid "Transaction amounts are now displayed"
msgstr "Verrichtingsbedragen worden nu weergegeven"
#: apps/users/views.py:81
#: apps/users/views.py:83
msgid "Sounds are now muted"
msgstr "De Geluiden zijn nu gedempt"
#: apps/users/views.py:84
#: apps/users/views.py:86
msgid "Sounds will now play"
msgstr "De geluiden worden nu afgespeeld"
#: apps/users/views.py:100
#: apps/users/views.py:102
msgid "Your settings have been updated"
msgstr "Jouw instellingen zijn bijgewerkt"
@@ -1274,6 +1411,8 @@ msgstr "Rekeningsgroep bewerken"
#: templates/dca/fragments/strategy/details.html:63
#: templates/entities/fragments/table.html:23
#: templates/exchange_rates/fragments/table.html:19
#: templates/exchange_rates_services/fragments/list.html:42
#: templates/exchange_rates_services/fragments/table.html:19
#: templates/import_app/fragments/profiles/list.html:44
#: templates/installment_plans/fragments/table.html:23
#: templates/recurring_transactions/fragments/table.html:25
@@ -1292,6 +1431,8 @@ msgstr "Acties"
#: templates/dca/fragments/strategy/list.html:34
#: templates/entities/fragments/table.html:28
#: templates/exchange_rates/fragments/table.html:23
#: templates/exchange_rates_services/fragments/list.html:46
#: templates/exchange_rates_services/fragments/table.html:23
#: templates/import_app/fragments/profiles/list.html:48
#: templates/installment_plans/fragments/table.html:27
#: templates/recurring_transactions/fragments/table.html:29
@@ -1313,6 +1454,8 @@ msgstr "Bijwerken"
#: templates/dca/fragments/strategy/list.html:42
#: templates/entities/fragments/table.html:36
#: templates/exchange_rates/fragments/table.html:31
#: templates/exchange_rates_services/fragments/list.html:53
#: templates/exchange_rates_services/fragments/table.html:31
#: templates/import_app/fragments/profiles/list.html:69
#: templates/import_app/fragments/runs/list.html:102
#: templates/installment_plans/fragments/table.html:56
@@ -1336,6 +1479,8 @@ msgstr "Verwijderen"
#: templates/dca/fragments/strategy/list.html:46
#: templates/entities/fragments/table.html:40
#: templates/exchange_rates/fragments/table.html:36
#: templates/exchange_rates_services/fragments/list.html:57
#: templates/exchange_rates_services/fragments/table.html:36
#: templates/import_app/fragments/profiles/list.html:73
#: templates/import_app/fragments/runs/list.html:106
#: templates/installment_plans/fragments/table.html:48
@@ -1362,6 +1507,8 @@ msgstr "Weet je het zeker?"
#: templates/dca/fragments/strategy/list.html:47
#: templates/entities/fragments/table.html:41
#: templates/exchange_rates/fragments/table.html:37
#: templates/exchange_rates_services/fragments/list.html:58
#: templates/exchange_rates_services/fragments/table.html:37
#: templates/import_app/fragments/profiles/list.html:74
#: templates/rules/fragments/list.html:49
#: templates/rules/fragments/transaction_rule/view.html:61
@@ -1379,6 +1526,8 @@ msgstr "Je kunt dit niet meer terugdraaien!"
#: templates/dca/fragments/strategy/list.html:48
#: templates/entities/fragments/table.html:42
#: templates/exchange_rates/fragments/table.html:38
#: templates/exchange_rates_services/fragments/list.html:59
#: templates/exchange_rates_services/fragments/table.html:38
#: templates/import_app/fragments/profiles/list.html:75
#: templates/import_app/fragments/runs/list.html:108
#: templates/installment_plans/fragments/table.html:62
@@ -1510,37 +1659,37 @@ msgid "Restore"
msgstr ""
#: templates/cotton/ui/account_card.html:15
#: templates/cotton/ui/currency_card.html:13
#: templates/cotton/ui/currency_card.html:10
msgid "projected income"
msgstr "verwachte inkomsten"
#: templates/cotton/ui/account_card.html:37
#: templates/cotton/ui/currency_card.html:35
#: templates/cotton/ui/currency_card.html:32
msgid "projected expenses"
msgstr "verwachte uitgaven"
#: templates/cotton/ui/account_card.html:61
#: templates/cotton/ui/currency_card.html:59
#: templates/cotton/ui/currency_card.html:56
msgid "projected total"
msgstr "verwachte totaal"
#: templates/cotton/ui/account_card.html:86
#: templates/cotton/ui/currency_card.html:84
#: templates/cotton/ui/currency_card.html:81
msgid "current income"
msgstr "huidige inkomsten"
#: templates/cotton/ui/account_card.html:108
#: templates/cotton/ui/currency_card.html:106
#: templates/cotton/ui/currency_card.html:103
msgid "current expenses"
msgstr "huidige uitgaven"
#: templates/cotton/ui/account_card.html:130
#: templates/cotton/ui/currency_card.html:128
#: templates/cotton/ui/currency_card.html:125
msgid "current total"
msgstr "huidige totaal"
#: templates/cotton/ui/account_card.html:156
#: templates/cotton/ui/currency_card.html:154
#: templates/cotton/ui/currency_card.html:151
msgid "final total"
msgstr "eindtotaal"
@@ -1794,10 +1943,12 @@ msgid "No entities"
msgstr "Geen bedrijven"
#: templates/exchange_rates/fragments/add.html:5
#: templates/exchange_rates_services/fragments/add.html:5
msgid "Add exchange rate"
msgstr "Wisselkoers toevoegen"
#: templates/exchange_rates/fragments/edit.html:5
#: templates/exchange_rates_services/fragments/edit.html:5
msgid "Edit exchange rate"
msgstr "Wisselkoers bewerken"
@@ -1810,22 +1961,68 @@ msgid "All"
msgstr "Allemaal"
#: templates/exchange_rates/fragments/table.html:11
#: templates/exchange_rates_services/fragments/table.html:11
msgid "Pairing"
msgstr "Koppelen"
#: templates/exchange_rates/fragments/table.html:12
#: templates/exchange_rates_services/fragments/table.html:12
msgid "Rate"
msgstr "Tarief"
#: templates/exchange_rates/fragments/table.html:51
#: templates/exchange_rates_services/fragments/table.html:51
msgid "No exchange rates"
msgstr "Geen wisselkoersen"
#: templates/exchange_rates/fragments/table.html:58
#: templates/exchange_rates_services/fragments/table.html:58
#: templates/transactions/fragments/list_all.html:47
msgid "Page navigation"
msgstr "Paginanavigatie"
#: templates/exchange_rates_services/fragments/list.html:6
#: templates/exchange_rates_services/pages/index.html:4
#: templates/includes/navbar.html:133
#, fuzzy
#| msgid "Exchange Rates"
msgid "Automatic Exchange Rates"
msgstr "Wisselkoersen"
#: templates/exchange_rates_services/fragments/list.html:21
msgid "Fetch all"
msgstr ""
#: templates/exchange_rates_services/fragments/list.html:33
#, fuzzy
#| msgid "Overview"
msgid "Service"
msgstr "Overzicht"
#: templates/exchange_rates_services/fragments/list.html:34
msgid "Targeting"
msgstr ""
#: templates/exchange_rates_services/fragments/list.html:35
msgid "Last fetch"
msgstr ""
#: templates/exchange_rates_services/fragments/list.html:67
#, fuzzy
#| msgid "Currencies"
msgid "currencies"
msgstr "Munteenheden"
#: templates/exchange_rates_services/fragments/list.html:67
#, fuzzy
#| msgid "Accounts"
msgid "accounts"
msgstr "Rekeningen"
#: templates/exchange_rates_services/fragments/list.html:75
msgid "No services configured"
msgstr ""
#: templates/import_app/fragments/profiles/add.html:6
msgid "Add new import profile"
msgstr "Nieuw importprofiel toevoegen"
@@ -1924,6 +2121,10 @@ msgstr "Navigatie Knop"
msgid "Overview"
msgstr "Overzicht"
#: templates/includes/navbar.html:40
msgid "Net Worth"
msgstr "Netto Waarde"
#: templates/includes/navbar.html:44
msgid "Current"
msgstr "Huidige"
@@ -1965,15 +2166,15 @@ msgstr "Automatisatie"
msgid "Rules"
msgstr "Regels"
#: templates/includes/navbar.html:141
#: templates/includes/navbar.html:143
msgid "Only use this if you know what you're doing"
msgstr "Gebruik dit alleen als je weet wat je doet"
#: templates/includes/navbar.html:142
#: templates/includes/navbar.html:144
msgid "Django Admin"
msgstr "Django Beheerder"
#: templates/includes/navbar.html:151
#: templates/includes/navbar.html:153
msgid "Calculator"
msgstr "Rekenmachine"
@@ -2142,14 +2343,6 @@ msgstr "Oudste eerst"
msgid "Newest first"
msgstr "Nieuwste eerst"
#: templates/net_worth/net_worth.html:9
msgid "Current Net Worth"
msgstr "Huidige Nettowaarde"
#: templates/net_worth/net_worth.html:9
msgid "Projected Net Worth"
msgstr "Verwachte Nettowaarde"
#: templates/net_worth/net_worth.html:17
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "By currency"
@@ -2385,3 +2578,8 @@ msgstr "Jaaroverzicht"
#: templates/yearly_overview/pages/overview_by_currency.html:63
msgid "Year"
msgstr "Jaar"
#, fuzzy
#~| msgid "Exchange rate deleted successfully"
#~ msgid "Exchange rates queued to be fetched successfully"
#~ msgstr "Wisselkoers succesvol verwijderd"

View File

@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-01 22:04+0000\n"
"PO-Revision-Date: 2025-02-01 19:04-0300\n"
"POT-Creation-Date: 2025-02-07 11:45-0300\n"
"PO-Revision-Date: 2025-02-07 11:46-0300\n"
"Last-Translator: Herculino Trotta\n"
"Language-Team: \n"
"Language: pt_BR\n"
@@ -24,23 +24,24 @@ msgid "Group name"
msgstr "Nome do grupo"
#: apps/accounts/forms.py:40 apps/accounts/forms.py:96
#: apps/currencies/forms.py:52 apps/currencies/forms.py:90 apps/dca/forms.py:41
#: apps/dca/forms.py:93 apps/import_app/forms.py:34 apps/rules/forms.py:45
#: apps/rules/forms.py:87 apps/transactions/forms.py:190
#: apps/transactions/forms.py:257 apps/transactions/forms.py:581
#: apps/transactions/forms.py:624 apps/transactions/forms.py:656
#: apps/transactions/forms.py:691 apps/transactions/forms.py:827
#: apps/currencies/forms.py:53 apps/currencies/forms.py:91
#: apps/currencies/forms.py:142 apps/dca/forms.py:41 apps/dca/forms.py:93
#: apps/import_app/forms.py:34 apps/rules/forms.py:45 apps/rules/forms.py:87
#: apps/transactions/forms.py:190 apps/transactions/forms.py:257
#: apps/transactions/forms.py:581 apps/transactions/forms.py:624
#: apps/transactions/forms.py:656 apps/transactions/forms.py:691
#: apps/transactions/forms.py:827
msgid "Update"
msgstr "Atualizar"
#: apps/accounts/forms.py:48 apps/accounts/forms.py:104
#: apps/common/widgets/tom_select.py:12 apps/currencies/forms.py:60
#: apps/currencies/forms.py:98 apps/dca/forms.py:49 apps/dca/forms.py:102
#: apps/import_app/forms.py:42 apps/rules/forms.py:53 apps/rules/forms.py:95
#: apps/transactions/forms.py:174 apps/transactions/forms.py:199
#: apps/transactions/forms.py:589 apps/transactions/forms.py:632
#: apps/transactions/forms.py:664 apps/transactions/forms.py:699
#: apps/transactions/forms.py:835
#: apps/common/widgets/tom_select.py:12 apps/currencies/forms.py:61
#: apps/currencies/forms.py:99 apps/currencies/forms.py:150
#: apps/dca/forms.py:49 apps/dca/forms.py:102 apps/import_app/forms.py:42
#: apps/rules/forms.py:53 apps/rules/forms.py:95 apps/transactions/forms.py:174
#: apps/transactions/forms.py:199 apps/transactions/forms.py:589
#: apps/transactions/forms.py:632 apps/transactions/forms.py:664
#: apps/transactions/forms.py:699 apps/transactions/forms.py:835
#: templates/account_groups/fragments/list.html:9
#: templates/accounts/fragments/list.html:9
#: templates/categories/fragments/list.html:9
@@ -49,6 +50,7 @@ msgstr "Atualizar"
#: templates/dca/fragments/strategy/list.html:9
#: templates/entities/fragments/list.html:9
#: templates/exchange_rates/fragments/list.html:10
#: templates/exchange_rates_services/fragments/list.html:10
#: templates/import_app/fragments/profiles/list.html:7
#: templates/import_app/fragments/profiles/list.html:10
#: templates/installment_plans/fragments/list.html:9
@@ -70,7 +72,7 @@ msgstr "Novo saldo"
#: apps/transactions/forms.py:39 apps/transactions/forms.py:291
#: apps/transactions/forms.py:298 apps/transactions/forms.py:478
#: apps/transactions/forms.py:723 apps/transactions/models.py:159
#: apps/transactions/models.py:314 apps/transactions/models.py:494
#: apps/transactions/models.py:328 apps/transactions/models.py:508
msgid "Category"
msgstr "Categoria"
@@ -78,8 +80,8 @@ msgstr "Categoria"
#: apps/transactions/filters.py:74 apps/transactions/forms.py:47
#: apps/transactions/forms.py:307 apps/transactions/forms.py:315
#: apps/transactions/forms.py:471 apps/transactions/forms.py:716
#: apps/transactions/models.py:165 apps/transactions/models.py:316
#: apps/transactions/models.py:498 templates/includes/navbar.html:105
#: apps/transactions/models.py:165 apps/transactions/models.py:330
#: apps/transactions/models.py:512 templates/includes/navbar.html:105
#: templates/tags/fragments/list.html:5 templates/tags/pages/index.html:4
msgid "Tags"
msgstr "Tags"
@@ -93,6 +95,7 @@ msgstr "Tags"
#: templates/categories/fragments/table.html:16
#: templates/currencies/fragments/list.html:26
#: templates/entities/fragments/table.html:16
#: templates/exchange_rates_services/fragments/list.html:32
#: templates/import_app/fragments/profiles/list.html:36
#: templates/installment_plans/fragments/table.html:16
#: templates/recurring_transactions/fragments/table.html:18
@@ -111,17 +114,17 @@ msgstr "Grupo da Conta"
msgid "Account Groups"
msgstr "Grupos da Conta"
#: apps/accounts/models.py:31 apps/currencies/models.py:32
#: apps/accounts/models.py:31 apps/currencies/models.py:39
#: templates/accounts/fragments/list.html:27
msgid "Currency"
msgstr "Moeda"
#: apps/accounts/models.py:37 apps/currencies/models.py:20
#: apps/accounts/models.py:37 apps/currencies/models.py:27
#: templates/accounts/fragments/list.html:28
msgid "Exchange Currency"
msgstr "Moeda de Câmbio"
#: apps/accounts/models.py:42 apps/currencies/models.py:25
#: apps/accounts/models.py:42 apps/currencies/models.py:32
msgid "Default currency for exchange calculations"
msgstr "Moeda padrão para os cálculos de câmbio"
@@ -151,7 +154,7 @@ msgstr ""
#: apps/accounts/models.py:59 apps/rules/models.py:19
#: apps/transactions/forms.py:59 apps/transactions/forms.py:463
#: apps/transactions/forms.py:708 apps/transactions/models.py:132
#: apps/transactions/models.py:274 apps/transactions/models.py:476
#: apps/transactions/models.py:288 apps/transactions/models.py:490
msgid "Account"
msgstr "Conta"
@@ -349,35 +352,36 @@ msgstr "Limpar"
msgid "No results..."
msgstr "Sem resultados..."
#: apps/currencies/forms.py:16 apps/currencies/models.py:15
#: apps/currencies/forms.py:17 apps/currencies/models.py:22
msgid "Prefix"
msgstr "Prefixo"
#: apps/currencies/forms.py:17 apps/currencies/models.py:16
#: apps/currencies/forms.py:18 apps/currencies/models.py:23
msgid "Suffix"
msgstr "Sufixo"
#: apps/currencies/forms.py:68 apps/dca/models.py:156 apps/rules/models.py:22
#: apps/currencies/forms.py:69 apps/dca/models.py:156 apps/rules/models.py:22
#: apps/transactions/forms.py:63 apps/transactions/forms.py:319
#: apps/transactions/models.py:142
#: templates/dca/fragments/strategy/details.html:52
#: templates/exchange_rates/fragments/table.html:10
#: templates/exchange_rates_services/fragments/table.html:10
msgid "Date"
msgstr "Data"
#: apps/currencies/models.py:8
#: apps/currencies/models.py:14
msgid "Currency Code"
msgstr "Código da Moeda"
#: apps/currencies/models.py:9
#: apps/currencies/models.py:16
msgid "Currency Name"
msgstr "Nome da Moeda"
#: apps/currencies/models.py:13
#: apps/currencies/models.py:20
msgid "Decimal Places"
msgstr "Casas Decimais"
#: apps/currencies/models.py:33 apps/transactions/filters.py:60
#: apps/currencies/models.py:40 apps/transactions/filters.py:60
#: templates/currencies/fragments/list.html:5
#: templates/currencies/pages/index.html:4 templates/includes/navbar.html:119
#: templates/includes/navbar.html:121
@@ -387,36 +391,143 @@ msgstr "Casas Decimais"
msgid "Currencies"
msgstr "Moedas"
#: apps/currencies/models.py:41
#: apps/currencies/models.py:48
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:52
#: apps/currencies/models.py:59
msgid "From Currency"
msgstr "Moeda de origem"
#: apps/currencies/models.py:58
#: apps/currencies/models.py:65
msgid "To Currency"
msgstr "Moeda de destino"
#: apps/currencies/models.py:61 apps/currencies/models.py:66
#: apps/currencies/models.py:68 apps/currencies/models.py:73
msgid "Exchange Rate"
msgstr "Taxa de Câmbio"
#: apps/currencies/models.py:63
#: apps/currencies/models.py:70
msgid "Date and Time"
msgstr "Data e Tempo"
#: apps/currencies/models.py:67 templates/exchange_rates/fragments/list.html:6
#: apps/currencies/models.py:74 templates/exchange_rates/fragments/list.html:6
#: templates/exchange_rates/pages/index.html:4
#: templates/includes/navbar.html:123
msgid "Exchange Rates"
msgstr "Taxas de Câmbio"
#: apps/currencies/models.py:79
#: apps/currencies/models.py:86
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:99
msgid "On"
msgstr "Em"
#: apps/currencies/models.py:100
msgid "Every X hours"
msgstr "A cada X horas"
#: apps/currencies/models.py:101
msgid "Not on"
msgstr "Não em"
#: apps/currencies/models.py:103
msgid "Service Name"
msgstr "Nome do Serviço"
#: apps/currencies/models.py:105
msgid "Service Type"
msgstr "Tipo de Serviço"
#: apps/currencies/models.py:107 apps/transactions/models.py:71
#: apps/transactions/models.py:90 apps/transactions/models.py:109
#: templates/categories/fragments/list.html:21
#: templates/entities/fragments/list.html:21
#: templates/recurring_transactions/fragments/list.html:21
#: templates/tags/fragments/list.html:21
msgid "Active"
msgstr "Ativo"
#: apps/currencies/models.py:112
msgid "API Key"
msgstr "Chave de API"
#: apps/currencies/models.py:113
msgid "API key for the service (if required)"
msgstr "Chave de API para o serviço (se necessário)"
#: apps/currencies/models.py:118
msgid "Interval Type"
msgstr "Tipo de Intervalo"
#: apps/currencies/models.py:122
msgid "Interval"
msgstr "Intervalo"
#: apps/currencies/models.py:125
msgid "Last Successful Fetch"
msgstr "Última execução bem-sucedida"
#: apps/currencies/models.py:130
msgid "Target Currencies"
msgstr "Moedas-alvo"
#: apps/currencies/models.py:132
msgid ""
"Select currencies to fetch exchange rates for. Rates will be fetched for "
"each currency against their set exchange currency."
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:140
msgid "Target Accounts"
msgstr "Contas-alvo"
#: apps/currencies/models.py:142
msgid ""
"Select accounts to fetch exchange rates for. Rates will be fetched for each "
"account's currency against their set exchange currency."
msgstr ""
"Selecione as contas para as quais deseja obter taxas de câmbio. As taxas "
"serão obtidas para a moeda de cada conta em relação à moeda de câmbio "
"definida."
#: apps/currencies/models.py:149
msgid "Exchange Rate Service"
msgstr "Serviço de Taxa de Câmbio"
#: apps/currencies/models.py:150
msgid "Exchange Rate Services"
msgstr "Serviços de Taxa de Câmbio"
#: apps/currencies/models.py:202
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:211
msgid "'Every X hours' interval must be between 0 and 23."
msgstr "Intervalo do tipo 'A cada X horas' requerer um número entre 0 e 23."
#: apps/currencies/models.py:225
msgid ""
"Invalid hour format. Use comma-separated hours (0-23) and/or ranges (e.g., "
"'1-5,8,10-12')."
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:236
msgid ""
"Invalid format. Please check the requirements for your selected interval "
"type."
msgstr ""
"Formato inválido. Por favor cheque os requisitos para o seu tipo de "
"intervalo."
#: apps/currencies/views/currencies.py:42
msgid "Currency added successfully"
msgstr "Moeda adicionada com sucesso"
@@ -441,6 +552,22 @@ 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
msgid "Service added successfully"
msgstr "Serviço adicionado com sucesso"
#: apps/currencies/views/exchange_rates_services.py:74
msgid "Service updated successfully"
msgstr "Serviço atualizado com sucesso"
#: apps/currencies/views/exchange_rates_services.py:100
msgid "Service deleted successfully"
msgstr "Serviço apagado com sucesso"
#: apps/currencies/views/exchange_rates_services.py:115
msgid "Services queued successfully"
msgstr "Serviços marcados para execução com sucesso"
#: apps/dca/models.py:17
msgid "Target Currency"
msgstr "Moeda de destino"
@@ -451,7 +578,7 @@ msgstr "Moeda de pagamento"
#: apps/dca/models.py:27 apps/dca/models.py:179 apps/rules/models.py:26
#: apps/transactions/forms.py:333 apps/transactions/models.py:155
#: apps/transactions/models.py:323 apps/transactions/models.py:504
#: apps/transactions/models.py:337 apps/transactions/models.py:518
msgid "Notes"
msgstr "Notas"
@@ -615,7 +742,7 @@ msgstr "Já existe um valor para esse campo na regra."
#: apps/rules/models.py:10 apps/rules/models.py:25
#: apps/transactions/forms.py:325 apps/transactions/models.py:153
#: apps/transactions/models.py:281 apps/transactions/models.py:490
#: apps/transactions/models.py:295 apps/transactions/models.py:504
msgid "Description"
msgstr "Descrição"
@@ -624,7 +751,7 @@ msgid "Trigger"
msgstr "Gatilho"
#: apps/rules/models.py:20 apps/transactions/models.py:139
#: apps/transactions/models.py:279 apps/transactions/models.py:482
#: apps/transactions/models.py:293 apps/transactions/models.py:496
msgid "Type"
msgstr "Tipo"
@@ -638,21 +765,21 @@ msgstr "Pago"
#: apps/rules/models.py:23 apps/transactions/forms.py:66
#: apps/transactions/forms.py:322 apps/transactions/forms.py:492
#: apps/transactions/models.py:143 apps/transactions/models.py:297
#: apps/transactions/models.py:506
#: apps/transactions/models.py:143 apps/transactions/models.py:311
#: apps/transactions/models.py:520
msgid "Reference Date"
msgstr "Data de Referência"
#: apps/rules/models.py:24 apps/transactions/models.py:148
#: apps/transactions/models.py:487
#: apps/transactions/models.py:501
msgid "Amount"
msgstr "Quantia"
#: apps/rules/models.py:29 apps/transactions/filters.py:81
#: apps/transactions/forms.py:55 apps/transactions/forms.py:486
#: apps/transactions/forms.py:731 apps/transactions/models.py:117
#: apps/transactions/models.py:170 apps/transactions/models.py:319
#: apps/transactions/models.py:501 templates/entities/fragments/list.html:5
#: apps/transactions/models.py:170 apps/transactions/models.py:333
#: apps/transactions/models.py:515 templates/entities/fragments/list.html:5
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:107
msgid "Entities"
msgstr "Entidades"
@@ -790,14 +917,6 @@ msgstr "Data final deve ser após data inicial"
msgid "Mute"
msgstr "Silenciada"
#: apps/transactions/models.py:71 apps/transactions/models.py:90
#: apps/transactions/models.py:109 templates/categories/fragments/list.html:21
#: templates/entities/fragments/list.html:21
#: templates/recurring_transactions/fragments/list.html:21
#: templates/tags/fragments/list.html:21
msgid "Active"
msgstr "Ativo"
#: apps/transactions/models.py:73
msgid ""
"Deactivated categories won't be able to be selected when creating new "
@@ -855,11 +974,11 @@ msgstr "Renda"
msgid "Expense"
msgstr "Despesa"
#: apps/transactions/models.py:181 apps/transactions/models.py:326
#: apps/transactions/models.py:181 apps/transactions/models.py:340
msgid "Installment Plan"
msgstr "Parcelamento"
#: apps/transactions/models.py:190 apps/transactions/models.py:527
#: apps/transactions/models.py:190 apps/transactions/models.py:541
msgid "Recurring Transaction"
msgstr "Transação Recorrente"
@@ -891,95 +1010,95 @@ msgstr "Transação"
msgid "Transactions"
msgstr "Transações"
#: apps/transactions/models.py:268
#: apps/transactions/models.py:282
msgid "Yearly"
msgstr "Anual"
#: apps/transactions/models.py:269 apps/users/models.py:26
#: apps/transactions/models.py:283 apps/users/models.py:26
#: templates/includes/navbar.html:26
msgid "Monthly"
msgstr "Mensal"
#: apps/transactions/models.py:270
#: apps/transactions/models.py:284
msgid "Weekly"
msgstr "Semanal"
#: apps/transactions/models.py:271
#: apps/transactions/models.py:285
msgid "Daily"
msgstr "Diária"
#: apps/transactions/models.py:284
#: apps/transactions/models.py:298
msgid "Number of Installments"
msgstr "Número de Parcelas"
#: apps/transactions/models.py:289
#: apps/transactions/models.py:303
msgid "Installment Start"
msgstr "Parcela inicial"
#: apps/transactions/models.py:290
#: apps/transactions/models.py:304
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:295 apps/transactions/models.py:510
#: apps/transactions/models.py:309 apps/transactions/models.py:524
msgid "Start Date"
msgstr "Data de Início"
#: apps/transactions/models.py:299 apps/transactions/models.py:511
#: apps/transactions/models.py:313 apps/transactions/models.py:525
msgid "End Date"
msgstr "Data Final"
#: apps/transactions/models.py:304
#: apps/transactions/models.py:318
msgid "Recurrence"
msgstr "Recorrência"
#: apps/transactions/models.py:307
#: apps/transactions/models.py:321
msgid "Installment Amount"
msgstr "Valor da Parcela"
#: apps/transactions/models.py:327 templates/includes/navbar.html:69
#: apps/transactions/models.py:341 templates/includes/navbar.html:69
#: templates/installment_plans/fragments/list.html:5
#: templates/installment_plans/pages/index.html:4
msgid "Installment Plans"
msgstr "Parcelamentos"
#: apps/transactions/models.py:469
#: apps/transactions/models.py:483
msgid "day(s)"
msgstr "dia(s)"
#: apps/transactions/models.py:470
#: apps/transactions/models.py:484
msgid "week(s)"
msgstr "semana(s)"
#: apps/transactions/models.py:471
#: apps/transactions/models.py:485
msgid "month(s)"
msgstr "mês(es)"
#: apps/transactions/models.py:472
#: apps/transactions/models.py:486
msgid "year(s)"
msgstr "ano(s)"
#: apps/transactions/models.py:474
#: apps/transactions/models.py:488
#: templates/recurring_transactions/fragments/list.html:24
msgid "Paused"
msgstr "Pausado"
#: apps/transactions/models.py:513
#: apps/transactions/models.py:527
msgid "Recurrence Type"
msgstr "Tipo de recorrência"
#: apps/transactions/models.py:516
#: apps/transactions/models.py:530
msgid "Recurrence Interval"
msgstr "Intervalo de recorrência"
#: apps/transactions/models.py:520
#: apps/transactions/models.py:534
msgid "Last Generated Date"
msgstr "Última data gerada"
#: apps/transactions/models.py:523
#: apps/transactions/models.py:537
msgid "Last Generated Reference Date"
msgstr "Última data de referência gerada"
#: apps/transactions/models.py:528 templates/includes/navbar.html:71
#: apps/transactions/models.py:542 templates/includes/navbar.html:71
#: templates/recurring_transactions/fragments/list.html:5
#: templates/recurring_transactions/pages/index.html:4
msgid "Recurring Transactions"
@@ -1180,15 +1299,15 @@ msgstr "Essa conta está desativada"
msgid "Default"
msgstr "Padrão"
#: apps/users/forms.py:91 apps/users/models.py:40
#: apps/users/forms.py:91 apps/users/models.py:41
msgid "Date Format"
msgstr "Formato de Data"
#: apps/users/forms.py:96 apps/users/models.py:45
#: apps/users/forms.py:96 apps/users/models.py:46
msgid "Datetime Format"
msgstr "Formato de Data e Hora"
#: apps/users/forms.py:102 apps/users/models.py:48
#: apps/users/forms.py:102 apps/users/models.py:49
msgid "Number Format"
msgstr "Formato de Número"
@@ -1204,51 +1323,55 @@ msgstr "Anual por moeda"
msgid "Yearly by account"
msgstr "Anual por conta"
#: apps/users/models.py:29 templates/includes/navbar.html:40
msgid "Net Worth"
msgstr "Patrimônio"
#: apps/users/models.py:29 templates/net_worth/net_worth.html:9
msgid "Current Net Worth"
msgstr "Patrimônio Atual"
#: apps/users/models.py:30
#: apps/users/models.py:30 templates/net_worth/net_worth.html:9
msgid "Projected Net Worth"
msgstr "Patrimônio Previsto"
#: apps/users/models.py:31
msgid "All Transactions"
msgstr "Todas as transações"
#: apps/users/models.py:31 templates/includes/navbar.html:32
#: apps/users/models.py:32 templates/includes/navbar.html:32
msgid "Calendar"
msgstr "Calendário"
#: apps/users/models.py:53 apps/users/models.py:59
#: apps/users/models.py:54 apps/users/models.py:60
msgid "Auto"
msgstr "Automático"
#: apps/users/models.py:55
#: apps/users/models.py:56
msgid "Language"
msgstr "Linguagem"
#: apps/users/models.py:61
#: apps/users/models.py:62
msgid "Time Zone"
msgstr "Fuso horário"
#: apps/users/models.py:67
#: apps/users/models.py:68
msgid "Start page"
msgstr "Página inicial"
#: apps/users/views.py:60
#: apps/users/views.py:62
msgid "Transaction amounts are now hidden"
msgstr "Os valores das transações agora estão ocultos"
#: apps/users/views.py:63
#: apps/users/views.py:65
msgid "Transaction amounts are now displayed"
msgstr "Os valores das transações agora estão sendo exibidos"
#: apps/users/views.py:81
#: apps/users/views.py:83
msgid "Sounds are now muted"
msgstr "Os sons agora estão silenciados"
#: apps/users/views.py:84
#: apps/users/views.py:86
msgid "Sounds will now play"
msgstr "Os sons agora serão reproduzidos"
#: apps/users/views.py:100
#: apps/users/views.py:102
msgid "Your settings have been updated"
msgstr "Suas configurações foram atualizadas"
@@ -1267,6 +1390,8 @@ msgstr "Editar grupo de conta"
#: templates/dca/fragments/strategy/details.html:63
#: templates/entities/fragments/table.html:23
#: templates/exchange_rates/fragments/table.html:19
#: templates/exchange_rates_services/fragments/list.html:42
#: templates/exchange_rates_services/fragments/table.html:19
#: templates/import_app/fragments/profiles/list.html:44
#: templates/installment_plans/fragments/table.html:23
#: templates/recurring_transactions/fragments/table.html:25
@@ -1285,6 +1410,8 @@ msgstr "Ações"
#: templates/dca/fragments/strategy/list.html:34
#: templates/entities/fragments/table.html:28
#: templates/exchange_rates/fragments/table.html:23
#: templates/exchange_rates_services/fragments/list.html:46
#: templates/exchange_rates_services/fragments/table.html:23
#: templates/import_app/fragments/profiles/list.html:48
#: templates/installment_plans/fragments/table.html:27
#: templates/recurring_transactions/fragments/table.html:29
@@ -1306,6 +1433,8 @@ msgstr "Editar"
#: templates/dca/fragments/strategy/list.html:42
#: templates/entities/fragments/table.html:36
#: templates/exchange_rates/fragments/table.html:31
#: templates/exchange_rates_services/fragments/list.html:53
#: templates/exchange_rates_services/fragments/table.html:31
#: templates/import_app/fragments/profiles/list.html:69
#: templates/import_app/fragments/runs/list.html:102
#: templates/installment_plans/fragments/table.html:56
@@ -1329,6 +1458,8 @@ msgstr "Apagar"
#: templates/dca/fragments/strategy/list.html:46
#: templates/entities/fragments/table.html:40
#: templates/exchange_rates/fragments/table.html:36
#: templates/exchange_rates_services/fragments/list.html:57
#: templates/exchange_rates_services/fragments/table.html:36
#: templates/import_app/fragments/profiles/list.html:73
#: templates/import_app/fragments/runs/list.html:106
#: templates/installment_plans/fragments/table.html:48
@@ -1355,6 +1486,8 @@ msgstr "Tem certeza?"
#: templates/dca/fragments/strategy/list.html:47
#: templates/entities/fragments/table.html:41
#: templates/exchange_rates/fragments/table.html:37
#: templates/exchange_rates_services/fragments/list.html:58
#: templates/exchange_rates_services/fragments/table.html:37
#: templates/import_app/fragments/profiles/list.html:74
#: templates/rules/fragments/list.html:49
#: templates/rules/fragments/transaction_rule/view.html:61
@@ -1372,6 +1505,8 @@ msgstr "Você não será capaz de reverter isso!"
#: templates/dca/fragments/strategy/list.html:48
#: templates/entities/fragments/table.html:42
#: templates/exchange_rates/fragments/table.html:38
#: templates/exchange_rates_services/fragments/list.html:59
#: templates/exchange_rates_services/fragments/table.html:38
#: templates/import_app/fragments/profiles/list.html:75
#: templates/import_app/fragments/runs/list.html:108
#: templates/installment_plans/fragments/table.html:62
@@ -1503,37 +1638,37 @@ msgid "Restore"
msgstr "Restaurar"
#: templates/cotton/ui/account_card.html:15
#: templates/cotton/ui/currency_card.html:13
#: templates/cotton/ui/currency_card.html:10
msgid "projected income"
msgstr "renda prevista"
#: templates/cotton/ui/account_card.html:37
#: templates/cotton/ui/currency_card.html:35
#: templates/cotton/ui/currency_card.html:32
msgid "projected expenses"
msgstr "despesas previstas"
#: templates/cotton/ui/account_card.html:61
#: templates/cotton/ui/currency_card.html:59
#: templates/cotton/ui/currency_card.html:56
msgid "projected total"
msgstr "total previsto"
#: templates/cotton/ui/account_card.html:86
#: templates/cotton/ui/currency_card.html:84
#: templates/cotton/ui/currency_card.html:81
msgid "current income"
msgstr "renda atual"
#: templates/cotton/ui/account_card.html:108
#: templates/cotton/ui/currency_card.html:106
#: templates/cotton/ui/currency_card.html:103
msgid "current expenses"
msgstr "despesas atuais"
#: templates/cotton/ui/account_card.html:130
#: templates/cotton/ui/currency_card.html:128
#: templates/cotton/ui/currency_card.html:125
msgid "current total"
msgstr "total atual"
#: templates/cotton/ui/account_card.html:156
#: templates/cotton/ui/currency_card.html:154
#: templates/cotton/ui/currency_card.html:151
msgid "final total"
msgstr "total final"
@@ -1788,10 +1923,12 @@ msgid "No entities"
msgstr "Sem entidades"
#: templates/exchange_rates/fragments/add.html:5
#: templates/exchange_rates_services/fragments/add.html:5
msgid "Add exchange rate"
msgstr "Adicionar taxa de câmbio"
#: templates/exchange_rates/fragments/edit.html:5
#: templates/exchange_rates_services/fragments/edit.html:5
msgid "Edit exchange rate"
msgstr "Editar taxa de câmbio"
@@ -1804,22 +1941,60 @@ msgid "All"
msgstr "Todas"
#: templates/exchange_rates/fragments/table.html:11
#: templates/exchange_rates_services/fragments/table.html:11
msgid "Pairing"
msgstr "Pares"
#: templates/exchange_rates/fragments/table.html:12
#: templates/exchange_rates_services/fragments/table.html:12
msgid "Rate"
msgstr "Taxa de Câmbio"
#: templates/exchange_rates/fragments/table.html:51
#: templates/exchange_rates_services/fragments/table.html:51
msgid "No exchange rates"
msgstr "Nenhuma taxa de câmbio"
#: templates/exchange_rates/fragments/table.html:58
#: templates/exchange_rates_services/fragments/table.html:58
#: templates/transactions/fragments/list_all.html:47
msgid "Page navigation"
msgstr "Navegação por página"
#: templates/exchange_rates_services/fragments/list.html:6
#: templates/exchange_rates_services/pages/index.html:4
#: templates/includes/navbar.html:133
msgid "Automatic Exchange Rates"
msgstr "Taxas de Câmbio Automáticas"
#: templates/exchange_rates_services/fragments/list.html:21
msgid "Fetch all"
msgstr "Executar todos"
#: templates/exchange_rates_services/fragments/list.html:33
msgid "Service"
msgstr "Serviço"
#: templates/exchange_rates_services/fragments/list.html:34
msgid "Targeting"
msgstr "Alvos"
#: templates/exchange_rates_services/fragments/list.html:35
msgid "Last fetch"
msgstr "Última execução"
#: templates/exchange_rates_services/fragments/list.html:67
msgid "currencies"
msgstr "moedas"
#: templates/exchange_rates_services/fragments/list.html:67
msgid "accounts"
msgstr "contas"
#: templates/exchange_rates_services/fragments/list.html:75
msgid "No services configured"
msgstr "Nenhum serviço configurado"
#: templates/import_app/fragments/profiles/add.html:6
msgid "Add new import profile"
msgstr "Adicionar novo perfil de importação"
@@ -1919,6 +2094,10 @@ msgstr "Alternar navegação"
msgid "Overview"
msgstr "Visão Geral"
#: templates/includes/navbar.html:40
msgid "Net Worth"
msgstr "Patrimônio"
#: templates/includes/navbar.html:44
msgid "Current"
msgstr "Atual"
@@ -1960,15 +2139,15 @@ msgstr "Automação"
msgid "Rules"
msgstr "Regras"
#: templates/includes/navbar.html:141
#: templates/includes/navbar.html:143
msgid "Only use this if you know what you're doing"
msgstr "Só use isso se você souber o que está fazendo"
#: templates/includes/navbar.html:142
#: templates/includes/navbar.html:144
msgid "Django Admin"
msgstr "Django Admin"
#: templates/includes/navbar.html:151
#: templates/includes/navbar.html:153
msgid "Calculator"
msgstr "Calculadora"
@@ -2136,14 +2315,6 @@ msgstr "Mais antigas primeiro"
msgid "Newest first"
msgstr "Mais novas primeiro"
#: templates/net_worth/net_worth.html:9
msgid "Current Net Worth"
msgstr "Patrimônio Atual"
#: templates/net_worth/net_worth.html:9
msgid "Projected Net Worth"
msgstr "Patrimônio Previsto"
#: templates/net_worth/net_worth.html:17
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "By currency"
@@ -2373,6 +2544,19 @@ msgstr "Visão Anual"
msgid "Year"
msgstr "Ano"
#~ msgid "Fetch Interval (hours)"
#~ msgstr "Intervalo de busca (horas)"
#~ msgid "Fetch every"
#~ msgstr "Buscar a cada"
#~ msgid "hours"
#~ msgstr "horas"
#, fuzzy
#~ msgid "Exchange rates queued to be fetched successfully"
#~ msgstr "Taxas de câmbio com sucesso"
#, fuzzy
#~| msgid "Transaction updated successfully"
#~ msgid "{count} transactions updated successfully"

View File

@@ -26,7 +26,7 @@
{% if account.exchanged and account.exchanged.income_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.currency.income_projected"
:amount="account.exchanged.income_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>

View File

@@ -2,9 +2,6 @@
{% load i18n %}
<div class="col card shadow">
<div class="card-body">
<div class="tw-text-sm mb-2">
<span class="badge text-bg-primary">{{ currency.currency.code }}</span>
</div>
<h5 class="card-title">
{{ currency.currency.name }}
</h5>
@@ -24,7 +21,7 @@
{% if currency.exchanged and currency.exchanged.income_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.currency.income_projected"
:amount="currency.exchanged.income_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>

View File

@@ -40,8 +40,8 @@
</div>
</td>
<td class="col-3">{{ exchange_rate.date|date:"SHORT_DATETIME_FORMAT" }}</td>
<td class="col-3"><span class="badge rounded-pill text-bg-secondary">{{ exchange_rate.from_currency.code }}</span> x <span class="badge rounded-pill text-bg-secondary">{{ exchange_rate.to_currency.code }}</span></td>
<td class="col-3">1 {{ exchange_rate.from_currency.code }} ≅ {% currency_display amount=exchange_rate.rate prefix=exchange_rate.to_currency.prefix suffix=exchange_rate.to_currency.suffix decimal_places=exchange_rate.to_currency.decimal_places%}</td>
<td class="col-3"><span class="badge rounded-pill text-bg-secondary">{{ exchange_rate.from_currency.name }}</span> x <span class="badge rounded-pill text-bg-secondary">{{ exchange_rate.to_currency.name }}</span></td>
<td class="col-3">1 {{ exchange_rate.from_currency.name }} ≅ {% currency_display amount=exchange_rate.rate prefix=exchange_rate.to_currency.prefix suffix=exchange_rate.to_currency.suffix decimal_places=exchange_rate.to_currency.decimal_places%}</td>
</tr>
{% endfor %}
</tbody>

View File

@@ -0,0 +1,11 @@
{% extends 'extends/offcanvas.html' %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title %}{% translate 'Add exchange rate' %}{% endblock %}
{% block body %}
<form hx-post="{% url 'automatic_exchange_rate_add' %}" hx-target="#generic-offcanvas" novalidate>
{% crispy form %}
</form>
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends 'extends/offcanvas.html' %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title %}{% translate 'Edit exchange rate' %}{% endblock %}
{% block body %}
<form hx-post="{% url 'automatic_exchange_rate_edit' pk=service.id %}" hx-target="#generic-offcanvas" novalidate>
{% crispy form %}
</form>
{% endblock %}

View File

@@ -0,0 +1,79 @@
{% load currency_display %}
{% load i18n %}
<div class="container px-md-3 py-3 column-gap-5">
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
{% spaceless %}
<div>{% translate 'Automatic Exchange Rates' %}<span>
<a class="text-decoration-none tw-text-2xl p-1 category-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Add" %}"
hx-get="{% url 'automatic_exchange_rate_add' %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i></a>
</span></div>
{% endspaceless %}
</div>
<div class="card">
<div class="card-header text-body-secondary">
<button type="button" hx-get="{% url 'automatic_exchange_rate_force_fetch' %}"
class="btn btn-outline-primary btn-sm">{% trans 'Fetch all' %}</button>
</div>
<div class="card-body">
{% if services %}
<c-config.search></c-config.search>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th scope="col" class="col-auto"></th>
<th scope="col" class="col-auto"></th>
<th scope="col" class="col-auto">{% translate 'Name' %}</th>
<th scope="col" class="col">{% translate 'Service' %}</th>
<th scope="col" class="col">{% translate 'Targeting' %}</th>
<th scope="col" class="col">{% translate 'Last fetch' %}</th>
</tr>
</thead>
<tbody>
{% for service in services %}
<tr class="services">
<td class="col-auto">
<div class="btn-group" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'automatic_exchange_rate_edit' pk=service.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm text-danger"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'automatic_exchange_rate_delete' pk=service.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
</div>
</td>
<td class="col-auto">{% if service.is_active %}<i class="fa-solid fa-circle text-success"></i>{% else %}
<i class="fa-solid fa-circle text-danger"></i>{% endif %}</td>
<td class="col-auto">{{ service.name }}</td>
<td class="col">{{ service.get_service_type_display }}</td>
<td class="col">{{ service.target_currencies.count }} {% trans 'currencies' %}, {{ service.target_accounts.count }} {% trans 'accounts' %}</td>
<td class="col">{{ service.last_fetch|date:"SHORT_DATETIME_FORMAT" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<c-msg.empty title="{% translate "No services configured" %}" remove-padding></c-msg.empty>
{% endif %}
</div>
</div>
</div>

View File

@@ -0,0 +1,132 @@
{% load currency_display %}
{% load i18n %}
<div class="card-body show-loading" hx-get="{% url 'exchange_rates_list_pair' %}" hx-trigger="updated from:window" hx-swap="outerHTML" hx-vals='{"page": "{{ page_obj.number }}", "from": "{{ from_currency|default_if_none:"" }}", "to": "{{ to_currency|default_if_none:"" }}"}'>
{% if page_obj %}
<div class="table-responsive">
<table class="table table-hover text-nowrap">
<thead>
<tr>
<th scope="col" class="col-auto"></th>
<th scope="col" class="col">{% translate 'Date' %}</th>
<th scope="col" class="col">{% translate 'Pairing' %}</th>
<th scope="col" class="col">{% translate 'Rate' %}</th>
</tr>
</thead>
<tbody>
{% for exchange_rate in page_obj %}
<tr class="exchange-rate">
<td class="col-auto">
<div class="btn-group" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'exchange_rate_edit' pk=exchange_rate.id %}"
hx-target="#generic-offcanvas"
hx-swap="innerHTML">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm text-danger"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'exchange_rate_delete' pk=exchange_rate.id %}"
hx-trigger='confirmed'
hx-swap="innerHTML"
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
</div>
</td>
<td class="col-3">{{ exchange_rate.date|date:"SHORT_DATETIME_FORMAT" }}</td>
<td class="col-3"><span class="badge rounded-pill text-bg-secondary">{{ exchange_rate.from_currency.name }}</span> x <span class="badge rounded-pill text-bg-secondary">{{ exchange_rate.to_currency.name }}</span></td>
<td class="col-3">1 {{ exchange_rate.from_currency.name }} ≅ {% currency_display amount=exchange_rate.rate prefix=exchange_rate.to_currency.prefix suffix=exchange_rate.to_currency.suffix decimal_places=exchange_rate.to_currency.decimal_places%}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<c-msg.empty title="{% translate "No exchange rates" %}" remove-padding></c-msg.empty>
{% endif %}
{% if page_obj.has_other_pages %}
<div class="mt-auto">
<input value="{{ page_obj.number }}" name="page" type="hidden" id="page">
<nav aria-label="{% translate 'Page navigation' %}">
<ul class="pagination justify-content-center mt-5">
<li class="page-item">
<a class="page-link tw-cursor-pointer {% if not page_obj.has_previous %}disabled{% endif %}"
hx-get="{% if page_obj.has_previous %}{% url 'exchange_rates_list_pair' %}{% endif %}"
hx-vals='{"page": 1, "from": "{{ from_currency|default_if_none:"" }}", "to": "{{ to_currency|default_if_none:"" }}"}'
hx-include="#filter, #order"
hx-target="#exchange-rates-table"
aria-label="Primeira página"
hx-swap="show:top">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% for page_number in page_obj.paginator.page_range %}
{% comment %}
This conditional allows us to display up to 3 pages before and after the current page
If you decide to remove this conditional, all the pages will be displayed
You can change the 3 to any number you want e.g
To display only 5 pagination items, change the 3 to 2 (2 before and 2 after the current page)
{% endcomment %}
{% if page_number <= page_obj.number|add:3 and page_number >= page_obj.number|add:-3 %}
{% if page_obj.number == page_number %}
<li class="page-item active">
<a class="page-link tw-cursor-pointer">
{{ page_number }}
</a>
</li>
{% else %}
<li class="page-item">
<a class="page-link tw-cursor-pointer"
hx-get="{% url 'exchange_rates_list_pair' %}"
hx-vals='{"page": {{ page_number }}, "from": "{{ from_currency|default_if_none:"" }}", "to": "{{ to_currency|default_if_none:"" }}"}'
hx-target="#exchange-rates-table"
hx-swap="show:top">
{{ page_number }}
</a>
</li>
{% endif %}
{% endif %}
{% endfor %}
{% if page_obj.number|add:3 < page_obj.paginator.num_pages %}
<li class="page-item">
<a class="page-link disabled"
aria-label="...">
<span aria-hidden="true">...</span>
</a>
</li>
<li class="page-item">
<a class="page-link tw-cursor-pointer"
hx-get="{% url 'exchange_rates_list_pair' %}" hx-target="#exchange-rates-table"
hx-vals='{"page": {{ page_obj.paginator.num_pages }}, "from": "{{ from_currency|default_if_none:"" }}", "to": "{{ to_currency|default_if_none:"" }}"}'
hx-include="#filter, #order"
hx-swap="show:top"
aria-label="Última página">
<span aria-hidden="true">{{ page_obj.paginator.num_pages }}</span>
</a>
</li>
{% endif %}
<li class="page-item">
<a class="page-link {% if not page_obj.has_next %}disabled{% endif %} tw-cursor-pointer"
hx-get="{% if page_obj.has_next %}{% url 'exchange_rates_list_pair' %}{% endif %}"
hx-vals='{"page": {{ page_obj.paginator.num_pages }}, "from": "{{ from_currency|default_if_none:"" }}", "to": "{{ to_currency|default_if_none:"" }}"}'
hx-include="#filter, #order"
hx-swap="show:top"
hx-target="#exchange-rates-table"
aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</nav>
</div>
{% endif %}
</div>

View File

@@ -0,0 +1,8 @@
{% extends "layouts/base.html" %}
{% load i18n %}
{% block title %}{% translate 'Automatic Exchange Rates' %}{% endblock %}
{% block content %}
<div hx-get="{% url 'automatic_exchange_rates_list' %}" hx-trigger="load, updated from:window" class="show-loading"></div>
{% endblock %}

View File

@@ -129,6 +129,8 @@
href="{% url 'rules_index' %}">{% translate 'Rules' %}</a></li>
<li><a class="dropdown-item {% active_link views='import_profiles_index' %}"
href="{% url 'import_profiles_index' %}">{% translate 'Import' %} <span class="badge text-bg-primary">beta</span></a></li>
<li><a class="dropdown-item {% active_link views='automatic_exchange_rates_index' %}"
href="{% url 'automatic_exchange_rates_index' %}">{% translate 'Automatic Exchange Rates' %}</a></li>
<li>
<hr class="dropdown-divider">
</li>

View File

@@ -4,11 +4,13 @@
{% javascript_pack 'sweetalert2' attrs="defer" %}
{% javascript_pack 'select' attrs="defer" %}
{% javascript_pack 'datepicker' %}
{% javascript_pack 'autosize' attrs="defer" %}
{% include 'includes/scripts/hyperscript/init_tom_select.html' %}
{% include 'includes/scripts/hyperscript/init_date_picker.html' %}
{% include 'includes/scripts/hyperscript/hide_amount.html' %}
{% include 'includes/scripts/hyperscript/tooltip.html' %}
{% include 'includes/scripts/hyperscript/autosize.html' %}
{% include 'includes/scripts/hyperscript/htmx_error_handler.html' %}
{% include 'includes/scripts/hyperscript/sounds.html' %}
{% include 'includes/scripts/hyperscript/swal.html' %}

View File

@@ -0,0 +1,7 @@
<script type="text/hyperscript">
on htmx:afterSettle
for elem in <.textarea/>
autosize(elem)
end
end
</script>

View File

@@ -1,5 +1,5 @@
<div id="toasts">
<div class="toast-container position-fixed bottom-0 end-0 p-3" hx-trigger="load, updated from:window" hx-get="{% url 'toasts' %}" hx-swap="beforeend">
<div class="toast-container position-fixed bottom-0 end-0 p-3" hx-trigger="load, updated from:window, toasts from:window" hx-get="{% url 'toasts' %}" hx-swap="beforeend">
</div>
</div>

View File

@@ -4,5 +4,11 @@ set -o errexit
set -o pipefail
set -o nounset
rm -f /tmp/migrations_complete
python manage.py migrate
# Create flag file to signal migrations are complete
touch /tmp/migrations_complete
exec python manage.py runserver 0.0.0.0:8000

View File

@@ -4,4 +4,12 @@ set -o errexit
set -o nounset
exec watchfiles --filter python "python manage.py procrastinate worker"
# Wait for migrations to complete
until [ -f /tmp/migrations_complete ]; do
echo "Procastinate is waiting for web app to start..."
sleep 2
done
rm -f /tmp/migrations_complete
exec python manage.py procrastinate worker

View File

@@ -20,7 +20,7 @@ directory=/usr/src/app
command=/bin/bash /start
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2
stderr_logfile=/dev/fd/1
stderr_logfile_maxbytes=0
autorestart=true
startretries=5
@@ -33,7 +33,7 @@ numprocs=%(ENV_TASK_WORKERS)s
numprocs_start=1
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2
stderr_logfile=/dev/fd/1
stderr_logfile_maxbytes=0
autorestart=true
startretries=5

View File

@@ -4,8 +4,13 @@ set -o errexit
set -o pipefail
set -o nounset
# Remove flag file if it exists from previous run
rm -f /tmp/migrations_complete
python manage.py collectstatic --noinput
python manage.py migrate
# Create flag file to signal migrations are complete
touch /tmp/migrations_complete
exec gunicorn WYGIWYH.wsgi:application --bind 0.0.0.0:8000 --timeout 600

View File

@@ -4,4 +4,12 @@ set -o errexit
set -o nounset
# Wait for migrations to complete
until [ -f /tmp/migrations_complete ]; do
echo "Procastinate is waiting for web app to start..."
sleep 2
done
rm -f /tmp/migrations_complete
exec python manage.py procrastinate worker

View File

@@ -17,7 +17,7 @@ directory=/usr/src/app
command=/bin/bash /start
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2
stderr_logfile=/dev/fd/1
stderr_logfile_maxbytes=0
autorestart=true
startretries=5
@@ -31,7 +31,7 @@ numprocs=%(ENV_TASK_WORKERS)s
numprocs_start=1
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2
stderr_logfile=/dev/fd/1
stderr_logfile_maxbytes=0
autorestart=true
startretries=5

View File

@@ -1,54 +1,3 @@
import autosize from "autosize/dist/autosize";
let autosize_textareas = document.querySelectorAll('textarea[autosize]');
autosize(autosize_textareas);
document.addEventListener('shown.bs.collapse', function () {
autosize.update(autosize_textareas);
});
// UPDATE AUTOSIZE TEXT AREAS FOR FORMS INSIDE HTMX MODALS
document.addEventListener('updated.bs.modal', function () {
let new_autosize_textareas = document.querySelectorAll('textarea[autosize]');
autosize(new_autosize_textareas);
});
let charcount_textareas = document.querySelectorAll('textarea[countchars], input[countchars]');
charcount_textareas.forEach(formElement => {
countTextArea(formElement);
formElement.addEventListener('input', () => countTextArea(formElement));
});
function countTextArea(formElement) {
let name = formElement.name;
let max_chars = null;
if (formElement.dataset.maxChars) {
max_chars = formElement.dataset.maxChars;
} else if (formElement.hasAttribute("maxlength")) {
max_chars = formElement.getAttribute("maxlength");
}
let cur_chars = formElement.value.length;
let wrapper = document.querySelector(`#charcount-${name}`);
let char_counter = document.querySelector(`#char-counter-${name}`);
let max_counter = document.querySelector(`#max-counter-${name}`);
char_counter.textContent = cur_chars;
if (max_counter) {
max_counter.textContent = max_chars;
wrapper.classList.remove("text-bg-warning", "text-bg-normal", "text-bg-success", "text-bg-danger");
if (cur_chars === 0) {
wrapper.classList.add("text-bg-secondary");
} else if (cur_chars > max_chars - 1) {
wrapper.classList.add("text-bg-danger");
} else if (cur_chars < max_chars && cur_chars > max_chars * (90 / 100)) {
wrapper.classList.add("text-bg-warning");
} else if (cur_chars < max_chars - ((max_chars * (10 / 100)) - 1)) {
wrapper.classList.add("text-bg-success");
}
}
}
window.autosize = autosize;