Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2020-11-15 17:03:28 +11:00
commit af55af6b8f
19 changed files with 385 additions and 95 deletions

View File

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

View File

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

View File

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

View File

@ -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: with open(cfg_filename, 'r') as cfg:
CONFIG = yaml.safe_load(cfg) CONFIG = yaml.safe_load(cfg)
# Read the autogenerated key-file
key_file = open(os.path.join(BASE_DIR, 'secret_key.txt'), 'r')
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.
- Primarily this configuration happens in the config.yaml file
- However there may be reason to configure the DB via environmental variables
- The following code lets the user "mix and match" database configuration
"""
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: else:
eprint("Warning: Database backend not specified - using default (sqlite)") logger.debug(f' INVENTREE_DB_{key} not found in environment variables')
DATABASES['default'] = {
'ENGINE': 'django.db.backends.sqlite3', # Check that required database configuration options are specified
'NAME': os.path.join(BASE_DIR, 'inventree_db.sqlite3'), 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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = []

View File

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

View 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

View File

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