Currency API Updates (#4300)

* Adds an API endpoint for manually updating / refreshing currency data from the server

* Update currency rates manually from the settings page

* Add 'last updated' information to the currency exchange backend

* Load currency exchange data via API (on setings page)

* Bump API version

* Table cleanup
This commit is contained in:
Oliver 2023-02-03 12:43:55 +11:00 committed by GitHub
parent 3869d98b32
commit ce3dabedb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 102 additions and 64 deletions

View File

@ -2,11 +2,15 @@
# InvenTree API version
INVENTREE_API_VERSION = 92
INVENTREE_API_VERSION = 93
"""
Increment this API version number whenever there is a significant change to the API that any clients need to know about
v93 -> 2023-02-03 : https://github.com/inventree/InvenTree/pull/4300
- Adds extra information to the currency exchange endpoint
- Adds API endpoint for manually updating exchange rates
v92 -> 2023-02-02 : https://github.com/inventree/InvenTree/pull/4293
- Adds API endpoint for currency exchange information

View File

@ -31,8 +31,8 @@ from stock.urls import stock_urls
from users.api import user_urls
from .api import InfoView, NotFoundView
from .views import (AboutView, AppearanceSelectView, CurrencyRefreshView,
CustomConnectionsView, CustomEmailView, CustomLoginView,
from .views import (AboutView, AppearanceSelectView, CustomConnectionsView,
CustomEmailView, CustomLoginView,
CustomPasswordResetFromKeyView,
CustomSessionDeleteOtherView, CustomSessionDeleteView,
CustomTwoFactorRemove, DatabaseStatsView, DynamicJsView,
@ -73,7 +73,6 @@ settings_urls = [
re_path(r'^i18n/?', include('django.conf.urls.i18n')),
re_path(r'^appearance/?', AppearanceSelectView.as_view(), name='settings-appearance'),
re_path(r'^currencies-refresh/', CurrencyRefreshView.as_view(), name='settings-currencies-refresh'),
# Catch any other urls
re_path(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/settings.html'), name='settings'),

View File

@ -652,20 +652,6 @@ class CustomLoginView(LoginView):
return super().get(request, *args, **kwargs)
class CurrencyRefreshView(RedirectView):
"""POST endpoint to refresh / update exchange rates."""
url = reverse_lazy("settings-currencies")
def post(self, request, *args, **kwargs):
"""On a POST request we will attempt to refresh the exchange rates."""
from InvenTree.tasks import offload_task, update_exchange_rates
offload_task(update_exchange_rates, force_sync=True)
return redirect(reverse_lazy('settings'))
class AppearanceSelectView(RedirectView):
"""View for selecting a color theme."""

View File

@ -9,7 +9,7 @@ from django.views.decorators.csrf import csrf_exempt
from django_filters.rest_framework import DjangoFilterBackend
from django_q.tasks import async_task
from djmoney.contrib.exchange.models import Rate
from djmoney.contrib.exchange.models import ExchangeBackend, Rate
from rest_framework import filters, permissions, serializers
from rest_framework.exceptions import NotAcceptable, NotFound
from rest_framework.permissions import IsAdminUser
@ -104,10 +104,7 @@ class WebhookView(CsrfExemptMixin, APIView):
class CurrencyExchangeView(APIView):
"""API endpoint for displaying currency information
TODO: Add a POST hook to refresh / update the currency exchange data
"""
"""API endpoint for displaying currency information"""
permission_classes = [
permissions.IsAuthenticated,
@ -122,9 +119,17 @@ class CurrencyExchangeView(APIView):
except Exception:
rates = []
# Information on last update
try:
backend = ExchangeBackend.objects.get(name='InvenTreeExchange')
updated = backend.last_update
except Exception:
updated = None
response = {
'base_currency': common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY', 'USD'),
'exchange_rates': {}
'exchange_rates': {},
'updated': updated,
}
for rate in rates:
@ -133,6 +138,29 @@ class CurrencyExchangeView(APIView):
return Response(response)
class CurrencyRefreshView(APIView):
"""API endpoint for manually refreshing currency exchange rates.
User must be a 'staff' user to access this endpoint
"""
permission_classes = [
permissions.IsAuthenticated,
permissions.IsAdminUser,
]
def post(self, request, *args, **kwargs):
"""Performing a POST request will update currency exchange rates"""
from InvenTree.tasks import update_exchange_rates
update_exchange_rates
return Response({
'success': 'Exchange rates updated',
})
class SettingsList(ListAPI):
"""Generic ListView for settings.
@ -452,6 +480,7 @@ common_api_urls = [
# Currencies
re_path(r'^currency/', include([
re_path(r'^exchange/', CurrencyExchangeView.as_view(), name='api-currency-exchange'),
re_path(r'^refresh/', CurrencyRefreshView.as_view(), name='api-currency-refresh'),
])),
# Notifications

View File

@ -11,6 +11,7 @@
<div class='panel-content'>
<table class='table table-striped table-condensed'>
<tbody>
{% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-globe" %}
{% include "InvenTree/settings/setting.html" with key="PART_INTERNAL_PRICE" %}
{% include "InvenTree/settings/setting.html" with key="PART_BOM_USE_INTERNAL_PRICE" %}
{% include "InvenTree/settings/setting.html" with key="PRICING_DECIMAL_PLACES" %}
@ -29,54 +30,27 @@
<div class='panel-heading'>
<div class='d-flex flex-wrap'>
<h4>{% trans "Currency Settings" %}</h4>
<h4>{% trans "Exchange Rates" %}</h4>
{% include "spacer.html" %}
<div class='btn-group' role='group'>
<form action='{% url "settings-currencies-refresh" %}' method='post'>
<div id='refresh-rates-form'>
{% csrf_token %}
<button type='submit' id='update-rates' class='btn btn-primary float-right'>{% trans "Update Now" %}</button>
</div>
</form>
<button type='button' id='btn-update-rates' class='btn btn-primary float-right'>
<span class='fas fa-sync-alt'></span> {% trans "Update Now" %}
</button>
</div>
</div>
</div>
<div class='panel-content'>
{% if rates_updated %}
<div class='alert alert-block alert-info'>
{% trans "Last Update" %} - {{ rates_updated }}
</div>
{% else %}
<div class='alert alert-block alert-warning'>
{% trans "Last Update" %} - {% trans "Never" %}
</div>
{% endif %}
<div class='alert alert-block alert-info'>
{% trans "Last Update" %} - {{ rates_updated }}
</div>
{% else %}
<div class='alert alert-block alert-warning'>
{% trans "Last Update" %} - {% trans "Never" %}
</div>
{% endif %}
<table class='table table-striped table-condensed'>
<tbody>
{% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-globe" %}
<table class='table table-striped table-condensed' id='exchange-rate-table'></table>
<tr>
<td></td>
<th>{% trans "Base Currency" %}</th>
<th>{{ base_currency }}</th>
<th colspan='2'></th>
</tr>
<tr>
<td></td>
<th>{% trans "Exchange Rates" %}</th>
<th>{% trans "Currency" %}</th>
<th>{% trans "Rate" %}</th>
</tr>
{% for rate in rates %}
<tr>
<td></td>
<td></td>
<td>{{ rate.currency }}</td>
<td>{{ rate.value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock panel_content %}

View File

@ -151,6 +151,52 @@ $("#edit-password").on('click', function() {
);
});
$('#btn-update-rates').click(function() {
inventreePut(
'{% url "api-currency-refresh" %}',
{},
{
method: 'POST',
success: function(data) {
location.reload();
}
}
);
});
$('#exchange-rate-table').inventreeTable({
url: '{% url "api-currency-exchange" %}',
search: false,
showColumns: false,
sortable: true,
sidePagination: 'client',
onLoadSuccess: function(response) {
var data = response.exchange_rates || {};
var rows = [];
for (var currency in data) {
rows.push({
'currency': currency,
'rate': data[currency],
});
}
$('#exchange-rate-table').bootstrapTable('load', rows);
},
columns: [
{
field: 'currency',
sortable: true,
title: '{% trans "Currency" %}',
},
{
field: 'rate',
sortable: true,
title: '{% trans "Rate" %}',
}
]
});
$('#category-select').select2({
placeholder: '',