mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +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:
parent
551f66ff90
commit
794e375009
@ -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)])
|
key_file.write_text(key)
|
||||||
f.write(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,262 +41,137 @@ 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',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'file': 'qr.html',
|
||||||
|
'name': 'QR Code',
|
||||||
|
'description': 'Simple QR code label',
|
||||||
|
'width': 24,
|
||||||
|
'height': 24,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.create_labels_category(
|
||||||
|
StockLocationLabel,
|
||||||
|
'stocklocation',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'file': 'qr.html',
|
||||||
|
'name': 'QR Code',
|
||||||
|
'description': 'Simple QR code label',
|
||||||
|
'width': 24,
|
||||||
|
'height': 24,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'file': 'qr_and_text.html',
|
||||||
|
'name': 'QR and text',
|
||||||
|
'description': 'Label with QR code and name of location',
|
||||||
|
'width': 50,
|
||||||
|
'height': 24,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.create_labels_category(
|
||||||
|
PartLabel,
|
||||||
|
'part',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'file': 'part_label.html',
|
||||||
|
'name': 'Part Label',
|
||||||
|
'description': 'Simple part label',
|
||||||
|
'width': 70,
|
||||||
|
'height': 24,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'file': 'part_label_code128.html',
|
||||||
|
'name': 'Barcode Part Label',
|
||||||
|
'description': 'Simple part label with Code128 barcode',
|
||||||
|
'width': 70,
|
||||||
|
'height': 24,
|
||||||
|
},
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
dst_dir = os.path.join(
|
def create_labels_category(self, model, ref_name, labels):
|
||||||
settings.MEDIA_ROOT,
|
"""Create folder and database entries for the default templates, if they do not already exist."""
|
||||||
'label',
|
# Create root dir for templates
|
||||||
'inventree',
|
src_dir = Path(__file__).parent.joinpath(
|
||||||
'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',
|
|
||||||
'name': 'QR Code',
|
|
||||||
'description': 'Simple QR code label',
|
|
||||||
'width': 24,
|
|
||||||
'height': 24,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
for label in labels:
|
|
||||||
|
|
||||||
filename = os.path.join(
|
|
||||||
'label',
|
|
||||||
'inventree',
|
|
||||||
'stockitem',
|
|
||||||
label['file'],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if the file exists in the media directory
|
|
||||||
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',
|
'templates',
|
||||||
'label',
|
'label',
|
||||||
'stocklocation',
|
ref_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
dst_dir = os.path.join(
|
dst_dir = settings.MEDIA_ROOT.joinpath(
|
||||||
settings.MEDIA_ROOT,
|
|
||||||
'label',
|
'label',
|
||||||
'inventree',
|
'inventree',
|
||||||
'stocklocation',
|
ref_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not os.path.exists(dst_dir):
|
if not dst_dir.exists():
|
||||||
logger.info(f"Creating required directory: '{dst_dir}'")
|
logger.info(f"Creating required directory: '{dst_dir}'")
|
||||||
os.makedirs(dst_dir, exist_ok=True)
|
dst_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
labels = [
|
|
||||||
{
|
|
||||||
'file': 'qr.html',
|
|
||||||
'name': 'QR Code',
|
|
||||||
'description': 'Simple QR code label',
|
|
||||||
'width': 24,
|
|
||||||
'height': 24,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'file': 'qr_and_text.html',
|
|
||||||
'name': 'QR and text',
|
|
||||||
'description': 'Label with QR code and name of location',
|
|
||||||
'width': 50,
|
|
||||||
'height': 24,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
# Create lables
|
||||||
for label in labels:
|
for label in labels:
|
||||||
|
self.create_template_label(model, src_dir, ref_name, label)
|
||||||
|
|
||||||
filename = os.path.join(
|
def create_template_label(self, model, src_dir, ref_name, label):
|
||||||
'label',
|
"""Ensure a label template is in place."""
|
||||||
'inventree',
|
filename = os.path.join(
|
||||||
'stocklocation',
|
|
||||||
label['file'],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if the file exists in the media directory
|
|
||||||
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',
|
|
||||||
)
|
|
||||||
|
|
||||||
dst_dir = os.path.join(
|
|
||||||
settings.MEDIA_ROOT,
|
|
||||||
'label',
|
'label',
|
||||||
'inventree',
|
'inventree',
|
||||||
'part',
|
ref_name,
|
||||||
|
label['file']
|
||||||
)
|
)
|
||||||
|
|
||||||
if not os.path.exists(dst_dir):
|
src_file = src_dir.joinpath(label['file'])
|
||||||
logger.info(f"Creating required directory: '{dst_dir}'")
|
dst_file = settings.MEDIA_ROOT.joinpath(filename)
|
||||||
os.makedirs(dst_dir, exist_ok=True)
|
|
||||||
|
|
||||||
labels = [
|
to_copy = False
|
||||||
{
|
|
||||||
'file': 'part_label.html',
|
|
||||||
'name': 'Part Label',
|
|
||||||
'description': 'Simple part label',
|
|
||||||
'width': 70,
|
|
||||||
'height': 24,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'file': 'part_label_code128.html',
|
|
||||||
'name': 'Barcode Part Label',
|
|
||||||
'description': 'Simple part label with Code128 barcode',
|
|
||||||
'width': 70,
|
|
||||||
'height': 24,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
for label in labels:
|
if dst_file.exists():
|
||||||
|
# File already exists - let's see if it is the "same"
|
||||||
|
|
||||||
filename = os.path.join(
|
if hashFile(dst_file) != hashFile(src_file): # pragma: no cover
|
||||||
'label',
|
logger.info(f"Hash differs for '{filename}'")
|
||||||
'inventree',
|
|
||||||
'part',
|
|
||||||
label['file']
|
|
||||||
)
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
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
|
to_copy = True
|
||||||
|
|
||||||
if to_copy:
|
else:
|
||||||
logger.info(f"Copying label template '{dst_file}'")
|
logger.info(f"Label template '{filename}' is not present")
|
||||||
shutil.copyfile(src_file, dst_file)
|
to_copy = True
|
||||||
|
|
||||||
# Check if a label matching the template already exists
|
if to_copy:
|
||||||
if PartLabel.objects.filter(label=filename).exists():
|
logger.info(f"Copying label template '{dst_file}'")
|
||||||
continue # pragma: no cover
|
# Ensure destionation dir exists
|
||||||
|
dst_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
logger.info(f"Creating entry for PartLabel '{label['name']}'")
|
# Copy file
|
||||||
|
shutil.copyfile(src_file, dst_file)
|
||||||
|
|
||||||
PartLabel.objects.create(
|
# Check if a label matching the template already exists
|
||||||
name=label['name'],
|
if model.objects.filter(label=filename).exists():
|
||||||
description=label['description'],
|
return # pragma: no cover
|
||||||
label=filename,
|
|
||||||
filters='',
|
logger.info(f"Creating entry for {model} '{label['name']}'")
|
||||||
enabled=True,
|
|
||||||
width=label['width'],
|
model.objects.create(
|
||||||
height=label['height'],
|
name=label['name'],
|
||||||
)
|
description=label['description'],
|
||||||
|
label=filename,
|
||||||
|
filters='',
|
||||||
|
enabled=True,
|
||||||
|
width=label['width'],
|
||||||
|
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 ...")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user