Compare commits

..

64 Commits
0.7.3 ... 0.8.2

Author SHA1 Message Date
Herculino Trotta
cd7ecd42ea Merge pull request #109
feat: allow for a subset of markdown (bold, italics, strikethrough, links) when displaying notes
2025-01-29 13:53:09 -03:00
Herculino Trotta
0b83ad6b3e feat: allow for a subset of markdown (bold, italics, strikethrough, links) when displaying notes 2025-01-29 13:52:46 -03:00
Herculino Trotta
d0ef08252e Merge pull request #108
feat: improve transactions list loading time
2025-01-29 13:47:05 -03:00
Herculino Trotta
1140d9c896 feat: improve transactions list loading time
Prefetch more values and allow them to be cached
2025-01-29 13:46:06 -03:00
Herculino Trotta
b2843a1ec1 Merge pull request #106 from DragonHeart69/main
Small change in Dutch translation
2025-01-29 08:40:31 -03:00
Dimitri Decrock
d25aba7be9 small change to number format again 2025-01-29 06:12:54 +01:00
Dimitri Decrock
c3eaca3e9a Merge branch 'eitchtee:main' into main 2025-01-29 06:10:17 +01:00
Herculino Trotta
5677706452 Merge pull request #105
fix: unable to load transactions on first login
2025-01-29 00:56:22 -03:00
Herculino Trotta
5bf7f9f272 fix: unable to load transactions on first login 2025-01-29 00:56:06 -03:00
Herculino Trotta
448841dadc Merge pull request #104 from eitchtee/dev
fix: wrong filename
2025-01-29 00:15:32 -03:00
Herculino Trotta
1b6934694e fix: wrong filename 2025-01-29 00:14:45 -03:00
Herculino Trotta
d4d00ba02f Merge pull request #103 from eitchtee/dev
feat: reduce db queries when saving order on session
2025-01-29 00:14:18 -03:00
Herculino Trotta
19a65ac45f feat: reduce db queries when saving order on session 2025-01-29 00:12:47 -03:00
Herculino Trotta
b72e7bd707 Merge pull request #102
docker: set single container as new default
2025-01-29 00:12:40 -03:00
Herculino Trotta
190be3e813 docker: set single container as new default 2025-01-29 00:11:39 -03:00
Herculino Trotta
88300b314c Merge pull request #101 from eitchtee/eitchtee-patch-1
Update release.yml
2025-01-28 23:47:34 -03:00
Herculino Trotta
fab77c8d9f Update release.yml 2025-01-28 23:47:18 -03:00
Herculino Trotta
1ae7158d7e Merge pull request #100 from eitchtee/dev
docker: fix permission error
2025-01-28 23:46:11 -03:00
Herculino Trotta
05f0356288 docker: fix permission error 2025-01-28 23:45:01 -03:00
Herculino Trotta
b3cea17b8d Merge pull request #99
docker: add single-container support
2025-01-28 23:35:08 -03:00
Herculino Trotta
0b66b23f16 docker: add single-container support 2025-01-28 23:34:48 -03:00
Herculino Trotta
80fdf70f7d Add a nightly docker tag built whenever there's a push to main 2025-01-28 23:13:23 -03:00
Herculino Trotta
fa931b0db2 Merge pull request #98
feat: cleanup expired sessions every first day of month at 6am
2025-01-28 21:33:00 -03:00
Herculino Trotta
cab79b4203 feat: cleanup expired sessions every first day of month at 6am 2025-01-28 21:32:41 -03:00
Herculino Trotta
ddab3db6b5 Merge pull request #97
feat(import:v1): accept list as source, first valid one will be used.
2025-01-28 21:24:44 -03:00
Herculino Trotta
9fa704811c feat(import:v1): accept list as source, first valid one will be used. 2025-01-28 21:24:23 -03:00
Herculino Trotta
4c0d14def0 Merge pull request #96
feat: store selected "order by" on session
2025-01-28 20:05:46 -03:00
Herculino Trotta
43382d2ffe feat: store selected "order by" on session
Closes #95
2025-01-28 20:05:00 -03:00
Dimitri Decrock
65ad51c273 smal change to number format 2025-01-28 19:16:52 +01:00
Herculino Trotta
27d448afd6 feat: add locale files for de (german) 2025-01-28 14:03:38 -03:00
Herculino Trotta
1dd90974bd Merge pull request #93
refactor: remove toasts from login screen
2025-01-28 13:54:20 -03:00
Herculino Trotta
31cc8db3ac refactor: remove toasts from login screen
Fixes #91
2025-01-28 13:53:47 -03:00
Herculino Trotta
3d85a15ec9 Merge pull request #90
feat: enable bulk actions on specific transactions list (calendar, recurring and installment)
2025-01-27 22:46:19 -03:00
Herculino Trotta
90f98c2d15 feat: enable bulk actions on specific transactions list (calendar, recurring and installment) 2025-01-27 22:45:40 -03:00
Herculino Trotta
643855e60e Merge pull request #89 from eitchtee/dev
fix(calendar): tooltip error when transaction has no description and wrong color
2025-01-27 22:44:43 -03:00
Herculino Trotta
e0f7b532f8 fix(calendar): tooltip error when transaction has no description and wrong color 2025-01-27 22:44:05 -03:00
Herculino Trotta
b4d3e4b42f Merge pull request #88 from eitchtee/dev
feat: add "Clear cache" button to user menu
2025-01-27 21:50:48 -03:00
Herculino Trotta
9a7ccb0973 feat: add "Clear cache" button to user menu 2025-01-27 21:49:32 -03:00
Herculino Trotta
a9b67ff272 Merge pull request #87
fix(security): toasts and month_year_picker accessible without login
2025-01-27 21:42:36 -03:00
Herculino Trotta
233b9629a2 fix(security): toasts and month_year_picker accessible without login 2025-01-27 21:41:55 -03:00
Herculino Trotta
4180c177f1 Merge pull request #86
fix: cleanup_deleted_transactions task couldn't trigger
2025-01-27 21:34:15 -03:00
Herculino Trotta
f1bc04756f fix: cleanup_deleted_transactions task couldn't trigger 2025-01-27 21:33:46 -03:00
Herculino Trotta
13795c797f Merge pull request #85
feat: add number format user setting and improve date format handling
2025-01-27 13:31:28 -03:00
Herculino Trotta
331a7d5b18 locale: update translations 2025-01-27 13:30:06 -03:00
Herculino Trotta
81b8da30d6 feat: add number_format to user_settings form 2025-01-27 13:26:08 -03:00
Herculino Trotta
80bad240e7 refactor: remove custom_date filter 2025-01-27 13:25:47 -03:00
Herculino Trotta
187c56c96c refactor: remove user attr from datepicker
since monkey patched get_format already does what we want
2025-01-27 13:25:06 -03:00
Herculino Trotta
3796112d77 feat: monkey patch get_format to return usersettings 2025-01-27 13:22:21 -03:00
Herculino Trotta
958940089a feat: add number_format user setting 2025-01-27 13:20:12 -03:00
Herculino Trotta
a08548bb13 feat: add local access to user and request from anywhere 2025-01-27 13:19:28 -03:00
Herculino Trotta
7fe446e510 refactor: remove custom_date filter 2025-01-27 13:18:57 -03:00
Herculino Trotta
eccb0d15ee Merge pull request #83 from eitchtee/eitchtee-patch-1
Update README.md
2025-01-26 21:03:45 -03:00
Herculino Trotta
7ebd329706 Update README.md 2025-01-26 21:03:14 -03:00
Herculino Trotta
d3fcd5fe7e Merge pull request #82
fix datepicker datetime handling and action-bar
2025-01-26 20:56:53 -03:00
Herculino Trotta
b0a3acbdde fix: transactions action bar error on page change 2025-01-26 20:56:03 -03:00
Herculino Trotta
33ce38d74c feat(datepicker): improve value handling 2025-01-26 20:54:29 -03:00
Herculino Trotta
fa51a7fef9 fix(datepicker): wrong datetime format 2025-01-26 20:53:16 -03:00
Herculino Trotta
d7c072a35c fix(currencies): don't error out if from_currency or to_currency isn't set 2025-01-26 20:52:47 -03:00
Herculino Trotta
c88a6dcf3a Update README.md 2025-01-26 11:49:28 -03:00
Herculino Trotta
fcb54a0af2 Merge pull request #79 from DragonHeart69/main
Add new Dutch translations for v0.7.2
2025-01-26 11:20:35 -03:00
Herculino Trotta
eec2ced481 refactor(settings): drop SQL_ENGINE env variable as only postgres is supported 2025-01-26 11:19:38 -03:00
Herculino Trotta
58a6048857 fix(settings): respect SQL_PORT env variable, defaulting to 5432 if not available 2025-01-26 11:17:38 -03:00
Herculino Trotta
93774cca64 docker: update python image from slim-buster to slim-bookworm 2025-01-26 11:16:39 -03:00
Dimitri Decrock
679f49badc Add new Dutch translations for v0.7.2 2025-01-26 13:37:06 +01:00
59 changed files with 3225 additions and 565 deletions

View File

@@ -9,7 +9,6 @@ SECRET_KEY=<GENERATE A SAFE SECRET KEY AND PLACE IT HERE>
DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
OUTBOUND_PORT=9005
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=wygiwyh
SQL_USER=wygiwyh
SQL_PASSWORD=<INSERT A SAFE PASSWORD HERE>
@@ -24,3 +23,5 @@ WEB_CONCURRENCY=4
ENABLE_SOFT_DELETE=false
# If ENABLE_SOFT_DELETE is true, transactions deleted for more than KEEP_DELETED_TRANSACTIONS_FOR days will be truly deleted. Set to 0 to keep all.
KEEP_DELETED_TRANSACTIONS_FOR=365
TASK_WORKERS=1 # This only work if you're using the single container option. Increase to have more open queues via procrastinate, you probably don't need to increase this.

View File

@@ -3,6 +3,8 @@ name: Release Pipeline
on:
release:
types: [created]
push:
branches: [ main ]
env:
IMAGE_NAME: wygiwyh
@@ -29,7 +31,21 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push image
- name: Build and push nightly image
if: github.event_name == 'push'
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/prod/django/Dockerfile
push: true
provenance: false
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:nightly
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build and push release image
if: github.event_name == 'release'
uses: docker/build-push-action@v6
with:
context: .

View File

