mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Update demo data hook to copy media files (#3441)
* Simplify settings.py / config.py - get_setting function has been streamlined - move some functions into config.py * Spelling fix: IGNORRED is IGNORED * Ensure yaml is installed as part of docker image - invoke path is still mucking us around * Fix broken migration * Copy media files from demo dataset when installing test data * Cleanup settings.py * Fix for configuration file traversal * Line fix * Update quickstart guide for docker * Allow plugin file and plugin dir to be specified in configuration file * Cleanup config template file * Allow secret_key information to be provided in configuration file * Adjust root paths for CI tests * resolve paths * Revert paths for CI step * remove dead code * Revert configuration variables to old names - Prevent breaking changes * Simplify secret key generation * Fix default timeout for background worker process * Revert change for customization options
This commit is contained in:
parent
d4eae9da11
commit
e9b0f02ecd
3
.gitignore
vendored
3
.gitignore
vendored
@ -50,7 +50,6 @@ docs/_build
|
|||||||
inventree_media
|
inventree_media
|
||||||
inventree_static
|
inventree_static
|
||||||
static_i18n
|
static_i18n
|
||||||
inventree-data
|
|
||||||
|
|
||||||
# Local config file
|
# Local config file
|
||||||
config.yaml
|
config.yaml
|
||||||
@ -79,6 +78,8 @@ js_tmp/
|
|||||||
|
|
||||||
# Development files
|
# Development files
|
||||||
dev/
|
dev/
|
||||||
|
data/
|
||||||
|
env/
|
||||||
|
|
||||||
# Locale stats file
|
# Locale stats file
|
||||||
locale_stats.json
|
locale_stats.json
|
||||||
|
@ -18,6 +18,7 @@ pip install invoke && invoke setup-dev --tests
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/inventree/InvenTree.git && cd InvenTree
|
git clone https://github.com/inventree/InvenTree.git && cd InvenTree
|
||||||
|
docker compose run inventree-dev-server invoke install
|
||||||
docker compose run inventree-dev-server invoke setup-test
|
docker compose run inventree-dev-server invoke setup-test
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
@ -172,21 +172,13 @@ class InvenTreeConfig(AppConfig):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# get values
|
# get values
|
||||||
add_user = get_setting(
|
add_user = get_setting('INVENTREE_ADMIN_USER', 'admin_user')
|
||||||
'INVENTREE_ADMIN_USER',
|
add_email = get_setting('INVENTREE_ADMIN_EMAIL', 'admin_email')
|
||||||
settings.CONFIG.get('admin_user', False)
|
add_password = get_setting('INVENTREE_ADMIN_PASSWORD', 'admin_password')
|
||||||
)
|
|
||||||
add_email = get_setting(
|
|
||||||
'INVENTREE_ADMIN_EMAIL',
|
|
||||||
settings.CONFIG.get('admin_email', False)
|
|
||||||
)
|
|
||||||
add_password = get_setting(
|
|
||||||
'INVENTREE_ADMIN_PASSWORD',
|
|
||||||
settings.CONFIG.get('admin_password', False)
|
|
||||||
)
|
|
||||||
|
|
||||||
# check if all values are present
|
# check if all values are present
|
||||||
set_variables = 0
|
set_variables = 0
|
||||||
|
|
||||||
for tested_var in [add_user, add_email, add_password]:
|
for tested_var in [add_user, add_email, add_password]:
|
||||||
if tested_var:
|
if tested_var:
|
||||||
set_variables += 1
|
set_variables += 1
|
||||||
|
@ -2,18 +2,27 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import shutil
|
import shutil
|
||||||
|
import string
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
|
||||||
|
def is_true(x):
|
||||||
|
"""Shortcut function to determine if a value "looks" like a boolean"""
|
||||||
|
return str(x).strip().lower() in ['1', 'y', 'yes', 't', 'true', 'on']
|
||||||
|
|
||||||
|
|
||||||
def get_base_dir() -> Path:
|
def get_base_dir() -> Path:
|
||||||
"""Returns the base (top-level) InvenTree directory."""
|
"""Returns the base (top-level) InvenTree directory."""
|
||||||
return Path(__file__).parent.parent.resolve()
|
return Path(__file__).parent.parent.resolve()
|
||||||
|
|
||||||
|
|
||||||
def get_config_file() -> Path:
|
def get_config_file(create=True) -> Path:
|
||||||
"""Returns the path of the InvenTree configuration file.
|
"""Returns the path of the InvenTree configuration file.
|
||||||
|
|
||||||
Note: It will be created it if does not already exist!
|
Note: It will be created it if does not already exist!
|
||||||
@ -28,7 +37,7 @@ def get_config_file() -> Path:
|
|||||||
# Config file is *not* specified - use the default
|
# Config file is *not* specified - use the default
|
||||||
cfg_filename = base_dir.joinpath('config.yaml').resolve()
|
cfg_filename = base_dir.joinpath('config.yaml').resolve()
|
||||||
|
|
||||||
if not cfg_filename.exists():
|
if not cfg_filename.exists() and create:
|
||||||
print("InvenTree configuration file 'config.yaml' not found - creating default file")
|
print("InvenTree configuration file 'config.yaml' not found - creating default file")
|
||||||
|
|
||||||
cfg_template = base_dir.joinpath("config_template.yaml")
|
cfg_template = base_dir.joinpath("config_template.yaml")
|
||||||
@ -38,45 +47,159 @@ def get_config_file() -> Path:
|
|||||||
return cfg_filename
|
return cfg_filename
|
||||||
|
|
||||||
|
|
||||||
def get_plugin_file():
|
def load_config_data() -> map:
|
||||||
"""Returns the path of the InvenTree plugins specification file.
|
"""Load configuration data from the config file."""
|
||||||
|
|
||||||
Note: It will be created if it does not already exist!
|
cfg_file = get_config_file()
|
||||||
"""
|
|
||||||
# Check if the plugin.txt file (specifying required plugins) is specified
|
|
||||||
PLUGIN_FILE = os.getenv('INVENTREE_PLUGIN_FILE')
|
|
||||||
|
|
||||||
if not PLUGIN_FILE:
|
with open(cfg_file, 'r') as cfg:
|
||||||
# If not specified, look in the same directory as the configuration file
|
data = yaml.safe_load(cfg)
|
||||||
config_dir = get_config_file().parent
|
|
||||||
PLUGIN_FILE = config_dir.joinpath('plugins.txt')
|
|
||||||
else:
|
|
||||||
# Make sure we are using a modern Path object
|
|
||||||
PLUGIN_FILE = Path(PLUGIN_FILE)
|
|
||||||
|
|
||||||
if not PLUGIN_FILE.exists():
|
return data
|
||||||
logger.warning("Plugin configuration file does not exist")
|
|
||||||
logger.info(f"Creating plugin file at '{PLUGIN_FILE}'")
|
|
||||||
|
|
||||||
# If opening the file fails (no write permission, for example), then this will throw an error
|
|
||||||
PLUGIN_FILE.write_text("# InvenTree Plugins (uses PIP framework to install)\n\n")
|
|
||||||
|
|
||||||
return PLUGIN_FILE
|
|
||||||
|
|
||||||
|
|
||||||
def get_setting(environment_var, backup_val, default_value=None):
|
def get_setting(env_var=None, config_key=None, default_value=None):
|
||||||
"""Helper function for retrieving a configuration setting value.
|
"""Helper function for retrieving a configuration setting value.
|
||||||
|
|
||||||
- First preference is to look for the environment variable
|
- First preference is to look for the environment variable
|
||||||
- Second preference is to look for the value of the settings file
|
- Second preference is to look for the value of the settings file
|
||||||
- Third preference is the default value
|
- Third preference is the default value
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
env_var: Name of the environment variable e.g. 'INVENTREE_STATIC_ROOT'
|
||||||
|
config_key: Key to lookup in the configuration file
|
||||||
|
default_value: Value to return if first two options are not provided
|
||||||
|
|
||||||
"""
|
"""
|
||||||
val = os.getenv(environment_var)
|
|
||||||
|
|
||||||
if val is not None:
|
# First, try to load from the environment variables
|
||||||
return val
|
if env_var is not None:
|
||||||
|
val = os.getenv(env_var, None)
|
||||||
|
|
||||||
if backup_val is not None:
|
if val is not None:
|
||||||
return backup_val
|
return val
|
||||||
|
|
||||||
|
# Next, try to load from configuration file
|
||||||
|
if config_key is not None:
|
||||||
|
cfg_data = load_config_data()
|
||||||
|
|
||||||
|
result = None
|
||||||
|
|
||||||
|
# Hack to allow 'path traversal' in configuration file
|
||||||
|
for key in config_key.strip().split('.'):
|
||||||
|
|
||||||
|
if type(cfg_data) is not dict or key not in cfg_data:
|
||||||
|
result = None
|
||||||
|
break
|
||||||
|
|
||||||
|
result = cfg_data[key]
|
||||||
|
cfg_data = cfg_data[key]
|
||||||
|
|
||||||
|
if result is not None:
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Finally, return the default value
|
||||||
return default_value
|
return default_value
|
||||||
|
|
||||||
|
|
||||||
|
def get_boolean_setting(env_var=None, config_key=None, default_value=False):
|
||||||
|
"""Helper function for retreiving a boolean configuration setting"""
|
||||||
|
|
||||||
|
return is_true(get_setting(env_var, config_key, default_value))
|
||||||
|
|
||||||
|
|
||||||
|
def get_media_dir(create=True):
|
||||||
|
"""Return the absolute path for the 'media' directory (where uploaded files are stored)"""
|
||||||
|
|
||||||
|
md = get_setting('INVENTREE_MEDIA_ROOT', 'media_root')
|
||||||
|
|
||||||
|
if not md:
|
||||||
|
raise FileNotFoundError('INVENTREE_MEDIA_ROOT not specified')
|
||||||
|
|
||||||
|
md = Path(md).resolve()
|
||||||
|
|
||||||
|
if create:
|
||||||
|
md.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
return md
|
||||||
|
|
||||||
|
|
||||||
|
def get_static_dir(create=True):
|
||||||
|
"""Return the absolute path for the 'static' directory (where static files are stored)"""
|
||||||
|
|
||||||
|
sd = get_setting('INVENTREE_STATIC_ROOT', 'static_root')
|
||||||
|
|
||||||
|
if not sd:
|
||||||
|
raise FileNotFoundError('INVENTREE_STATIC_ROOT not specified')
|
||||||
|
|
||||||
|
sd = Path(sd).resolve()
|
||||||
|
|
||||||
|
if create:
|
||||||
|
sd.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
return sd
|
||||||
|
|
||||||
|
|
||||||
|
def get_plugin_file():
|
||||||
|
"""Returns the path of the InvenTree plugins specification file.
|
||||||
|
|
||||||
|
Note: It will be created if it does not already exist!
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check if the plugin.txt file (specifying required plugins) is specified
|
||||||
|
plugin_file = get_setting('INVENTREE_PLUGIN_FILE', 'plugin_file')
|
||||||
|
|
||||||
|
if not plugin_file:
|
||||||
|
# If not specified, look in the same directory as the configuration file
|
||||||
|
config_dir = get_config_file().parent
|
||||||
|
plugin_file = config_dir.joinpath('plugins.txt')
|
||||||
|
else:
|
||||||
|
# Make sure we are using a modern Path object
|
||||||
|
plugin_file = Path(plugin_file)
|
||||||
|
|
||||||
|
if not plugin_file.exists():
|
||||||
|
logger.warning("Plugin configuration file does not exist - creating default file")
|
||||||
|
logger.info(f"Creating plugin file at '{plugin_file}'")
|
||||||
|
|
||||||
|
# If opening the file fails (no write permission, for example), then this will throw an error
|
||||||
|
plugin_file.write_text("# InvenTree Plugins (uses PIP framework to install)\n\n")
|
||||||
|
|
||||||
|
return plugin_file
|
||||||
|
|
||||||
|
|
||||||
|
def get_secret_key():
|
||||||
|
"""Return the secret key value which will be used by django.
|
||||||
|
|
||||||
|
Following options are tested, in descending order of preference:
|
||||||
|
|
||||||
|
A) Check for environment variable INVENTREE_SECRET_KEY => Use raw key data
|
||||||
|
B) Check for environment variable INVENTREE_SECRET_KEY_FILE => Load key data from file
|
||||||
|
C) Look for default key file "secret_key.txt"
|
||||||
|
D) Create "secret_key.txt" if it does not exist
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Look for environment variable
|
||||||
|
if secret_key := get_setting('INVENTREE_SECRET_KEY', 'secret_key'):
|
||||||
|
logger.info("SECRET_KEY loaded by INVENTREE_SECRET_KEY") # pragma: no cover
|
||||||
|
return secret_key
|
||||||
|
|
||||||
|
# Look for secret key file
|
||||||
|
if secret_key_file := get_setting('INVENTREE_SECRET_KEY_FILE', 'secret_key_file'):
|
||||||
|
secret_key_file = Path(secret_key_file).resolve()
|
||||||
|
else:
|
||||||
|
# Default location for secret key file
|
||||||
|
secret_key_file = get_base_dir().joinpath("secret_key.txt").resolve()
|
||||||
|
|
||||||
|
if not secret_key_file.exists():
|
||||||
|
logger.info(f"Generating random key file at '{secret_key_file}'")
|
||||||
|
|
||||||
|
# Create a random key file
|
||||||
|
options = string.digits + string.ascii_letters + string.punctuation
|
||||||
|
key = ''.join([random.choice(options) for i in range(100)])
|
||||||
|
secret_key_file.write_text(key)
|
||||||
|
|
||||||
|
logger.info(f"Loading SECRET_KEY from '{secret_key_file}'")
|
||||||
|
|
||||||
|
key_data = secret_key_file.read_text().strip()
|
||||||
|
|
||||||
|
return key_data
|
||||||
|
@ -29,7 +29,7 @@ def log_error(path):
|
|||||||
kind, info, data = sys.exc_info()
|
kind, info, data = sys.exc_info()
|
||||||
|
|
||||||
# Check if the eror is on the ignore list
|
# Check if the eror is on the ignore list
|
||||||
if kind in settings.IGNORRED_ERRORS:
|
if kind in settings.IGNORED_ERRORS:
|
||||||
return
|
return
|
||||||
|
|
||||||
Error.objects.create(
|
Error.objects.create(
|
||||||
|
@ -157,7 +157,7 @@ class InvenTreeExceptionProcessor(ExceptionProcessor):
|
|||||||
kind, info, data = sys.exc_info()
|
kind, info, data = sys.exc_info()
|
||||||
|
|
||||||
# Check if the eror is on the ignore list
|
# Check if the eror is on the ignore list
|
||||||
if kind in settings.IGNORRED_ERRORS:
|
if kind in settings.IGNORED_ERRORS:
|
||||||
return
|
return
|
||||||
|
|
||||||
return super().process_exception(request, exception)
|
return super().process_exception(request, exception)
|
||||||
|
@ -11,9 +11,7 @@ database setup in this file.
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import random
|
|
||||||
import socket
|
import socket
|
||||||
import string
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@ -24,22 +22,14 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
import moneyed
|
import moneyed
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
import yaml
|
|
||||||
from sentry_sdk.integrations.django import DjangoIntegration
|
from sentry_sdk.integrations.django import DjangoIntegration
|
||||||
|
|
||||||
from .config import get_base_dir, get_config_file, get_plugin_file, get_setting
|
from . import config
|
||||||
|
from .config import get_boolean_setting, get_setting
|
||||||
|
|
||||||
def _is_true(x):
|
|
||||||
# Shortcut function to determine if a value "looks" like a boolean
|
|
||||||
return str(x).strip().lower() in ['1', 'y', 'yes', 't', 'true']
|
|
||||||
|
|
||||||
|
|
||||||
# Default Sentry DSN (can be overriden if user wants custom sentry integration)
|
|
||||||
INVENTREE_DSN = 'https://3928ccdba1d34895abde28031fd00100@o378676.ingest.sentry.io/6494600'
|
|
||||||
|
|
||||||
# Determine if we are running in "test" mode e.g. "manage.py test"
|
# Determine if we are running in "test" mode e.g. "manage.py test"
|
||||||
TESTING = 'test' in sys.argv
|
TESTING = 'test' in sys.argv
|
||||||
|
|
||||||
# Are enviroment variables manipulated by tests? Needs to be set by testing code
|
# Are enviroment variables manipulated by tests? Needs to be set by testing code
|
||||||
TESTING_ENV = False
|
TESTING_ENV = False
|
||||||
|
|
||||||
@ -47,33 +37,17 @@ TESTING_ENV = False
|
|||||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR.joinpath(...)
|
# Build paths inside the project like this: BASE_DIR.joinpath(...)
|
||||||
BASE_DIR = get_base_dir()
|
BASE_DIR = config.get_base_dir()
|
||||||
|
|
||||||
cfg_filename = get_config_file()
|
# Load configuration data
|
||||||
|
CONFIG = config.load_config_data()
|
||||||
with open(cfg_filename, 'r') as cfg:
|
|
||||||
CONFIG = yaml.safe_load(cfg)
|
|
||||||
|
|
||||||
# We will place any config files in the same directory as the config file
|
|
||||||
config_dir = cfg_filename.parent
|
|
||||||
|
|
||||||
# Default action is to run the system in Debug mode
|
# Default action is to run the system in Debug mode
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = _is_true(get_setting(
|
DEBUG = get_boolean_setting('INVENTREE_DEBUG', 'debug', True)
|
||||||
'INVENTREE_DEBUG',
|
|
||||||
CONFIG.get('debug', True)
|
|
||||||
))
|
|
||||||
|
|
||||||
DOCKER = _is_true(get_setting(
|
|
||||||
'INVENTREE_DOCKER',
|
|
||||||
False
|
|
||||||
))
|
|
||||||
|
|
||||||
# Configure logging settings
|
# Configure logging settings
|
||||||
log_level = get_setting(
|
log_level = get_setting('INVENTREE_LOG_LEVEL', 'log_level', 'WARNING')
|
||||||
'INVENTREE_LOG_LEVEL',
|
|
||||||
CONFIG.get('log_level', 'WARNING')
|
|
||||||
)
|
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=log_level,
|
level=log_level,
|
||||||
@ -105,78 +79,20 @@ LOGGING = {
|
|||||||
# Get a logger instance for this setup file
|
# Get a logger instance for this setup file
|
||||||
logger = logging.getLogger("inventree")
|
logger = logging.getLogger("inventree")
|
||||||
|
|
||||||
"""
|
# Load SECRET_KEY
|
||||||
Specify a secret key to be used by django.
|
SECRET_KEY = config.get_secret_key()
|
||||||
|
|
||||||
Following options are tested, in descending order of preference:
|
|
||||||
|
|
||||||
A) Check for environment variable INVENTREE_SECRET_KEY => Use raw key data
|
|
||||||
B) Check for environment variable INVENTREE_SECRET_KEY_FILE => Load key data from file
|
|
||||||
C) Look for default key file "secret_key.txt"
|
|
||||||
d) Create "secret_key.txt" if it does not exist
|
|
||||||
"""
|
|
||||||
|
|
||||||
if secret_key := os.getenv("INVENTREE_SECRET_KEY"):
|
|
||||||
# Secret key passed in directly
|
|
||||||
SECRET_KEY = secret_key.strip() # pragma: no cover
|
|
||||||
logger.info("SECRET_KEY loaded by INVENTREE_SECRET_KEY") # pragma: no cover
|
|
||||||
else:
|
|
||||||
# Secret key passed in by file location
|
|
||||||
key_file = os.getenv("INVENTREE_SECRET_KEY_FILE")
|
|
||||||
|
|
||||||
if key_file:
|
|
||||||
key_file = Path(key_file).resolve() # pragma: no cover
|
|
||||||
else:
|
|
||||||
# default secret key location
|
|
||||||
key_file = BASE_DIR.joinpath("secret_key.txt").resolve()
|
|
||||||
|
|
||||||
if not key_file.exists(): # pragma: no cover
|
|
||||||
logger.info(f"Generating random key file at '{key_file}'")
|
|
||||||
# Create a random key file
|
|
||||||
options = string.digits + string.ascii_letters + string.punctuation
|
|
||||||
key = ''.join([random.choice(options) for i in range(100)])
|
|
||||||
key_file.write_text(key)
|
|
||||||
|
|
||||||
logger.info(f"Loading SECRET_KEY from '{key_file}'")
|
|
||||||
|
|
||||||
try:
|
|
||||||
SECRET_KEY = open(key_file, "r").read().strip()
|
|
||||||
except Exception: # pragma: no cover
|
|
||||||
logger.exception(f"Couldn't load keyfile {key_file}")
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
# The filesystem location for served static files
|
# The filesystem location for served static files
|
||||||
STATIC_ROOT = Path(
|
STATIC_ROOT = config.get_static_dir()
|
||||||
get_setting(
|
|
||||||
'INVENTREE_STATIC_ROOT',
|
|
||||||
CONFIG.get('static_root', None)
|
|
||||||
)
|
|
||||||
).resolve()
|
|
||||||
|
|
||||||
if STATIC_ROOT is None: # pragma: no cover
|
# The filesystem location for uploaded meadia files
|
||||||
print("ERROR: INVENTREE_STATIC_ROOT directory not defined")
|
MEDIA_ROOT = config.get_media_dir()
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
# Ensure the root really is availalble
|
|
||||||
STATIC_ROOT.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
# The filesystem location for served static files
|
|
||||||
MEDIA_ROOT = Path(
|
|
||||||
get_setting(
|
|
||||||
'INVENTREE_MEDIA_ROOT',
|
|
||||||
CONFIG.get('media_root', None)
|
|
||||||
)
|
|
||||||
).resolve()
|
|
||||||
|
|
||||||
if MEDIA_ROOT is None: # pragma: no cover
|
|
||||||
print("ERROR: INVENTREE_MEDIA_ROOT directory is not defined")
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
# Ensure the root really is availalble
|
|
||||||
MEDIA_ROOT.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
# List of allowed hosts (default = allow all)
|
# List of allowed hosts (default = allow all)
|
||||||
ALLOWED_HOSTS = CONFIG.get('allowed_hosts', ['*'])
|
ALLOWED_HOSTS = get_setting(
|
||||||
|
config_key='allowed_hosts',
|
||||||
|
default_value=['*']
|
||||||
|
)
|
||||||
|
|
||||||
# Cross Origin Resource Sharing (CORS) options
|
# Cross Origin Resource Sharing (CORS) options
|
||||||
|
|
||||||
@ -184,13 +100,15 @@ ALLOWED_HOSTS = CONFIG.get('allowed_hosts', ['*'])
|
|||||||
CORS_URLS_REGEX = r'^/api/.*$'
|
CORS_URLS_REGEX = r'^/api/.*$'
|
||||||
|
|
||||||
# Extract CORS options from configuration file
|
# Extract CORS options from configuration file
|
||||||
cors_opt = CONFIG.get('cors', None)
|
CORS_ORIGIN_ALLOW_ALL = get_boolean_setting(
|
||||||
|
config_key='cors.allow_all',
|
||||||
|
default_value=False,
|
||||||
|
)
|
||||||
|
|
||||||
if cors_opt:
|
CORS_ORIGIN_WHITELIST = get_setting(
|
||||||
CORS_ORIGIN_ALLOW_ALL = cors_opt.get('allow_all', False)
|
config_key='cors.whitelist',
|
||||||
|
default_value=[]
|
||||||
if not CORS_ORIGIN_ALLOW_ALL:
|
)
|
||||||
CORS_ORIGIN_WHITELIST = cors_opt.get('whitelist', []) # pragma: no cover
|
|
||||||
|
|
||||||
# Web URL endpoint for served static files
|
# Web URL endpoint for served static files
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
@ -214,12 +132,6 @@ STATIC_COLOR_THEMES_DIR = STATIC_ROOT.joinpath('css', 'color-themes')
|
|||||||
# Web URL endpoint for served media files
|
# Web URL endpoint for served media files
|
||||||
MEDIA_URL = '/media/'
|
MEDIA_URL = '/media/'
|
||||||
|
|
||||||
if DEBUG:
|
|
||||||
logger.info("InvenTree running with DEBUG enabled")
|
|
||||||
|
|
||||||
logger.debug(f"MEDIA_ROOT: '{MEDIA_ROOT}'")
|
|
||||||
logger.debug(f"STATIC_ROOT: '{STATIC_ROOT}'")
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
@ -320,6 +232,9 @@ INTERNAL_IPS = [
|
|||||||
'127.0.0.1',
|
'127.0.0.1',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Internal flag to determine if we are running in docker mode
|
||||||
|
DOCKER = get_boolean_setting('INVENTREE_DOCKER', default_value=False)
|
||||||
|
|
||||||
if DOCKER: # pragma: no cover
|
if DOCKER: # pragma: no cover
|
||||||
# Internal IP addresses are different when running under docker
|
# Internal IP addresses are different when running under docker
|
||||||
hostname, ___, ips = socket.gethostbyname_ex(socket.gethostname())
|
hostname, ___, ips = socket.gethostbyname_ex(socket.gethostname())
|
||||||
@ -334,7 +249,8 @@ if DEBUG:
|
|||||||
# Base URL for admin pages (default="admin")
|
# Base URL for admin pages (default="admin")
|
||||||
INVENTREE_ADMIN_URL = get_setting(
|
INVENTREE_ADMIN_URL = get_setting(
|
||||||
'INVENTREE_ADMIN_URL',
|
'INVENTREE_ADMIN_URL',
|
||||||
CONFIG.get('admin_url', 'admin'),
|
config_key='admin_url',
|
||||||
|
default_value='admin'
|
||||||
)
|
)
|
||||||
|
|
||||||
ROOT_URLCONF = 'InvenTree.urls'
|
ROOT_URLCONF = 'InvenTree.urls'
|
||||||
@ -498,7 +414,7 @@ if "postgres" in db_engine: # pragma: no cover
|
|||||||
# long to connect to the database server
|
# long to connect to the database server
|
||||||
# # seconds, 2 is minium allowed by libpq
|
# # seconds, 2 is minium allowed by libpq
|
||||||
db_options["connect_timeout"] = int(
|
db_options["connect_timeout"] = int(
|
||||||
os.getenv("INVENTREE_DB_TIMEOUT", 2)
|
get_setting('INVENTREE_DB_TIMEOUT', 'database.timeout', 2)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Setup TCP keepalive
|
# Setup TCP keepalive
|
||||||
@ -509,23 +425,27 @@ if "postgres" in db_engine: # pragma: no cover
|
|||||||
# # 0 - TCP Keepalives disabled; 1 - enabled
|
# # 0 - TCP Keepalives disabled; 1 - enabled
|
||||||
if "keepalives" not in db_options:
|
if "keepalives" not in db_options:
|
||||||
db_options["keepalives"] = int(
|
db_options["keepalives"] = int(
|
||||||
os.getenv("INVENTREE_DB_TCP_KEEPALIVES", "1")
|
get_setting('INVENTREE_DB_TCP_KEEPALIVES', 'database.tcp_keepalives', 1)
|
||||||
)
|
)
|
||||||
# # Seconds after connection is idle to send keep alive
|
|
||||||
|
# Seconds after connection is idle to send keep alive
|
||||||
if "keepalives_idle" not in db_options:
|
if "keepalives_idle" not in db_options:
|
||||||
db_options["keepalives_idle"] = int(
|
db_options["keepalives_idle"] = int(
|
||||||
os.getenv("INVENTREE_DB_TCP_KEEPALIVES_IDLE", "1")
|
get_setting('INVENTREE_DB_TCP_KEEPALIVES_IDLE', 'database.tcp_keepalives_idle', 1)
|
||||||
)
|
)
|
||||||
# # Seconds after missing ACK to send another keep alive
|
|
||||||
|
# Seconds after missing ACK to send another keep alive
|
||||||
if "keepalives_interval" not in db_options:
|
if "keepalives_interval" not in db_options:
|
||||||
db_options["keepalives_interval"] = int(
|
db_options["keepalives_interval"] = int(
|
||||||
os.getenv("INVENTREE_DB_TCP_KEEPALIVES_INTERVAL", "1")
|
get_setting("INVENTREE_DB_TCP_KEEPALIVES_INTERVAL", "database.tcp_keepalives_internal", "1")
|
||||||
)
|
)
|
||||||
# # Number of missing ACKs before we close the connection
|
|
||||||
|
# Number of missing ACKs before we close the connection
|
||||||
if "keepalives_count" not in db_options:
|
if "keepalives_count" not in db_options:
|
||||||
db_options["keepalives_count"] = int(
|
db_options["keepalives_count"] = int(
|
||||||
os.getenv("INVENTREE_DB_TCP_KEEPALIVES_COUNT", "5")
|
get_setting("INVENTREE_DB_TCP_KEEPALIVES_COUNT", "database.tcp_keepalives_count", "5")
|
||||||
)
|
)
|
||||||
|
|
||||||
# # Milliseconds for how long pending data should remain unacked
|
# # Milliseconds for how long pending data should remain unacked
|
||||||
# by the remote server
|
# by the remote server
|
||||||
# TODO: Supported starting in PSQL 11
|
# TODO: Supported starting in PSQL 11
|
||||||
@ -538,17 +458,11 @@ if "postgres" in db_engine: # pragma: no cover
|
|||||||
# https://www.postgresql.org/docs/devel/transaction-iso.html
|
# https://www.postgresql.org/docs/devel/transaction-iso.html
|
||||||
# https://docs.djangoproject.com/en/3.2/ref/databases/#isolation-level
|
# https://docs.djangoproject.com/en/3.2/ref/databases/#isolation-level
|
||||||
if "isolation_level" not in db_options:
|
if "isolation_level" not in db_options:
|
||||||
serializable = _is_true(
|
serializable = get_boolean_setting('INVENTREE_DB_ISOLATION_SERIALIZABLE', 'database.serializable', False)
|
||||||
os.getenv("INVENTREE_DB_ISOLATION_SERIALIZABLE", "false")
|
db_options["isolation_level"] = ISOLATION_LEVEL_SERIALIZABLE if serializable else ISOLATION_LEVEL_READ_COMMITTED
|
||||||
)
|
|
||||||
db_options["isolation_level"] = (
|
|
||||||
ISOLATION_LEVEL_SERIALIZABLE
|
|
||||||
if serializable
|
|
||||||
else ISOLATION_LEVEL_READ_COMMITTED
|
|
||||||
)
|
|
||||||
|
|
||||||
# Specific options for MySql / MariaDB backend
|
# Specific options for MySql / MariaDB backend
|
||||||
if "mysql" in db_engine: # pragma: no cover
|
elif "mysql" in db_engine: # pragma: no cover
|
||||||
# TODO TCP time outs and keepalives
|
# TODO TCP time outs and keepalives
|
||||||
|
|
||||||
# MariaDB's default isolation level is Repeatable Read which is
|
# MariaDB's default isolation level is Repeatable Read which is
|
||||||
@ -558,15 +472,11 @@ if "mysql" in db_engine: # pragma: no cover
|
|||||||
# https://mariadb.com/kb/en/mariadb-transactions-and-isolation-levels-for-sql-server-users/#changing-the-isolation-level
|
# https://mariadb.com/kb/en/mariadb-transactions-and-isolation-levels-for-sql-server-users/#changing-the-isolation-level
|
||||||
# https://docs.djangoproject.com/en/3.2/ref/databases/#mysql-isolation-level
|
# https://docs.djangoproject.com/en/3.2/ref/databases/#mysql-isolation-level
|
||||||
if "isolation_level" not in db_options:
|
if "isolation_level" not in db_options:
|
||||||
serializable = _is_true(
|
serializable = get_boolean_setting('INVENTREE_DB_ISOLATION_SERIALIZABLE', 'database.serializable', False)
|
||||||
os.getenv("INVENTREE_DB_ISOLATION_SERIALIZABLE", "false")
|
db_options["isolation_level"] = "serializable" if serializable else "read committed"
|
||||||
)
|
|
||||||
db_options["isolation_level"] = (
|
|
||||||
"serializable" if serializable else "read committed"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Specific options for sqlite backend
|
# Specific options for sqlite backend
|
||||||
if "sqlite" in db_engine:
|
elif "sqlite" in db_engine:
|
||||||
# TODO: Verify timeouts are not an issue because no network is involved for SQLite
|
# TODO: Verify timeouts are not an issue because no network is involved for SQLite
|
||||||
|
|
||||||
# SQLite's default isolation level is Serializable due to SQLite's
|
# SQLite's default isolation level is Serializable due to SQLite's
|
||||||
@ -591,13 +501,11 @@ DATABASES = {
|
|||||||
'default': db_config
|
'default': db_config
|
||||||
}
|
}
|
||||||
|
|
||||||
_cache_config = CONFIG.get("cache", {})
|
# Cache configuration
|
||||||
_cache_host = _cache_config.get("host", os.getenv("INVENTREE_CACHE_HOST"))
|
cache_host = get_setting('INVENTREE_CACHE_HOST', 'cache.host', None)
|
||||||
_cache_port = _cache_config.get(
|
cache_port = get_setting('INVENTREE_CACHE_PORT', 'cache.port', '6379')
|
||||||
"port", os.getenv("INVENTREE_CACHE_PORT", "6379")
|
|
||||||
)
|
|
||||||
|
|
||||||
if _cache_host: # pragma: no cover
|
if cache_host: # pragma: no cover
|
||||||
# We are going to rely upon a possibly non-localhost for our cache,
|
# We are going to rely upon a possibly non-localhost for our cache,
|
||||||
# so don't wait too long for the cache as nothing in the cache should be
|
# so don't wait too long for the cache as nothing in the cache should be
|
||||||
# irreplacable.
|
# irreplacable.
|
||||||
@ -606,7 +514,7 @@ if _cache_host: # pragma: no cover
|
|||||||
"SOCKET_CONNECT_TIMEOUT": int(os.getenv("CACHE_CONNECT_TIMEOUT", "2")),
|
"SOCKET_CONNECT_TIMEOUT": int(os.getenv("CACHE_CONNECT_TIMEOUT", "2")),
|
||||||
"SOCKET_TIMEOUT": int(os.getenv("CACHE_SOCKET_TIMEOUT", "2")),
|
"SOCKET_TIMEOUT": int(os.getenv("CACHE_SOCKET_TIMEOUT", "2")),
|
||||||
"CONNECTION_POOL_KWARGS": {
|
"CONNECTION_POOL_KWARGS": {
|
||||||
"socket_keepalive": _is_true(
|
"socket_keepalive": config.is_true(
|
||||||
os.getenv("CACHE_TCP_KEEPALIVE", "1")
|
os.getenv("CACHE_TCP_KEEPALIVE", "1")
|
||||||
),
|
),
|
||||||
"socket_keepalive_options": {
|
"socket_keepalive_options": {
|
||||||
@ -628,7 +536,7 @@ if _cache_host: # pragma: no cover
|
|||||||
CACHES = {
|
CACHES = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "django_redis.cache.RedisCache",
|
"BACKEND": "django_redis.cache.RedisCache",
|
||||||
"LOCATION": f"redis://{_cache_host}:{_cache_port}/0",
|
"LOCATION": f"redis://{cache_host}:{cache_port}/0",
|
||||||
"OPTIONS": _cache_options,
|
"OPTIONS": _cache_options,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -639,17 +547,11 @@ else:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
# django-q background worker configuration
|
||||||
# 4 background workers seems like a sensible default
|
|
||||||
background_workers = int(os.environ.get('INVENTREE_BACKGROUND_WORKERS', 4))
|
|
||||||
except ValueError: # pragma: no cover
|
|
||||||
background_workers = 4
|
|
||||||
|
|
||||||
# django-q configuration
|
|
||||||
Q_CLUSTER = {
|
Q_CLUSTER = {
|
||||||
'name': 'InvenTree',
|
'name': 'InvenTree',
|
||||||
'workers': background_workers,
|
'workers': int(get_setting('INVENTREE_BACKGROUND_WORKERS', 'background.workers', 4)),
|
||||||
'timeout': 90,
|
'timeout': int(get_setting('INVENTREE_BACKGROUND_TIMEOUT', 'background.timeout', 90)),
|
||||||
'retry': 120,
|
'retry': 120,
|
||||||
'queue_limit': 50,
|
'queue_limit': 50,
|
||||||
'bulk': 10,
|
'bulk': 10,
|
||||||
@ -657,7 +559,7 @@ Q_CLUSTER = {
|
|||||||
'sync': False,
|
'sync': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _cache_host: # pragma: no cover
|
if cache_host: # pragma: no cover
|
||||||
# If using external redis cache, make the cache the broker for Django Q
|
# If using external redis cache, make the cache the broker for Django Q
|
||||||
# as well
|
# as well
|
||||||
Q_CLUSTER["django_redis"] = "worker"
|
Q_CLUSTER["django_redis"] = "worker"
|
||||||
@ -698,8 +600,7 @@ if type(EXTRA_URL_SCHEMES) not in [list]: # pragma: no cover
|
|||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/dev/topics/i18n/
|
# https://docs.djangoproject.com/en/dev/topics/i18n/
|
||||||
|
LANGUAGE_CODE = get_setting('INVENTREE_LANGUAGE', 'language', 'en-us')
|
||||||
LANGUAGE_CODE = CONFIG.get('language', 'en-us')
|
|
||||||
|
|
||||||
# If a new language translation is supported, it must be added here
|
# If a new language translation is supported, it must be added here
|
||||||
LANGUAGES = [
|
LANGUAGES = [
|
||||||
@ -730,7 +631,7 @@ LANGUAGES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Testing interface translations
|
# Testing interface translations
|
||||||
if get_setting('TEST_TRANSLATIONS', False): # pragma: no cover
|
if get_boolean_setting('TEST_TRANSLATIONS', default_value=False): # pragma: no cover
|
||||||
# Set default language
|
# Set default language
|
||||||
LANGUAGE_CODE = 'xx'
|
LANGUAGE_CODE = 'xx'
|
||||||
|
|
||||||
@ -762,68 +663,29 @@ for currency in CURRENCIES:
|
|||||||
print(f"Currency code '{currency}' is not supported")
|
print(f"Currency code '{currency}' is not supported")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
# Custom currency exchange backend
|
# Custom currency exchange backend
|
||||||
EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeExchange'
|
EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeExchange'
|
||||||
|
|
||||||
# Extract email settings from the config file
|
# Email configuration options
|
||||||
email_config = CONFIG.get('email', {})
|
EMAIL_BACKEND = get_setting('INVENTREE_EMAIL_BACKEND', 'email.backend', 'django.core.mail.backends.smtp.EmailBackend')
|
||||||
|
EMAIL_HOST = get_setting('INVENTREE_EMAIL_HOST', 'email.host', '')
|
||||||
|
EMAIL_PORT = int(get_setting('INVENTREE_EMAIL_PORT', 'email.port', 25))
|
||||||
|
EMAIL_HOST_USER = get_setting('INVENTREE_EMAIL_USERNAME', 'email.username', '')
|
||||||
|
EMAIL_HOST_PASSWORD = get_setting('INVENTREE_EMAIL_PASSWORD', 'email.password', '')
|
||||||
|
EMAIL_SUBJECT_PREFIX = get_setting('INVENTREE_EMAIL_PREFIX', 'email.prefix', '[InvenTree] ')
|
||||||
|
EMAIL_USE_TLS = get_boolean_setting('INVENTREE_EMAIL_TLS', 'email.tls', False)
|
||||||
|
EMAIL_USE_SSL = get_boolean_setting('INVENTREE_EMAIL_SSL', 'email.ssl', False)
|
||||||
|
|
||||||
EMAIL_BACKEND = get_setting(
|
DEFUALT_FROM_EMAIL = get_setting('INVENTREE_EMAIL_SENDER', 'email.sender', '')
|
||||||
'INVENTREE_EMAIL_BACKEND',
|
|
||||||
email_config.get('backend', 'django.core.mail.backends.smtp.EmailBackend')
|
|
||||||
)
|
|
||||||
|
|
||||||
# Email backend settings
|
|
||||||
EMAIL_HOST = get_setting(
|
|
||||||
'INVENTREE_EMAIL_HOST',
|
|
||||||
email_config.get('host', '')
|
|
||||||
)
|
|
||||||
|
|
||||||
EMAIL_PORT = get_setting(
|
|
||||||
'INVENTREE_EMAIL_PORT',
|
|
||||||
email_config.get('port', 25)
|
|
||||||
)
|
|
||||||
|
|
||||||
EMAIL_HOST_USER = get_setting(
|
|
||||||
'INVENTREE_EMAIL_USERNAME',
|
|
||||||
email_config.get('username', ''),
|
|
||||||
)
|
|
||||||
|
|
||||||
EMAIL_HOST_PASSWORD = get_setting(
|
|
||||||
'INVENTREE_EMAIL_PASSWORD',
|
|
||||||
email_config.get('password', ''),
|
|
||||||
)
|
|
||||||
|
|
||||||
DEFAULT_FROM_EMAIL = get_setting(
|
|
||||||
'INVENTREE_EMAIL_SENDER',
|
|
||||||
email_config.get('sender', ''),
|
|
||||||
)
|
|
||||||
|
|
||||||
EMAIL_SUBJECT_PREFIX = '[InvenTree] '
|
|
||||||
|
|
||||||
EMAIL_USE_LOCALTIME = False
|
EMAIL_USE_LOCALTIME = False
|
||||||
|
|
||||||
EMAIL_USE_TLS = get_setting(
|
|
||||||
'INVENTREE_EMAIL_TLS',
|
|
||||||
email_config.get('tls', False),
|
|
||||||
)
|
|
||||||
|
|
||||||
EMAIL_USE_SSL = get_setting(
|
|
||||||
'INVENTREE_EMAIL_SSL',
|
|
||||||
email_config.get('ssl', False),
|
|
||||||
)
|
|
||||||
|
|
||||||
EMAIL_TIMEOUT = 60
|
EMAIL_TIMEOUT = 60
|
||||||
|
|
||||||
LOCALE_PATHS = (
|
LOCALE_PATHS = (
|
||||||
BASE_DIR.joinpath('locale/'),
|
BASE_DIR.joinpath('locale/'),
|
||||||
)
|
)
|
||||||
|
|
||||||
TIME_ZONE = get_setting(
|
TIME_ZONE = get_setting('INVENTREE_TIMEZONE', 'timezone', 'UTC')
|
||||||
'INVENTREE_TIMEZONE',
|
|
||||||
CONFIG.get('timezone', 'UTC')
|
|
||||||
)
|
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
@ -856,8 +718,8 @@ SOCIALACCOUNT_PROVIDERS = CONFIG.get('social_providers', [])
|
|||||||
SOCIALACCOUNT_STORE_TOKENS = True
|
SOCIALACCOUNT_STORE_TOKENS = True
|
||||||
|
|
||||||
# settings for allauth
|
# settings for allauth
|
||||||
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = get_setting('INVENTREE_LOGIN_CONFIRM_DAYS', CONFIG.get('login_confirm_days', 3))
|
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = get_setting('INVENTREE_LOGIN_CONFIRM_DAYS', 'login_confirm_days', 3)
|
||||||
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = get_setting('INVENTREE_LOGIN_ATTEMPTS', CONFIG.get('login_attempts', 5))
|
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = get_setting('INVENTREE_LOGIN_ATTEMPTS', 'login_attempts', 5)
|
||||||
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
|
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
|
||||||
ACCOUNT_PREVENT_ENUMERATION = True
|
ACCOUNT_PREVENT_ENUMERATION = True
|
||||||
|
|
||||||
@ -877,8 +739,8 @@ SOCIALACCOUNT_ADAPTER = 'InvenTree.forms.CustomSocialAccountAdapter'
|
|||||||
ACCOUNT_ADAPTER = 'InvenTree.forms.CustomAccountAdapter'
|
ACCOUNT_ADAPTER = 'InvenTree.forms.CustomAccountAdapter'
|
||||||
|
|
||||||
# login settings
|
# login settings
|
||||||
REMOTE_LOGIN = get_setting('INVENTREE_REMOTE_LOGIN', CONFIG.get('remote_login', False))
|
REMOTE_LOGIN = get_boolean_setting('INVENTREE_REMOTE_LOGIN', 'remote_login_enabled', False)
|
||||||
REMOTE_LOGIN_HEADER = get_setting('INVENTREE_REMOTE_LOGIN_HEADER', CONFIG.get('remote_login_header', 'REMOTE_USER'))
|
REMOTE_LOGIN_HEADER = get_setting('INVENTREE_REMOTE_LOGIN_HEADER', 'remote_login_header', 'REMOTE_USER')
|
||||||
|
|
||||||
# Markdownify configuration
|
# Markdownify configuration
|
||||||
# Ref: https://django-markdownify.readthedocs.io/en/latest/settings.html
|
# Ref: https://django-markdownify.readthedocs.io/en/latest/settings.html
|
||||||
@ -909,11 +771,12 @@ MARKDOWNIFY = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Error reporting
|
# sentry.io integration for error reporting
|
||||||
SENTRY_ENABLED = get_setting('INVENTREE_SENTRY_ENABLED', CONFIG.get('sentry_enabled', False))
|
SENTRY_ENABLED = get_boolean_setting('INVENTREE_SENTRY_ENABLED', 'sentry_enabled', False)
|
||||||
SENTRY_DSN = get_setting('INVENTREE_SENTRY_DSN', CONFIG.get('sentry_dsn', INVENTREE_DSN))
|
# Default Sentry DSN (can be overriden if user wants custom sentry integration)
|
||||||
|
INVENTREE_DSN = 'https://3928ccdba1d34895abde28031fd00100@o378676.ingest.sentry.io/6494600'
|
||||||
SENTRY_SAMPLE_RATE = float(get_setting('INVENTREE_SENTRY_SAMPLE_RATE', CONFIG.get('sentry_sample_rate', 0.1)))
|
SENTRY_DSN = get_setting('INVENTREE_SENTRY_DSN', 'sentry_dsn', INVENTREE_DSN)
|
||||||
|
SENTRY_SAMPLE_RATE = float(get_setting('INVENTREE_SENTRY_SAMPLE_RATE', 'sentry_sample_rate', 0.1))
|
||||||
|
|
||||||
if SENTRY_ENABLED and SENTRY_DSN: # pragma: no cover
|
if SENTRY_ENABLED and SENTRY_DSN: # pragma: no cover
|
||||||
sentry_sdk.init(
|
sentry_sdk.init(
|
||||||
@ -932,7 +795,7 @@ if SENTRY_ENABLED and SENTRY_DSN: # pragma: no cover
|
|||||||
sentry_sdk.set_tag(f'inventree_{key}', val)
|
sentry_sdk.set_tag(f'inventree_{key}', val)
|
||||||
|
|
||||||
# In-database error logging
|
# In-database error logging
|
||||||
IGNORRED_ERRORS = [
|
IGNORED_ERRORS = [
|
||||||
Http404
|
Http404
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -941,33 +804,29 @@ MAINTENANCE_MODE_RETRY_AFTER = 60
|
|||||||
MAINTENANCE_MODE_STATE_BACKEND = 'maintenance_mode.backends.DefaultStorageBackend'
|
MAINTENANCE_MODE_STATE_BACKEND = 'maintenance_mode.backends.DefaultStorageBackend'
|
||||||
|
|
||||||
# Are plugins enabled?
|
# Are plugins enabled?
|
||||||
PLUGINS_ENABLED = _is_true(get_setting(
|
PLUGINS_ENABLED = get_boolean_setting('INVENTREE_PLUGINS_ENABLED', 'plugins_enabled', False)
|
||||||
'INVENTREE_PLUGINS_ENABLED',
|
|
||||||
CONFIG.get('plugins_enabled', False),
|
|
||||||
))
|
|
||||||
|
|
||||||
PLUGIN_FILE = get_plugin_file()
|
PLUGIN_FILE = config.get_plugin_file()
|
||||||
|
|
||||||
# Plugin test settings
|
# Plugin test settings
|
||||||
PLUGIN_TESTING = get_setting('PLUGIN_TESTING', TESTING) # are plugins beeing tested?
|
PLUGIN_TESTING = CONFIG.get('PLUGIN_TESTING', TESTING) # are plugins beeing tested?
|
||||||
PLUGIN_TESTING_SETUP = get_setting('PLUGIN_TESTING_SETUP', False) # load plugins from setup hooks in testing?
|
PLUGIN_TESTING_SETUP = CONFIG.get('PLUGIN_TESTING_SETUP', False) # load plugins from setup hooks in testing?
|
||||||
PLUGIN_TESTING_EVENTS = False # Flag if events are tested right now
|
PLUGIN_TESTING_EVENTS = False # Flag if events are tested right now
|
||||||
PLUGIN_RETRY = get_setting('PLUGIN_RETRY', 5) # how often should plugin loading be tried?
|
PLUGIN_RETRY = CONFIG.get('PLUGIN_RETRY', 5) # how often should plugin loading be tried?
|
||||||
PLUGIN_FILE_CHECKED = False # Was the plugin file checked?
|
PLUGIN_FILE_CHECKED = False # Was the plugin file checked?
|
||||||
|
|
||||||
# User interface customization values
|
# User interface customization values
|
||||||
CUSTOMIZE = get_setting(
|
CUSTOMIZE = get_setting('INVENTREE_CUSTOMIZE', 'customize', {})
|
||||||
'INVENTREE_CUSTOMIZE',
|
|
||||||
CONFIG.get('customize', {}),
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
CUSTOM_LOGO = get_setting(
|
CUSTOM_LOGO = get_setting('INVENTREE_CUSTOM_LOGO', 'customize.logo', None)
|
||||||
'INVENTREE_CUSTOM_LOGO',
|
|
||||||
CUSTOMIZE.get('logo', False)
|
|
||||||
)
|
|
||||||
|
|
||||||
# check that the logo-file exsists in media
|
# check that the logo-file exsists in media
|
||||||
if CUSTOM_LOGO and not default_storage.exists(CUSTOM_LOGO): # pragma: no cover
|
if CUSTOM_LOGO and not default_storage.exists(CUSTOM_LOGO): # pragma: no cover
|
||||||
logger.warning(f"The custom logo file '{CUSTOM_LOGO}' could not be found in the default media storage")
|
logger.warning(f"The custom logo file '{CUSTOM_LOGO}' could not be found in the default media storage")
|
||||||
CUSTOM_LOGO = False
|
CUSTOM_LOGO = False
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
logger.info("InvenTree running with DEBUG enabled")
|
||||||
|
|
||||||
|
logger.debug(f"MEDIA_ROOT: '{MEDIA_ROOT}'")
|
||||||
|
logger.debug(f"STATIC_ROOT: '{STATIC_ROOT}'")
|
||||||
|
@ -84,7 +84,7 @@ class MiddlewareTests(InvenTreeTestCase):
|
|||||||
log_error('testpath')
|
log_error('testpath')
|
||||||
|
|
||||||
# Test setup without ignored errors
|
# Test setup without ignored errors
|
||||||
settings.IGNORRED_ERRORS = []
|
settings.IGNORED_ERRORS = []
|
||||||
response = self.client.get(reverse('part-detail', kwargs={'pk': 9999}))
|
response = self.client.get(reverse('part-detail', kwargs={'pk': 9999}))
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
check(1)
|
check(1)
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
from common.models import InvenTreeSetting
|
from common.models import InvenTreeSetting
|
||||||
from InvenTree.settings import get_setting, CONFIG
|
from InvenTree.config import get_setting
|
||||||
|
|
||||||
def set_default_currency(apps, schema_editor):
|
def set_default_currency(apps, schema_editor):
|
||||||
""" migrate the currency setting from config.yml to db """
|
""" migrate the currency setting from config.yml to db """
|
||||||
# get value from settings-file
|
# get value from settings-file
|
||||||
base_currency = get_setting('INVENTREE_BASE_CURRENCY', CONFIG.get('base_currency', 'USD'))
|
base_currency = get_setting('INVENTREE_BASE_CURRENCY', 'base_currency', 'USD')
|
||||||
# write to database
|
# write to database
|
||||||
InvenTreeSetting.set_setting('INVENTREE_DEFAULT_CURRENCY', base_currency, None, create=True)
|
InvenTreeSetting.set_setting('INVENTREE_DEFAULT_CURRENCY', base_currency, None, create=True)
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
# Database backend selection - Configure backend database settings
|
# Database backend selection - Configure backend database settings
|
||||||
# Ref: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-DATABASES
|
# Documentation: https://inventree.readthedocs.io/en/latest/start/config/
|
||||||
# Specify database parameters below as they appear in the Django docs
|
|
||||||
|
|
||||||
# Note: Database configuration options can also be specified from environmental variables,
|
# Note: Database configuration options can also be specified from environmental variables,
|
||||||
# with the prefix INVENTREE_DB_
|
# with the prefix INVENTREE_DB_
|
||||||
@ -44,20 +43,32 @@ database:
|
|||||||
# ENGINE: sqlite3
|
# ENGINE: sqlite3
|
||||||
# NAME: '/home/inventree/database.sqlite3'
|
# NAME: '/home/inventree/database.sqlite3'
|
||||||
|
|
||||||
|
# Set debug to False to run in production mode
|
||||||
|
# Use the environment variable INVENTREE_DEBUG
|
||||||
|
debug: True
|
||||||
|
|
||||||
|
# Configure the system logging level
|
||||||
|
# Use environment variable INVENTREE_LOG_LEVEL
|
||||||
|
# Options: DEBUG / INFO / WARNING / ERROR / CRITICAL
|
||||||
|
log_level: WARNING
|
||||||
|
|
||||||
# Select default system language (default is 'en-us')
|
# Select default system language (default is 'en-us')
|
||||||
|
# Use the environment variable INVENTREE_LANGUAGE
|
||||||
language: en-us
|
language: en-us
|
||||||
|
|
||||||
# System time-zone (default is UTC)
|
# System time-zone (default is UTC)
|
||||||
# Reference: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
# Reference: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||||
# Select an option from the "TZ database name" column
|
|
||||||
# Use the environment variable INVENTREE_TIMEZONE
|
|
||||||
timezone: UTC
|
timezone: UTC
|
||||||
|
|
||||||
# Base currency code
|
# Base currency code (or use env var INVENTREE_BASE_CURRENCY)
|
||||||
base_currency: USD
|
base_currency: USD
|
||||||
|
|
||||||
# List of currencies supported by default.
|
# Add new user on first startup
|
||||||
# Add other currencies here to allow use in InvenTree
|
#admin_user: admin
|
||||||
|
#admin_email: info@example.com
|
||||||
|
#admin_password: inventree
|
||||||
|
|
||||||
|
# List of currencies supported by default. Add other currencies here to allow use in InvenTree
|
||||||
currencies:
|
currencies:
|
||||||
- AUD
|
- AUD
|
||||||
- CAD
|
- CAD
|
||||||
@ -70,15 +81,6 @@ currencies:
|
|||||||
|
|
||||||
# Email backend configuration
|
# Email backend configuration
|
||||||
# Ref: https://docs.djangoproject.com/en/dev/topics/email/
|
# Ref: https://docs.djangoproject.com/en/dev/topics/email/
|
||||||
# Available options:
|
|
||||||
# host: Email server host address
|
|
||||||
# port: Email port
|
|
||||||
# username: Account username
|
|
||||||
# password: Account password
|
|
||||||
# prefix: Email subject prefix
|
|
||||||
# tls: Enable TLS support
|
|
||||||
# ssl: Enable SSL support
|
|
||||||
|
|
||||||
# Alternatively, these options can all be set using environment variables,
|
# Alternatively, these options can all be set using environment variables,
|
||||||
# with the INVENTREE_EMAIL_ prefix:
|
# with the INVENTREE_EMAIL_ prefix:
|
||||||
# e.g. INVENTREE_EMAIL_HOST / INVENTREE_EMAIL_PORT / INVENTREE_EMAIL_USERNAME
|
# e.g. INVENTREE_EMAIL_HOST / INVENTREE_EMAIL_PORT / INVENTREE_EMAIL_USERNAME
|
||||||
@ -94,31 +96,17 @@ email:
|
|||||||
tls: False
|
tls: False
|
||||||
ssl: False
|
ssl: False
|
||||||
|
|
||||||
# Set debug to False to run in production mode
|
|
||||||
# Use the environment variable INVENTREE_DEBUG
|
|
||||||
debug: True
|
|
||||||
|
|
||||||
# Set debug_toolbar to True to enable a debugging toolbar for InvenTree
|
|
||||||
# Note: This will only be displayed if DEBUG mode is enabled,
|
|
||||||
# and only if InvenTree is accessed from a local IP (127.0.0.1)
|
|
||||||
debug_toolbar: False
|
|
||||||
|
|
||||||
# Set sentry_enabled to True to report errors back to the maintainers
|
# Set sentry_enabled to True to report errors back to the maintainers
|
||||||
# Use the environment variable INVENTREE_SENTRY_ENABLED
|
# Set sentry,dsn to your custom DSN if you want to use your own instance for error reporting
|
||||||
# sentry_enabled: True
|
sentry_enabled: False
|
||||||
|
#sentry_sample_rate: 0.1
|
||||||
# Set sentry_dsn to your custom DSN if you want to use your own instance for error reporting
|
#sentry_dsn: https://custom@custom.ingest.sentry.io/custom
|
||||||
# Use the environment variable INVENTREE_SENTRY_DSN
|
|
||||||
# sentry_dsn: https://custom@custom.ingest.sentry.io/custom
|
|
||||||
|
|
||||||
# Set this variable to True to enable InvenTree Plugins
|
# Set this variable to True to enable InvenTree Plugins
|
||||||
# Alternatively, use the environment variable INVENTREE_PLUGINS_ENABLED
|
# Alternatively, use the environment variable INVENTREE_PLUGINS_ENABLED
|
||||||
plugins_enabled: False
|
plugins_enabled: False
|
||||||
|
#plugin_file: /path/to/plugins.txt
|
||||||
# Configure the system logging level
|
#plugin_dir: /path/to/plugins/
|
||||||
# Use environment variable INVENTREE_LOG_LEVEL
|
|
||||||
# Options: DEBUG / INFO / WARNING / ERROR / CRITICAL
|
|
||||||
log_level: WARNING
|
|
||||||
|
|
||||||
# Allowed hosts (see ALLOWED_HOSTS in Django settings documentation)
|
# Allowed hosts (see ALLOWED_HOSTS in Django settings documentation)
|
||||||
# A list of strings representing the host/domain names that this Django site can serve.
|
# A list of strings representing the host/domain names that this Django site can serve.
|
||||||
@ -138,14 +126,15 @@ cors:
|
|||||||
# - https://sub.example.com
|
# - https://sub.example.com
|
||||||
|
|
||||||
# MEDIA_ROOT is the local filesystem location for storing uploaded files
|
# MEDIA_ROOT is the local filesystem location for storing uploaded files
|
||||||
# By default, it is stored under /home/inventree/data/media
|
# media_root: '/home/inventree/data/media'
|
||||||
# Use environment variable INVENTREE_MEDIA_ROOT
|
|
||||||
media_root: '/home/inventree/data/media'
|
|
||||||
|
|
||||||
# STATIC_ROOT is the local filesystem location for storing static files
|
# STATIC_ROOT is the local filesystem location for storing static files
|
||||||
# By default, it is stored under /home/inventree/data/static
|
# static_root: '/home/inventree/data/static'
|
||||||
# Use environment variable INVENTREE_STATIC_ROOT
|
|
||||||
static_root: '/home/inventree/data/static'
|
# Background worker options
|
||||||
|
background:
|
||||||
|
workers: 4
|
||||||
|
timeout: 90
|
||||||
|
|
||||||
# Optional URL schemes to allow in URL fields
|
# Optional URL schemes to allow in URL fields
|
||||||
# By default, only the following schemes are allowed: ['http', 'https', 'ftp', 'ftps']
|
# By default, only the following schemes are allowed: ['http', 'https', 'ftp', 'ftps']
|
||||||
@ -156,25 +145,14 @@ static_root: '/home/inventree/data/static'
|
|||||||
# - ssh
|
# - ssh
|
||||||
|
|
||||||
# Login configuration
|
# Login configuration
|
||||||
# How long do confirmation mail last?
|
login_confirm_days: 3
|
||||||
# Use environment variable INVENTREE_LOGIN_CONFIRM_DAYS
|
login_attempts: 5
|
||||||
#login_confirm_days: 3
|
|
||||||
# How many wrong login attempts are permitted?
|
|
||||||
# Use environment variable INVENTREE_LOGIN_ATTEMPTS
|
|
||||||
#login_attempts: 5
|
|
||||||
|
|
||||||
# Remote / proxy login
|
# Remote / proxy login
|
||||||
# These settings can introduce security problems if configured incorrectly. Please read
|
# These settings can introduce security problems if configured incorrectly. Please read
|
||||||
# https://docs.djangoproject.com/en/4.0/howto/auth-remote-user/ for more details
|
# https://docs.djangoproject.com/en/4.0/howto/auth-remote-user/ for more details
|
||||||
# Use environment variable INVENTREE_REMOTE_LOGIN
|
remote_login_enabled: False
|
||||||
# remote_login: True
|
remote_login_header: REMOTE_USER
|
||||||
# Use environment variable INVENTREE_REMOTE_LOGIN_HEADER
|
|
||||||
# remote_login_header: REMOTE_USER
|
|
||||||
|
|
||||||
# Add new user on first startup
|
|
||||||
#admin_user: admin
|
|
||||||
#admin_email: info@example.com
|
|
||||||
#admin_password: inventree
|
|
||||||
|
|
||||||
# Permit custom authentication backends
|
# Permit custom authentication backends
|
||||||
#authentication_backends:
|
#authentication_backends:
|
||||||
|
@ -22,6 +22,8 @@ from django.utils.text import slugify
|
|||||||
from maintenance_mode.core import (get_maintenance_mode, maintenance_mode_on,
|
from maintenance_mode.core import (get_maintenance_mode, maintenance_mode_on,
|
||||||
set_maintenance_mode)
|
set_maintenance_mode)
|
||||||
|
|
||||||
|
from InvenTree.config import get_setting
|
||||||
|
|
||||||
from .helpers import (IntegrationPluginError, get_plugins, handle_error,
|
from .helpers import (IntegrationPluginError, get_plugins, handle_error,
|
||||||
log_error)
|
log_error)
|
||||||
from .plugin import InvenTreePlugin
|
from .plugin import InvenTreePlugin
|
||||||
@ -199,7 +201,7 @@ class PluginsRegistry:
|
|||||||
if settings.TESTING:
|
if settings.TESTING:
|
||||||
custom_dirs = os.getenv('INVENTREE_PLUGIN_TEST_DIR', None)
|
custom_dirs = os.getenv('INVENTREE_PLUGIN_TEST_DIR', None)
|
||||||
else:
|
else:
|
||||||
custom_dirs = os.getenv('INVENTREE_PLUGIN_DIR', None)
|
custom_dirs = get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir')
|
||||||
|
|
||||||
# Load from user specified directories (unless in testing mode)
|
# Load from user specified directories (unless in testing mode)
|
||||||
dirs.append('plugins')
|
dirs.append('plugins')
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
# Base python requirements for docker containers
|
# Base python requirements for docker containers
|
||||||
|
|
||||||
# Basic package requirements
|
# Basic package requirements
|
||||||
|
invoke>=1.4.0 # Invoke build tool
|
||||||
|
pyyaml>=6.0
|
||||||
setuptools==60.0.5
|
setuptools==60.0.5
|
||||||
wheel>=0.37.0
|
wheel>=0.37.0
|
||||||
invoke>=1.4.0 # Invoke build tool
|
|
||||||
|
|
||||||
# Database links
|
# Database links
|
||||||
psycopg2>=2.9.1
|
psycopg2>=2.9.1
|
||||||
|
13
tasks.py
13
tasks.py
@ -4,6 +4,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@ -522,6 +523,8 @@ def test(c, database=None):
|
|||||||
def setup_test(c, ignore_update=False, dev=False, path="inventree-demo-dataset"):
|
def setup_test(c, ignore_update=False, dev=False, path="inventree-demo-dataset"):
|
||||||
"""Setup a testing enviroment."""
|
"""Setup a testing enviroment."""
|
||||||
|
|
||||||
|
from InvenTree.InvenTree.config import get_media_dir
|
||||||
|
|
||||||
if not ignore_update:
|
if not ignore_update:
|
||||||
update(c)
|
update(c)
|
||||||
|
|
||||||
@ -540,8 +543,16 @@ def setup_test(c, ignore_update=False, dev=False, path="inventree-demo-dataset")
|
|||||||
migrate(c)
|
migrate(c)
|
||||||
|
|
||||||
# Load data
|
# Load data
|
||||||
print("Loading data ...")
|
print("Loading database records ...")
|
||||||
import_records(c, filename=f'{path}/inventree_data.json', clear=True)
|
import_records(c, filename=f'{path}/inventree_data.json', clear=True)
|
||||||
|
|
||||||
|
# Copy media files
|
||||||
|
print("Copying media files ...")
|
||||||
|
src = Path(path).joinpath('media').resolve()
|
||||||
|
dst = get_media_dir()
|
||||||
|
|
||||||
|
shutil.copytree(src, dst, dirs_exist_ok=True)
|
||||||
|
|
||||||
print("Done setting up test enviroment...")
|
print("Done setting up test enviroment...")
|
||||||
print("========================================")
|
print("========================================")
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user