From 747b0554e1f92323a3da20e82a3415cafc74a639 Mon Sep 17 00:00:00 2001 From: eeintech Date: Thu, 20 May 2021 13:45:26 -0400 Subject: [PATCH] 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