mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
af55af6b8f
@ -5,6 +5,8 @@ Main JSON interface views
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
|
|
||||||
@ -21,7 +23,10 @@ from .version import inventreeVersion, inventreeInstanceName
|
|||||||
from plugins import plugins as inventree_plugins
|
from plugins import plugins as inventree_plugins
|
||||||
|
|
||||||
|
|
||||||
print("Loading action plugins")
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
logger.info("Loading action plugins...")
|
||||||
action_plugins = inventree_plugins.load_action_plugins()
|
action_plugins = inventree_plugins.load_action_plugins()
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ from InvenTree.settings import *
|
|||||||
|
|
||||||
# Override the 'test' database
|
# Override the 'test' database
|
||||||
if 'test' in sys.argv:
|
if 'test' in sys.argv:
|
||||||
eprint('InvenTree: Running tests - Using MySQL test database')
|
print('InvenTree: Running tests - Using MySQL test database')
|
||||||
|
|
||||||
DATABASES['default'] = {
|
DATABASES['default'] = {
|
||||||
# Ensure mysql backend is being used
|
# Ensure mysql backend is being used
|
||||||
|
@ -6,7 +6,7 @@ from InvenTree.settings import *
|
|||||||
|
|
||||||
# Override the 'test' database
|
# Override the 'test' database
|
||||||
if 'test' in sys.argv:
|
if 'test' in sys.argv:
|
||||||
eprint('InvenTree: Running tests - Using PostGreSQL test database')
|
print('InvenTree: Running tests - Using PostGreSQL test database')
|
||||||
|
|
||||||
DATABASES['default'] = {
|
DATABASES['default'] = {
|
||||||
# Ensure postgresql backend is being used
|
# Ensure postgresql backend is being used
|
||||||
|
@ -22,32 +22,58 @@ from datetime import datetime
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
def eprint(*args, **kwargs):
|
|
||||||
""" Print a warning message to stderr """
|
|
||||||
print(*args, file=sys.stderr, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
cfg_filename = os.path.join(BASE_DIR, 'config.yaml')
|
cfg_filename = os.path.join(BASE_DIR, 'config.yaml')
|
||||||
|
|
||||||
if not os.path.exists(cfg_filename):
|
if not os.path.exists(cfg_filename):
|
||||||
CONFIG = {}
|
print("Error: config.yaml not found")
|
||||||
eprint("Warning: config.yaml not found - using default settings")
|
sys.exit(-1)
|
||||||
else:
|
|
||||||
with open(cfg_filename, 'r') as cfg:
|
|
||||||
CONFIG = yaml.safe_load(cfg)
|
|
||||||
|
|
||||||
# Read the autogenerated key-file
|
with open(cfg_filename, 'r') as cfg:
|
||||||
key_file = open(os.path.join(BASE_DIR, 'secret_key.txt'), 'r')
|
CONFIG = yaml.safe_load(cfg)
|
||||||
|
|
||||||
SECRET_KEY = key_file.read().strip()
|
|
||||||
|
|
||||||
# Default action is to run the system in Debug mode
|
# Default action is to run the system in Debug mode
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = CONFIG.get('debug', True)
|
DEBUG = CONFIG.get('debug', True)
|
||||||
|
|
||||||
|
# Configure logging settings
|
||||||
|
|
||||||
|
log_level = CONFIG.get('log_level', 'DEBUG').upper()
|
||||||
|
|
||||||
|
if log_level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']:
|
||||||
|
log_level = 'WARNING'
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
'version': 1,
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
'handlers': {
|
||||||
|
'console': {
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'root': {
|
||||||
|
'handlers': ['console'],
|
||||||
|
'level': log_level,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=log_level,
|
||||||
|
format='%(asctime)s %(levelname)s %(message)s',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get a logger instance for this setup file
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Read the autogenerated key-file
|
||||||
|
key_file_name = os.path.join(BASE_DIR, 'secret_key.txt')
|
||||||
|
logger.info(f'Loading SERCRET_KEY from {key_file_name}')
|
||||||
|
key_file = open(key_file_name, 'r')
|
||||||
|
|
||||||
|
SECRET_KEY = key_file.read().strip()
|
||||||
|
|
||||||
# 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', ['*'])
|
||||||
|
|
||||||
@ -65,13 +91,6 @@ if cors_opt:
|
|||||||
if not CORS_ORIGIN_ALLOW_ALL:
|
if not CORS_ORIGIN_ALLOW_ALL:
|
||||||
CORS_ORIGIN_WHITELIST = cors_opt.get('whitelist', [])
|
CORS_ORIGIN_WHITELIST = cors_opt.get('whitelist', [])
|
||||||
|
|
||||||
if DEBUG:
|
|
||||||
# will output to your console
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.DEBUG,
|
|
||||||
format='%(asctime)s %(levelname)s %(message)s',
|
|
||||||
)
|
|
||||||
|
|
||||||
# Web URL endpoint for served static files
|
# Web URL endpoint for served static files
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
@ -92,14 +111,18 @@ MEDIA_URL = '/media/'
|
|||||||
MEDIA_ROOT = os.path.abspath(CONFIG.get('media_root', os.path.join(BASE_DIR, 'media')))
|
MEDIA_ROOT = os.path.abspath(CONFIG.get('media_root', os.path.join(BASE_DIR, 'media')))
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print("InvenTree running in DEBUG mode")
|
logger.info("InvenTree running in DEBUG mode")
|
||||||
print("MEDIA_ROOT:", MEDIA_ROOT)
|
|
||||||
print("STATIC_ROOT:", STATIC_ROOT)
|
logger.info(f"MEDIA_ROOT: '{MEDIA_ROOT}'")
|
||||||
|
logger.info(f"STATIC_ROOT: '{STATIC_ROOT}'")
|
||||||
|
|
||||||
# Does the user wish to use the sentry.io integration?
|
# Does the user wish to use the sentry.io integration?
|
||||||
sentry_opts = CONFIG.get('sentry', {})
|
sentry_opts = CONFIG.get('sentry', {})
|
||||||
|
|
||||||
if sentry_opts.get('enabled', False):
|
if sentry_opts.get('enabled', False):
|
||||||
|
|
||||||
|
logger.info("Configuring sentry.io integration")
|
||||||
|
|
||||||
dsn = sentry_opts.get('dsn', None)
|
dsn = sentry_opts.get('dsn', None)
|
||||||
|
|
||||||
if dsn is not None:
|
if dsn is not None:
|
||||||
@ -111,11 +134,11 @@ if sentry_opts.get('enabled', False):
|
|||||||
sentry_sdk.init(dsn=dsn, integrations=[DjangoIntegration()], send_default_pii=True)
|
sentry_sdk.init(dsn=dsn, integrations=[DjangoIntegration()], send_default_pii=True)
|
||||||
|
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
print("sentry_sdk module not found. Install using 'pip install sentry-sdk'")
|
logger.error("sentry_sdk module not found. Install using 'pip install sentry-sdk'")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("Warning: Sentry.io DSN not specified")
|
logger.warning("Sentry.io DSN not specified in config file")
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
@ -160,17 +183,6 @@ INSTALLED_APPS = [
|
|||||||
'error_report', # Error reporting in the admin interface
|
'error_report', # Error reporting in the admin interface
|
||||||
]
|
]
|
||||||
|
|
||||||
LOGGING = {
|
|
||||||
'version': 1,
|
|
||||||
'disable_existing_loggers': False,
|
|
||||||
'handlers': {
|
|
||||||
'console': {
|
|
||||||
'level': 'DEBUG',
|
|
||||||
'class': 'logging.StreamHandler',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
MIDDLEWARE = CONFIG.get('middleware', [
|
MIDDLEWARE = CONFIG.get('middleware', [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
@ -193,7 +205,7 @@ AUTHENTICATION_BACKENDS = CONFIG.get('authentication_backends', [
|
|||||||
|
|
||||||
# If the debug toolbar is enabled, add the modules
|
# If the debug toolbar is enabled, add the modules
|
||||||
if DEBUG and CONFIG.get('debug_toolbar', False):
|
if DEBUG and CONFIG.get('debug_toolbar', False):
|
||||||
print("Running with DEBUG_TOOLBAR enabled")
|
logger.info("Running with DEBUG_TOOLBAR enabled")
|
||||||
INSTALLED_APPS.append('debug_toolbar')
|
INSTALLED_APPS.append('debug_toolbar')
|
||||||
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')
|
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')
|
||||||
|
|
||||||
@ -285,7 +297,7 @@ When running unit tests, enforce usage of sqlite3 database,
|
|||||||
so that the tests can be run in RAM without any setup requirements
|
so that the tests can be run in RAM without any setup requirements
|
||||||
"""
|
"""
|
||||||
if 'test' in sys.argv:
|
if 'test' in sys.argv:
|
||||||
eprint('InvenTree: Running tests - Using sqlite3 memory database')
|
logger.info('InvenTree: Running tests - Using sqlite3 memory database')
|
||||||
DATABASES['default'] = {
|
DATABASES['default'] = {
|
||||||
# Ensure sqlite3 backend is being used
|
# Ensure sqlite3 backend is being used
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
@ -295,14 +307,69 @@ if 'test' in sys.argv:
|
|||||||
|
|
||||||
# Database backend selection
|
# Database backend selection
|
||||||
else:
|
else:
|
||||||
if 'database' in CONFIG:
|
"""
|
||||||
DATABASES['default'] = CONFIG['database']
|
Configure the database backend based on the user-specified values.
|
||||||
else:
|
|
||||||
eprint("Warning: Database backend not specified - using default (sqlite)")
|
- Primarily this configuration happens in the config.yaml file
|
||||||
DATABASES['default'] = {
|
- However there may be reason to configure the DB via environmental variables
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
- The following code lets the user "mix and match" database configuration
|
||||||
'NAME': os.path.join(BASE_DIR, 'inventree_db.sqlite3'),
|
"""
|
||||||
}
|
|
||||||
|
logger.info("Configuring database backend:")
|
||||||
|
|
||||||
|
# Extract database configuration from the config.yaml file
|
||||||
|
db_config = CONFIG.get('database', {})
|
||||||
|
|
||||||
|
# If a particular database option is not specified in the config file,
|
||||||
|
# look for it in the environmental variables
|
||||||
|
# e.g. INVENTREE_DB_NAME / INVENTREE_DB_USER / etc
|
||||||
|
|
||||||
|
db_keys = ['ENGINE', 'NAME', 'USER', 'PASSWORD', 'HOST', 'PORT']
|
||||||
|
|
||||||
|
for key in db_keys:
|
||||||
|
if key not in db_config:
|
||||||
|
logger.debug(f" - Missing {key} value: Looking for environment variable INVENTREE_DB_{key}")
|
||||||
|
env_key = f'INVENTREE_DB_{key}'
|
||||||
|
env_var = os.environ.get(env_key, None)
|
||||||
|
|
||||||
|
if env_var is not None:
|
||||||
|
logger.info(f'Using environment variable INVENTREE_DB_{key}')
|
||||||
|
db_config[key] = env_var
|
||||||
|
else:
|
||||||
|
logger.debug(f' INVENTREE_DB_{key} not found in environment variables')
|
||||||
|
|
||||||
|
# Check that required database configuration options are specified
|
||||||
|
reqiured_keys = ['ENGINE', 'NAME']
|
||||||
|
|
||||||
|
for key in reqiured_keys:
|
||||||
|
if key not in db_config:
|
||||||
|
error_msg = f'Missing required database configuration value {key} in config.yaml'
|
||||||
|
logger.error(error_msg)
|
||||||
|
|
||||||
|
print('Error: ' + error_msg)
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Special considerations for the database 'ENGINE' setting.
|
||||||
|
It can be specified in config.yaml (or envvar) as either (for example):
|
||||||
|
- sqlite3
|
||||||
|
- django.db.backends.sqlite3
|
||||||
|
- django.db.backends.postgresql
|
||||||
|
"""
|
||||||
|
|
||||||
|
db_engine = db_config['ENGINE']
|
||||||
|
|
||||||
|
if db_engine.lower() in ['sqlite3', 'postgresql', 'mysql']:
|
||||||
|
# Prepend the required python module string
|
||||||
|
db_engine = f'django.db.backends.{db_engine.lower()}'
|
||||||
|
db_config['ENGINE'] = db_engine
|
||||||
|
|
||||||
|
db_name = db_config['NAME']
|
||||||
|
|
||||||
|
logger.info(f"Database ENGINE: '{db_engine}'")
|
||||||
|
logger.info(f"Database NAME: '{db_name}'")
|
||||||
|
|
||||||
|
DATABASES['default'] = db_config
|
||||||
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
'default': {
|
||||||
@ -341,7 +408,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||||||
EXTRA_URL_SCHEMES = CONFIG.get('extra_url_schemes', [])
|
EXTRA_URL_SCHEMES = CONFIG.get('extra_url_schemes', [])
|
||||||
|
|
||||||
if not type(EXTRA_URL_SCHEMES) in [list]:
|
if not type(EXTRA_URL_SCHEMES) in [list]:
|
||||||
eprint("Warning: extra_url_schemes not correctly formatted")
|
logger.warning("extra_url_schemes not correctly formatted")
|
||||||
EXTRA_URL_SCHEMES = []
|
EXTRA_URL_SCHEMES = []
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
|
@ -82,7 +82,7 @@ settings_urls = [
|
|||||||
url(r'^purchase-order/?', SettingsView.as_view(template_name='InvenTree/settings/po.html'), name='settings-po'),
|
url(r'^purchase-order/?', SettingsView.as_view(template_name='InvenTree/settings/po.html'), name='settings-po'),
|
||||||
url(r'^sales-order/?', SettingsView.as_view(template_name='InvenTree/settings/so.html'), name='settings-so'),
|
url(r'^sales-order/?', SettingsView.as_view(template_name='InvenTree/settings/so.html'), name='settings-so'),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/edit/?', SettingEdit.as_view(), name='setting-edit'),
|
url(r'^(?P<pk>\d+)/edit/', SettingEdit.as_view(), name='setting-edit'),
|
||||||
|
|
||||||
# Catch any other urls
|
# Catch any other urls
|
||||||
url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings'),
|
url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings'),
|
||||||
|
@ -7,7 +7,7 @@ import django
|
|||||||
|
|
||||||
import common.models
|
import common.models
|
||||||
|
|
||||||
INVENTREE_SW_VERSION = "0.1.4 pre"
|
INVENTREE_SW_VERSION = "0.1.5 pre"
|
||||||
|
|
||||||
|
|
||||||
def inventreeInstanceName():
|
def inventreeInstanceName():
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import logging
|
||||||
|
|
||||||
from InvenTree import plugins as InvenTreePlugins
|
from InvenTree import plugins as InvenTreePlugins
|
||||||
from barcode import plugins as BarcodePlugins
|
from barcode import plugins as BarcodePlugins
|
||||||
@ -10,6 +11,9 @@ from stock.serializers import StockItemSerializer, LocationSerializer
|
|||||||
from part.serializers import PartSerializer
|
from part.serializers import PartSerializer
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def hash_barcode(barcode_data):
|
def hash_barcode(barcode_data):
|
||||||
"""
|
"""
|
||||||
Calculate an MD5 hash of barcode data
|
Calculate an MD5 hash of barcode data
|
||||||
@ -130,18 +134,17 @@ def load_barcode_plugins(debug=False):
|
|||||||
Function to load all barcode plugins
|
Function to load all barcode plugins
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if debug:
|
logger.debug("Loading barcode plugins")
|
||||||
print("Loading barcode plugins")
|
|
||||||
|
|
||||||
plugins = InvenTreePlugins.get_plugins(BarcodePlugins, BarcodePlugin)
|
plugins = InvenTreePlugins.get_plugins(BarcodePlugins, BarcodePlugin)
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
if len(plugins) > 0:
|
if len(plugins) > 0:
|
||||||
print("Discovered {n} plugins:".format(n=len(plugins)))
|
logger.info(f"Discovered {len(plugins)} barcode plugins")
|
||||||
|
|
||||||
for p in plugins:
|
for p in plugins:
|
||||||
print(" - {p}".format(p=p.PLUGIN_NAME))
|
logger.debug(" - {p}".format(p=p.PLUGIN_NAME))
|
||||||
else:
|
else:
|
||||||
print("No barcode plugins found")
|
logger.debug("No barcode plugins found")
|
||||||
|
|
||||||
return plugins
|
return plugins
|
||||||
|
@ -8,7 +8,8 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
|
from django.db.utils import IntegrityError, OperationalError
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
import djmoney.settings
|
import djmoney.settings
|
||||||
@ -16,7 +17,6 @@ from djmoney.models.fields import MoneyField
|
|||||||
from djmoney.contrib.exchange.models import convert_money
|
from djmoney.contrib.exchange.models import convert_money
|
||||||
from djmoney.contrib.exchange.exceptions import MissingRate
|
from djmoney.contrib.exchange.exceptions import MissingRate
|
||||||
|
|
||||||
from django.db.utils import OperationalError
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -230,7 +230,7 @@ class InvenTreeSetting(models.Model):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_value(cls, key):
|
def get_setting_default(cls, key):
|
||||||
"""
|
"""
|
||||||
Return the default value for a particular setting.
|
Return the default value for a particular setting.
|
||||||
|
|
||||||
@ -281,20 +281,23 @@ class InvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
setting = InvenTreeSetting.objects.filter(key__iexact=key).first()
|
setting = InvenTreeSetting.objects.filter(key__iexact=key).first()
|
||||||
except OperationalError:
|
|
||||||
# Settings table has not been created yet!
|
|
||||||
return None
|
|
||||||
except (ValueError, InvenTreeSetting.DoesNotExist):
|
except (ValueError, InvenTreeSetting.DoesNotExist):
|
||||||
|
setting = None
|
||||||
|
except (IntegrityError, OperationalError):
|
||||||
|
setting = None
|
||||||
|
|
||||||
|
# Setting does not exist! (Try to create it)
|
||||||
|
if not setting:
|
||||||
|
|
||||||
|
setting = InvenTreeSetting(key=key, value=InvenTreeSetting.get_setting_default(key))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Attempt Create the setting if it does not exist
|
# Wrap this statement in "atomic", so it can be rolled back if it fails
|
||||||
setting = InvenTreeSetting.create(
|
with transaction.atomic():
|
||||||
key=key,
|
setting.save()
|
||||||
value=InvenTreeSetting.get_default_value(key)
|
except (IntegrityError, OperationalError):
|
||||||
)
|
# It might be the case that the database isn't created yet
|
||||||
except OperationalError:
|
pass
|
||||||
# Settings table has not been created yet
|
|
||||||
setting = None
|
|
||||||
|
|
||||||
return setting
|
return setting
|
||||||
|
|
||||||
@ -322,7 +325,7 @@ class InvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
# If no backup value is specified, atttempt to retrieve a "default" value
|
# If no backup value is specified, atttempt to retrieve a "default" value
|
||||||
if backup_value is None:
|
if backup_value is None:
|
||||||
backup_value = cls.get_default_value(key)
|
backup_value = cls.get_setting_default(key)
|
||||||
|
|
||||||
setting = InvenTreeSetting.get_setting_object(key)
|
setting = InvenTreeSetting.get_setting_object(key)
|
||||||
|
|
||||||
@ -380,7 +383,7 @@ class InvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def default_value(self):
|
def default_value(self):
|
||||||
return InvenTreeSetting.get_default_value(self.key)
|
return InvenTreeSetting.get_setting_default(self.key)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
@ -403,6 +406,9 @@ class InvenTreeSetting(models.Model):
|
|||||||
if validator is not None:
|
if validator is not None:
|
||||||
self.run_validator(validator)
|
self.run_validator(validator)
|
||||||
|
|
||||||
|
if self.is_bool():
|
||||||
|
self.value = InvenTree.helpers.str2bool(self.value)
|
||||||
|
|
||||||
def run_validator(self, validator):
|
def run_validator(self, validator):
|
||||||
"""
|
"""
|
||||||
Run a validator against the 'value' field for this InvenTreeSetting object.
|
Run a validator against the 'value' field for this InvenTreeSetting object.
|
||||||
|
143
InvenTree/common/test_views.py
Normal file
143
InvenTree/common/test_views.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
"""
|
||||||
|
Unit tests for the views associated with the 'common' app
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
from common.models import InvenTreeSetting
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsViewTest(TestCase):
|
||||||
|
"""
|
||||||
|
Tests for the settings management views
|
||||||
|
"""
|
||||||
|
|
||||||
|
fixtures = [
|
||||||
|
'settings',
|
||||||
|
]
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
# Create a user (required to access the views / forms)
|
||||||
|
self.user = get_user_model().objects.create_user(
|
||||||
|
username='username',
|
||||||
|
email='me@email.com',
|
||||||
|
password='password',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client.login(username='username', password='password')
|
||||||
|
|
||||||
|
def get_url(self, pk):
|
||||||
|
return reverse('setting-edit', args=(pk,))
|
||||||
|
|
||||||
|
def get_setting(self, title):
|
||||||
|
|
||||||
|
return InvenTreeSetting.get_setting_object(title)
|
||||||
|
|
||||||
|
def get(self, url, status=200):
|
||||||
|
|
||||||
|
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status)
|
||||||
|
|
||||||
|
data = json.loads(response.content)
|
||||||
|
|
||||||
|
return response, data
|
||||||
|
|
||||||
|
def post(self, url, data, valid=None):
|
||||||
|
|
||||||
|
response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||||
|
|
||||||
|
json_data = json.loads(response.content)
|
||||||
|
|
||||||
|
# If a particular status code is required
|
||||||
|
if valid is not None:
|
||||||
|
if valid:
|
||||||
|
self.assertEqual(json_data['form_valid'], True)
|
||||||
|
else:
|
||||||
|
self.assertEqual(json_data['form_valid'], False)
|
||||||
|
|
||||||
|
form_errors = json.loads(json_data['form_errors'])
|
||||||
|
|
||||||
|
return json_data, form_errors
|
||||||
|
|
||||||
|
def test_instance_name(self):
|
||||||
|
"""
|
||||||
|
Test that we can get the settings view for particular setting objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Start with something basic - load the settings view for INVENTREE_INSTANCE
|
||||||
|
setting = self.get_setting('INVENTREE_INSTANCE')
|
||||||
|
|
||||||
|
self.assertIsNotNone(setting)
|
||||||
|
self.assertEqual(setting.value, 'My very first InvenTree Instance')
|
||||||
|
|
||||||
|
url = self.get_url(setting.pk)
|
||||||
|
|
||||||
|
self.get(url)
|
||||||
|
|
||||||
|
new_name = 'A new instance name!'
|
||||||
|
|
||||||
|
# Change the instance name via the form
|
||||||
|
data, errors = self.post(url, {'value': new_name}, valid=True)
|
||||||
|
|
||||||
|
name = InvenTreeSetting.get_setting('INVENTREE_INSTANCE')
|
||||||
|
|
||||||
|
self.assertEqual(name, new_name)
|
||||||
|
|
||||||
|
def test_choices(self):
|
||||||
|
"""
|
||||||
|
Tests for a setting which has choices
|
||||||
|
"""
|
||||||
|
|
||||||
|
setting = InvenTreeSetting.get_setting_object('INVENTREE_DEFAULT_CURRENCY')
|
||||||
|
|
||||||
|
# Default value!
|
||||||
|
self.assertEqual(setting.value, 'USD')
|
||||||
|
|
||||||
|
url = self.get_url(setting.pk)
|
||||||
|
|
||||||
|
# Try posting an invalid currency option
|
||||||
|
data, errors = self.post(url, {'value': 'XPQaaa'}, valid=False)
|
||||||
|
|
||||||
|
self.assertIsNotNone(errors.get('value'), None)
|
||||||
|
|
||||||
|
# Try posting a valid currency option
|
||||||
|
data, errors = self.post(url, {'value': 'AUD'}, valid=True)
|
||||||
|
|
||||||
|
def test_binary_values(self):
|
||||||
|
"""
|
||||||
|
Test for binary value
|
||||||
|
"""
|
||||||
|
|
||||||
|
setting = InvenTreeSetting.get_setting_object('PART_COMPONENT')
|
||||||
|
|
||||||
|
self.assertTrue(setting.as_bool())
|
||||||
|
|
||||||
|
url = self.get_url(setting.pk)
|
||||||
|
|
||||||
|
setting.value = True
|
||||||
|
setting.save()
|
||||||
|
|
||||||
|
# Try posting some invalid values
|
||||||
|
# The value should be "cleaned" and stay the same
|
||||||
|
for value in ['', 'abc', 'cat', 'TRUETRUETRUE']:
|
||||||
|
self.post(url, {'value': value}, valid=True)
|
||||||
|
|
||||||
|
# Try posting some valid (True) values
|
||||||
|
for value in [True, 'True', '1', 'yes']:
|
||||||
|
self.post(url, {'value': value}, valid=True)
|
||||||
|
self.assertTrue(InvenTreeSetting.get_setting('PART_COMPONENT'))
|
||||||
|
|
||||||
|
# Try posting some valid (False) values
|
||||||
|
for value in [False, 'False']:
|
||||||
|
self.post(url, {'value': value}, valid=True)
|
||||||
|
self.assertFalse(InvenTreeSetting.get_setting('PART_COMPONENT'))
|
@ -70,7 +70,7 @@ class SettingsTest(TestCase):
|
|||||||
|
|
||||||
for key in InvenTreeSetting.GLOBAL_SETTINGS.keys():
|
for key in InvenTreeSetting.GLOBAL_SETTINGS.keys():
|
||||||
|
|
||||||
value = InvenTreeSetting.get_default_value(key)
|
value = InvenTreeSetting.get_setting_default(key)
|
||||||
|
|
||||||
InvenTreeSetting.set_setting(key, value, self.user)
|
InvenTreeSetting.set_setting(key, value, self.user)
|
||||||
|
|
||||||
|
@ -72,3 +72,32 @@ class SettingEdit(AjaxUpdateView):
|
|||||||
form.fields['value'].help_text = description
|
form.fields['value'].help_text = description
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
def validate(self, setting, form):
|
||||||
|
"""
|
||||||
|
Perform custom validation checks on the form data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = form.cleaned_data
|
||||||
|
|
||||||
|
value = data.get('value', None)
|
||||||
|
|
||||||
|
if setting.choices():
|
||||||
|
"""
|
||||||
|
If a set of choices are provided for a given setting,
|
||||||
|
the provided value must be one of those choices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
choices = [choice[0] for choice in setting.choices()]
|
||||||
|
|
||||||
|
if value not in choices:
|
||||||
|
form.add_error('value', _('Supplied value is not allowed'))
|
||||||
|
|
||||||
|
if setting.is_bool():
|
||||||
|
"""
|
||||||
|
If a setting is defined as a boolean setting,
|
||||||
|
the provided value must look somewhat like a boolean value!
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not str2bool(value, test=True) and not str2bool(value, test=False):
|
||||||
|
form.add_error('value', _('Supplied value must be a boolean'))
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.db.utils import OperationalError, ProgrammingError
|
from django.db.utils import OperationalError, ProgrammingError
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CompanyConfig(AppConfig):
|
class CompanyConfig(AppConfig):
|
||||||
name = 'company'
|
name = 'company'
|
||||||
|
|
||||||
@ -21,7 +25,7 @@ class CompanyConfig(AppConfig):
|
|||||||
|
|
||||||
from .models import Company
|
from .models import Company
|
||||||
|
|
||||||
print("InvenTree: Checking Company image thumbnails")
|
logger.debug("Checking Company image thumbnails")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for company in Company.objects.all():
|
for company in Company.objects.all():
|
||||||
@ -30,11 +34,11 @@ class CompanyConfig(AppConfig):
|
|||||||
loc = os.path.join(settings.MEDIA_ROOT, url)
|
loc = os.path.join(settings.MEDIA_ROOT, url)
|
||||||
|
|
||||||
if not os.path.exists(loc):
|
if not os.path.exists(loc):
|
||||||
print("InvenTree: Generating thumbnail for Company '{c}'".format(c=company.name))
|
logger.info("InvenTree: Generating thumbnail for Company '{c}'".format(c=company.name))
|
||||||
try:
|
try:
|
||||||
company.image.render_variations(replace=False)
|
company.image.render_variations(replace=False)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print("Image file missing")
|
logger.warning("Image file missing")
|
||||||
company.image = None
|
company.image = None
|
||||||
company.save()
|
company.save()
|
||||||
except (OperationalError, ProgrammingError):
|
except (OperationalError, ProgrammingError):
|
||||||
|
@ -1,22 +1,41 @@
|
|||||||
|
|
||||||
# Database backend selection - Configure backend database settings
|
# Database backend selection - Configure backend database settings
|
||||||
# Ref: https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-DATABASES
|
# Ref: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-DATABASES
|
||||||
# Specify database parameters below as they appear in the Django docs
|
# Specify database parameters below as they appear in the Django docs
|
||||||
|
|
||||||
|
# Note: Database configuration options can also be specified from environmental variables,
|
||||||
|
# with the prefix INVENTREE_DB_
|
||||||
|
# e.g INVENTREE_DB_NAME / INVENTREE_DB_USER / INVENTREE_DB_PASSWORD
|
||||||
database:
|
database:
|
||||||
# Example configuration - sqlite (default)
|
# Default configuration - sqlite filesystem database
|
||||||
ENGINE: django.db.backends.sqlite3
|
ENGINE: sqlite3
|
||||||
NAME: '../inventree_default_db.sqlite3'
|
NAME: '../inventree_default_db.sqlite3'
|
||||||
|
|
||||||
# For more complex database installations, further parameters are required
|
# For more complex database installations, further parameters are required
|
||||||
# Refer to the django documentation for full list of options
|
# Refer to the django documentation for full list of options
|
||||||
|
|
||||||
# Example Configuration - MySQL
|
# --- Available options: ---
|
||||||
|
# ENGINE: Database engine. Selection from:
|
||||||
|
# - sqlite3
|
||||||
|
# - mysql
|
||||||
|
# - postgresql
|
||||||
|
# NAME: Database name
|
||||||
|
# USER: Database username (if required)
|
||||||
|
# PASSWORD: Database password (if required)
|
||||||
|
# HOST: Database host address (if required)
|
||||||
|
# PORT: Database host port (if required)
|
||||||
|
|
||||||
|
# --- Example Configuration - sqlite3 ---
|
||||||
|
# ENGINE: sqlite3
|
||||||
|
# NAME: '/path/to/database.sqlite3'
|
||||||
|
|
||||||
|
# --- Example Configuration - MySQL ---
|
||||||
#ENGINE: django.db.backends.mysql
|
#ENGINE: django.db.backends.mysql
|
||||||
#NAME: inventree
|
#NAME: inventree
|
||||||
#USER: inventree_username
|
#USER: inventree_username
|
||||||
#PASSWORD: inventree_password
|
#PASSWORD: inventree_password
|
||||||
#HOST: ''
|
#HOST: '127.0.0.1'
|
||||||
#PORT: ''
|
#PORT: '5432'
|
||||||
|
|
||||||
# Select default system language (default is 'en-us')
|
# Select default system language (default is 'en-us')
|
||||||
language: en-us
|
language: en-us
|
||||||
@ -45,6 +64,9 @@ debug: True
|
|||||||
# and only if InvenTree is accessed from a local IP (127.0.0.1)
|
# and only if InvenTree is accessed from a local IP (127.0.0.1)
|
||||||
debug_toolbar: False
|
debug_toolbar: False
|
||||||
|
|
||||||
|
# Configure the system logging level
|
||||||
|
# Options: DEBUG / INFO / WARNING / ERROR / CRITICAL
|
||||||
|
log_level: WARNING
|
||||||
|
|
||||||
# Allowed hosts (see ALLOWED_HOSTS in Django settings documentation)
|
# Allowed hosts (see ALLOWED_HOSTS in Django settings documentation)
|
||||||
# A list of strings representing the host/domain names that this Django site can serve.
|
# A list of strings representing the host/domain names that this Django site can serve.
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.db.utils import OperationalError, ProgrammingError
|
from django.db.utils import OperationalError, ProgrammingError
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PartConfig(AppConfig):
|
class PartConfig(AppConfig):
|
||||||
name = 'part'
|
name = 'part'
|
||||||
|
|
||||||
@ -27,7 +31,7 @@ class PartConfig(AppConfig):
|
|||||||
|
|
||||||
from .models import Part
|
from .models import Part
|
||||||
|
|
||||||
print("InvenTree: Checking Part image thumbnails")
|
logger.debug("InvenTree: Checking Part image thumbnails")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for part in Part.objects.all():
|
for part in Part.objects.all():
|
||||||
@ -36,11 +40,11 @@ class PartConfig(AppConfig):
|
|||||||
loc = os.path.join(settings.MEDIA_ROOT, url)
|
loc = os.path.join(settings.MEDIA_ROOT, url)
|
||||||
|
|
||||||
if not os.path.exists(loc):
|
if not os.path.exists(loc):
|
||||||
print("InvenTree: Generating thumbnail for Part '{p}'".format(p=part.name))
|
logger.info("InvenTree: Generating thumbnail for Part '{p}'".format(p=part.name))
|
||||||
try:
|
try:
|
||||||
part.image.render_variations(replace=False)
|
part.image.render_variations(replace=False)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print("Image file missing")
|
logger.warning("Image file missing")
|
||||||
part.image = None
|
part.image = None
|
||||||
part.save()
|
part.save()
|
||||||
except (OperationalError, ProgrammingError):
|
except (OperationalError, ProgrammingError):
|
||||||
|
@ -213,6 +213,7 @@ class EditPartForm(HelperForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Part
|
model = Part
|
||||||
fields = [
|
fields = [
|
||||||
|
'confirm_creation',
|
||||||
'category',
|
'category',
|
||||||
'selected_category_templates',
|
'selected_category_templates',
|
||||||
'parent_category_templates',
|
'parent_category_templates',
|
||||||
@ -222,7 +223,6 @@ class EditPartForm(HelperForm):
|
|||||||
'revision',
|
'revision',
|
||||||
'bom_copy',
|
'bom_copy',
|
||||||
'parameters_copy',
|
'parameters_copy',
|
||||||
'confirm_creation',
|
|
||||||
'keywords',
|
'keywords',
|
||||||
'variant_of',
|
'variant_of',
|
||||||
'link',
|
'link',
|
||||||
|
@ -350,7 +350,7 @@ class Part(MPTTModel):
|
|||||||
# Get part category
|
# Get part category
|
||||||
category = self.category
|
category = self.category
|
||||||
|
|
||||||
if add_category_templates:
|
if category and add_category_templates:
|
||||||
# Store templates added to part
|
# Store templates added to part
|
||||||
template_list = []
|
template_list = []
|
||||||
|
|
||||||
|
@ -1320,8 +1320,6 @@ class BomUpload(InvenTreeRoleMixin, FormView):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
print(row, row['part_match'], len(row['part_options']))
|
|
||||||
|
|
||||||
def extractDataFromFile(self, bom):
|
def extractDataFromFile(self, bom):
|
||||||
""" Read data from the BOM file """
|
""" Read data from the BOM file """
|
||||||
|
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import plugins.plugin as plugin
|
import plugins.plugin as plugin
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ActionPlugin(plugin.InvenTreePlugin):
|
class ActionPlugin(plugin.InvenTreePlugin):
|
||||||
"""
|
"""
|
||||||
The ActionPlugin class is used to perform custom actions
|
The ActionPlugin class is used to perform custom actions
|
||||||
|
@ -3,12 +3,16 @@
|
|||||||
import inspect
|
import inspect
|
||||||
import importlib
|
import importlib
|
||||||
import pkgutil
|
import pkgutil
|
||||||
|
import logging
|
||||||
|
|
||||||
# Action plugins
|
# Action plugins
|
||||||
import plugins.action as action
|
import plugins.action as action
|
||||||
from plugins.action.action import ActionPlugin
|
from plugins.action.action import ActionPlugin
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def iter_namespace(pkg):
|
def iter_namespace(pkg):
|
||||||
|
|
||||||
return pkgutil.iter_modules(pkg.__path__, pkg.__name__ + ".")
|
return pkgutil.iter_modules(pkg.__path__, pkg.__name__ + ".")
|
||||||
@ -52,14 +56,14 @@ def load_action_plugins():
|
|||||||
Return a list of all registered action plugins
|
Return a list of all registered action plugins
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print("Loading action plugins")
|
logger.debug("Loading action plugins")
|
||||||
|
|
||||||
plugins = get_plugins(action, ActionPlugin)
|
plugins = get_plugins(action, ActionPlugin)
|
||||||
|
|
||||||
if len(plugins) > 0:
|
if len(plugins) > 0:
|
||||||
print("Discovered {n} action plugins:".format(n=len(plugins)))
|
logger.info("Discovered {n} action plugins:".format(n=len(plugins)))
|
||||||
|
|
||||||
for ap in plugins:
|
for ap in plugins:
|
||||||
print(" - {ap}".format(ap=ap.PLUGIN_NAME))
|
logger.debug(" - {ap}".format(ap=ap.PLUGIN_NAME))
|
||||||
|
|
||||||
return plugins
|
return plugins
|
||||||
|
Loading…
Reference in New Issue
Block a user