2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00

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.conversion
import InvenTree.ready import InvenTree.ready
import InvenTree.tasks import InvenTree.tasks
from common.settings import get_global_setting, set_global_setting
from InvenTree.config import get_setting from InvenTree.config import get_setting
logger = logging.getLogger('inventree') 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 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 - If multi-site support is enabled, update the site URL for the current site
""" """
import common.models
if not InvenTree.ready.canAppAccessDatabase(): if not InvenTree.ready.canAppAccessDatabase():
return return
@ -248,13 +247,8 @@ class InvenTreeConfig(AppConfig):
if settings.SITE_URL: if settings.SITE_URL:
try: try:
if ( if get_global_setting('INVENTREE_BASE_URL') != settings.SITE_URL:
common.models.InvenTreeSetting.get_setting('INVENTREE_BASE_URL') set_global_setting('INVENTREE_BASE_URL', settings.SITE_URL)
!= settings.SITE_URL
):
common.models.InvenTreeSetting.set_setting(
'INVENTREE_BASE_URL', settings.SITE_URL
)
logger.info('Updated INVENTREE_SITE_URL to %s', settings.SITE_URL) logger.info('Updated INVENTREE_SITE_URL to %s', settings.SITE_URL)
except Exception: except Exception:
pass pass

View File

@ -14,6 +14,7 @@ from rest_framework.fields import URLField as RestURLField
from rest_framework.fields import empty from rest_framework.fields import empty
import InvenTree.helpers import InvenTree.helpers
from common.settings import get_global_setting
from .validators import AllowedURLValidator, allowable_url_schemes from .validators import AllowedURLValidator, allowable_url_schemes
@ -32,11 +33,7 @@ class InvenTreeRestURLField(RestURLField):
def run_validation(self, data=empty): def run_validation(self, data=empty):
"""Override default validation behaviour for this field type.""" """Override default validation behaviour for this field type."""
import common.models strict_urls = get_global_setting('INVENTREE_STRICT_URLS', True, cache=False)
strict_urls = common.models.InvenTreeSetting.get_setting(
'INVENTREE_STRICT_URLS', True, cache=False
)
if not strict_urls and data is not empty and '://' not in data: if not strict_urls and data is not empty and '://' not in data:
# Validate as if there were a schema provided # 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.helpers_model
import InvenTree.sso import InvenTree.sso
from common.models import InvenTreeSetting from common.settings import get_global_setting
from InvenTree.exceptions import log_error from InvenTree.exceptions import log_error
logger = logging.getLogger('inventree') logger = logging.getLogger('inventree')
@ -172,12 +172,12 @@ class CustomSignupForm(SignupForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Check settings to influence which fields are needed.""" """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) super().__init__(*args, **kwargs)
# check for two mail fields # 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( self.fields['email2'] = forms.EmailField(
label=_('Email (again)'), label=_('Email (again)'),
widget=forms.TextInput( widget=forms.TextInput(
@ -189,7 +189,7 @@ class CustomSignupForm(SignupForm):
) )
# check for two password fields # 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') self.fields.pop('password2')
# reorder fields # reorder fields
@ -202,7 +202,7 @@ class CustomSignupForm(SignupForm):
cleaned_data = super().clean() cleaned_data = super().clean()
# check for two mail fields # 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') email = cleaned_data.get('email')
email2 = cleaned_data.get('email2') email2 = cleaned_data.get('email2')
if (email and email2) and email != email2: if (email and email2) and email != email2:
@ -213,10 +213,7 @@ class CustomSignupForm(SignupForm):
def registration_enabled(): def registration_enabled():
"""Determine whether user registration is enabled.""" """Determine whether user registration is enabled."""
if ( if get_global_setting('LOGIN_ENABLE_REG') or InvenTree.sso.registration_enabled():
InvenTreeSetting.get_setting('LOGIN_ENABLE_REG')
or InvenTree.sso.registration_enabled()
):
if settings.EMAIL_HOST: if settings.EMAIL_HOST:
return True return True
else: else:
@ -240,9 +237,7 @@ class RegistratonMixin:
def clean_email(self, email): def clean_email(self, email):
"""Check if the mail is valid to the pattern in LOGIN_SIGNUP_MAIL_RESTRICTION (if enabled in settings).""" """Check if the mail is valid to the pattern in LOGIN_SIGNUP_MAIL_RESTRICTION (if enabled in settings)."""
mail_restriction = InvenTreeSetting.get_setting( mail_restriction = get_global_setting('LOGIN_SIGNUP_MAIL_RESTRICTION', None)
'LOGIN_SIGNUP_MAIL_RESTRICTION', None
)
if not mail_restriction: if not mail_restriction:
return super().clean_email(email) return super().clean_email(email)
@ -273,7 +268,7 @@ class RegistratonMixin:
user = super().save_user(request, user, form) user = super().save_user(request, user, form)
# Check if a default group is set in settings # 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: if start_group:
try: try:
group = Group.objects.get(id=start_group) group = Group.objects.get(id=start_group)
@ -333,7 +328,7 @@ class CustomSocialAccountAdapter(
def is_auto_signup_allowed(self, request, sociallogin): def is_auto_signup_allowed(self, request, sociallogin):
"""Check if auto signup is enabled in settings.""" """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 super().is_auto_signup_allowed(request, sociallogin)
return False return False
@ -385,7 +380,7 @@ class CustomRegisterSerializer(RegisterSerializer):
def __init__(self, instance=None, data=..., **kwargs): def __init__(self, instance=None, data=..., **kwargs):
"""Check settings to influence which fields are needed.""" """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) super().__init__(instance, data, **kwargs)
def save(self, request): def save(self, request):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -36,6 +36,7 @@ import InvenTree.tasks
import common.models import common.models
from common.notifications import trigger_notification, InvenTreeNotificationBodies from common.notifications import trigger_notification, InvenTreeNotificationBodies
from common.settings import get_global_setting
from plugin.events import trigger_event from plugin.events import trigger_event
import part.models import part.models
@ -136,7 +137,7 @@ class Build(
super().clean() super().clean()
if common.models.InvenTreeSetting.get_setting('BUILDORDER_REQUIRE_RESPONSIBLE'): if get_global_setting('BUILDORDER_REQUIRE_RESPONSIBLE'):
if not self.responsible: if not self.responsible:
raise ValidationError({ raise ValidationError({
'responsible': _('Responsible user or group must be specified') '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 from InvenTree import status_codes as status
import common.models import common.models
from common.settings import set_global_setting
import build.tasks import build.tasks
from build.models import Build, BuildItem, BuildLine, generate_next_build_reference from build.models import Build, BuildItem, BuildLine, generate_next_build_reference
from part.models import Part, BomItem, BomItemSubstitute, PartTestTemplate from part.models import Part, BomItem, BomItemSubstitute, PartTestTemplate
@ -215,7 +216,7 @@ class BuildTest(BuildTestBase):
def test_ref_int(self): def test_ref_int(self):
"""Test the "integer reference" field used for natural sorting""" """Test the "integer reference" field used for natural sorting"""
# Set build reference to new value # 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 = { refs = {
'BO-123-456': 123, 'BO-123-456': 123,
@ -238,7 +239,7 @@ class BuildTest(BuildTestBase):
self.assertEqual(build.reference_int, ref_int) self.assertEqual(build.reference_int, ref_int)
# Set build reference back to default value # 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): def test_ref_validation(self):
"""Test that the reference field validation works as expected""" """Test that the reference field validation works as expected"""
@ -271,7 +272,7 @@ class BuildTest(BuildTestBase):
) )
# Try a new validator pattern # 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 [ for ref in [
'1234-BO', '1234-BO',
@ -285,11 +286,11 @@ class BuildTest(BuildTestBase):
) )
# Set build reference back to default value # 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): def test_next_ref(self):
"""Test that the next reference is automatically generated""" """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( build = Build.objects.create(
part=self.assembly, part=self.assembly,
@ -311,7 +312,7 @@ class BuildTest(BuildTestBase):
self.assertEqual(build.reference_int, 988) self.assertEqual(build.reference_int, 988)
# Set build reference back to default value # 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): def test_init(self):
"""Perform some basic tests before we start the ball rolling""" """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""" """Test the prevention completion when a required test is missing feature"""
# with required tests incompleted the save should fail # 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): with self.assertRaises(ValidationError):
self.build_w_tests_trackable.complete_build_output(self.stockitem_with_required_test, None) 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.models
import common.serializers import common.serializers
from common.settings import get_global_setting
from generic.states.api import AllStatusViews, StatusView from generic.states.api import AllStatusViews, StatusView
from InvenTree.api import BulkDeleteMixin, MetadataView from InvenTree.api import BulkDeleteMixin, MetadataView
from InvenTree.config import CONFIG_LOOKUPS from InvenTree.config import CONFIG_LOOKUPS
@ -149,7 +150,7 @@ class CurrencyExchangeView(APIView):
updated = None updated = None
response = { response = {
'base_currency': common.models.InvenTreeSetting.get_setting( 'base_currency': get_global_setting(
'INVENTREE_DEFAULT_CURRENCY', backup_value='USD' 'INVENTREE_DEFAULT_CURRENCY', backup_value='USD'
), ),
'exchange_rates': {}, 'exchange_rates': {},

View File

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

View File

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

View File

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

View File

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

View File

@ -35,6 +35,7 @@ import stock.models
import users.models as UserModels import users.models as UserModels
from common.currency import currency_code_default from common.currency import currency_code_default
from common.notifications import InvenTreeNotificationBodies from common.notifications import InvenTreeNotificationBodies
from common.settings import get_global_setting
from company.models import Address, Company, Contact, SupplierPart from company.models import Address, Company, Contact, SupplierPart
from generic.states import StateTransitionMixin from generic.states import StateTransitionMixin
from InvenTree.exceptions import log_error from InvenTree.exceptions import log_error
@ -44,7 +45,7 @@ from InvenTree.fields import (
RoundingDecimalField, RoundingDecimalField,
) )
from InvenTree.helpers import decimal2string, pui_url 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 ( from order.status_codes import (
PurchaseOrderStatus, PurchaseOrderStatus,
PurchaseOrderStatusGroups, PurchaseOrderStatusGroups,
@ -232,9 +233,7 @@ class Order(
# Check if a responsible owner is required for this order type # Check if a responsible owner is required for this order type
if self.REQUIRE_RESPONSIBLE_SETTING: if self.REQUIRE_RESPONSIBLE_SETTING:
if common_models.InvenTreeSetting.get_setting( if get_global_setting(self.REQUIRE_RESPONSIBLE_SETTING, backup_value=False):
self.REQUIRE_RESPONSIBLE_SETTING, backup_value=False
):
if not self.responsible: if not self.responsible:
raise ValidationError({ raise ValidationError({
'responsible': _('Responsible user or group must be specified') 'responsible': _('Responsible user or group must be specified')
@ -820,9 +819,7 @@ class PurchaseOrder(TotalPriceMixin, Order):
# Has this order been completed? # Has this order been completed?
if len(self.pending_line_items()) == 0: if len(self.pending_line_items()) == 0:
if common_models.InvenTreeSetting.get_setting( if get_global_setting('PURCHASEORDER_AUTO_COMPLETE', True):
'PURCHASEORDER_AUTO_COMPLETE', True
):
self.received_by = user self.received_by = user
self.complete_order() # This will save the model self.complete_order() # This will save the model
@ -1073,7 +1070,7 @@ class SalesOrder(TotalPriceMixin, Order):
return False return False
bypass_shipped = InvenTree.helpers.str2bool( 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: 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: if created:
# A new SalesOrder has just been created # A new SalesOrder has just been created
if getSetting('SALESORDER_DEFAULT_SHIPMENT'): if get_global_setting('SALESORDER_DEFAULT_SHIPMENT'):
# Create default shipment # Create default shipment
SalesOrderShipment.objects.create(order=instance, reference='1') 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 build.status_codes import BuildStatusGroups
from common.currency import currency_code_default from common.currency import currency_code_default
from common.models import InvenTreeSetting from common.models import InvenTreeSetting
from common.settings import get_global_setting, set_global_setting
from company.models import SupplierPart from company.models import SupplierPart
from InvenTree import helpers, validators from InvenTree import helpers, validators
from InvenTree.fields import InvenTreeURLField from InvenTree.fields import InvenTreeURLField
@ -482,9 +483,7 @@ class Part(
if self.active: if self.active:
raise ValidationError(_('Cannot delete this part as it is still active')) raise ValidationError(_('Cannot delete this part as it is still active'))
if not common.models.InvenTreeSetting.get_setting( if not get_global_setting('PART_ALLOW_DELETE_FROM_ASSEMBLY', cache=False):
'PART_ALLOW_DELETE_FROM_ASSEMBLY', cache=False
):
if BomItem.objects.filter(sub_part=self).exists(): if BomItem.objects.filter(sub_part=self).exists():
raise ValidationError( raise ValidationError(
_('Cannot delete this part as it is used in an assembly') _('Cannot delete this part as it is used in an assembly')
@ -649,9 +648,7 @@ class Part(
raise ValidationError({'IPN': exc.message}) raise ValidationError({'IPN': exc.message})
# If we get to here, none of the plugins have raised an error # If we get to here, none of the plugins have raised an error
pattern = common.models.InvenTreeSetting.get_setting( pattern = get_global_setting('PART_IPN_REGEX', '', create=False).strip()
'PART_IPN_REGEX', '', create=False
).strip()
if pattern: if pattern:
match = re.search(pattern, self.IPN) match = re.search(pattern, self.IPN)
@ -719,9 +716,7 @@ class Part(
from part.models import Part from part.models import Part
from stock.models import StockItem from stock.models import StockItem
if common.models.InvenTreeSetting.get_setting( if get_global_setting('SERIAL_NUMBER_GLOBALLY_UNIQUE', False):
'SERIAL_NUMBER_GLOBALLY_UNIQUE', False
):
# Serial number must be unique across *all* parts # Serial number must be unique across *all* parts
parts = Part.objects.all() parts = Part.objects.all()
else: else:
@ -775,9 +770,7 @@ class Part(
) )
# Generate a query for any stock items for this part variant tree with non-empty serial numbers # Generate a query for any stock items for this part variant tree with non-empty serial numbers
if common.models.InvenTreeSetting.get_setting( if get_global_setting('SERIAL_NUMBER_GLOBALLY_UNIQUE', False):
'SERIAL_NUMBER_GLOBALLY_UNIQUE', False
):
# Serial numbers are unique across all parts # Serial numbers are unique across all parts
pass pass
else: else:
@ -831,9 +824,7 @@ class Part(
super().validate_unique(exclude) super().validate_unique(exclude)
# User can decide whether duplicate IPN (Internal Part Number) values are allowed # User can decide whether duplicate IPN (Internal Part Number) values are allowed
allow_duplicate_ipn = common.models.InvenTreeSetting.get_setting( allow_duplicate_ipn = get_global_setting('PART_ALLOW_DUPLICATE_IPN')
'PART_ALLOW_DUPLICATE_IPN'
)
# Raise an error if an IPN is set, and it is a duplicate # Raise an error if an IPN is set, and it is a duplicate
if self.IPN and not allow_duplicate_ipn: if self.IPN and not allow_duplicate_ipn:
@ -2749,11 +2740,11 @@ class PartPricing(common.models.MetaMixin):
purchase_max = purchase_cost purchase_max = purchase_cost
# Also check if manual stock item pricing is included # 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() items = self.part.stock_items.all()
# Limit to stock items updated within a certain window # 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: if days > 0:
date_threshold = InvenTree.helpers.current_date() - timedelta(days=days) date_threshold = InvenTree.helpers.current_date() - timedelta(days=days)
@ -2789,7 +2780,7 @@ class PartPricing(common.models.MetaMixin):
min_int_cost = None min_int_cost = None
max_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 # Only calculate internal pricing if internal pricing is enabled
for pb in self.part.internalpricebreaks.all(): for pb in self.part.internalpricebreaks.all():
cost = self.convert(pb.price) cost = self.convert(pb.price)
@ -2865,7 +2856,7 @@ class PartPricing(common.models.MetaMixin):
variant_min = None variant_min = None
variant_max = 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: if self.part.is_template:
variants = self.part.get_descendants(include_self=False) 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] 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 '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 # Add supplier pricing data, *unless* historical pricing information should override
if self.purchase_cost_min is None or not purchase_history_override: if self.purchase_cost_min is None or not purchase_history_override:
min_costs.append(self.supplier_price_min) 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: if self.purchase_cost_max is None or not purchase_history_override:
max_costs.append(self.supplier_price_max) 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 # Include variant pricing in overall calculations
min_costs.append(self.variant_cost_min) min_costs.append(self.variant_cost_min)
max_costs.append(self.variant_cost_max) 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: if overall_max is None or cost > overall_max:
overall_max = cost 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 # Check if internal pricing should override other pricing
if self.internal_cost_min is not None: if self.internal_cost_min is not None:
overall_min = self.internal_cost_min overall_min = self.internal_cost_min
@ -3774,7 +3765,7 @@ class PartParameter(InvenTree.models.InvenTreeMetadataModel):
super().clean() super().clean()
# Validate the parameter data against the template units # 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 'PART_PARAMETER_ENFORCE_UNITS', True, cache=False, create=False
): ):
if self.template.units: if self.template.units:
@ -3916,7 +3907,7 @@ class PartCategoryParameterTemplate(InvenTree.models.InvenTreeMetadataModel):
if ( if (
self.default_value self.default_value
and InvenTreeSetting.get_setting( and get_global_setting(
'PART_PARAMETER_ENFORCE_UNITS', True, cache=False, create=False 'PART_PARAMETER_ENFORCE_UNITS', True, cache=False, create=False
) )
and self.parameter_template.units and self.parameter_template.units
@ -4325,9 +4316,7 @@ class BomItem(
def price_range(self, internal=False): def price_range(self, internal=False):
"""Return the price-range for this BOM item.""" """Return the price-range for this BOM item."""
# get internal price setting # get internal price setting
use_internal = common.models.InvenTreeSetting.get_setting( use_internal = get_global_setting('PART_BOM_USE_INTERNAL_PRICE', False)
'PART_BOM_USE_INTERNAL_PRICE', False
)
prange = self.sub_part.get_price_range( prange = self.sub_part.get_price_range(
self.quantity, internal=use_internal and internal self.quantity, internal=use_internal and internal
) )

View File

@ -1,38 +1,38 @@
"""User-configurable settings for the Part app.""" """User-configurable settings for the Part app."""
from common.models import InvenTreeSetting from common.settings import get_global_setting
def part_assembly_default(): def part_assembly_default():
"""Returns the default value for the 'assembly' field of a Part object.""" """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(): def part_template_default():
"""Returns the default value for the 'is_template' field of a Part object.""" """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(): def part_virtual_default():
"""Returns the default value for the 'is_virtual' field of Part object.""" """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(): def part_component_default():
"""Returns the default value for the 'component' field of a Part object.""" """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(): def part_purchaseable_default():
"""Returns the default value for the 'purchasable' field for a Part object.""" """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(): def part_salable_default():
"""Returns the default value for the 'salable' field for a Part object.""" """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(): def part_trackable_default():
"""Returns the default value for the 'trackable' field for a Part object.""" """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 _ from django.utils.translation import gettext_lazy as _
import common.currency import common.currency
import common.models
import common.notifications import common.notifications
import common.settings
import company.models import company.models
import InvenTree.helpers import InvenTree.helpers
import InvenTree.helpers_model import InvenTree.helpers_model
import InvenTree.tasks import InvenTree.tasks
import part.models import part.models
import part.stocktake import part.stocktake
from common.settings import get_global_setting
from InvenTree.tasks import ( from InvenTree.tasks import (
ScheduledTask, ScheduledTask,
check_daily_holdoff, check_daily_holdoff,
@ -99,7 +98,7 @@ def check_missing_pricing(limit=250):
pp.schedule_for_update() pp.schedule_for_update()
# Find any parts which have 'old' pricing information # 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) stale_date = datetime.now().date() - timedelta(days=days)
results = part.models.PartPricing.objects.filter(updated__lte=stale_date)[:limit] 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 # First let's delete any old stocktake reports
delete_n_days = int( delete_n_days = int(
common.models.InvenTreeSetting.get_setting( get_global_setting('STOCKTAKE_DELETE_REPORT_DAYS', 30, cache=False)
'STOCKTAKE_DELETE_REPORT_DAYS', 30, cache=False
)
) )
threshold = datetime.now() - timedelta(days=delete_n_days) threshold = datetime.now() - timedelta(days=delete_n_days)
old_reports = part.models.PartStocktakeReport.objects.filter(date__lt=threshold) old_reports = part.models.PartStocktakeReport.objects.filter(date__lt=threshold)
@ -158,17 +155,11 @@ def scheduled_stocktake_reports():
old_reports.delete() old_reports.delete()
# Next, check if stocktake functionality is enabled # Next, check if stocktake functionality is enabled
if not common.models.InvenTreeSetting.get_setting( if not get_global_setting('STOCKTAKE_ENABLE', False, cache=False):
'STOCKTAKE_ENABLE', False, cache=False
):
logger.info('Stocktake functionality is not enabled - exiting') logger.info('Stocktake functionality is not enabled - exiting')
return return
report_n_days = int( report_n_days = int(get_global_setting('STOCKTAKE_AUTO_DAYS', 0, cache=False))
common.models.InvenTreeSetting.get_setting(
'STOCKTAKE_AUTO_DAYS', 0, cache=False
)
)
if report_n_days < 1: if report_n_days < 1:
logger.info('Stocktake auto reports are disabled, exiting') logger.info('Stocktake auto reports are disabled, exiting')

View File

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

View File

@ -12,6 +12,7 @@ import company.models
import order.models import order.models
import part.models import part.models
import stock.models import stock.models
from common.settings import get_global_setting, set_global_setting
from InvenTree.unit_test import InvenTreeTestCase from InvenTree.unit_test import InvenTreeTestCase
from order.status_codes import PurchaseOrderStatus from order.status_codes import PurchaseOrderStatus
@ -172,7 +173,7 @@ class PartPricingTests(InvenTreeTestCase):
def test_internal_pricing(self): def test_internal_pricing(self):
"""Tests for internal price breaks.""" """Tests for internal price breaks."""
# Ensure internal pricing is enabled # 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 pricing = self.part.pricing
@ -221,9 +222,7 @@ class PartPricingTests(InvenTreeTestCase):
) )
# Ensure that initially, stock item pricing is disabled # Ensure that initially, stock item pricing is disabled
common.models.InvenTreeSetting.set_setting( set_global_setting('PRICING_USE_STOCK_PRICING', False, None)
'PRICING_USE_STOCK_PRICING', False, None
)
pricing = p.pricing pricing = p.pricing
pricing.update_pricing() pricing.update_pricing()
@ -235,9 +234,7 @@ class PartPricingTests(InvenTreeTestCase):
self.assertIsNone(pricing.overall_max) self.assertIsNone(pricing.overall_max)
# Turn on stock pricing # Turn on stock pricing
common.models.InvenTreeSetting.set_setting( set_global_setting('PRICING_USE_STOCK_PRICING', True, None)
'PRICING_USE_STOCK_PRICING', True, None
)
pricing.update_pricing() 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 from django.dispatch.dispatcher import receiver
import InvenTree.exceptions import InvenTree.exceptions
from common.settings import get_global_setting
from InvenTree.ready import canAppAccessDatabase, isImportingData from InvenTree.ready import canAppAccessDatabase, isImportingData
from InvenTree.tasks import offload_task from InvenTree.tasks import offload_task
from plugin.registry import registry from plugin.registry import registry
@ -21,9 +22,7 @@ def trigger_event(event, *args, **kwargs):
This event will be stored in the database, This event will be stored in the database,
and the worker will respond to it later on. and the worker will respond to it later on.
""" """
from common.models import InvenTreeSetting if not get_global_setting('ENABLE_PLUGINS_EVENTS', False):
if not InvenTreeSetting.get_setting('ENABLE_PLUGINS_EVENTS', False):
# Do nothing if plugin events are not enabled # Do nothing if plugin events are not enabled
return return
@ -50,12 +49,10 @@ def register_event(event, *args, **kwargs):
Note: This function is processed by the background worker, Note: This function is processed by the background worker,
as it performs multiple database access operations. as it performs multiple database access operations.
""" """
from common.models import InvenTreeSetting
logger.debug("Registering triggered event: '%s'", event) logger.debug("Registering triggered event: '%s'", event)
# Determine if there are any plugins which are interested in responding # 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 # Check if the plugin registry needs to be reloaded
registry.check_reload() registry.check_reload()

View File

@ -38,11 +38,9 @@ class AppMixin:
force_reload (bool, optional): Only reload base apps. Defaults to False. force_reload (bool, optional): Only reload base apps. Defaults to False.
full_reload (bool, optional): Reload everything - including plugin mechanism. 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( if settings.PLUGIN_TESTING or get_global_setting('ENABLE_PLUGINS_APP'):
'ENABLE_PLUGINS_APP'
):
logger.info('Registering IntegrationPlugin apps') logger.info('Registering IntegrationPlugin apps')
apps_changed = False apps_changed = False

View File

@ -5,6 +5,7 @@ import logging
from django.conf import settings from django.conf import settings
from django.urls import include, re_path from django.urls import include, re_path
from common.settings import get_global_setting
from plugin.urls import PLUGIN_BASE from plugin.urls import PLUGIN_BASE
logger = logging.getLogger('inventree') logger = logging.getLogger('inventree')
@ -36,11 +37,7 @@ class UrlsMixin:
force_reload (bool, optional): Only reload base apps. Defaults to False. force_reload (bool, optional): Only reload base apps. Defaults to False.
full_reload (bool, optional): Reload everything - including plugin mechanism. 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 get_global_setting('ENABLE_PLUGINS_URL'):
if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting(
'ENABLE_PLUGINS_URL'
):
logger.info('Registering UrlsMixin Plugin') logger.info('Registering UrlsMixin Plugin')
urls_changed = False urls_changed = False
# check whether an activated plugin extends UrlsMixin # 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.text import slugify
from django.utils.translation import gettext_lazy as _ 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.config import get_plugin_dir
from InvenTree.ready import canAppAccessDatabase from InvenTree.ready import canAppAccessDatabase
@ -732,12 +733,10 @@ class PluginsRegistry:
# region plugin registry hash calculations # region plugin registry hash calculations
def update_plugin_hash(self): def update_plugin_hash(self):
"""When the state of the plugin registry changes, update the hash.""" """When the state of the plugin registry changes, update the hash."""
from common.models import InvenTreeSetting
self.registry_hash = self.calculate_plugin_hash() self.registry_hash = self.calculate_plugin_hash()
try: try:
old_hash = InvenTreeSetting.get_setting( old_hash = get_global_setting(
'_PLUGIN_REGISTRY_HASH', '', create=False, cache=False '_PLUGIN_REGISTRY_HASH', '', create=False, cache=False
) )
except Exception: except Exception:
@ -748,7 +747,7 @@ class PluginsRegistry:
logger.debug( logger.debug(
'Updating plugin registry hash: %s', str(self.registry_hash) 'Updating plugin registry hash: %s', str(self.registry_hash)
) )
InvenTreeSetting.set_setting( set_global_setting(
'_PLUGIN_REGISTRY_HASH', self.registry_hash, change_user=None '_PLUGIN_REGISTRY_HASH', self.registry_hash, change_user=None
) )
except (OperationalError, ProgrammingError): except (OperationalError, ProgrammingError):
@ -776,8 +775,6 @@ class PluginsRegistry:
""" """
from hashlib import md5 from hashlib import md5
from common.models import InvenTreeSetting
data = md5() data = md5()
# Hash for all loaded plugins # Hash for all loaded plugins
@ -789,7 +786,7 @@ class PluginsRegistry:
for k in self.plugin_settings_keys(): for k in self.plugin_settings_keys():
try: try:
val = InvenTreeSetting.get_setting(k, False, create=False) val = get_global_setting(k, False, create=False)
msg = f'{k}-{val}' msg = f'{k}-{val}'
data.update(msg.encode()) data.update(msg.encode())
@ -800,8 +797,6 @@ class PluginsRegistry:
def check_reload(self): def check_reload(self):
"""Determine if the registry needs to be reloaded.""" """Determine if the registry needs to be reloaded."""
from common.models import InvenTreeSetting
if settings.TESTING: if settings.TESTING:
# Skip if running during unit testing # Skip if running during unit testing
return return
@ -817,9 +812,7 @@ class PluginsRegistry:
self.registry_hash = self.calculate_plugin_hash() self.registry_hash = self.calculate_plugin_hash()
try: try:
reg_hash = InvenTreeSetting.get_setting( reg_hash = get_global_setting('_PLUGIN_REGISTRY_HASH', '', create=False)
'_PLUGIN_REGISTRY_HASH', '', create=False
)
except Exception as exc: except Exception as exc:
logger.exception('Failed to retrieve plugin registry hash: %s', str(exc)) logger.exception('Failed to retrieve plugin registry hash: %s', str(exc))
return return

View File

@ -4,8 +4,8 @@ from django import template
from django.conf import settings as djangosettings from django.conf import settings as djangosettings
from django.urls import reverse from django.urls import reverse
from common.models import InvenTreeSetting
from common.notifications import storage from common.notifications import storage
from common.settings import get_global_setting
from plugin.registry import registry from plugin.registry import registry
register = template.Library() register = template.Library()
@ -55,7 +55,7 @@ def navigation_enabled(*args, **kwargs):
"""Is plugin navigation enabled?""" """Is plugin navigation enabled?"""
if djangosettings.PLUGIN_TESTING: if djangosettings.PLUGIN_TESTING:
return True 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() @register.simple_tag()

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
"""JSON serializers for Stock app.""" """JSON serializers for Stock app."""
import logging import logging
from datetime import datetime, timedelta from datetime import timedelta
from decimal import Decimal from decimal import Decimal
from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ValidationError as DjangoValidationError
@ -16,7 +16,6 @@ from sql_util.utils import SubqueryCount, SubquerySum
from taggit.serializers import TagListSerializerField from taggit.serializers import TagListSerializerField
import build.models import build.models
import common.models
import company.models import company.models
import InvenTree.helpers import InvenTree.helpers
import InvenTree.serializers import InvenTree.serializers
@ -25,6 +24,7 @@ import part.filters as part_filters
import part.models as part_models import part.models as part_models
import stock.filters import stock.filters
import stock.status_codes import stock.status_codes
from common.settings import get_global_setting
from company.serializers import SupplierPartSerializer from company.serializers import SupplierPartSerializer
from InvenTree.serializers import InvenTreeCurrencySerializer, InvenTreeDecimalField from InvenTree.serializers import InvenTreeCurrencySerializer, InvenTreeDecimalField
from part.serializers import PartBriefSerializer, PartTestTemplateSerializer from part.serializers import PartBriefSerializer, PartTestTemplateSerializer
@ -476,7 +476,7 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeTagModelSerializer):
) )
# Add flag to indicate if the StockItem is stale # 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_date = InvenTree.helpers.current_date() + timedelta(days=stale_days)
stale_filter = ( stale_filter = (
StockItem.IN_STOCK_FILTER StockItem.IN_STOCK_FILTER
@ -730,7 +730,7 @@ class InstallStockItemSerializer(serializers.Serializer):
parent_item = self.context['item'] parent_item = self.context['item']
parent_part = parent_item.part parent_part = parent_item.part
if common.models.InvenTreeSetting.get_setting( if get_global_setting(
'STOCK_ENFORCE_BOM_INSTALLATION', backup_value=True, cache=False 'STOCK_ENFORCE_BOM_INSTALLATION', backup_value=True, cache=False
): ):
# Check if the selected part is in the Bill of Materials of the parent item # 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.urls import reverse
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
import common.settings from common.settings import get_global_setting
from InvenTree.views import InvenTreeRoleMixin from InvenTree.views import InvenTreeRoleMixin
from plugin.views import InvenTreePluginViewMixin 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 # No 'ownership' checks are necessary for the top-level StockLocation view
context['user_owns_location'] = True context['user_owns_location'] = True
context['location_owner'] = None context['location_owner'] = None
context['ownership_enabled'] = common.models.InvenTreeSetting.get_setting( context['ownership_enabled'] = get_global_setting('STOCK_OWNERSHIP_CONTROL')
'STOCK_OWNERSHIP_CONTROL'
)
return context return context
@ -53,9 +51,7 @@ class StockLocationDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailVi
"""Extend template context.""" """Extend template context."""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['ownership_enabled'] = common.models.InvenTreeSetting.get_setting( context['ownership_enabled'] = get_global_setting('STOCK_OWNERSHIP_CONTROL')
'STOCK_OWNERSHIP_CONTROL'
)
context['location_owner'] = context['location'].get_location_owner() context['location_owner'] = context['location'].get_location_owner()
context['user_owns_location'] = context['location'].check_ownership( context['user_owns_location'] = context['location'].check_ownership(
self.request.user self.request.user
@ -80,9 +76,7 @@ class StockItemDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView):
data['previous'] = self.object.get_next_serialized_item(reverse=True) data['previous'] = self.object.get_next_serialized_item(reverse=True)
data['next'] = self.object.get_next_serialized_item() data['next'] = self.object.get_next_serialized_item()
data['ownership_enabled'] = common.models.InvenTreeSetting.get_setting( data['ownership_enabled'] = get_global_setting('STOCK_OWNERSHIP_CONTROL')
'STOCK_OWNERSHIP_CONTROL'
)
data['item_owner'] = self.object.get_item_owner() data['item_owner'] = self.object.get_item_owner()
data['user_owns_item'] = self.object.check_ownership(self.request.user) 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 from rest_framework.authtoken.models import Token as AuthToken
import common.models as common_models
import InvenTree.helpers import InvenTree.helpers
import InvenTree.models import InvenTree.models
from common.settings import get_global_setting
from InvenTree.ready import canAppAccessDatabase, isImportingData from InvenTree.ready import canAppAccessDatabase, isImportingData
logger = logging.getLogger('inventree') logger = logging.getLogger('inventree')
@ -34,7 +34,7 @@ logger = logging.getLogger('inventree')
# string representation of a user # string representation of a user
def user_model_str(self): def user_model_str(self):
"""Function to override the default Django User __str__.""" """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: if self.first_name or self.last_name:
return f'{self.first_name} {self.last_name}' return f'{self.first_name} {self.last_name}'
return self.username return self.username
@ -816,11 +816,8 @@ class Owner(models.Model):
def __str__(self): def __str__(self):
"""Defines the owner string representation.""" """Defines the owner string representation."""
if ( if self.owner_type.name == 'user' and get_global_setting(
self.owner_type.name == 'user'
and common_models.InvenTreeSetting.get_setting(
'DISPLAY_FULL_NAMES', cache=True 'DISPLAY_FULL_NAMES', cache=True
)
): ):
display_name = self.owner.get_full_name() display_name = self.owner.get_full_name()
else: else:
@ -829,11 +826,8 @@ class Owner(models.Model):
def name(self): def name(self):
"""Return the 'name' of this owner.""" """Return the 'name' of this owner."""
if ( if self.owner_type.name == 'user' and get_global_setting(
self.owner_type.name == 'user'
and common_models.InvenTreeSetting.get_setting(
'DISPLAY_FULL_NAMES', cache=True 'DISPLAY_FULL_NAMES', cache=True
)
): ):
return self.owner.get_full_name() or str(self.owner) return self.owner.get_full_name() or str(self.owner)
return str(self.owner) return str(self.owner)