From 5c9db1071053efa1262accab7ef5488daf971ebb Mon Sep 17 00:00:00 2001 From: Herculino Trotta Date: Wed, 19 Feb 2025 15:44:18 -0300 Subject: [PATCH] fix(export): unable to import decimals --- app/apps/export_app/resources/currencies.py | 7 ++++++- app/apps/export_app/resources/dca.py | 12 ++++++++++++ app/apps/export_app/resources/transactions.py | 19 +++++++++++++++++++ app/apps/export_app/views.py | 17 ++++++++--------- app/apps/export_app/widgets/foreign_key.py | 11 +++++++++++ app/apps/export_app/widgets/numbers.py | 18 ++++++++++++++++++ 6 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 app/apps/export_app/widgets/numbers.py diff --git a/app/apps/export_app/resources/currencies.py b/app/apps/export_app/resources/currencies.py index c74ea95..d97e03d 100644 --- a/app/apps/export_app/resources/currencies.py +++ b/app/apps/export_app/resources/currencies.py @@ -2,13 +2,15 @@ from import_export import fields, resources, widgets from apps.accounts.models import Account from apps.currencies.models import Currency, ExchangeRate, ExchangeRateService +from apps.export_app.widgets.foreign_key import SkipMissingForeignKeyWidget +from apps.export_app.widgets.numbers import UniversalDecimalWidget class CurrencyResource(resources.ModelResource): exchange_currency = fields.Field( attribute="exchange_currency", column_name="exchange_currency", - widget=widgets.ForeignKeyWidget(Currency, "name"), + widget=SkipMissingForeignKeyWidget(Currency, "name"), ) class Meta: @@ -26,6 +28,9 @@ class ExchangeRateResource(resources.ModelResource): column_name="to_currency", widget=widgets.ForeignKeyWidget(Currency, "name"), ) + rate = fields.Field( + attribute="rate", column_name="rate", widget=UniversalDecimalWidget() + ) class Meta: model = ExchangeRate diff --git a/app/apps/export_app/resources/dca.py b/app/apps/export_app/resources/dca.py index e9345cb..645b02a 100644 --- a/app/apps/export_app/resources/dca.py +++ b/app/apps/export_app/resources/dca.py @@ -3,6 +3,7 @@ from import_export.widgets import ForeignKeyWidget from apps.dca.models import DCAStrategy, DCAEntry from apps.currencies.models import Currency +from apps.export_app.widgets.numbers import UniversalDecimalWidget class DCAStrategyResource(resources.ModelResource): @@ -22,5 +23,16 @@ class DCAStrategyResource(resources.ModelResource): class DCAEntryResource(resources.ModelResource): + amount_paid = fields.Field( + attribute="amount_paid", + column_name="amount_paid", + widget=UniversalDecimalWidget(), + ) + amount_received = fields.Field( + attribute="amount_received", + column_name="amount_received", + widget=UniversalDecimalWidget(), + ) + class Meta: model = DCAEntry diff --git a/app/apps/export_app/resources/transactions.py b/app/apps/export_app/resources/transactions.py index 9644cd2..9668bce 100644 --- a/app/apps/export_app/resources/transactions.py +++ b/app/apps/export_app/resources/transactions.py @@ -13,6 +13,7 @@ from apps.transactions.models import ( RecurringTransaction, InstallmentPlan, ) +from apps.export_app.widgets.numbers import UniversalDecimalWidget class TransactionResource(resources.ModelResource): @@ -44,6 +45,12 @@ class TransactionResource(resources.ModelResource): column_name="internal_id", attribute="internal_id" ) + amount = fields.Field( + attribute="amount", + column_name="amount", + widget=UniversalDecimalWidget(), + ) + class Meta: model = Transaction @@ -91,6 +98,12 @@ class RecurringTransactionResource(resources.ModelResource): widget=AutoCreateManyToManyWidget(TransactionEntity, field="name"), ) + amount = fields.Field( + attribute="amount", + column_name="amount", + widget=UniversalDecimalWidget(), + ) + class Meta: model = RecurringTransaction @@ -120,5 +133,11 @@ class InstallmentPlanResource(resources.ModelResource): widget=AutoCreateManyToManyWidget(TransactionEntity, field="name"), ) + installment_amount = fields.Field( + attribute="installment_amount", + column_name="installment_amount", + widget=UniversalDecimalWidget(), + ) + class Meta: model = InstallmentPlan diff --git a/app/apps/export_app/views.py b/app/apps/export_app/views.py index d95b7ee..ce9917f 100644 --- a/app/apps/export_app/views.py +++ b/app/apps/export_app/views.py @@ -240,11 +240,6 @@ def process_imports(request, cleaned_data): dataset = Dataset() dataset.load(content, format="csv") - # Debug logging - logger.debug(f"Importing {field_name}") - logger.debug(f"Headers: {dataset.headers}") - logger.debug(f"First row: {dataset[0] if len(dataset) > 0 else 'No data'}") - # Perform the import result = resource.import_data( dataset, @@ -265,6 +260,8 @@ def process_imports(request, cleaned_data): raise ImportError(f"Error importing {field_name}: {str(e)}") with transaction.atomic(): + files = {} + if zip_file := cleaned_data.get("zip_file"): # Process ZIP file with zipfile.ZipFile(zip_file) as z: @@ -273,10 +270,12 @@ def process_imports(request, cleaned_data): with z.open(filename) as f: content = f.read().decode("utf-8") - for field_name, resource_class in import_order: - if name == field_name: - import_dataset(content, resource_class, field_name) - break + files[name] = content + + for field_name, resource_class in import_order: + if field_name in files.keys(): + content = files[field_name] + import_dataset(content, resource_class, field_name) else: # Process individual files for field_name, resource_class in import_order: diff --git a/app/apps/export_app/widgets/foreign_key.py b/app/apps/export_app/widgets/foreign_key.py index 232a810..fd582ac 100644 --- a/app/apps/export_app/widgets/foreign_key.py +++ b/app/apps/export_app/widgets/foreign_key.py @@ -9,3 +9,14 @@ class AutoCreateForeignKeyWidget(ForeignKeyWidget): except self.model.DoesNotExist: return self.model.objects.create(name=value) return None + + +class SkipMissingForeignKeyWidget(ForeignKeyWidget): + def clean(self, value, row=None, *args, **kwargs): + if not value: + return None + + try: + return super().clean(value, row, *args, **kwargs) + except self.model.DoesNotExist: + return None diff --git a/app/apps/export_app/widgets/numbers.py b/app/apps/export_app/widgets/numbers.py new file mode 100644 index 0000000..9cf002b --- /dev/null +++ b/app/apps/export_app/widgets/numbers.py @@ -0,0 +1,18 @@ +from decimal import Decimal + +from import_export.widgets import NumberWidget + + +class UniversalDecimalWidget(NumberWidget): + def clean(self, value, row=None, *args, **kwargs): + if self.is_empty(value): + return None + # Replace comma with dot if present + if isinstance(value, str): + value = value.replace(",", ".") + return Decimal(str(value)) + + def render(self, value, obj=None, **kwargs): + if value is None: + return "" + return str(value).replace(",", ".")