diff --git a/.gitignore b/.gitignore index 5ab3c7c..84af340 100644 --- a/.gitignore +++ b/.gitignore @@ -123,6 +123,7 @@ celerybeat.pid # Environments .env +.prod.env .venv env/ venv/ @@ -161,4 +162,5 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ -node_modules/ \ No newline at end of file +node_modules/ +postgres_data/ \ No newline at end of file diff --git a/app/WYGIWYH/settings.py b/app/WYGIWYH/settings.py index 12dd32c..0158fa0 100644 --- a/app/WYGIWYH/settings.py +++ b/app/WYGIWYH/settings.py @@ -314,7 +314,7 @@ CACHES = { } } -DJANGO_VITE_ASSETS_PATH = ROOT_DIR / "frontend" / "build" +DJANGO_VITE_ASSETS_PATH = STATIC_ROOT DJANGO_VITE_MANIFEST_PATH = DJANGO_VITE_ASSETS_PATH / "manifest.json" DJANGO_VITE_DEV_MODE = DEBUG DJANGO_VITE_DEV_SERVER_PORT = 5173 @@ -365,7 +365,6 @@ SOCIALACCOUNT_ADAPTER = "allauth.socialaccount.adapter.DefaultSocialAccountAdapt # CRISPY FORMS CRISPY_ALLOWED_TEMPLATE_PACKS = [ - "bootstrap5", "crispy_forms/pure_text", "crispy-daisyui", ] diff --git a/app/apps/accounts/forms.py b/app/apps/accounts/forms.py index c4dc2f1..8d33603 100644 --- a/app/apps/accounts/forms.py +++ b/app/apps/accounts/forms.py @@ -1,23 +1,21 @@ +from apps.accounts.models import Account, AccountGroup +from apps.common.fields.forms.dynamic_select import ( + DynamicModelChoiceField, + DynamicModelMultipleChoiceField, +) +from apps.common.widgets.crispy.submit import NoClassSubmit +from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput +from apps.common.widgets.tom_select import TomSelect +from apps.currencies.models import Currency +from apps.transactions.models import TransactionCategory, TransactionTag from crispy_bootstrap5.bootstrap5 import Switch from crispy_forms.bootstrap import FormActions from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Field, Column, Row +from crispy_forms.layout import Column, Field, Layout, Row from django import forms from django.db.models import Q from django.utils.translation import gettext_lazy as _ -from apps.accounts.models import Account -from apps.accounts.models import AccountGroup -from apps.common.fields.forms.dynamic_select import ( - DynamicModelMultipleChoiceField, - DynamicModelChoiceField, -) -from apps.common.widgets.crispy.submit import NoClassSubmit -from apps.common.widgets.tom_select import TomSelect -from apps.transactions.models import TransactionCategory, TransactionTag -from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput -from apps.currencies.models import Currency - class AccountGroupForm(forms.ModelForm): class Meta: @@ -156,8 +154,8 @@ class AccountBalanceForm(forms.Form): self.helper.layout = Layout( "new_balance", Row( - Column("category", css_class="form-group col-md-6 mb-0"), - Column("tags", css_class="form-group col-md-6 mb-0"), + Column("category", css_class="md:col-span-6 mb-0"), + Column("tags", css_class="md:col-span-6 mb-0"), css_class="form-row", ), Field("account_id"), diff --git a/app/apps/common/templatetags/toast_bg.py b/app/apps/common/templatetags/toast_bg.py index 5c1b8ed..f9858d8 100644 --- a/app/apps/common/templatetags/toast_bg.py +++ b/app/apps/common/templatetags/toast_bg.py @@ -11,7 +11,7 @@ def toast_bg(tags): elif "warning" in tags: return "warning" elif "error" in tags: - return "danger" + return "error" elif "info" in tags: return "info" diff --git a/app/apps/common/widgets/datepicker.py b/app/apps/common/widgets/datepicker.py index 9928ba1..e875084 100644 --- a/app/apps/common/widgets/datepicker.py +++ b/app/apps/common/widgets/datepicker.py @@ -1,15 +1,14 @@ import datetime -from django.forms import widgets -from django.utils import formats, translation, dates -from django.utils.translation import gettext_lazy as _ - +from apps.common.functions.format import get_format from apps.common.utils.django import ( - django_to_python_datetime, django_to_airdatepicker_datetime, django_to_airdatepicker_datetime_separated, + django_to_python_datetime, ) -from apps.common.functions.format import get_format +from django.forms import widgets +from django.utils import dates, formats, translation +from django.utils.translation import gettext_lazy as _ class AirDatePickerInput(widgets.DateInput): @@ -52,6 +51,8 @@ class AirDatePickerInput(widgets.DateInput): def build_attrs(self, base_attrs, extra_attrs=None): attrs = super().build_attrs(base_attrs, extra_attrs) + attrs["class"] = attrs.get("class", "") + " input" + attrs["data-now-button-txt"] = _("Today") attrs["data-auto-close"] = str(self.auto_close).lower() attrs["data-clear-button"] = str(self.clear_button).lower() diff --git a/app/apps/currencies/forms.py b/app/apps/currencies/forms.py index cab3120..0c9a732 100644 --- a/app/apps/currencies/forms.py +++ b/app/apps/currencies/forms.py @@ -1,16 +1,15 @@ -from crispy_bootstrap5.bootstrap5 import Switch -from crispy_forms.bootstrap import FormActions -from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Row, Column -from django import forms -from django.forms import CharField -from django.utils.translation import gettext_lazy as _ - from apps.common.widgets.crispy.submit import NoClassSubmit from apps.common.widgets.datepicker import AirDateTimePickerInput from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput from apps.common.widgets.tom_select import TomSelect from apps.currencies.models import Currency, ExchangeRate, ExchangeRateService +from crispy_bootstrap5.bootstrap5 import Switch +from crispy_forms.bootstrap import FormActions +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Column, Layout, Row +from django import forms +from django.forms import CharField +from django.utils.translation import gettext_lazy as _ class CurrencyForm(forms.ModelForm): @@ -132,8 +131,8 @@ class ExchangeRateServiceForm(forms.ModelForm): Switch("singleton"), "api_key", Row( - Column("interval_type", css_class="form-group col-md-6"), - Column("fetch_interval", css_class="form-group col-md-6"), + Column("interval_type", css_class="md:col-span-6"), + Column("fetch_interval", css_class="md:col-span-6"), ), "target_currencies", "target_accounts", diff --git a/app/apps/dca/forms.py b/app/apps/dca/forms.py index db045aa..1e6d6e0 100644 --- a/app/apps/dca/forms.py +++ b/app/apps/dca/forms.py @@ -1,22 +1,20 @@ -from crispy_bootstrap5.bootstrap5 import Switch, BS5Accordion -from crispy_forms.bootstrap import FormActions, AccordionGroup -from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Row, Column, HTML -from django import forms -from django.utils.translation import gettext_lazy as _ - from apps.accounts.models import Account -from apps.common.widgets.crispy.submit import NoClassSubmit -from apps.common.widgets.datepicker import AirDatePickerInput -from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput -from apps.common.widgets.tom_select import TomSelect -from apps.dca.models import DCAStrategy, DCAEntry -from apps.common.widgets.tom_select import TransactionSelect -from apps.transactions.models import Transaction, TransactionTag, TransactionCategory from apps.common.fields.forms.dynamic_select import ( DynamicModelChoiceField, DynamicModelMultipleChoiceField, ) +from apps.common.widgets.crispy.submit import NoClassSubmit +from apps.common.widgets.datepicker import AirDatePickerInput +from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput +from apps.common.widgets.tom_select import TomSelect, TransactionSelect +from apps.dca.models import DCAEntry, DCAStrategy +from apps.transactions.models import Transaction, TransactionCategory, TransactionTag +from crispy_bootstrap5.bootstrap5 import BS5Accordion, Switch +from crispy_forms.bootstrap import AccordionGroup, FormActions +from crispy_forms.helper import FormHelper +from crispy_forms.layout import HTML, Column, Layout, Row +from django import forms +from django.utils.translation import gettext_lazy as _ class DCAStrategyForm(forms.ModelForm): @@ -36,8 +34,8 @@ class DCAStrategyForm(forms.ModelForm): self.helper.layout = Layout( "name", Row( - Column("payment_currency", css_class="form-group col-md-6"), - Column("target_currency", css_class="form-group col-md-6"), + Column("payment_currency", css_class="md:col-span-6"), + Column("target_currency", css_class="md:col-span-6"), ), "notes", ) @@ -155,8 +153,8 @@ class DCAEntryForm(forms.ModelForm): self.helper.layout = Layout( "date", Row( - Column("amount_paid", css_class="form-group col-md-6"), - Column("amount_received", css_class="form-group col-md-6"), + Column("amount_paid", css_class="md:col-span-6"), + Column("amount_received", css_class="md:col-span-6"), ), "notes", BS5Accordion( @@ -175,11 +173,9 @@ class DCAEntryForm(forms.ModelForm): Row( Column( "from_category", - css_class="form-group col-md-6 mb-0", - ), - Column( - "from_tags", css_class="form-group col-md-6 mb-0" + css_class="md:col-span-6 mb-0", ), + Column("from_tags", css_class="md:col-span-6 mb-0"), css_class="form-row", ), ), @@ -195,10 +191,8 @@ class DCAEntryForm(forms.ModelForm): css_class="form-row", ), Row( - Column( - "to_category", css_class="form-group col-md-6 mb-0" - ), - Column("to_tags", css_class="form-group col-md-6 mb-0"), + Column("to_category", css_class="md:col-span-6 mb-0"), + Column("to_tags", css_class="md:col-span-6 mb-0"), css_class="form-row", ), ), diff --git a/app/apps/insights/forms.py b/app/apps/insights/forms.py index 4efabf9..ab23b7b 100644 --- a/app/apps/insights/forms.py +++ b/app/apps/insights/forms.py @@ -1,15 +1,14 @@ -from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Field, Row, Column -from django import forms -from django.utils.translation import gettext_lazy as _ - from apps.common.widgets.datepicker import ( + AirDatePickerInput, AirMonthYearPickerInput, AirYearPickerInput, - AirDatePickerInput, ) -from apps.transactions.models import TransactionCategory from apps.common.widgets.tom_select import TomSelect +from apps.transactions.models import TransactionCategory +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Column, Field, Layout, Row +from django import forms +from django.utils.translation import gettext_lazy as _ class SingleMonthForm(forms.Form): @@ -59,8 +58,8 @@ class MonthRangeForm(forms.Form): self.helper.layout = Layout( Row( - Column("month_from", css_class="form-group col-md-6"), - Column("month_to", css_class="form-group col-md-6"), + Column("month_from", css_class="md:col-span-6"), + Column("month_to", css_class="md:col-span-6"), ), ) @@ -82,8 +81,8 @@ class YearRangeForm(forms.Form): self.helper.layout = Layout( Row( - Column("year_from", css_class="form-group col-md-6"), - Column("year_to", css_class="form-group col-md-6"), + Column("year_from", css_class="md:col-span-6"), + Column("year_to", css_class="md:col-span-6"), ), ) @@ -105,8 +104,8 @@ class DateRangeForm(forms.Form): self.helper.layout = Layout( Row( - Column("date_from", css_class="form-group col-md-6"), - Column("date_to", css_class="form-group col-md-6"), + Column("date_from", css_class="md:col-span-6"), + Column("date_to", css_class="md:col-span-6"), css_class="mb-0", ), ) diff --git a/app/apps/rules/forms.py b/app/apps/rules/forms.py index 64c6201..7bfa40c 100644 --- a/app/apps/rules/forms.py +++ b/app/apps/rules/forms.py @@ -1,20 +1,21 @@ -from crispy_bootstrap5.bootstrap5 import Switch, BS5Accordion -from crispy_forms.bootstrap import FormActions, AccordionGroup +from apps.common.fields.forms.dynamic_select import DynamicModelChoiceField +from apps.common.widgets.crispy.submit import NoClassSubmit +from apps.common.widgets.tom_select import TomSelect, TransactionSelect +from apps.rules.models import ( + TransactionRule, + TransactionRuleAction, + UpdateOrCreateTransactionRuleAction, +) +from apps.transactions.forms import BulkEditTransactionForm +from apps.transactions.models import Transaction +from crispy_bootstrap5.bootstrap5 import BS5Accordion, Switch +from crispy_forms.bootstrap import AccordionGroup, FormActions from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Field, Row, Column, HTML +from crispy_forms.layout import HTML, Column, Field, Layout, Row from django import forms from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ -from apps.common.widgets.crispy.submit import NoClassSubmit -from apps.common.widgets.crispy.submit import NoClassSubmit -from apps.common.widgets.tom_select import TomSelect, TransactionSelect -from apps.rules.models import TransactionRule, UpdateOrCreateTransactionRuleAction -from apps.rules.models import TransactionRuleAction -from apps.common.fields.forms.dynamic_select import DynamicModelChoiceField -from apps.transactions.forms import BulkEditTransactionForm -from apps.transactions.models import Transaction - class TransactionRuleForm(forms.ModelForm): class Meta: @@ -221,7 +222,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_type_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_type", rows=1), @@ -231,7 +232,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_is_paid_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_is_paid", rows=1), @@ -241,7 +242,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_mute_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_mute", rows=1), @@ -251,7 +252,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_account_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_account", rows=1), @@ -261,7 +262,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_entities_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_entities", rows=1), @@ -271,7 +272,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_date_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_date", rows=1), @@ -281,7 +282,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_reference_date_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_reference_date", rows=1), @@ -291,7 +292,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_description_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_description", rows=1), @@ -301,7 +302,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_amount_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_amount", rows=1), @@ -311,7 +312,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_category_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_category", rows=1), @@ -321,7 +322,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_tags_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_tags", rows=1), @@ -331,7 +332,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_notes_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_notes", rows=1), @@ -341,7 +342,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_internal_note_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_internal_note", rows=1), @@ -351,7 +352,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm): Row( Column( Field("search_internal_id_operator"), - css_class="form-group col-md-4", + css_class="md:col-span-4", ), Column( Field("search_internal_id", rows=1), diff --git a/app/apps/transactions/filters.py b/app/apps/transactions/filters.py index 257ab6a..2110223 100644 --- a/app/apps/transactions/filters.py +++ b/app/apps/transactions/filters.py @@ -1,11 +1,4 @@ import django_filters -from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Field, Row, Column -from django import forms -from django.db.models import Q -from django.utils.translation import gettext_lazy as _ -from django_filters import Filter - from apps.accounts.models import Account from apps.common.fields.month_year import MonthYearFormField from apps.common.widgets.datepicker import AirDatePickerInput @@ -15,9 +8,15 @@ from apps.currencies.models import Currency from apps.transactions.models import ( Transaction, TransactionCategory, - TransactionTag, TransactionEntity, + TransactionTag, ) +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Column, Field, Layout, Row +from django import forms +from django.db.models import Q +from django.utils.translation import gettext_lazy as _ +from django_filters import Filter SITUACAO_CHOICES = ( ("1", _("Paid")), @@ -159,13 +158,13 @@ class TransactionsFilter(django_filters.FilterSet): Field("description"), Row(Column("date_start"), Column("date_end")), Row( - Column("reference_date_start", css_class="form-group col-md-6 mb-0"), - Column("reference_date_end", css_class="form-group col-md-6 mb-0"), + Column("reference_date_start", css_class="md:col-span-6 mb-0"), + Column("reference_date_end", css_class="md:col-span-6 mb-0"), css_class="form-row", ), Row( - Column("from_amount", css_class="form-group col-md-6 mb-0"), - Column("to_amount", css_class="form-group col-md-6 mb-0"), + Column("from_amount", css_class="md:col-span-6 mb-0"), + Column("to_amount", css_class="md:col-span-6 mb-0"), css_class="form-row", ), Field("account", size=1), diff --git a/app/apps/transactions/forms.py b/app/apps/transactions/forms.py index 684e6d8..7503d78 100644 --- a/app/apps/transactions/forms.py +++ b/app/apps/transactions/forms.py @@ -1,20 +1,5 @@ from copy import deepcopy -from crispy_bootstrap5.bootstrap5 import Switch, BS5Accordion -from crispy_forms.bootstrap import FormActions, AccordionGroup, AppendedText -from crispy_forms.helper import FormHelper -from crispy_forms.layout import ( - Layout, - Row, - Column, - Field, - Div, - HTML, -) -from django import forms -from django.db.models import Q -from django.utils.translation import gettext_lazy as _ - from apps.accounts.models import Account from apps.common.fields.forms.dynamic_select import ( DynamicModelChoiceField, @@ -26,14 +11,28 @@ from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput from apps.common.widgets.tom_select import TomSelect from apps.rules.signals import transaction_created, transaction_updated from apps.transactions.models import ( + InstallmentPlan, + QuickTransaction, + RecurringTransaction, Transaction, TransactionCategory, - TransactionTag, - InstallmentPlan, - RecurringTransaction, TransactionEntity, - QuickTransaction, + TransactionTag, ) +from crispy_bootstrap5.bootstrap5 import BS5Accordion, Switch +from crispy_forms.bootstrap import AccordionGroup, AppendedText, FormActions +from crispy_forms.helper import FormHelper +from crispy_forms.layout import ( + HTML, + Column, + Div, + Field, + Layout, + Row, +) +from django import forms +from django.db.models import Q +from django.utils.translation import gettext_lazy as _ class TransactionForm(forms.ModelForm): @@ -134,20 +133,20 @@ class TransactionForm(forms.ModelForm): ), Field("is_paid", template="transactions/widgets/paid_toggle_button.html"), Row( - Column("account", css_class="form-group col-md-6 mb-0"), - Column("entities", css_class="form-group col-md-6 mb-0"), + Column("account", css_class="md:col-span-6 mb-0"), + Column("entities", css_class="md:col-span-6 mb-0"), css_class="form-row", ), Row( - Column(Field("date"), css_class="form-group col-md-6 mb-0"), - Column(Field("reference_date"), css_class="form-group col-md-6 mb-0"), + Column(Field("date"), css_class="md:col-span-6 mb-0"), + Column(Field("reference_date"), css_class="md:col-span-6 mb-0"), css_class="form-row", ), "description", Field("amount", inputmode="decimal"), Row( - Column("category", css_class="form-group col-md-6 mb-0"), - Column("tags", css_class="form-group col-md-6 mb-0"), + Column("category", css_class="md:col-span-6 mb-0"), + Column("tags", css_class="md:col-span-6 mb-0"), css_class="form-row", ), "notes", @@ -164,8 +163,8 @@ class TransactionForm(forms.ModelForm): Field("is_paid", template="transactions/widgets/paid_toggle_button.html"), "account", Row( - Column(Field("date"), css_class="form-group col-md-6 mb-0"), - Column(Field("reference_date"), css_class="form-group col-md-6 mb-0"), + Column(Field("date"), css_class="md:col-span-6 mb-0"), + Column(Field("reference_date"), css_class="md:col-span-6 mb-0"), css_class="form-row", ), "description", @@ -175,8 +174,8 @@ class TransactionForm(forms.ModelForm): _("More"), "entities", Row( - Column("category", css_class="form-group col-md-6 mb-0"), - Column("tags", css_class="form-group col-md-6 mb-0"), + Column("category", css_class="md:col-span-6 mb-0"), + Column("tags", css_class="md:col-span-6 mb-0"), css_class="form-row", ), "notes", @@ -350,15 +349,15 @@ class QuickTransactionForm(forms.ModelForm): "name", HTML("
"), Row( - Column("account", css_class="form-group col-md-6 mb-0"), - Column("entities", css_class="form-group col-md-6 mb-0"), + Column("account", css_class="md:col-span-6 mb-0"), + Column("entities", css_class="md:col-span-6 mb-0"), css_class="form-row", ), "description", Field("amount", inputmode="decimal"), Row( - Column("category", css_class="form-group col-md-6 mb-0"), - Column("tags", css_class="form-group col-md-6 mb-0"), + Column("category", css_class="md:col-span-6 mb-0"), + Column("tags", css_class="md:col-span-6 mb-0"), css_class="form-row", ), "notes", @@ -481,20 +480,20 @@ class BulkEditTransactionForm(forms.Form): template="transactions/widgets/unselectable_paid_toggle_button.html", ), Row( - Column("account", css_class="form-group col-md-6 mb-0"), - Column("entities", css_class="form-group col-md-6 mb-0"), + Column("account", css_class="md:col-span-6 mb-0"), + Column("entities", css_class="md:col-span-6 mb-0"), css_class="form-row", ), Row( - Column(Field("date"), css_class="form-group col-md-6 mb-0"), - Column(Field("reference_date"), css_class="form-group col-md-6 mb-0"), + Column(Field("date"), css_class="md:col-span-6 mb-0"), + Column(Field("reference_date"), css_class="md:col-span-6 mb-0"), css_class="form-row", ), "description", Field("amount", inputmode="decimal"), Row( - Column("category", css_class="form-group col-md-6 mb-0"), - Column("tags", css_class="form-group col-md-6 mb-0"), + Column("category", css_class="md:col-span-6 mb-0"), + Column("tags", css_class="md:col-span-6 mb-0"), css_class="form-row", ), "notes", @@ -600,10 +599,10 @@ class TransferForm(forms.Form): self.helper.layout = Layout( Row( - Column(Field("date"), css_class="form-group col-md-6 mb-0"), + Column(Field("date"), css_class="md:col-span-6 mb-0"), Column( Field("reference_date"), - css_class="form-group col-md-6 mb-0", + css_class="md:col-span-6 mb-0", ), css_class="form-row", ), @@ -612,45 +611,29 @@ class TransferForm(forms.Form): Switch("mute"), Row( Column( - Row( - Column( - "from_account", - css_class="form-group col-md-6 mb-0", - ), - Column( - Field("from_amount"), - css_class="form-group col-md-6 mb-0", - ), - css_class="form-row", - ), - Row( - Column("from_category", css_class="form-group col-md-6 mb-0"), - Column("from_tags", css_class="form-group col-md-6 mb-0"), - css_class="form-row", - ), + "from_account", + css_class="md:col-span-6", ), - css_class="p-1 mx-1 my-3 border rounded-3", + Column( + Field("from_amount"), + css_class="md:col-span-6", + ), + Column("from_category", css_class="md:col-span-6"), + Column("from_tags", css_class="md:col-span-6"), + css_class="bg-base-200 rounded-box p-4 border-base-content/60 border mb-3", ), Row( Column( - Row( - Column( - "to_account", - css_class="form-group col-md-6 mb-0", - ), - Column( - Field("to_amount"), - css_class="form-group col-md-6 mb-0", - ), - css_class="form-row", - ), - Row( - Column("to_category", css_class="form-group col-md-6 mb-0"), - Column("to_tags", css_class="form-group col-md-6 mb-0"), - css_class="form-row", - ), + "to_account", + css_class="md:col-span-6 mb-0", ), - css_class="p-1 mx-1 my-3 border rounded-3", + Column( + Field("to_amount"), + css_class="md:col-span-6 mb-0", + ), + Column("to_category", css_class="md:col-span-6 mb-0"), + Column("to_tags", css_class="md:col-span-6 mb-0"), + css_class="bg-base-200 rounded-box p-4 border-base-content/60 border mb-3", ), FormActions( NoClassSubmit( @@ -841,8 +824,8 @@ class InstallmentPlanForm(forms.ModelForm): template="transactions/widgets/income_expense_toggle_buttons.html", ), Row( - Column("account", css_class="form-group col-md-6 mb-0"), - Column("entities", css_class="form-group col-md-6 mb-0"), + Column("account", css_class="md:col-span-6 mb-0"), + Column("entities", css_class="md:col-span-6 mb-0"), css_class="form-row", ), "description", @@ -850,20 +833,20 @@ class InstallmentPlanForm(forms.ModelForm): "notes", Switch("add_notes_to_transaction"), Row( - Column("number_of_installments", css_class="form-group col-md-6 mb-0"), - Column("installment_start", css_class="form-group col-md-6 mb-0"), + Column("number_of_installments", css_class="md:col-span-6 mb-0"), + Column("installment_start", css_class="md:col-span-6 mb-0"), css_class="form-row", ), Row( - Column("start_date", css_class="form-group col-md-4 mb-0"), - Column("reference_date", css_class="form-group col-md-4 mb-0"), - Column("recurrence", css_class="form-group col-md-4 mb-0"), + Column("start_date", css_class="md:col-span-4 mb-0"), + Column("reference_date", css_class="md:col-span-4 mb-0"), + Column("recurrence", css_class="md:col-span-4 mb-0"), css_class="form-row", ), "installment_amount", Row( - Column("category", css_class="form-group col-md-6 mb-0"), - Column("tags", css_class="form-group col-md-6 mb-0"), + Column("category", css_class="md:col-span-6 mb-0"), + Column("tags", css_class="md:col-span-6 mb-0"), css_class="form-row", ), ) @@ -1103,29 +1086,29 @@ class RecurringTransactionForm(forms.ModelForm): template="transactions/widgets/income_expense_toggle_buttons.html", ), Row( - Column("account", css_class="form-group col-md-6 mb-0"), - Column("entities", css_class="form-group col-md-6 mb-0"), + Column("account", css_class="md:col-span-6 mb-0"), + Column("entities", css_class="md:col-span-6 mb-0"), css_class="form-row", ), "description", Switch("add_description_to_transaction"), "amount", Row( - Column("category", css_class="form-group col-md-6 mb-0"), - Column("tags", css_class="form-group col-md-6 mb-0"), + Column("category", css_class="md:col-span-6 mb-0"), + Column("tags", css_class="md:col-span-6 mb-0"), css_class="form-row", ), "notes", Switch("add_notes_to_transaction"), Row( - Column("start_date", css_class="form-group col-md-6 mb-0"), - Column("reference_date", css_class="form-group col-md-6 mb-0"), + Column("start_date", css_class="md:col-span-6 mb-0"), + Column("reference_date", css_class="md:col-span-6 mb-0"), css_class="form-row", ), Row( - Column("recurrence_interval", css_class="form-group col-md-4 mb-0"), - Column("recurrence_type", css_class="form-group col-md-4 mb-0"), - Column("end_date", css_class="form-group col-md-4 mb-0"), + Column("recurrence_interval", css_class="md:col-span-4 mb-0"), + Column("recurrence_type", css_class="md:col-span-4 mb-0"), + Column("end_date", css_class="md:col-span-4 mb-0"), css_class="form-row", ), AppendedText("keep_at_most", _("future transactions")), diff --git a/app/apps/transactions/models.py b/app/apps/transactions/models.py index 7f56229..12a65cf 100644 --- a/app/apps/transactions/models.py +++ b/app/apps/transactions/models.py @@ -2,29 +2,29 @@ import decimal import logging from copy import deepcopy +from apps.common.fields.month_year import MonthYearModelField +from apps.common.functions.decimals import truncate_decimal +from apps.common.middleware.thread_local import get_current_user +from apps.common.models import ( + OwnedObject, + OwnedObjectManager, + SharedObject, + SharedObjectManager, +) +from apps.common.templatetags.decimal import drop_trailing_zeros, localize_number +from apps.currencies.utils.convert import convert +from apps.transactions.validators import validate_decimal_places, validate_non_negative from dateutil.relativedelta import relativedelta from django.conf import settings from django.core.validators import MinValueValidator from django.db import models, transaction from django.db.models import Q from django.dispatch import Signal +from django.forms import ValidationError from django.template.defaultfilters import date from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from apps.common.fields.month_year import MonthYearModelField -from apps.common.functions.decimals import truncate_decimal -from apps.common.templatetags.decimal import localize_number, drop_trailing_zeros -from apps.currencies.utils.convert import convert -from apps.transactions.validators import validate_decimal_places, validate_non_negative -from apps.common.middleware.thread_local import get_current_user -from apps.common.models import ( - SharedObject, - SharedObjectManager, - OwnedObject, - OwnedObjectManager, -) - logger = logging.getLogger() @@ -381,21 +381,32 @@ class Transaction(OwnedObject): db_table = "transactions" default_manager_name = "objects" - def clean_fields(self, *args, **kwargs): + def clean(self): + super().clean() + + # Only process amount and reference_date if account exists + # If account is missing, Django's required field validation will handle it + try: + account = self.account + except Transaction.account.RelatedObjectDoesNotExist: + # Account doesn't exist, skip processing that depends on it + # Django will add the required field error + return + + # Validate and normalize amount if isinstance(self.amount, (str, int, float)): self.amount = decimal.Decimal(str(self.amount)) self.amount = truncate_decimal( - value=self.amount, decimal_places=self.account.currency.decimal_places + value=self.amount, decimal_places=account.currency.decimal_places ) + # Normalize reference_date if self.reference_date: self.reference_date = self.reference_date.replace(day=1) elif not self.reference_date and self.date: self.reference_date = self.date.replace(day=1) - super().clean_fields(*args, **kwargs) - def save(self, *args, **kwargs): # This is not recommended as it will run twice on some cases like form and API saves. # We only do this here because we forgot to independently call it on multiple places. diff --git a/app/apps/users/forms.py b/app/apps/users/forms.py index defa4fe..3f05051 100644 --- a/app/apps/users/forms.py +++ b/app/apps/users/forms.py @@ -1,35 +1,43 @@ +from apps.common.middleware.thread_local import get_current_user +from apps.common.widgets.crispy.submit import NoClassSubmit +from apps.users.models import UserSettings from crispy_forms.bootstrap import ( FormActions, ) from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Submit, Row, Column, Field, Div, HTML +from crispy_forms.layout import HTML, Column, Div, Field, Layout, Row, Submit from django import forms from django.contrib.auth import get_user_model from django.contrib.auth.forms import ( - UsernameField, AuthenticationForm, UserCreationForm, + UsernameField, ) from django.db import transaction from django.utils.translation import gettext_lazy as _ -from apps.common.widgets.crispy.submit import NoClassSubmit -from apps.users.models import UserSettings -from apps.common.middleware.thread_local import get_current_user - class LoginForm(AuthenticationForm): username = UsernameField( label=_("E-mail"), widget=forms.EmailInput( - attrs={"class": "form-control", "placeholder": "E-mail", "name": "email"} + attrs={ + "class": "input", + "placeholder": _("E-mail"), + "name": "email", + "autocomplete": "email", + } ), ) password = forms.CharField( label=_("Password"), strip=False, widget=forms.PasswordInput( - attrs={"class": "form-control", "placeholder": "Senha"} + attrs={ + "class": "input", + "placeholder": _("Password"), + "autocomplete": "current-password", + } ), ) @@ -191,8 +199,8 @@ class UserUpdateForm(forms.ModelForm): # Define the layout using Crispy Forms, including the new fields self.helper.layout = Layout( Row( - Column("first_name", css_class="form-group col-md-6"), - Column("last_name", css_class="form-group col-md-6"), + Column("first_name", css_class="md:col-span-6"), + Column("last_name", css_class="md:col-span-6"), css_class="row", ), Field("email"), @@ -354,8 +362,8 @@ class UserAddForm(UserCreationForm): self.helper.layout = Layout( Field("email"), Row( - Column("first_name", css_class="form-group col-md-6"), - Column("last_name", css_class="form-group col-md-6"), + Column("first_name", css_class="md:col-span-6"), + Column("last_name", css_class="md:col-span-6"), css_class="row", ), # UserCreationForm provides 'password1' and 'password2' fields diff --git a/app/templates/account_groups/fragments/list.html b/app/templates/account_groups/fragments/list.html index aae721a..285ed62 100644 --- a/app/templates/account_groups/fragments/list.html +++ b/app/templates/account_groups/fragments/list.html @@ -1,9 +1,9 @@ {% load i18n %} -
-
+
+
{% spaceless %}
{% translate 'Account Groups' %} - -
-
+
+
{% if account_groups %} - +
- + {% for account_group in account_groups %} -
{% translate 'Name' %}