@@ -95,31 +95,16 @@ You can now access localhost:OUTBOUND_PORT
> [!NOTE]
> If you're going to use another IP that isn't localhost, add it to `DJANGO_ALLOWED_HOSTS`, without `http://`
## Building from source
Features are only added to main when ready, if you want to run the latest version, you must build from source.
Features are only added to `main` when ready, if you want to run the latest version, you must build from source.
All the required Dockerfiles are [here](https://github.com/eitchtee/WYGIWYH/tree/main/docker/prod).
```bash
# Create a folder for WYGIWYH (optional)
$ mkdir WYGIWYH
## Unraid
# Go into the folder
$ cd WYGIWYH
[nwithan8](https://github.com/nwithan8) has kindly provided a Unraid template for WYGIWYH, have a look at the [unraid_templates](https://github.com/nwithan8/unraid_templates) repo.
# Clone this repository
$ git clone https://github.com/eitchtee/WYGIWYH.git .
$ cp docker-compose.prod.yml docker-compose.yml
$ cp .env.example .env
# Now edit both files as you see fit
# Run the app
$ docker compose up -d --build
# Create the first admin account
$ docker compose exec -it web python manage.py createsuperuser
```
WYGIWYH and WYGIWYH--Procrastinate should be available on the Unraid Store. You need both for all features.
# How it works

View File

@@ -77,6 +77,7 @@ INSTALLED_APPS = [
]
MIDDLEWARE = [
"apps.common.middleware.thread_local.ThreadLocalMiddleware",
"debug_toolbar.middleware.DebugToolbarMiddleware",
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
@@ -126,12 +127,12 @@ WSGI_APPLICATION = "WYGIWYH.wsgi.application"
DATABASES = {
"default": {
"ENGINE": os.environ.get("SQL_ENGINE", "django.db.backends.sqlite3"),
"NAME": os.environ.get("SQL_DATABASE", BASE_DIR / "db.sqlite3"),
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ.get("SQL_DATABASE"),
"USER": os.environ.get("SQL_USER", "user"),
"PASSWORD": os.environ.get("SQL_PASSWORD", "password"),
"HOST": os.environ.get("SQL_HOST", "localhost"),
"PORT": "5432",
"PORT": os.environ.get("SQL_PORT", "5432"),
}
}
@@ -221,7 +222,7 @@ SESSION_COOKIE_SECURE = os.getenv("HTTPS_ENABLED", "false").lower() == "true"
DEBUG_TOOLBAR_CONFIG = {
"ROOT_TAG_EXTRA_ATTRS": "hx-preserve",
"SHOW_TOOLBAR_CALLBACK": lambda r: False, # disables it}
# "SHOW_TOOLBAR_CALLBACK": lambda r: False, # disables it
}
DEBUG_TOOLBAR_PANELS = [
"debug_toolbar.panels.history.HistoryPanel",

View File

@@ -0,0 +1,31 @@
from apps.common.middleware.thread_local import get_current_user
from django.utils.formats import get_format as original_get_format
def get_format(format_type=None, lang=None, use_l10n=None):
user = get_current_user()
if user and user.is_authenticated and hasattr(user, "settings"):
user_settings = user.settings
if format_type == "THOUSAND_SEPARATOR":
number_format = getattr(user_settings, "number_format", None)
if number_format == "DC":
return "."
elif number_format == "CD":
return ","
elif format_type == "DECIMAL_SEPARATOR":
number_format = getattr(user_settings, "number_format", None)
if number_format == "DC":
return ","
elif number_format == "CD":
return "."
elif format_type == "SHORT_DATE_FORMAT":
date_format = getattr(user_settings, "date_format", None)
if date_format and date_format != "SHORT_DATE_FORMAT":
return date_format
elif format_type == "SHORT_DATETIME_FORMAT":
datetime_format = getattr(user_settings, "datetime_format", None)
if datetime_format and datetime_format != "SHORT_DATETIME_FORMAT":
return datetime_format
return original_get_format(format_type, lang, use_l10n)

View File

@@ -1,14 +1,17 @@
import zoneinfo
from django.utils import formats
from django.utils import timezone, translation
from django.utils.translation import activate
from django.utils.functional import lazy
from apps.common.functions.format import get_format as custom_get_format
from apps.users.models import UserSettings
class LocalizationMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.patch_get_format()
def __call__(self, request):
tz = request.COOKIES.get("mytz")
@@ -33,9 +36,14 @@ class LocalizationMiddleware:
timezone.activate(zoneinfo.ZoneInfo("UTC"))
if user_language and user_language != "auto":
activate(user_language)
translation.activate(user_language)
else:
detected_language = translation.get_language_from_request(request)
activate(detected_language)
translation.activate(detected_language)
return self.get_response(request)
@staticmethod
def patch_get_format():
formats.get_format = custom_get_format
formats.get_format_lazy = lazy(custom_get_format, str, list, tuple)

View File

@@ -0,0 +1,73 @@
"""
threadlocals middleware
~~~~~~~~~~~~~~~~~~~~~~~
make the request object everywhere available (e.g. in model instance).
based on: http://code.djangoproject.com/wiki/CookBookThreadlocalsAndUser
Put this into your settings:
--------------------------------------------------------------------------
MIDDLEWARE_CLASSES = (
...
'django_tools.middlewares.ThreadLocal.ThreadLocalMiddleware',
...
)
--------------------------------------------------------------------------
Usage:
--------------------------------------------------------------------------
from django_tools.middlewares import ThreadLocal
# Get the current request object:
request = ThreadLocal.get_current_request()
# You can get the current user directly with:
user = ThreadLocal.get_current_user()
--------------------------------------------------------------------------
:copyleft: 2009-2017 by the django-tools team, see AUTHORS for more details.
:license: GNU GPL v3 or above, see LICENSE for more details.
"""
try:
from threading import local
except ImportError:
from django.utils._threading_local import local
try:
from django.utils.deprecation import MiddlewareMixin
except ImportError:
MiddlewareMixin = object # fallback for Django < 1.10
_thread_locals = local()
def get_current_request():
"""returns the request object for this thread"""
return getattr(_thread_locals, "request", None)
def get_current_user():
"""returns the current user, if exist, otherwise returns None"""
request = get_current_request()
if request:
return getattr(request, "user", None)
class ThreadLocalMiddleware(MiddlewareMixin):
"""Simple middleware that adds the request object in thread local storage."""
def process_request(self, request):
_thread_locals.request = request
def process_response(self, request, response):
if hasattr(_thread_locals, "request"):
del _thread_locals.request
return response
def process_exception(self, request, exception):
if hasattr(_thread_locals, "request"):
del _thread_locals.request

View File

@@ -1,5 +1,8 @@
import logging
from asgiref.sync import sync_to_async
from django.core import management
from procrastinate import builtin_tasks
from procrastinate.contrib.django import app
@@ -24,3 +27,16 @@ async def remove_old_jobs(context, timestamp):
exc_info=True,
)
raise e
@app.periodic(cron="0 6 1 * *")
@app.task(queueing_lock="remove_expired_sessions")
async def remove_expired_sessions(timestamp=None):
"""Cleanup expired sessions by using Django management command."""
try:
await sync_to_async(management.call_command)("clearsessions", verbosity=0)
except Exception:
logger.error(
"Error while executing 'remove_expired_sessions' task",
exc_info=True,
)

View File

@@ -1,32 +0,0 @@
from django import template
from django.template.defaultfilters import date as date_filter
from django.utils import formats, timezone
register = template.Library()
@register.filter
def custom_date(value, user=None):
if not value:
return ""
# Determine if the value is a datetime or just a date
is_datetime = hasattr(value, "hour")
# Convert to current timezone if it's a datetime
if is_datetime and timezone.is_aware(value):
value = timezone.localtime(value)
if user and user.is_authenticated:
user_settings = user.settings
if is_datetime:
format_setting = user_settings.datetime_format
else:
format_setting = user_settings.date_format
return formats.date_format(value, format_setting, use_l10n=True)
return date_filter(
value, "SHORT_DATE_FORMAT" if not is_datetime else "SHORT_DATETIME_FORMAT"
)

View File

@@ -1,6 +1,6 @@
from django import template
from django.utils.formats import get_format
from apps.common.functions.format import get_format
register = template.Library()

View File

@@ -0,0 +1,52 @@
from typing import Optional
import mistune
from django import template
from django.utils.safestring import mark_safe
from mistune import HTMLRenderer, Markdown, BlockParser, InlineParser, safe_entity
from mistune.plugins.formatting import strikethrough as plugin_strikethrough
from mistune.plugins.url import url as plugin_url
register = template.Library()
class CustomRenderer(HTMLRenderer):
def link(self, text: str, url: str, title: Optional[str] = None) -> str:
s = '<a rel="nofollow" target="_blank" href="' + self.safe_url(url) + '"'
if title:
s += ' title="' + safe_entity(title) + '"'
return s + ">" + text + "</a>"
def paragraph(self, text: str) -> str:
return text + "\n"
def softbreak(self) -> str:
return "\n"
def blank_line(self) -> str:
return "\n"
block = BlockParser()
block.rules = ["blank_line"]
inline = InlineParser(hard_wrap=False)
inline.rules = [
"emphasis",
"link",
"auto_link",
"auto_email",
"linebreak",
"softbreak",
]
markdown = Markdown(
renderer=CustomRenderer(escape=False),
block=block,
inline=inline,
plugins=[plugin_strikethrough, plugin_url],
)
@register.filter(name="limited_markdown")
def limited_markdown(value):
return mark_safe(markdown(value))

View File

@@ -13,4 +13,9 @@ urlpatterns = [
views.month_year_picker,
name="month_year_picker",
),
path(
"cache/invalidate/",
views.invalidate_cache,
name="invalidate_cache",
),
]

View File

@@ -35,7 +35,7 @@ def django_to_python_datetime(django_format):
def django_to_airdatepicker_datetime(django_format):
format_map = {
# Time
"h": "h", # Hour (12-hour)
"h": "hh", # Hour (12-hour)
"H": "H", # Hour (24-hour)
"i": "m", # Minutes
"A": "AA", # AM/PM uppercase
@@ -76,7 +76,7 @@ def django_to_airdatepicker_datetime(django_format):
def django_to_airdatepicker_datetime_separated(django_format):
format_map = {
# Time formats
"h": "hH", # Hour (12-hour)
"h": "hh", # Hour (12-hour)
"H": "HH", # Hour (24-hour)
"i": "mm", # Minutes
"A": "AA", # AM/PM uppercase

View File

@@ -1,17 +1,32 @@
from dateutil.relativedelta import relativedelta
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Count
from django.db.models.functions import ExtractYear, ExtractMonth
from django.http import HttpResponse
from django.shortcuts import render
from django.urls import reverse
from django.utils import timezone
from django.views.decorators.http import require_http_methods
from django.utils.translation import gettext_lazy as _
from cachalot.api import invalidate
from apps.common.decorators.htmx import only_htmx
from apps.transactions.models import Transaction
@only_htmx
@login_required
@require_http_methods(["GET"])
def toasts(request):
return render(request, "common/fragments/toasts.html")
@only_htmx
@login_required
@require_http_methods(["GET"])
def month_year_picker(request):
field = request.GET.get("field", "reference_date")
for_ = request.GET.get("for", None)
@@ -84,3 +99,19 @@ def month_year_picker(request):
"current_year": current_year,
},
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def invalidate_cache(request):
invalidate()
messages.success(request, _("Cache cleared successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated",
},
)

View File

@@ -2,7 +2,6 @@ import datetime
from django.forms import widgets
from django.utils import formats, translation, dates
from django.utils.formats import get_format
from django.utils.translation import gettext_lazy as _
from apps.common.utils.django import (
@@ -10,6 +9,7 @@ from apps.common.utils.django import (
django_to_airdatepicker_datetime,
django_to_airdatepicker_datetime_separated,
)
from apps.common.functions.format import get_format
class AirDatePickerInput(widgets.DateInput):
@@ -19,12 +19,10 @@ class AirDatePickerInput(widgets.DateInput):
format=None,
clear_button=True,
auto_close=True,
user=None,
*args,
**kwargs,
):
attrs = attrs or {}
self.user = user
super().__init__(attrs=attrs, format=format, *args, **kwargs)
self.clear_button = clear_button
self.auto_close = auto_close
@@ -41,12 +39,6 @@ class AirDatePickerInput(widgets.DateInput):
if self.format:
return self.format
if self.user and hasattr(self.user, "settings"):
user_format = self.user.settings.date_format
if user_format == "SHORT_DATE_FORMAT":
return get_format("SHORT_DATE_FORMAT", use_l10n=True)
return user_format
return get_format("SHORT_DATE_FORMAT", use_l10n=True)
def build_attrs(self, base_attrs, extra_attrs=None):
@@ -97,12 +89,10 @@ class AirDateTimePickerInput(widgets.DateTimeInput):
timepicker=True,
clear_button=True,
auto_close=True,
user=None,
*args,
**kwargs,
):
attrs = attrs or {}
self.user = user
super().__init__(attrs=attrs, format=format, *args, **kwargs)
self.timepicker = timepicker
self.clear_button = clear_button
@@ -120,12 +110,6 @@ class AirDateTimePickerInput(widgets.DateTimeInput):
if self.format:
return self.format
if self.user and hasattr(self.user, "settings"):
user_format = self.user.settings.datetime_format
if user_format == "SHORT_DATETIME_FORMAT":
return get_format("SHORT_DATETIME_FORMAT", use_l10n=True)
return user_format
return get_format("SHORT_DATETIME_FORMAT", use_l10n=True)
def build_attrs(self, base_attrs, extra_attrs=None):
@@ -148,9 +132,14 @@ class AirDateTimePickerInput(widgets.DateTimeInput):
def format_value(self, value):
"""Format the value for display in the widget."""
if value:
if value and isinstance(value, (datetime.date, datetime.datetime)):
self.attrs["data-value"] = datetime.datetime.strftime(
value, "%Y-%m-%d %H:%M:00"
value, "%Y-%m-%dT%H:%M:00"
)
elif value and isinstance(value, str):
value = datetime.datetime.strptime(value, "%Y-%m-%d %H:%M:00")
self.attrs["data-value"] = datetime.datetime.strftime(
value, "%Y-%m-%dT%H:%M:00"
)
if value is None:
@@ -195,6 +184,7 @@ class AirMonthYearPickerInput(AirDatePickerInput):
# Add data attributes for AirDatepicker configuration
attrs["data-now-button-txt"] = _("Today")
attrs["data-date-format"] = "MMMM yyyy"
return attrs

View File

@@ -1,7 +1,9 @@
from decimal import Decimal, InvalidOperation
from django import forms
from django.utils.formats import get_format, number_format
from django.utils.formats import number_format
from apps.common.functions.format import get_format
def convert_to_decimal(value: str):

View File

@@ -72,7 +72,7 @@ class ExchangeRateForm(forms.ModelForm):
model = ExchangeRate
fields = ["from_currency", "to_currency", "rate", "date"]
def __init__(self, *args, user=None, **kwargs):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
@@ -81,9 +81,7 @@ class ExchangeRateForm(forms.ModelForm):
self.helper.layout = Layout("date", "from_currency", "to_currency", "rate")
self.fields["rate"].widget = ArbitraryDecimalDisplayNumberInput()
self.fields["date"].widget = AirDateTimePickerInput(
clear_button=False, user=user
)
self.fields["date"].widget = AirDateTimePickerInput(clear_button=False)
if self.instance and self.instance.pk:
self.helper.layout.append(

View File

@@ -72,7 +72,9 @@ class ExchangeRate(models.Model):
def clean(self):
super().clean()
if self.from_currency == self.to_currency:
raise ValidationError(
{"to_currency": _("From and To currencies cannot be the same.")}
)
# Check if the attributes exist before comparing them
if hasattr(self, "from_currency") and hasattr(self, "to_currency"):
if self.from_currency == self.to_currency:
raise ValidationError(
{"to_currency": _("From and To currencies cannot be the same.")}
)

View File

@@ -83,7 +83,7 @@ def exchange_rates_list_pair(request):
@require_http_methods(["GET", "POST"])
def exchange_rate_add(request):
if request.method == "POST":
form = ExchangeRateForm(request.POST, user=request.user)
form = ExchangeRateForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, _("Exchange rate added successfully"))
@@ -95,7 +95,7 @@ def exchange_rate_add(request):
},
)
else:
form = ExchangeRateForm(user=request.user)
form = ExchangeRateForm()
return render(
request,
@@ -111,7 +111,7 @@ def exchange_rate_edit(request, pk):
exchange_rate = get_object_or_404(ExchangeRate, id=pk)
if request.method == "POST":
form = ExchangeRateForm(request.POST, instance=exchange_rate, user=request.user)
form = ExchangeRateForm(request.POST, instance=exchange_rate)
if form.is_valid():
form.save()
messages.success(request, _("Exchange rate updated successfully"))
@@ -123,7 +123,7 @@ def exchange_rate_edit(request, pk):
},
)
else:
form = ExchangeRateForm(instance=exchange_rate, user=request.user)
form = ExchangeRateForm(instance=exchange_rate)
return render(
request,

View File

@@ -65,7 +65,7 @@ class DCAEntryForm(forms.ModelForm):
"notes": forms.Textarea(attrs={"rows": 3}),
}
def __init__(self, *args, user=None, **kwargs):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
@@ -106,4 +106,4 @@ class DCAEntryForm(forms.ModelForm):
self.fields["amount_paid"].widget = ArbitraryDecimalDisplayNumberInput()
self.fields["amount_received"].widget = ArbitraryDecimalDisplayNumberInput()
self.fields["date"].widget = AirDatePickerInput(clear_button=False, user=user)
self.fields["date"].widget = AirDatePickerInput(clear_button=False)

View File

@@ -155,7 +155,7 @@ def strategy_detail(request, strategy_id):
def strategy_entry_add(request, strategy_id):
strategy = get_object_or_404(DCAStrategy, id=strategy_id)
if request.method == "POST":
form = DCAEntryForm(request.POST, user=request.user)
form = DCAEntryForm(request.POST)
if form.is_valid():
entry = form.save(commit=False)
entry.strategy = strategy
@@ -169,7 +169,7 @@ def strategy_entry_add(request, strategy_id):
},
)
else:
form = DCAEntryForm(user=request.user)
form = DCAEntryForm()
return render(
request,
@@ -184,7 +184,7 @@ def strategy_entry_edit(request, strategy_id, entry_id):
dca_entry = get_object_or_404(DCAEntry, id=entry_id, strategy__id=strategy_id)
if request.method == "POST":
form = DCAEntryForm(request.POST, instance=dca_entry, user=request.user)
form = DCAEntryForm(request.POST, instance=dca_entry)
if form.is_valid():
form.save()
messages.success(request, _("Entry updated successfully"))
@@ -196,7 +196,7 @@ def strategy_entry_edit(request, strategy_id, entry_id):
},
)
else:
form = DCAEntryForm(instance=dca_entry, user=request.user)
form = DCAEntryForm(instance=dca_entry)
return render(
request,

View File

@@ -65,7 +65,7 @@ class CSVImportSettings(BaseModel):
class ColumnMapping(BaseModel):
source: Optional[str] = Field(
source: Optional[str] | Optional[list[str]] = Field(
default=None,
description="CSV column header. If None, the field will be generated from transformations",
)

View File

@@ -486,8 +486,18 @@ class ImportService:
mapped_data = {}
for field, mapping in self.mapping.items():
# If source is None, use None as the initial value
value = row.get(mapping.source) if mapping.source else None
value = None
if isinstance(mapping.source, str):
value = row.get(mapping.source)
elif isinstance(mapping.source, list):
for source in mapping.source:
value = row.get(source)
if value is not None:
break
else:
# If source is None, use None as the initial value
value = None
# Use default_value if value is None
if value is None:

View File

@@ -30,6 +30,8 @@ def index(request):
@login_required
@require_http_methods(["GET"])
def monthly_overview(request, month: int, year: int):
order = request.session.get("monthly_transactions_order", "default")
if month < 1 or month > 12:
from django.http import Http404
@@ -41,7 +43,7 @@ def monthly_overview(request, month: int, year: int):
previous_month = 12 if month == 1 else month - 1
previous_year = year - 1 if previous_month == 12 and month == 1 else year
f = TransactionsFilter(request.GET, user=request.user)
f = TransactionsFilter(request.GET)
return render(
request,
@@ -54,6 +56,7 @@ def monthly_overview(request, month: int, year: int):
"previous_month": previous_month,
"previous_year": previous_year,
"filter": f,
"order": order,
},
)
@@ -62,9 +65,14 @@ def monthly_overview(request, month: int, year: int):
@login_required
@require_http_methods(["GET"])
def transactions_list(request, month: int, year: int):
order = request.GET.get("order")
order = request.session.get("monthly_transactions_order", "default")
f = TransactionsFilter(request.GET, user=request.user)
if "order" in request.GET:
order = request.GET["order"]
if order != request.session.get("monthly_transactions_order", "default"):
request.session["monthly_transactions_order"] = order
f = TransactionsFilter(request.GET)
transactions_filtered = (
f.qs.filter()
.filter(
@@ -76,9 +84,12 @@ def transactions_list(request, month: int, year: int):
"account__group",
"category",
"tags",
"tags__id",
"account__exchange_currency",
"account__currency",
"installment_plan",
"entities__name",
"entities__id",
)
)

View File

@@ -133,7 +133,7 @@ class TransactionsFilter(django_filters.FilterSet):
"to_amount",
]
def __init__(self, data=None, user=None, *args, **kwargs):
def __init__(self, data=None, *args, **kwargs):
# if filterset is bound, use initial values as defaults
if data is not None:
# get a mutable copy of the QueryDict
@@ -182,5 +182,5 @@ class TransactionsFilter(django_filters.FilterSet):
self.form.fields["to_amount"].widget = ArbitraryDecimalDisplayNumberInput()
self.form.fields["from_amount"].widget = ArbitraryDecimalDisplayNumberInput()
self.form.fields["date_start"].widget = AirDatePickerInput(user=user)
self.form.fields["date_end"].widget = AirDatePickerInput(user=user)
self.form.fields["date_start"].widget = AirDatePickerInput()
self.form.fields["date_end"].widget = AirDatePickerInput()

