From 34ded08ee717a961d5d7eaf70590f415416ca4a6 Mon Sep 17 00:00:00 2001 From: eeintech Date: Wed, 19 May 2021 16:17:03 -0400 Subject: [PATCH 01/14] Added InvenTreeFixerExchangeBackend class --- InvenTree/InvenTree/exchange.py | 44 +++++++++++++++++++++++++++++++++ InvenTree/InvenTree/settings.py | 2 +- InvenTree/InvenTree/tasks.py | 26 ++++++++----------- InvenTree/config_template.yaml | 7 ++++++ 4 files changed, 62 insertions(+), 17 deletions(-) diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py index 06de4861ec..59d2883e2a 100644 --- a/InvenTree/InvenTree/exchange.py +++ b/InvenTree/InvenTree/exchange.py @@ -1,4 +1,10 @@ +from django.conf import settings as inventree_settings + +from djmoney import settings as djmoney_settings from djmoney.contrib.exchange.backends.base import BaseExchangeBackend +from djmoney.contrib.exchange.backends import FixerBackend + +from common.models import InvenTreeSetting class InvenTreeManualExchangeBackend(BaseExchangeBackend): @@ -19,3 +25,41 @@ class InvenTreeManualExchangeBackend(BaseExchangeBackend): """ return {} + + +class InvenTreeFixerExchangeBackend(FixerBackend): + """ + Backend for updating currency exchange rates using Fixer.IO API + """ + + def get_api_key(self): + """ Get API key from global settings """ + + fixer_api_key = InvenTreeSetting.get_setting('INVENTREE_FIXER_API_KEY', '').strip() + + if not fixer_api_key: + # API key not provided + return None + + return fixer_api_key + + def __init__(self): + """ Override FixerBackend init to get access_key from global settings """ + + fixer_api_key = self.get_api_key() + + super().__init__(url=djmoney_settings.FIXER_URL, access_key=fixer_api_key) + + def update_rates(self): + """ Override update_rates method using currencies found in the settings """ + + currencies = ','.join(inventree_settings.CURRENCIES) + + base = inventree_settings.BASE_CURRENCY + + super().update_rates(base_currency=base, symbols=currencies) + + def get_rates(self, **kwargs): + """ Returns a mapping : """ + + return {} diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 5cf0b0c544..e99c3a3eea 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -515,7 +515,7 @@ CURRENCIES = CONFIG.get( BASE_CURRENCY = CONFIG.get('base_currency', 'USD') -EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeManualExchangeBackend' +EXCHANGE_BACKEND = 'InvenTree.exchange.' + CONFIG.get('exchange_backend', 'InvenTreeManualExchangeBackend') # Extract email settings from the config file email_config = CONFIG.get('email', {}) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index b81b4e6de4..0076bb9544 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -163,30 +163,24 @@ def check_for_updates(): def update_exchange_rates(): """ - If an API key for fixer.io has been provided, attempt to update currency exchange rates + Update backend rates """ try: - import common.models + from .exchange import InvenTreeManualExchangeBackend, InvenTreeFixerExchangeBackend from django.conf import settings - from djmoney.contrib.exchange.backends import FixerBackend except AppRegistryNotReady: # Apps not yet loaded! return + + # Get backend + if 'InvenTreeManualExchangeBackend' in settings.EXCHANGE_BACKEND: + backend = InvenTreeFixerExchangeBackend() + else: + backend = InvenTreeManualExchangeBackend() - fixer_api_key = common.models.InvenTreeSetting.get_setting('INVENTREE_FIXER_API_KEY', '').strip() - - if not fixer_api_key: - # API key not provided - return - - backend = FixerBackend(access_key=fixer_api_key) - - currencies = ','.join(settings.CURRENCIES) - - base = settings.BASE_CURRENCY - - backend.update_rates(base_currency=base, symbols=currencies) + # Update rates + backend.update_rates() def send_email(subject, body, recipients, from_email=None): diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml index 87dfb6b545..63525b0d79 100644 --- a/InvenTree/config_template.yaml +++ b/InvenTree/config_template.yaml @@ -62,6 +62,13 @@ currencies: - JPY - NZD - USD +# Define base currency (can also be defined in the global settings) +# base_currency: USD +# Define exchange backend +# Choices are: +# - InvenTreeManualExchangeBackend +# - InvenTreeFixerExchangeBackend +exchange_backend: InvenTreeFixerExchangeBackend # Email backend configuration # Ref: https://docs.djangoproject.com/en/dev/topics/email/ From 6d5b2d3227f1bf0a92971e473ab226374e8359a3 Mon Sep 17 00:00:00 2001 From: eeintech Date: Wed, 19 May 2021 17:06:41 -0400 Subject: [PATCH 02/14] Added 'Currencies' settings view in global settings --- InvenTree/InvenTree/tasks.py | 4 +- InvenTree/InvenTree/urls.py | 3 ++ InvenTree/InvenTree/views.py | 15 +++++++ .../InvenTree/settings/currencies.html | 43 +++++++++++++++++++ .../templates/InvenTree/settings/global.html | 2 - .../templates/InvenTree/settings/tabs.html | 3 ++ 6 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 InvenTree/templates/InvenTree/settings/currencies.html diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 0076bb9544..fb6a45f368 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -175,9 +175,9 @@ def update_exchange_rates(): # Get backend if 'InvenTreeManualExchangeBackend' in settings.EXCHANGE_BACKEND: - backend = InvenTreeFixerExchangeBackend() - else: backend = InvenTreeManualExchangeBackend() + else: + backend = InvenTreeFixerExchangeBackend() # Update rates backend.update_rates() diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index ab2ced7d5e..d3132ca2a8 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -41,6 +41,7 @@ from .views import IndexView, SearchView, DatabaseStatsView from .views import SettingsView, EditUserView, SetPasswordView from .views import AppearanceSelectView, SettingCategorySelectView from .views import DynamicJsView +from .views import ExchangeRatesView from common.views import SettingEdit @@ -90,6 +91,8 @@ settings_urls = [ url(r'^build/?', SettingsView.as_view(template_name='InvenTree/settings/build.html'), name='settings-build'), url(r'^purchase-order/?', SettingsView.as_view(template_name='InvenTree/settings/po.html'), name='settings-po'), url(r'^sales-order/?', SettingsView.as_view(template_name='InvenTree/settings/so.html'), name='settings-so'), + url(r'^currencies/?', SettingsView.as_view(template_name='InvenTree/settings/currencies.html'), name='settings-currencies'), + url(r'^echange-rates/?', ExchangeRatesView.as_view(), name='refresh-exchange-rates'), url(r'^(?P\d+)/edit/', SettingEdit.as_view(), name='setting-edit'), diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index d285efae36..bd03a18dda 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -27,6 +27,7 @@ from users.models import check_user_role, RuleSet from .forms import DeleteForm, EditUserForm, SetPasswordForm from .forms import ColorThemeSelectForm, SettingCategorySelectForm from .helpers import str2bool +from .tasks import update_exchange_rates from rest_framework import views @@ -908,3 +909,17 @@ class DatabaseStatsView(AjaxView): """ return ctx + + +class ExchangeRatesView(SettingsView): + + success_url = reverse_lazy('settings-currencies') + + def post(self, request, *args, **kwargs): + + # Process exchange rates + update_exchange_rates() + + # TODO: Update context + + return HttpResponseRedirect(self.success_url) diff --git a/InvenTree/templates/InvenTree/settings/currencies.html b/InvenTree/templates/InvenTree/settings/currencies.html new file mode 100644 index 0000000000..9ec5b518fb --- /dev/null +++ b/InvenTree/templates/InvenTree/settings/currencies.html @@ -0,0 +1,43 @@ +{% extends "InvenTree/settings/settings.html" %} +{% load i18n %} +{% load inventree_extras %} + +{% block tabs %} +{% include "InvenTree/settings/tabs.html" with tab='currencies' %} +{% endblock %} + +{% block subtitle %} +{% trans "Currency Settings" %} +{% endblock %} + +{% block settings %} + + + {% include "InvenTree/settings/header.html" %} + + {% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-dollar-sign" %} + {% include "InvenTree/settings/setting.html" with key="INVENTREE_FIXER_API_KEY" icon="fa-key" %} + +
+ +
+
+

