Setting refactor (#7404)

* Add helper functions to set/get settings

* Refactor instances of get_setting

* UPdates

* Fix for task

* Add debug messages

- Work out what is going on in CI

* add more debug

- Cannot reproduce locally?

* More debug...

* Remove debug prints

* Add better debug msg

* Simplify unit test

* Increase timeout for plugin tests

* Update validator code
This commit is contained in:
Oliver 2024-06-13 12:14:43 +10:00 committed by GitHub
parent 4c7a74ef05
commit 129975adc6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 235 additions and 316 deletions

View File

@ -14,6 +14,7 @@ from django.db.utils import IntegrityError, OperationalError
import InvenTree.conversion
import InvenTree.ready
import InvenTree.tasks
from common.settings import get_global_setting, set_global_setting
from InvenTree.config import get_setting
logger = logging.getLogger('inventree')
@ -238,8 +239,6 @@ class InvenTreeConfig(AppConfig):
- If a fixed SITE_URL is specified (via configuration), it should override the INVENTREE_BASE_URL setting
- If multi-site support is enabled, update the site URL for the current site
"""
import common.models
if not InvenTree.ready.canAppAccessDatabase():
return
@ -248,13 +247,8 @@ class InvenTreeConfig(AppConfig):
if settings.SITE_URL:
try:
if (
common.models.InvenTreeSetting.get_setting('INVENTREE_BASE_URL')
!= settings.SITE_URL
):
common.models.InvenTreeSetting.set_setting(
'INVENTREE_BASE_URL', settings.SITE_URL
)
if get_global_setting('INVENTREE_BASE_URL') != settings.SITE_URL:
set_global_setting('INVENTREE_BASE_URL', settings.SITE_URL)
logger.info('Updated INVENTREE_SITE_URL to %s', settings.SITE_URL)
except Exception:
pass

View File

@ -14,6 +14,7 @@ from rest_framework.fields import URLField as RestURLField
from rest_framework.fields import empty
import InvenTree.helpers
from common.settings import get_global_setting
from .validators import AllowedURLValidator, allowable_url_schemes
@ -32,11 +33,7 @@ class InvenTreeRestURLField(RestURLField):
def run_validation(self, data=empty):
"""Override default validation behaviour for this field type."""
import common.models
strict_urls = common.models.InvenTreeSetting.get_setting(
'INVENTREE_STRICT_URLS', True, cache=False
)
strict_urls = get_global_setting('INVENTREE_STRICT_URLS', True, cache=False)
if not strict_urls and data is not empty and '://' not in data:
# Validate as if there were a schema provided

View File

@ -24,7 +24,7 @@ from rest_framework import serializers
import InvenTree.helpers_model
import InvenTree.sso
from common.models import InvenTreeSetting
from common.settings import get_global_setting
from InvenTree.exceptions import log_error
logger = logging.getLogger('inventree')
@ -172,12 +172,12 @@ class CustomSignupForm(SignupForm):
def __init__(self, *args, **kwargs):
"""Check settings to influence which fields are needed."""
kwargs['email_required'] = InvenTreeSetting.get_setting('LOGIN_MAIL_REQUIRED')
kwargs['email_required'] = get_global_setting('LOGIN_MAIL_REQUIRED')
super().__init__(*args, **kwargs)
# check for two mail fields
if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'):
if get_global_setting('LOGIN_SIGNUP_MAIL_TWICE'):
self.fields['email2'] = forms.EmailField(
label=_('Email (again)'),
widget=forms.TextInput(
@ -189,7 +189,7 @@ class CustomSignupForm(SignupForm):
)
# check for two password fields
if not InvenTreeSetting.get_setting('LOGIN_SIGNUP_PWD_TWICE'):
if not get_global_setting('LOGIN_SIGNUP_PWD_TWICE'):
self.fields.pop('password2')
# reorder fields
@ -202,7 +202,7 @@ class CustomSignupForm(SignupForm):
cleaned_data = super().clean()
# check for two mail fields
if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'):
if get_global_setting('LOGIN_SIGNUP_MAIL_TWICE'):
email = cleaned_data.get('email')
email2 = cleaned_data.get('email2')
if (email and email2) and email != email2:
@ -213,10 +213,7 @@ class CustomSignupForm(SignupForm):
def registration_enabled():
"""Determine whether user registration is enabled."""
if (
InvenTreeSetting.get_setting('LOGIN_ENABLE_REG')
or InvenTree.sso.registration_enabled()
):
if get_global_setting('LOGIN_ENABLE_REG') or InvenTree.sso.registration_enabled():
if settings.EMAIL_HOST:
return True
else:
@ -240,9 +237,7 @@ class RegistratonMixin:
def clean_email(self, email):
"""Check if the mail is valid to the pattern in LOGIN_SIGNUP_MAIL_RESTRICTION (if enabled in settings)."""
mail_restriction = InvenTreeSetting.get_setting(
'LOGIN_SIGNUP_MAIL_RESTRICTION', None
)
mail_restriction = get_global_setting('LOGIN_SIGNUP_MAIL_RESTRICTION', None)
if not mail_restriction:
return super().clean_email(email)
@ -273,7 +268,7 @@ class RegistratonMixin:
user = super().save_user(request, user, form)
# Check if a default group is set in settings
start_group = InvenTreeSetting.get_setting('SIGNUP_GROUP')
start_group = get_global_setting('SIGNUP_GROUP')
if start_group:
try:
group = Group.objects.get(id=start_group)
@ -333,7 +328,7 @@ class CustomSocialAccountAdapter(
def is_auto_signup_allowed(self, request, sociallogin):
"""Check if auto signup is enabled in settings."""
if InvenTreeSetting.get_setting('LOGIN_SIGNUP_SSO_AUTO', True):
if get_global_setting('LOGIN_SIGNUP_SSO_AUTO', True):
return super().is_auto_signup_allowed(request, sociallogin)
return False
@ -385,7 +380,7 @@ class CustomRegisterSerializer(RegisterSerializer):
def __init__(self, instance=None, data=..., **kwargs):
"""Check settings to influence which fields are needed."""
kwargs['email_required'] = InvenTreeSetting.get_setting('LOGIN_MAIL_REQUIRED')
kwargs['email_required'] = get_global_setting('LOGIN_MAIL_REQUIRED')
super().__init__(instance, data, **kwargs)
def save(self, request):

View File

@ -15,7 +15,6 @@ from djmoney.contrib.exchange.models import convert_money
from djmoney.money import Money
from PIL import Image
import common.models
import InvenTree
import InvenTree.helpers_model
import InvenTree.version
@ -24,16 +23,12 @@ from common.notifications import (
NotificationBody,
trigger_notification,
)
from common.settings import get_global_setting
from InvenTree.format import format_money
logger = logging.getLogger('inventree')
def getSetting(key, backup_value=None):
"""Shortcut for reading a setting value from the database."""
return common.models.InvenTreeSetting.get_setting(key, backup_value=backup_value)
def get_base_url(request=None):
"""Return the base URL for the InvenTree server.
@ -44,6 +39,8 @@ def get_base_url(request=None):
3. If settings.SITE_URL is set (e.g. in the Django settings), use that
4. If the InvenTree setting INVENTREE_BASE_URL is set, use that
"""
import common.models
# Check if a request is provided
if request:
return request.build_absolute_uri('/')
@ -62,9 +59,7 @@ def get_base_url(request=None):
# Check if a global InvenTree setting is provided
try:
if site_url := common.models.InvenTreeSetting.get_setting(
'INVENTREE_BASE_URL', create=False
):
if site_url := get_global_setting('INVENTREE_BASE_URL', create=False):
return site_url
except (ProgrammingError, OperationalError):
pass
@ -112,25 +107,20 @@ def download_image_from_url(remote_url, timeout=2.5):
ValueError: Server responded with invalid 'Content-Length' value
TypeError: Response is not a valid image
"""
import common.models
# Check that the provided URL at least looks valid
validator = URLValidator()
validator(remote_url)
# Calculate maximum allowable image size (in bytes)
max_size = (
int(
common.models.InvenTreeSetting.get_setting(
'INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE'
)
)
* 1024
* 1024
int(get_global_setting('INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE')) * 1024 * 1024
)
# Add user specified user-agent to request (if specified)
user_agent = common.models.InvenTreeSetting.get_setting(
'INVENTREE_DOWNLOAD_FROM_URL_USER_AGENT'
)
user_agent = get_global_setting('INVENTREE_DOWNLOAD_FROM_URL_USER_AGENT')
if user_agent:
headers = {'User-Agent': user_agent}
else:
@ -216,6 +206,8 @@ def render_currency(
max_decimal_places: The maximum number of decimal places to render to. If unspecified, uses the PRICING_DECIMAL_PLACES setting.
include_symbol: If True, include the currency symbol in the output
"""
import common.models
if money in [None, '']:
return '-'
@ -231,19 +223,13 @@ def render_currency(
pass
if decimal_places is None:
decimal_places = common.models.InvenTreeSetting.get_setting(
'PRICING_DECIMAL_PLACES', 6
)
decimal_places = get_global_setting('PRICING_DECIMAL_PLACES', 6)
if min_decimal_places is None:
min_decimal_places = common.models.InvenTreeSetting.get_setting(
'PRICING_DECIMAL_PLACES_MIN', 0
)
min_decimal_places = get_global_setting('PRICING_DECIMAL_PLACES_MIN', 0)
if max_decimal_places is None:
max_decimal_places = common.models.InvenTreeSetting.get_setting(
'PRICING_DECIMAL_PLACES', 6
)
max_decimal_places = get_global_setting('PRICING_DECIMAL_PLACES', 6)
value = Decimal(str(money.amount)).normalize()
value = str(value)

View File

@ -15,7 +15,7 @@ from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
import InvenTree.sso
from common.models import InvenTreeSetting
from common.settings import get_global_setting
from InvenTree.mixins import CreateAPI, ListAPI, ListCreateAPI
from InvenTree.serializers import EmptySerializer, InvenTreeModelSerializer
@ -177,12 +177,10 @@ class SocialProviderListView(ListAPI):
data = {
'sso_enabled': InvenTree.sso.login_enabled(),
'sso_registration': InvenTree.sso.registration_enabled(),
'mfa_required': InvenTreeSetting.get_setting('LOGIN_ENFORCE_MFA'),
'mfa_required': get_global_setting('LOGIN_ENFORCE_MFA'),
'providers': provider_list,
'registration_enabled': InvenTreeSetting.get_setting('LOGIN_ENABLE_REG'),
'password_forgotten_enabled': InvenTreeSetting.get_setting(
'LOGIN_ENABLE_PWD_FORGOT'
),
'registration_enabled': get_global_setting('LOGIN_ENABLE_REG'),
'password_forgotten_enabled': get_global_setting('LOGIN_ENABLE_PWD_FORGOT'),
}
return Response(data)

View File

@ -2,7 +2,7 @@
import logging
from common.models import InvenTreeSetting
from common.settings import get_global_setting
from InvenTree.helpers import str2bool
logger = logging.getLogger('inventree')
@ -64,14 +64,14 @@ def provider_display_name(provider):
def login_enabled() -> bool:
"""Return True if SSO login is enabled."""
return str2bool(InvenTreeSetting.get_setting('LOGIN_ENABLE_SSO'))
return str2bool(get_global_setting('LOGIN_ENABLE_SSO'))
def registration_enabled() -> bool:
"""Return True if SSO registration is enabled."""
return str2bool(InvenTreeSetting.get_setting('LOGIN_ENABLE_SSO_REG'))
return str2bool(get_global_setting('LOGIN_ENABLE_SSO_REG'))
def auto_registration_enabled() -> bool:
"""Return True if SSO auto-registration is enabled."""
return str2bool(InvenTreeSetting.get_setting('LOGIN_SIGNUP_SSO_AUTO'))
return str2bool(get_global_setting('LOGIN_SIGNUP_SSO_AUTO'))

View File

@ -26,6 +26,7 @@ from maintenance_mode.core import (
set_maintenance_mode,
)
from common.settings import get_global_setting, set_global_setting
from InvenTree.config import get_setting
from plugin import registry
@ -90,7 +91,6 @@ def check_daily_holdoff(task_name: str, n_days: int = 1) -> bool:
Note that this function creates some *hidden* global settings (designated with the _ prefix),
which are used to keep a running track of when the particular task was was last run.
"""
from common.models import InvenTreeSetting
from InvenTree.ready import isInTestMode
if n_days <= 0:
@ -107,7 +107,7 @@ def check_daily_holdoff(task_name: str, n_days: int = 1) -> bool:
success_key = f'_{task_name}_SUCCESS'
# Check for recent success information
last_success = InvenTreeSetting.get_setting(success_key, '', cache=False)
last_success = get_global_setting(success_key, '', cache=False)
if last_success:
try:
@ -125,7 +125,7 @@ def check_daily_holdoff(task_name: str, n_days: int = 1) -> bool:
return False
# Check for any information we have about this task
last_attempt = InvenTreeSetting.get_setting(attempt_key, '', cache=False)
last_attempt = get_global_setting(attempt_key, '', cache=False)
if last_attempt:
try:
@ -152,22 +152,14 @@ def check_daily_holdoff(task_name: str, n_days: int = 1) -> bool:
def record_task_attempt(task_name: str):
"""Record that a multi-day task has been attempted *now*."""
from common.models import InvenTreeSetting
logger.info("Logging task attempt for '%s'", task_name)
InvenTreeSetting.set_setting(
f'_{task_name}_ATTEMPT', datetime.now().isoformat(), None
)
set_global_setting(f'_{task_name}_ATTEMPT', datetime.now().isoformat(), None)
def record_task_success(task_name: str):
"""Record that a multi-day task was successful *now*."""
from common.models import InvenTreeSetting
InvenTreeSetting.set_setting(
f'_{task_name}_SUCCESS', datetime.now().isoformat(), None
)
set_global_setting(f'_{task_name}_SUCCESS', datetime.now().isoformat(), None)
def offload_task(
@ -380,9 +372,7 @@ def delete_successful_tasks():
try:
from django_q.models import Success
from common.models import InvenTreeSetting
days = InvenTreeSetting.get_setting('INVENTREE_DELETE_TASKS_DAYS', 30)
days = get_global_setting('INVENTREE_DELETE_TASKS_DAYS', 30)
threshold = timezone.now() - timedelta(days=days)
# Delete successful tasks
@ -404,9 +394,7 @@ def delete_failed_tasks():
try:
from django_q.models import Failure
from common.models import InvenTreeSetting
days = InvenTreeSetting.get_setting('INVENTREE_DELETE_TASKS_DAYS', 30)
days = get_global_setting('INVENTREE_DELETE_TASKS_DAYS', 30)
threshold = timezone.now() - timedelta(days=days)
# Delete failed tasks
@ -426,9 +414,7 @@ def delete_old_error_logs():
try:
from error_report.models import Error
from common.models import InvenTreeSetting
days = InvenTreeSetting.get_setting('INVENTREE_DELETE_ERRORS_DAYS', 30)
days = get_global_setting('INVENTREE_DELETE_ERRORS_DAYS', 30)
threshold = timezone.now() - timedelta(days=days)
errors = Error.objects.filter(when__lte=threshold)
@ -448,13 +434,9 @@ def delete_old_error_logs():
def delete_old_notifications():
"""Delete old notification logs."""
try:
from common.models import (
InvenTreeSetting,
NotificationEntry,
NotificationMessage,
)
from common.models import NotificationEntry, NotificationMessage
days = InvenTreeSetting.get_setting('INVENTREE_DELETE_NOTIFICATIONS_DAYS', 30)
days = get_global_setting('INVENTREE_DELETE_NOTIFICATIONS_DAYS', 30)
threshold = timezone.now() - timedelta(days=days)
items = NotificationEntry.objects.filter(updated__lte=threshold)
@ -479,7 +461,6 @@ def delete_old_notifications():
def check_for_updates():
"""Check if there is an update for InvenTree."""
try:
import common.models
from common.notifications import trigger_superuser_notification
except AppRegistryNotReady: # pragma: no cover
# Apps not yet loaded!
@ -487,9 +468,7 @@ def check_for_updates():
return
interval = int(
common.models.InvenTreeSetting.get_setting(
'INVENTREE_UPDATE_CHECK_INTERVAL', 7, cache=False
)
get_global_setting('INVENTREE_UPDATE_CHECK_INTERVAL', 7, cache=False)
)
# Check if we should check for updates *today*
@ -538,7 +517,7 @@ def check_for_updates():
logger.info("Latest InvenTree version: '%s'", tag)
# Save the version to the database
common.models.InvenTreeSetting.set_setting('_INVENTREE_LATEST_VERSION', tag, None)
set_global_setting('_INVENTREE_LATEST_VERSION', tag, None)
# Record that this task was successful
record_task_success('check_for_updates')
@ -572,7 +551,6 @@ def update_exchange_rates(force: bool = False):
from djmoney.contrib.exchange.models import Rate
from common.currency import currency_code_default, currency_codes
from common.models import InvenTreeSetting
from InvenTree.exchange import InvenTreeExchange
except AppRegistryNotReady: # pragma: no cover
# Apps not yet loaded!
@ -585,9 +563,7 @@ def update_exchange_rates(force: bool = False):
return
if not force:
interval = int(
InvenTreeSetting.get_setting('CURRENCY_UPDATE_INTERVAL', 1, cache=False)
)
interval = int(get_global_setting('CURRENCY_UPDATE_INTERVAL', 1, cache=False))
if not check_daily_holdoff('update_exchange_rates', interval):
logger.info('Skipping exchange rate update (interval not reached)')
@ -617,15 +593,11 @@ def update_exchange_rates(force: bool = False):
@scheduled_task(ScheduledTask.DAILY)
def run_backup():
"""Run the backup command."""
from common.models import InvenTreeSetting
if not InvenTreeSetting.get_setting('INVENTREE_BACKUP_ENABLE', False, cache=False):
if not get_global_setting('INVENTREE_BACKUP_ENABLE', False, cache=False):
# Backups are not enabled - exit early
return
interval = int(
InvenTreeSetting.get_setting('INVENTREE_BACKUP_DAYS', 1, cache=False)
)
interval = int(get_global_setting('INVENTREE_BACKUP_DAYS', 1, cache=False))
# Check if should run this task *today*
if not check_daily_holdoff('run_backup', interval):
@ -655,13 +627,12 @@ def check_for_migrations(force: bool = False, reload_registry: bool = True):
If the setting auto_update is enabled we will start updating.
"""
from common.models import InvenTreeSetting
from plugin import registry
def set_pending_migrations(n: int):
"""Helper function to inform the user about pending migrations."""
logger.info('There are %s pending migrations', n)
InvenTreeSetting.set_setting('_PENDING_MIGRATIONS', n, None)
set_global_setting('_PENDING_MIGRATIONS', n, None)
logger.info('Checking for pending database migrations')

View File

@ -17,6 +17,7 @@ import InvenTree.helpers
import InvenTree.helpers_model
import plugin.models
from common.currency import currency_code_default
from common.settings import get_global_setting
from InvenTree import settings, version
from plugin import registry
from plugin.plugin import InvenTreePlugin
@ -135,7 +136,7 @@ def inventree_in_debug_mode(*args, **kwargs):
@register.simple_tag()
def inventree_show_about(user, *args, **kwargs):
"""Return True if the about modal should be shown."""
if common.models.InvenTreeSetting.get_setting('INVENTREE_RESTRICT_ABOUT'):
if get_global_setting('INVENTREE_RESTRICT_ABOUT'):
# Return False if the user is not a superuser, or no user information is provided
if not user or not user.is_superuser:
return False
@ -373,7 +374,7 @@ def settings_value(key, *args, **kwargs):
return common.models.InvenTreeUserSetting.get_setting(key)
return common.models.InvenTreeUserSetting.get_setting(key, user=kwargs['user'])
return common.models.InvenTreeSetting.get_setting(key)
return get_global_setting(key)
@register.simple_tag()

View File

@ -122,6 +122,7 @@ class InvenTreeTaskTests(TestCase):
def test_task_check_for_updates(self):
"""Test the task check_for_updates."""
# Check that setting should be empty
InvenTreeSetting.set_setting('_INVENTREE_LATEST_VERSION', '')
self.assertEqual(InvenTreeSetting.get_setting('_INVENTREE_LATEST_VERSION'), '')
# Get new version

View File

@ -16,6 +16,8 @@ from django.conf import settings
from dulwich.repo import NotGitRepository, Repo
from common.settings import get_global_setting
from .api_version import INVENTREE_API_TEXT, INVENTREE_API_VERSION
# InvenTree software version
@ -51,17 +53,14 @@ def checkMinPythonVersion():
def inventreeInstanceName():
"""Returns the InstanceName settings for the current database."""
import common.models
return common.models.InvenTreeSetting.get_setting('INVENTREE_INSTANCE', '')
return get_global_setting('INVENTREE_INSTANCE', '')
def inventreeInstanceTitle():
"""Returns the InstanceTitle for the current database."""
import common.models
if get_global_setting('INVENTREE_INSTANCE_TITLE', False):
return get_global_setting('INVENTREE_INSTANCE', 'InvenTree')
if common.models.InvenTreeSetting.get_setting('INVENTREE_INSTANCE_TITLE', False):
return common.models.InvenTreeSetting.get_setting('INVENTREE_INSTANCE', '')
return 'InvenTree'
@ -122,9 +121,7 @@ def isInvenTreeUpToDate():
A background task periodically queries GitHub for latest version, and stores it to the database as "_INVENTREE_LATEST_VERSION"
"""
import common.models
latest = common.models.InvenTreeSetting.get_setting(
latest = get_global_setting(
'_INVENTREE_LATEST_VERSION', backup_value=None, create=False
)

View File

@ -36,6 +36,7 @@ import InvenTree.tasks
import common.models
from common.notifications import trigger_notification, InvenTreeNotificationBodies
from common.settings import get_global_setting
from plugin.events import trigger_event
import part.models
@ -136,7 +137,7 @@ class Build(
super().clean()
if common.models.InvenTreeSetting.get_setting('BUILDORDER_REQUIRE_RESPONSIBLE'):
if get_global_setting('BUILDORDER_REQUIRE_RESPONSIBLE'):
if not self.responsible:
raise ValidationError({
'responsible': _('Responsible user or group must be specified')

View File

@ -12,6 +12,7 @@ from django.db.models import Sum
from InvenTree import status_codes as status
import common.models
from common.settings import set_global_setting
import build.tasks
from build.models import Build, BuildItem, BuildLine, generate_next_build_reference
from part.models import Part, BomItem, BomItemSubstitute, PartTestTemplate
@ -215,7 +216,7 @@ class BuildTest(BuildTestBase):
def test_ref_int(self):
"""Test the "integer reference" field used for natural sorting"""
# Set build reference to new value
common.models.InvenTreeSetting.set_setting('BUILDORDER_REFERENCE_PATTERN', 'BO-{ref}-???', change_user=None)
set_global_setting('BUILDORDER_REFERENCE_PATTERN', 'BO-{ref}-???', change_user=None)
refs = {
'BO-123-456': 123,
@ -238,7 +239,7 @@ class BuildTest(BuildTestBase):
self.assertEqual(build.reference_int, ref_int)
# Set build reference back to default value
common.models.InvenTreeSetting.set_setting('BUILDORDER_REFERENCE_PATTERN', 'BO-{ref:04d}', change_user=None)
set_global_setting('BUILDORDER_REFERENCE_PATTERN', 'BO-{ref:04d}', change_user=None)
def test_ref_validation(self):
"""Test that the reference field validation works as expected"""
@ -271,7 +272,7 @@ class BuildTest(BuildTestBase):
)
# Try a new validator pattern
common.models.InvenTreeSetting.set_setting('BUILDORDER_REFERENCE_PATTERN', '{ref}-BO', change_user=None)
set_global_setting('BUILDORDER_REFERENCE_PATTERN', '{ref}-BO', change_user=None)
for ref in [
'1234-BO',
@ -285,11 +286,11 @@ class BuildTest(BuildTestBase):
)
# Set build reference back to default value
common.models.InvenTreeSetting.set_setting('BUILDORDER_REFERENCE_PATTERN', 'BO-{ref:04d}', change_user=None)
set_global_setting('BUILDORDER_REFERENCE_PATTERN', 'BO-{ref:04d}', change_user=None)
def test_next_ref(self):
"""Test that the next reference is automatically generated"""
common.models.InvenTreeSetting.set_setting('BUILDORDER_REFERENCE_PATTERN', 'XYZ-{ref:06d}', change_user=None)
set_global_setting('BUILDORDER_REFERENCE_PATTERN', 'XYZ-{ref:06d}', change_user=None)
build = Build.objects.create(
part=self.assembly,
@ -311,7 +312,7 @@ class BuildTest(BuildTestBase):
self.assertEqual(build.reference_int, 988)
# Set build reference back to default value
common.models.InvenTreeSetting.set_setting('BUILDORDER_REFERENCE_PATTERN', 'BO-{ref:04d}', change_user=None)
set_global_setting('BUILDORDER_REFERENCE_PATTERN', 'BO-{ref:04d}', change_user=None)
def test_init(self):
"""Perform some basic tests before we start the ball rolling"""
@ -647,7 +648,7 @@ class BuildTest(BuildTestBase):
"""Test the prevention completion when a required test is missing feature"""
# with required tests incompleted the save should fail
common.models.InvenTreeSetting.set_setting('PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS', True, change_user=None)
set_global_setting('PREVENT_BUILD_COMPLETION_HAVING_INCOMPLETED_TESTS', True, change_user=None)
with self.assertRaises(ValidationError):
self.build_w_tests_trackable.complete_build_output(self.stockitem_with_required_test, None)

View File

@ -22,6 +22,7 @@ from rest_framework.views import APIView
import common.models
import common.serializers
from common.settings import get_global_setting
from generic.states.api import AllStatusViews, StatusView
from InvenTree.api import BulkDeleteMixin, MetadataView
from InvenTree.config import CONFIG_LOOKUPS
@ -149,7 +150,7 @@ class CurrencyExchangeView(APIView):
updated = None
response = {
'base_currency': common.models.InvenTreeSetting.get_setting(
'base_currency': get_global_setting(
'INVENTREE_DEFAULT_CURRENCY', backup_value='USD'
),
'exchange_rates': {},

View File

@ -5,6 +5,7 @@ import logging
from django.apps import AppConfig
import InvenTree.ready
from common.settings import get_global_setting, set_global_setting
logger = logging.getLogger('inventree')
@ -27,16 +28,12 @@ class CommonConfig(AppConfig):
def clear_restart_flag(self):
"""Clear the SERVER_RESTART_REQUIRED setting."""
try:
import common.models
if common.models.InvenTreeSetting.get_setting(
if get_global_setting(
'SERVER_RESTART_REQUIRED', backup_value=False, create=False, cache=False
):
logger.info('Clearing SERVER_RESTART_REQUIRED flag')
if not InvenTree.ready.isImportingData():
common.models.InvenTreeSetting.set_setting(
'SERVER_RESTART_REQUIRED', False, None
)
set_global_setting('SERVER_RESTART_REQUIRED', False, None)
except Exception:
pass

View File

@ -17,7 +17,7 @@ logger = logging.getLogger('inventree')
def currency_code_default():
"""Returns the default currency code (or USD if not specified)."""
from common.models import InvenTreeSetting
from common.settings import get_global_setting
try:
cached_value = cache.get('currency_code_default', '')
@ -28,7 +28,7 @@ def currency_code_default():
return cached_value
try:
code = InvenTreeSetting.get_setting(
code = get_global_setting(
'INVENTREE_DEFAULT_CURRENCY', backup_value='', create=True, cache=True
)
except Exception: # pragma: no cover
@ -59,9 +59,9 @@ def currency_codes_default_list() -> str:
def currency_codes() -> list:
"""Returns the current currency codes."""
from common.models import InvenTreeSetting
from common.settings import get_global_setting
codes = InvenTreeSetting.get_setting('CURRENCY_CODES', '', create=False).strip()
codes = get_global_setting('CURRENCY_CODES', '', create=False).strip()
if not codes:
codes = currency_codes_default_list()
@ -150,6 +150,9 @@ def currency_exchange_plugins() -> list:
except Exception:
plugs = []
if len(plugs) == 0:
return None
return [('', _('No plugin'))] + [(plug.slug, plug.human_name) for plug in plugs]

View File

@ -1,6 +1,44 @@
"""User-configurable settings for the common app."""
def get_global_setting(key, backup_value=None, **kwargs):
"""Return the value of a global setting using the provided key."""
from common.models import InvenTreeSetting
kwargs['backup_value'] = backup_value
return InvenTreeSetting.get_setting(key, **kwargs)
def set_global_setting(key, value, change_user=None, create=True, **kwargs):
"""Set the value of a global setting using the provided key."""
from common.models import InvenTreeSetting
kwargs['change_user'] = change_user
kwargs['create'] = create
return InvenTreeSetting.set_setting(key, value, **kwargs)
def get_user_setting(key, user, backup_value=None, **kwargs):
"""Return the value of a user-specific setting using the provided key."""
from common.models import InvenTreeUserSetting
kwargs['user'] = user
kwargs['backup_value'] = backup_value
return InvenTreeUserSetting.get_setting(key, **kwargs)
def set_user_setting(key, value, user, **kwargs):
"""Set the value of a user-specific setting using the provided key."""
from common.models import InvenTreeUserSetting
kwargs['user'] = user
return InvenTreeUserSetting.set_setting(key, value, **kwargs)
def stock_expiry_enabled():
"""Returns True if the stock expiry feature is enabled."""
from common.models import InvenTreeSetting

View File

@ -18,6 +18,7 @@ from django.urls import reverse
import PIL
from common.settings import get_global_setting, set_global_setting
from InvenTree.helpers import str2bool
from InvenTree.unit_test import InvenTreeAPITestCase, InvenTreeTestCase, PluginMixin
from plugin import registry
@ -273,13 +274,19 @@ class SettingsTest(InvenTreeTestCase):
print(f"run_settings_check failed for user setting '{key}'")
raise exc
@override_settings(SITE_URL=None)
@override_settings(SITE_URL=None, PLUGIN_TESTING=True, PLUGIN_TESTING_SETUP=True)
def test_defaults(self):
"""Populate the settings with default values."""
N = len(InvenTreeSetting.SETTINGS.keys())
for key in InvenTreeSetting.SETTINGS.keys():
value = InvenTreeSetting.get_setting_default(key)
InvenTreeSetting.set_setting(key, value, self.user)
try:
InvenTreeSetting.set_setting(key, value, change_user=self.user)
except Exception as exc:
print(f"test_defaults: Failed to set default value for setting '{key}'")
raise exc
self.assertEqual(value, InvenTreeSetting.get_setting(key))
@ -287,11 +294,6 @@ class SettingsTest(InvenTreeTestCase):
setting = InvenTreeSetting.get_setting_object(key)
if setting.is_bool():
if setting.default_value in ['', None]:
raise ValueError(
f'Default value for boolean setting {key} not provided'
) # pragma: no cover
if setting.default_value not in [True, False]:
raise ValueError(
f'Non-boolean default value specified for {key}'
@ -975,17 +977,13 @@ class CommonTest(InvenTreeAPITestCase):
from plugin import registry
# set flag true
common.models.InvenTreeSetting.set_setting(
'SERVER_RESTART_REQUIRED', True, None
)
set_global_setting('SERVER_RESTART_REQUIRED', True, None)
# reload the app
registry.reload_plugins()
# now it should be false again
self.assertFalse(
common.models.InvenTreeSetting.get_setting('SERVER_RESTART_REQUIRED')
)
self.assertFalse(get_global_setting('SERVER_RESTART_REQUIRED'))
def test_config_api(self):
"""Test config URLs."""

View File

@ -5,7 +5,7 @@ import re
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
import InvenTree.helpers_model
from common.settings import get_global_setting
def validate_notes_model_type(value):
@ -13,6 +13,7 @@ def validate_notes_model_type(value):
The provided value must map to a model which implements the 'InvenTreeNotesMixin'.
"""
import InvenTree.helpers_model
import InvenTree.models
if not value:
@ -31,11 +32,9 @@ def validate_notes_model_type(value):
def validate_decimal_places_min(value):
"""Validator for PRICING_DECIMAL_PLACES_MIN setting."""
from common.models import InvenTreeSetting
try:
value = int(value)
places_max = int(InvenTreeSetting.get_setting('PRICING_DECIMAL_PLACES'))
places_max = int(get_global_setting('PRICING_DECIMAL_PLACES', create=False))
except Exception:
return
@ -45,11 +44,9 @@ def validate_decimal_places_min(value):
def validate_decimal_places_max(value):
"""Validator for PRICING_DECIMAL_PLACES_MAX setting."""
from common.models import InvenTreeSetting
try:
value = int(value)
places_min = int(InvenTreeSetting.get_setting('PRICING_DECIMAL_PLACES_MIN'))
places_min = int(get_global_setting('PRICING_DECIMAL_PLACES_MIN', create=False))
except Exception:
return

View File

@ -35,6 +35,7 @@ import stock.models
import users.models as UserModels
from common.currency import currency_code_default
from common.notifications import InvenTreeNotificationBodies
from common.settings import get_global_setting
from company.models import Address, Company, Contact, SupplierPart
from generic.states import StateTransitionMixin
from InvenTree.exceptions import log_error
@ -44,7 +45,7 @@ from InvenTree.fields import (
RoundingDecimalField,
)
from InvenTree.helpers import decimal2string, pui_url
from InvenTree.helpers_model import getSetting, notify_responsible
from InvenTree.helpers_model import notify_responsible
from order.status_codes import (
PurchaseOrderStatus,
PurchaseOrderStatusGroups,
@ -232,9 +233,7 @@ class Order(
# Check if a responsible owner is required for this order type
if self.REQUIRE_RESPONSIBLE_SETTING:
if common_models.InvenTreeSetting.get_setting(
self.REQUIRE_RESPONSIBLE_SETTING, backup_value=False
):
if get_global_setting(self.REQUIRE_RESPONSIBLE_SETTING, backup_value=False):
if not self.responsible:
raise ValidationError({
'responsible': _('Responsible user or group must be specified')
@ -820,9 +819,7 @@ class PurchaseOrder(TotalPriceMixin, Order):
# Has this order been completed?
if len(self.pending_line_items()) == 0:
if common_models.InvenTreeSetting.get_setting(
'PURCHASEORDER_AUTO_COMPLETE', True
):
if get_global_setting('PURCHASEORDER_AUTO_COMPLETE', True):
self.received_by = user
self.complete_order() # This will save the model
@ -1073,7 +1070,7 @@ class SalesOrder(TotalPriceMixin, Order):
return False
bypass_shipped = InvenTree.helpers.str2bool(
common_models.InvenTreeSetting.get_setting('SALESORDER_SHIP_COMPLETE')
get_global_setting('SALESORDER_SHIP_COMPLETE')
)
if bypass_shipped or self.status == SalesOrderStatus.SHIPPED:
@ -1231,7 +1228,7 @@ def after_save_sales_order(sender, instance: SalesOrder, created: bool, **kwargs
if created:
# A new SalesOrder has just been created
if getSetting('SALESORDER_DEFAULT_SHIPMENT'):
if get_global_setting('SALESORDER_DEFAULT_SHIPMENT'):
# Create default shipment
SalesOrderShipment.objects.create(order=instance, reference='1')

View File

@ -50,6 +50,7 @@ from build import models as BuildModels
from build.status_codes import BuildStatusGroups
from common.currency import currency_code_default
from common.models import InvenTreeSetting
from common.settings import get_global_setting, set_global_setting
from company.models import SupplierPart
from InvenTree import helpers, validators
from InvenTree.fields import InvenTreeURLField
@ -482,9 +483,7 @@ class Part(
if self.active:
raise ValidationError(_('Cannot delete this part as it is still active'))
if not common.models.InvenTreeSetting.get_setting(
'PART_ALLOW_DELETE_FROM_ASSEMBLY', cache=False
):
if not get_global_setting('PART_ALLOW_DELETE_FROM_ASSEMBLY', cache=False):
if BomItem.objects.filter(sub_part=self).exists():
raise ValidationError(
_('Cannot delete this part as it is used in an assembly')
@ -649,9 +648,7 @@ class Part(
raise ValidationError({'IPN': exc.message})
# If we get to here, none of the plugins have raised an error
pattern = common.models.InvenTreeSetting.get_setting(
'PART_IPN_REGEX', '', create=False
).strip()
pattern = get_global_setting('PART_IPN_REGEX', '', create=False).strip()
if pattern:
match = re.search(pattern, self.IPN)
@ -719,9 +716,7 @@ class Part(
from part.models import Part
from stock.models import StockItem
if common.models.InvenTreeSetting.get_setting(
'SERIAL_NUMBER_GLOBALLY_UNIQUE', False
):
if get_global_setting('SERIAL_NUMBER_GLOBALLY_UNIQUE', False):
# Serial number must be unique across *all* parts
parts = Part.objects.all()
else:
@ -775,9 +770,7 @@ class Part(
)
# Generate a query for any stock items for this part variant tree with non-empty serial numbers
if common.models.InvenTreeSetting.get_setting(
'SERIAL_NUMBER_GLOBALLY_UNIQUE', False
):
if get_global_setting('SERIAL_NUMBER_GLOBALLY_UNIQUE', False):
# Serial numbers are unique across all parts
pass
else:
@ -831,9 +824,7 @@ class Part(
super().validate_unique(exclude)
# User can decide whether duplicate IPN (Internal Part Number) values are allowed
allow_duplicate_ipn = common.models.InvenTreeSetting.get_setting(
'PART_ALLOW_DUPLICATE_IPN'
)
allow_duplicate_ipn = get_global_setting('PART_ALLOW_DUPLICATE_IPN')
# Raise an error if an IPN is set, and it is a duplicate
if self.IPN and not allow_duplicate_ipn:
@ -2749,11 +2740,11 @@ class PartPricing(common.models.MetaMixin):
purchase_max = purchase_cost
# Also check if manual stock item pricing is included
if InvenTreeSetting.get_setting('PRICING_USE_STOCK_PRICING', True):
if get_global_setting('PRICING_USE_STOCK_PRICING', True):
items = self.part.stock_items.all()
# Limit to stock items updated within a certain window
days = int(InvenTreeSetting.get_setting('PRICING_STOCK_ITEM_AGE_DAYS', 0))
days = int(get_global_setting('PRICING_STOCK_ITEM_AGE_DAYS', 0))
if days > 0:
date_threshold = InvenTree.helpers.current_date() - timedelta(days=days)
@ -2789,7 +2780,7 @@ class PartPricing(common.models.MetaMixin):
min_int_cost = None
max_int_cost = None
if InvenTreeSetting.get_setting('PART_INTERNAL_PRICE', False):
if get_global_setting('PART_INTERNAL_PRICE', False):
# Only calculate internal pricing if internal pricing is enabled
for pb in self.part.internalpricebreaks.all():
cost = self.convert(pb.price)
@ -2865,7 +2856,7 @@ class PartPricing(common.models.MetaMixin):
variant_min = None
variant_max = None
active_only = InvenTreeSetting.get_setting('PRICING_ACTIVE_VARIANTS', False)
active_only = get_global_setting('PRICING_ACTIVE_VARIANTS', False)
if self.part.is_template:
variants = self.part.get_descendants(include_self=False)
@ -2907,11 +2898,11 @@ class PartPricing(common.models.MetaMixin):
max_costs = [self.bom_cost_max, self.purchase_cost_max, self.internal_cost_max]
purchase_history_override = InvenTreeSetting.get_setting(
purchase_history_override = get_global_setting(
'PRICING_PURCHASE_HISTORY_OVERRIDES_SUPPLIER', False
)
if InvenTreeSetting.get_setting('PRICING_USE_SUPPLIER_PRICING', True):
if get_global_setting('PRICING_USE_SUPPLIER_PRICING', True):
# Add supplier pricing data, *unless* historical pricing information should override
if self.purchase_cost_min is None or not purchase_history_override:
min_costs.append(self.supplier_price_min)
@ -2919,7 +2910,7 @@ class PartPricing(common.models.MetaMixin):
if self.purchase_cost_max is None or not purchase_history_override:
max_costs.append(self.supplier_price_max)
if InvenTreeSetting.get_setting('PRICING_USE_VARIANT_PRICING', True):
if get_global_setting('PRICING_USE_VARIANT_PRICING', True):
# Include variant pricing in overall calculations
min_costs.append(self.variant_cost_min)
max_costs.append(self.variant_cost_max)
@ -2946,7 +2937,7 @@ class PartPricing(common.models.MetaMixin):
if overall_max is None or cost > overall_max:
overall_max = cost
if InvenTreeSetting.get_setting('PART_BOM_USE_INTERNAL_PRICE', False):
if get_global_setting('PART_BOM_USE_INTERNAL_PRICE', False):
# Check if internal pricing should override other pricing
if self.internal_cost_min is not None:
overall_min = self.internal_cost_min
@ -3774,7 +3765,7 @@ class PartParameter(InvenTree.models.InvenTreeMetadataModel):
super().clean()
# Validate the parameter data against the template units
if InvenTreeSetting.get_setting(
if get_global_setting(
'PART_PARAMETER_ENFORCE_UNITS', True, cache=False, create=False
):
if self.template.units:
@ -3916,7 +3907,7 @@ class PartCategoryParameterTemplate(InvenTree.models.InvenTreeMetadataModel):
if (
self.default_value
and InvenTreeSetting.get_setting(
and get_global_setting(
'PART_PARAMETER_ENFORCE_UNITS', True, cache=False, create=False
)
and self.parameter_template.units
@ -4325,9 +4316,7 @@ class BomItem(
def price_range(self, internal=False):
"""Return the price-range for this BOM item."""
# get internal price setting
use_internal = common.models.InvenTreeSetting.get_setting(
'PART_BOM_USE_INTERNAL_PRICE', False
)
use_internal = get_global_setting('PART_BOM_USE_INTERNAL_PRICE', False)
prange = self.sub_part.get_price_range(
self.quantity, internal=use_internal and internal
)

View File

@ -1,38 +1,38 @@
"""User-configurable settings for the Part app."""
from common.models import InvenTreeSetting
from common.settings import get_global_setting
def part_assembly_default():
"""Returns the default value for the 'assembly' field of a Part object."""
return InvenTreeSetting.get_setting('PART_ASSEMBLY')
return get_global_setting('PART_ASSEMBLY')
def part_template_default():
"""Returns the default value for the 'is_template' field of a Part object."""
return InvenTreeSetting.get_setting('PART_TEMPLATE')
return get_global_setting('PART_TEMPLATE')
def part_virtual_default():
"""Returns the default value for the 'is_virtual' field of Part object."""
return InvenTreeSetting.get_setting('PART_VIRTUAL')
return get_global_setting('PART_VIRTUAL')
def part_component_default():
"""Returns the default value for the 'component' field of a Part object."""
return InvenTreeSetting.get_setting('PART_COMPONENT')
return get_global_setting('PART_COMPONENT')
def part_purchaseable_default():
"""Returns the default value for the 'purchasable' field for a Part object."""
return InvenTreeSetting.get_setting('PART_PURCHASEABLE')
return get_global_setting('PART_PURCHASEABLE')
def part_salable_default():
"""Returns the default value for the 'salable' field for a Part object."""
return InvenTreeSetting.get_setting('PART_SALABLE')
return get_global_setting('PART_SALABLE')
def part_trackable_default():
"""Returns the default value for the 'trackable' field for a Part object."""
return InvenTreeSetting.get_setting('PART_TRACKABLE')
return get_global_setting('PART_TRACKABLE')

View File

@ -9,15 +9,14 @@ from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
import common.currency
import common.models
import common.notifications
import common.settings
import company.models
import InvenTree.helpers
import InvenTree.helpers_model
import InvenTree.tasks
import part.models
import part.stocktake
from common.settings import get_global_setting
from InvenTree.tasks import (
ScheduledTask,
check_daily_holdoff,
@ -99,7 +98,7 @@ def check_missing_pricing(limit=250):
pp.schedule_for_update()
# Find any parts which have 'old' pricing information
days = int(common.models.InvenTreeSetting.get_setting('PRICING_UPDATE_DAYS', 30))
days = int(get_global_setting('PRICING_UPDATE_DAYS', 30))
stale_date = datetime.now().date() - timedelta(days=days)
results = part.models.PartPricing.objects.filter(updated__lte=stale_date)[:limit]
@ -146,9 +145,7 @@ def scheduled_stocktake_reports():
# First let's delete any old stocktake reports
delete_n_days = int(
common.models.InvenTreeSetting.get_setting(
'STOCKTAKE_DELETE_REPORT_DAYS', 30, cache=False
)
get_global_setting('STOCKTAKE_DELETE_REPORT_DAYS', 30, cache=False)
)
threshold = datetime.now() - timedelta(days=delete_n_days)
old_reports = part.models.PartStocktakeReport.objects.filter(date__lt=threshold)
@ -158,17 +155,11 @@ def scheduled_stocktake_reports():
old_reports.delete()
# Next, check if stocktake functionality is enabled
if not common.models.InvenTreeSetting.get_setting(
'STOCKTAKE_ENABLE', False, cache=False
):
if not get_global_setting('STOCKTAKE_ENABLE', False, cache=False):
logger.info('Stocktake functionality is not enabled - exiting')
return
report_n_days = int(
common.models.InvenTreeSetting.get_setting(
'STOCKTAKE_AUTO_DAYS', 0, cache=False
)
)
report_n_days = int(get_global_setting('STOCKTAKE_AUTO_DAYS', 0, cache=False))
if report_n_days < 1:
logger.info('Stocktake auto reports are disabled, exiting')

View File

@ -18,6 +18,7 @@ from common.models import (
NotificationMessage,
)
from common.notifications import UIMessageNotification, storage
from common.settings import get_global_setting, set_global_setting
from InvenTree import version
from InvenTree.templatetags import inventree_extras
from InvenTree.unit_test import InvenTreeTestCase
@ -500,17 +501,17 @@ class PartSettingsTest(InvenTreeTestCase):
def test_custom(self):
"""Update some of the part values and re-test."""
for val in [True, False]:
InvenTreeSetting.set_setting('PART_COMPONENT', val, self.user)
InvenTreeSetting.set_setting('PART_PURCHASEABLE', val, self.user)
InvenTreeSetting.set_setting('PART_SALABLE', val, self.user)
InvenTreeSetting.set_setting('PART_TRACKABLE', val, self.user)
InvenTreeSetting.set_setting('PART_ASSEMBLY', val, self.user)
InvenTreeSetting.set_setting('PART_TEMPLATE', val, self.user)
set_global_setting('PART_COMPONENT', val, self.user)
set_global_setting('PART_PURCHASEABLE', val, self.user)
set_global_setting('PART_SALABLE', val, self.user)
set_global_setting('PART_TRACKABLE', val, self.user)
set_global_setting('PART_ASSEMBLY', val, self.user)
set_global_setting('PART_TEMPLATE', val, self.user)
self.assertEqual(val, InvenTreeSetting.get_setting('PART_COMPONENT'))
self.assertEqual(val, InvenTreeSetting.get_setting('PART_PURCHASEABLE'))
self.assertEqual(val, InvenTreeSetting.get_setting('PART_SALABLE'))
self.assertEqual(val, InvenTreeSetting.get_setting('PART_TRACKABLE'))
self.assertEqual(val, get_global_setting('PART_COMPONENT'))
self.assertEqual(val, get_global_setting('PART_PURCHASEABLE'))
self.assertEqual(val, get_global_setting('PART_SALABLE'))
self.assertEqual(val, get_global_setting('PART_TRACKABLE'))
part = self.make_part()
@ -546,7 +547,7 @@ class PartSettingsTest(InvenTreeTestCase):
part.validate_unique()
# Now update the settings so duplicate IPN values are *not* allowed
InvenTreeSetting.set_setting('PART_ALLOW_DUPLICATE_IPN', False, self.user)
set_global_setting('PART_ALLOW_DUPLICATE_IPN', False, self.user)
with self.assertRaises(ValidationError):
part = Part(name='Hello', description='A thing', IPN='IPN123', revision='C')

View File

@ -12,6 +12,7 @@ import company.models
import order.models
import part.models
import stock.models
from common.settings import get_global_setting, set_global_setting
from InvenTree.unit_test import InvenTreeTestCase
from order.status_codes import PurchaseOrderStatus
@ -172,7 +173,7 @@ class PartPricingTests(InvenTreeTestCase):
def test_internal_pricing(self):
"""Tests for internal price breaks."""
# Ensure internal pricing is enabled
common.models.InvenTreeSetting.set_setting('PART_INTERNAL_PRICE', True, None)
set_global_setting('PART_INTERNAL_PRICE', True, None)
pricing = self.part.pricing
@ -221,9 +222,7 @@ class PartPricingTests(InvenTreeTestCase):
)
# Ensure that initially, stock item pricing is disabled
common.models.InvenTreeSetting.set_setting(
'PRICING_USE_STOCK_PRICING', False, None
)
set_global_setting('PRICING_USE_STOCK_PRICING', False, None)
pricing = p.pricing
pricing.update_pricing()
@ -235,9 +234,7 @@ class PartPricingTests(InvenTreeTestCase):
self.assertIsNone(pricing.overall_max)
# Turn on stock pricing
common.models.InvenTreeSetting.set_setting(
'PRICING_USE_STOCK_PRICING', True, None
)
set_global_setting('PRICING_USE_STOCK_PRICING', True, None)
pricing.update_pricing()

View File

@ -8,6 +8,7 @@ from django.db.models.signals import post_delete, post_save
from django.dispatch.dispatcher import receiver
import InvenTree.exceptions
from common.settings import get_global_setting
from InvenTree.ready import canAppAccessDatabase, isImportingData
from InvenTree.tasks import offload_task
from plugin.registry import registry
@ -21,9 +22,7 @@ def trigger_event(event, *args, **kwargs):
This event will be stored in the database,
and the worker will respond to it later on.
"""
from common.models import InvenTreeSetting
if not InvenTreeSetting.get_setting('ENABLE_PLUGINS_EVENTS', False):
if not get_global_setting('ENABLE_PLUGINS_EVENTS', False):
# Do nothing if plugin events are not enabled
return
@ -50,12 +49,10 @@ def register_event(event, *args, **kwargs):
Note: This function is processed by the background worker,
as it performs multiple database access operations.
"""
from common.models import InvenTreeSetting
logger.debug("Registering triggered event: '%s'", event)
# Determine if there are any plugins which are interested in responding
if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting('ENABLE_PLUGINS_EVENTS'):
if settings.PLUGIN_TESTING or get_global_setting('ENABLE_PLUGINS_EVENTS'):
# Check if the plugin registry needs to be reloaded
registry.check_reload()

View File

@ -38,11 +38,9 @@ class AppMixin:
force_reload (bool, optional): Only reload base apps. Defaults to False.
full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False.
"""
from common.models import InvenTreeSetting
from common.settings import get_global_setting
if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting(
'ENABLE_PLUGINS_APP'
):
if settings.PLUGIN_TESTING or get_global_setting('ENABLE_PLUGINS_APP'):
logger.info('Registering IntegrationPlugin apps')
apps_changed = False

View File

@ -5,6 +5,7 @@ import logging
from django.conf import settings
from django.urls import include, re_path
from common.settings import get_global_setting
from plugin.urls import PLUGIN_BASE
logger = logging.getLogger('inventree')
@ -36,11 +37,7 @@ class UrlsMixin:
force_reload (bool, optional): Only reload base apps. Defaults to False.
full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False.
"""
from common.models import InvenTreeSetting
if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting(
'ENABLE_PLUGINS_URL'
):
if settings.PLUGIN_TESTING or get_global_setting('ENABLE_PLUGINS_URL'):
logger.info('Registering UrlsMixin Plugin')
urls_changed = False
# check whether an activated plugin extends UrlsMixin

View File

@ -25,6 +25,7 @@ from django.urls import clear_url_caches, path
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from common.settings import get_global_setting, set_global_setting
from InvenTree.config import get_plugin_dir
from InvenTree.ready import canAppAccessDatabase
@ -732,12 +733,10 @@ class PluginsRegistry:
# region plugin registry hash calculations
def update_plugin_hash(self):
"""When the state of the plugin registry changes, update the hash."""
from common.models import InvenTreeSetting
self.registry_hash = self.calculate_plugin_hash()
try:
old_hash = InvenTreeSetting.get_setting(
old_hash = get_global_setting(
'_PLUGIN_REGISTRY_HASH', '', create=False, cache=False
)
except Exception:
@ -748,7 +747,7 @@ class PluginsRegistry:
logger.debug(
'Updating plugin registry hash: %s', str(self.registry_hash)
)
InvenTreeSetting.set_setting(
set_global_setting(
'_PLUGIN_REGISTRY_HASH', self.registry_hash, change_user=None
)
except (OperationalError, ProgrammingError):
@ -776,8 +775,6 @@ class PluginsRegistry:
"""
from hashlib import md5
from common.models import InvenTreeSetting
data = md5()
# Hash for all loaded plugins
@ -789,7 +786,7 @@ class PluginsRegistry:
for k in self.plugin_settings_keys():
try:
val = InvenTreeSetting.get_setting(k, False, create=False)
val = get_global_setting(k, False, create=False)
msg = f'{k}-{val}'
data.update(msg.encode())
@ -800,8 +797,6 @@ class PluginsRegistry:
def check_reload(self):
"""Determine if the registry needs to be reloaded."""
from common.models import InvenTreeSetting
if settings.TESTING:
# Skip if running during unit testing
return
@ -817,9 +812,7 @@ class PluginsRegistry:
self.registry_hash = self.calculate_plugin_hash()
try:
reg_hash = InvenTreeSetting.get_setting(
'_PLUGIN_REGISTRY_HASH', '', create=False
)
reg_hash = get_global_setting('_PLUGIN_REGISTRY_HASH', '', create=False)
except Exception as exc:
logger.exception('Failed to retrieve plugin registry hash: %s', str(exc))
return

View File

@ -4,8 +4,8 @@ from django import template
from django.conf import settings as djangosettings
from django.urls import reverse
from common.models import InvenTreeSetting
from common.notifications import storage
from common.settings import get_global_setting
from plugin.registry import registry
register = template.Library()
@ -55,7 +55,7 @@ def navigation_enabled(*args, **kwargs):
"""Is plugin navigation enabled?"""
if djangosettings.PLUGIN_TESTING:
return True
return InvenTreeSetting.get_setting('ENABLE_PLUGINS_NAVIGATION') # pragma: no cover
return get_global_setting('ENABLE_PLUGINS_NAVIGATION') # pragma: no cover
@register.simple_tag()

View File

@ -35,7 +35,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
'packagename': 'invalid_package_name-asdads-asfd-asdf-asdf-asdf',
},
expected_code=400,
max_query_time=20,
max_query_time=30,
)
# valid - Pypi
@ -43,7 +43,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
url,
{'confirm': True, 'packagename': self.PKG_NAME},
expected_code=201,
max_query_time=20,
max_query_time=30,
).data
self.assertEqual(data['success'], 'Installed plugin successfully')
@ -53,7 +53,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
url,
{'confirm': True, 'url': self.PKG_URL},
expected_code=201,
max_query_time=20,
max_query_time=30,
).data
self.assertEqual(data['success'], 'Installed plugin successfully')
@ -63,7 +63,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
url,
{'confirm': True, 'url': self.PKG_URL, 'packagename': self.PKG_NAME},
expected_code=201,
max_query_time=20,
max_query_time=30,
).data
self.assertEqual(data['success'], 'Installed plugin successfully')

View File

@ -6,6 +6,8 @@ import logging
from django.utils.translation import gettext_lazy as _
from common.settings import get_global_setting
logger = logging.getLogger('inventree')
@ -67,10 +69,8 @@ def page_size(page_code):
def report_page_size_default():
"""Returns the default page size for PDF reports."""
from common.models import InvenTreeSetting
try:
page_size = InvenTreeSetting.get_setting('REPORT_DEFAULT_PAGE_SIZE', 'A4')
page_size = get_global_setting('REPORT_DEFAULT_PAGE_SIZE', 'A4')
except Exception as exc:
logger.exception('Error getting default page size: %s', str(exc))
page_size = 'A4'

View File

@ -15,7 +15,7 @@ from PIL import Image
import InvenTree.helpers
import InvenTree.helpers_model
import report.helpers
from common.models import InvenTreeSetting
from common.settings import get_global_setting
from company.models import Company
from part.models import Part
@ -87,7 +87,7 @@ def asset(filename):
filename = '' + filename
# If in debug mode, return URL to the image, not a local file
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE', cache=False)
debug_mode = get_global_setting('REPORT_DEBUG_MODE', cache=False)
# Test if the file actually exists
full_path = settings.MEDIA_ROOT.joinpath('report', 'assets', filename).resolve()
@ -132,7 +132,7 @@ def uploaded_image(
filename = '' + filename
# If in debug mode, return URL to the image, not a local file
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE', cache=False)
debug_mode = get_global_setting('REPORT_DEBUG_MODE', cache=False)
# Check if the file exists
if not filename:
@ -300,7 +300,7 @@ def logo_image(**kwargs):
- Otherwise, return a path to the default InvenTree logo
"""
# If in debug mode, return URL to the image, not a local file
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE', cache=False)
debug_mode = get_global_setting('REPORT_DEBUG_MODE', cache=False)
return InvenTree.helpers.getLogoImage(as_file=not debug_mode, **kwargs)

View File

@ -32,6 +32,7 @@ import InvenTree.ready
import InvenTree.tasks
import report.mixins
import report.models
from common.settings import get_global_setting
from company import models as CompanyModels
from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField
from order.status_codes import SalesOrderStatusGroups
@ -234,9 +235,7 @@ class StockLocation(
if user.is_superuser:
return True
ownership_enabled = common.models.InvenTreeSetting.get_setting(
'STOCK_OWNERSHIP_CONTROL'
)
ownership_enabled = get_global_setting('STOCK_OWNERSHIP_CONTROL')
if not ownership_enabled:
# Location ownership function is not enabled, so return True
@ -310,9 +309,7 @@ def default_delete_on_deplete():
Now, there is a user-configurable setting to govern default behaviour.
"""
try:
return common.models.InvenTreeSetting.get_setting(
'STOCK_DELETE_DEPLETED_DEFAULT', True
)
return get_global_setting('STOCK_DELETE_DEPLETED_DEFAULT', True)
except (IntegrityError, OperationalError):
# Revert to original default behaviour
return True
@ -996,9 +993,7 @@ class StockItem(
if user.is_superuser:
return True
ownership_enabled = common.models.InvenTreeSetting.get_setting(
'STOCK_OWNERSHIP_CONTROL'
)
ownership_enabled = get_global_setting('STOCK_OWNERSHIP_CONTROL')
if not ownership_enabled:
# Location ownership function is not enabled, so return True
@ -1027,7 +1022,7 @@ class StockItem(
today = InvenTree.helpers.current_date()
stale_days = common.models.InvenTreeSetting.get_setting('STOCK_STALE_DAYS')
stale_days = get_global_setting('STOCK_STALE_DAYS')
if stale_days <= 0:
return False
@ -1897,7 +1892,7 @@ class StockItem(
except InvalidOperation:
return False
allow_out_of_stock_transfer = common.models.InvenTreeSetting.get_setting(
allow_out_of_stock_transfer = get_global_setting(
'STOCK_ALLOW_OUT_OF_STOCK_TRANSFER', backup_value=False, cache=False
)

View File

@ -1,7 +1,7 @@
"""JSON serializers for Stock app."""
import logging
from datetime import datetime, timedelta
from datetime import timedelta
from decimal import Decimal
from django.core.exceptions import ValidationError as DjangoValidationError
@ -16,7 +16,6 @@ from sql_util.utils import SubqueryCount, SubquerySum
from taggit.serializers import TagListSerializerField
import build.models
import common.models
import company.models
import InvenTree.helpers
import InvenTree.serializers
@ -25,6 +24,7 @@ import part.filters as part_filters
import part.models as part_models
import stock.filters
import stock.status_codes
from common.settings import get_global_setting
from company.serializers import SupplierPartSerializer
from InvenTree.serializers import InvenTreeCurrencySerializer, InvenTreeDecimalField
from part.serializers import PartBriefSerializer, PartTestTemplateSerializer
@ -476,7 +476,7 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeTagModelSerializer):
)
# Add flag to indicate if the StockItem is stale
stale_days = common.models.InvenTreeSetting.get_setting('STOCK_STALE_DAYS')
stale_days = get_global_setting('STOCK_STALE_DAYS')
stale_date = InvenTree.helpers.current_date() + timedelta(days=stale_days)
stale_filter = (
StockItem.IN_STOCK_FILTER
@ -730,7 +730,7 @@ class InstallStockItemSerializer(serializers.Serializer):
parent_item = self.context['item']
parent_part = parent_item.part
if common.models.InvenTreeSetting.get_setting(
if get_global_setting(
'STOCK_ENFORCE_BOM_INSTALLATION', backup_value=True, cache=False
):
# Check if the selected part is in the Bill of Materials of the parent item

View File

@ -4,7 +4,7 @@ from django.http import HttpResponseRedirect
from django.urls import reverse
from django.views.generic import DetailView, ListView
import common.settings
from common.settings import get_global_setting
from InvenTree.views import InvenTreeRoleMixin
from plugin.views import InvenTreePluginViewMixin
@ -34,9 +34,7 @@ class StockIndex(InvenTreeRoleMixin, InvenTreePluginViewMixin, ListView):
# No 'ownership' checks are necessary for the top-level StockLocation view
context['user_owns_location'] = True
context['location_owner'] = None
context['ownership_enabled'] = common.models.InvenTreeSetting.get_setting(
'STOCK_OWNERSHIP_CONTROL'
)
context['ownership_enabled'] = get_global_setting('STOCK_OWNERSHIP_CONTROL')
return context
@ -53,9 +51,7 @@ class StockLocationDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailVi
"""Extend template context."""
context = super().get_context_data(**kwargs)
context['ownership_enabled'] = common.models.InvenTreeSetting.get_setting(
'STOCK_OWNERSHIP_CONTROL'
)
context['ownership_enabled'] = get_global_setting('STOCK_OWNERSHIP_CONTROL')
context['location_owner'] = context['location'].get_location_owner()
context['user_owns_location'] = context['location'].check_ownership(
self.request.user
@ -80,9 +76,7 @@ class StockItemDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView):
data['previous'] = self.object.get_next_serialized_item(reverse=True)
data['next'] = self.object.get_next_serialized_item()
data['ownership_enabled'] = common.models.InvenTreeSetting.get_setting(
'STOCK_OWNERSHIP_CONTROL'
)
data['ownership_enabled'] = get_global_setting('STOCK_OWNERSHIP_CONTROL')
data['item_owner'] = self.object.get_item_owner()
data['user_owns_item'] = self.object.check_ownership(self.request.user)

View File

@ -21,9 +21,9 @@ from django.utils.translation import gettext_lazy as _
from rest_framework.authtoken.models import Token as AuthToken
import common.models as common_models
import InvenTree.helpers
import InvenTree.models
from common.settings import get_global_setting
from InvenTree.ready import canAppAccessDatabase, isImportingData
logger = logging.getLogger('inventree')
@ -34,7 +34,7 @@ logger = logging.getLogger('inventree')
# string representation of a user
def user_model_str(self):
"""Function to override the default Django User __str__."""
if common_models.InvenTreeSetting.get_setting('DISPLAY_FULL_NAMES', cache=True):
if get_global_setting('DISPLAY_FULL_NAMES', cache=True):
if self.first_name or self.last_name:
return f'{self.first_name} {self.last_name}'
return self.username
@ -816,11 +816,8 @@ class Owner(models.Model):
def __str__(self):
"""Defines the owner string representation."""
if (
self.owner_type.name == 'user'
and common_models.InvenTreeSetting.get_setting(
'DISPLAY_FULL_NAMES', cache=True
)
if self.owner_type.name == 'user' and get_global_setting(
'DISPLAY_FULL_NAMES', cache=True
):
display_name = self.owner.get_full_name()
else:
@ -829,11 +826,8 @@ class Owner(models.Model):
def name(self):
"""Return the 'name' of this owner."""
if (
self.owner_type.name == 'user'
and common_models.InvenTreeSetting.get_setting(
'DISPLAY_FULL_NAMES', cache=True
)
if self.owner_type.name == 'user' and get_global_setting(
'DISPLAY_FULL_NAMES', cache=True
):
return self.owner.get_full_name() or str(self.owner)
return str(self.owner)