Ready for review

This commit is contained in:
eeintech 2021-05-20 13:45:26 -04:00
parent bed6a7e49c
commit 747b0554e1
6 changed files with 148 additions and 48 deletions

View File

@ -3,6 +3,7 @@ from django.conf import settings as inventree_settings
from djmoney import settings as djmoney_settings from djmoney import settings as djmoney_settings
from djmoney.contrib.exchange.backends.base import BaseExchangeBackend from djmoney.contrib.exchange.backends.base import BaseExchangeBackend
from djmoney.contrib.exchange.models import Rate
from common.models import InvenTreeSetting from common.models import InvenTreeSetting
@ -27,19 +28,24 @@ class InvenTreeManualExchangeBackend(BaseExchangeBackend):
name = 'inventree' name = 'inventree'
url = None url = None
default_currency = None base_currency = None
currencies = [] currencies = []
def update_default_currency(self): 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): def __init__(self, url=None):
""" Overrides init to update url, base currency and currencies """
self.url = url self.url = url
self.update_default_currency() self.update_default_currency()
# Update name
self.name = self.name + '-' + self.base_currency.lower()
self.currencies = inventree_settings.CURRENCIES self.currencies = inventree_settings.CURRENCIES
super().__init__() super().__init__()
@ -47,7 +53,22 @@ class InvenTreeManualExchangeBackend(BaseExchangeBackend):
def get_rates(self, **kwargs): def get_rates(self, **kwargs):
""" Returns a mapping <currency>: <rate> """ """ Returns a mapping <currency>: <rate> """
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): class InvenTreeFixerExchangeBackend(InvenTreeManualExchangeBackend):
@ -55,7 +76,7 @@ class InvenTreeFixerExchangeBackend(InvenTreeManualExchangeBackend):
Backend for updating currency exchange rates using Fixer.IO API Backend for updating currency exchange rates using Fixer.IO API
""" """
name = 'fixer.io' name = 'fixer'
access_key = None access_key = None
def get_api_key(self): def get_api_key(self):
@ -67,24 +88,48 @@ class InvenTreeFixerExchangeBackend(InvenTreeManualExchangeBackend):
# API key not provided # API key not provided
return None return None
return fixer_api_key self.access_key = fixer_api_key
def __init__(self): def __init__(self):
""" Override FixerBackend init to get access_key from global settings """ """ Override init to get access_key from global settings """
fixer_api_key = self.get_api_key() self.get_api_key()
if fixer_api_key is None: if self.access_key is None:
raise ImproperlyConfigured("fixer.io API key is needed to use InvenTreeFixerExchangeBackend") raise ImproperlyConfigured("fixer.io API key is needed to use InvenTreeFixerExchangeBackend")
self.access_key = fixer_api_key
super().__init__(url=djmoney_settings.FIXER_URL) 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 """ 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) symbols = ','.join(self.currencies)
super().update_rates(base_currency=self.base_currency, symbols=symbols) super().update_rates(base_currency=self.base_currency, symbols=symbols)
def get_rates(self, **params):
""" Returns a mapping <currency>: <rate> """
# 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 {}

View File

@ -15,6 +15,7 @@ from crispy_forms.bootstrap import PrependedText, AppendedText, PrependedAppende
from common.models import ColorTheme from common.models import ColorTheme
from part.models import PartCategory from part.models import PartCategory
from .exchange import InvenTreeManualExchangeBackend from .exchange import InvenTreeManualExchangeBackend
@ -254,7 +255,7 @@ class SettingExchangeRatesForm(forms.Form):
exchange_rate_backend.update_default_currency() exchange_rate_backend.update_default_currency()
for currency in exchange_rate_backend.currencies: for currency in exchange_rate_backend.currencies:
if currency != exchange_rate_backend.default_currency: if currency != exchange_rate_backend.base_currency:
# Set field name # Set field name
field_name = currency field_name = currency
# Set field input box # Set field input box
@ -264,9 +265,10 @@ class SettingExchangeRatesForm(forms.Form):
widget=forms.NumberInput(attrs={ widget=forms.NumberInput(attrs={
'name': field_name, 'name': field_name,
'class': 'numberinput', 'class': 'numberinput',
'style': 'width: 200px;',
'type': 'number', 'type': 'number',
'min': '0', 'min': '0',
'step': 'any', 'step': 'any',
'value': '', 'value': 0,
}) })
) )

View File

@ -8,7 +8,7 @@ import json
import os.path import os.path
from PIL import Image from PIL import Image
from decimal import Decimal from decimal import Decimal, InvalidOperation
from wsgiref.util import FileWrapper from wsgiref.util import FileWrapper
from django.http import StreamingHttpResponse from django.http import StreamingHttpResponse
@ -606,3 +606,19 @@ def getNewestMigrationFile(app, exclude_extension=True):
newest_file = newest_file.replace('.py', '') newest_file = newest_file.replace('.py', '')
return newest_file 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()

View File

@ -23,6 +23,7 @@ from part.models import Part, PartCategory
from stock.models import StockLocation, StockItem from stock.models import StockLocation, StockItem
from common.models import InvenTreeSetting, ColorTheme from common.models import InvenTreeSetting, ColorTheme
from users.models import check_user_role, RuleSet from users.models import check_user_role, RuleSet
from InvenTree.helpers import clean_decimal
from .forms import DeleteForm, EditUserForm, SetPasswordForm from .forms import DeleteForm, EditUserForm, SetPasswordForm
from .forms import ColorThemeSelectForm, SettingCategorySelectForm from .forms import ColorThemeSelectForm, SettingCategorySelectForm
@ -918,12 +919,30 @@ class CurrencySettingsView(FormView):
template_name = 'InvenTree/settings/currencies.html' template_name = 'InvenTree/settings/currencies.html'
success_url = reverse_lazy('settings-currencies') 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): def get_context_data(self, **kwargs):
context = super().get_context_data(**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 # 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 context['exchange_backend'] = exchange_rate_backend.name
@ -934,23 +953,45 @@ class CurrencySettingsView(FormView):
form = super().get_form() form = super().get_form()
# Get exchange rate backend # Get exchange rate backend
exchange_rate_backend = get_exchange_rate_backend() exchange_rate_backend = self.get_exchange_rate_backend()
# Get stored exchange rates
stored_rates = exchange_rate_backend.get_stored_rates()
if exchange_rate_backend.name == 'fixer.io':
# Disable all the fields
for field in form.fields: for field in form.fields:
if 'fixer' in exchange_rate_backend.name:
# Disable all the fields
form.fields[field].disabled = True form.fields[field].disabled = True
form.fields[field].initial = clean_decimal(stored_rates.get(field, 0))
return form return form
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.get_form()
# Get exchange rate backend # Get exchange rate backend
exchange_rate_backend = get_exchange_rate_backend() exchange_rate_backend = self.get_exchange_rate_backend()
# Process exchange rates if 'fixer' in exchange_rate_backend.name:
exchange_rate_backend.update_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
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)

View File

@ -5,14 +5,13 @@ Django forms for interacting with common objects
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from decimal import Decimal, InvalidOperation
from django import forms from django import forms
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from djmoney.forms.fields import MoneyField from djmoney.forms.fields import MoneyField
from InvenTree.forms import HelperForm from InvenTree.forms import HelperForm
from InvenTree.helpers import clean_decimal
from .files import FileManager from .files import FileManager
from .models import InvenTreeSetting from .models import InvenTreeSetting
@ -119,21 +118,6 @@ class MatchItem(forms.Form):
super().__init__(*args, **kwargs) 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 # Setup FileManager
file_manager.setup() file_manager.setup()
@ -160,7 +144,7 @@ class MatchItem(forms.Form):
'type': 'number', 'type': 'number',
'min': '0', 'min': '0',
'step': 'any', 'step': 'any',
'value': clean(row.get('quantity', '')), 'value': clean_decimal(row.get('quantity', '')),
}) })
) )
@ -202,7 +186,7 @@ class MatchItem(forms.Form):
decimal_places=5, decimal_places=5,
max_digits=19, max_digits=19,
required=False, required=False,
default_amount=clean(value), default_amount=clean_decimal(value),
) )
else: else:
self.fields[field_name] = forms.CharField( self.fields[field_name] = forms.CharField(

View File

@ -22,11 +22,12 @@
<div class='row'> <div class='row'>
<div class='col-sm-6'> <div class='col-sm-6'>
<h4>{% trans "Exchange Rates" %}</h4> <h4>{% trans "Exchange Rates - Convert to " %}{{ default_currency }}</h4>
</div> </div>
</div> </div>
<form action="{% url 'settings-currencies' %}" method="post"> <form action="{% url 'settings-currencies' %}" method="post">
<div id='exchange_rate_form'>
{% csrf_token %} {% csrf_token %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% crispy form %} {% crispy form %}
@ -35,6 +36,17 @@
{% else %} {% else %}
<button type="submit" class='btn btn-primary'>{% trans "Update Exchange Rates" %}</button> <button type="submit" class='btn btn-primary'>{% trans "Update Exchange Rates" %}</button>
{% endif %} {% endif %}
</div>
</form> </form>
{% endblock %} {% 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 %}