mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-03 22:55:43 +00:00 
			
		
		
		
	Switch to pathlib (#3392)
* switch to pathlib * more pathlib * useconvert env to path * fix typo * use resolve instead of absolute * fix gitppod * also allow parents * replace more os operations * fix string replacement feature * make sure root dirs exsist * fix replace function * refactor duplicate code * reduce code * make sure dirs exist * fix typo * also create parent dirs * fix match statement * fix statments expecting string * return getMigrationFileNames to old behaviour * fully resolve config file * make sure comparison works * use pathlib in tasks * fix file count test * reduce code duplication in test + add test for part * fix test * re-add os * Make pathlib usage simpler
This commit is contained in:
		@@ -7,11 +7,12 @@ tasks:
 | 
				
			|||||||
      export INVENTREE_STATIC_ROOT='/workspace/InvenTree/dev/static'
 | 
					      export INVENTREE_STATIC_ROOT='/workspace/InvenTree/dev/static'
 | 
				
			||||||
      export PIP_USER='no'
 | 
					      export PIP_USER='no'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      sudo apt install gettext
 | 
				
			||||||
      python3 -m venv venv
 | 
					      python3 -m venv venv
 | 
				
			||||||
      source venv/bin/activate
 | 
					      source venv/bin/activate
 | 
				
			||||||
      pip install invoke
 | 
					      pip install invoke
 | 
				
			||||||
      mkdir dev
 | 
					      mkdir dev
 | 
				
			||||||
      inv test-setup
 | 
					      inv setup-test
 | 
				
			||||||
      gp sync-done start_server
 | 
					      gp sync-done start_server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 - name: Start server
 | 
					 - name: Start server
 | 
				
			||||||
@@ -23,6 +24,7 @@ tasks:
 | 
				
			|||||||
      export INVENTREE_MEDIA_ROOT='/workspace/InvenTree/inventree-data/media'
 | 
					      export INVENTREE_MEDIA_ROOT='/workspace/InvenTree/inventree-data/media'
 | 
				
			||||||
      export INVENTREE_STATIC_ROOT='/workspace/InvenTree/dev/static'
 | 
					      export INVENTREE_STATIC_ROOT='/workspace/InvenTree/dev/static'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      source venv/bin/activate
 | 
				
			||||||
      inv server
 | 
					      inv server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ports:
 | 
					ports:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,16 +3,17 @@
 | 
				
			|||||||
import logging
 | 
					import logging
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
logger = logging.getLogger('inventree')
 | 
					logger = logging.getLogger('inventree')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_base_dir():
 | 
					def get_base_dir() -> Path:
 | 
				
			||||||
    """Returns the base (top-level) InvenTree directory."""
 | 
					    """Returns the base (top-level) InvenTree directory."""
 | 
				
			||||||
    return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 | 
					    return Path(__file__).parent.parent.resolve()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_config_file():
 | 
					def get_config_file() -> 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!
 | 
				
			||||||
@@ -22,16 +23,15 @@ def get_config_file():
 | 
				
			|||||||
    cfg_filename = os.getenv('INVENTREE_CONFIG_FILE')
 | 
					    cfg_filename = os.getenv('INVENTREE_CONFIG_FILE')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if cfg_filename:
 | 
					    if cfg_filename:
 | 
				
			||||||
        cfg_filename = cfg_filename.strip()
 | 
					        cfg_filename = Path(cfg_filename.strip()).resolve()
 | 
				
			||||||
        cfg_filename = os.path.abspath(cfg_filename)
 | 
					 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        # Config file is *not* specified - use the default
 | 
					        # Config file is *not* specified - use the default
 | 
				
			||||||
        cfg_filename = os.path.join(base_dir, 'config.yaml')
 | 
					        cfg_filename = base_dir.joinpath('config.yaml').resolve()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not os.path.exists(cfg_filename):
 | 
					    if not cfg_filename.exists():
 | 
				
			||||||
        print("InvenTree configuration file 'config.yaml' not found - creating default file")
 | 
					        print("InvenTree configuration file 'config.yaml' not found - creating default file")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cfg_template = os.path.join(base_dir, "config_template.yaml")
 | 
					        cfg_template = base_dir.joinpath("config_template.yaml")
 | 
				
			||||||
        shutil.copyfile(cfg_template, cfg_filename)
 | 
					        shutil.copyfile(cfg_template, cfg_filename)
 | 
				
			||||||
        print(f"Created config file {cfg_filename}")
 | 
					        print(f"Created config file {cfg_filename}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,18 +48,18 @@ def get_plugin_file():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if not PLUGIN_FILE:
 | 
					    if not PLUGIN_FILE:
 | 
				
			||||||
        # If not specified, look in the same directory as the configuration 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        config_dir = os.path.dirname(get_config_file())
 | 
					    if not PLUGIN_FILE.exists():
 | 
				
			||||||
 | 
					 | 
				
			||||||
        PLUGIN_FILE = os.path.join(config_dir, 'plugins.txt')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not os.path.exists(PLUGIN_FILE):
 | 
					 | 
				
			||||||
        logger.warning("Plugin configuration file does not exist")
 | 
					        logger.warning("Plugin configuration file does not exist")
 | 
				
			||||||
        logger.info(f"Creating plugin file at '{PLUGIN_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
 | 
					        # If opening the file fails (no write permission, for example), then this will throw an error
 | 
				
			||||||
        with open(PLUGIN_FILE, 'w') as plugin_file:
 | 
					        PLUGIN_FILE.write_text("# InvenTree Plugins (uses PIP framework to install)\n\n")
 | 
				
			||||||
            plugin_file.write("# InvenTree Plugins (uses PIP framework to install)\n\n")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return PLUGIN_FILE
 | 
					    return PLUGIN_FILE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import os
 | 
				
			|||||||
import os.path
 | 
					import os.path
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
from decimal import Decimal, InvalidOperation
 | 
					from decimal import Decimal, InvalidOperation
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
from wsgiref.util import FileWrapper
 | 
					from wsgiref.util import FileWrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
@@ -211,7 +212,7 @@ def getLogoImage(as_file=False, custom=True):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        if as_file:
 | 
					        if as_file:
 | 
				
			||||||
            path = os.path.join(settings.STATIC_ROOT, 'img/inventree.png')
 | 
					            path = settings.STATIC_ROOT.joinpath('img/inventree.png')
 | 
				
			||||||
            return f"file://{path}"
 | 
					            return f"file://{path}"
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return getStaticUrl('img/inventree.png')
 | 
					            return getStaticUrl('img/inventree.png')
 | 
				
			||||||
@@ -687,20 +688,17 @@ def addUserPermissions(user, permissions):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def getMigrationFileNames(app):
 | 
					def getMigrationFileNames(app):
 | 
				
			||||||
    """Return a list of all migration filenames for provided app."""
 | 
					    """Return a list of all migration filenames for provided app."""
 | 
				
			||||||
    local_dir = os.path.dirname(os.path.abspath(__file__))
 | 
					    local_dir = Path(__file__).parent
 | 
				
			||||||
 | 
					    files = local_dir.joinpath('..', app, 'migrations').iterdir()
 | 
				
			||||||
    migration_dir = os.path.join(local_dir, '..', app, 'migrations')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    files = os.listdir(migration_dir)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Regex pattern for migration files
 | 
					    # Regex pattern for migration files
 | 
				
			||||||
    pattern = r"^[\d]+_.*\.py$"
 | 
					    regex = re.compile(r"^[\d]+_.*\.py$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    migration_files = []
 | 
					    migration_files = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for f in files:
 | 
					    for f in files:
 | 
				
			||||||
        if re.match(pattern, f):
 | 
					        if regex.match(f.name):
 | 
				
			||||||
            migration_files.append(f)
 | 
					            migration_files.append(f.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return migration_files
 | 
					    return migration_files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -437,26 +437,12 @@ class InvenTreeAttachment(models.Model):
 | 
				
			|||||||
        if len(fn) == 0:
 | 
					        if len(fn) == 0:
 | 
				
			||||||
            raise ValidationError(_('Filename must not be empty'))
 | 
					            raise ValidationError(_('Filename must not be empty'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        attachment_dir = os.path.join(
 | 
					        attachment_dir = settings.MEDIA_ROOT.joinpath(self.getSubdir())
 | 
				
			||||||
            settings.MEDIA_ROOT,
 | 
					        old_file = settings.MEDIA_ROOT.joinpath(self.attachment.name)
 | 
				
			||||||
            self.getSubdir()
 | 
					        new_file = settings.MEDIA_ROOT.joinpath(self.getSubdir(), fn).resolve()
 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        old_file = os.path.join(
 | 
					 | 
				
			||||||
            settings.MEDIA_ROOT,
 | 
					 | 
				
			||||||
            self.attachment.name
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        new_file = os.path.join(
 | 
					 | 
				
			||||||
            settings.MEDIA_ROOT,
 | 
					 | 
				
			||||||
            self.getSubdir(),
 | 
					 | 
				
			||||||
            fn
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        new_file = os.path.abspath(new_file)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Check that there are no directory tricks going on...
 | 
					        # Check that there are no directory tricks going on...
 | 
				
			||||||
        if os.path.dirname(new_file) != attachment_dir:
 | 
					        if new_file.parent != attachment_dir:
 | 
				
			||||||
            logger.error(f"Attempted to rename attachment outside valid directory: '{new_file}'")
 | 
					            logger.error(f"Attempted to rename attachment outside valid directory: '{new_file}'")
 | 
				
			||||||
            raise ValidationError(_("Invalid attachment directory"))
 | 
					            raise ValidationError(_("Invalid attachment directory"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -473,11 +459,11 @@ class InvenTreeAttachment(models.Model):
 | 
				
			|||||||
        if len(fn.split('.')) < 2:
 | 
					        if len(fn.split('.')) < 2:
 | 
				
			||||||
            raise ValidationError(_("Filename missing extension"))
 | 
					            raise ValidationError(_("Filename missing extension"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not os.path.exists(old_file):
 | 
					        if not old_file.exists():
 | 
				
			||||||
            logger.error(f"Trying to rename attachment '{old_file}' which does not exist")
 | 
					            logger.error(f"Trying to rename attachment '{old_file}' which does not exist")
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if os.path.exists(new_file):
 | 
					        if new_file.exists():
 | 
				
			||||||
            raise ValidationError(_("Attachment with this filename already exists"))
 | 
					            raise ValidationError(_("Attachment with this filename already exists"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@ import random
 | 
				
			|||||||
import socket
 | 
					import socket
 | 
				
			||||||
import string
 | 
					import string
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import django.conf.locale
 | 
					import django.conf.locale
 | 
				
			||||||
from django.core.files.storage import default_storage
 | 
					from django.core.files.storage import default_storage
 | 
				
			||||||
@@ -44,7 +45,7 @@ TESTING_ENV = False
 | 
				
			|||||||
# New requirement for django 3.2+
 | 
					# New requirement for django 3.2+
 | 
				
			||||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
 | 
					DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 | 
					# Build paths inside the project like this: BASE_DIR.joinpath(...)
 | 
				
			||||||
BASE_DIR = get_base_dir()
 | 
					BASE_DIR = get_base_dir()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cfg_filename = get_config_file()
 | 
					cfg_filename = get_config_file()
 | 
				
			||||||
@@ -53,7 +54,7 @@ with open(cfg_filename, 'r') as cfg:
 | 
				
			|||||||
    CONFIG = yaml.safe_load(cfg)
 | 
					    CONFIG = yaml.safe_load(cfg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# We will place any config files in the same directory as the config file
 | 
					# We will place any config files in the same directory as the config file
 | 
				
			||||||
config_dir = os.path.dirname(cfg_filename)
 | 
					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!
 | 
				
			||||||
@@ -123,19 +124,17 @@ else:
 | 
				
			|||||||
    key_file = os.getenv("INVENTREE_SECRET_KEY_FILE")
 | 
					    key_file = os.getenv("INVENTREE_SECRET_KEY_FILE")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if key_file:
 | 
					    if key_file:
 | 
				
			||||||
        key_file = os.path.abspath(key_file)  # pragma: no cover
 | 
					        key_file = Path(key_file).resolve()  # pragma: no cover
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        # default secret key location
 | 
					        # default secret key location
 | 
				
			||||||
        key_file = os.path.join(BASE_DIR, "secret_key.txt")
 | 
					        key_file = BASE_DIR.joinpath("secret_key.txt").resolve()
 | 
				
			||||||
        key_file = os.path.abspath(key_file)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not os.path.exists(key_file):  # pragma: no cover
 | 
					    if not key_file.exists():  # pragma: no cover
 | 
				
			||||||
        logger.info(f"Generating random key file at '{key_file}'")
 | 
					        logger.info(f"Generating random key file at '{key_file}'")
 | 
				
			||||||
        # Create a random key file
 | 
					        # Create a random key file
 | 
				
			||||||
        with open(key_file, 'w') as f:
 | 
					 | 
				
			||||||
        options = string.digits + string.ascii_letters + string.punctuation
 | 
					        options = string.digits + string.ascii_letters + string.punctuation
 | 
				
			||||||
        key = ''.join([random.choice(options) for i in range(100)])
 | 
					        key = ''.join([random.choice(options) for i in range(100)])
 | 
				
			||||||
            f.write(key)
 | 
					        key_file.write_text(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logger.info(f"Loading SECRET_KEY from '{key_file}'")
 | 
					    logger.info(f"Loading SECRET_KEY from '{key_file}'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -146,28 +145,34 @@ else:
 | 
				
			|||||||
        sys.exit(-1)
 | 
					        sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# The filesystem location for served static files
 | 
					# The filesystem location for served static files
 | 
				
			||||||
STATIC_ROOT = os.path.abspath(
 | 
					STATIC_ROOT = Path(
 | 
				
			||||||
    get_setting(
 | 
					    get_setting(
 | 
				
			||||||
        'INVENTREE_STATIC_ROOT',
 | 
					        'INVENTREE_STATIC_ROOT',
 | 
				
			||||||
        CONFIG.get('static_root', None)
 | 
					        CONFIG.get('static_root', None)
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
)
 | 
					).resolve()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if STATIC_ROOT is None:  # pragma: no cover
 | 
					if STATIC_ROOT is None:  # pragma: no cover
 | 
				
			||||||
    print("ERROR: INVENTREE_STATIC_ROOT directory not defined")
 | 
					    print("ERROR: INVENTREE_STATIC_ROOT directory not defined")
 | 
				
			||||||
    sys.exit(1)
 | 
					    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
 | 
					# The filesystem location for served static files
 | 
				
			||||||
MEDIA_ROOT = os.path.abspath(
 | 
					MEDIA_ROOT = Path(
 | 
				
			||||||
    get_setting(
 | 
					    get_setting(
 | 
				
			||||||
        'INVENTREE_MEDIA_ROOT',
 | 
					        'INVENTREE_MEDIA_ROOT',
 | 
				
			||||||
        CONFIG.get('media_root', None)
 | 
					        CONFIG.get('media_root', None)
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
)
 | 
					).resolve()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if MEDIA_ROOT is None:  # pragma: no cover
 | 
					if MEDIA_ROOT is None:  # pragma: no cover
 | 
				
			||||||
    print("ERROR: INVENTREE_MEDIA_ROOT directory is not defined")
 | 
					    print("ERROR: INVENTREE_MEDIA_ROOT directory is not defined")
 | 
				
			||||||
    sys.exit(1)
 | 
					    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 = CONFIG.get('allowed_hosts', ['*'])
 | 
				
			||||||
@@ -193,17 +198,17 @@ STATICFILES_DIRS = []
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Translated Template settings
 | 
					# Translated Template settings
 | 
				
			||||||
STATICFILES_I18_PREFIX = 'i18n'
 | 
					STATICFILES_I18_PREFIX = 'i18n'
 | 
				
			||||||
STATICFILES_I18_SRC = os.path.join(BASE_DIR, 'templates', 'js', 'translated')
 | 
					STATICFILES_I18_SRC = BASE_DIR.joinpath('templates', 'js', 'translated')
 | 
				
			||||||
STATICFILES_I18_TRG = os.path.join(BASE_DIR, 'InvenTree', 'static_i18n')
 | 
					STATICFILES_I18_TRG = BASE_DIR.joinpath('InvenTree', 'static_i18n')
 | 
				
			||||||
STATICFILES_DIRS.append(STATICFILES_I18_TRG)
 | 
					STATICFILES_DIRS.append(STATICFILES_I18_TRG)
 | 
				
			||||||
STATICFILES_I18_TRG = os.path.join(STATICFILES_I18_TRG, STATICFILES_I18_PREFIX)
 | 
					STATICFILES_I18_TRG = STATICFILES_I18_TRG.joinpath(STATICFILES_I18_PREFIX)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
STATFILES_I18_PROCESSORS = [
 | 
					STATFILES_I18_PROCESSORS = [
 | 
				
			||||||
    'InvenTree.context.status_codes',
 | 
					    'InvenTree.context.status_codes',
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Color Themes Directory
 | 
					# Color Themes Directory
 | 
				
			||||||
STATIC_COLOR_THEMES_DIR = os.path.join(STATIC_ROOT, 'css', 'color-themes')
 | 
					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/'
 | 
				
			||||||
@@ -339,10 +344,10 @@ TEMPLATES = [
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
 | 
					        'BACKEND': 'django.template.backends.django.DjangoTemplates',
 | 
				
			||||||
        'DIRS': [
 | 
					        'DIRS': [
 | 
				
			||||||
            os.path.join(BASE_DIR, 'templates'),
 | 
					            BASE_DIR.joinpath('templates'),
 | 
				
			||||||
            # Allow templates in the reporting directory to be accessed
 | 
					            # Allow templates in the reporting directory to be accessed
 | 
				
			||||||
            os.path.join(MEDIA_ROOT, 'report'),
 | 
					            MEDIA_ROOT.joinpath('report'),
 | 
				
			||||||
            os.path.join(MEDIA_ROOT, 'label'),
 | 
					            MEDIA_ROOT.joinpath('label'),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        'OPTIONS': {
 | 
					        'OPTIONS': {
 | 
				
			||||||
            'context_processors': [
 | 
					            'context_processors': [
 | 
				
			||||||
@@ -809,7 +814,7 @@ EMAIL_USE_SSL = get_setting(
 | 
				
			|||||||
EMAIL_TIMEOUT = 60
 | 
					EMAIL_TIMEOUT = 60
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LOCALE_PATHS = (
 | 
					LOCALE_PATHS = (
 | 
				
			||||||
    os.path.join(BASE_DIR, 'locale/'),
 | 
					    BASE_DIR.joinpath('locale/'),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TIME_ZONE = get_setting(
 | 
					TIME_ZONE = get_setting(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -745,11 +745,11 @@ class TestSettings(helpers.InvenTreeTestCase):
 | 
				
			|||||||
            'inventree/data/config.yaml',
 | 
					            'inventree/data/config.yaml',
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertTrue(any([opt in config.get_config_file().lower() for opt in valid]))
 | 
					        self.assertTrue(any([opt in str(config.get_config_file()).lower() for opt in valid]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # with env set
 | 
					        # with env set
 | 
				
			||||||
        with self.in_env_context({'INVENTREE_CONFIG_FILE': 'my_special_conf.yaml'}):
 | 
					        with self.in_env_context({'INVENTREE_CONFIG_FILE': 'my_special_conf.yaml'}):
 | 
				
			||||||
            self.assertIn('inventree/my_special_conf.yaml', config.get_config_file().lower())
 | 
					            self.assertIn('inventree/my_special_conf.yaml', str(config.get_config_file()).lower())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_helpers_plugin_file(self):
 | 
					    def test_helpers_plugin_file(self):
 | 
				
			||||||
        """Test get_plugin_file."""
 | 
					        """Test get_plugin_file."""
 | 
				
			||||||
@@ -760,11 +760,11 @@ class TestSettings(helpers.InvenTreeTestCase):
 | 
				
			|||||||
            'inventree/data/plugins.txt',
 | 
					            'inventree/data/plugins.txt',
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertTrue(any([opt in config.get_plugin_file().lower() for opt in valid]))
 | 
					        self.assertTrue(any([opt in str(config.get_plugin_file()).lower() for opt in valid]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # with env set
 | 
					        # with env set
 | 
				
			||||||
        with self.in_env_context({'INVENTREE_PLUGIN_FILE': 'my_special_plugins.txt'}):
 | 
					        with self.in_env_context({'INVENTREE_PLUGIN_FILE': 'my_special_plugins.txt'}):
 | 
				
			||||||
            self.assertIn('my_special_plugins.txt', config.get_plugin_file())
 | 
					            self.assertIn('my_special_plugins.txt', str(config.get_plugin_file()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_helpers_setting(self):
 | 
					    def test_helpers_setting(self):
 | 
				
			||||||
        """Test get_setting."""
 | 
					        """Test get_setting."""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,6 @@ as JSON objects and passing them to modal forms (using jQuery / bootstrap).
 | 
				
			|||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.contrib.auth import password_validation
 | 
					from django.contrib.auth import password_validation
 | 
				
			||||||
@@ -638,7 +637,7 @@ class SettingsView(TemplateView):
 | 
				
			|||||||
            ctx["rates_updated"] = None
 | 
					            ctx["rates_updated"] = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # load locale stats
 | 
					        # load locale stats
 | 
				
			||||||
        STAT_FILE = os.path.abspath(os.path.join(settings.BASE_DIR, 'InvenTree/locale_stats.json'))
 | 
					        STAT_FILE = settings.BASE_DIR.joinpath('InvenTree/locale_stats.json').abolute()
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            ctx["locale_stats"] = json.load(open(STAT_FILE, 'r'))
 | 
					            ctx["locale_stats"] = json.load(open(STAT_FILE, 'r'))
 | 
				
			||||||
        except Exception:
 | 
					        except Exception:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,5 @@
 | 
				
			|||||||
"""Django views for interacting with common models."""
 | 
					"""Django views for interacting with common models."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.core.files.storage import FileSystemStorage
 | 
					from django.core.files.storage import FileSystemStorage
 | 
				
			||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
@@ -37,9 +35,9 @@ class MultiStepFormView(SessionWizardView):
 | 
				
			|||||||
    def process_media_folder(self):
 | 
					    def process_media_folder(self):
 | 
				
			||||||
        """Process media folder."""
 | 
					        """Process media folder."""
 | 
				
			||||||
        if self.media_folder:
 | 
					        if self.media_folder:
 | 
				
			||||||
            media_folder_abs = os.path.join(settings.MEDIA_ROOT, self.media_folder)
 | 
					            media_folder_abs = settings.MEDIA_ROOT.joinpath(self.media_folder)
 | 
				
			||||||
            if not os.path.exists(media_folder_abs):
 | 
					            if not media_folder_abs.exists():
 | 
				
			||||||
                os.mkdir(media_folder_abs)
 | 
					                media_folder_abs.mkdir(parents=True, exist_ok=True)
 | 
				
			||||||
            self.file_storage = FileSystemStorage(location=media_folder_abs)
 | 
					            self.file_storage = FileSystemStorage(location=media_folder_abs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_template_names(self):
 | 
					    def get_template_names(self):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ import logging
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
import warnings
 | 
					import warnings
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.apps import AppConfig
 | 
					from django.apps import AppConfig
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
@@ -40,40 +41,18 @@ class LabelConfig(AppConfig):
 | 
				
			|||||||
        """Create all default templates."""
 | 
					        """Create all default templates."""
 | 
				
			||||||
        # Test if models are ready
 | 
					        # Test if models are ready
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            from .models import StockLocationLabel
 | 
					            from .models import PartLabel, StockItemLabel, StockLocationLabel
 | 
				
			||||||
            assert bool(StockLocationLabel is not None)
 | 
					            assert bool(StockLocationLabel is not None)
 | 
				
			||||||
        except AppRegistryNotReady:  # pragma: no cover
 | 
					        except AppRegistryNotReady:  # pragma: no cover
 | 
				
			||||||
            # Database might not yet be ready
 | 
					            # Database might not yet be ready
 | 
				
			||||||
            warnings.warn('Database was not ready for creating labels')
 | 
					            warnings.warn('Database was not ready for creating labels')
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.create_stock_item_labels()
 | 
					        # Create the categories
 | 
				
			||||||
        self.create_stock_location_labels()
 | 
					        self.create_labels_category(
 | 
				
			||||||
        self.create_part_labels()
 | 
					            StockItemLabel,
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def create_stock_item_labels(self):
 | 
					 | 
				
			||||||
        """Create database entries for the default StockItemLabel templates, if they do not already exist."""
 | 
					 | 
				
			||||||
        from .models import StockItemLabel
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        src_dir = os.path.join(
 | 
					 | 
				
			||||||
            os.path.dirname(os.path.realpath(__file__)),
 | 
					 | 
				
			||||||
            'templates',
 | 
					 | 
				
			||||||
            'label',
 | 
					 | 
				
			||||||
            'stockitem',
 | 
					            'stockitem',
 | 
				
			||||||
        )
 | 
					            [
 | 
				
			||||||
 | 
					 | 
				
			||||||
        dst_dir = os.path.join(
 | 
					 | 
				
			||||||
            settings.MEDIA_ROOT,
 | 
					 | 
				
			||||||
            'label',
 | 
					 | 
				
			||||||
            'inventree',
 | 
					 | 
				
			||||||
            'stockitem',
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not os.path.exists(dst_dir):
 | 
					 | 
				
			||||||
            logger.info(f"Creating required directory: '{dst_dir}'")
 | 
					 | 
				
			||||||
            os.makedirs(dst_dir, exist_ok=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        labels = [
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    'file': 'qr.html',
 | 
					                    'file': 'qr.html',
 | 
				
			||||||
                    'name': 'QR Code',
 | 
					                    'name': 'QR Code',
 | 
				
			||||||
@@ -81,78 +60,12 @@ class LabelConfig(AppConfig):
 | 
				
			|||||||
                    'width': 24,
 | 
					                    'width': 24,
 | 
				
			||||||
                    'height': 24,
 | 
					                    'height': 24,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
        ]
 | 
					            ],
 | 
				
			||||||
 | 
					 | 
				
			||||||
        for label in labels:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            filename = os.path.join(
 | 
					 | 
				
			||||||
                'label',
 | 
					 | 
				
			||||||
                'inventree',
 | 
					 | 
				
			||||||
                'stockitem',
 | 
					 | 
				
			||||||
                label['file'],
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        self.create_labels_category(
 | 
				
			||||||
            # Check if the file exists in the media directory
 | 
					            StockLocationLabel,
 | 
				
			||||||
            src_file = os.path.join(src_dir, label['file'])
 | 
					 | 
				
			||||||
            dst_file = os.path.join(settings.MEDIA_ROOT, filename)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            to_copy = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if os.path.exists(dst_file):
 | 
					 | 
				
			||||||
                # File already exists - let's see if it is the "same",
 | 
					 | 
				
			||||||
                # or if we need to overwrite it with a newer copy!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if hashFile(dst_file) != hashFile(src_file):  # pragma: no cover
 | 
					 | 
				
			||||||
                    logger.info(f"Hash differs for '{filename}'")
 | 
					 | 
				
			||||||
                    to_copy = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                logger.info(f"Label template '{filename}' is not present")
 | 
					 | 
				
			||||||
                to_copy = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if to_copy:
 | 
					 | 
				
			||||||
                logger.info(f"Copying label template '{dst_file}'")
 | 
					 | 
				
			||||||
                shutil.copyfile(src_file, dst_file)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Check if a label matching the template already exists
 | 
					 | 
				
			||||||
            if StockItemLabel.objects.filter(label=filename).exists():
 | 
					 | 
				
			||||||
                continue  # pragma: no cover
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            logger.info(f"Creating entry for StockItemLabel '{label['name']}'")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            StockItemLabel.objects.create(
 | 
					 | 
				
			||||||
                name=label['name'],
 | 
					 | 
				
			||||||
                description=label['description'],
 | 
					 | 
				
			||||||
                label=filename,
 | 
					 | 
				
			||||||
                filters='',
 | 
					 | 
				
			||||||
                enabled=True,
 | 
					 | 
				
			||||||
                width=label['width'],
 | 
					 | 
				
			||||||
                height=label['height'],
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def create_stock_location_labels(self):
 | 
					 | 
				
			||||||
        """Create database entries for the default StockItemLocation templates, if they do not already exist."""
 | 
					 | 
				
			||||||
        from .models import StockLocationLabel
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        src_dir = os.path.join(
 | 
					 | 
				
			||||||
            os.path.dirname(os.path.realpath(__file__)),
 | 
					 | 
				
			||||||
            'templates',
 | 
					 | 
				
			||||||
            'label',
 | 
					 | 
				
			||||||
            'stocklocation',
 | 
					            'stocklocation',
 | 
				
			||||||
        )
 | 
					            [
 | 
				
			||||||
 | 
					 | 
				
			||||||
        dst_dir = os.path.join(
 | 
					 | 
				
			||||||
            settings.MEDIA_ROOT,
 | 
					 | 
				
			||||||
            'label',
 | 
					 | 
				
			||||||
            'inventree',
 | 
					 | 
				
			||||||
            'stocklocation',
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not os.path.exists(dst_dir):
 | 
					 | 
				
			||||||
            logger.info(f"Creating required directory: '{dst_dir}'")
 | 
					 | 
				
			||||||
            os.makedirs(dst_dir, exist_ok=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        labels = [
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    'file': 'qr.html',
 | 
					                    'file': 'qr.html',
 | 
				
			||||||
                    'name': 'QR Code',
 | 
					                    'name': 'QR Code',
 | 
				
			||||||
@@ -168,77 +81,11 @@ class LabelConfig(AppConfig):
 | 
				
			|||||||
                    'height': 24,
 | 
					                    'height': 24,
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
 | 
					 | 
				
			||||||
        for label in labels:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            filename = os.path.join(
 | 
					 | 
				
			||||||
                'label',
 | 
					 | 
				
			||||||
                'inventree',
 | 
					 | 
				
			||||||
                'stocklocation',
 | 
					 | 
				
			||||||
                label['file'],
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        self.create_labels_category(
 | 
				
			||||||
            # Check if the file exists in the media directory
 | 
					            PartLabel,
 | 
				
			||||||
            src_file = os.path.join(src_dir, label['file'])
 | 
					 | 
				
			||||||
            dst_file = os.path.join(settings.MEDIA_ROOT, filename)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            to_copy = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if os.path.exists(dst_file):
 | 
					 | 
				
			||||||
                # File already exists - let's see if it is the "same",
 | 
					 | 
				
			||||||
                # or if we need to overwrite it with a newer copy!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if hashFile(dst_file) != hashFile(src_file):  # pragma: no cover
 | 
					 | 
				
			||||||
                    logger.info(f"Hash differs for '{filename}'")
 | 
					 | 
				
			||||||
                    to_copy = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                logger.info(f"Label template '{filename}' is not present")
 | 
					 | 
				
			||||||
                to_copy = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if to_copy:
 | 
					 | 
				
			||||||
                logger.info(f"Copying label template '{dst_file}'")
 | 
					 | 
				
			||||||
                shutil.copyfile(src_file, dst_file)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Check if a label matching the template already exists
 | 
					 | 
				
			||||||
            if StockLocationLabel.objects.filter(label=filename).exists():
 | 
					 | 
				
			||||||
                continue  # pragma: no cover
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            logger.info(f"Creating entry for StockLocationLabel '{label['name']}'")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            StockLocationLabel.objects.create(
 | 
					 | 
				
			||||||
                name=label['name'],
 | 
					 | 
				
			||||||
                description=label['description'],
 | 
					 | 
				
			||||||
                label=filename,
 | 
					 | 
				
			||||||
                filters='',
 | 
					 | 
				
			||||||
                enabled=True,
 | 
					 | 
				
			||||||
                width=label['width'],
 | 
					 | 
				
			||||||
                height=label['height'],
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def create_part_labels(self):
 | 
					 | 
				
			||||||
        """Create database entries for the default PartLabel templates, if they do not already exist."""
 | 
					 | 
				
			||||||
        from .models import PartLabel
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        src_dir = os.path.join(
 | 
					 | 
				
			||||||
            os.path.dirname(os.path.realpath(__file__)),
 | 
					 | 
				
			||||||
            'templates',
 | 
					 | 
				
			||||||
            'label',
 | 
					 | 
				
			||||||
            'part',
 | 
					            'part',
 | 
				
			||||||
        )
 | 
					            [
 | 
				
			||||||
 | 
					 | 
				
			||||||
        dst_dir = os.path.join(
 | 
					 | 
				
			||||||
            settings.MEDIA_ROOT,
 | 
					 | 
				
			||||||
            'label',
 | 
					 | 
				
			||||||
            'inventree',
 | 
					 | 
				
			||||||
            'part',
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not os.path.exists(dst_dir):
 | 
					 | 
				
			||||||
            logger.info(f"Creating required directory: '{dst_dir}'")
 | 
					 | 
				
			||||||
            os.makedirs(dst_dir, exist_ok=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        labels = [
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    'file': 'part_label.html',
 | 
					                    'file': 'part_label.html',
 | 
				
			||||||
                    'name': 'Part Label',
 | 
					                    'name': 'Part Label',
 | 
				
			||||||
@@ -254,22 +101,46 @@ class LabelConfig(AppConfig):
 | 
				
			|||||||
                    'height': 24,
 | 
					                    'height': 24,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def create_labels_category(self, model, ref_name, labels):
 | 
				
			||||||
 | 
					        """Create folder and database entries for the default templates, if they do not already exist."""
 | 
				
			||||||
 | 
					        # Create root dir for templates
 | 
				
			||||||
 | 
					        src_dir = Path(__file__).parent.joinpath(
 | 
				
			||||||
 | 
					            'templates',
 | 
				
			||||||
 | 
					            'label',
 | 
				
			||||||
 | 
					            ref_name,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dst_dir = settings.MEDIA_ROOT.joinpath(
 | 
				
			||||||
 | 
					            'label',
 | 
				
			||||||
 | 
					            'inventree',
 | 
				
			||||||
 | 
					            ref_name,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not dst_dir.exists():
 | 
				
			||||||
 | 
					            logger.info(f"Creating required directory: '{dst_dir}'")
 | 
				
			||||||
 | 
					            dst_dir.mkdir(parents=True, exist_ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Create lables
 | 
				
			||||||
        for label in labels:
 | 
					        for label in labels:
 | 
				
			||||||
 | 
					            self.create_template_label(model, src_dir, ref_name, label)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def create_template_label(self, model, src_dir, ref_name, label):
 | 
				
			||||||
 | 
					        """Ensure a label template is in place."""
 | 
				
			||||||
        filename = os.path.join(
 | 
					        filename = os.path.join(
 | 
				
			||||||
            'label',
 | 
					            'label',
 | 
				
			||||||
            'inventree',
 | 
					            'inventree',
 | 
				
			||||||
                'part',
 | 
					            ref_name,
 | 
				
			||||||
            label['file']
 | 
					            label['file']
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            src_file = os.path.join(src_dir, label['file'])
 | 
					        src_file = src_dir.joinpath(label['file'])
 | 
				
			||||||
            dst_file = os.path.join(settings.MEDIA_ROOT, filename)
 | 
					        dst_file = settings.MEDIA_ROOT.joinpath(filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        to_copy = False
 | 
					        to_copy = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if os.path.exists(dst_file):
 | 
					        if dst_file.exists():
 | 
				
			||||||
            # File already exists - let's see if it is the "same"
 | 
					            # File already exists - let's see if it is the "same"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if hashFile(dst_file) != hashFile(src_file):  # pragma: no cover
 | 
					            if hashFile(dst_file) != hashFile(src_file):  # pragma: no cover
 | 
				
			||||||
@@ -282,15 +153,19 @@ class LabelConfig(AppConfig):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if to_copy:
 | 
					        if to_copy:
 | 
				
			||||||
            logger.info(f"Copying label template '{dst_file}'")
 | 
					            logger.info(f"Copying label template '{dst_file}'")
 | 
				
			||||||
 | 
					            # Ensure destionation dir exists
 | 
				
			||||||
 | 
					            dst_file.parent.mkdir(parents=True, exist_ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Copy file
 | 
				
			||||||
            shutil.copyfile(src_file, dst_file)
 | 
					            shutil.copyfile(src_file, dst_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Check if a label matching the template already exists
 | 
					        # Check if a label matching the template already exists
 | 
				
			||||||
            if PartLabel.objects.filter(label=filename).exists():
 | 
					        if model.objects.filter(label=filename).exists():
 | 
				
			||||||
                continue  # pragma: no cover
 | 
					            return  # pragma: no cover
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            logger.info(f"Creating entry for PartLabel '{label['name']}'")
 | 
					        logger.info(f"Creating entry for {model} '{label['name']}'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            PartLabel.objects.create(
 | 
					        model.objects.create(
 | 
				
			||||||
            name=label['name'],
 | 
					            name=label['name'],
 | 
				
			||||||
            description=label['description'],
 | 
					            description=label['description'],
 | 
				
			||||||
            label=filename,
 | 
					            label=filename,
 | 
				
			||||||
@@ -299,3 +174,4 @@ class LabelConfig(AppConfig):
 | 
				
			|||||||
            width=label['width'],
 | 
					            width=label['width'],
 | 
				
			||||||
            height=label['height'],
 | 
					            height=label['height'],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -155,7 +155,7 @@ class LabelTemplate(models.Model):
 | 
				
			|||||||
        template = template.replace('/', os.path.sep)
 | 
					        template = template.replace('/', os.path.sep)
 | 
				
			||||||
        template = template.replace('\\', os.path.sep)
 | 
					        template = template.replace('\\', os.path.sep)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        template = os.path.join(settings.MEDIA_ROOT, template)
 | 
					        template = settings.MEDIA_ROOT.joinpath(template)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return template
 | 
					        return template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,5 @@
 | 
				
			|||||||
"""Tests for labels"""
 | 
					"""Tests for labels"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.apps import apps
 | 
					from django.apps import apps
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.core.exceptions import ValidationError
 | 
					from django.core.exceptions import ValidationError
 | 
				
			||||||
@@ -42,27 +40,17 @@ class LabelTest(InvenTreeAPITestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def test_default_files(self):
 | 
					    def test_default_files(self):
 | 
				
			||||||
        """Test that label files exist in the MEDIA directory."""
 | 
					        """Test that label files exist in the MEDIA directory."""
 | 
				
			||||||
        item_dir = os.path.join(
 | 
					        def test_subdir(ref_name):
 | 
				
			||||||
            settings.MEDIA_ROOT,
 | 
					            item_dir = settings.MEDIA_ROOT.joinpath(
 | 
				
			||||||
                'label',
 | 
					                'label',
 | 
				
			||||||
                'inventree',
 | 
					                'inventree',
 | 
				
			||||||
            'stockitem',
 | 
					                ref_name,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            self.assertTrue(len([item_dir.iterdir()]) > 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        files = os.listdir(item_dir)
 | 
					        test_subdir('stockitem')
 | 
				
			||||||
 | 
					        test_subdir('stocklocation')
 | 
				
			||||||
        self.assertTrue(len(files) > 0)
 | 
					        test_subdir('part')
 | 
				
			||||||
 | 
					 | 
				
			||||||
        loc_dir = os.path.join(
 | 
					 | 
				
			||||||
            settings.MEDIA_ROOT,
 | 
					 | 
				
			||||||
            'label',
 | 
					 | 
				
			||||||
            'inventree',
 | 
					 | 
				
			||||||
            'stocklocation',
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        files = os.listdir(loc_dir)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.assertTrue(len(files) > 0)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_filters(self):
 | 
					    def test_filters(self):
 | 
				
			||||||
        """Test the label filters."""
 | 
					        """Test the label filters."""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -510,7 +510,7 @@ class PartImageSelect(AjaxUpdateView):
 | 
				
			|||||||
        data = {}
 | 
					        data = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if img:
 | 
					        if img:
 | 
				
			||||||
            img_path = os.path.join(settings.MEDIA_ROOT, 'part_images', img)
 | 
					            img_path = settings.MEDIA_ROOT.joinpath('part_images', img)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Ensure that the image already exists
 | 
					            # Ensure that the image already exists
 | 
				
			||||||
            if os.path.exists(img_path):
 | 
					            if os.path.exists(img_path):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import inspect
 | 
					import inspect
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import pathlib
 | 
					import pathlib
 | 
				
			||||||
import pkgutil
 | 
					import pkgutil
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
@@ -103,10 +102,10 @@ def get_git_log(path):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    output = None
 | 
					    output = None
 | 
				
			||||||
    if registry.git_is_modern:
 | 
					    if registry.git_is_modern:
 | 
				
			||||||
        path = path.replace(os.path.dirname(settings.BASE_DIR), '')[1:]
 | 
					        path = path.replace(str(settings.BASE_DIR.parent), '')[1:]
 | 
				
			||||||
        command = ['git', 'log', '-n', '1', "--pretty=format:'%H%n%aN%n%aE%n%aI%n%f%n%G?%n%GK'", '--follow', '--', path]
 | 
					        command = ['git', 'log', '-n', '1', "--pretty=format:'%H%n%aN%n%aE%n%aI%n%f%n%G?%n%GK'", '--follow', '--', path]
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            output = str(subprocess.check_output(command, cwd=os.path.dirname(settings.BASE_DIR)), 'utf-8')[1:-1]
 | 
					            output = str(subprocess.check_output(command, cwd=settings.BASE_DIR.parent), 'utf-8')[1:-1]
 | 
				
			||||||
            if output:
 | 
					            if output:
 | 
				
			||||||
                output = output.split('\n')
 | 
					                output = output.split('\n')
 | 
				
			||||||
        except subprocess.CalledProcessError:  # pragma: no cover
 | 
					        except subprocess.CalledProcessError:  # pragma: no cover
 | 
				
			||||||
@@ -125,7 +124,7 @@ def check_git_version():
 | 
				
			|||||||
    """Returns if the current git version supports modern features."""
 | 
					    """Returns if the current git version supports modern features."""
 | 
				
			||||||
    # get version string
 | 
					    # get version string
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        output = str(subprocess.check_output(['git', '--version'], cwd=os.path.dirname(settings.BASE_DIR)), 'utf-8')
 | 
					        output = str(subprocess.check_output(['git', '--version'], cwd=settings.BASE_DIR.parent), 'utf-8')
 | 
				
			||||||
    except subprocess.CalledProcessError:  # pragma: no cover
 | 
					    except subprocess.CalledProcessError:  # pragma: no cover
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
    except FileNotFoundError:  # pragma: no cover
 | 
					    except FileNotFoundError:  # pragma: no cover
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,9 +7,9 @@
 | 
				
			|||||||
import importlib
 | 
					import importlib
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import pathlib
 | 
					 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
from importlib import metadata, reload
 | 
					from importlib import metadata, reload
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
from typing import OrderedDict
 | 
					from typing import OrderedDict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.apps import apps
 | 
					from django.apps import apps
 | 
				
			||||||
@@ -207,7 +207,7 @@ class PluginsRegistry:
 | 
				
			|||||||
        if custom_dirs is not None:
 | 
					        if custom_dirs is not None:
 | 
				
			||||||
            # Allow multiple plugin directories to be specified
 | 
					            # Allow multiple plugin directories to be specified
 | 
				
			||||||
            for pd_text in custom_dirs.split(','):
 | 
					            for pd_text in custom_dirs.split(','):
 | 
				
			||||||
                pd = pathlib.Path(pd_text.strip()).absolute()
 | 
					                pd = Path(pd_text.strip()).absolute()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # Attempt to create the directory if it does not already exist
 | 
					                # Attempt to create the directory if it does not already exist
 | 
				
			||||||
                if not pd.exists():
 | 
					                if not pd.exists():
 | 
				
			||||||
@@ -248,7 +248,7 @@ class PluginsRegistry:
 | 
				
			|||||||
            logger.info(f"Loading plugins from directory '{plugin}'")
 | 
					            logger.info(f"Loading plugins from directory '{plugin}'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            parent_path = None
 | 
					            parent_path = None
 | 
				
			||||||
            parent_obj = pathlib.Path(plugin)
 | 
					            parent_obj = Path(plugin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # If a "path" is provided, some special handling is required
 | 
					            # If a "path" is provided, some special handling is required
 | 
				
			||||||
            if parent_obj.name is not plugin and len(parent_obj.parts) > 1:
 | 
					            if parent_obj.name is not plugin and len(parent_obj.parts) > 1:
 | 
				
			||||||
@@ -283,7 +283,7 @@ class PluginsRegistry:
 | 
				
			|||||||
            return True
 | 
					            return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            output = str(subprocess.check_output(['pip', 'install', '-U', '-r', settings.PLUGIN_FILE], cwd=os.path.dirname(settings.BASE_DIR)), 'utf-8')
 | 
					            output = str(subprocess.check_output(['pip', 'install', '-U', '-r', settings.PLUGIN_FILE], cwd=settings.BASE_DIR.parent), 'utf-8')
 | 
				
			||||||
        except subprocess.CalledProcessError as error:  # pragma: no cover
 | 
					        except subprocess.CalledProcessError as error:  # pragma: no cover
 | 
				
			||||||
            logger.error(f'Ran into error while trying to install plugins!\n{str(error)}')
 | 
					            logger.error(f'Ran into error while trying to install plugins!\n{str(error)}')
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
@@ -565,7 +565,7 @@ class PluginsRegistry:
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            # for local path plugins
 | 
					            # for local path plugins
 | 
				
			||||||
            plugin_path = '.'.join(pathlib.Path(plugin.path).relative_to(settings.BASE_DIR).parts)
 | 
					            plugin_path = '.'.join(Path(plugin.path).relative_to(settings.BASE_DIR).parts)
 | 
				
			||||||
        except ValueError:  # pragma: no cover
 | 
					        except ValueError:  # pragma: no cover
 | 
				
			||||||
            # plugin is shipped as package
 | 
					            # plugin is shipped as package
 | 
				
			||||||
            plugin_path = plugin.NAME
 | 
					            plugin_path = plugin.NAME
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
"""JSON serializers for plugin app."""
 | 
					"""JSON serializers for plugin app."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
@@ -144,7 +143,7 @@ class PluginConfigInstallSerializer(serializers.Serializer):
 | 
				
			|||||||
        success = False
 | 
					        success = False
 | 
				
			||||||
        # execute pypi
 | 
					        # execute pypi
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            result = subprocess.check_output(command, cwd=os.path.dirname(settings.BASE_DIR))
 | 
					            result = subprocess.check_output(command, cwd=settings.BASE_DIR.parent)
 | 
				
			||||||
            ret['result'] = str(result, 'utf-8')
 | 
					            ret['result'] = str(result, 'utf-8')
 | 
				
			||||||
            ret['success'] = True
 | 
					            ret['success'] = True
 | 
				
			||||||
            success = True
 | 
					            success = True
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@
 | 
				
			|||||||
import logging
 | 
					import logging
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.apps import AppConfig
 | 
					from django.apps import AppConfig
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
@@ -25,23 +26,21 @@ class ReportConfig(AppConfig):
 | 
				
			|||||||
    def create_default_reports(self, model, reports):
 | 
					    def create_default_reports(self, model, reports):
 | 
				
			||||||
        """Copy defualt report files across to the media directory."""
 | 
					        """Copy defualt report files across to the media directory."""
 | 
				
			||||||
        # Source directory for report templates
 | 
					        # Source directory for report templates
 | 
				
			||||||
        src_dir = os.path.join(
 | 
					        src_dir = Path(__file__).parent.joinpath(
 | 
				
			||||||
            os.path.dirname(os.path.realpath(__file__)),
 | 
					 | 
				
			||||||
            'templates',
 | 
					            'templates',
 | 
				
			||||||
            'report',
 | 
					            'report',
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Destination directory
 | 
					        # Destination directory
 | 
				
			||||||
        dst_dir = os.path.join(
 | 
					        dst_dir = settings.MEDIA_ROOT.joinpath(
 | 
				
			||||||
            settings.MEDIA_ROOT,
 | 
					 | 
				
			||||||
            'report',
 | 
					            'report',
 | 
				
			||||||
            'inventree',
 | 
					            'inventree',
 | 
				
			||||||
            model.getSubdir(),
 | 
					            model.getSubdir(),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not os.path.exists(dst_dir):
 | 
					        if not dst_dir.exists():
 | 
				
			||||||
            logger.info(f"Creating missing directory: '{dst_dir}'")
 | 
					            logger.info(f"Creating missing directory: '{dst_dir}'")
 | 
				
			||||||
            os.makedirs(dst_dir, exist_ok=True)
 | 
					            dst_dir.mkdir(parents=True, exist_ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Copy each report template across (if required)
 | 
					        # Copy each report template across (if required)
 | 
				
			||||||
        for report in reports:
 | 
					        for report in reports:
 | 
				
			||||||
@@ -54,10 +53,10 @@ class ReportConfig(AppConfig):
 | 
				
			|||||||
                report['file'],
 | 
					                report['file'],
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            src_file = os.path.join(src_dir, report['file'])
 | 
					            src_file = src_dir.joinpath(report['file'])
 | 
				
			||||||
            dst_file = os.path.join(settings.MEDIA_ROOT, filename)
 | 
					            dst_file = settings.MEDIA_ROOT.joinpath(filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if not os.path.exists(dst_file):
 | 
					            if not dst_file.exists():
 | 
				
			||||||
                logger.info(f"Copying test report template '{dst_file}'")
 | 
					                logger.info(f"Copying test report template '{dst_file}'")
 | 
				
			||||||
                shutil.copyfile(src_file, dst_file)
 | 
					                shutil.copyfile(src_file, dst_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -111,14 +111,13 @@ class ReportBase(models.Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        path = os.path.join('report', 'report_template', self.getSubdir(), filename)
 | 
					        path = os.path.join('report', 'report_template', self.getSubdir(), filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fullpath = os.path.join(settings.MEDIA_ROOT, path)
 | 
					        fullpath = settings.MEDIA_ROOT.joinpath(path).resolve()
 | 
				
			||||||
        fullpath = os.path.abspath(fullpath)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # If the report file is the *same* filename as the one being uploaded,
 | 
					        # If the report file is the *same* filename as the one being uploaded,
 | 
				
			||||||
        # remove the original one from the media directory
 | 
					        # remove the original one from the media directory
 | 
				
			||||||
        if str(filename) == str(self.template):
 | 
					        if str(filename) == str(self.template):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if os.path.exists(fullpath):
 | 
					            if fullpath.exists():
 | 
				
			||||||
                logger.info(f"Deleting existing report template: '{filename}'")
 | 
					                logger.info(f"Deleting existing report template: '{filename}'")
 | 
				
			||||||
                os.remove(fullpath)
 | 
					                os.remove(fullpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -139,10 +138,12 @@ class ReportBase(models.Model):
 | 
				
			|||||||
        Required for passing the file to an external process
 | 
					        Required for passing the file to an external process
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        template = self.template.name
 | 
					        template = self.template.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # TODO @matmair change to using new file objects
 | 
				
			||||||
        template = template.replace('/', os.path.sep)
 | 
					        template = template.replace('/', os.path.sep)
 | 
				
			||||||
        template = template.replace('\\', os.path.sep)
 | 
					        template = template.replace('\\', os.path.sep)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        template = os.path.join(settings.MEDIA_ROOT, template)
 | 
					        template = settings.MEDIA_ROOT.joinpath(template)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return template
 | 
					        return template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -474,14 +475,13 @@ def rename_snippet(instance, filename):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    path = os.path.join('report', 'snippets', filename)
 | 
					    path = os.path.join('report', 'snippets', filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fullpath = os.path.join(settings.MEDIA_ROOT, path)
 | 
					    fullpath = settings.MEDIA_ROOT.joinpath(path).resolve()
 | 
				
			||||||
    fullpath = os.path.abspath(fullpath)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # If the snippet file is the *same* filename as the one being uploaded,
 | 
					    # If the snippet file is the *same* filename as the one being uploaded,
 | 
				
			||||||
    # delete the original one from the media directory
 | 
					    # delete the original one from the media directory
 | 
				
			||||||
    if str(filename) == str(instance.snippet):
 | 
					    if str(filename) == str(instance.snippet):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if os.path.exists(fullpath):
 | 
					        if fullpath.exists():
 | 
				
			||||||
            logger.info(f"Deleting existing snippet file: '{filename}'")
 | 
					            logger.info(f"Deleting existing snippet file: '{filename}'")
 | 
				
			||||||
            os.remove(fullpath)
 | 
					            os.remove(fullpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -517,10 +517,9 @@ def rename_asset(instance, filename):
 | 
				
			|||||||
    # If the asset file is the *same* filename as the one being uploaded,
 | 
					    # If the asset file is the *same* filename as the one being uploaded,
 | 
				
			||||||
    # delete the original one from the media directory
 | 
					    # delete the original one from the media directory
 | 
				
			||||||
    if str(filename) == str(instance.asset):
 | 
					    if str(filename) == str(instance.asset):
 | 
				
			||||||
        fullpath = os.path.join(settings.MEDIA_ROOT, path)
 | 
					        fullpath = settings.MEDIA_ROOT.joinpath(path).resolve()
 | 
				
			||||||
        fullpath = os.path.abspath(fullpath)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if os.path.exists(fullpath):
 | 
					        if fullpath.exists():
 | 
				
			||||||
            logger.info(f"Deleting existing asset file: '{filename}'")
 | 
					            logger.info(f"Deleting existing asset file: '{filename}'")
 | 
				
			||||||
            os.remove(fullpath)
 | 
					            os.remove(fullpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,9 +32,9 @@ def asset(filename):
 | 
				
			|||||||
    debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
 | 
					    debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Test if the file actually exists
 | 
					    # Test if the file actually exists
 | 
				
			||||||
    full_path = os.path.join(settings.MEDIA_ROOT, 'report', 'assets', filename)
 | 
					    full_path = settings.MEDIA_ROOT.joinpath('report', 'assets', filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not os.path.exists(full_path) or not os.path.isfile(full_path):
 | 
					    if not full_path.exists() or not full_path.is_file():
 | 
				
			||||||
        raise FileNotFoundError(f"Asset file '{filename}' does not exist")
 | 
					        raise FileNotFoundError(f"Asset file '{filename}' does not exist")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if debug_mode:
 | 
					    if debug_mode:
 | 
				
			||||||
@@ -63,9 +63,8 @@ def uploaded_image(filename, replace_missing=True, replacement_file='blank_image
 | 
				
			|||||||
        exists = False
 | 
					        exists = False
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            full_path = os.path.join(settings.MEDIA_ROOT, filename)
 | 
					            full_path = settings.MEDIA_ROOT.joinpath(filename).resolve()
 | 
				
			||||||
            full_path = os.path.abspath(full_path)
 | 
					            exists = full_path.exists() and full_path.is_file()
 | 
				
			||||||
            exists = os.path.exists(full_path) and os.path.isfile(full_path)
 | 
					 | 
				
			||||||
        except Exception:
 | 
					        except Exception:
 | 
				
			||||||
            exists = False
 | 
					            exists = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -85,11 +84,9 @@ def uploaded_image(filename, replace_missing=True, replacement_file='blank_image
 | 
				
			|||||||
    else:
 | 
					    else:
 | 
				
			||||||
        # Return file path
 | 
					        # Return file path
 | 
				
			||||||
        if exists:
 | 
					        if exists:
 | 
				
			||||||
            path = os.path.join(settings.MEDIA_ROOT, filename)
 | 
					            path = settings.MEDIA_ROOT.joinpath(filename).resolve()
 | 
				
			||||||
            path = os.path.abspath(path)
 | 
					 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            path = os.path.join(settings.STATIC_ROOT, 'img', replacement_file)
 | 
					            path = settings.STATIC_ROOT.joinpath('img', replacement_file).resolve()
 | 
				
			||||||
            path = os.path.abspath(path)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return f"file://{path}"
 | 
					        return f"file://{path}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.core.cache import cache
 | 
					from django.core.cache import cache
 | 
				
			||||||
@@ -38,12 +39,11 @@ class ReportTagTest(TestCase):
 | 
				
			|||||||
                report_tags.asset("bad_file.txt")
 | 
					                report_tags.asset("bad_file.txt")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Create an asset file
 | 
					        # Create an asset file
 | 
				
			||||||
        asset_dir = os.path.join(settings.MEDIA_ROOT, 'report', 'assets')
 | 
					        asset_dir = settings.MEDIA_ROOT.joinpath('report', 'assets')
 | 
				
			||||||
        os.makedirs(asset_dir, exist_ok=True)
 | 
					        asset_dir.mkdir(parents=True, exist_ok=True)
 | 
				
			||||||
        asset_path = os.path.join(asset_dir, 'test.txt')
 | 
					        asset_path = asset_dir.joinpath('test.txt')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with open(asset_path, 'w') as f:
 | 
					        asset_path.write_text("dummy data")
 | 
				
			||||||
            f.write("dummy data")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.debug_mode(True)
 | 
					        self.debug_mode(True)
 | 
				
			||||||
        asset = report_tags.asset('test.txt')
 | 
					        asset = report_tags.asset('test.txt')
 | 
				
			||||||
@@ -68,13 +68,11 @@ class ReportTagTest(TestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        # Create a dummy image
 | 
					        # Create a dummy image
 | 
				
			||||||
        img_path = 'part/images/'
 | 
					        img_path = 'part/images/'
 | 
				
			||||||
        img_path = os.path.join(settings.MEDIA_ROOT, img_path)
 | 
					        img_path = settings.MEDIA_ROOT.joinpath(img_path)
 | 
				
			||||||
        img_file = os.path.join(img_path, 'test.jpg')
 | 
					        img_file = img_path.joinpath('test.jpg')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        os.makedirs(img_path, exist_ok=True)
 | 
					        img_path.mkdir(parents=True, exist_ok=True)
 | 
				
			||||||
 | 
					        img_file.write_text("dummy data")
 | 
				
			||||||
        with open(img_file, 'w') as f:
 | 
					 | 
				
			||||||
            f.write("dummy data")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test in debug mode. Returns blank image as dummy file is not a valid image
 | 
					        # Test in debug mode. Returns blank image as dummy file is not a valid image
 | 
				
			||||||
        self.debug_mode(True)
 | 
					        self.debug_mode(True)
 | 
				
			||||||
@@ -91,7 +89,7 @@ class ReportTagTest(TestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.debug_mode(False)
 | 
					        self.debug_mode(False)
 | 
				
			||||||
        img = report_tags.uploaded_image('part/images/test.jpg')
 | 
					        img = report_tags.uploaded_image('part/images/test.jpg')
 | 
				
			||||||
        self.assertEqual(img, f'file://{img_path}test.jpg')
 | 
					        self.assertEqual(img, f'file://{img_path.joinpath("test.jpg")}')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_part_image(self):
 | 
					    def test_part_image(self):
 | 
				
			||||||
        """Unit tests for the 'part_image' tag"""
 | 
					        """Unit tests for the 'part_image' tag"""
 | 
				
			||||||
@@ -178,8 +176,7 @@ class ReportTest(InvenTreeAPITestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def copyReportTemplate(self, filename, description):
 | 
					    def copyReportTemplate(self, filename, description):
 | 
				
			||||||
        """Copy the provided report template into the required media directory."""
 | 
					        """Copy the provided report template into the required media directory."""
 | 
				
			||||||
        src_dir = os.path.join(
 | 
					        src_dir = Path(__file__).parent.joinpath(
 | 
				
			||||||
            os.path.dirname(os.path.realpath(__file__)),
 | 
					 | 
				
			||||||
            'templates',
 | 
					            'templates',
 | 
				
			||||||
            'report'
 | 
					            'report'
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@@ -190,18 +187,15 @@ class ReportTest(InvenTreeAPITestCase):
 | 
				
			|||||||
            self.model.getSubdir(),
 | 
					            self.model.getSubdir(),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dst_dir = os.path.join(
 | 
					        dst_dir = settings.MEDIA_ROOT.joinpath(template_dir)
 | 
				
			||||||
            settings.MEDIA_ROOT,
 | 
					 | 
				
			||||||
            template_dir
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not os.path.exists(dst_dir):  # pragma: no cover
 | 
					        if not dst_dir.exists():  # pragma: no cover
 | 
				
			||||||
            os.makedirs(dst_dir, exist_ok=True)
 | 
					            dst_dir.mkdir(parents=True, exist_ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        src_file = os.path.join(src_dir, filename)
 | 
					        src_file = src_dir.joinpath(filename)
 | 
				
			||||||
        dst_file = os.path.join(dst_dir, filename)
 | 
					        dst_file = dst_dir.joinpath(filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not os.path.exists(dst_file):  # pragma: no cover
 | 
					        if not dst_file.exists():  # pragma: no cover
 | 
				
			||||||
            shutil.copyfile(src_file, dst_file)
 | 
					            shutil.copyfile(src_file, dst_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Convert to an "internal" filename
 | 
					        # Convert to an "internal" filename
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										26
									
								
								tasks.py
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								tasks.py
									
									
									
									
									
								
							@@ -5,6 +5,7 @@ import os
 | 
				
			|||||||
import pathlib
 | 
					import pathlib
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from invoke import task
 | 
					from invoke import task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -52,23 +53,23 @@ def content_excludes():
 | 
				
			|||||||
    return output
 | 
					    return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def localDir():
 | 
					def localDir() -> Path:
 | 
				
			||||||
    """Returns the directory of *THIS* file.
 | 
					    """Returns the directory of *THIS* file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Used to ensure that the various scripts always run
 | 
					    Used to ensure that the various scripts always run
 | 
				
			||||||
    in the correct directory.
 | 
					    in the correct directory.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    return os.path.dirname(os.path.abspath(__file__))
 | 
					    return Path(__file__).parent.resolve()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def managePyDir():
 | 
					def managePyDir():
 | 
				
			||||||
    """Returns the directory of the manage.py file."""
 | 
					    """Returns the directory of the manage.py file."""
 | 
				
			||||||
    return os.path.join(localDir(), 'InvenTree')
 | 
					    return localDir().joinpath('InvenTree')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def managePyPath():
 | 
					def managePyPath():
 | 
				
			||||||
    """Return the path of the manage.py file."""
 | 
					    """Return the path of the manage.py file."""
 | 
				
			||||||
    return os.path.join(managePyDir(), 'manage.py')
 | 
					    return managePyDir().joinpath('manage.py')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def manage(c, cmd, pty: bool = False):
 | 
					def manage(c, cmd, pty: bool = False):
 | 
				
			||||||
@@ -171,7 +172,7 @@ def translate_stats(c):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    The file generated from this is needed for the UI.
 | 
					    The file generated from this is needed for the UI.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    path = os.path.join('InvenTree', 'script', 'translation_stats.py')
 | 
					    path = Path('InvenTree', 'script', 'translation_stats.py')
 | 
				
			||||||
    c.run(f'python3 {path}')
 | 
					    c.run(f'python3 {path}')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -252,12 +253,11 @@ def export_records(c, filename='data.json', overwrite=False, include_permissions
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    # Get an absolute path to the file
 | 
					    # Get an absolute path to the file
 | 
				
			||||||
    if not os.path.isabs(filename):
 | 
					    if not os.path.isabs(filename):
 | 
				
			||||||
        filename = os.path.join(localDir(), filename)
 | 
					        filename = localDir().joinpath(filename).resolve()
 | 
				
			||||||
        filename = os.path.abspath(filename)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    print(f"Exporting database records to file '{filename}'")
 | 
					    print(f"Exporting database records to file '{filename}'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if os.path.exists(filename) and overwrite is False:
 | 
					    if filename.exists() and overwrite is False:
 | 
				
			||||||
        response = input("Warning: file already exists. Do you want to overwrite? [y/N]: ")
 | 
					        response = input("Warning: file already exists. Do you want to overwrite? [y/N]: ")
 | 
				
			||||||
        response = str(response).strip().lower()
 | 
					        response = str(response).strip().lower()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -306,7 +306,7 @@ def import_records(c, filename='data.json', clear=False):
 | 
				
			|||||||
    """Import database records from a file."""
 | 
					    """Import database records from a file."""
 | 
				
			||||||
    # Get an absolute path to the supplied filename
 | 
					    # Get an absolute path to the supplied filename
 | 
				
			||||||
    if not os.path.isabs(filename):
 | 
					    if not os.path.isabs(filename):
 | 
				
			||||||
        filename = os.path.join(localDir(), filename)
 | 
					        filename = localDir().joinpath(filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not os.path.exists(filename):
 | 
					    if not os.path.exists(filename):
 | 
				
			||||||
        print(f"Error: File '{filename}' does not exist")
 | 
					        print(f"Error: File '{filename}' does not exist")
 | 
				
			||||||
@@ -442,8 +442,8 @@ def test_translations(c):
 | 
				
			|||||||
    from django.conf import settings
 | 
					    from django.conf import settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # setup django
 | 
					    # setup django
 | 
				
			||||||
    base_path = os.getcwd()
 | 
					    base_path = Path.cwd()
 | 
				
			||||||
    new_base_path = pathlib.Path('InvenTree').absolute()
 | 
					    new_base_path = pathlib.Path('InvenTree').resolve()
 | 
				
			||||||
    sys.path.append(str(new_base_path))
 | 
					    sys.path.append(str(new_base_path))
 | 
				
			||||||
    os.chdir(new_base_path)
 | 
					    os.chdir(new_base_path)
 | 
				
			||||||
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'InvenTree.settings')
 | 
					    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'InvenTree.settings')
 | 
				
			||||||
@@ -487,8 +487,8 @@ def test_translations(c):
 | 
				
			|||||||
                    file_new.write(line)
 | 
					                    file_new.write(line)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # change out translation files
 | 
					    # change out translation files
 | 
				
			||||||
    os.rename(file_path, str(file_path) + '_old')
 | 
					    file_path.rename(str(file_path) + '_old')
 | 
				
			||||||
    os.rename(new_file_path, file_path)
 | 
					    new_file_path.rename(file_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # compile languages
 | 
					    # compile languages
 | 
				
			||||||
    print("Compile languages ...")
 | 
					    print("Compile languages ...")
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user