mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 19:46:46 +00:00
[FR] Simplify / optimize setting loading (#4152)
* [FR] Simplify / optimize setting loading Cache config.yaml data on load and use cached for get_settings Fixes #4149 * move the cache setting to config * add docstring * spell fix * Add lookup where settings come from Fixes #3982 * Fix spelling
This commit is contained in:
parent
0e96654b6a
commit
82bdd7780d
@ -1,5 +1,6 @@
|
|||||||
"""Helper functions for loading InvenTree configuration options."""
|
"""Helper functions for loading InvenTree configuration options."""
|
||||||
|
|
||||||
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
@ -8,6 +9,8 @@ import string
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
|
CONFIG_DATA = None
|
||||||
|
CONFIG_LOOKUPS = {}
|
||||||
|
|
||||||
|
|
||||||
def is_true(x):
|
def is_true(x):
|
||||||
@ -56,8 +59,18 @@ def get_config_file(create=True) -> Path:
|
|||||||
return cfg_filename
|
return cfg_filename
|
||||||
|
|
||||||
|
|
||||||
def load_config_data() -> map:
|
def load_config_data(set_cache: bool = False) -> map:
|
||||||
"""Load configuration data from the config file."""
|
"""Load configuration data from the config file.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
set_cache(bool): If True, the configuration data will be cached for future use after load.
|
||||||
|
"""
|
||||||
|
global CONFIG_DATA
|
||||||
|
|
||||||
|
# use cache if populated
|
||||||
|
# skip cache if cache should be set
|
||||||
|
if CONFIG_DATA is not None and not set_cache:
|
||||||
|
return CONFIG_DATA
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@ -66,6 +79,10 @@ def load_config_data() -> map:
|
|||||||
with open(cfg_file, 'r') as cfg:
|
with open(cfg_file, 'r') as cfg:
|
||||||
data = yaml.safe_load(cfg)
|
data = yaml.safe_load(cfg)
|
||||||
|
|
||||||
|
# Set the cache if requested
|
||||||
|
if set_cache:
|
||||||
|
CONFIG_DATA = data
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@ -82,22 +99,30 @@ def get_setting(env_var=None, config_key=None, default_value=None, typecast=None
|
|||||||
default_value: Value to return if first two options are not provided
|
default_value: Value to return if first two options are not provided
|
||||||
typecast: Function to use for typecasting the value
|
typecast: Function to use for typecasting the value
|
||||||
"""
|
"""
|
||||||
def try_typecasting(value):
|
def try_typecasting(value, source: str):
|
||||||
"""Attempt to typecast the value"""
|
"""Attempt to typecast the value"""
|
||||||
if typecast is not None:
|
if typecast is not None:
|
||||||
# Try to typecast the value
|
# Try to typecast the value
|
||||||
try:
|
try:
|
||||||
return typecast(value)
|
val = typecast(value)
|
||||||
|
set_metadata(source)
|
||||||
|
return val
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(f"Failed to typecast '{env_var}' with value '{value}' to type '{typecast}' with error {error}")
|
logger.error(f"Failed to typecast '{env_var}' with value '{value}' to type '{typecast}' with error {error}")
|
||||||
|
set_metadata(source)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def set_metadata(source: str):
|
||||||
|
"""Set lookup metadata for the setting."""
|
||||||
|
key = env_var or config_key
|
||||||
|
CONFIG_LOOKUPS[key] = {'env_var': env_var, 'config_key': config_key, 'source': source, 'accessed': datetime.datetime.now()}
|
||||||
|
|
||||||
# First, try to load from the environment variables
|
# First, try to load from the environment variables
|
||||||
if env_var is not None:
|
if env_var is not None:
|
||||||
val = os.getenv(env_var, None)
|
val = os.getenv(env_var, None)
|
||||||
|
|
||||||
if val is not None:
|
if val is not None:
|
||||||
return try_typecasting(val)
|
return try_typecasting(val, 'env')
|
||||||
|
|
||||||
# Next, try to load from configuration file
|
# Next, try to load from configuration file
|
||||||
if config_key is not None:
|
if config_key is not None:
|
||||||
@ -116,10 +141,10 @@ def get_setting(env_var=None, config_key=None, default_value=None, typecast=None
|
|||||||
cfg_data = cfg_data[key]
|
cfg_data = cfg_data[key]
|
||||||
|
|
||||||
if result is not None:
|
if result is not None:
|
||||||
return try_typecasting(result)
|
return try_typecasting(result, 'yaml')
|
||||||
|
|
||||||
# Finally, return the default value
|
# Finally, return the default value
|
||||||
return try_typecasting(default_value)
|
return try_typecasting(default_value, 'default')
|
||||||
|
|
||||||
|
|
||||||
def get_boolean_setting(env_var=None, config_key=None, default_value=False):
|
def get_boolean_setting(env_var=None, config_key=None, default_value=False):
|
||||||
|
@ -67,6 +67,14 @@ class RolePermission(permissions.BasePermission):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class IsSuperuser(permissions.IsAdminUser):
|
||||||
|
"""Allows access only to superuser users."""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
"""Check if the user is a superuser."""
|
||||||
|
return bool(request.user and request.user.is_superuser)
|
||||||
|
|
||||||
|
|
||||||
def auth_exempt(view_func):
|
def auth_exempt(view_func):
|
||||||
"""Mark a view function as being exempt from auth requirements."""
|
"""Mark a view function as being exempt from auth requirements."""
|
||||||
def wrapped_view(*args, **kwargs):
|
def wrapped_view(*args, **kwargs):
|
||||||
|
@ -52,7 +52,7 @@ DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
|||||||
BASE_DIR = config.get_base_dir()
|
BASE_DIR = config.get_base_dir()
|
||||||
|
|
||||||
# Load configuration data
|
# Load configuration data
|
||||||
CONFIG = config.load_config_data()
|
CONFIG = config.load_config_data(set_cache=True)
|
||||||
|
|
||||||
# Default action is to run the system in Debug mode
|
# Default action is to run the system in Debug mode
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
@ -13,7 +13,7 @@ from rest_framework.documentation import include_docs_urls
|
|||||||
|
|
||||||
from build.api import build_api_urls
|
from build.api import build_api_urls
|
||||||
from build.urls import build_urls
|
from build.urls import build_urls
|
||||||
from common.api import common_api_urls, settings_api_urls
|
from common.api import admin_api_urls, common_api_urls, settings_api_urls
|
||||||
from common.urls import common_urls
|
from common.urls import common_urls
|
||||||
from company.api import company_api_urls
|
from company.api import company_api_urls
|
||||||
from company.urls import (company_urls, manufacturer_part_urls,
|
from company.urls import (company_urls, manufacturer_part_urls,
|
||||||
@ -53,6 +53,7 @@ apipatterns = [
|
|||||||
re_path(r'^label/', include(label_api_urls)),
|
re_path(r'^label/', include(label_api_urls)),
|
||||||
re_path(r'^report/', include(report_api_urls)),
|
re_path(r'^report/', include(report_api_urls)),
|
||||||
re_path(r'^user/', include(user_urls)),
|
re_path(r'^user/', include(user_urls)),
|
||||||
|
re_path(r'^admin/', include(admin_api_urls)),
|
||||||
|
|
||||||
# Plugin endpoints
|
# Plugin endpoints
|
||||||
path('', include(plugin_api_urls)),
|
path('', include(plugin_api_urls)),
|
||||||
|
@ -18,9 +18,11 @@ from rest_framework.views import APIView
|
|||||||
import common.models
|
import common.models
|
||||||
import common.serializers
|
import common.serializers
|
||||||
from InvenTree.api import BulkDeleteMixin
|
from InvenTree.api import BulkDeleteMixin
|
||||||
|
from InvenTree.config import CONFIG_LOOKUPS
|
||||||
from InvenTree.helpers import inheritors
|
from InvenTree.helpers import inheritors
|
||||||
from InvenTree.mixins import (ListAPI, RetrieveAPI, RetrieveUpdateAPI,
|
from InvenTree.mixins import (ListAPI, RetrieveAPI, RetrieveUpdateAPI,
|
||||||
RetrieveUpdateDestroyAPI)
|
RetrieveUpdateDestroyAPI)
|
||||||
|
from InvenTree.permissions import IsSuperuser
|
||||||
from plugin.models import NotificationUserSetting
|
from plugin.models import NotificationUserSetting
|
||||||
from plugin.serializers import NotificationUserSettingSerializer
|
from plugin.serializers import NotificationUserSettingSerializer
|
||||||
|
|
||||||
@ -360,6 +362,29 @@ class NewsFeedEntryDetail(NewsFeedMixin, RetrieveUpdateDestroyAPI):
|
|||||||
"""Detail view for an individual news feed object."""
|
"""Detail view for an individual news feed object."""
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigList(ListAPI):
|
||||||
|
"""List view for all accessed configurations."""
|
||||||
|
|
||||||
|
queryset = CONFIG_LOOKUPS
|
||||||
|
serializer_class = common.serializers.ConfigSerializer
|
||||||
|
permission_classes = [IsSuperuser, ]
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigDetail(RetrieveAPI):
|
||||||
|
"""Detail view for an individual configuration."""
|
||||||
|
|
||||||
|
serializer_class = common.serializers.ConfigSerializer
|
||||||
|
permission_classes = [IsSuperuser, ]
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
"""Attempt to find a config object with the provided key."""
|
||||||
|
key = self.kwargs['key']
|
||||||
|
value = CONFIG_LOOKUPS.get(key, None)
|
||||||
|
if not value:
|
||||||
|
raise NotFound()
|
||||||
|
return {key: value}
|
||||||
|
|
||||||
|
|
||||||
settings_api_urls = [
|
settings_api_urls = [
|
||||||
# User settings
|
# User settings
|
||||||
re_path(r'^user/', include([
|
re_path(r'^user/', include([
|
||||||
@ -415,3 +440,9 @@ common_api_urls = [
|
|||||||
])),
|
])),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
admin_api_urls = [
|
||||||
|
# Admin
|
||||||
|
path('config/', ConfigList.as_view(), name='api-config-list'),
|
||||||
|
path('config/<str:key>/', ConfigDetail.as_view(), name='api-config-detail'),
|
||||||
|
]
|
||||||
|
@ -222,3 +222,16 @@ class NewsFeedEntrySerializer(InvenTreeModelSerializer):
|
|||||||
'summary',
|
'summary',
|
||||||
'read',
|
'read',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigSerializer(serializers.Serializer):
|
||||||
|
"""Serializer for the InvenTree configuration.
|
||||||
|
|
||||||
|
This is a read-only serializer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
"""Return the configuration data as a dictionary."""
|
||||||
|
if not isinstance(instance, str):
|
||||||
|
instance = list(instance.keys())[0]
|
||||||
|
return {'key': instance, **self.instance[instance]}
|
||||||
|
@ -827,7 +827,7 @@ class NotificationTest(InvenTreeAPITestCase):
|
|||||||
self.assertEqual(NotificationMessage.objects.filter(user=self.user).count(), 3)
|
self.assertEqual(NotificationMessage.objects.filter(user=self.user).count(), 3)
|
||||||
|
|
||||||
|
|
||||||
class LoadingTest(TestCase):
|
class CommonTest(InvenTreeAPITestCase):
|
||||||
"""Tests for the common config."""
|
"""Tests for the common config."""
|
||||||
|
|
||||||
def test_restart_flag(self):
|
def test_restart_flag(self):
|
||||||
@ -844,6 +844,30 @@ class LoadingTest(TestCase):
|
|||||||
# now it should be false again
|
# now it should be false again
|
||||||
self.assertFalse(common.models.InvenTreeSetting.get_setting('SERVER_RESTART_REQUIRED'))
|
self.assertFalse(common.models.InvenTreeSetting.get_setting('SERVER_RESTART_REQUIRED'))
|
||||||
|
|
||||||
|
def test_config_api(self):
|
||||||
|
"""Test config URLs."""
|
||||||
|
# Not superuser
|
||||||
|
self.get(reverse('api-config-list'), expected_code=403)
|
||||||
|
|
||||||
|
# Turn into superuser
|
||||||
|
self.user.is_superuser = True
|
||||||
|
self.user.save()
|
||||||
|
|
||||||
|
# Successfull checks
|
||||||
|
data = [
|
||||||
|
self.get(reverse('api-config-list'), expected_code=200).data[0], # list endpoint
|
||||||
|
self.get(reverse('api-config-detail', kwargs={'key': 'INVENTREE_DEBUG'}), expected_code=200).data, # detail endpoint
|
||||||
|
]
|
||||||
|
|
||||||
|
for item in data:
|
||||||
|
self.assertEqual(item['key'], 'INVENTREE_DEBUG')
|
||||||
|
self.assertEqual(item['env_var'], 'INVENTREE_DEBUG')
|
||||||
|
self.assertEqual(item['config_key'], 'debug')
|
||||||
|
|
||||||
|
# Turn into normal user again
|
||||||
|
self.user.is_superuser = False
|
||||||
|
self.user.save()
|
||||||
|
|
||||||
|
|
||||||
class ColorThemeTest(TestCase):
|
class ColorThemeTest(TestCase):
|
||||||
"""Tests for ColorTheme."""
|
"""Tests for ColorTheme."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user