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 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.http import JsonResponse
|
||||
|
||||
@ -21,7 +23,10 @@ from .version import inventreeVersion, inventreeInstanceName
|
||||
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()
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@ from InvenTree.settings import *
|
||||
|
||||
# Override the 'test' database
|
||||
if 'test' in sys.argv:
|
||||
eprint('InvenTree: Running tests - Using MySQL test database')
|
||||
print('InvenTree: Running tests - Using MySQL test database')
|
||||
|
||||
DATABASES['default'] = {
|
||||
# Ensure mysql backend is being used
|
||||
|
@ -6,7 +6,7 @@ from InvenTree.settings import *
|
||||
|
||||
# Override the 'test' database
|
||||
if 'test' in sys.argv:
|
||||
eprint('InvenTree: Running tests - Using PostGreSQL test database')
|
||||
print('InvenTree: Running tests - Using PostGreSQL test database')
|
||||
|
||||
DATABASES['default'] = {
|
||||
# Ensure postgresql backend is being used
|
||||
|
@ -22,32 +22,58 @@ from datetime import datetime
|
||||
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, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
cfg_filename = os.path.join(BASE_DIR, 'config.yaml')
|
||||
|
||||
if not os.path.exists(cfg_filename):
|
||||
CONFIG = {}
|
||||
eprint("Warning: config.yaml not found - using default settings")
|
||||
else:
|
||||
with open(cfg_filename, 'r') as cfg:
|
||||
CONFIG = yaml.safe_load(cfg)
|
||||
print("Error: config.yaml not found")
|
||||
sys.exit(-1)
|
||||
|
||||
# Read the autogenerated key-file
|
||||
key_file = open(os.path.join(BASE_DIR, 'secret_key.txt'), 'r')
|
||||
|
||||
SECRET_KEY = key_file.read().strip()
|
||||
with open(cfg_filename, 'r') as cfg:
|
||||
CONFIG = yaml.safe_load(cfg)
|
||||
|
||||
# Default action is to run the system in Debug mode
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
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)
|
||||
ALLOWED_HOSTS = CONFIG.get('allowed_hosts', ['*'])
|
||||
|
||||
@ -65,13 +91,6 @@ if cors_opt:
|
||||
if not CORS_ORIGIN_ALLOW_ALL:
|
||||
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
|
||||
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')))
|
||||
|
||||
if DEBUG:
|
||||
print("InvenTree running in DEBUG mode")
|
||||
print("MEDIA_ROOT:", MEDIA_ROOT)
|
||||
print("STATIC_ROOT:", STATIC_ROOT)
|
||||
logger.info("InvenTree running in DEBUG mode")
|
||||
|
||||
logger.info(f"MEDIA_ROOT: '{MEDIA_ROOT}'")
|
||||
logger.info(f"STATIC_ROOT: '{STATIC_ROOT}'")
|
||||
|
||||
# Does the user wish to use the sentry.io integration?
|
||||
sentry_opts = CONFIG.get('sentry', {})
|
||||
|
||||
if sentry_opts.get('enabled', False):
|
||||
|
||||
logger.info("Configuring sentry.io integration")
|
||||
|
||||
dsn = sentry_opts.get('dsn', 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)
|
||||
|
||||
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)
|
||||
|
||||
else:
|
||||
print("Warning: Sentry.io DSN not specified")
|
||||
logger.warning("Sentry.io DSN not specified in config file")
|
||||
|
||||
# Application definition
|
||||
|
||||
@ -160,17 +183,6 @@ INSTALLED_APPS = [
|
||||
'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', [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'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 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')
|
||||
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
|
||||
"""
|
||||
if 'test' in sys.argv:
|
||||
eprint('InvenTree: Running tests - Using sqlite3 memory database')
|
||||
logger.info('InvenTree: Running tests - Using sqlite3 memory database')
|
||||
DATABASES['default'] = {
|
||||
# Ensure sqlite3 backend is being used
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
@ -295,14 +307,69 @@ if 'test' in sys.argv:
|
||||
|
||||
# Database backend selection
|
||||
else:
|
||||
if 'database' in CONFIG:
|
||||
DATABASES['default'] = CONFIG['database']
|
||||
else:
|
||||
eprint("Warning: Database backend not specified - using default (sqlite)")
|
||||
DATABASES['default'] = {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'inventree_db.sqlite3'),
|
||||
}
|
||||
"""
|
||||
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:
|
||||
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 = {
|
||||
'default': {
|
||||
@ -341,7 +408,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
EXTRA_URL_SCHEMES = CONFIG.get('extra_url_schemes', [])
|
||||
|
||||
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 = []
|
||||
|
||||
# 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'^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
|
||||
url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings'),
|
||||
|
@ -7,7 +7,7 @@ import django
|
||||
|
||||
import common.models
|
||||
|
||||
INVENTREE_SW_VERSION = "0.1.4 pre"
|
||||
INVENTREE_SW_VERSION = "0.1.5 pre"
|
||||
|
||||
|
||||
def inventreeInstanceName():
|
||||
|
@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
from InvenTree import plugins as InvenTreePlugins
|
||||
from barcode import plugins as BarcodePlugins
|
||||
@ -10,6 +11,9 @@ from stock.serializers import StockItemSerializer, LocationSerializer
|
||||
from part.serializers import PartSerializer
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def hash_barcode(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
|
||||
"""
|
||||
|
||||
if debug:
|
||||
print("Loading barcode plugins")
|
||||
logger.debug("Loading barcode plugins")
|
||||
|
||||
plugins = InvenTreePlugins.get_plugins(BarcodePlugins, BarcodePlugin)
|
||||
|
||||
if debug:
|
||||
if len(plugins) > 0:
|
||||
print("Discovered {n} plugins:".format(n=len(plugins)))
|
||||
logger.info(f"Discovered {len(plugins)} barcode plugins")
|
||||
|
||||
for p in plugins:
|
||||
print(" - {p}".format(p=p.PLUGIN_NAME))
|
||||
logger.debug(" - {p}".format(p=p.PLUGIN_NAME))
|
||||
else:
|
||||
print("No barcode plugins found")
|
||||
logger.debug("No barcode plugins found")
|
||||
|
||||
return plugins
|
||||
|
@ -8,7 +8,8 @@ from __future__ import unicode_literals
|
||||
|
||||
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
|
||||
|
||||
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.exceptions import MissingRate
|
||||
|
||||
from django.db.utils import OperationalError
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.core.exceptions import ValidationError
|
||||
@ -230,7 +230,7 @@ class InvenTreeSetting(models.Model):
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_default_value(cls, key):
|
||||
def get_setting_default(cls, key):
|
||||
"""
|
||||
Return the default value for a particular setting.
|
||||
|
||||
@ -281,20 +281,23 @@ class InvenTreeSetting(models.Model):
|
||||
|
||||
try:
|
||||
setting = InvenTreeSetting.objects.filter(key__iexact=key).first()
|
||||
except OperationalError:
|
||||
# Settings table has not been created yet!
|
||||
return None
|
||||
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:
|
||||
# Attempt Create the setting if it does not exist
|
||||
setting = InvenTreeSetting.create(
|
||||
key=key,
|
||||
value=InvenTreeSetting.get_default_value(key)
|
||||
)
|
||||
except OperationalError:
|
||||
# Settings table has not been created yet
|
||||
setting = None
|
||||
# Wrap this statement in "atomic", so it can be rolled back if it fails
|
||||
with transaction.atomic():
|
||||
setting.save()
|
||||
except (IntegrityError, OperationalError):
|
||||
# It might be the case that the database isn't created yet
|
||||
pass
|
||||
|
||||
return setting
|
||||
|
||||
@ -322,7 +325,7 @@ class InvenTreeSetting(models.Model):
|
||||
|
||||
# If no backup value is specified, atttempt to retrieve a "default" value
|
||||
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)
|
||||
|
||||
@ -380,7 +383,7 @@ class InvenTreeSetting(models.Model):
|
||||
|
||||
@property
|
||||
def default_value(self):
|
||||
return InvenTreeSetting.get_default_value(self.key)
|
||||
return InvenTreeSetting.get_setting_default(self.key)
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
@ -403,6 +406,9 @@ class InvenTreeSetting(models.Model):
|
||||
if validator is not None:
|
||||
self.run_validator(validator)
|
||||
|
||||
if self.is_bool():
|
||||
self.value = InvenTree.helpers.str2bool(self.value)
|
||||
|
||||
def run_validator(self, validator):
|
||||
"""
|
||||
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():
|
||||
|
||||
value = InvenTreeSetting.get_default_value(key)
|
||||
value = InvenTreeSetting.get_setting_default(key)
|
||||
|
||||
InvenTreeSetting.set_setting(key, value, self.user)
|
||||
|
||||
|
@ -72,3 +72,32 @@ class SettingEdit(AjaxUpdateView):
|
||||
form.fields['value'].help_text = description
|
||||
|
||||
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
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.db.utils import OperationalError, ProgrammingError
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CompanyConfig(AppConfig):
|
||||
name = 'company'
|
||||
|
||||
@ -21,7 +25,7 @@ class CompanyConfig(AppConfig):
|
||||
|
||||
from .models import Company
|
||||
|
||||
print("InvenTree: Checking Company image thumbnails")
|
||||
logger.debug("Checking Company image thumbnails")
|
||||
|
||||
try:
|
||||
for company in Company.objects.all():
|
||||
@ -30,11 +34,11 @@ class CompanyConfig(AppConfig):
|
||||
loc = os.path.join(settings.MEDIA_ROOT, url)
|
||||
|
||||
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:
|
||||
company.image.render_variations(replace=False)
|
||||
except FileNotFoundError:
|
||||
print("Image file missing")
|
||||
logger.warning("Image file missing")
|
||||
company.image = None
|
||||
company.save()
|
||||
except (OperationalError, ProgrammingError):
|
||||
|
@ -1,22 +1,41 @@
|
||||
|
||||
# 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
|
||||
|
||||
# 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:
|
||||
# Example configuration - sqlite (default)
|
||||
ENGINE: django.db.backends.sqlite3
|
||||
# Default configuration - sqlite filesystem database
|
||||
ENGINE: sqlite3
|
||||
NAME: '../inventree_default_db.sqlite3'
|
||||
|
||||
# For more complex database installations, further parameters are required
|
||||
# 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
|
||||
#NAME: inventree
|
||||
#USER: inventree_username
|
||||
#PASSWORD: inventree_password
|
||||
#HOST: ''
|
||||
#PORT: ''
|
||||
#HOST: '127.0.0.1'
|
||||
#PORT: '5432'
|
||||
|
||||
# Select default system language (default is '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)
|
||||
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)
|
||||
# A list of strings representing the host/domain names that this Django site can serve.
|
||||
|
@ -1,12 +1,16 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from django.db.utils import OperationalError, ProgrammingError
|
||||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PartConfig(AppConfig):
|
||||
name = 'part'
|
||||
|
||||
@ -27,7 +31,7 @@ class PartConfig(AppConfig):
|
||||
|
||||
from .models import Part
|
||||
|
||||
print("InvenTree: Checking Part image thumbnails")
|
||||
logger.debug("InvenTree: Checking Part image thumbnails")
|
||||
|
||||
try:
|
||||
for part in Part.objects.all():
|
||||
@ -36,11 +40,11 @@ class PartConfig(AppConfig):
|
||||
loc = os.path.join(settings.MEDIA_ROOT, url)
|
||||
|
||||
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:
|
||||
part.image.render_variations(replace=False)
|
||||
except FileNotFoundError:
|
||||
print("Image file missing")
|
||||
logger.warning("Image file missing")
|
||||
part.image = None
|
||||
part.save()
|
||||
except (OperationalError, ProgrammingError):
|
||||
|
@ -213,6 +213,7 @@ class EditPartForm(HelperForm):
|
||||
class Meta:
|
||||
model = Part
|
||||
fields = [
|
||||
'confirm_creation',
|
||||
'category',
|
||||
'selected_category_templates',
|
||||
'parent_category_templates',
|
||||
@ -222,7 +223,6 @@ class EditPartForm(HelperForm):
|
||||
'revision',
|
||||
'bom_copy',
|
||||
'parameters_copy',
|
||||
'confirm_creation',
|
||||
'keywords',
|
||||
'variant_of',
|
||||
'link',
|
||||
|
@ -350,7 +350,7 @@ class Part(MPTTModel):
|
||||
# Get part category
|
||||
category = self.category
|
||||
|
||||
if add_category_templates:
|
||||
if category and add_category_templates:
|
||||
# Store templates added to part
|
||||
template_list = []
|
||||
|
||||
|
@ -1320,8 +1320,6 @@ class BomUpload(InvenTreeRoleMixin, FormView):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
print(row, row['part_match'], len(row['part_options']))
|
||||
|
||||
def extractDataFromFile(self, bom):
|
||||
""" Read data from the BOM file """
|
||||
|
||||
|
@ -1,8 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
import plugins.plugin as plugin
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ActionPlugin(plugin.InvenTreePlugin):
|
||||
"""
|
||||
The ActionPlugin class is used to perform custom actions
|
||||
|
@ -3,12 +3,16 @@
|
||||
import inspect
|
||||
import importlib
|
||||
import pkgutil
|
||||
import logging
|
||||
|
||||
# Action plugins
|
||||
import plugins.action as action
|
||||
from plugins.action.action import ActionPlugin
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def iter_namespace(pkg):
|
||||
|
||||
return pkgutil.iter_modules(pkg.__path__, pkg.__name__ + ".")
|
||||
@ -52,14 +56,14 @@ def load_action_plugins():
|
||||
Return a list of all registered action plugins
|
||||
"""
|
||||
|
||||
print("Loading action plugins")
|
||||
logger.debug("Loading action plugins")
|
||||
|
||||
plugins = get_plugins(action, ActionPlugin)
|
||||
|
||||
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:
|
||||
print(" - {ap}".format(ap=ap.PLUGIN_NAME))
|
||||
logger.debug(" - {ap}".format(ap=ap.PLUGIN_NAME))
|
||||
|
||||
return plugins
|
||||
|
Loading…
Reference in New Issue
Block a user