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:
@ -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:
|
||||
|
@ -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'),
|
||||
|
Reference in New Issue
Block a user