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:
Oliver 2022-07-31 23:16:58 +10:00 committed by GitHub
parent d4eae9da11
commit e9b0f02ecd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 311 additions and 343 deletions

3
.gitignore vendored
View File

@ -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

View File

@ -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
``` ```

View File

@ -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

View File

@ -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)
# First, try to load from the environment variables
if env_var is not None:
val = os.getenv(env_var, None)
if val is not None: if val is not None:
return val return val
if backup_val is not None: # Next, try to load from configuration file
return backup_val 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

View File

@ -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(

View File

@ -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)

View File

@ -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}'")

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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')

View File

@ -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

View File

@ -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("========================================")