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 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,
|
||||
)
|
||||
|
@ -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:
|
||||
|
@ -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."""
|
||||
|
@ -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-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`
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user