Repository: inventree/InvenTree

Issue:
[BUG] Exchange rate provider is closed now
### Please verify that this bug has NOT been raised before.

- [X] I checked and didn't find a similar issue

### Describe the bug*

Our current default exchange rate provider is closed now - and the change was not smooth either. See https://github.com/Formicka/exchangerate.host/issues/236#issuecomment-1738539199

We need to:
1) Disable the CI checks for now
2) Implement an alternative
3) (Optional) Bring down ApiLayer once and for all before they close all APIs we need

### Steps to Reproduce

Look at any current [CI](https://github.com/inventree/InvenTree/actions/runs/6333198194/job/17202067465?pr=5582)

### Expected behaviour

Updating exchange rates

### Deployment Method

- [X] Docker
- [X] Bare metal

### Version Information

Every version>0.6.0 and a couble before are probably effected.

### Please verify if you can reproduce this bug on the demo site.

- [X] I can reproduce this bug on the demo site.

### Relevant log output

_No response_


Discussion:

Resolving patch:
diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py
--- a/InvenTree/InvenTree/exchange.py
+++ b/InvenTree/InvenTree/exchange.py
@@ -1,12 +1,11 @@
-"""Exchangerate backend to use `exchangerate.host` to get rates."""
+"""Exchangerate backend to use `frankfurter.app` to get rates."""
 
-import ssl
+from decimal import Decimal
 from urllib.error import URLError
-from urllib.request import urlopen
 
 from django.db.utils import OperationalError
 
-import certifi
+import requests
 from djmoney.contrib.exchange.backends.base import SimpleExchangeBackend
 
 from common.settings import currency_code_default, currency_codes
@@ -15,19 +14,19 @@
 class InvenTreeExchange(SimpleExchangeBackend):
     """Backend for automatically updating currency exchange rates.
 
-    Uses the `exchangerate.host` service API
+    Uses the `frankfurter.app` service API
     """
 
     name = "InvenTreeExchange"
 
     def __init__(self):
         """Set API url."""
-        self.url = "https://api.exchangerate.host/latest"
+        self.url = "https://api.frankfurter.app/latest"
 
         super().__init__()
 
     def get_params(self):
-        """Placeholder to set API key. Currently not required by `exchangerate.host`."""
+        """Placeholder to set API key. Currently not required by `frankfurter.app`."""
         # No API key is required
         return {
         }
@@ -40,14 +39,22 @@ def get_response(self, **kwargs):
         url = self.get_url(**kwargs)
 
         try:
-            context = ssl.create_default_context(cafile=certifi.where())
-            response = urlopen(url, timeout=5, context=context)
-            return response.read()
+            response = requests.get(url=url, timeout=5)
+            return response.content
         except Exception:
             # Something has gone wrong, but we can just try again next time
             # Raise a TypeError so the outer function can handle this
             raise TypeError
 
+    def get_rates(self, **params):
+        """Intersect the requested currency codes with the available codes."""
+        rates = super().get_rates(**params)
+
+        # Add the base currency to the rates
+        rates[params["base_currency"]] = Decimal("1.0")
+
+        return rates
+
     def update_rates(self, base_currency=None):
         """Set the requested currency codes and get rates."""
         # Set default - see B008


Test patch:
diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py
--- a/InvenTree/InvenTree/tests.py
+++ b/InvenTree/InvenTree/tests.py
@@ -807,10 +807,6 @@ class CurrencyTests(TestCase):
     def test_rates(self):
         """Test exchange rate update."""
 
-        # 2023-09-28 check DISABLED due to https://github.com/inventree/InvenTree/issues/5631
-        # TODO re-enable after #5631 is solved
-        return True
-
         # Initially, there will not be any exchange rate information
         rates = Rate.objects.all()
 
@@ -837,6 +833,7 @@ def test_rates(self):
 
             else:  # pragma: no cover
                 print("Exchange rate update failed - retrying")
+                print(f'Expected {currency_codes()}, got {[a.currency for a in rates]}')
                 time.sleep(1)
 
         self.assertTrue(update_successful)
diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py
--- a/InvenTree/common/tests.py
+++ b/InvenTree/common/tests.py
@@ -1026,10 +1026,6 @@ def test_exchange_endpoint(self):
     def test_refresh_endpoint(self):
         """Call the 'refresh currencies' endpoint"""
 
-        # 2023-09-28 check DISABLED due to https://github.com/inventree/InvenTree/issues/5631
-        # TODO re-enable after #5631 is solved
-        return True
-
         from djmoney.contrib.exchange.models import Rate
 
         # Delete any existing exchange rate data