Simplify settings view

- Show various currency exchange rates
- Button to "refresh now"
This commit is contained in:
Oliver Walters 2021-05-27 16:34:37 +10:00
parent af1904b6e4
commit 6085478672
4 changed files with 92 additions and 160 deletions

View File

@ -16,8 +16,6 @@ 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
class HelperForm(forms.ModelForm): class HelperForm(forms.ModelForm):
""" Provides simple integration of crispy_forms extension. """ """ Provides simple integration of crispy_forms extension. """
@ -240,35 +238,3 @@ class SettingCategorySelectForm(forms.ModelForm):
css_class='row', 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.base_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',
'style': 'width: 200px;',
'type': 'number',
'min': '0',
'step': 'any',
'value': 0,
})
)

View File

@ -39,6 +39,7 @@ from rest_framework.documentation import include_docs_urls
from .views import IndexView, SearchView, DatabaseStatsView from .views import IndexView, SearchView, DatabaseStatsView
from .views import SettingsView, EditUserView, SetPasswordView from .views import SettingsView, EditUserView, SetPasswordView
from .views import CurrencySettingsView, CurrencyRefreshView
from .views import AppearanceSelectView, SettingCategorySelectView from .views import AppearanceSelectView, SettingCategorySelectView
from .views import DynamicJsView from .views import DynamicJsView
@ -82,15 +83,16 @@ settings_urls = [
url(r'^appearance/?', AppearanceSelectView.as_view(), name='settings-appearance'), url(r'^appearance/?', AppearanceSelectView.as_view(), name='settings-appearance'),
url(r'^i18n/?', include('django.conf.urls.i18n')), url(r'^i18n/?', include('django.conf.urls.i18n')),
url(r'^global/?', SettingsView.as_view(template_name='InvenTree/settings/global.html'), name='settings-global'), url(r'^global/', SettingsView.as_view(template_name='InvenTree/settings/global.html'), name='settings-global'),
url(r'^report/?', SettingsView.as_view(template_name='InvenTree/settings/report.html'), name='settings-report'), url(r'^report/', SettingsView.as_view(template_name='InvenTree/settings/report.html'), name='settings-report'),
url(r'^category/?', SettingCategorySelectView.as_view(), name='settings-category'), url(r'^category/', SettingCategorySelectView.as_view(), name='settings-category'),
url(r'^part/?', SettingsView.as_view(template_name='InvenTree/settings/part.html'), name='settings-part'), url(r'^part/', SettingsView.as_view(template_name='InvenTree/settings/part.html'), name='settings-part'),
url(r'^stock/?', SettingsView.as_view(template_name='InvenTree/settings/stock.html'), name='settings-stock'), url(r'^stock/', SettingsView.as_view(template_name='InvenTree/settings/stock.html'), name='settings-stock'),
url(r'^build/?', SettingsView.as_view(template_name='InvenTree/settings/build.html'), name='settings-build'), 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'^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'^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'^currencies/', CurrencySettingsView.as_view(), name='settings-currencies'),
url(r'^currencies-refresh/', CurrencyRefreshView.as_view(), name='settings-currencies-refresh'),
url(r'^(?P<pk>\d+)/edit/', SettingEdit.as_view(), name='setting-edit'), url(r'^(?P<pk>\d+)/edit/', SettingEdit.as_view(), name='setting-edit'),

View File

