diff --git a/InvenTree/InvenTree/api_version.py b/InvenTree/InvenTree/api_version.py index 7851efd8dd..1ef1df6a8f 100644 --- a/InvenTree/InvenTree/api_version.py +++ b/InvenTree/InvenTree/api_version.py @@ -4,11 +4,21 @@ InvenTree API version information # InvenTree API version -INVENTREE_API_VERSION = 40 +INVENTREE_API_VERSION = 43 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v43 -> 2022-04-26 : https://github.com/inventree/InvenTree/pull/2875 + - Adds API detail endpoint for PartSalePrice model + - Adds API detail endpoint for PartInternalPrice model + +v42 -> 2022-04-26 : https://github.com/inventree/InvenTree/pull/2833 + - Adds variant stock information to the Part and BomItem serializers + +v41 -> 2022-04-26 + - Fixes 'variant_of' filter for Part list endpoint + v40 -> 2022-04-19 - Adds ability to filter StockItem list by "tracked" parameter - This checks the serial number or batch code fields diff --git a/InvenTree/InvenTree/middleware.py b/InvenTree/InvenTree/middleware.py index a9c43c71b6..b43720b8bc 100644 --- a/InvenTree/InvenTree/middleware.py +++ b/InvenTree/InvenTree/middleware.py @@ -1,11 +1,11 @@ from django.shortcuts import HttpResponseRedirect from django.urls import reverse_lazy, Resolver404 -from django.db import connection from django.shortcuts import redirect from django.conf.urls import include, url +from django.conf import settings +from django.contrib.auth.middleware import PersistentRemoteUserMiddleware + import logging -import time -import operator from rest_framework.authtoken.models import Token from allauth_2fa.middleware import BaseRequire2FAMiddleware, AllauthTwoFactorMiddleware @@ -92,67 +92,6 @@ class AuthRequiredMiddleware(object): return response -class QueryCountMiddleware(object): - """ - This middleware will log the number of queries run - and the total time taken for each request (with a - status code of 200). It does not currently support - multi-db setups. - - To enable this middleware, set 'log_queries: True' in the local InvenTree config file. - - Reference: https://www.dabapps.com/blog/logging-sql-queries-django-13/ - - Note: 2020-08-15 - This is no longer used, instead we now rely on the django-debug-toolbar addon - """ - - def __init__(self, get_response): - self.get_response = get_response - - def __call__(self, request): - - t_start = time.time() - response = self.get_response(request) - t_stop = time.time() - - if response.status_code == 200: - total_time = 0 - - if len(connection.queries) > 0: - - queries = {} - - for query in connection.queries: - query_time = query.get('time') - - sql = query.get('sql').split('.')[0] - - if sql in queries: - queries[sql] += 1 - else: - queries[sql] = 1 - - if query_time is None: - # django-debug-toolbar monkeypatches the connection - # cursor wrapper and adds extra information in each - # item in connection.queries. The query time is stored - # under the key "duration" rather than "time" and is - # in milliseconds, not seconds. - query_time = float(query.get('duration', 0)) - - total_time += float(query_time) - - logger.debug('{n} queries run, {a:.3f}s / {b:.3f}s'.format( - n=len(connection.queries), - a=total_time, - b=(t_stop - t_start))) - - for x in sorted(queries.items(), key=operator.itemgetter(1), reverse=True): - print(x[0], ':', x[1]) - - return response - - url_matcher = url('', include(frontendpatterns)) @@ -176,3 +115,16 @@ class CustomAllauthTwoFactorMiddleware(AllauthTwoFactorMiddleware): super().process_request(request) except Resolver404: pass + + +class InvenTreeRemoteUserMiddleware(PersistentRemoteUserMiddleware): + """ + Middleware to check if HTTP-header based auth is enabled and to set it up + """ + header = settings.REMOTE_LOGIN_HEADER + + def process_request(self, request): + if not settings.REMOTE_LOGIN: + return + + return super().process_request(request) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index e1c584362f..8b43e2191c 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -25,6 +25,7 @@ import moneyed import yaml from django.utils.translation import gettext_lazy as _ from django.contrib.messages import constants as messages +from django.core.files.storage import default_storage import django.conf.locale from .config import get_base_dir, get_config_file, get_plugin_file, get_setting @@ -289,6 +290,7 @@ MIDDLEWARE = CONFIG.get('middleware', [ 'django.middleware.csrf.CsrfViewMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'InvenTree.middleware.InvenTreeRemoteUserMiddleware', # Remote / proxy auth 'django_otp.middleware.OTPMiddleware', # MFA support 'InvenTree.middleware.CustomAllauthTwoFactorMiddleware', # Flow control for allauth 'django.contrib.messages.middleware.MessageMiddleware', @@ -302,6 +304,7 @@ MIDDLEWARE = CONFIG.get('middleware', [ MIDDLEWARE.append('error_report.middleware.ExceptionProcessor') AUTHENTICATION_BACKENDS = CONFIG.get('authentication_backends', [ + 'django.contrib.auth.backends.RemoteUserBackend', # proxy login 'django.contrib.auth.backends.ModelBackend', 'allauth.account.auth_backends.AuthenticationBackend', # SSO login via external providers ]) @@ -687,7 +690,8 @@ LANGUAGES = [ ('nl', _('Dutch')), ('no', _('Norwegian')), ('pl', _('Polish')), - ('pt', _('Portugese')), + ('pt', _('Portuguese')), + ('pt-BR', _('Portuguese (Brazilian)')), ('ru', _('Russian')), ('sv', _('Swedish')), ('th', _('Thai')), @@ -853,6 +857,10 @@ ACCOUNT_FORMS = { SOCIALACCOUNT_ADAPTER = 'InvenTree.forms.CustomSocialAccountAdapter' ACCOUNT_ADAPTER = 'InvenTree.forms.CustomAccountAdapter' +# login settings +REMOTE_LOGIN = get_setting('INVENTREE_REMOTE_LOGIN', CONFIG.get('remote_login', False)) +REMOTE_LOGIN_HEADER = get_setting('INVENTREE_REMOTE_LOGIN_HEADER', CONFIG.get('remote_login_header', 'REMOTE_USER')) + # Markdownx configuration # Ref: https://neutronx.github.io/django-markdownx/customization/ MARKDOWNX_MEDIA_PATH = datetime.now().strftime('markdownx/%Y/%m/%d') @@ -912,3 +920,20 @@ PLUGIN_TESTING = get_setting('PLUGIN_TESTING', TESTING) # are plugins beeing te PLUGIN_TESTING_SETUP = get_setting('PLUGIN_TESTING_SETUP', False) # load plugins from setup hooks in testing? PLUGIN_RETRY = get_setting('PLUGIN_RETRY', 5) # how often should plugin loading be tried? PLUGIN_FILE_CHECKED = False # Was the plugin file checked? + +# user interface customization values +CUSTOMIZE = get_setting( + 'INVENTREE_CUSTOMIZE', + CONFIG.get('customize', {}), + {} +) + +CUSTOM_LOGO = get_setting( + 'INVENTREE_CUSTOM_LOGO', + CUSTOMIZE.get('logo', False) +) + +# check that the logo-file exsists in media +if CUSTOM_LOGO and not default_storage.exists(CUSTOM_LOGO): + CUSTOM_LOGO = False + logger.warning("The custom logo file could not be found in the default media storage") diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 9ba348b0b4..e5189e6073 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -871,6 +871,9 @@ class Build(MPTTModel, ReferenceIndexingMixin): part__in=[p for p in available_parts], ) + # Filter out "serialized" stock items, these cannot be auto-allocated + available_stock = available_stock.filter(Q(serial=None) | Q(serial='')) + if location: # Filter only stock items located "below" the specified location sublocations = location.get_descendants(include_self=True) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 1c1060a3b2..fe4965410e 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -771,7 +771,7 @@ class InvenTreeSetting(BaseInvenTreeSetting): }, 'INVENTREE_INSTANCE': { - 'name': _('InvenTree Instance Name'), + 'name': _('Server Instance Name'), 'default': 'InvenTree server', 'description': _('String descriptor for the server instance'), }, @@ -783,6 +783,13 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'default': False, }, + 'INVENTREE_RESTRICT_ABOUT': { + 'name': _('Restrict showing `about`'), + 'description': _('Show the `about` modal only to superusers'), + 'validator': bool, + 'default': False, + }, + 'INVENTREE_COMPANY_NAME': { 'name': _('Company name'), 'description': _('Internal company name'), @@ -1019,6 +1026,12 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'validator': bool, }, + 'STOCK_BATCH_CODE_TEMPLATE': { + 'name': _('Batch Code Template'), + 'description': _('Template for generating default batch codes for stock items'), + 'default': '', + }, + 'STOCK_ENABLE_EXPIRY': { 'name': _('Stock Expiry'), 'description': _('Enable stock expiry functionality'), @@ -1426,7 +1439,7 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): 'STICKY_HEADER': { 'name': _('Fixed Navbar'), - 'description': _('InvenTree navbar position is fixed to the top of the screen'), + 'description': _('The navbar position is fixed to the top of the screen'), 'default': False, 'validator': bool, }, diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 77a4f1a12d..c3ce4f9e51 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -63,8 +63,8 @@ class SettingsTest(TestCase): report_test_obj = InvenTreeSetting.get_setting_object('REPORT_ENABLE_TEST_REPORT') # check settings base fields - self.assertEqual(instance_obj.name, 'InvenTree Instance Name') - self.assertEqual(instance_obj.get_setting_name(instance_ref), 'InvenTree Instance Name') + self.assertEqual(instance_obj.name, 'Server Instance Name') + self.assertEqual(instance_obj.get_setting_name(instance_ref), 'Server Instance Name') self.assertEqual(instance_obj.description, 'String descriptor for the server instance') self.assertEqual(instance_obj.get_setting_description(instance_ref), 'String descriptor for the server instance') diff --git a/InvenTree/company/templates/company/manufacturer_part.html b/InvenTree/company/templates/company/manufacturer_part.html index 84e8016a59..fb33128a77 100644 --- a/InvenTree/company/templates/company/manufacturer_part.html +++ b/InvenTree/company/templates/company/manufacturer_part.html @@ -1,9 +1,10 @@ {% extends "page_base.html" %} {% load static %} {% load i18n %} +{% load inventree_extras %} {% block page_title %} -InvenTree | {% trans "Manufacturer Part" %} +{% inventree_title %} | {% trans "Manufacturer Part" %} {% endblock %} {% block sidebar %} diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml index 65dd20d3e8..a9a1db9ad8 100644 --- a/InvenTree/config_template.yaml +++ b/InvenTree/config_template.yaml @@ -154,6 +154,14 @@ static_root: '/home/inventree/data/static' # Use environment variable INVENTREE_LOGIN_ATTEMPTS #login_attempts: 5 +# Remote / proxy login +# These settings can introduce security problems if configured incorrectly. Please read +# https://docs.djangoproject.com/en/4.0/howto/auth-remote-user/ for more details +# Use environment variable INVENTREE_REMOTE_LOGIN +# remote_login: True +# Use environment variable INVENTREE_REMOTE_LOGIN_HEADER +# remote_login_header: REMOTE_USER + # Add new user on first startup #admin_user: admin #admin_email: info@example.com @@ -186,3 +194,10 @@ static_root: '/home/inventree/data/static' # KEYCLOAK_URL: 'https://keycloak.custom/auth' # KEYCLOAK_REALM: 'master' +# Customization options +# Add custom messages to the login page or main interface navbar or exchange the logo +# Use environment variable INVENTREE_CUSTOMIZE or INVENTREE_CUSTOM_LOGO +# customize: +# login_message: InvenTree demo instance - Click here for login details +# navbar_message: