Update django money / py-moneyed dependencies (#5778)

* Replace moneyed format_money function

* Update django-money to 3.3.0 and py-moneyed to 3.0

* Add CurrencyField migrations

* Fix checking if decimal_places is set

* Add currency formatting test

* Test fixing deepsource test patterns

* Revert "Test fixing deepsource test patterns"

This reverts commit 398ef93cd6.

* FIx requirements.txt formatting

* Revert "FIx requirements.txt formatting"

This reverts commit bb554b0758.
This commit is contained in:
Bobbe 2023-10-31 12:19:55 +01:00 committed by GitHub
parent ebaa7d64a8
commit 4913acda79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 283 additions and 17 deletions

View File

@ -3,8 +3,14 @@
import re
import string
from django.conf import settings
from django.utils import translation
from django.utils.translation import gettext_lazy as _
from babel import Locale
from babel.numbers import parse_pattern
from djmoney.money import Money
def parse_format_string(fmt_string: str) -> dict:
"""Extract formatting information from the provided format string.
@ -160,3 +166,34 @@ def extract_named_group(name: str, value: str, fmt_string: str) -> str:
# And return the value we are interested in
# Note: This will raise an IndexError if the named group was not matched
return result.group(name)
def format_money(money: Money, decimal_places: int = None, format: str = None) -> str:
"""Format money object according to the currently set local
Args:
decimal_places: Number of decimal places to use
format: Format pattern according LDML / the babel format pattern syntax (https://babel.pocoo.org/en/latest/numbers.html)
Returns:
str: The formatted string
Raises:
ValueError: format string is incorrectly specified
"""
language = None and translation.get_language() or settings.LANGUAGE_CODE
locale = Locale.parse(translation.to_locale(language))
if format:
pattern = parse_pattern(format)
else:
pattern = locale.currency_formats["standard"]
if decimal_places is not None:
pattern.frac_prec = (decimal_places, decimal_places)
return pattern.apply(
money.amount,
locale,
currency=money.currency.code,
currency_digits=decimal_places is None,
decimal_quantization=decimal_places is not None,
)

View File

@ -10,7 +10,6 @@ from django.core.validators import URLValidator
from django.db.utils import OperationalError, ProgrammingError
from django.utils.translation import gettext_lazy as _
import moneyed.localization
import requests
from djmoney.contrib.exchange.models import convert_money
from djmoney.money import Money
@ -22,6 +21,7 @@ import InvenTree.helpers_model
import InvenTree.version
from common.notifications import (InvenTreeNotificationBodies,
NotificationBody, trigger_notification)
from InvenTree.format import format_money
logger = logging.getLogger('inventree')
@ -167,14 +167,13 @@ def download_image_from_url(remote_url, timeout=2.5):
return img
def render_currency(money, decimal_places=None, currency=None, include_symbol=True, min_decimal_places=None, max_decimal_places=None):
def render_currency(money, decimal_places=None, currency=None, min_decimal_places=None, max_decimal_places=None):
"""Render a currency / Money object to a formatted string (e.g. for reports)
Arguments:
money: The Money instance to be rendered
decimal_places: The number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES setting.
currency: Optionally convert to the specified currency
include_symbol: Render with the appropriate currency symbol
min_decimal_places: The minimum number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES_MIN setting.
max_decimal_places: The maximum number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES setting.
"""
@ -216,11 +215,7 @@ def render_currency(money, decimal_places=None, currency=None, include_symbol=Tr
decimal_places = max(decimal_places, max_decimal_places)
return moneyed.localization.format_money(
money,
decimal_places=decimal_places,
include_symbol=include_symbol,
)
return format_money(money, decimal_places=decimal_places)
def getModelsWithMixin(mixin_class) -> list:

View File

@ -330,6 +330,33 @@ class FormatTest(TestCase):
"PO-###-{test}",
)
def test_currency_formatting(self):
"""Test that currency formatting works correctly for multiple currencies"""
test_data = (
(Money( 3651.285718, "USD"), 4, "$3,651.2857" ), # noqa: E201,E202
(Money(487587.849178, "CAD"), 5, "CA$487,587.84918"), # noqa: E201,E202
(Money( 0.348102, "EUR"), 1, "€0.3" ), # noqa: E201,E202
(Money( 0.916530, "GBP"), 1, "£0.9" ), # noqa: E201,E202
(Money( 61.031024, "JPY"), 3, "¥61.031" ), # noqa: E201,E202
(Money( 49609.694602, "JPY"), 1, "¥49,609.7" ), # noqa: E201,E202
(Money(155565.264777, "AUD"), 2, "A$155,565.26" ), # noqa: E201,E202
(Money( 0.820437, "CNY"), 4, "CN¥0.8204" ), # noqa: E201,E202
(Money( 7587.849178, "EUR"), 0, "€7,588" ), # noqa: E201,E202
(Money( 0.348102, "GBP"), 3, "£0.348" ), # noqa: E201,E202
(Money( 0.652923, "CHF"), 0, "CHF1" ), # noqa: E201,E202
(Money( 0.820437, "CNY"), 1, "CN¥0.8" ), # noqa: E201,E202
(Money(98789.5295680, "CHF"), 0, "CHF98,790" ), # noqa: E201,E202
(Money( 0.585787, "USD"), 1, "$0.6" ), # noqa: E201,E202
(Money( 0.690541, "CAD"), 3, "CA$0.691" ), # noqa: E201,E202
(Money( 427.814104, "AUD"), 5, "A$427.81410" ), # noqa: E201,E202
)
with self.settings(LANGUAGE_CODE="en-us"):
for value, decimal_places, expected_result in test_data:
result = InvenTree.format.format_money(value, decimal_places=decimal_places)
assert result == expected_result
class TestHelpers(TestCase):
"""Tests for InvenTree helper functions."""

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.22 on 2023-10-24 16:44
from django.db import migrations
import djmoney.models.fields
class Migration(migrations.Migration):
dependencies = [
('company', '0066_auto_20230616_2059'),
]
operations = [
migrations.AlterField(
model_name='supplierpricebreak',
name='price_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
]

View File

@ -0,0 +1,59 @@
# Generated by Django 3.2.22 on 2023-10-24 16:44
from django.db import migrations
import djmoney.models.fields
class Migration(migrations.Migration):
dependencies = [
('order', '0097_auto_20230529_0107'),
]
operations = [
migrations.AlterField(
model_name='purchaseorder',
name='total_price_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='purchaseorderextraline',
name='price_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='purchaseorderlineitem',
name='purchase_price_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='returnorder',
name='total_price_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='returnorderextraline',
name='price_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='returnorderlineitem',
name='price_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='salesorder',
name='total_price_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='salesorderextraline',
name='price_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='salesorderlineitem',
name='sale_price_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
]

View File

@ -0,0 +1,114 @@
# Generated by Django 3.2.22 on 2023-10-24 16:44
from django.db import migrations
import djmoney.models.fields
class Migration(migrations.Migration):
dependencies = [
('part', '0117_remove_part_responsible'),
]
operations = [
migrations.AlterField(
model_name='partinternalpricebreak',
name='price_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='bom_cost_max_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='bom_cost_min_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='internal_cost_max_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='internal_cost_min_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='overall_max_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='overall_min_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='purchase_cost_max_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='purchase_cost_min_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='sale_history_max_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='sale_history_min_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='sale_price_max_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='sale_price_min_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='supplier_price_max_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='supplier_price_min_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='variant_cost_max_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partpricing',
name='variant_cost_min_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partsellpricebreak',
name='price_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partstocktake',
name='cost_max_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
migrations.AlterField(
model_name='partstocktake',
name='cost_min_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.22 on 2023-10-24 16:44
from django.db import migrations
import djmoney.models.fields
class Migration(migrations.Migration):
dependencies = [
('stock', '0103_stock_location_types'),
]
operations = [
migrations.AlterField(
model_name='stockitem',
name='purchase_price_currency',
field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True),
),
]

View File

@ -16,7 +16,7 @@ django-ical # iCal export for calendar views
django-import-export>=3.3.1 # Data import / export for admin interface
django-maintenance-mode # Shut down application while reloading etc.
django-markdownify # Markdown rendering
django-money<3.0.0 # Django app for currency management # FIXED 2022-06-26 to make sure py-moneyed is not conflicting
django-money>=3.0.0 # Django app for currency management
django-mptt==0.11.0 # Modified Preorder Tree Traversal
django-redis>=5.0.0 # Redis integration
django-q2 # Background task scheduling
@ -49,6 +49,3 @@ sentry-sdk # Error reporting (optional)
setuptools # Standard dependency
tablib[xls,xlsx,yaml] # Support for XLS and XLSX formats
weasyprint==54.3 # PDF generation
# Fixed sub-dependencies
py-moneyed<2.0 # For django-money # FIXED 2022-06-18 as we need `moneyed.localization`

View File

@ -113,7 +113,7 @@ django-maintenance-mode==0.19.0
# via -r requirements.in
django-markdownify==0.9.3
# via -r requirements.in
django-money==2.1.1
django-money==3.3.0
# via -r requirements.in
django-mptt==0.11.0
# via -r requirements.in
@ -211,10 +211,8 @@ pillow==9.5.0
# weasyprint
pint==0.21
# via -r requirements.in
py-moneyed==1.2
# via
# -r requirements.in
# django-money
py-moneyed==3.0
# via django-money
pycparser==2.21
# via cffi
pydyf==0.8.0
@ -302,6 +300,7 @@ tinycss2==1.2.1
typing-extensions==4.8.0
# via
# asgiref
# py-moneyed
# qrcode
uritemplate==4.1.1
# via