feat: first batch of work

This commit is contained in:
Herculino Trotta
2025-11-01 03:15:44 -03:00
parent e600d87968
commit a63367a772
175 changed files with 3433 additions and 2245 deletions
+13 -15
View File
@@ -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"),
+1 -1
View File
@@ -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"
+7 -6
View File
@@ -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()
+9 -10
View File
@@ -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",
+20 -26
View File
@@ -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",
),
),
+12 -13
View File
@@ -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",
),
)
+27 -26
View File
@@ -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),
+11 -12
View File
@@ -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),
+76 -93
View File
@@ -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("<hr />"),
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")),
+28 -17
View File
@@ -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.
+20 -12
View File
@@ -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