mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Ready for review
This commit is contained in:
parent
bed6a7e49c
commit
747b0554e1
@ -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 {}
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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(
|
||||||
|
@ -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 %}
|
Loading…
Reference in New Issue
Block a user