View File

@@ -86,7 +86,7 @@ class TransactionForm(forms.ModelForm):
"account": TomSelect(clear_button=False, group_by="group"),
}
def __init__(self, *args, user=None, **kwargs):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# if editing a transaction display non-archived items and it's own item even if it's archived
@@ -177,7 +177,7 @@ class TransactionForm(forms.ModelForm):
)
self.fields["reference_date"].required = False
self.fields["date"].widget = AirDatePickerInput(clear_button=False, user=user)
self.fields["date"].widget = AirDatePickerInput(clear_button=False)
if self.instance and self.instance.pk:
decimal_places = self.instance.account.currency.decimal_places
@@ -333,7 +333,7 @@ class TransferForm(forms.Form):
label=_("Notes"),
)
def __init__(self, *args, user=None, **kwargs):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
@@ -402,7 +402,7 @@ class TransferForm(forms.Form):
self.fields["from_amount"].widget = ArbitraryDecimalDisplayNumberInput()
self.fields["to_amount"].widget = ArbitraryDecimalDisplayNumberInput()
self.fields["date"].widget = AirDatePickerInput(clear_button=False, user=user)
self.fields["date"].widget = AirDatePickerInput(clear_button=False)
def clean(self):
cleaned_data = super().clean()
@@ -515,7 +515,7 @@ class InstallmentPlanForm(forms.ModelForm):
"notes": forms.Textarea(attrs={"rows": 3}),
}
def __init__(self, *args, user=None, **kwargs):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# if editing display non-archived items and it's own item even if it's archived
@@ -572,9 +572,7 @@ class InstallmentPlanForm(forms.ModelForm):
)
self.fields["installment_amount"].widget = ArbitraryDecimalDisplayNumberInput()
self.fields["start_date"].widget = AirDatePickerInput(
clear_button=False, user=user
)
self.fields["start_date"].widget = AirDatePickerInput(clear_button=False)
if self.instance and self.instance.pk:
self.helper.layout.append(
@@ -762,7 +760,7 @@ class RecurringTransactionForm(forms.ModelForm):
),
}
def __init__(self, *args, user=None, **kwargs):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# if editing display non-archived items and it's own item even if it's archived
@@ -819,10 +817,8 @@ class RecurringTransactionForm(forms.ModelForm):
)
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
self.fields["start_date"].widget = AirDatePickerInput(
clear_button=False, user=user
)
self.fields["end_date"].widget = AirDatePickerInput(user=user)
self.fields["start_date"].widget = AirDatePickerInput(clear_button=False)
self.fields["end_date"].widget = AirDatePickerInput()
if self.instance and self.instance.pk:
self.helper.layout.append(

View File

@@ -49,7 +49,7 @@ class SoftDeleteQuerySet(models.QuerySet):
class SoftDeleteManager(models.Manager):
def get_queryset(self):
qs = SoftDeleteQuerySet(self.model, using=self._db)
return qs if not settings.ENABLE_SOFT_DELETE else qs.filter(deleted=False)
return qs.filter(deleted=False)
class AllObjectsManager(models.Manager):
@@ -60,7 +60,7 @@ class AllObjectsManager(models.Manager):
class DeletedObjectsManager(models.Manager):
def get_queryset(self):
qs = SoftDeleteQuerySet(self.model, using=self._db)
return qs if not settings.ENABLE_SOFT_DELETE else qs.filter(deleted=True)
return qs.filter(deleted=True)
class TransactionCategory(models.Model):

View File

@@ -27,7 +27,7 @@ def generate_recurring_transactions(timestamp=None):
@app.periodic(cron="10 1 * * *")
@app.task
def cleanup_deleted_transactions():
def cleanup_deleted_transactions(timestamp=None):
with cachalot_disabled():
if settings.ENABLE_SOFT_DELETE and settings.KEEP_DELETED_TRANSACTIONS_FOR == 0:
return "KEEP_DELETED_TRANSACTIONS_FOR is 0, no cleanup performed."
@@ -44,7 +44,7 @@ def cleanup_deleted_transactions():
days=settings.KEEP_DELETED_TRANSACTIONS_FOR
)
invalidate("transactions.Transaction")
invalidate()
# Hard delete soft-deleted transactions older than the cutoff date
old_transactions = Transaction.deleted_objects.filter(deleted_at__lt=cutoff_date)

View File

@@ -81,7 +81,7 @@ def installment_plan_transactions(request, installment_plan_id):
@require_http_methods(["GET", "POST"])
def installment_plan_add(request):
if request.method == "POST":
form = InstallmentPlanForm(request.POST, user=request.user)
form = InstallmentPlanForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, _("Installment Plan added successfully"))
@@ -93,7 +93,7 @@ def installment_plan_add(request):
},
)
else:
form = InstallmentPlanForm(user=request.user)
form = InstallmentPlanForm()
return render(
request,
@@ -109,9 +109,7 @@ def installment_plan_edit(request, installment_plan_id):
installment_plan = get_object_or_404(InstallmentPlan, id=installment_plan_id)
if request.method == "POST":
form = InstallmentPlanForm(
request.POST, instance=installment_plan, user=request.user
)
form = InstallmentPlanForm(request.POST, instance=installment_plan)
if form.is_valid():
form.save()
messages.success(request, _("Installment Plan updated successfully"))
@@ -123,7 +121,7 @@ def installment_plan_edit(request, installment_plan_id):
},
)
else:
form = InstallmentPlanForm(instance=installment_plan, user=request.user)
form = InstallmentPlanForm(instance=installment_plan)
return render(
request,

View File

@@ -106,7 +106,7 @@ def recurring_transaction_transactions(request, recurring_transaction_id):
@require_http_methods(["GET", "POST"])
def recurring_transaction_add(request):
if request.method == "POST":
form = RecurringTransactionForm(request.POST, user=request.user)
form = RecurringTransactionForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, _("Recurring Transaction added successfully"))
@@ -118,7 +118,7 @@ def recurring_transaction_add(request):
},
)
else:
form = RecurringTransactionForm(user=request.user)
form = RecurringTransactionForm()
return render(
request,
@@ -136,9 +136,7 @@ def recurring_transaction_edit(request, recurring_transaction_id):
)
if request.method == "POST":
form = RecurringTransactionForm(
request.POST, instance=recurring_transaction, user=request.user
)
form = RecurringTransactionForm(request.POST, instance=recurring_transaction)
if form.is_valid():
form.save()
messages.success(request, _("Recurring Transaction updated successfully"))
@@ -150,9 +148,7 @@ def recurring_transaction_edit(request, recurring_transaction_id):
},
)
else:
form = RecurringTransactionForm(
instance=recurring_transaction, user=request.user
)
form = RecurringTransactionForm(instance=recurring_transaction)
return render(
request,

View File

@@ -44,7 +44,7 @@ def transaction_add(request):
).date()
if request.method == "POST":
form = TransactionForm(request.POST, user=request.user)
form = TransactionForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, _("Transaction added successfully"))
@@ -55,7 +55,6 @@ def transaction_add(request):
)
else:
form = TransactionForm(
user=request.user,
initial={
"date": expected_date,
"type": transaction_type,
@@ -84,13 +83,12 @@ def transaction_simple_add(request):
).date()
if request.method == "POST":
form = TransactionForm(request.POST, user=request.user)
form = TransactionForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, _("Transaction added successfully"))
form = TransactionForm(
user=request.user,
initial={
"date": expected_date,
"type": transaction_type,
@@ -99,7 +97,6 @@ def transaction_simple_add(request):
else:
form = TransactionForm(
user=request.user,
initial={
"date": expected_date,
"type": transaction_type,
@@ -120,7 +117,7 @@ def transaction_edit(request, transaction_id, **kwargs):
transaction = get_object_or_404(Transaction, id=transaction_id)
if request.method == "POST":
form = TransactionForm(request.POST, user=request.user, instance=transaction)
form = TransactionForm(request.POST, instance=transaction)
if form.is_valid():
form.save()
messages.success(request, _("Transaction updated successfully"))
@@ -130,7 +127,7 @@ def transaction_edit(request, transaction_id, **kwargs):
headers={"HX-Trigger": "updated, hide_offcanvas"},
)
else:
form = TransactionForm(instance=transaction, user=request.user)
form = TransactionForm(instance=transaction)
return render(
request,
@@ -152,7 +149,7 @@ def transactions_bulk_edit(request):
count = transactions.count()
if request.method == "POST":
form = BulkEditTransactionForm(request.POST, user=request.user)
form = BulkEditTransactionForm(request.POST)
if form.is_valid():
# Apply changes from the form to all selected transactions
for transaction in transactions:
@@ -184,9 +181,7 @@ def transactions_bulk_edit(request):
headers={"HX-Trigger": "updated, hide_offcanvas"},
)
else:
form = BulkEditTransactionForm(
initial={"is_paid": None, "type": None}, user=request.user
)
form = BulkEditTransactionForm(initial={"is_paid": None, "type": None})
context = {
"form": form,
@@ -276,7 +271,7 @@ def transactions_transfer(request):
).date()
if request.method == "POST":
form = TransferForm(request.POST, user=request.user)
form = TransferForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, _("Transfer added successfully"))
@@ -290,7 +285,6 @@ def transactions_transfer(request):
"reference_date": expected_date,
"date": expected_date,
},
user=request.user,
)
return render(request, "transactions/fragments/transfer.html", {"form": form})
@@ -319,29 +313,40 @@ def transaction_pay(request, transaction_id):
@login_required
@require_http_methods(["GET"])
def transaction_all_index(request):
f = TransactionsFilter(request.GET, user=request.user)
return render(request, "transactions/pages/transactions.html", {"filter": f})
order = request.session.get("all_transactions_order", "default")
f = TransactionsFilter(request.GET)
return render(
request, "transactions/pages/transactions.html", {"filter": f, "order": order}
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def transaction_all_list(request):
order = request.GET.get("order")
order = request.session.get("all_transactions_order", "default")
if "order" in request.GET:
order = request.GET["order"]
if order != request.session.get("all_transactions_order", "default"):
request.session["all_transactions_order"] = order
transactions = Transaction.objects.prefetch_related(
"account",
"account__group",
"category",
"tags",
"tags__id",
"account__exchange_currency",
"account__currency",
"installment_plan",
"entities__name",
"entities__id",
).all()
transactions = default_order(transactions, order=order)
f = TransactionsFilter(request.GET, user=request.user, queryset=transactions)
f = TransactionsFilter(request.GET, queryset=transactions)
page_number = request.GET.get("page", 1)
paginator = Paginator(f.qs, 100)
@@ -371,7 +376,7 @@ def transaction_all_summary(request):
"installment_plan",
).all()
f = TransactionsFilter(request.GET, user=request.user, queryset=transactions)
f = TransactionsFilter(request.GET, queryset=transactions)
currency_data = calculate_currency_totals(f.qs.all(), ignore_empty=True)
currency_percentages = calculate_percentage_distribution(currency_data)

View File

