mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
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 commit398ef93cd6
. * FIx requirements.txt formatting * Revert "FIx requirements.txt formatting" This reverts commitbb554b0758
.
This commit is contained in:
parent
ebaa7d64a8
commit
4913acda79
@ -3,8 +3,14 @@
|
|||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.utils import translation
|
||||||
from django.utils.translation import gettext_lazy as _
|
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:
|
def parse_format_string(fmt_string: str) -> dict:
|
||||||
"""Extract formatting information from the provided format string.
|
"""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
|
# And return the value we are interested in
|
||||||
# Note: This will raise an IndexError if the named group was not matched
|
# Note: This will raise an IndexError if the named group was not matched
|
||||||
return result.group(name)
|
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,
|
||||||
|
)
|
||||||
|
@ -10,7 +10,6 @@ from django.core.validators import URLValidator
|
|||||||
from django.db.utils import OperationalError, ProgrammingError
|
from django.db.utils import OperationalError, ProgrammingError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import moneyed.localization
|
|
||||||
import requests
|
import requests
|
||||||
from djmoney.contrib.exchange.models import convert_money
|
from djmoney.contrib.exchange.models import convert_money
|
||||||
from djmoney.money import Money
|
from djmoney.money import Money
|
||||||
@ -22,6 +21,7 @@ import InvenTree.helpers_model
|
|||||||
import InvenTree.version
|
import InvenTree.version
|
||||||
from common.notifications import (InvenTreeNotificationBodies,
|
from common.notifications import (InvenTreeNotificationBodies,
|
||||||
NotificationBody, trigger_notification)
|
NotificationBody, trigger_notification)
|
||||||
|
from InvenTree.format import format_money
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
@ -167,14 +167,13 @@ def download_image_from_url(remote_url, timeout=2.5):
|
|||||||
return img
|
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)
|
"""Render a currency / Money object to a formatted string (e.g. for reports)
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
money: The Money instance to be rendered
|
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.
|
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
|
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.
|
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.
|
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)
|
decimal_places = max(decimal_places, max_decimal_places)
|
||||||
|
|
||||||
return moneyed.localization.format_money(
|
return format_money(money, decimal_places=decimal_places)
|
||||||
money,
|
|
||||||
decimal_places=decimal_places,
|
|
||||||
include_symbol=include_symbol,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def getModelsWithMixin(mixin_class) -> list:
|
def getModelsWithMixin(mixin_class) -> list:
|
||||||
|
@ -330,6 +330,33 @@ class FormatTest(TestCase):
|
|||||||
"PO-###-{test}",
|
"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):
|
class TestHelpers(TestCase):
|
||||||
"""Tests for InvenTree helper functions."""
|
"""Tests for InvenTree helper functions."""
|
||||||
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
59
InvenTree/order/migrations/0098_auto_20231024_1844.py
Normal file
59
InvenTree/order/migrations/0098_auto_20231024_1844.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
114
InvenTree/part/migrations/0118_auto_20231024_1844.py
Normal file
114
InvenTree/part/migrations/0118_auto_20231024_1844.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -16,7 +16,7 @@ django-ical # iCal export for calendar views
|
|||||||
django-import-export>=3.3.1 # Data import / export for admin interface
|
django-import-export>=3.3.1 # Data import / export for admin interface
|
||||||
django-maintenance-mode # Shut down application while reloading etc.
|
django-maintenance-mode # Shut down application while reloading etc.
|
||||||
django-markdownify # Markdown rendering
|
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-mptt==0.11.0 # Modified Preorder Tree Traversal
|
||||||
django-redis>=5.0.0 # Redis integration
|
django-redis>=5.0.0 # Redis integration
|
||||||
django-q2 # Background task scheduling
|
django-q2 # Background task scheduling
|
||||||
@ -49,6 +49,3 @@ sentry-sdk # Error reporting (optional)
|
|||||||
setuptools # Standard dependency
|
setuptools # Standard dependency
|
||||||
tablib[xls,xlsx,yaml] # Support for XLS and XLSX formats
|
tablib[xls,xlsx,yaml] # Support for XLS and XLSX formats
|
||||||
weasyprint==54.3 # PDF generation
|
weasyprint==54.3 # PDF generation
|
||||||
|
|
||||||
# Fixed sub-dependencies
|
|
||||||
py-moneyed<2.0 # For django-money # FIXED 2022-06-18 as we need `moneyed.localization`
|
|
||||||
|
@ -113,7 +113,7 @@ django-maintenance-mode==0.19.0
|
|||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-markdownify==0.9.3
|
django-markdownify==0.9.3
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-money==2.1.1
|
django-money==3.3.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-mptt==0.11.0
|
django-mptt==0.11.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
@ -211,10 +211,8 @@ pillow==9.5.0
|
|||||||
# weasyprint
|
# weasyprint
|
||||||
pint==0.21
|
pint==0.21
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
py-moneyed==1.2
|
py-moneyed==3.0
|
||||||
# via
|
# via django-money
|
||||||
# -r requirements.in
|
|
||||||
# django-money
|
|
||||||
pycparser==2.21
|
pycparser==2.21
|
||||||
# via cffi
|
# via cffi
|
||||||
pydyf==0.8.0
|
pydyf==0.8.0
|
||||||
@ -302,6 +300,7 @@ tinycss2==1.2.1
|
|||||||
typing-extensions==4.8.0
|
typing-extensions==4.8.0
|
||||||
# via
|
# via
|
||||||
# asgiref
|
# asgiref
|
||||||
|
# py-moneyed
|
||||||
# qrcode
|
# qrcode
|
||||||
uritemplate==4.1.1
|
uritemplate==4.1.1
|
||||||
# via
|
# via
|
||||||
|
Loading…
Reference in New Issue
Block a user