2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-02 03:30:54 +00:00

Exchange rate plugin (#5667)

* Add plugin mixin class for supporting exchange rates

* Split some mixin classes out into their own files

- mixins.py is becoming quite bloated!

* Add some new settings for controlling currency updates

* Adds basic plugin implementation

* Refactor existing implementation

- Builtin plugin uses frankfurter.app API
- Better error / edge case handlign

* Add sample plugin for currency exchange

* Allow user to select which plugin to use for plugin updates

* Observe user-configured setting for how often exchange rates are updated

* Updates for some of the sample plugins

* Fix plugin slug

* Add doc page

* Document simple example

* Improve sample

* Add blank page for currency settings info

* More info in "config" page

* Update docs again

* Updated unit tests

* Fill out default settings values when InvenTree runs

* Add log messages

* Significant improvement in default settings speed

- Use bulk create
- Be efficient
- Dont' be inefficient

* More strict checks

* Refactor default values implementation

- Don't run at startup
- Run on list API
- Implement generic @classmethod
This commit is contained in:
Oliver
2023-10-05 21:19:28 +11:00
committed by GitHub
parent f5e8f27fcd
commit c7eb90347a
27 changed files with 760 additions and 405 deletions

View File

@ -160,7 +160,7 @@ class CurrencyRefreshView(APIView):
from InvenTree.tasks import update_exchange_rates
update_exchange_rates()
update_exchange_rates(force=True)
return Response({
'success': 'Exchange rates updated',
@ -192,6 +192,12 @@ class GlobalSettingsList(SettingsList):
queryset = common.models.InvenTreeSetting.objects.exclude(key__startswith="_")
serializer_class = common.serializers.GlobalSettingsSerializer
def list(self, request, *args, **kwargs):
"""Ensure all global settings are created"""
common.models.InvenTreeSetting.build_default_values()
return super().list(request, *args, **kwargs)
class GlobalSettingsPermissions(permissions.BasePermission):
"""Special permission class to determine if the user is "staff"."""
@ -245,6 +251,12 @@ class UserSettingsList(SettingsList):
queryset = common.models.InvenTreeUserSetting.objects.all()
serializer_class = common.serializers.UserSettingsSerializer
def list(self, request, *args, **kwargs):
"""Ensure all user settings are created"""
common.models.InvenTreeUserSetting.build_default_values(user=request.user)
return super().list(request, *args, **kwargs)
def filter_queryset(self, queryset):
"""Only list settings which apply to the current user."""
try:

View File

@ -197,6 +197,32 @@ class BaseInvenTreeSetting(models.Model):
# Execute after_save action
self._call_settings_function('after_save', args, kwargs)
@classmethod
def build_default_values(cls, **kwargs):
"""Ensure that all values defined in SETTINGS are present in the database
If a particular setting is not present, create it with the default value
"""
try:
existing_keys = cls.objects.filter(**kwargs).values_list('key', flat=True)
settings_keys = cls.SETTINGS.keys()
missing_keys = set(settings_keys) - set(existing_keys)
if len(missing_keys) > 0:
logger.info("Building %s default values for %s", len(missing_keys), str(cls))
cls.objects.bulk_create([
cls(
key=key,
value=cls.get_setting_default(key),
**kwargs
) for key in missing_keys if not key.startswith('_')
])
except Exception as exc:
logger.exception("Failed to build default values for %s (%s)", str(cls), str(type(exc)))
pass
def _call_settings_function(self, reference: str, args, kwargs):
"""Call a function associated with a particular setting.
@ -939,6 +965,20 @@ def validate_email_domains(setting):
raise ValidationError(_(f'Invalid domain name: {domain}'))
def currency_exchange_plugins():
"""Return a set of plugin choices which can be used for currency exchange"""
try:
from plugin import registry
plugs = registry.with_mixin('currencyexchange', active=True)
except Exception:
plugs = []
return [
('', _('No plugin')),
] + [(plug.slug, plug.human_name) for plug in plugs]
def update_exchange_rates(setting):
"""Update exchange rates when base currency is changed"""
@ -948,7 +988,7 @@ def update_exchange_rates(setting):
if not InvenTree.ready.canAppAccessDatabase():
return
InvenTree.tasks.update_exchange_rates()
InvenTree.tasks.update_exchange_rates(force=True)
def reload_plugin_registry(setting):
@ -1053,6 +1093,24 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'after_save': update_exchange_rates,
},
'CURRENCY_UPDATE_INTERVAL': {
'name': _('Currency Update Interval'),
'description': _('How often to update exchange rates (set to zero to disable)'),
'default': 1,
'units': _('days'),
'validator': [
int,
MinValueValidator(0),
],
},
'CURRENCY_UPDATE_PLUGIN': {
'name': _('Currency Update Plugin'),
'description': _('Currency update plugin to use'),
'choices': currency_exchange_plugins,
'default': 'inventreecurrencyexchange'
},
'INVENTREE_DOWNLOAD_FROM_URL': {
'name': _('Download from URL'),
'description': _('Allow download of remote images and files from external URL'),