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:
parent
4c7a74ef05
commit
129975adc6
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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'))
|
||||||
|
@ -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')
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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')
|
||||||
|
@ -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)
|
||||||
|
@ -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': {},
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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."""
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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')
|
||||||
|
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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')
|
||||||
|
@ -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')
|
||||||
|
@ -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')
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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')
|
||||||
|
|
||||||
|
@ -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'
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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'
|
'DISPLAY_FULL_NAMES', cache=True
|
||||||
and common_models.InvenTreeSetting.get_setting(
|
|
||||||
'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'
|
'DISPLAY_FULL_NAMES', cache=True
|
||||||
and common_models.InvenTreeSetting.get_setting(
|
|
||||||
'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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user