{% trans "Exchange Rates" %}

+
+
+ + +
+ +
+ {% csrf_token %} + +
+ +{% endblock %} + +{% block js_ready %} +{{ block.super }} +{% comment %} TODO: Update exchange-rates table! {% endcomment %} +{% comment %} Or do it using context instead of JS? {% endcomment %} +{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/settings/global.html b/InvenTree/templates/InvenTree/settings/global.html index 5c5dccfb2a..fd91dafc67 100644 --- a/InvenTree/templates/InvenTree/settings/global.html +++ b/InvenTree/templates/InvenTree/settings/global.html @@ -19,8 +19,6 @@ {% include "InvenTree/settings/setting.html" with key="INVENTREE_INSTANCE_TITLE" icon="fa-info-circle" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_BASE_URL" icon="fa-globe" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_COMPANY_NAME" icon="fa-building" %} - {% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-dollar-sign" %} - {% include "InvenTree/settings/setting.html" with key="INVENTREE_FIXER_API_KEY" icon="fa-key" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_DOWNLOAD_FROM_URL" icon="fa-cloud-download-alt" %} diff --git a/InvenTree/templates/InvenTree/settings/tabs.html b/InvenTree/templates/InvenTree/settings/tabs.html index 3f8be0a313..360618fc34 100644 --- a/InvenTree/templates/InvenTree/settings/tabs.html +++ b/InvenTree/templates/InvenTree/settings/tabs.html @@ -36,5 +36,8 @@
  • {% trans "Sales Orders" %}
  • +
  • + {% trans "Currencies" %} +
  • {% endif %} From bed6a7e49c11f2cbb8ad4eb12ef1e6a16f16e616 Mon Sep 17 00:00:00 2001 From: eeintech Date: Thu, 20 May 2021 09:49:56 -0400 Subject: [PATCH 03/14] Added exchange rates form --- InvenTree/InvenTree/exchange.py | 61 +++++++++++++------ InvenTree/InvenTree/forms.py | 36 ++++++++++- InvenTree/InvenTree/tasks.py | 10 +-- InvenTree/InvenTree/urls.py | 5 +- InvenTree/InvenTree/views.py | 37 ++++++++++- .../InvenTree/settings/currencies.html | 19 +++--- 6 files changed, 125 insertions(+), 43 deletions(-) diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py index 59d2883e2a..03891828a0 100644 --- a/InvenTree/InvenTree/exchange.py +++ b/InvenTree/InvenTree/exchange.py @@ -1,12 +1,21 @@ +from django.core.exceptions import ImproperlyConfigured from django.conf import settings as inventree_settings from djmoney import settings as djmoney_settings from djmoney.contrib.exchange.backends.base import BaseExchangeBackend -from djmoney.contrib.exchange.backends import FixerBackend from common.models import InvenTreeSetting +def get_exchange_rate_backend(): + """ Return the exchange rate backend set by user """ + + if 'InvenTreeManualExchangeBackend' in inventree_settings.EXCHANGE_BACKEND: + return InvenTreeManualExchangeBackend() + else: + return InvenTreeFixerExchangeBackend() + + class InvenTreeManualExchangeBackend(BaseExchangeBackend): """ Backend for manually updating currency exchange rates @@ -16,22 +25,39 @@ class InvenTreeManualExchangeBackend(BaseExchangeBackend): Specifically: https://github.com/django-money/django-money/tree/master/djmoney/contrib/exchange/backends """ - name = "inventree" + name = 'inventree' url = None + default_currency = None + currencies = [] + + def update_default_currency(self): + + self.default_currency = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY', inventree_settings.BASE_CURRENCY) + + def __init__(self, url=None): + + self.url = url + + self.update_default_currency() + + self.currencies = inventree_settings.CURRENCIES + + super().__init__() def get_rates(self, **kwargs): - """ - Do not get any rates... - """ + """ Returns a mapping : """ return {} -class InvenTreeFixerExchangeBackend(FixerBackend): +class InvenTreeFixerExchangeBackend(InvenTreeManualExchangeBackend): """ Backend for updating currency exchange rates using Fixer.IO API """ + name = 'fixer.io' + access_key = None + def get_api_key(self): """ Get API key from global settings """ @@ -48,18 +74,17 @@ class InvenTreeFixerExchangeBackend(FixerBackend): fixer_api_key = self.get_api_key() - super().__init__(url=djmoney_settings.FIXER_URL, access_key=fixer_api_key) + if fixer_api_key is None: + raise ImproperlyConfigured("fixer.io API key is needed to use InvenTreeFixerExchangeBackend") + + self.access_key = fixer_api_key + + super().__init__(url=djmoney_settings.FIXER_URL) def update_rates(self): - """ Override update_rates method using currencies found in the settings """ + """ Override update_rates method using currencies found in the settings + """ + + symbols = ','.join(self.currencies) - currencies = ','.join(inventree_settings.CURRENCIES) - - base = inventree_settings.BASE_CURRENCY - - super().update_rates(base_currency=base, symbols=currencies) - - def get_rates(self, **kwargs): - """ Returns a mapping : """ - - return {} + super().update_rates(base_currency=self.base_currency, symbols=symbols) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 52d1c8758f..a744671afb 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -7,12 +7,15 @@ from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ from django import forms +from django.contrib.auth.models import User + from crispy_forms.helper import FormHelper from crispy_forms.layout import Layout, Field from crispy_forms.bootstrap import PrependedText, AppendedText, PrependedAppendedText, StrictButton, Div -from django.contrib.auth.models import User + from common.models import ColorTheme from part.models import PartCategory +from .exchange import InvenTreeManualExchangeBackend class HelperForm(forms.ModelForm): @@ -236,3 +239,34 @@ class SettingCategorySelectForm(forms.ModelForm): css_class='row', ), ) + + +class SettingExchangeRatesForm(forms.Form): + """ Form for displaying and setting currency exchange rates manually """ + + def __init__(self, *args, **kwargs): + + super().__init__(*args, **kwargs) + + exchange_rate_backend = InvenTreeManualExchangeBackend() + + # Update default currency (in case it has changed) + exchange_rate_backend.update_default_currency() + + for currency in exchange_rate_backend.currencies: + if currency != exchange_rate_backend.default_currency: + # Set field name + field_name = currency + # Set field input box + self.fields[field_name] = forms.CharField( + label=field_name, + required=False, + widget=forms.NumberInput(attrs={ + 'name': field_name, + 'class': 'numberinput', + 'type': 'number', + 'min': '0', + 'step': 'any', + 'value': '', + }) + ) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index fb6a45f368..0468365dae 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -167,17 +167,13 @@ def update_exchange_rates(): """ try: - from .exchange import InvenTreeManualExchangeBackend, InvenTreeFixerExchangeBackend - from django.conf import settings + from .exchange import get_exchange_rate_backend except AppRegistryNotReady: # Apps not yet loaded! return - # Get backend - if 'InvenTreeManualExchangeBackend' in settings.EXCHANGE_BACKEND: - backend = InvenTreeManualExchangeBackend() - else: - backend = InvenTreeFixerExchangeBackend() + # Get exchange rate backend + backend = get_exchange_rate_backend() # Update rates backend.update_rates() diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index d3132ca2a8..d297dc18ad 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -41,7 +41,7 @@ from .views import IndexView, SearchView, DatabaseStatsView from .views import SettingsView, EditUserView, SetPasswordView from .views import AppearanceSelectView, SettingCategorySelectView from .views import DynamicJsView -from .views import ExchangeRatesView +from .views import CurrencySettingsView from common.views import SettingEdit @@ -91,8 +91,7 @@ settings_urls = [ url(r'^build/?', SettingsView.as_view(template_name='InvenTree/settings/build.html'), name='settings-build'), url(r'^purchase-order/?', SettingsView.as_view(template_name='InvenTree/settings/po.html'), name='settings-po'), url(r'^sales-order/?', SettingsView.as_view(template_name='InvenTree/settings/so.html'), name='settings-so'), - url(r'^currencies/?', SettingsView.as_view(template_name='InvenTree/settings/currencies.html'), name='settings-currencies'), - url(r'^echange-rates/?', ExchangeRatesView.as_view(), name='refresh-exchange-rates'), + url(r'^currencies/?', CurrencySettingsView.as_view(), name='settings-currencies'), url(r'^(?P\d+)/edit/', SettingEdit.as_view(), name='setting-edit'), diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index bd03a18dda..50f4a095e8 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -26,8 +26,9 @@ from users.models import check_user_role, RuleSet from .forms import DeleteForm, EditUserForm, SetPasswordForm from .forms import ColorThemeSelectForm, SettingCategorySelectForm +from .forms import SettingExchangeRatesForm from .helpers import str2bool -from .tasks import update_exchange_rates +from .exchange import get_exchange_rate_backend from rest_framework import views @@ -911,14 +912,44 @@ class DatabaseStatsView(AjaxView): return ctx -class ExchangeRatesView(SettingsView): +class CurrencySettingsView(FormView): + form_class = SettingExchangeRatesForm + template_name = 'InvenTree/settings/currencies.html' success_url = reverse_lazy('settings-currencies') + def get_context_data(self, **kwargs): + + context = super().get_context_data(**kwargs) + + # Get exchange rate backend + exchange_rate_backend = get_exchange_rate_backend() + + context['exchange_backend'] = exchange_rate_backend.name + + return context + + def get_form(self): + + form = super().get_form() + + # Get exchange rate backend + exchange_rate_backend = get_exchange_rate_backend() + + if exchange_rate_backend.name == 'fixer.io': + # Disable all the fields + for field in form.fields: + form.fields[field].disabled = True + + return form + def post(self, request, *args, **kwargs): + # Get exchange rate backend + exchange_rate_backend = get_exchange_rate_backend() + # Process exchange rates - update_exchange_rates() + exchange_rate_backend.update_rates() # TODO: Update context diff --git a/InvenTree/templates/InvenTree/settings/currencies.html b/InvenTree/templates/InvenTree/settings/currencies.html index 9ec5b518fb..52d6558bad 100644 --- a/InvenTree/templates/InvenTree/settings/currencies.html +++ b/InvenTree/templates/InvenTree/settings/currencies.html @@ -26,18 +26,15 @@ - -
    - -
    + {% csrf_token %} - + {% load crispy_forms_tags %} + {% crispy form %} + {% if exchange_backend == 'fixer.io' %} + + {% else %} + + {% endif %}
    {% endblock %} - -{% block js_ready %} -{{ block.super }} -{% comment %} TODO: Update exchange-rates table! {% endcomment %} -{% comment %} Or do it using context instead of JS? {% endcomment %} -{% endblock %} \ No newline at end of file From 747b0554e1f92323a3da20e82a3415cafc74a639 Mon Sep 17 00:00:00 2001 From: eeintech Date: Thu, 20 May 2021 13:45:26 -0400 Subject: [PATCH 04/14] Ready for review --- InvenTree/InvenTree/exchange.py | 75 +++++++++++++++---- InvenTree/InvenTree/forms.py | 6 +- InvenTree/InvenTree/helpers.py | 18 ++++- InvenTree/InvenTree/views.py | 61 ++++++++++++--- InvenTree/common/forms.py | 22 +----- .../InvenTree/settings/currencies.html | 14 +++- 6 files changed, 148 insertions(+), 48 deletions(-) diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py index 03891828a0..ce1db06711 100644 --- a/InvenTree/InvenTree/exchange.py +++ b/InvenTree/InvenTree/exchange.py @@ -3,6 +3,7 @@ from django.conf import settings as inventree_settings from djmoney import settings as djmoney_settings from djmoney.contrib.exchange.backends.base import BaseExchangeBackend +from djmoney.contrib.exchange.models import Rate from common.models import InvenTreeSetting @@ -27,19 +28,24 @@ class InvenTreeManualExchangeBackend(BaseExchangeBackend): name = 'inventree' url = None - default_currency = None + base_currency = None currencies = [] def update_default_currency(self): + """ Update to base currency """ - self.default_currency = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY', inventree_settings.BASE_CURRENCY) + self.base_currency = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY', inventree_settings.BASE_CURRENCY) def __init__(self, url=None): + """ Overrides init to update url, base currency and currencies """ self.url = url self.update_default_currency() + # Update name + self.name = self.name + '-' + self.base_currency.lower() + self.currencies = inventree_settings.CURRENCIES super().__init__() @@ -47,7 +53,22 @@ class InvenTreeManualExchangeBackend(BaseExchangeBackend): def get_rates(self, **kwargs): """ Returns a mapping : """ - return {} + return kwargs.get('rates', {}) + + def get_stored_rates(self): + """ Returns stored rate for specified backend and base currency """ + + stored_rates = {} + + stored_rates_obj = Rate.objects.all().prefetch_related('backend') + + for rate in stored_rates_obj: + # Find match for backend and base currency + if rate.backend.name == self.name and rate.backend.base_currency == self.base_currency: + # print(f'{rate.currency} | {rate.value} | {rate.backend} | {rate.backend.base_currency}') + stored_rates[rate.currency] = rate.value + + return stored_rates class InvenTreeFixerExchangeBackend(InvenTreeManualExchangeBackend): @@ -55,7 +76,7 @@ class InvenTreeFixerExchangeBackend(InvenTreeManualExchangeBackend): Backend for updating currency exchange rates using Fixer.IO API """ - name = 'fixer.io' + name = 'fixer' access_key = None def get_api_key(self): @@ -67,24 +88,48 @@ class InvenTreeFixerExchangeBackend(InvenTreeManualExchangeBackend): # API key not provided return None - return fixer_api_key - - def __init__(self): - """ Override FixerBackend init to get access_key from global settings """ - - fixer_api_key = self.get_api_key() - - if fixer_api_key is None: - raise ImproperlyConfigured("fixer.io API key is needed to use InvenTreeFixerExchangeBackend") - self.access_key = fixer_api_key + def __init__(self): + """ Override init to get access_key from global settings """ + + self.get_api_key() + + if self.access_key is None: + raise ImproperlyConfigured("fixer.io API key is needed to use InvenTreeFixerExchangeBackend") + super().__init__(url=djmoney_settings.FIXER_URL) - def update_rates(self): + def get_params(self): + """ Returns parameters (access key) """ + + return {"access_key": self.access_key} + + def update_rates(self, base_currency=None): """ Override update_rates method using currencies found in the settings """ + + if base_currency: + self.base_currency = base_currency + else: + self.update_default_currency() symbols = ','.join(self.currencies) super().update_rates(base_currency=self.base_currency, symbols=symbols) + + def get_rates(self, **params): + """ Returns a mapping : """ + + # Set base currency + params.update(base=self.base_currency) + + response = self.get_response(**params) + + try: + return self.parse_json(response)['rates'] + except KeyError: + # API response did not contain any rate + pass + + return {} diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index a744671afb..488e982ddc 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -15,6 +15,7 @@ from crispy_forms.bootstrap import PrependedText, AppendedText, PrependedAppende from common.models import ColorTheme from part.models import PartCategory + from .exchange import InvenTreeManualExchangeBackend @@ -254,7 +255,7 @@ class SettingExchangeRatesForm(forms.Form): exchange_rate_backend.update_default_currency() for currency in exchange_rate_backend.currencies: - if currency != exchange_rate_backend.default_currency: + if currency != exchange_rate_backend.base_currency: # Set field name field_name = currency # Set field input box @@ -264,9 +265,10 @@ class SettingExchangeRatesForm(forms.Form): widget=forms.NumberInput(attrs={ 'name': field_name, 'class': 'numberinput', + 'style': 'width: 200px;', 'type': 'number', 'min': '0', 'step': 'any', - 'value': '', + 'value': 0, }) ) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 1097c5663b..9d00697230 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -8,7 +8,7 @@ import json import os.path from PIL import Image -from decimal import Decimal +from decimal import Decimal, InvalidOperation from wsgiref.util import FileWrapper from django.http import StreamingHttpResponse @@ -606,3 +606,19 @@ def getNewestMigrationFile(app, exclude_extension=True): newest_file = newest_file.replace('.py', '') return newest_file + + +def clean_decimal(number): + """ Clean-up decimal value """ + + # Check if empty + if number is None or number == '': + return Decimal(0) + + # Check if decimal type + try: + clean_number = Decimal(number) + except InvalidOperation: + clean_number = number + + return clean_number.quantize(Decimal(1)) if clean_number == clean_number.to_integral() else clean_number.normalize() diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 50f4a095e8..48e42dd890 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -23,6 +23,7 @@ from part.models import Part, PartCategory from stock.models import StockLocation, StockItem from common.models import InvenTreeSetting, ColorTheme from users.models import check_user_role, RuleSet +from InvenTree.helpers import clean_decimal from .forms import DeleteForm, EditUserForm, SetPasswordForm from .forms import ColorThemeSelectForm, SettingCategorySelectForm @@ -918,12 +919,30 @@ class CurrencySettingsView(FormView): template_name = 'InvenTree/settings/currencies.html' success_url = reverse_lazy('settings-currencies') + exchange_rate_backend = None + + def get_exchange_rate_backend(self): + + if not self.exchange_rate_backend: + self.exchange_rate_backend = get_exchange_rate_backend() + + return self.exchange_rate_backend + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + # Set default API result + if 'api_rates_success' not in context: + context['default_currency'] = True + else: + # Update form + context['form'] = self.get_form() + # Get exchange rate backend - exchange_rate_backend = get_exchange_rate_backend() + exchange_rate_backend = self.get_exchange_rate_backend() + + context['default_currency'] = exchange_rate_backend.base_currency context['exchange_backend'] = exchange_rate_backend.name @@ -934,23 +953,45 @@ class CurrencySettingsView(FormView): form = super().get_form() # Get exchange rate backend - exchange_rate_backend = get_exchange_rate_backend() + exchange_rate_backend = self.get_exchange_rate_backend() - if exchange_rate_backend.name == 'fixer.io': - # Disable all the fields - for field in form.fields: + # Get stored exchange rates + stored_rates = exchange_rate_backend.get_stored_rates() + + for field in form.fields: + if 'fixer' in exchange_rate_backend.name: + # Disable all the fields form.fields[field].disabled = True + form.fields[field].initial = clean_decimal(stored_rates.get(field, 0)) return form def post(self, request, *args, **kwargs): + form = self.get_form() + # Get exchange rate backend - exchange_rate_backend = get_exchange_rate_backend() + exchange_rate_backend = self.get_exchange_rate_backend() - # Process exchange rates - exchange_rate_backend.update_rates() + if 'fixer' in exchange_rate_backend.name: + # Refresh rate from Fixer.IO API + exchange_rate_backend.update_rates(base_currency=exchange_rate_backend.base_currency) + # Check if rates have been updated + if not exchange_rate_backend.get_stored_rates(): + # Update context + context = {'api_rates_success': False} + # Return view with updated context + return self.render_to_response(self.get_context_data(form=form, **context)) + else: + # Update rates from form + manual_rates = {} - # TODO: Update context + if form.is_valid(): + for field, value in form.cleaned_data.items(): + manual_rates[field] = clean_decimal(value) - return HttpResponseRedirect(self.success_url) + exchange_rate_backend.update_rates(base_currency=exchange_rate_backend.base_currency, **{'rates': manual_rates}) + else: + return self.form_invalid(form) + + return self.form_valid(form) diff --git a/InvenTree/common/forms.py b/InvenTree/common/forms.py index 8a0017e38b..bab7ede74c 100644 --- a/InvenTree/common/forms.py +++ b/InvenTree/common/forms.py @@ -5,14 +5,13 @@ Django forms for interacting with common objects # -*- coding: utf-8 -*- from __future__ import unicode_literals -from decimal import Decimal, InvalidOperation - from django import forms from django.utils.translation import gettext as _ from djmoney.forms.fields import MoneyField from InvenTree.forms import HelperForm +from InvenTree.helpers import clean_decimal from .files import FileManager from .models import InvenTreeSetting @@ -119,21 +118,6 @@ class MatchItem(forms.Form): super().__init__(*args, **kwargs) - def clean(number): - """ Clean-up decimal value """ - - # Check if empty - if not number: - return number - - # Check if decimal type - try: - clean_number = Decimal(number) - except InvalidOperation: - clean_number = number - - return clean_number.quantize(Decimal(1)) if clean_number == clean_number.to_integral() else clean_number.normalize() - # Setup FileManager file_manager.setup() @@ -160,7 +144,7 @@ class MatchItem(forms.Form): 'type': 'number', 'min': '0', 'step': 'any', - 'value': clean(row.get('quantity', '')), + 'value': clean_decimal(row.get('quantity', '')), }) ) @@ -202,7 +186,7 @@ class MatchItem(forms.Form): decimal_places=5, max_digits=19, required=False, - default_amount=clean(value), + default_amount=clean_decimal(value), ) else: self.fields[field_name] = forms.CharField( diff --git a/InvenTree/templates/InvenTree/settings/currencies.html b/InvenTree/templates/InvenTree/settings/currencies.html index 52d6558bad..10b5709238 100644 --- a/InvenTree/templates/InvenTree/settings/currencies.html +++ b/InvenTree/templates/InvenTree/settings/currencies.html @@ -22,11 +22,12 @@
    -

    {% trans "Exchange Rates" %}

    +

    {% trans "Exchange Rates - Convert to " %}{{ default_currency }}

    +
    {% csrf_token %} {% load crispy_forms_tags %} {% crispy form %} @@ -35,6 +36,17 @@ {% else %} {% endif %} +
    {% endblock %} + +{% block js_ready %} +{{ block.super }} + +{% if api_rates_success is False %} + var alert_msg = {% blocktrans %}"Failed to refresh exchange rates. Verify your API key and/or subscription plan" {% endblocktrans %}; + showAlertOrCache("alert-danger", alert_msg, null, 5000); +{% endif %} + +{% endblock %} \ No newline at end of file From 27799b43b236525b818fa32d2d4c66b57e0fa96b Mon Sep 17 00:00:00 2001 From: eeintech Date: Thu, 20 May 2021 13:51:54 -0400 Subject: [PATCH 05/14] Template fix/improvement --- InvenTree/templates/InvenTree/settings/currencies.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/InvenTree/templates/InvenTree/settings/currencies.html b/InvenTree/templates/InvenTree/settings/currencies.html index 10b5709238..fa9d234988 100644 --- a/InvenTree/templates/InvenTree/settings/currencies.html +++ b/InvenTree/templates/InvenTree/settings/currencies.html @@ -16,7 +16,9 @@ {% include "InvenTree/settings/header.html" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-dollar-sign" %} + {% if 'fixer' in exchange_backend %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_FIXER_API_KEY" icon="fa-key" %} + {% endif %} @@ -31,7 +33,7 @@ {% csrf_token %} {% load crispy_forms_tags %} {% crispy form %} - {% if exchange_backend == 'fixer.io' %} + {% if 'fixer' in exchange_backend %} {% else %} From bbd95f2c7059255b6217d01ea0c338b96deb8eae Mon Sep 17 00:00:00 2001 From: eeintech Date: Thu, 20 May 2021 14:52:56 -0400 Subject: [PATCH 06/14] Fixed exchange rate backend test --- InvenTree/InvenTree/tasks.py | 2 +- InvenTree/company/tests.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 0468365dae..b3649bcdcf 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -176,7 +176,7 @@ def update_exchange_rates(): backend = get_exchange_rate_backend() # Update rates - backend.update_rates() + backend.update_rates(base_currency=backend.base_currency) def send_email(subject, body, recipients, from_email=None): diff --git a/InvenTree/company/tests.py b/InvenTree/company/tests.py index 5dd3bf81ab..e07516e180 100644 --- a/InvenTree/company/tests.py +++ b/InvenTree/company/tests.py @@ -11,7 +11,7 @@ from .models import Company, Contact, ManufacturerPart, SupplierPart from .models import rename_company_image from part.models import Part -from InvenTree.exchange import InvenTreeManualExchangeBackend +from InvenTree.exchange import get_exchange_rate_backend from djmoney.contrib.exchange.models import Rate @@ -40,13 +40,15 @@ class CompanySimpleTest(TestCase): self.acme0002 = SupplierPart.objects.get(SKU='ACME0002') self.zerglphs = SupplierPart.objects.get(SKU='ZERGLPHS') self.zergm312 = SupplierPart.objects.get(SKU='ZERGM312') - - InvenTreeManualExchangeBackend().update_rates() + + # Exchange rate backend + backend = get_exchange_rate_backend() + backend.update_rates(base_currency=backend.base_currency) Rate.objects.create( currency='AUD', value='1.35', - backend_id='inventree', + backend_id=backend.name, ) def test_company_model(self): From be3f37f28f286fcf05ef6b24af6526a6835331ed Mon Sep 17 00:00:00 2001 From: eeintech Date: Thu, 20 May 2021 15:22:18 -0400 Subject: [PATCH 07/14] Updated default currency backend --- InvenTree/config_template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml index 63525b0d79..7d301934f0 100644 --- a/InvenTree/config_template.yaml +++ b/InvenTree/config_template.yaml @@ -68,7 +68,7 @@ currencies: # Choices are: # - InvenTreeManualExchangeBackend # - InvenTreeFixerExchangeBackend -exchange_backend: InvenTreeFixerExchangeBackend +exchange_backend: InvenTreeManualExchangeBackend # Email backend configuration # Ref: https://docs.djangoproject.com/en/dev/topics/email/ From 53ce848145c3137581ce0b8ea7b6315e82165b74 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 May 2021 14:48:56 +0200 Subject: [PATCH 08/14] better translation --- InvenTree/templates/InvenTree/settings/currencies.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/templates/InvenTree/settings/currencies.html b/InvenTree/templates/InvenTree/settings/currencies.html index fa9d234988..d9b046b9f5 100644 --- a/InvenTree/templates/InvenTree/settings/currencies.html +++ b/InvenTree/templates/InvenTree/settings/currencies.html @@ -24,7 +24,7 @@
    -

    {% trans "Exchange Rates - Convert to " %}{{ default_currency }}

    +

    {% blocktrans with cur=default_currency %}Exchange Rates - Convert to {{cur}}{% endblocktrans %}

    From 4503f23ae4e7c8292c59c93ba66d7a6a021d71a4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 May 2021 14:55:05 +0200 Subject: [PATCH 09/14] beeing safe with wrong / unknown setttings --- InvenTree/InvenTree/exchange.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py index ce1db06711..8de0c9902f 100644 --- a/InvenTree/InvenTree/exchange.py +++ b/InvenTree/InvenTree/exchange.py @@ -13,8 +13,10 @@ def get_exchange_rate_backend(): if 'InvenTreeManualExchangeBackend' in inventree_settings.EXCHANGE_BACKEND: return InvenTreeManualExchangeBackend() - else: + elif 'InvenTreeFixerExchangeBackend' in inventree_settings.EXCHANGE_BACKEND: return InvenTreeFixerExchangeBackend() + else: + raise ImproperlyConfigured('Exchange Backend wrongly configured') class InvenTreeManualExchangeBackend(BaseExchangeBackend): From b1b974a1f611141dd5c8a751bcfc0b79264ef756 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 May 2021 14:55:41 +0200 Subject: [PATCH 10/14] safer check if fixer-backend is used --- InvenTree/InvenTree/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 48e42dd890..778316ddd5 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -959,7 +959,7 @@ class CurrencySettingsView(FormView): stored_rates = exchange_rate_backend.get_stored_rates() for field in form.fields: - if 'fixer' in exchange_rate_backend.name: + if exchange_rate_backend.name.startswith('fixer-'): # Disable all the fields form.fields[field].disabled = True form.fields[field].initial = clean_decimal(stored_rates.get(field, 0)) @@ -973,7 +973,7 @@ class CurrencySettingsView(FormView): # Get exchange rate backend exchange_rate_backend = self.get_exchange_rate_backend() - if 'fixer' in exchange_rate_backend.name: + if exchange_rate_backend.name.startswith('fixer-'): # Refresh rate from Fixer.IO API exchange_rate_backend.update_rates(base_currency=exchange_rate_backend.base_currency) # Check if rates have been updated From 619cba6007c92c0ac75d6c2c755e8e7fdea58df5 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 25 May 2021 10:37:53 +1000 Subject: [PATCH 11/14] Currency: Use ExchangeRateHost backend by default, rather than fixer.io - No longer support fixer.io as it requires an API key and does not support "base" currency --- InvenTree/InvenTree/tasks.py | 10 ++-------- InvenTree/common/models.py | 6 ------ InvenTree/templates/InvenTree/settings/global.html | 1 - 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index b81b4e6de4..e9e37e63a3 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -169,18 +169,12 @@ def update_exchange_rates(): try: import common.models from django.conf import settings - from djmoney.contrib.exchange.backends import FixerBackend + from InvenTree.exchange import ExchangeRateHostBackend except AppRegistryNotReady: # Apps not yet loaded! return - fixer_api_key = common.models.InvenTreeSetting.get_setting('INVENTREE_FIXER_API_KEY', '').strip() - - if not fixer_api_key: - # API key not provided - return - - backend = FixerBackend(access_key=fixer_api_key) + backend = ExchangeRateHostBackend() currencies = ','.join(settings.CURRENCIES) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index e499e9b801..99712b2a93 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -87,12 +87,6 @@ class InvenTreeSetting(models.Model): 'choices': djmoney.settings.CURRENCY_CHOICES, }, - 'INVENTREE_FIXER_API_KEY': { - 'name': _('fixer.io API key'), - 'description': _('API key for fixer.io currency conversion service'), - 'default': '', - }, - 'INVENTREE_DOWNLOAD_FROM_URL': { 'name': _('Download from URL'), 'description': _('Allow download of remote images and files from external URL'), diff --git a/InvenTree/templates/InvenTree/settings/global.html b/InvenTree/templates/InvenTree/settings/global.html index 5c5dccfb2a..a0347490d0 100644 --- a/InvenTree/templates/InvenTree/settings/global.html +++ b/InvenTree/templates/InvenTree/settings/global.html @@ -20,7 +20,6 @@ {% include "InvenTree/settings/setting.html" with key="INVENTREE_BASE_URL" icon="fa-globe" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_COMPANY_NAME" icon="fa-building" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-dollar-sign" %} - {% include "InvenTree/settings/setting.html" with key="INVENTREE_FIXER_API_KEY" icon="fa-key" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_DOWNLOAD_FROM_URL" icon="fa-cloud-download-alt" %} From 0a26a069092e3a0d0f6a0ebce929c38ac174dba6 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 25 May 2021 10:46:30 +1000 Subject: [PATCH 12/14] Use INVENTREE_DEFAULT_CURRENCY as specified base currency --- InvenTree/InvenTree/settings.py | 4 +--- InvenTree/InvenTree/tasks.py | 5 ++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 5cf0b0c544..f0a3312b39 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -513,9 +513,7 @@ CURRENCIES = CONFIG.get( ], ) -BASE_CURRENCY = CONFIG.get('base_currency', 'USD') - -EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeManualExchangeBackend' +EXCHANGE_BACKEND = 'InvenTree.exchange.ExchangeRateHostBackend' # Extract email settings from the config file email_config = CONFIG.get('email', {}) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index e9e37e63a3..ad33232fe0 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -175,10 +175,13 @@ def update_exchange_rates(): return backend = ExchangeRateHostBackend() + print(f"Updating exchange rates from {backend.url}") currencies = ','.join(settings.CURRENCIES) - base = settings.BASE_CURRENCY + base = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') + + print(f"Using base currency '{base}'") backend.update_rates(base_currency=base, symbols=currencies) From 93bfe4c5f11d05e873629d4427ae13fae3d0b205 Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 25 May 2021 11:19:07 -0400 Subject: [PATCH 13/14] Added 'Custom Exchange Rate' boolea setting Removed Fixer.io exchange rate backend --- InvenTree/InvenTree/exchange.py | 60 +++++-------------- InvenTree/InvenTree/settings.py | 2 - InvenTree/InvenTree/tasks.py | 7 +-- InvenTree/InvenTree/views.py | 6 +- InvenTree/common/models.py | 7 +++ InvenTree/config_template.yaml | 7 --- .../InvenTree/settings/currencies.html | 6 +- 7 files changed, 29 insertions(+), 66 deletions(-) diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py index 2a2d287e1c..0a75436b1e 100644 --- a/InvenTree/InvenTree/exchange.py +++ b/InvenTree/InvenTree/exchange.py @@ -1,7 +1,5 @@ -from django.core.exceptions import ImproperlyConfigured from django.conf import settings as inventree_settings -from djmoney import settings as djmoney_settings from djmoney.contrib.exchange.backends.base import BaseExchangeBackend from djmoney.contrib.exchange.models import Rate @@ -11,12 +9,12 @@ from common.models import InvenTreeSetting def get_exchange_rate_backend(): """ Return the exchange rate backend set by user """ - if 'InvenTreeManualExchangeBackend' in inventree_settings.EXCHANGE_BACKEND: + custom = InvenTreeSetting.get_setting('CUSTOM_EXCHANGE_RATES', False) + + if custom: return InvenTreeManualExchangeBackend() - elif 'InvenTreeFixerExchangeBackend' in inventree_settings.EXCHANGE_BACKEND: - return InvenTreeFixerExchangeBackend() else: - raise ImproperlyConfigured('Exchange Backend wrongly configured') + return ExchangeRateHostBackend() class InvenTreeManualExchangeBackend(BaseExchangeBackend): @@ -30,13 +28,14 @@ class InvenTreeManualExchangeBackend(BaseExchangeBackend): name = 'inventree' url = None + custom_rates = True base_currency = None currencies = [] def update_default_currency(self): """ Update to base currency """ - self.base_currency = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY', inventree_settings.BASE_CURRENCY) + self.base_currency = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY', 'USD') def __init__(self, url=None): """ Overrides init to update url, base currency and currencies """ @@ -73,39 +72,23 @@ class InvenTreeManualExchangeBackend(BaseExchangeBackend): return stored_rates -class InvenTreeFixerExchangeBackend(InvenTreeManualExchangeBackend): +class ExchangeRateHostBackend(InvenTreeManualExchangeBackend): """ - Backend for updating currency exchange rates using Fixer.IO API + Backend for https://exchangerate.host/ """ - name = 'fixer' - access_key = None - - def get_api_key(self): - """ Get API key from global settings """ - - fixer_api_key = InvenTreeSetting.get_setting('INVENTREE_FIXER_API_KEY', '').strip() - - if not fixer_api_key: - # API key not provided - return None - - self.access_key = fixer_api_key + name = "exchangerate.host" def __init__(self): - """ Override init to get access_key from global settings """ + self.url = "https://api.exchangerate.host/latest" - self.get_api_key() + self.custom_rates = False - if self.access_key is None: - raise ImproperlyConfigured("fixer.io API key is needed to use InvenTreeFixerExchangeBackend") - - super().__init__(url=djmoney_settings.FIXER_URL) + super().__init__(url=self.url) def get_params(self): - """ Returns parameters (access key) """ - - return {"access_key": self.access_key} + # No API key is required + return {} def update_rates(self, base_currency=None): """ Override update_rates method using currencies found in the settings @@ -135,18 +118,3 @@ class InvenTreeFixerExchangeBackend(InvenTreeManualExchangeBackend): pass return {} - - -class ExchangeRateHostBackend(SimpleExchangeBackend): - """ - Backend for https://exchangerate.host/ - """ - - name = "exchangerate.host" - - def __init__(self): - self.url = "https://api.exchangerate.host/latest" - - def get_params(self): - # No API key is required - return {} diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index f0a3312b39..afa43396f5 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -513,8 +513,6 @@ CURRENCIES = CONFIG.get( ], ) -EXCHANGE_BACKEND = 'InvenTree.exchange.ExchangeRateHostBackend' - # Extract email settings from the config file email_config = CONFIG.get('email', {}) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index ad33232fe0..365a94fd07 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -163,12 +163,11 @@ def check_for_updates(): def update_exchange_rates(): """ - If an API key for fixer.io has been provided, attempt to update currency exchange rates + Update currency exchange rates """ try: import common.models - from django.conf import settings from InvenTree.exchange import ExchangeRateHostBackend except AppRegistryNotReady: # Apps not yet loaded! @@ -177,13 +176,11 @@ def update_exchange_rates(): backend = ExchangeRateHostBackend() print(f"Updating exchange rates from {backend.url}") - currencies = ','.join(settings.CURRENCIES) - base = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') print(f"Using base currency '{base}'") - backend.update_rates(base_currency=base, symbols=currencies) + backend.update_rates(base_currency=base) def send_email(subject, body, recipients, from_email=None): diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 778316ddd5..a18845bf02 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -944,6 +944,8 @@ class CurrencySettingsView(FormView): context['default_currency'] = exchange_rate_backend.base_currency + context['custom_rates'] = exchange_rate_backend.custom_rates + context['exchange_backend'] = exchange_rate_backend.name return context @@ -959,7 +961,7 @@ class CurrencySettingsView(FormView): stored_rates = exchange_rate_backend.get_stored_rates() for field in form.fields: - if exchange_rate_backend.name.startswith('fixer-'): + if not exchange_rate_backend.custom_rates: # Disable all the fields form.fields[field].disabled = True form.fields[field].initial = clean_decimal(stored_rates.get(field, 0)) @@ -973,7 +975,7 @@ class CurrencySettingsView(FormView): # Get exchange rate backend exchange_rate_backend = self.get_exchange_rate_backend() - if exchange_rate_backend.name.startswith('fixer-'): + if not exchange_rate_backend.custom_rates: # Refresh rate from Fixer.IO API exchange_rate_backend.update_rates(base_currency=exchange_rate_backend.base_currency) # Check if rates have been updated diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 99712b2a93..74c6c82b41 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -87,6 +87,13 @@ class InvenTreeSetting(models.Model): 'choices': djmoney.settings.CURRENCY_CHOICES, }, + 'CUSTOM_EXCHANGE_RATES': { + 'name': _('Custom Exchange Rates'), + 'description': _('Enable custom exchange rates'), + 'validator': bool, + 'default': False, + }, + 'INVENTREE_DOWNLOAD_FROM_URL': { 'name': _('Download from URL'), 'description': _('Allow download of remote images and files from external URL'), diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml index 7d301934f0..87dfb6b545 100644 --- a/InvenTree/config_template.yaml +++ b/InvenTree/config_template.yaml @@ -62,13 +62,6 @@ currencies: - JPY - NZD - USD -# Define base currency (can also be defined in the global settings) -# base_currency: USD -# Define exchange backend -# Choices are: -# - InvenTreeManualExchangeBackend -# - InvenTreeFixerExchangeBackend -exchange_backend: InvenTreeManualExchangeBackend # Email backend configuration # Ref: https://docs.djangoproject.com/en/dev/topics/email/ diff --git a/InvenTree/templates/InvenTree/settings/currencies.html b/InvenTree/templates/InvenTree/settings/currencies.html index d9b046b9f5..b6cf9fea81 100644 --- a/InvenTree/templates/InvenTree/settings/currencies.html +++ b/InvenTree/templates/InvenTree/settings/currencies.html @@ -16,9 +16,7 @@ {% include "InvenTree/settings/header.html" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-dollar-sign" %} - {% if 'fixer' in exchange_backend %} - {% include "InvenTree/settings/setting.html" with key="INVENTREE_FIXER_API_KEY" icon="fa-key" %} - {% endif %} + {% include "InvenTree/settings/setting.html" with key="CUSTOM_EXCHANGE_RATES" icon="fa-edit" %} @@ -33,7 +31,7 @@ {% csrf_token %} {% load crispy_forms_tags %} {% crispy form %} - {% if 'fixer' in exchange_backend %} + {% if custom_rates is False %} {% else %} From b04ad48178f72fabed21bb6ac7255f716095f200 Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 25 May 2021 11:39:04 -0400 Subject: [PATCH 14/14] Fixed test using manual exchange backend, template update --- InvenTree/company/tests.py | 4 ++-- InvenTree/templates/InvenTree/settings/currencies.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/InvenTree/company/tests.py b/InvenTree/company/tests.py index e07516e180..2c6e722440 100644 --- a/InvenTree/company/tests.py +++ b/InvenTree/company/tests.py @@ -11,7 +11,7 @@ from .models import Company, Contact, ManufacturerPart, SupplierPart from .models import rename_company_image from part.models import Part -from InvenTree.exchange import get_exchange_rate_backend +from InvenTree.exchange import InvenTreeManualExchangeBackend from djmoney.contrib.exchange.models import Rate @@ -42,7 +42,7 @@ class CompanySimpleTest(TestCase): self.zergm312 = SupplierPart.objects.get(SKU='ZERGM312') # Exchange rate backend - backend = get_exchange_rate_backend() + backend = InvenTreeManualExchangeBackend() backend.update_rates(base_currency=backend.base_currency) Rate.objects.create( diff --git a/InvenTree/templates/InvenTree/settings/currencies.html b/InvenTree/templates/InvenTree/settings/currencies.html index b6cf9fea81..dd47bc6cdd 100644 --- a/InvenTree/templates/InvenTree/settings/currencies.html +++ b/InvenTree/templates/InvenTree/settings/currencies.html @@ -45,7 +45,7 @@ {{ block.super }} {% if api_rates_success is False %} - var alert_msg = {% blocktrans %}"Failed to refresh exchange rates. Verify your API key and/or subscription plan" {% endblocktrans %}; + var alert_msg = {% blocktrans %}"Failed to refresh exchange rates" {% endblocktrans %}; showAlertOrCache("alert-danger", alert_msg, null, 5000); {% endif %}