mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-04-28 17:32:38 +02:00
Currency hotfixes (#1240)
* hotfix: hardcode list of currencies to workaround failing API calls See https://github.com/spiral-project/ihatemoney/issues/1232 for a discussion on currencies * Temporarily disable some currency operations to prevent crashes Here is what is disabled: - setting or changing the default currency on an existing project - adding or editing a bill with a currency that differs from the default currency of the project --------- Co-authored-by: Baptiste Jonglez <git@bitsofnetworks.org>
This commit is contained in:
parent
c5c8dba631
commit
1a2fa0476b
6 changed files with 216 additions and 19 deletions
|
@ -36,13 +36,181 @@ class CurrencyConverter(object, metaclass=Singleton):
|
||||||
return rates
|
return rates
|
||||||
|
|
||||||
def get_currencies(self, with_no_currency=True):
|
def get_currencies(self, with_no_currency=True):
|
||||||
rates = [
|
currencies = [
|
||||||
rate
|
"AED",
|
||||||
for rate in self.get_rates()
|
"AFN",
|
||||||
if with_no_currency or rate != self.no_currency
|
"ALL",
|
||||||
|
"AMD",
|
||||||
|
"ANG",
|
||||||
|
"AOA",
|
||||||
|
"ARS",
|
||||||
|
"AUD",
|
||||||
|
"AWG",
|
||||||
|
"AZN",
|
||||||
|
"BAM",
|
||||||
|
"BBD",
|
||||||
|
"BDT",
|
||||||
|
"BGN",
|
||||||
|
"BHD",
|
||||||
|
"BIF",
|
||||||
|
"BMD",
|
||||||
|
"BND",
|
||||||
|
"BOB",
|
||||||
|
"BRL",
|
||||||
|
"BSD",
|
||||||
|
"BTC",
|
||||||
|
"BTN",
|
||||||
|
"BWP",
|
||||||
|
"BYN",
|
||||||
|
"BZD",
|
||||||
|
"CAD",
|
||||||
|
"CDF",
|
||||||
|
"CHF",
|
||||||
|
"CLF",
|
||||||
|
"CLP",
|
||||||
|
"CNH",
|
||||||
|
"CNY",
|
||||||
|
"COP",
|
||||||
|
"CRC",
|
||||||
|
"CUC",
|
||||||
|
"CUP",
|
||||||
|
"CVE",
|
||||||
|
"CZK",
|
||||||
|
"DJF",
|
||||||
|
"DKK",
|
||||||
|
"DOP",
|
||||||
|
"DZD",
|
||||||
|
"EGP",
|
||||||
|
"ERN",
|
||||||
|
"ETB",
|
||||||
|
"EUR",
|
||||||
|
"FJD",
|
||||||
|
"FKP",
|
||||||
|
"GBP",
|
||||||
|
"GEL",
|
||||||
|
"GGP",
|
||||||
|
"GHS",
|
||||||
|
"GIP",
|
||||||
|
"GMD",
|
||||||
|
"GNF",
|
||||||
|
"GTQ",
|
||||||
|
"GYD",
|
||||||
|
"HKD",
|
||||||
|
"HNL",
|
||||||
|
"HRK",
|
||||||
|
"HTG",
|
||||||
|
"HUF",
|
||||||
|
"IDR",
|
||||||
|
"ILS",
|
||||||
|
"IMP",
|
||||||
|
"INR",
|
||||||
|
"IQD",
|
||||||
|
"IRR",
|
||||||
|
"ISK",
|
||||||
|
"JEP",
|
||||||
|
"JMD",
|
||||||
|
"JOD",
|
||||||
|
"JPY",
|
||||||
|
"KES",
|
||||||
|
"KGS",
|
||||||
|
"KHR",
|
||||||
|
"KMF",
|
||||||
|
"KPW",
|
||||||
|
"KRW",
|
||||||
|
"KWD",
|
||||||
|
"KYD",
|
||||||
|
"KZT",
|
||||||
|
"LAK",
|
||||||
|
"LBP",
|
||||||
|
"LKR",
|
||||||
|
"LRD",
|
||||||
|
"LSL",
|
||||||
|
"LYD",
|
||||||
|
"MAD",
|
||||||
|
"MDL",
|
||||||
|
"MGA",
|
||||||
|
"MKD",
|
||||||
|
"MMK",
|
||||||
|
"MNT",
|
||||||
|
"MOP",
|
||||||
|
"MRU",
|
||||||
|
"MUR",
|
||||||
|
"MVR",
|
||||||
|
"MWK",
|
||||||
|
"MXN",
|
||||||
|
"MYR",
|
||||||
|
"MZN",
|
||||||
|
"NAD",
|
||||||
|
"NGN",
|
||||||
|
"NIO",
|
||||||
|
"NOK",
|
||||||
|
"NPR",
|
||||||
|
"NZD",
|
||||||
|
"OMR",
|
||||||
|
"PAB",
|
||||||
|
"PEN",
|
||||||
|
"PGK",
|
||||||
|
"PHP",
|
||||||
|
"PKR",
|
||||||
|
"PLN",
|
||||||
|
"PYG",
|
||||||
|
"QAR",
|
||||||
|
"RON",
|
||||||
|
"RSD",
|
||||||
|
"RUB",
|
||||||
|
"RWF",
|
||||||
|
"SAR",
|
||||||
|
"SBD",
|
||||||
|
"SCR",
|
||||||
|
"SDG",
|
||||||
|
"SEK",
|
||||||
|
"SGD",
|
||||||
|
"SHP",
|
||||||
|
"SLL",
|
||||||
|
"SOS",
|
||||||
|
"SRD",
|
||||||
|
"SSP",
|
||||||
|
"STD",
|
||||||
|
"STN",
|
||||||
|
"SVC",
|
||||||
|
"SYP",
|
||||||
|
"SZL",
|
||||||
|
"THB",
|
||||||
|
"TJS",
|
||||||
|
"TMT",
|
||||||
|
"TND",
|
||||||
|
"TOP",
|
||||||
|
"TRY",
|
||||||
|
"TTD",
|
||||||
|
"TWD",
|
||||||
|
"TZS",
|
||||||
|
"UAH",
|
||||||
|
"UGX",
|
||||||
|
"USD",
|
||||||
|
"UYU",
|
||||||
|
"UZS",
|
||||||
|
"VEF",
|
||||||
|
"VES",
|
||||||
|
"VND",
|
||||||
|
"VUV",
|
||||||
|
"WST",
|
||||||
|
"XAF",
|
||||||
|
"XAG",
|
||||||
|
"XAU",
|
||||||
|
"XCD",
|
||||||
|
"XDR",
|
||||||
|
"XOF",
|
||||||
|
"XPD",
|
||||||
|
"XPF",
|
||||||
|
"XPT",
|
||||||
|
"YER",
|
||||||
|
"ZAR",
|
||||||
|
"ZMW",
|
||||||
|
"ZWL",
|
||||||
]
|
]
|
||||||
rates.sort(key=lambda rate: "" if rate == self.no_currency else rate)
|
if with_no_currency:
|
||||||
return rates
|
currencies.append(self.no_currency)
|
||||||
|
return currencies
|
||||||
|
|
||||||
def exchange_currency(self, amount, source_currency, dest_currency):
|
def exchange_currency(self, amount, source_currency, dest_currency):
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -67,6 +67,9 @@ def get_billform_for(project, set_default=True, **kwargs):
|
||||||
if form.original_currency.data is None:
|
if form.original_currency.data is None:
|
||||||
form.original_currency.data = project.default_currency
|
form.original_currency.data = project.default_currency
|
||||||
|
|
||||||
|
# Used in validate_original_currency
|
||||||
|
form.project_currency = project.default_currency
|
||||||
|
|
||||||
show_no_currency = form.original_currency.data == CurrencyConverter.no_currency
|
show_no_currency = form.original_currency.data == CurrencyConverter.no_currency
|
||||||
|
|
||||||
form.original_currency.choices = [
|
form.original_currency.choices = [
|
||||||
|
@ -185,12 +188,20 @@ class EditProjectForm(FlaskForm):
|
||||||
and field.data == CurrencyConverter.no_currency
|
and field.data == CurrencyConverter.no_currency
|
||||||
and project.has_multiple_currencies()
|
and project.has_multiple_currencies()
|
||||||
):
|
):
|
||||||
raise ValidationError(
|
msg = _(
|
||||||
_(
|
|
||||||
"This project cannot be set to 'no currency'"
|
"This project cannot be set to 'no currency'"
|
||||||
" because it contains bills in multiple currencies."
|
" because it contains bills in multiple currencies."
|
||||||
)
|
)
|
||||||
|
raise ValidationError(msg)
|
||||||
|
if (
|
||||||
|
project is not None
|
||||||
|
and field.data != CurrencyConverter.no_currency
|
||||||
|
and project.has_bills()
|
||||||
|
):
|
||||||
|
msg = _(
|
||||||
|
"Cannot change project currency because currency conversion is broken"
|
||||||
)
|
)
|
||||||
|
raise ValidationError(msg)
|
||||||
|
|
||||||
def update(self, project):
|
def update(self, project):
|
||||||
"""Update the project with the information from the form"""
|
"""Update the project with the information from the form"""
|
||||||
|
@ -406,6 +417,17 @@ class BillForm(FlaskForm):
|
||||||
# See https://github.com/python-babel/babel/issues/821
|
# See https://github.com/python-babel/babel/issues/821
|
||||||
raise ValidationError(f"Result is too high: {field.data}")
|
raise ValidationError(f"Result is too high: {field.data}")
|
||||||
|
|
||||||
|
def validate_original_currency(self, field):
|
||||||
|
# Workaround for currency API breakage
|
||||||
|
# See #1232
|
||||||
|
if field.data not in [CurrencyConverter.no_currency, self.project_currency]:
|
||||||
|
msg = _(
|
||||||
|
"Failed to convert from %(bill_currency)s currency to %(project_currency)s",
|
||||||
|
bill_currency=field.data,
|
||||||
|
project_currency=self.project_currency,
|
||||||
|
)
|
||||||
|
raise ValidationError(msg)
|
||||||
|
|
||||||
|
|
||||||
class MemberForm(FlaskForm):
|
class MemberForm(FlaskForm):
|
||||||
name = StringField(_("Name"), validators=[DataRequired()], filters=[strip_filter])
|
name = StringField(_("Name"), validators=[DataRequired()], filters=[strip_filter])
|
||||||
|
|
|
@ -2,6 +2,8 @@ import base64
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from ihatemoney.tests.common.help_functions import em_surround
|
from ihatemoney.tests.common.help_functions import em_surround
|
||||||
from ihatemoney.tests.common.ihatemoney_testcase import IhatemoneyTestCase
|
from ihatemoney.tests.common.ihatemoney_testcase import IhatemoneyTestCase
|
||||||
|
|
||||||
|
@ -615,6 +617,7 @@ class TestAPI(IhatemoneyTestCase):
|
||||||
)
|
)
|
||||||
self.assertStatus(400, req)
|
self.assertStatus(400, req)
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Currency conversion is broken")
|
||||||
def test_currencies(self):
|
def test_currencies(self):
|
||||||
# check /currencies for list of supported currencies
|
# check /currencies for list of supported currencies
|
||||||
resp = self.client.get("/api/currencies")
|
resp = self.client.get("/api/currencies")
|
||||||
|
|
|
@ -1403,6 +1403,7 @@ class TestBudget(IhatemoneyTestCase):
|
||||||
member = models.Person.query.filter(models.Person.id == 1).one_or_none()
|
member = models.Person.query.filter(models.Person.id == 1).one_or_none()
|
||||||
assert member is None
|
assert member is None
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Currency conversion is broken")
|
||||||
def test_currency_switch(self):
|
def test_currency_switch(self):
|
||||||
# A project should be editable
|
# A project should be editable
|
||||||
self.post_project("raclette")
|
self.post_project("raclette")
|
||||||
|
@ -1529,6 +1530,7 @@ class TestBudget(IhatemoneyTestCase):
|
||||||
assert '<p class="alert alert-danger">' in resp.data.decode("utf-8")
|
assert '<p class="alert alert-danger">' in resp.data.decode("utf-8")
|
||||||
assert self.get_project("raclette").default_currency == "USD"
|
assert self.get_project("raclette").default_currency == "USD"
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Currency conversion is broken")
|
||||||
def test_currency_switch_to_bill_currency(self):
|
def test_currency_switch_to_bill_currency(self):
|
||||||
# Default currency is 'XXX', but we should start from a project with a currency
|
# Default currency is 'XXX', but we should start from a project with a currency
|
||||||
self.post_project("raclette", default_currency="USD")
|
self.post_project("raclette", default_currency="USD")
|
||||||
|
@ -1563,6 +1565,7 @@ class TestBudget(IhatemoneyTestCase):
|
||||||
bill = project.get_bills().first()
|
bill = project.get_bills().first()
|
||||||
assert bill.converted_amount == bill.amount
|
assert bill.converted_amount == bill.amount
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Currency conversion is broken")
|
||||||
def test_currency_switch_to_no_currency(self):
|
def test_currency_switch_to_no_currency(self):
|
||||||
# Default currency is 'XXX', but we should start from a project with a currency
|
# Default currency is 'XXX', but we should start from a project with a currency
|
||||||
self.post_project("raclette", default_currency="USD")
|
self.post_project("raclette", default_currency="USD")
|
||||||
|
@ -1626,7 +1629,7 @@ class TestBudget(IhatemoneyTestCase):
|
||||||
"payer": 1,
|
"payer": 1,
|
||||||
"payed_for": [1],
|
"payed_for": [1],
|
||||||
"amount": "0",
|
"amount": "0",
|
||||||
"original_currency": "EUR",
|
"original_currency": "XXX",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1703,7 +1706,7 @@ class TestBudget(IhatemoneyTestCase):
|
||||||
Tests that the RSS feed output content is expected.
|
Tests that the RSS feed output content is expected.
|
||||||
"""
|
"""
|
||||||
with fake_time("2023-07-25 12:00:00"):
|
with fake_time("2023-07-25 12:00:00"):
|
||||||
self.post_project("raclette")
|
self.post_project("raclette", default_currency="EUR")
|
||||||
self.client.post("/raclette/members/add", data={"name": "george"})
|
self.client.post("/raclette/members/add", data={"name": "george"})
|
||||||
self.client.post("/raclette/members/add", data={"name": "peter"})
|
self.client.post("/raclette/members/add", data={"name": "peter"})
|
||||||
self.client.post("/raclette/members/add", data={"name": "steven"})
|
self.client.post("/raclette/members/add", data={"name": "steven"})
|
||||||
|
@ -1787,7 +1790,7 @@ class TestBudget(IhatemoneyTestCase):
|
||||||
history is disabled.
|
history is disabled.
|
||||||
"""
|
"""
|
||||||
with fake_time("2023-07-25 12:00:00"):
|
with fake_time("2023-07-25 12:00:00"):
|
||||||
self.post_project("raclette", project_history=False)
|
self.post_project("raclette", default_currency="EUR", project_history=False)
|
||||||
self.client.post("/raclette/members/add", data={"name": "george"})
|
self.client.post("/raclette/members/add", data={"name": "george"})
|
||||||
self.client.post("/raclette/members/add", data={"name": "peter"})
|
self.client.post("/raclette/members/add", data={"name": "peter"})
|
||||||
self.client.post("/raclette/members/add", data={"name": "steven"})
|
self.client.post("/raclette/members/add", data={"name": "steven"})
|
||||||
|
@ -1900,7 +1903,7 @@ class TestBudget(IhatemoneyTestCase):
|
||||||
"payer": 1,
|
"payer": 1,
|
||||||
"payed_for": [1],
|
"payed_for": [1],
|
||||||
"amount": "12",
|
"amount": "12",
|
||||||
"original_currency": "EUR",
|
"original_currency": "XXX",
|
||||||
},
|
},
|
||||||
follow_redirects=True,
|
follow_redirects=True,
|
||||||
)
|
)
|
||||||
|
@ -1961,7 +1964,7 @@ class TestBudget(IhatemoneyTestCase):
|
||||||
"payer": 1,
|
"payer": 1,
|
||||||
"payed_for": [1],
|
"payed_for": [1],
|
||||||
"amount": "12",
|
"amount": "12",
|
||||||
"original_currency": "EUR",
|
"original_currency": "XXX",
|
||||||
},
|
},
|
||||||
follow_redirects=True,
|
follow_redirects=True,
|
||||||
)
|
)
|
||||||
|
|
|
@ -466,6 +466,7 @@ class TestExport(IhatemoneyTestCase):
|
||||||
resp = self.client.get("/raclette/export/transactions.wrong")
|
resp = self.client.get("/raclette/export/transactions.wrong")
|
||||||
assert resp.status_code == 404
|
assert resp.status_code == 404
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Currency conversion is broken")
|
||||||
def test_export_with_currencies(self):
|
def test_export_with_currencies(self):
|
||||||
self.post_project("raclette", default_currency="EUR")
|
self.post_project("raclette", default_currency="EUR")
|
||||||
|
|
||||||
|
|
|
@ -385,9 +385,9 @@ class TestCurrencyConverter:
|
||||||
assert one == two
|
assert one == two
|
||||||
|
|
||||||
def test_get_currencies(self):
|
def test_get_currencies(self):
|
||||||
assert set(self.converter.get_currencies()) == set(
|
currencies = self.converter.get_currencies()
|
||||||
["USD", "EUR", "CAD", "PLN", CurrencyConverter.no_currency]
|
for currency in ["USD", "EUR", "CAD", "PLN", CurrencyConverter.no_currency]:
|
||||||
)
|
assert currency in currencies
|
||||||
|
|
||||||
def test_exchange_currency(self):
|
def test_exchange_currency(self):
|
||||||
result = self.converter.exchange_currency(100, "USD", "EUR")
|
result = self.converter.exchange_currency(100, "USD", "EUR")
|
||||||
|
|
Loading…
Reference in a new issue