2
0
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:
Matthias Mair 2022-07-27 02:42:34 +02:00 committed by GitHub
parent 551f66ff90
commit 794e375009
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 242 additions and 403 deletions

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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