diff --git a/ihatemoney/currency_convertor.py b/ihatemoney/currency_convertor.py index 1139a4455..ca0ff3669 100644 --- a/ihatemoney/currency_convertor.py +++ b/ihatemoney/currency_convertor.py @@ -36,13 +36,181 @@ def get_rates(self): return rates def get_currencies(self, with_no_currency=True): - rates = [ - rate - for rate in self.get_rates() - if with_no_currency or rate != self.no_currency + currencies = [ + "AED", + "AFN", + "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) - return rates + if with_no_currency: + currencies.append(self.no_currency) + return currencies def exchange_currency(self, amount, source_currency, dest_currency): if ( diff --git a/ihatemoney/forms.py b/ihatemoney/forms.py index 72f9d1222..c0f875074 100644 --- a/ihatemoney/forms.py +++ b/ihatemoney/forms.py @@ -67,6 +67,9 @@ def get_billform_for(project, set_default=True, **kwargs): if form.original_currency.data is None: 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 form.original_currency.choices = [ @@ -185,12 +188,20 @@ def validate_default_currency(self, field): and field.data == CurrencyConverter.no_currency and project.has_multiple_currencies() ): - raise ValidationError( - _( - "This project cannot be set to 'no currency'" - " because it contains bills in multiple currencies." - ) + msg = _( + "This project cannot be set to 'no currency'" + " 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): """Update the project with the information from the form""" @@ -406,6 +417,17 @@ def validate_amount(self, field): # See https://github.com/python-babel/babel/issues/821 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): name = StringField(_("Name"), validators=[DataRequired()], filters=[strip_filter]) diff --git a/ihatemoney/tests/api_test.py b/ihatemoney/tests/api_test.py index f4c133322..9d70e8e69 100644 --- a/ihatemoney/tests/api_test.py +++ b/ihatemoney/tests/api_test.py @@ -2,6 +2,8 @@ import datetime import json +import pytest + from ihatemoney.tests.common.help_functions import em_surround from ihatemoney.tests.common.ihatemoney_testcase import IhatemoneyTestCase @@ -615,6 +617,7 @@ def test_bills_with_calculation(self): ) self.assertStatus(400, req) + @pytest.mark.skip(reason="Currency conversion is broken") def test_currencies(self): # check /currencies for list of supported currencies resp = self.client.get("/api/currencies") diff --git a/ihatemoney/tests/budget_test.py b/ihatemoney/tests/budget_test.py index 1537233ff..b4ec2c248 100644 --- a/ihatemoney/tests/budget_test.py +++ b/ihatemoney/tests/budget_test.py @@ -1403,6 +1403,7 @@ def test_access_other_projects(self): member = models.Person.query.filter(models.Person.id == 1).one_or_none() assert member is None + @pytest.mark.skip(reason="Currency conversion is broken") def test_currency_switch(self): # A project should be editable self.post_project("raclette") @@ -1529,6 +1530,7 @@ def test_currency_switch(self): assert '
' in resp.data.decode("utf-8") assert self.get_project("raclette").default_currency == "USD" + @pytest.mark.skip(reason="Currency conversion is broken") def test_currency_switch_to_bill_currency(self): # Default currency is 'XXX', but we should start from a project with a currency self.post_project("raclette", default_currency="USD") @@ -1563,6 +1565,7 @@ def test_currency_switch_to_bill_currency(self): bill = project.get_bills().first() assert bill.converted_amount == bill.amount + @pytest.mark.skip(reason="Currency conversion is broken") def test_currency_switch_to_no_currency(self): # Default currency is 'XXX', but we should start from a project with a currency self.post_project("raclette", default_currency="USD") @@ -1626,7 +1629,7 @@ def test_amount_is_null(self): "payer": 1, "payed_for": [1], "amount": "0", - "original_currency": "EUR", + "original_currency": "XXX", }, ) @@ -1703,7 +1706,7 @@ def test_rss_feed(self): Tests that the RSS feed output content is expected. """ 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": "peter"}) self.client.post("/raclette/members/add", data={"name": "steven"}) @@ -1787,7 +1790,7 @@ def test_rss_feed_history_disabled(self): history is disabled. """ 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": "peter"}) self.client.post("/raclette/members/add", data={"name": "steven"}) @@ -1900,7 +1903,7 @@ def test_rss_if_modified_since_header(self): "payer": 1, "payed_for": [1], "amount": "12", - "original_currency": "EUR", + "original_currency": "XXX", }, follow_redirects=True, ) @@ -1961,7 +1964,7 @@ def test_rss_etag_headers(self): "payer": 1, "payed_for": [1], "amount": "12", - "original_currency": "EUR", + "original_currency": "XXX", }, follow_redirects=True, ) diff --git a/ihatemoney/tests/import_test.py b/ihatemoney/tests/import_test.py index 67709d8b3..463363c2d 100644 --- a/ihatemoney/tests/import_test.py +++ b/ihatemoney/tests/import_test.py @@ -466,6 +466,7 @@ def test_export(self): resp = self.client.get("/raclette/export/transactions.wrong") assert resp.status_code == 404 + @pytest.mark.skip(reason="Currency conversion is broken") def test_export_with_currencies(self): self.post_project("raclette", default_currency="EUR") diff --git a/ihatemoney/tests/main_test.py b/ihatemoney/tests/main_test.py index 70c119088..be0868913 100644 --- a/ihatemoney/tests/main_test.py +++ b/ihatemoney/tests/main_test.py @@ -385,9 +385,9 @@ def test_only_one_instance(self): assert one == two def test_get_currencies(self): - assert set(self.converter.get_currencies()) == set( - ["USD", "EUR", "CAD", "PLN", CurrencyConverter.no_currency] - ) + currencies = self.converter.get_currencies() + for currency in ["USD", "EUR", "CAD", "PLN", CurrencyConverter.no_currency]: + assert currency in currencies def test_exchange_currency(self): result = self.converter.exchange_currency(100, "USD", "EUR")