@@ -81,6 +81,12 @@ class UserSettingsForm(forms.ModelForm):
("Y.m.d h:i A", "2025.01.20 03:30 PM"),
]
NUMBER_FORMAT_CHOICES = [
("AA", _("Default")),
("DC", "1.234,50"),
("CD", "1,234.50"),
]
date_format = forms.ChoiceField(
choices=DATE_FORMAT_CHOICES, initial="SHORT_DATE_FORMAT", label=_("Date Format")
)
@@ -90,6 +96,12 @@ class UserSettingsForm(forms.ModelForm):
label=_("Datetime Format"),
)
number_format = forms.ChoiceField(
choices=NUMBER_FORMAT_CHOICES,
initial="AA",
label=_("Number Format"),
)
class Meta:
model = UserSettings
fields = [
@@ -98,6 +110,7 @@ class UserSettingsForm(forms.ModelForm):
"start_page",
"date_format",
"datetime_format",
"number_format",
]
def __init__(self, *args, **kwargs):
@@ -111,6 +124,7 @@ class UserSettingsForm(forms.ModelForm):
"timezone",
"date_format",
"datetime_format",
"number_format",
"start_page",
FormActions(
NoClassSubmit(

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.5 on 2025-01-27 12:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0016_alter_usersettings_language'),
]
operations = [
migrations.AddField(
model_name='usersettings',
name='number_format',
field=models.CharField(default='AA', max_length=2, verbose_name='Number Format'),
),
]

View File

@@ -1,8 +1,8 @@
import pytz
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
from django.contrib.auth.models import AbstractUser, Group
from django.db import models
from django.utils.translation import gettext_lazy as _
from apps.users.managers import UserManager
@@ -44,6 +44,9 @@ class UserSettings(models.Model):
default="SHORT_DATETIME_FORMAT",
verbose_name=_("Datetime Format"),
)
number_format = models.CharField(
max_length=2, default="AA", verbose_name=_("Number Format")
)
language = models.CharField(
max_length=10,
@@ -66,3 +69,6 @@ class UserSettings(models.Model):
def __str__(self):
return f"{self.user.email}'s settings"
def clean(self):
super().clean()

File diff suppressed because it is too large Load Diff

View File

@@ -2,17 +2,17 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-25 18:49+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"POT-Creation-Date: 2025-01-28 00:49+0000\n"
"PO-Revision-Date: 2025-01-29 06:12+0100\n"
"Last-Translator: Dimitri Decrock <dimitri@fam-decrock.eu>\n"
"Language-Team: \n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -24,28 +24,28 @@ msgid "Group name"
msgstr "Groepsnaam"
#: apps/accounts/forms.py:40 apps/accounts/forms.py:96
#: apps/currencies/forms.py:52 apps/currencies/forms.py:92 apps/dca/forms.py:41
#: 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:583
#: apps/transactions/forms.py:626 apps/transactions/forms.py:658
#: apps/transactions/forms.py:693 apps/transactions/forms.py:831
#: 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:100 apps/dca/forms.py:49 apps/dca/forms.py:102
#: 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:591 apps/transactions/forms.py:634
#: apps/transactions/forms.py:666 apps/transactions/forms.py:701
#: apps/transactions/forms.py:839
#: 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
#: templates/currencies/fragments/list.html:9
#: templates/dca/fragments/strategy/details.html:38
#: templates/dca/fragments/strategy/details.html:37
#: templates/dca/fragments/strategy/list.html:9
#: templates/entities/fragments/list.html:9
#: templates/exchange_rates/fragments/list.html:10
@@ -69,7 +69,7 @@ msgstr "Nieuw saldo"
#: apps/accounts/forms.py:119 apps/rules/models.py:27
#: apps/transactions/forms.py:39 apps/transactions/forms.py:291
#: apps/transactions/forms.py:298 apps/transactions/forms.py:478
#: apps/transactions/forms.py:725 apps/transactions/models.py:159
#: apps/transactions/forms.py:723 apps/transactions/models.py:159
#: apps/transactions/models.py:311 apps/transactions/models.py:491
msgid "Category"
msgstr "Categorie"
@@ -77,7 +77,7 @@ msgstr "Categorie"
#: apps/accounts/forms.py:126 apps/rules/models.py:28
#: 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:718
#: apps/transactions/forms.py:471 apps/transactions/forms.py:716
#: apps/transactions/models.py:165 apps/transactions/models.py:313
#: apps/transactions/models.py:495 templates/includes/navbar.html:98
#: templates/tags/fragments/list.html:5 templates/tags/pages/index.html:4
@@ -122,7 +122,6 @@ msgid "Exchange Currency"
msgstr "Eenheid Wisselgeld"
#: apps/accounts/models.py:42 apps/currencies/models.py:25
#, fuzzy
msgid "Default currency for exchange calculations"
msgstr "Standaard munteenheid voor wisselberekeningen"
@@ -152,7 +151,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:710 apps/transactions/models.py:132
#: apps/transactions/forms.py:708 apps/transactions/models.py:132
#: apps/transactions/models.py:271 apps/transactions/models.py:473
msgid "Account"
msgstr "Rekening"
@@ -195,12 +194,10 @@ msgid "Account deleted successfully"
msgstr "Rekening succesvol verwijderd"
#: apps/accounts/views/balance.py:77
#, fuzzy
msgid "Balance reconciliation"
msgstr "Saldi afstemming"
#: apps/accounts/views/balance.py:85
#, fuzzy
msgid "Account balances have been reconciled successfully"
msgstr "Rekeningsaldi zijn succesvol afgestemd"
@@ -325,11 +322,15 @@ msgstr "Fout"
msgid "Info"
msgstr "Info"
#: apps/common/widgets/datepicker.py:55 apps/common/widgets/datepicker.py:197
#: apps/common/views.py:110
msgid "Cache cleared successfully"
msgstr "Categorie succesvol bijgewerkt"
#: apps/common/widgets/datepicker.py:47 apps/common/widgets/datepicker.py:186
msgid "Today"
msgstr "Vandaag"
#: apps/common/widgets/datepicker.py:139
#: apps/common/widgets/datepicker.py:123
msgid "Now"
msgstr "Nu"
@@ -349,30 +350,26 @@ msgid "No results..."
msgstr "Geen resultaten..."
#: apps/currencies/forms.py:16 apps/currencies/models.py:15
#, fuzzy
msgid "Prefix"
msgstr "Voorvoegsel"
#: apps/currencies/forms.py:17 apps/currencies/models.py:16
#, fuzzy
msgid "Suffix"
msgstr "Achtervoegsel"
#: apps/currencies/forms.py:68 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:53
#: templates/exchange_rates/fragments/table.html:11
#: templates/dca/fragments/strategy/details.html:52
#: templates/exchange_rates/fragments/table.html:10
msgid "Date"
msgstr "Datum"
#: apps/currencies/models.py:8
#, fuzzy
msgid "Currency Code"
msgstr "Munteenheids Code"
#: apps/currencies/models.py:9
#, fuzzy
msgid "Currency Name"
msgstr "Munteenheids Naam"
@@ -414,7 +411,7 @@ msgstr "Datum en Tijd"
msgid "Exchange Rates"
msgstr "Wisselkoersen"
#: apps/currencies/models.py:77
#: apps/currencies/models.py:79
msgid "From and To currencies cannot be the same."
msgstr "Van en Naar munteenheid kunnen niet dezelfde zijn."
@@ -468,11 +465,11 @@ msgstr "DCA Strategieën"
msgid "Strategy"
msgstr "Strategie"
#: apps/dca/models.py:158 templates/dca/fragments/strategy/details.html:55
#: apps/dca/models.py:158 templates/dca/fragments/strategy/details.html:54
msgid "Amount Paid"
msgstr "Betaald bedrag"
#: apps/dca/models.py:161 templates/dca/fragments/strategy/details.html:54
#: apps/dca/models.py:161 templates/dca/fragments/strategy/details.html:53
msgid "Amount Received"
msgstr "Ontvangen bedrag"
@@ -518,43 +515,43 @@ msgstr "Item succesvol verwijderd"
#: apps/import_app/forms.py:49
msgid "Select a file"
msgstr ""
msgstr "Selecteer een bestand"
#: apps/import_app/forms.py:61
#: templates/import_app/fragments/profiles/list.html:62
#: templates/includes/navbar.html:124
msgid "Import"
msgstr ""
msgstr "Importeer"
#: apps/import_app/models.py:15
msgid "YAML Configuration"
msgstr ""
msgstr "YAML Configuratie"
#: apps/import_app/models.py:19
#: templates/import_app/fragments/profiles/list.html:37
msgid "Version"
msgstr ""
msgstr "Versie"
#: apps/import_app/models.py:30
#, python-brace-format
msgid "Version {number}"
msgstr ""
msgstr "Versie {number}"
#: apps/import_app/models.py:39
msgid "Invalid YAML Configuration: "
msgstr ""
msgstr "Ongeldige YAML Configuratie: "
#: apps/import_app/models.py:45
msgid "Queued"
msgstr ""
msgstr "In wachtrij"
#: apps/import_app/models.py:46
msgid "Processing"
msgstr ""
msgstr "Verwerking"
#: apps/import_app/models.py:47
msgid "Failed"
msgstr ""
msgstr "Mislukt"
#: apps/import_app/models.py:48
#: templates/installment_plans/fragments/list.html:24
@@ -564,31 +561,31 @@ msgstr "Voltooid"
#: apps/import_app/models.py:54
msgid "Status"
msgstr ""
msgstr "Status"
#: apps/import_app/models.py:62
msgid "File name"
msgstr ""
msgstr "Bestandsnaam"
#: apps/import_app/views.py:75
msgid "Import Profile added successfully"
msgstr ""
msgstr "Importprofiel succesvol toegevoegd"
#: apps/import_app/views.py:110
msgid "Import Profile update successfully"
msgstr ""
msgstr "Importprofiel succesvol bijgewerkt"
#: apps/import_app/views.py:136
msgid "Import Profile deleted successfully"
msgstr ""
msgstr "Importprofiel succesvol verwijderd"
#: apps/import_app/views.py:194
msgid "Import Run queued successfully"
msgstr ""
msgstr "Importrun met succes in de wachtrij geplaatst"
#: apps/import_app/views.py:220
msgid "Run deleted successfully"
msgstr ""
msgstr "Run met succes verwijderd"
#: apps/rules/forms.py:20
msgid "Run on creation"
@@ -650,7 +647,7 @@ 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:733 apps/transactions/models.py:117
#: apps/transactions/forms.py:731 apps/transactions/models.py:117
#: apps/transactions/models.py:170 apps/transactions/models.py:316
#: apps/transactions/models.py:498 templates/entities/fragments/list.html:5
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:100
@@ -738,7 +735,7 @@ msgstr "Maximaal bedrag"
#: apps/transactions/forms.py:158
msgid "More"
msgstr ""
msgstr "Meer"
#: apps/transactions/forms.py:266
msgid "From Account"
@@ -768,23 +765,23 @@ msgstr "Overschrijving"
msgid "From and To accounts must be different."
msgstr "Van en Naar rekening moeten verschillend zijn."
#: apps/transactions/forms.py:612
#: apps/transactions/forms.py:610
msgid "Tag name"
msgstr "Labelnaam"
#: apps/transactions/forms.py:644
#: apps/transactions/forms.py:642
msgid "Entity name"
msgstr "Naam van bedrijf"
#: apps/transactions/forms.py:676
#: apps/transactions/forms.py:674
msgid "Category name"
msgstr "Naam van categorie"
#: apps/transactions/forms.py:678
#: apps/transactions/forms.py:676
msgid "Muted categories won't count towards your monthly total"
msgstr "Gedempte categorieën tellen niet mee voor je maandtotaal"
#: apps/transactions/forms.py:850
#: apps/transactions/forms.py:846
msgid "End date should be after the start date"
msgstr "De einddatum moet na de begindatum vallen"
@@ -867,19 +864,19 @@ msgstr "Terugkerende verrichting"
#: apps/transactions/models.py:192
msgid "Internal Note"
msgstr ""
msgstr "Interne opmerking"
#: apps/transactions/models.py:194
msgid "Internal ID"
msgstr ""
msgstr "Interne ID"
#: apps/transactions/models.py:198
msgid "Deleted"
msgstr ""
msgstr "Verwijderd"
#: apps/transactions/models.py:203
msgid "Deleted At"
msgstr ""
msgstr "Verwijderd Op"
#: apps/transactions/models.py:211
msgid "Transaction"
@@ -1001,29 +998,29 @@ msgstr "%(value)s is geen niet-negatief getal"
#, python-format
msgid "%(count)s transaction marked as paid"
msgid_plural "%(count)s transactions marked as paid"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%(count)s verrichting gemarkeerd als betaald"
msgstr[1] "%(count)s verrichtingen gemarkeerd als betaald"
#: apps/transactions/views/actions.py:47
#, python-format
msgid "%(count)s transaction marked as not paid"
msgid_plural "%(count)s transactions marked as not paid"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%(count)s verrichting gemarkeerd als niet betaald"
msgstr[1] "%(count)s verrichtingen gemarkeerd als niet betaald"
#: apps/transactions/views/actions.py:71
#, python-format
msgid "%(count)s transaction deleted successfully"
msgid_plural "%(count)s transactions deleted successfully"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%(count)s verrichting succesvol verwijderd"
msgstr[1] "%(count)s verrichtingen succesvol verwijderd"
#: apps/transactions/views/actions.py:106
#, python-format
msgid "%(count)s transaction duplicated successfully"
msgid_plural "%(count)s transactions duplicated successfully"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%(count)s verrichting succesvol gedupliceerd"
msgstr[1] "%(count)s verrichtingen succesvol gedupliceerd"
#: apps/transactions/views/categories.py:64
msgid "Category added successfully"
@@ -1053,15 +1050,15 @@ msgstr "Bedrijf succesvol verwijderd"
msgid "Installment Plan added successfully"
msgstr "Afbetalingsplan succesvol toegevoegd"
#: apps/transactions/views/installment_plans.py:117
#: apps/transactions/views/installment_plans.py:115
msgid "Installment Plan updated successfully"
msgstr "Afbetalingsplan succesvol bijgewerkt"
#: apps/transactions/views/installment_plans.py:142
#: apps/transactions/views/installment_plans.py:140
msgid "Installment Plan refreshed successfully"
msgstr "Afbetalingsplan succesvol vernieuwd"
#: apps/transactions/views/installment_plans.py:160
#: apps/transactions/views/installment_plans.py:158
msgid "Installment Plan deleted successfully"
msgstr "Afbetalingsplan succesvol verwijderd"
@@ -1069,23 +1066,23 @@ msgstr "Afbetalingsplan succesvol verwijderd"
msgid "Recurring Transaction added successfully"
msgstr "Terugkerende Verrichting succesvol toegevoegd"
#: apps/transactions/views/recurring_transactions.py:144
#: apps/transactions/views/recurring_transactions.py:142
msgid "Recurring Transaction updated successfully"
msgstr "Terugkerende Verrichting succesvol bijgewerkt"
#: apps/transactions/views/recurring_transactions.py:190
#: apps/transactions/views/recurring_transactions.py:186
msgid "Recurring transaction unpaused successfully"
msgstr "Terugkerende Verrichting succesvol hervat"
#: apps/transactions/views/recurring_transactions.py:193
#: apps/transactions/views/recurring_transactions.py:189
msgid "Recurring transaction paused successfully"
msgstr "Terugkerende Verrichting succesvol onderbroken"
#: apps/transactions/views/recurring_transactions.py:219
#: apps/transactions/views/recurring_transactions.py:215
msgid "Recurring transaction finished successfully"
msgstr "Terugkerende Verrichting succesvol voltooid"
#: apps/transactions/views/recurring_transactions.py:239
#: apps/transactions/views/recurring_transactions.py:235
msgid "Recurring Transaction deleted successfully"
msgstr "Terugkerende Verrichting succesvol verwijderd"
@@ -1102,30 +1099,30 @@ msgid "Tag deleted successfully"
msgstr "Label succesvol verwijderd"
#: apps/transactions/views/transactions.py:50
#: apps/transactions/views/transactions.py:90
#: apps/transactions/views/transactions.py:89
msgid "Transaction added successfully"
msgstr "Verrichting succesvol toegevoegd"
#: apps/transactions/views/transactions.py:126
#: apps/transactions/views/transactions.py:123
msgid "Transaction updated successfully"
msgstr "Verrichting succesvol bijgewerkt"
#: apps/transactions/views/transactions.py:176
#: apps/transactions/views/transactions.py:173
#, python-format
msgid "%(count)s transaction updated successfully"
msgid_plural "%(count)s transactions updated successfully"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%(count)s verrichting succesvol bijgewerkt"
msgstr[1] "%(count)s verrichtingen succesvol bijgewerkt"
#: apps/transactions/views/transactions.py:214
#: apps/transactions/views/transactions.py:209
msgid "Transaction duplicated successfully"
msgstr "Verrichting succesvol gedupliceerd"
#: apps/transactions/views/transactions.py:256
#: apps/transactions/views/transactions.py:251
msgid "Transaction deleted successfully"
msgstr "Verrichting succesvol verwijderd"
#: apps/transactions/views/transactions.py:282
#: apps/transactions/views/transactions.py:277
msgid "Transfer added successfully"
msgstr "Transactie succesvol toegevoegd"
@@ -1165,21 +1162,25 @@ msgstr "Ongeldig e-mailadres of wachtwoord"
msgid "This account is deactivated"
msgstr "Deze gebruiker is gedeactiveerd"
#: apps/users/forms.py:50 apps/users/forms.py:63
#: apps/users/forms.py:50 apps/users/forms.py:63 apps/users/forms.py:85
#: templates/monthly_overview/pages/overview.html:116
#: templates/transactions/pages/transactions.html:35
msgid "Default"
msgstr "Standaard"
#: apps/users/forms.py:85 apps/users/models.py:40
#: apps/users/forms.py:91 apps/users/models.py:40
msgid "Date Format"
msgstr "Datumnotatie"
#: apps/users/forms.py:90 apps/users/models.py:45
#: apps/users/forms.py:96 apps/users/models.py:45
msgid "Datetime Format"
msgstr "Tijdsnotatie"
#: apps/users/forms.py:117
#: apps/users/forms.py:102 apps/users/models.py:48
msgid "Number Format"
msgstr "Schrijfwijze Nummers"
#: apps/users/forms.py:131
msgid "Save"
msgstr "Opslaan"
@@ -1203,19 +1204,19 @@ msgstr "Alle Verrichtingen"
msgid "Calendar"
msgstr "Kalender"
#: apps/users/models.py:50 apps/users/models.py:56
#: apps/users/models.py:53 apps/users/models.py:59
msgid "Auto"
msgstr "Automatisch"
#: apps/users/models.py:52
#: apps/users/models.py:55
msgid "Language"
msgstr "Taal"
#: apps/users/models.py:58
#: apps/users/models.py:61
msgid "Time Zone"
msgstr "Tijdszone"
#: apps/users/models.py:64
#: apps/users/models.py:67
msgid "Start page"
msgstr "Startpagina"
@@ -1251,9 +1252,9 @@ msgstr "Rekeningsgroep bewerken"
#: templates/accounts/fragments/list.html:37
#: templates/categories/fragments/table.html:24
#: templates/currencies/fragments/list.html:33
#: templates/dca/fragments/strategy/details.html:64
#: templates/dca/fragments/strategy/details.html:63
#: templates/entities/fragments/table.html:23
#: templates/exchange_rates/fragments/table.html:20
#: templates/exchange_rates/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
@@ -1265,13 +1266,13 @@ msgstr "Acties"
#: templates/account_groups/fragments/list.html:36
#: templates/accounts/fragments/list.html:41
#: templates/categories/fragments/table.html:29
#: templates/cotton/transaction/item.html:110
#: templates/cotton/ui/transactions_action_bar.html:43
#: templates/cotton/transaction/item.html:109
#: templates/cotton/ui/transactions_action_bar.html:47
#: templates/currencies/fragments/list.html:37
#: templates/dca/fragments/strategy/details.html:68
#: templates/dca/fragments/strategy/details.html:67
#: templates/dca/fragments/strategy/list.html:34
#: templates/entities/fragments/table.html:28
#: templates/exchange_rates/fragments/table.html:24
#: templates/exchange_rates/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
@@ -1284,13 +1285,13 @@ msgstr "Bijwerken"
#: templates/account_groups/fragments/list.html:43
#: templates/accounts/fragments/list.html:48
#: templates/categories/fragments/table.html:36
#: templates/cotton/transaction/item.html:125
#: templates/cotton/ui/transactions_action_bar.html:80
#: templates/cotton/transaction/item.html:124
#: templates/cotton/ui/transactions_action_bar.html:84
#: templates/currencies/fragments/list.html:44
#: templates/dca/fragments/strategy/details.html:76
#: templates/dca/fragments/strategy/details.html:75
#: templates/dca/fragments/strategy/list.html:42
#: templates/entities/fragments/table.html:36
#: templates/exchange_rates/fragments/table.html:32
#: templates/exchange_rates/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
@@ -1305,13 +1306,13 @@ msgstr "Verwijderen"
#: templates/account_groups/fragments/list.html:47
#: templates/accounts/fragments/list.html:52
#: templates/categories/fragments/table.html:41
#: templates/cotton/transaction/item.html:129
#: templates/cotton/ui/transactions_action_bar.html:82
#: templates/cotton/transaction/item.html:128
#: templates/cotton/ui/transactions_action_bar.html:86
#: templates/currencies/fragments/list.html:48
#: templates/dca/fragments/strategy/details.html:81
#: templates/dca/fragments/strategy/details.html:80
#: templates/dca/fragments/strategy/list.html:46
#: templates/entities/fragments/table.html:40
#: templates/exchange_rates/fragments/table.html:37
#: templates/exchange_rates/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
@@ -1329,13 +1330,13 @@ msgstr "Weet je het zeker?"
#: templates/account_groups/fragments/list.html:48
#: templates/accounts/fragments/list.html:53
#: templates/categories/fragments/table.html:42
#: templates/cotton/transaction/item.html:130
#: templates/cotton/ui/transactions_action_bar.html:83
#: templates/cotton/transaction/item.html:129
#: templates/cotton/ui/transactions_action_bar.html:87
#: templates/currencies/fragments/list.html:49
#: templates/dca/fragments/strategy/details.html:82
#: templates/dca/fragments/strategy/details.html:81
#: templates/dca/fragments/strategy/list.html:47
#: templates/entities/fragments/table.html:41
#: templates/exchange_rates/fragments/table.html:38
#: templates/exchange_rates/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
@@ -1346,12 +1347,12 @@ msgstr "Je kunt dit niet meer terugdraaien!"
#: templates/account_groups/fragments/list.html:49
#: templates/accounts/fragments/list.html:54
#: templates/categories/fragments/table.html:43
#: templates/cotton/transaction/item.html:131
#: templates/cotton/transaction/item.html:130
#: templates/currencies/fragments/list.html:50
#: templates/dca/fragments/strategy/details.html:83
#: templates/dca/fragments/strategy/details.html:82
#: templates/dca/fragments/strategy/list.html:48
#: templates/entities/fragments/table.html:42
#: templates/exchange_rates/fragments/table.html:39
#: templates/exchange_rates/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
@@ -1426,11 +1427,11 @@ msgstr "ZAT"
msgid "SUN"
msgstr "ZON"
#: templates/calendar_view/fragments/list_transactions.html:6
#: templates/calendar_view/fragments/list_transactions.html:5
msgid "Transactions on"
msgstr "Verrichtingen op"
#: templates/calendar_view/fragments/list_transactions.html:16
#: templates/calendar_view/fragments/list_transactions.html:15
msgid "No transactions on this date"
msgstr "Geen verrichtingen op deze datum"
@@ -1457,7 +1458,6 @@ msgstr "Terugkerende"
#: templates/monthly_overview/pages/overview.html:91
#: templates/yearly_overview/pages/overview_by_account.html:86
#: templates/yearly_overview/pages/overview_by_currency.html:88
#, fuzzy
msgid "Balance"
msgstr "Saldo"
@@ -1490,12 +1490,12 @@ msgstr "Sluiten"
msgid "Search"
msgstr "Zoeken"
#: templates/cotton/transaction/item.html:6
#: templates/cotton/transaction/item.html:5
msgid "Select"
msgstr "Selecteer"
#: templates/cotton/transaction/item.html:117
#: templates/cotton/ui/transactions_action_bar.html:72
#: templates/cotton/transaction/item.html:116
#: templates/cotton/ui/transactions_action_bar.html:76
msgid "Duplicate"
msgstr "Dupliceren"
@@ -1519,63 +1519,62 @@ msgstr "Verwachte uitgaven"
msgid "Current Expenses"
msgstr "Huidige uitgaven"
#: templates/cotton/ui/transactions_action_bar.html:25
#: templates/cotton/ui/transactions_action_bar.html:29
msgid "Select All"
msgstr "Alles selecteren"
#: templates/cotton/ui/transactions_action_bar.html:31
#: templates/cotton/ui/transactions_action_bar.html:35
msgid "Unselect All"
msgstr "Alles deselecteren"
#: templates/cotton/ui/transactions_action_bar.html:48
#: templates/cotton/ui/transactions_action_bar.html:139
#: templates/cotton/ui/transactions_action_bar.html:52
#: templates/cotton/ui/transactions_action_bar.html:143
msgid "Toggle Dropdown"
msgstr "In- Uitklapbaar"
#: templates/cotton/ui/transactions_action_bar.html:56
#: templates/cotton/ui/transactions_action_bar.html:60
msgid "Mark as unpaid"
msgstr "Markeren als niet betaald"
#: templates/cotton/ui/transactions_action_bar.html:63
#: templates/cotton/ui/transactions_action_bar.html:67
msgid "Mark as paid"
msgstr "Markeren als betaald"
#: templates/cotton/ui/transactions_action_bar.html:84
#: templates/cotton/ui/transactions_action_bar.html:88
msgid "Yes, delete them!"
msgstr "Ja, verwijder ze!"
#: templates/cotton/ui/transactions_action_bar.html:130
#: templates/cotton/ui/transactions_action_bar.html:154
#: templates/cotton/ui/transactions_action_bar.html:174
#: templates/cotton/ui/transactions_action_bar.html:194
#: templates/cotton/ui/transactions_action_bar.html:214
#: templates/cotton/ui/transactions_action_bar.html:234
#: templates/cotton/ui/transactions_action_bar.html:254
#: templates/cotton/ui/transactions_action_bar.html:134
#: templates/cotton/ui/transactions_action_bar.html:158
#: templates/cotton/ui/transactions_action_bar.html:178
#: templates/cotton/ui/transactions_action_bar.html:198
#: templates/cotton/ui/transactions_action_bar.html:218
#: templates/cotton/ui/transactions_action_bar.html:238
#: templates/cotton/ui/transactions_action_bar.html:258
msgid "copied!"
msgstr "gekopieerd!"
#: templates/cotton/ui/transactions_action_bar.html:147
#: templates/cotton/ui/transactions_action_bar.html:151
msgid "Flat Total"
msgstr "Vast Totaal"
#: templates/cotton/ui/transactions_action_bar.html:167
#: templates/cotton/ui/transactions_action_bar.html:171
msgid "Real Total"
msgstr "Werkelijk Totaal"
#: templates/cotton/ui/transactions_action_bar.html:187
#: templates/cotton/ui/transactions_action_bar.html:191
msgid "Mean"
msgstr "Gemiddelde"
#: templates/cotton/ui/transactions_action_bar.html:207
#: templates/cotton/ui/transactions_action_bar.html:211
msgid "Max"
msgstr "Maximaal"
#: templates/cotton/ui/transactions_action_bar.html:227
#: templates/cotton/ui/transactions_action_bar.html:231
msgid "Min"
msgstr "Minimaal"
#: templates/cotton/ui/transactions_action_bar.html:247
#, fuzzy
#: templates/cotton/ui/transactions_action_bar.html:251
msgid "Count"
msgstr "Rekenen"
@@ -1607,91 +1606,91 @@ msgstr "DCA-item bewerken"
msgid "Add DCA strategy"
msgstr "DCA-strategie toevoegen"
#: templates/dca/fragments/strategy/details.html:23
#: templates/dca/fragments/strategy/details.html:22
msgid "No exchange rate available"
msgstr "Geen wisselkoers beschikbaar"
#: templates/dca/fragments/strategy/details.html:34
#: templates/dca/fragments/strategy/details.html:33
msgid "Entries"
msgstr "Idems"
#: templates/dca/fragments/strategy/details.html:56
#: templates/dca/fragments/strategy/details.html:55
msgid "Current Value"
msgstr "Actuele waarde"
#: templates/dca/fragments/strategy/details.html:57
#: templates/dca/fragments/strategy/details.html:56
msgid "P/L"
msgstr "W&V"
#: templates/dca/fragments/strategy/details.html:125
#: templates/dca/fragments/strategy/details.html:124
msgid "No entries for this DCA"
msgstr "Geen idems in deze DCA"
#: templates/dca/fragments/strategy/details.html:126
#: templates/dca/fragments/strategy/details.html:125
#: templates/monthly_overview/fragments/list.html:41
#: templates/transactions/fragments/list_all.html:40
msgid "Try adding one"
msgstr "Probeer er een toe te voegen"
#: templates/dca/fragments/strategy/details.html:136
#: templates/dca/fragments/strategy/details.html:135
msgid "Total Invested"
msgstr "Totaal Geïnvesteerd"
#: templates/dca/fragments/strategy/details.html:150
#: templates/dca/fragments/strategy/details.html:149
msgid "Total Received"
msgstr "Totaal Ontvangen"
#: templates/dca/fragments/strategy/details.html:164
#: templates/dca/fragments/strategy/details.html:163
msgid "Current Total Value"
msgstr "Huidige Totaalwaarde"
#: templates/dca/fragments/strategy/details.html:178
#: templates/dca/fragments/strategy/details.html:177
msgid "Average Entry Price"
msgstr "Gemiddelde Instapprijs"
#: templates/dca/fragments/strategy/details.html:192
#: templates/dca/fragments/strategy/details.html:191
msgid "Total P/L"
msgstr "Totaal W&V"
#: templates/dca/fragments/strategy/details.html:208
#: templates/dca/fragments/strategy/details.html:207
#, python-format
msgid "Total %% P/L"
msgstr "Totaal %% W&V"
#: templates/dca/fragments/strategy/details.html:227
#: templates/dca/fragments/strategy/details.html:226
#, python-format
msgid "P/L %%"
msgstr "W&V %%"
#: templates/dca/fragments/strategy/details.html:289
#: templates/dca/fragments/strategy/details.html:288
msgid "Performance Over Time"
msgstr "Prestaties Na Verloop Van Tijd"
#: templates/dca/fragments/strategy/details.html:307
#: templates/dca/fragments/strategy/details.html:306
msgid "Entry Price"
msgstr "Ingangsprijs"
#: templates/dca/fragments/strategy/details.html:315
#: templates/dca/fragments/strategy/details.html:314
msgid "Current Price"
msgstr "Actuele Prijs"
#: templates/dca/fragments/strategy/details.html:323
#: templates/dca/fragments/strategy/details.html:322
msgid "Amount Bought"
msgstr "Gekocht Bedrag"
#: templates/dca/fragments/strategy/details.html:391
#: templates/dca/fragments/strategy/details.html:390
msgid "Entry Price vs Current Price"
msgstr "Instapprijs vs Huidige Prijs"
#: templates/dca/fragments/strategy/details.html:407
#: templates/dca/fragments/strategy/details.html:406
msgid "Days Between Investments"
msgstr "Dagen Tussen Investeringen"
#: templates/dca/fragments/strategy/details.html:454
#: templates/dca/fragments/strategy/details.html:453
msgid "Investment Frequency"
msgstr "Investeringsfrequentie"
#: templates/dca/fragments/strategy/details.html:456
#: templates/dca/fragments/strategy/details.html:455
msgid "The straighter the blue line, the more consistent your DCA strategy is."
msgstr "Hoe rechter de blauwe lijn, hoe consistenter je DCA-strategie is."
@@ -1736,108 +1735,108 @@ msgstr "Wisselkoers bewerken"
msgid "All"
msgstr "Allemaal"
#: templates/exchange_rates/fragments/table.html:12
#: templates/exchange_rates/fragments/table.html:11
msgid "Pairing"
msgstr "Koppelen"
#: templates/exchange_rates/fragments/table.html:13
#, fuzzy
#: templates/exchange_rates/fragments/table.html:12
msgid "Rate"
msgstr "Tarief"
#: templates/exchange_rates/fragments/table.html:52
#: templates/exchange_rates/fragments/table.html:51
msgid "No exchange rates"
msgstr "Geen wisselkoersen"
#: templates/exchange_rates/fragments/table.html:59
#: templates/exchange_rates/fragments/table.html:58
#: templates/transactions/fragments/list_all.html:47
msgid "Page navigation"
msgstr "Paginanavigatie"
#: templates/import_app/fragments/profiles/add.html:6
msgid "Add new import profile"
msgstr ""
msgstr "Nieuw importprofiel toevoegen"
#: templates/import_app/fragments/profiles/add.html:11
msgid "A message from the author"
msgstr ""
msgstr "Een bericht van de auteur"
#: templates/import_app/fragments/profiles/edit.html:5
msgid "Edit import profile"
msgstr ""
msgstr "Importprofiel bewerken"
#: templates/import_app/fragments/profiles/list.html:5
#: templates/import_app/pages/profiles_index.html:4
msgid "Import Profiles"
msgstr ""
msgstr "Profielen importeren"
#: templates/import_app/fragments/profiles/list.html:17
msgid "New"
msgstr ""
msgstr "Nieuw"
#: templates/import_app/fragments/profiles/list.html:21
msgid "From preset"
msgstr ""
msgstr "Van voorinstelling"
#: templates/import_app/fragments/profiles/list.html:55
msgid "Runs"
msgstr ""
msgstr "Runs"
#: templates/import_app/fragments/profiles/list.html:86
msgid "No import profiles"
msgstr ""
msgstr "Geen importprofielen"
#: templates/import_app/fragments/profiles/list_presets.html:5
msgid "Import Presets"
msgstr ""
msgstr "Presets importeren"
#: templates/import_app/fragments/profiles/list_presets.html:33
msgid "By"
msgstr ""
msgstr "Bij"
#: templates/import_app/fragments/profiles/list_presets.html:40
msgid "No presets yet"
msgstr ""
msgstr "Nog geen voorinstellingen"
#: templates/import_app/fragments/runs/add.html:5
msgid "Import file with profile"
msgstr ""
msgstr "Bestand met profiel importeren"
#: templates/import_app/fragments/runs/list.html:5
msgid "Runs for"
msgstr ""
msgstr "Runs voor"
#: templates/import_app/fragments/runs/list.html:29
msgid "Total Items"
msgstr ""
msgstr "Totale artikelen"
#: templates/import_app/fragments/runs/list.html:42
msgid "Processed Items"
msgstr ""
msgstr "Verwerkte artikelen"
#: templates/import_app/fragments/runs/list.html:55
msgid "Skipped Items"
msgstr ""
msgstr "Overgeslagen artikelen"
#: templates/import_app/fragments/runs/list.html:68
msgid "Failed Items"
msgstr ""
msgstr "Mislukte artikelen"
#: templates/import_app/fragments/runs/list.html:81
msgid "Successful Items"
msgstr ""
msgstr "Succesvolle Artikelen"
#: templates/import_app/fragments/runs/list.html:96
msgid "Logs"
msgstr ""
msgstr "Logboeken"
#: templates/import_app/fragments/runs/list.html:107
msgid "You won't be able to revert this! All imported items will be kept."
msgstr ""
"Je kunt dit niet terugdraaien! Alle geïmporteerde items blijven behouden."
#: templates/import_app/fragments/runs/list.html:116
msgid "No runs yet"
msgstr ""
msgstr "Nog geen runs"
#: templates/import_app/fragments/runs/log.html:5
msgid "Logs for"
@@ -1904,7 +1903,11 @@ msgstr "Rekenmachine"
msgid "Settings"
msgstr "Instellingen"
#: templates/includes/navbar/user_menu.html:37
#: templates/includes/navbar/user_menu.html:38
msgid "Clear cache"
msgstr "Leegmaken"
#: templates/includes/navbar/user_menu.html:42
msgid "Logout"
msgstr "Uitloggen"
@@ -2224,15 +2227,15 @@ msgstr "Afbetalingsplan Toevoegen"
#: templates/transactions/fragments/bulk_edit.html:5
msgid "Bulk Editing"
msgstr ""
msgstr "Bulkbewerking"
#: templates/transactions/fragments/bulk_edit.html:8
msgid "Editing"
msgstr ""
msgstr "Bewerking"
#: templates/transactions/fragments/bulk_edit.html:8
msgid "transactions"
msgstr ""
msgstr "verrichtingen"
#: templates/transactions/fragments/edit.html:5
#: templates/transactions/fragments/edit_installment_plan.html:5
@@ -2302,7 +2305,7 @@ msgstr "Filter"
#: templates/transactions/widgets/unselectable_income_expense_toggle_buttons.html:14
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:8
msgid "Unchanged"
msgstr ""
msgstr "Ongewijzigd"
#: templates/users/generic/hide_amounts.html:2
msgid "Hide amounts"

View File

@@ -8,9 +8,9 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-25 16:51+0000\n"
"PO-Revision-Date: 2025-01-25 13:53-0300\n"
"Last-Translator: \n"
"POT-Creation-Date: 2025-01-28 00:49+0000\n"
"PO-Revision-Date: 2025-01-27 21:49-0300\n"
"Last-Translator: Herculino Trotta\n"
"Language-Team: \n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
@@ -24,28 +24,28 @@ 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:92 apps/dca/forms.py:41
#: 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:583
#: apps/transactions/forms.py:626 apps/transactions/forms.py:658
#: apps/transactions/forms.py:693 apps/transactions/forms.py:831
#: 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:100 apps/dca/forms.py:49 apps/dca/forms.py:102
#: 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:591 apps/transactions/forms.py:634
#: apps/transactions/forms.py:666 apps/transactions/forms.py:701
#: apps/transactions/forms.py:839
#: 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
#: templates/currencies/fragments/list.html:9
#: templates/dca/fragments/strategy/details.html:38
#: templates/dca/fragments/strategy/details.html:37
#: templates/dca/fragments/strategy/list.html:9
#: templates/entities/fragments/list.html:9
#: templates/exchange_rates/fragments/list.html:10
@@ -69,7 +69,7 @@ msgstr "Novo saldo"
#: apps/accounts/forms.py:119 apps/rules/models.py:27
#: apps/transactions/forms.py:39 apps/transactions/forms.py:291
#: apps/transactions/forms.py:298 apps/transactions/forms.py:478
#: apps/transactions/forms.py:725 apps/transactions/models.py:159
#: apps/transactions/forms.py:723 apps/transactions/models.py:159
#: apps/transactions/models.py:311 apps/transactions/models.py:491
msgid "Category"
msgstr "Categoria"
@@ -77,7 +77,7 @@ msgstr "Categoria"
#: apps/accounts/forms.py:126 apps/rules/models.py:28
#: 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:718
#: apps/transactions/forms.py:471 apps/transactions/forms.py:716
#: apps/transactions/models.py:165 apps/transactions/models.py:313
#: apps/transactions/models.py:495 templates/includes/navbar.html:98
#: templates/tags/fragments/list.html:5 templates/tags/pages/index.html:4
@@ -150,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:710 apps/transactions/models.py:132
#: apps/transactions/forms.py:708 apps/transactions/models.py:132
#: apps/transactions/models.py:271 apps/transactions/models.py:473
msgid "Account"
msgstr "Conta"
@@ -320,11 +320,15 @@ msgstr "Erro"
msgid "Info"
msgstr "Informação"
#: apps/common/widgets/datepicker.py:55 apps/common/widgets/datepicker.py:197
#: apps/common/views.py:110
msgid "Cache cleared successfully"
msgstr "Cache limpo com sucesso"
#: apps/common/widgets/datepicker.py:47 apps/common/widgets/datepicker.py:186
msgid "Today"
msgstr "Hoje"
#: apps/common/widgets/datepicker.py:139
#: apps/common/widgets/datepicker.py:123
msgid "Now"
msgstr "Agora"
@@ -354,8 +358,8 @@ msgstr "Sufixo"
#: apps/currencies/forms.py:68 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:53
#: templates/exchange_rates/fragments/table.html:11
#: templates/dca/fragments/strategy/details.html:52
#: templates/exchange_rates/fragments/table.html:10
msgid "Date"
msgstr "Data"
@@ -405,7 +409,7 @@ msgstr "Data e Tempo"
msgid "Exchange Rates"
msgstr "Taxas de Câmbio"
#: apps/currencies/models.py:77
#: apps/currencies/models.py:79
msgid "From and To currencies cannot be the same."
msgstr "As moedas De e Para não podem ser as mesmas."
@@ -459,11 +463,11 @@ msgstr "Estratégias CMP"
msgid "Strategy"
msgstr "Estratégia"
#: apps/dca/models.py:158 templates/dca/fragments/strategy/details.html:55
#: apps/dca/models.py:158 templates/dca/fragments/strategy/details.html:54
msgid "Amount Paid"
msgstr "Quantia paga"
#: apps/dca/models.py:161 templates/dca/fragments/strategy/details.html:54
#: apps/dca/models.py:161 templates/dca/fragments/strategy/details.html:53
msgid "Amount Received"
msgstr "Quantia recebida"
@@ -641,7 +645,7 @@ 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:733 apps/transactions/models.py:117
#: apps/transactions/forms.py:731 apps/transactions/models.py:117
#: apps/transactions/models.py:170 apps/transactions/models.py:316
#: apps/transactions/models.py:498 templates/entities/fragments/list.html:5
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:100
@@ -759,23 +763,23 @@ msgstr "Transferir"
msgid "From and To accounts must be different."
msgstr "As contas De e Para devem ser diferentes."
#: apps/transactions/forms.py:612
#: apps/transactions/forms.py:610
msgid "Tag name"
msgstr "Nome da Tag"
#: apps/transactions/forms.py:644
#: apps/transactions/forms.py:642
msgid "Entity name"
msgstr "Nome da entidade"
#: apps/transactions/forms.py:676
#: apps/transactions/forms.py:674
msgid "Category name"
msgstr "Nome da Categoria"
#: apps/transactions/forms.py:678
#: apps/transactions/forms.py:676
msgid "Muted categories won't count towards your monthly total"
msgstr "As categorias silenciadas não serão contabilizadas em seu total mensal"
#: apps/transactions/forms.py:850
#: apps/transactions/forms.py:846
msgid "End date should be after the start date"
msgstr "Data final deve ser após data inicial"
@@ -1043,15 +1047,15 @@ msgstr "Entidade apagada com sucesso"
msgid "Installment Plan added successfully"
msgstr "Parcelamento adicionado com sucesso"
#: apps/transactions/views/installment_plans.py:117
#: apps/transactions/views/installment_plans.py:115
msgid "Installment Plan updated successfully"
msgstr "Parcelamento atualizado com sucesso"
#: apps/transactions/views/installment_plans.py:142
#: apps/transactions/views/installment_plans.py:140
msgid "Installment Plan refreshed successfully"
msgstr "Parcelamento atualizado com sucesso"
#: apps/transactions/views/installment_plans.py:160
#: apps/transactions/views/installment_plans.py:158
msgid "Installment Plan deleted successfully"
msgstr "Parcelamento apagado com sucesso"
@@ -1059,23 +1063,23 @@ msgstr "Parcelamento apagado com sucesso"
msgid "Recurring Transaction added successfully"
msgstr "Transação Recorrente adicionada com sucesso"
#: apps/transactions/views/recurring_transactions.py:144
#: apps/transactions/views/recurring_transactions.py:142
msgid "Recurring Transaction updated successfully"
msgstr "Transação Recorrente atualizada com sucesso"
#: apps/transactions/views/recurring_transactions.py:190
#: apps/transactions/views/recurring_transactions.py:186
msgid "Recurring transaction unpaused successfully"
msgstr "Transação Recorrente despausada com sucesso"
#: apps/transactions/views/recurring_transactions.py:193
#: apps/transactions/views/recurring_transactions.py:189
msgid "Recurring transaction paused successfully"
msgstr "Transação Recorrente pausada com sucesso"
#: apps/transactions/views/recurring_transactions.py:219
#: apps/transactions/views/recurring_transactions.py:215
msgid "Recurring transaction finished successfully"
msgstr "Transação Recorrente finalizada com sucesso"
#: apps/transactions/views/recurring_transactions.py:239
#: apps/transactions/views/recurring_transactions.py:235
msgid "Recurring Transaction deleted successfully"
msgstr "Transação Recorrente apagada com sucesso"
@@ -1092,30 +1096,30 @@ msgid "Tag deleted successfully"
msgstr "Tag apagada com sucesso"
#: apps/transactions/views/transactions.py:50
#: apps/transactions/views/transactions.py:90
#: apps/transactions/views/transactions.py:89
msgid "Transaction added successfully"
msgstr "Transação adicionada com sucesso"
#: apps/transactions/views/transactions.py:126
#: apps/transactions/views/transactions.py:123
msgid "Transaction updated successfully"
msgstr "Transação atualizada com sucesso"
#: apps/transactions/views/transactions.py:176
#: apps/transactions/views/transactions.py:173
#, python-format
msgid "%(count)s transaction updated successfully"
msgid_plural "%(count)s transactions updated successfully"
msgstr[0] "%(count)s transação atualizada com sucesso"
msgstr[1] "%(count)s transações atualizadas com sucesso"
#: apps/transactions/views/transactions.py:214
#: apps/transactions/views/transactions.py:209
msgid "Transaction duplicated successfully"
msgstr "Transação duplicada com sucesso"
#: apps/transactions/views/transactions.py:256
#: apps/transactions/views/transactions.py:251
msgid "Transaction deleted successfully"
msgstr "Transação apagada com sucesso"
#: apps/transactions/views/transactions.py:282
#: apps/transactions/views/transactions.py:277
msgid "Transfer added successfully"
msgstr "Transferência adicionada com sucesso"
@@ -1155,21 +1159,25 @@ msgstr "E-mail ou senha inválidos"
msgid "This account is deactivated"
msgstr "Essa conta está desativada"
#: apps/users/forms.py:50 apps/users/forms.py:63
#: apps/users/forms.py:50 apps/users/forms.py:63 apps/users/forms.py:85
#: templates/monthly_overview/pages/overview.html:116
#: templates/transactions/pages/transactions.html:35
msgid "Default"
msgstr "Padrão"
#: apps/users/forms.py:85 apps/users/models.py:40
#: apps/users/forms.py:91 apps/users/models.py:40
msgid "Date Format"
msgstr "Formato de Data"
#: apps/users/forms.py:90 apps/users/models.py:45
#: apps/users/forms.py:96 apps/users/models.py:45
msgid "Datetime Format"
msgstr "Formato de Data e Hora"
#: apps/users/forms.py:117
#: apps/users/forms.py:102 apps/users/models.py:48
msgid "Number Format"
msgstr "Formato de Número"
#: apps/users/forms.py:131
msgid "Save"
msgstr "Salvar"
@@ -1193,19 +1201,19 @@ msgstr "Todas as transações"
msgid "Calendar"
msgstr "Calendário"
#: apps/users/models.py:50 apps/users/models.py:56
#: apps/users/models.py:53 apps/users/models.py:59
msgid "Auto"
msgstr "Automático"
#: apps/users/models.py:52
#: apps/users/models.py:55
msgid "Language"
msgstr "Linguagem"
#: apps/users/models.py:58
#: apps/users/models.py:61
msgid "Time Zone"
msgstr "Fuso horário"
#: apps/users/models.py:64
#: apps/users/models.py:67
msgid "Start page"
msgstr "Página inicial"
@@ -1241,9 +1249,9 @@ msgstr "Editar grupo de conta"
#: templates/accounts/fragments/list.html:37
#: templates/categories/fragments/table.html:24
#: templates/currencies/fragments/list.html:33
#: templates/dca/fragments/strategy/details.html:64
#: templates/dca/fragments/strategy/details.html:63
#: templates/entities/fragments/table.html:23
#: templates/exchange_rates/fragments/table.html:20
#: templates/exchange_rates/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
@@ -1255,13 +1263,13 @@ msgstr "Ações"
#: templates/account_groups/fragments/list.html:36
#: templates/accounts/fragments/list.html:41
#: templates/categories/fragments/table.html:29
#: templates/cotton/transaction/item.html:110
#: templates/cotton/ui/transactions_action_bar.html:43
#: templates/cotton/transaction/item.html:109
#: templates/cotton/ui/transactions_action_bar.html:47
#: templates/currencies/fragments/list.html:37
#: templates/dca/fragments/strategy/details.html:68
#: templates/dca/fragments/strategy/details.html:67
#: templates/dca/fragments/strategy/list.html:34
#: templates/entities/fragments/table.html:28
#: templates/exchange_rates/fragments/table.html:24
#: templates/exchange_rates/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
@@ -1274,13 +1282,13 @@ msgstr "Editar"
#: templates/account_groups/fragments/list.html:43
#: templates/accounts/fragments/list.html:48
#: templates/categories/fragments/table.html:36
#: templates/cotton/transaction/item.html:125
#: templates/cotton/ui/transactions_action_bar.html:80
#: templates/cotton/transaction/item.html:124
#: templates/cotton/ui/transactions_action_bar.html:84
#: templates/currencies/fragments/list.html:44
#: templates/dca/fragments/strategy/details.html:76
#: templates/dca/fragments/strategy/details.html:75
#: templates/dca/fragments/strategy/list.html:42
#: templates/entities/fragments/table.html:36
#: templates/exchange_rates/fragments/table.html:32
#: templates/exchange_rates/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
@@ -1295,13 +1303,13 @@ msgstr "Apagar"
#: templates/account_groups/fragments/list.html:47
#: templates/accounts/fragments/list.html:52
#: templates/categories/fragments/table.html:41
#: templates/cotton/transaction/item.html:129
#: templates/cotton/ui/transactions_action_bar.html:82
#: templates/cotton/transaction/item.html:128
#: templates/cotton/ui/transactions_action_bar.html:86
#: templates/currencies/fragments/list.html:48
#: templates/dca/fragments/strategy/details.html:81
#: templates/dca/fragments/strategy/details.html:80
#: templates/dca/fragments/strategy/list.html:46
#: templates/entities/fragments/table.html:40
#: templates/exchange_rates/fragments/table.html:37
#: templates/exchange_rates/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
@@ -1319,13 +1327,13 @@ msgstr "Tem certeza?"
#: templates/account_groups/fragments/list.html:48
#: templates/accounts/fragments/list.html:53
#: templates/categories/fragments/table.html:42
#: templates/cotton/transaction/item.html:130
#: templates/cotton/ui/transactions_action_bar.html:83
#: templates/cotton/transaction/item.html:129
#: templates/cotton/ui/transactions_action_bar.html:87
#: templates/currencies/fragments/list.html:49
#: templates/dca/fragments/strategy/details.html:82
#: templates/dca/fragments/strategy/details.html:81
#: templates/dca/fragments/strategy/list.html:47
#: templates/entities/fragments/table.html:41
#: templates/exchange_rates/fragments/table.html:38
#: templates/exchange_rates/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
@@ -1336,12 +1344,12 @@ msgstr "Você não será capaz de reverter isso!"
#: templates/account_groups/fragments/list.html:49
#: templates/accounts/fragments/list.html:54
#: templates/categories/fragments/table.html:43
#: templates/cotton/transaction/item.html:131
#: templates/cotton/transaction/item.html:130
#: templates/currencies/fragments/list.html:50
#: templates/dca/fragments/strategy/details.html:83
#: templates/dca/fragments/strategy/details.html:82
#: templates/dca/fragments/strategy/list.html:48
#: templates/entities/fragments/table.html:42
#: templates/exchange_rates/fragments/table.html:39
#: templates/exchange_rates/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
@@ -1416,11 +1424,11 @@ msgstr "SÁB"
msgid "SUN"
msgstr "DOM"
#: templates/calendar_view/fragments/list_transactions.html:6
#: templates/calendar_view/fragments/list_transactions.html:5
msgid "Transactions on"
msgstr "Transações em"
#: templates/calendar_view/fragments/list_transactions.html:16
#: templates/calendar_view/fragments/list_transactions.html:15
msgid "No transactions on this date"
msgstr "Nenhuma transação nesta data"
@@ -1479,12 +1487,12 @@ msgstr "Fechar"
msgid "Search"
msgstr "Buscar"
#: templates/cotton/transaction/item.html:6
#: templates/cotton/transaction/item.html:5
msgid "Select"
msgstr "Selecionar"
#: templates/cotton/transaction/item.html:117
#: templates/cotton/ui/transactions_action_bar.html:72
#: templates/cotton/transaction/item.html:116
#: templates/cotton/ui/transactions_action_bar.html:76
msgid "Duplicate"
msgstr "Duplicar"
@@ -1508,62 +1516,62 @@ msgstr "Despesas Previstas"
msgid "Current Expenses"
msgstr "Despesas Atuais"
#: templates/cotton/ui/transactions_action_bar.html:25
#: templates/cotton/ui/transactions_action_bar.html:29
msgid "Select All"
msgstr "Selecionar todos"
#: templates/cotton/ui/transactions_action_bar.html:31
#: templates/cotton/ui/transactions_action_bar.html:35
msgid "Unselect All"
msgstr "Desmarcar todos"
#: templates/cotton/ui/transactions_action_bar.html:48
#: templates/cotton/ui/transactions_action_bar.html:139
#: templates/cotton/ui/transactions_action_bar.html:52
#: templates/cotton/ui/transactions_action_bar.html:143
msgid "Toggle Dropdown"
msgstr "Alternar menu suspenso"
#: templates/cotton/ui/transactions_action_bar.html:56
#: templates/cotton/ui/transactions_action_bar.html:60
msgid "Mark as unpaid"
msgstr "Marcar como não pago"
#: templates/cotton/ui/transactions_action_bar.html:63
#: templates/cotton/ui/transactions_action_bar.html:67
msgid "Mark as paid"
msgstr "Marcar como pago"
#: templates/cotton/ui/transactions_action_bar.html:84
#: templates/cotton/ui/transactions_action_bar.html:88
msgid "Yes, delete them!"
msgstr "Sim, apague!"
#: templates/cotton/ui/transactions_action_bar.html:130
#: templates/cotton/ui/transactions_action_bar.html:154
#: templates/cotton/ui/transactions_action_bar.html:174
#: templates/cotton/ui/transactions_action_bar.html:194
#: templates/cotton/ui/transactions_action_bar.html:214
#: templates/cotton/ui/transactions_action_bar.html:234
#: templates/cotton/ui/transactions_action_bar.html:254
#: templates/cotton/ui/transactions_action_bar.html:134
#: templates/cotton/ui/transactions_action_bar.html:158
#: templates/cotton/ui/transactions_action_bar.html:178
#: templates/cotton/ui/transactions_action_bar.html:198
#: templates/cotton/ui/transactions_action_bar.html:218
#: templates/cotton/ui/transactions_action_bar.html:238
#: templates/cotton/ui/transactions_action_bar.html:258
msgid "copied!"
msgstr "copiado!"
#: templates/cotton/ui/transactions_action_bar.html:147
#: templates/cotton/ui/transactions_action_bar.html:151
msgid "Flat Total"
msgstr "Total Fixo"
#: templates/cotton/ui/transactions_action_bar.html:167
#: templates/cotton/ui/transactions_action_bar.html:171
msgid "Real Total"
msgstr "Total Real"
#: templates/cotton/ui/transactions_action_bar.html:187
#: templates/cotton/ui/transactions_action_bar.html:191
msgid "Mean"
msgstr "Média"
#: templates/cotton/ui/transactions_action_bar.html:207
#: templates/cotton/ui/transactions_action_bar.html:211
msgid "Max"
msgstr "Máximo"
#: templates/cotton/ui/transactions_action_bar.html:227
#: templates/cotton/ui/transactions_action_bar.html:231
msgid "Min"
msgstr "Minímo"
#: templates/cotton/ui/transactions_action_bar.html:247
#: templates/cotton/ui/transactions_action_bar.html:251
msgid "Count"
msgstr "Contagem"
@@ -1595,91 +1603,91 @@ msgstr "Editar entrada CMP"
msgid "Add DCA strategy"
msgstr "Adicionar estratégia CMP"
#: templates/dca/fragments/strategy/details.html:23
#: templates/dca/fragments/strategy/details.html:22
msgid "No exchange rate available"
msgstr "Nenhuma taxa de câmbio disponível"
#: templates/dca/fragments/strategy/details.html:34
#: templates/dca/fragments/strategy/details.html:33
msgid "Entries"
msgstr "Entradas"
#: templates/dca/fragments/strategy/details.html:56
#: templates/dca/fragments/strategy/details.html:55
msgid "Current Value"
msgstr "Valor atual"
#: templates/dca/fragments/strategy/details.html:57
#: templates/dca/fragments/strategy/details.html:56
msgid "P/L"
msgstr "P/L"
#: templates/dca/fragments/strategy/details.html:125
#: templates/dca/fragments/strategy/details.html:124
msgid "No entries for this DCA"
msgstr "Nenhuma entrada neste CMP"
#: templates/dca/fragments/strategy/details.html:126
#: templates/dca/fragments/strategy/details.html:125
#: templates/monthly_overview/fragments/list.html:41
#: templates/transactions/fragments/list_all.html:40
msgid "Try adding one"
msgstr "Tente adicionar uma"
#: templates/dca/fragments/strategy/details.html:136
#: templates/dca/fragments/strategy/details.html:135
msgid "Total Invested"
msgstr "Total investido"
#: templates/dca/fragments/strategy/details.html:150
#: templates/dca/fragments/strategy/details.html:149
msgid "Total Received"
msgstr "Total recebido"
#: templates/dca/fragments/strategy/details.html:164
#: templates/dca/fragments/strategy/details.html:163
msgid "Current Total Value"
msgstr "Valor total atual"
#: templates/dca/fragments/strategy/details.html:178
#: templates/dca/fragments/strategy/details.html:177
msgid "Average Entry Price"
msgstr "Preço médio de entrada"
#: templates/dca/fragments/strategy/details.html:192
#: templates/dca/fragments/strategy/details.html:191
msgid "Total P/L"
msgstr "P/L total"
#: templates/dca/fragments/strategy/details.html:208
#: templates/dca/fragments/strategy/details.html:207
#, python-format
msgid "Total %% P/L"
msgstr "P/L%% Total"
#: templates/dca/fragments/strategy/details.html:227
#: templates/dca/fragments/strategy/details.html:226
#, python-format
msgid "P/L %%"
msgstr "P/L %%"
#: templates/dca/fragments/strategy/details.html:289
#: templates/dca/fragments/strategy/details.html:288
msgid "Performance Over Time"
msgstr "Desempenho ao longo do tempo"
#: templates/dca/fragments/strategy/details.html:307
#: templates/dca/fragments/strategy/details.html:306
msgid "Entry Price"
msgstr "Preço de Entrada"
#: templates/dca/fragments/strategy/details.html:315
#: templates/dca/fragments/strategy/details.html:314
msgid "Current Price"
msgstr "Preço atual"
#: templates/dca/fragments/strategy/details.html:323
#: templates/dca/fragments/strategy/details.html:322
msgid "Amount Bought"
msgstr "Quantia comprada"
#: templates/dca/fragments/strategy/details.html:391
#: templates/dca/fragments/strategy/details.html:390
msgid "Entry Price vs Current Price"
msgstr "Preço de Entrada vs Preço Atual"
#: templates/dca/fragments/strategy/details.html:407
#: templates/dca/fragments/strategy/details.html:406
msgid "Days Between Investments"
msgstr "Dias entre investimentos"
#: templates/dca/fragments/strategy/details.html:454
#: templates/dca/fragments/strategy/details.html:453
msgid "Investment Frequency"
msgstr "Frequência de Investimento"
#: templates/dca/fragments/strategy/details.html:456
#: templates/dca/fragments/strategy/details.html:455
msgid "The straighter the blue line, the more consistent your DCA strategy is."
msgstr ""
"Quanto mais reta for a linha azul, mais consistente é sua estratégia de CMP."
@@ -1725,19 +1733,19 @@ msgstr "Editar taxa de câmbio"
msgid "All"
msgstr "Todas"
#: templates/exchange_rates/fragments/table.html:12
#: templates/exchange_rates/fragments/table.html:11
msgid "Pairing"
msgstr "Pares"
#: templates/exchange_rates/fragments/table.html:13
#: templates/exchange_rates/fragments/table.html:12
msgid "Rate"
msgstr "Taxa de Câmbio"
#: templates/exchange_rates/fragments/table.html:52
#: templates/exchange_rates/fragments/table.html:51
msgid "No exchange rates"
msgstr "Nenhuma taxa de câmbio"
#: templates/exchange_rates/fragments/table.html:59
#: templates/exchange_rates/fragments/table.html:58
#: templates/transactions/fragments/list_all.html:47
msgid "Page navigation"
msgstr "Navegação por página"
@@ -1894,7 +1902,11 @@ msgstr "Calculadora"
msgid "Settings"
msgstr "Configurações"
#: templates/includes/navbar/user_menu.html:37
#: templates/includes/navbar/user_menu.html:38
msgid "Clear cache"
msgstr "Limpar cache"
#: templates/includes/navbar/user_menu.html:42
msgid "Logout"
msgstr "Sair"

View File

@@ -39,23 +39,23 @@
{% for transaction in date.transactions %}
{% if transaction.is_paid %}
{% if transaction.type == "IN" and not transaction.account.is_asset %}
<i class="fa-solid fa-circle-check tw-text-green-400" data-bs-toggle="tooltip" data-bs-title="{{ transaction.description }}"></i>
<i class="fa-solid fa-circle-check tw-text-green-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
{% elif transaction.type == "IN" and transaction.account.is_asset %}
<i class="fa-solid fa-circle-check tw-text-green-300" data-bs-toggle="tooltip" data-bs-title="{{ transaction.description }}"></i>
<i class="fa-solid fa-circle-check tw-text-green-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
{% elif transaction.type == "EX" and not transaction.account.is_asset %}
<i class="fa-solid fa-circle-check tw-text-red-400" data-bs-toggle="tooltip" data-bs-title="{{ transaction.description }}"></i>
<i class="fa-solid fa-circle-check tw-text-red-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
{% elif transaction.type == "EX" and transaction.account.is_asset %}
<i class="fa-solid fa-circle-check tw-text-red-300" data-bs-toggle="tooltip" data-bs-title="{{ transaction.description }}"></i>
<i class="fa-solid fa-circle-check tw-text-red-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
{% endif %}
{% else %}
{% if transaction.type == "IN" and not transaction.account.is_asset %}
<i class="fa-regular fa-circle tw-text-green-400" data-bs-toggle="tooltip" data-bs-title="{{ transaction.description }}"></i>
<i class="fa-regular fa-circle tw-text-green-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
{% elif transaction.type == "IN" and transaction.account.is_asset %}
<i class="fa-regular fa-circle tw-text-green-300" data-bs-toggle="tooltip" data-bs-title="{{ transaction.description }}"></i>
<i class="fa-regular fa-circle tw-text-green-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Income' %}{% endif %}"></i>
{% elif transaction.type == "EX" and not transaction.account.is_asset %}
<i class="fa-regular fa-circle tw-text-red-400" data-bs-toggle="tooltip" data-bs-title="{{ transaction.description }}"></i>
<i class="fa-regular fa-circle tw-text-red-400" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
{% elif transaction.type == "EX" and transaction.account.is_asset %}
<i class="fa-regular fa-circle tw-text-green-300" data-bs-toggle="tooltip" data-bs-title="{{ transaction.description }}"></i>
<i class="fa-regular fa-circle tw-text-red-300" data-bs-toggle="tooltip" data-bs-title="{% if transaction.description %}{{ transaction.description }}{% else %}{% trans 'Expense' %}{% endif %}"></i>
{% endif %}
{% endif %}
{% endfor %}

View File

@@ -1,19 +1,18 @@
{% extends 'extends/offcanvas.html' %}
{% load date %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title %}{% translate 'Transactions on' %} {{ date|custom_date:request.user }}{% endblock %}
{% block title %}{% translate 'Transactions on' %} {{ date|date:"SHORT_DATE_FORMAT" }}{% endblock %}
{% block body %}
<div hx-get="{% url 'calendar_transactions_list' day=date.day month=date.month year=date.year %}" hx-trigger="updated from:window" hx-vals='{"disable_selection": true}' hx-target="closest .offcanvas" class="show-loading">
{% for transaction in transactions %}
<c-transaction.item
:transaction="transaction"
:disable-selection="True"></c-transaction.item>
{% empty %}
<c-msg.empty
title="{% translate 'No transactions on this date' %}"></c-msg.empty>
{% endfor %}
<div hx-get="{% url 'calendar_transactions_list' day=date.day month=date.month year=date.year %}" hx-trigger="updated from:window" hx-vals='{"disable_selection": true}' hx-target="closest .offcanvas" class="show-loading" id="transactions-list">
{% for transaction in transactions %}
<c-transaction.item :transaction="transaction"></c-transaction.item>
{% empty %}
<c-msg.empty
title="{% translate 'No transactions on this date' %}"></c-msg.empty>
{% endfor %}
{# Floating bar #}
<c-ui.transactions-action-bar></c-ui.transactions-action-bar>
</div>
{% endblock %}

View File

@@ -1,4 +1,4 @@
{% load date %}
{% load markdown %}
{% load i18n %}
<div class="transaction d-flex my-1 {% if transaction.type == "EX" %}expense{% else %}income{% endif %}">
{% if not disable_selection %}
@@ -27,7 +27,7 @@
{# Date#}
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-calendar fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.date|custom_date:request.user }} • {{ transaction.reference_date|date:"b/Y" }}</div>
<div class="col ps-0">{{ transaction.date|date:"SHORT_DATE_FORMAT" }} • {{ transaction.reference_date|date:"b/Y" }}</div>
</div>
{# Description#}
<div class="mb-2 mb-lg-1 text-white tw-text-base">
@@ -55,7 +55,7 @@
{% if transaction.notes %}
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-align-left fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.notes | linebreaksbr }}</div>
<div class="col ps-0">{{ transaction.notes | limited_markdown | linebreaksbr }}</div>
</div>
{% endif %}
{# Category#}

View File

@@ -2,12 +2,16 @@
<div class="tw-sticky tw-bottom-4 tw-left-0 tw-right-0 tw-z-50 tw-hidden mx-auto tw-w-fit" id="actions-bar"
_="on change from #transactions-list or htmx:afterSettle from window
if no <input[type='checkbox']:checked/> in #transactions-list
add .slide-in-bottom-reverse then settle
then add .tw-hidden to #actions-bar
then remove .slide-in-bottom-reverse
if #actions-bar
add .slide-in-bottom-reverse then settle
then add .tw-hidden to #actions-bar
then remove .slide-in-bottom-reverse
end
else
remove .tw-hidden from #actions-bar
then trigger selected_transactions_updated
if #actions-bar
remove .tw-hidden from #actions-bar
then trigger selected_transactions_updated
end
end
end">
<div class="card slide-in-bottom">

View File

@@ -1,4 +1,3 @@
{% load date %}
{% load currency_display %}
{% load i18n %}
<div class="container-fluid px-md-3 py-3 column-gap-5">
@@ -17,7 +16,7 @@
:prefix="strategy.payment_currency.prefix"
:suffix="strategy.payment_currency.suffix"
:decimal_places="strategy.payment_currency.decimal_places">
• {{ strategy.current_price.1|custom_date:request.user }}
• {{ strategy.current_price.1|date:"SHORT_DATETIME_FORMAT" }}
</c-amount.display>
{% else %}
<div class="tw-text-red-400">{% trans "No exchange rate available" %}</div>
@@ -84,7 +83,7 @@
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
</div>
</td>
<td>{{ entry.date|custom_date:request.user }}</td>
<td>{{ entry.date|date:"SHORT_DATE_FORMAT" }}</td>
<td>
<c-amount.display
:amount="entry.amount_received"
@@ -222,7 +221,7 @@
new Chart(perfomancectx, {
type: 'line',
data: {
labels: [{% for entry in entries_data %}'{{ entry.entry.date|custom_date:request.user }}'{% if not forloop.last %}, {% endif %}{% endfor %}],
labels: [{% for entry in entries_data %}'{{ entry.entry.date|date:"SHORT_DATE_FORMAT" }}'{% if not forloop.last %}, {% endif %}{% endfor %}],
datasets: [{
label: '{% trans "P/L %" %}',
data: [{% for entry in entries_data %}{{ entry.profit_loss_percentage|floatformat:"-40u" }}{% if not forloop.last %}, {% endif %}{% endfor %}],

View File

@@ -1,4 +1,3 @@
{% load date %}
{% 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:"" }}"}'>
@@ -40,7 +39,7 @@
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
</div>
</td>
<td class="col-3">{{ exchange_rate.date|custom_date:request.user }}</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>
</tr>

View File

@@ -33,6 +33,11 @@
</li>
{% endspaceless %}
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item" hx-get="{% url 'invalidate_cache' %}" role="button">
<i class="fa-solid fa-broom me-2 fa-fw"></i>{% translate 'Clear cache' %}
</a>
</li>
<li><a class="dropdown-item" href="{% url 'logout' %}"><i class="fa-solid fa-door-open me-2 fa-fw"></i
>{% translate 'Logout' %}</a></li>
</ul>

View File

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

View File

@@ -23,8 +23,6 @@
<div id="content">
{% block content %}{% endblock %}
</div>
{% include 'includes/toasts.html' %}
{% include 'includes/scripts.html' %}
{% block extra_js %}{% endblock %}

View File

@@ -113,9 +113,9 @@
<div class="text-sm-end" _="on change trigger updated on window">
<label for="order">{% translate "Order by" %}</label>
<select class="tw-border-0 focus-visible:tw-outline-0 w-full pe-2 tw-leading-normal text-bg-tertiary tw-font-medium rounded" name="order" id="order">
<option value="default">{% translate 'Default' %}</option>
<option value="older">{% translate 'Oldest first' %}</option>
<option value="newer">{% translate 'Newest first' %}</option>
<option value="default" {% if order == 'default' %}selected{% endif %}>{% translate 'Default' %}</option>
<option value="older" {% if order == 'older' %}selected{% endif %}>{% translate 'Oldest first' %}</option>
<option value="newer" {% if order == 'newer' %}selected{% endif %}>{% translate 'Newest first' %}</option>
</select>
</div>
</div>

View File

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

View File

@@ -32,9 +32,9 @@
<div class="tw-content-center" _="on change trigger updated on window">
<label for="order">{% translate "Order by" %}</label>
<select class="tw-border-0 focus-visible:tw-outline-0 w-full pe-2 tw-leading-normal text-bg-tertiary tw-font-medium rounded" name="order" id="order">
<option value="default">{% translate 'Default' %}</option>
<option value="older">{% translate 'Oldest first' %}</option>
<option value="newer">{% translate 'Newest first' %}</option>
<option value="default" {% if order == 'default' %}selected{% endif %}>{% translate 'Default' %}</option>
<option value="older" {% if order == 'older' %}selected{% endif %}>{% translate 'Oldest first' %}</option>
<option value="newer" {% if order == 'newer' %}selected{% endif %}>{% translate 'Newest first' %}</option>
</select>
</div>
</div>

View File

@@ -3,13 +3,13 @@ volumes:
wygiwyh_temp:
services:
web: &django
web:
build:
context: .
dockerfile: ./docker/dev/django/Dockerfile
image: wygiwyh_dev_server
container_name: wygiwyh_dev_server
command: /start
command: /start-supervisor
volumes:
- ./app/:/usr/src/app/:z
- ./frontend/:/usr/src/frontend:z
@@ -54,12 +54,12 @@ services:
- '${SQL_PORT}:5432'
restart: unless-stopped
procrastinate:
<<: *django
image: wygiwyh_dev_procrastinate
container_name: wygiwyh_dev_procrastinate
depends_on:
- db
ports: [ ]
command: /start-procrastinate
restart: unless-stopped
# procrastinate:
# <<: *django
# image: wygiwyh_dev_procrastinate
# container_name: wygiwyh_dev_procrastinate
# depends_on:
# - db
# ports: [ ]
# command: /start-procrastinate
# restart: unless-stopped

View File

@@ -2,15 +2,13 @@ services:
web:
image: eitchtee/wygiwyh:latest
container_name: ${SERVER_NAME}
command: /start
command: /start-single
ports:
- "${OUTBOUND_PORT}:8000"
env_file:
- .env
depends_on:
- db
volumes:
- wygiwyh_temp:/usr/src/app/temp/
restart: unless-stopped
db:
@@ -23,18 +21,3 @@ services:
- POSTGRES_USER=${SQL_USER}
- POSTGRES_PASSWORD=${SQL_PASSWORD}
- POSTGRES_DB=${SQL_DATABASE}
procrastinate:
image: eitchtee/wygiwyh:latest
container_name: ${PROCRASTINATE_NAME}
depends_on:
- db
env_file:
- .env
volumes:
- wygiwyh_temp:/usr/src/app/temp/
command: /start-procrastinate
restart: unless-stopped
volumes:
wygiwyh_temp:

View File

@@ -1,4 +1,4 @@
FROM python:3.11-slim-buster AS python-build-stage
FROM python:3.11-slim-bookworm AS python-build-stage
RUN apt-get update && apt-get install --no-install-recommends -y \
build-essential \
@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
COPY ../requirements.txt .
RUN pip wheel --wheel-dir /usr/src/app/wheels -r requirements.txt
FROM python:3.11-slim-buster AS python-run-stage
FROM python:3.11-slim-bookworm AS python-run-stage
WORKDIR /usr/src/app
@@ -18,7 +18,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
COPY --from=python-build-stage /usr/src/app/wheels /wheels/
RUN apt-get update && \
apt-get install --no-install-recommends -y gettext && \
apt-get install --no-install-recommends -y gettext supervisor && \
rm -rf /var/lib/apt/lists/* && \
pip install --upgrade pip && \
pip install --no-cache-dir --no-index --find-links=/wheels/ /wheels/* && \
@@ -26,9 +26,15 @@ RUN apt-get update && \
COPY ./docker/dev/django/start /start
COPY ./docker/dev/procrastinate/start /start-procrastinate
COPY ./docker/dev/supervisord/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY ./docker/dev/supervisord/supervisord.conf /etc/supervisord.conf
COPY ./docker/dev/supervisord/start /start-supervisor
RUN sed -i 's/\r$//g' /start && \
chmod +x /start && \
sed -i 's/\r$//g' /start-procrastinate && \
chmod +x /start-procrastinate
chmod +x /start-procrastinate && \
sed -i 's/\r$//g' /start-supervisor && \
chmod +x /start-supervisor
COPY ./app .

View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -o errexit
set -o pipefail
set -o nounset
export TASK_WORKERS=${TASK_WORKERS:=1}
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf

View File

@@ -0,0 +1,39 @@
[supervisord]
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0
pidfile=/tmp/supervisord.pid
user=root
[supervisorctl]
serverurl=unix:///run/supervisord.sock
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[unix_http_server]
file=/run/supervisord.sock
chmod=0700
[program:web]
directory=/usr/src/app
command=/bin/bash /start
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2
stderr_logfile_maxbytes=0
autorestart=true
startretries=5
[program:procrastinate]
directory=/usr/src/app
command=/bin/bash /start-procrastinate
process_name=%(program_name)s_%(process_num)02d
numprocs=%(ENV_TASK_WORKERS)s
numprocs_start=1
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2
stderr_logfile_maxbytes=0
autorestart=true
startretries=5

View File

@@ -1,4 +1,4 @@
FROM python:3.11-slim-buster AS python-build-stage
FROM python:3.11-slim-bookworm AS python-build-stage
RUN apt-get update && apt-get install --no-install-recommends -y \
build-essential \
@@ -17,7 +17,7 @@ RUN --mount=type=cache,target=/root/.npm \
npm install --verbose && \
npm run build
FROM python:3.11-slim-buster AS python-run-stage
FROM python:3.11-slim-bookworm AS python-run-stage
COPY --from=webpack_build /usr/src/frontend/build /usr/src/frontend/build
WORKDIR /usr/src/app
@@ -31,7 +31,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
COPY --from=python-build-stage /usr/src/app/wheels /wheels/
RUN --mount=type=cache,target=/root/.cache/apt \
apt-get update && \
apt-get install --no-install-recommends -y gettext && \
apt-get install --no-install-recommends -y gettext supervisor && \
rm -rf /var/lib/apt/lists/* && \
pip install --upgrade pip && \
pip install --no-cache-dir --no-index --find-links=/wheels/ /wheels/* && \
@@ -39,10 +39,15 @@ RUN --mount=type=cache,target=/root/.cache/apt \
COPY --chown=app:app ./docker/prod/django/start /start
COPY --chown=app:app ./docker/prod/procrastinate/start /start-procrastinate
COPY --chown=app:app ./docker/prod/supervisord/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY --chown=app:app ./docker/prod/supervisord/supervisord.conf /etc/supervisord.conf
COPY --chown=app:app ./docker/prod/supervisord/start /start-single
RUN sed -i 's/\r$//g' /start && \
chmod +x /start && \
sed -i 's/\r$//g' /start-procrastinate && \
chmod +x /start-procrastinate
chmod +x /start-procrastinate && \
sed -i 's/\r$//g' /start-single && \
chmod +x /start-single
COPY --chown=app:app ./app .

View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -o errexit
set -o pipefail
set -o nounset
export TASK_WORKERS=${TASK_WORKERS:=1}
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf

View File

@@ -0,0 +1,37 @@
[supervisord]
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0
pidfile=/tmp/supervisord.pid
[supervisorctl]
serverurl=unix:///tmp/supervisord.sock
[unix_http_server]
file=/tmp/supervisord.sock
chmod=0700
[program:web]
user=app
directory=/usr/src/app
command=/bin/bash /start
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2
stderr_logfile_maxbytes=0
autorestart=true
startretries=5
[program:procrastinate]
user=app
directory=/usr/src/app
command=/bin/bash /start-procrastinate
process_name=%(program_name)s_%(process_num)02d
numprocs=%(ENV_TASK_WORKERS)s
numprocs_start=1
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2
stderr_logfile_maxbytes=0
autorestart=true
startretries=5

View File

@@ -163,8 +163,8 @@ window.MonthYearPicker = function createDynamicDatePicker(element) {
let opts = {...baseOpts, ...positionConfig};
if (element.dataset.value) {
opts["selectedDates"] = [element.dataset.value];
opts["startDate"] = [element.dataset.value];
opts["selectedDates"] = [new Date(element.dataset.value + "T00:00:00")];
opts["startDate"] = [new Date(element.dataset.value + "T00:00:00")];
}
return new AirDatepicker(element, opts);
};

View File

@@ -26,3 +26,4 @@ python-dateutil~=2.9.0.post0
simpleeval~=1.0.0
pydantic~=2.10.5
PyYAML~=6.0.2
mistune~=3.1.1