@ -12,12 +12,15 @@ from django.utils.translation import gettext_lazy as _
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.http import JsonResponse, HttpResponseRedirect from django.http import JsonResponse, HttpResponseRedirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.conf import settings
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views import View from django.views import View
from django.views.generic import ListView, DetailView, CreateView, FormView, DeleteView, UpdateView from django.views.generic import ListView, DetailView, CreateView, FormView, DeleteView, UpdateView
from django.views.generic.base import TemplateView from django.views.generic.base import RedirectView, TemplateView
from djmoney.contrib.exchange.models import ExchangeBackend, Rate
from part.models import Part, PartCategory from part.models import Part, PartCategory
from stock.models import StockLocation, StockItem from stock.models import StockLocation, StockItem
@ -25,11 +28,11 @@ 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 InvenTree.helpers import clean_decimal
import InvenTree.tasks
from .forms import DeleteForm, EditUserForm, SetPasswordForm from .forms import DeleteForm, EditUserForm, SetPasswordForm
from .forms import ColorThemeSelectForm, SettingCategorySelectForm from .forms import ColorThemeSelectForm, SettingCategorySelectForm
from .forms import SettingExchangeRatesForm
from .helpers import str2bool from .helpers import str2bool
from .exchange import get_exchange_rate_backend
from rest_framework import views from rest_framework import views
@ -772,6 +775,50 @@ class SettingsView(TemplateView):
return ctx return ctx
class CurrencyRefreshView(RedirectView):
url = reverse_lazy("settings-currencies")
def post(self, request, *args, **kwargs):
"""
On a POST request we will attempt to refresh the exchange rates
"""
print("POST!")
# Will block for a little bit
InvenTree.tasks.update_exchange_rates()
return self.get(request, *args, **kwargs)
class CurrencySettingsView(TemplateView):
"""
View for configuring currency settings
"""
template_name = "InvenTree/settings/currencies.html"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs).copy()
ctx['settings'] = InvenTreeSetting.objects.all().order_by('key')
ctx["base_currency"] = settings.BASE_CURRENCY
ctx["currencies"] = settings.CURRENCIES
ctx["rates"] = Rate.objects.filter(backend="InvenTreeExchange")
# When were the rates last updated?
try:
backend = ExchangeBackend.objects.get(name='InvenTreeExchange')
ctx["rates_updated"] = backend.last_update
except:
ctx["rates_updated"] = None
return ctx
class AppearanceSelectView(FormView): class AppearanceSelectView(FormView):
""" View for selecting a color theme """ """ View for selecting a color theme """
@ -911,89 +958,3 @@ class DatabaseStatsView(AjaxView):
""" """
return ctx return ctx
class CurrencySettingsView(FormView):
form_class = SettingExchangeRatesForm
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 = self.get_exchange_rate_backend()
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
def get_form(self):
form = super().get_form()
# Get exchange rate backend
exchange_rate_backend = self.get_exchange_rate_backend()
# Get stored exchange rates
stored_rates = exchange_rate_backend.get_stored_rates()
for field in form.fields:
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))
return form
def post(self, request, *args, **kwargs):
form = self.get_form()
# Get exchange rate backend
exchange_rate_backend = self.get_exchange_rate_backend()
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
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 = {}
if form.is_valid():
for field, value in form.cleaned_data.items():
manual_rates[field] = clean_decimal(value)
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

@ -13,40 +13,43 @@
{% block settings %} {% block settings %}
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
{% include "InvenTree/settings/header.html" %}
<tbody> <tbody>
{% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-dollar-sign" %} <tr>
{% include "InvenTree/settings/setting.html" with key="CUSTOM_EXCHANGE_RATES" icon="fa-edit" %} <th>{% trans "Base Currency" %}</th>
</tbody> <th>{{ base_currency }}</th>
</table> </tr>
<tr>
<div class='row'> <th colspan='2'>{% trans "Exchange Rates" %}</th>
<div class='col-sm-6'> </tr>
<h4>{% blocktrans with cur=default_currency %}Exchange Rates - Convert to {{cur}}{% endblocktrans %}</h4> {% for rate in rates %}
</div> <tr>
</div> <td>{{ rate.currency }}</td>
<td>{{ rate.value }}</td>
<form action="{% url 'settings-currencies' %}" method="post"> </tr>
<div id='exchange_rate_form'> {% endfor %}
{% csrf_token %} <tr>
{% load crispy_forms_tags %} <th>
{% crispy form %} {% trans "Last Update" %}
{% if custom_rates is False %} </th>
<button type="submit" class='btn btn-primary'>{% trans "Refresh Exchange Rates" %}</button> <td>
{% if rates_updated %}
{{ rates_updated }}
{% else %} {% else %}
<button type="submit" class='btn btn-primary'>{% trans "Update Exchange Rates" %}</button> <i>{% trans "Never" %}</i>
{% endif %} {% endif %}
<form action='{% url "settings-currencies-refresh" %}' method='post'>
<div id='refresh-rates-form'>
{% csrf_token %}
<button type='submit' id='update-rates' class='btn btn-default float-right'>{% trans "Update Now" %}</button>
</div> </div>
</form> </form>
</td>
</tr>
</tbody>
</table>
{% endblock %} {% endblock %}
{% block js_ready %} {% block js_ready %}
{{ block.super }} {{ block.super }}
{% if api_rates_success is False %}
var alert_msg = {% blocktrans %}"Failed to refresh exchange rates" {% endblocktrans %};
showAlertOrCache("alert-danger", alert_msg, null, 5000);
{% endif %}
{% endblock %} {% endblock %}