From 20b6e0fd1a398f8f348f6d768d833604d51a6609 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 28 Jul 2021 15:46:52 +1000 Subject: [PATCH 01/20] Update version.py --- InvenTree/InvenTree/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 88acbaff54..adff6da0fb 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -8,7 +8,7 @@ import re import common.models -INVENTREE_SW_VERSION = "0.4.0" +INVENTREE_SW_VERSION = "0.5.0 pre" INVENTREE_API_VERSION = 8 From 399e44fce7dea1069c4a971f06df61325045c57d Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 28 Jul 2021 22:30:41 +1000 Subject: [PATCH 02/20] Copy static files when starting dev server --- docker/start_dev_server.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/start_dev_server.sh b/docker/start_dev_server.sh index fcd178915c..a12a958a9a 100644 --- a/docker/start_dev_server.sh +++ b/docker/start_dev_server.sh @@ -45,5 +45,7 @@ python3 manage.py migrate --noinput || exit 1 python3 manage.py migrate --run-syncdb || exit 1 python3 manage.py clearsessions || exit 1 +invoke static + # Launch a development server python3 manage.py runserver ${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT} From 5744796506a988fb9ae4c2f9dce9930e96d11f54 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 28 Jul 2021 23:32:49 +1000 Subject: [PATCH 03/20] Adds an API filter class for the ManufacturerPart list endpoint --- InvenTree/company/api.py | 45 ++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index 76b08ec27e..1d754900a5 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -6,6 +6,8 @@ Provides a JSON API for the Company app from __future__ import unicode_literals from django_filters.rest_framework import DjangoFilterBackend +from django_filters import rest_framework as rest_filters + from rest_framework import filters from rest_framework import generics @@ -84,6 +86,15 @@ class CompanyDetail(generics.RetrieveUpdateDestroyAPIView): return queryset +class ManufacturerPartFilter(rest_filters.FilterSet): + """ + Custom API filters for the ManufacturerPart list endpoint. + """ + + # Filter by 'active' status of linked part + active = rest_filters.BooleanFilter(field_name='part__active') + + class ManufacturerPartList(generics.ListCreateAPIView): """ API endpoint for list view of ManufacturerPart object @@ -98,6 +109,7 @@ class ManufacturerPartList(generics.ListCreateAPIView): ) serializer_class = ManufacturerPartSerializer + filterset_class = ManufacturerPartFilter def get_serializer(self, *args, **kwargs): @@ -115,36 +127,6 @@ class ManufacturerPartList(generics.ListCreateAPIView): return self.serializer_class(*args, **kwargs) - def filter_queryset(self, queryset): - """ - Custom filtering for the queryset. - """ - - queryset = super().filter_queryset(queryset) - - params = self.request.query_params - - # Filter by manufacturer - manufacturer = params.get('manufacturer', None) - - if manufacturer is not None: - queryset = queryset.filter(manufacturer=manufacturer) - - # Filter by parent part? - part = params.get('part', None) - - if part is not None: - queryset = queryset.filter(part=part) - - # Filter by 'active' status of the part? - active = params.get('active', None) - - if active is not None: - active = str2bool(active) - queryset = queryset.filter(part__active=active) - - return queryset - filter_backends = [ DjangoFilterBackend, filters.SearchFilter, @@ -152,6 +134,9 @@ class ManufacturerPartList(generics.ListCreateAPIView): ] filter_fields = [ + 'MPN', + 'manufacturer', + 'part', ] search_fields = [ From baa6283d20b9741125001eace0c0218842e38f48 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 28 Jul 2021 23:47:50 +1000 Subject: [PATCH 04/20] Fixes --- InvenTree/company/api.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index 1d754900a5..6c51d97d66 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -91,6 +91,14 @@ class ManufacturerPartFilter(rest_filters.FilterSet): Custom API filters for the ManufacturerPart list endpoint. """ + class Meta: + model = ManufacturerPart + fields = [ + 'manufacturer', + 'MPN', + 'part', + ] + # Filter by 'active' status of linked part active = rest_filters.BooleanFilter(field_name='part__active') @@ -133,12 +141,6 @@ class ManufacturerPartList(generics.ListCreateAPIView): filters.OrderingFilter, ] - filter_fields = [ - 'MPN', - 'manufacturer', - 'part', - ] - search_fields = [ 'manufacturer__name', 'description', From d43312d1627c3b9f8539ff472c7cfbfeb66dbe79 Mon Sep 17 00:00:00 2001 From: eeintech Date: Wed, 28 Jul 2021 13:29:12 -0400 Subject: [PATCH 05/20] Missing comma propagating to translated JS files --- InvenTree/templates/js/inventree.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/templates/js/inventree.js b/InvenTree/templates/js/inventree.js index aa574eae83..aa87008fbe 100644 --- a/InvenTree/templates/js/inventree.js +++ b/InvenTree/templates/js/inventree.js @@ -94,7 +94,7 @@ function inventreeDocReady() { {% if request.user %} limit: {% settings_value 'SEARCH_PREVIEW_RESULTS' user=request.user %}, {% else %} - limit: 25 + limit: 25, {% endif %} offset: 0 }, From bc3c3be751c263f2bfdc3aa90d2d085a815e7781 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 09:10:46 +1000 Subject: [PATCH 06/20] force linux-style line endings for .sh files --- .gitattributes | 2 +- docker/start_prod_server.sh | 2 +- docker/start_prod_worker.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitattributes b/.gitattributes index 6ab0760ad1..bfbc22d191 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,5 +7,5 @@ *.yml text *.yaml text *.conf text -*.sh text +*.sh text eol=lf *.js text \ No newline at end of file diff --git a/docker/start_prod_server.sh b/docker/start_prod_server.sh index 1bba2c9ed3..1660a64e60 100644 --- a/docker/start_prod_server.sh +++ b/docker/start_prod_server.sh @@ -39,4 +39,4 @@ python3 manage.py collectstatic --noinput || exit 1 python3 manage.py clearsessions || exit 1 # Now we can launch the server -gunicorn -c $INVENTREE_HOME/gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:$INVENTREE_WEB_PORT \ No newline at end of file +gunicorn -c $INVENTREE_HOME/gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:$INVENTREE_WEB_PORT diff --git a/docker/start_prod_worker.sh b/docker/start_prod_worker.sh index 4a13d71230..d0762b430e 100644 --- a/docker/start_prod_worker.sh +++ b/docker/start_prod_worker.sh @@ -11,4 +11,4 @@ python3 manage.py wait_for_db sleep 10 # Now we can launch the background worker process -python3 manage.py qcluster \ No newline at end of file +python3 manage.py qcluster From 14aebfdae1eaa6d21128cb8afe03d7fa076fef7f Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 09:23:24 +1000 Subject: [PATCH 07/20] Split dynamic javascript files into two separate directories - One gets translated and is served statically - One does not get translated and is served dynamically - Add CI step --- .github/workflows/javascript.yaml | 28 ++++ InvenTree/InvenTree/settings.py | 2 +- InvenTree/InvenTree/urls.py | 49 +++---- .../templates/js/{ => dynamic}/inventree.js | 0 .../templates/js/{ => translated}/api.js | 0 .../js/{ => translated}/attachment.js | 0 .../templates/js/{ => translated}/barcode.js | 0 .../templates/js/{ => translated}/bom.js | 0 .../templates/js/{ => translated}/build.js | 0 .../templates/js/{ => translated}/calendar.js | 0 .../templates/js/{ => translated}/company.js | 0 .../templates/js/{ => translated}/filters.js | 0 .../templates/js/{ => translated}/forms.js | 0 .../templates/js/{ => translated}/label.js | 0 .../templates/js/{ => translated}/modals.js | 0 .../js/{ => translated}/model_renderers.js | 0 .../templates/js/{ => translated}/nav.js | 0 .../templates/js/{ => translated}/order.js | 0 .../templates/js/{ => translated}/part.js | 0 .../templates/js/{ => translated}/report.js | 0 .../templates/js/{ => translated}/stock.js | 0 .../js/{ => translated}/table_filters.js | 0 .../templates/js/{ => translated}/tables.js | 0 ci/check_js_templates.py | 121 ++++++++++++++++++ 24 files changed, 177 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/javascript.yaml rename InvenTree/templates/js/{ => dynamic}/inventree.js (100%) rename InvenTree/templates/js/{ => translated}/api.js (100%) rename InvenTree/templates/js/{ => translated}/attachment.js (100%) rename InvenTree/templates/js/{ => translated}/barcode.js (100%) rename InvenTree/templates/js/{ => translated}/bom.js (100%) rename InvenTree/templates/js/{ => translated}/build.js (100%) rename InvenTree/templates/js/{ => translated}/calendar.js (100%) rename InvenTree/templates/js/{ => translated}/company.js (100%) rename InvenTree/templates/js/{ => translated}/filters.js (100%) rename InvenTree/templates/js/{ => translated}/forms.js (100%) rename InvenTree/templates/js/{ => translated}/label.js (100%) rename InvenTree/templates/js/{ => translated}/modals.js (100%) rename InvenTree/templates/js/{ => translated}/model_renderers.js (100%) rename InvenTree/templates/js/{ => translated}/nav.js (100%) rename InvenTree/templates/js/{ => translated}/order.js (100%) rename InvenTree/templates/js/{ => translated}/part.js (100%) rename InvenTree/templates/js/{ => translated}/report.js (100%) rename InvenTree/templates/js/{ => translated}/stock.js (100%) rename InvenTree/templates/js/{ => translated}/table_filters.js (100%) rename InvenTree/templates/js/{ => translated}/tables.js (100%) create mode 100644 ci/check_js_templates.py diff --git a/.github/workflows/javascript.yaml b/.github/workflows/javascript.yaml new file mode 100644 index 0000000000..d5e5fca18f --- /dev/null +++ b/.github/workflows/javascript.yaml @@ -0,0 +1,28 @@ +# Check javascript template files + +name: Javascript Templates + +on: + push: + branches: + - master + + pull_request: + branches-ignore: + - l10* + +jobs: + + python: + runs-on: ubuntu-latest + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Checkout Code + uses: actions/checkout@v2 + - name: Check Files + run: | + cd ci + python check_js_templates.py + \ No newline at end of file diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index fe22111232..f9ecaa394d 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -202,7 +202,7 @@ STATICFILES_DIRS = [ # Translated Template settings STATICFILES_I18_PREFIX = 'i18n' -STATICFILES_I18_SRC = os.path.join(BASE_DIR, 'templates', 'js') +STATICFILES_I18_SRC = os.path.join(BASE_DIR, 'templates', 'js', 'translated') STATICFILES_I18_TRG = STATICFILES_DIRS[0] + '_' + STATICFILES_I18_PREFIX STATICFILES_DIRS.append(STATICFILES_I18_TRG) STATICFILES_I18_TRG = os.path.join(STATICFILES_I18_TRG, STATICFILES_I18_PREFIX) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 41ef306a50..7d398c6e4f 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -93,28 +93,32 @@ settings_urls = [ url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/settings.html'), name='settings'), ] -# Some javascript files are served 'dynamically', allowing them to pass through the Django translation layer +# These javascript files are served "dynamically" - i.e. rendered on demand dynamic_javascript_urls = [ - url(r'^api.js', DynamicJsView.as_view(template_name='js/api.js'), name='api.js'), - url(r'^attachment.js', DynamicJsView.as_view(template_name='js/attachment.js'), name='attachment.js'), - url(r'^barcode.js', DynamicJsView.as_view(template_name='js/barcode.js'), name='barcode.js'), - url(r'^bom.js', DynamicJsView.as_view(template_name='js/bom.js'), name='bom.js'), - url(r'^build.js', DynamicJsView.as_view(template_name='js/build.js'), name='build.js'), - url(r'^calendar.js', DynamicJsView.as_view(template_name='js/calendar.js'), name='calendar.js'), - url(r'^company.js', DynamicJsView.as_view(template_name='js/company.js'), name='company.js'), - url(r'^filters.js', DynamicJsView.as_view(template_name='js/filters.js'), name='filters.js'), - url(r'^forms.js', DynamicJsView.as_view(template_name='js/forms.js'), name='forms.js'), - url(r'^inventree.js', DynamicJsView.as_view(template_name='js/inventree.js'), name='inventree.js'), - url(r'^label.js', DynamicJsView.as_view(template_name='js/label.js'), name='label.js'), - url(r'^model_renderers.js', DynamicJsView.as_view(template_name='js/model_renderers.js'), name='model_renderers.js'), - url(r'^modals.js', DynamicJsView.as_view(template_name='js/modals.js'), name='modals.js'), - url(r'^nav.js', DynamicJsView.as_view(template_name='js/nav.js'), name='nav.js'), - url(r'^order.js', DynamicJsView.as_view(template_name='js/order.js'), name='order.js'), - url(r'^part.js', DynamicJsView.as_view(template_name='js/part.js'), name='part.js'), - url(r'^report.js', DynamicJsView.as_view(template_name='js/report.js'), name='report.js'), - url(r'^stock.js', DynamicJsView.as_view(template_name='js/stock.js'), name='stock.js'), - url(r'^tables.js', DynamicJsView.as_view(template_name='js/tables.js'), name='tables.js'), - url(r'^table_filters.js', DynamicJsView.as_view(template_name='js/table_filters.js'), name='table_filters.js'), + url(r'^inventree.js', DynamicJsView.as_view(template_name='js/dynamic/inventree.js'), name='inventree.js'), +] + +# These javascript files are pased through the Django translation layer +translated_javascript_urls = [ + url(r'^api.js', DynamicJsView.as_view(template_name='js/translated/api.js'), name='api.js'), + url(r'^attachment.js', DynamicJsView.as_view(template_name='js/translated/attachment.js'), name='attachment.js'), + url(r'^barcode.js', DynamicJsView.as_view(template_name='js/translated/barcode.js'), name='barcode.js'), + url(r'^bom.js', DynamicJsView.as_view(template_name='js/translated/bom.js'), name='bom.js'), + url(r'^build.js', DynamicJsView.as_view(template_name='js/translated/build.js'), name='build.js'), + url(r'^calendar.js', DynamicJsView.as_view(template_name='js/translated/calendar.js'), name='calendar.js'), + url(r'^company.js', DynamicJsView.as_view(template_name='js/translated/company.js'), name='company.js'), + url(r'^filters.js', DynamicJsView.as_view(template_name='js/translated/filters.js'), name='filters.js'), + url(r'^forms.js', DynamicJsView.as_view(template_name='js/translated/forms.js'), name='forms.js'), + url(r'^label.js', DynamicJsView.as_view(template_name='js/translated/label.js'), name='label.js'), + url(r'^model_renderers.js', DynamicJsView.as_view(template_name='js/translated/model_renderers.js'), name='model_renderers.js'), + url(r'^modals.js', DynamicJsView.as_view(template_name='js/translated/modals.js'), name='modals.js'), + url(r'^nav.js', DynamicJsView.as_view(template_name='js/translated/nav.js'), name='nav.js'), + url(r'^order.js', DynamicJsView.as_view(template_name='js/translated/order.js'), name='order.js'), + url(r'^part.js', DynamicJsView.as_view(template_name='js/translated/part.js'), name='part.js'), + url(r'^report.js', DynamicJsView.as_view(template_name='js/translated/report.js'), name='report.js'), + url(r'^stock.js', DynamicJsView.as_view(template_name='js/translated/stock.js'), name='stock.js'), + url(r'^tables.js', DynamicJsView.as_view(template_name='js/translated/tables.js'), name='tables.js'), + url(r'^table_filters.js', DynamicJsView.as_view(template_name='js/translated/table_filters.js'), name='table_filters.js'), ] urlpatterns = [ @@ -123,7 +127,8 @@ urlpatterns = [ url(r'^supplier-part/', include(supplier_part_urls)), # "Dynamic" javascript files which are rendered using InvenTree templating. - url(r'^dynamic/', include(dynamic_javascript_urls)), + url(r'^js/dynamic/', include(dynamic_javascript_urls)), + url(r'^js/i18n/', include(translated_javascript_urls)), url(r'^common/', include(common_urls)), diff --git a/InvenTree/templates/js/inventree.js b/InvenTree/templates/js/dynamic/inventree.js similarity index 100% rename from InvenTree/templates/js/inventree.js rename to InvenTree/templates/js/dynamic/inventree.js diff --git a/InvenTree/templates/js/api.js b/InvenTree/templates/js/translated/api.js similarity index 100% rename from InvenTree/templates/js/api.js rename to InvenTree/templates/js/translated/api.js diff --git a/InvenTree/templates/js/attachment.js b/InvenTree/templates/js/translated/attachment.js similarity index 100% rename from InvenTree/templates/js/attachment.js rename to InvenTree/templates/js/translated/attachment.js diff --git a/InvenTree/templates/js/barcode.js b/InvenTree/templates/js/translated/barcode.js similarity index 100% rename from InvenTree/templates/js/barcode.js rename to InvenTree/templates/js/translated/barcode.js diff --git a/InvenTree/templates/js/bom.js b/InvenTree/templates/js/translated/bom.js similarity index 100% rename from InvenTree/templates/js/bom.js rename to InvenTree/templates/js/translated/bom.js diff --git a/InvenTree/templates/js/build.js b/InvenTree/templates/js/translated/build.js similarity index 100% rename from InvenTree/templates/js/build.js rename to InvenTree/templates/js/translated/build.js diff --git a/InvenTree/templates/js/calendar.js b/InvenTree/templates/js/translated/calendar.js similarity index 100% rename from InvenTree/templates/js/calendar.js rename to InvenTree/templates/js/translated/calendar.js diff --git a/InvenTree/templates/js/company.js b/InvenTree/templates/js/translated/company.js similarity index 100% rename from InvenTree/templates/js/company.js rename to InvenTree/templates/js/translated/company.js diff --git a/InvenTree/templates/js/filters.js b/InvenTree/templates/js/translated/filters.js similarity index 100% rename from InvenTree/templates/js/filters.js rename to InvenTree/templates/js/translated/filters.js diff --git a/InvenTree/templates/js/forms.js b/InvenTree/templates/js/translated/forms.js similarity index 100% rename from InvenTree/templates/js/forms.js rename to InvenTree/templates/js/translated/forms.js diff --git a/InvenTree/templates/js/label.js b/InvenTree/templates/js/translated/label.js similarity index 100% rename from InvenTree/templates/js/label.js rename to InvenTree/templates/js/translated/label.js diff --git a/InvenTree/templates/js/modals.js b/InvenTree/templates/js/translated/modals.js similarity index 100% rename from InvenTree/templates/js/modals.js rename to InvenTree/templates/js/translated/modals.js diff --git a/InvenTree/templates/js/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js similarity index 100% rename from InvenTree/templates/js/model_renderers.js rename to InvenTree/templates/js/translated/model_renderers.js diff --git a/InvenTree/templates/js/nav.js b/InvenTree/templates/js/translated/nav.js similarity index 100% rename from InvenTree/templates/js/nav.js rename to InvenTree/templates/js/translated/nav.js diff --git a/InvenTree/templates/js/order.js b/InvenTree/templates/js/translated/order.js similarity index 100% rename from InvenTree/templates/js/order.js rename to InvenTree/templates/js/translated/order.js diff --git a/InvenTree/templates/js/part.js b/InvenTree/templates/js/translated/part.js similarity index 100% rename from InvenTree/templates/js/part.js rename to InvenTree/templates/js/translated/part.js diff --git a/InvenTree/templates/js/report.js b/InvenTree/templates/js/translated/report.js similarity index 100% rename from InvenTree/templates/js/report.js rename to InvenTree/templates/js/translated/report.js diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/translated/stock.js similarity index 100% rename from InvenTree/templates/js/stock.js rename to InvenTree/templates/js/translated/stock.js diff --git a/InvenTree/templates/js/table_filters.js b/InvenTree/templates/js/translated/table_filters.js similarity index 100% rename from InvenTree/templates/js/table_filters.js rename to InvenTree/templates/js/translated/table_filters.js diff --git a/InvenTree/templates/js/tables.js b/InvenTree/templates/js/translated/tables.js similarity index 100% rename from InvenTree/templates/js/tables.js rename to InvenTree/templates/js/translated/tables.js diff --git a/ci/check_js_templates.py b/ci/check_js_templates.py new file mode 100644 index 0000000000..aa40b88742 --- /dev/null +++ b/ci/check_js_templates.py @@ -0,0 +1,121 @@ +""" +Test that the "translated" javascript files to not contain template tags +which need to be determined at "run time". + +This is because the "translated" javascript files are compiled into the "static" directory. + +They should only contain template tags that render static information. +""" + +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import sys +import re +import os +import pathlib + +here = os.path.abspath(os.path.dirname(__file__)) +template_dir = os.path.abspath(os.path.join(here, '..', 'InvenTree', 'templates')) + +# We only care about the 'translated' files +js_i18n_dir = os.path.join(template_dir, 'js', 'translated') +js_dynamic_dir = os.path.join(template_dir, 'js', 'dynamic') + +errors = 0 + +print("=================================") +print("Checking static javascript files:") +print("=================================") + +def check_invalid_tag(data): + + pattern = r"{%(\w+)" + + err_count = 0 + + for idx, line in enumerate(data): + + results = re.findall(pattern, line) + + for result in results: + err_count += 1 + + print(f" - Error on line {idx+1}: %{{{result[0]}") + + return err_count + +def check_prohibited_tags(data): + + allowed_tags = [ + 'if', + 'elif', + 'else', + 'endif', + 'for', + 'endfor', + 'trans', + 'load', + 'include', + 'url', + ] + + pattern = r"{% (\w+)\s" + + err_count = 0 + + has_trans = False + + for idx, line in enumerate(data): + + for tag in re.findall(pattern, line): + + if tag not in allowed_tags: + print(f" > Line {idx+1} - '{tag}'") + err_count += 1 + + if tag == 'trans': + has_trans = True + + if not has_trans: + print(f" > missing 'trans' tag") + err_count += 1 + + return err_count + + +for filename in pathlib.Path(js_i18n_dir).rglob('*.js'): + + print(f"Checking file 'translated/{os.path.basename(filename)}':") + + with open(filename, 'r') as js_file: + data = js_file.readlines() + + errors += check_invalid_tag(data) + errors += check_prohibited_tags(data) + +for filename in pathlib.Path(js_dynamic_dir).rglob('*.js'): + + print(f"Checking file 'dynamic/{os.path.basename(filename)}':") + + # Check that the 'dynamic' files do not contains any translated strings + with open(filename, 'r') as js_file: + data = js_file.readlines() + + pattern = r'{% trans ' + + err_count = 0 + + for idx, line in enumerate(data): + + results = re.findall(pattern, line) + + if len(results) > 0: + errors += 1 + + print(f" > {{% trans %}} tag found at line {idx + 1}") + +if errors > 0: + print(f"Found {errors} incorrect template tags") + +sys.exit(errors) From 8e97d14f1f10141f7de9ca7264440e7fb26aa463 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 09:26:56 +1000 Subject: [PATCH 08/20] Rename CI test --- .github/workflows/javascript.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/javascript.yaml b/.github/workflows/javascript.yaml index d5e5fca18f..908a87e31c 100644 --- a/.github/workflows/javascript.yaml +++ b/.github/workflows/javascript.yaml @@ -13,7 +13,7 @@ on: jobs: - python: + javascript: runs-on: ubuntu-latest env: From 915756eacf7227b41bf9f615c6adb9832abd6a46 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 09:28:08 +1000 Subject: [PATCH 09/20] Improve test output --- ci/check_js_templates.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/check_js_templates.py b/ci/check_js_templates.py index aa40b88742..e3c1f0148f 100644 --- a/ci/check_js_templates.py +++ b/ci/check_js_templates.py @@ -71,14 +71,14 @@ def check_prohibited_tags(data): for tag in re.findall(pattern, line): if tag not in allowed_tags: - print(f" > Line {idx+1} - '{tag}'") + print(f" > Line {idx+1} contains prohibited template tag '{tag}'") err_count += 1 if tag == 'trans': has_trans = True if not has_trans: - print(f" > missing 'trans' tag") + print(f" > file is missing 'trans' tags") err_count += 1 return err_count @@ -113,7 +113,7 @@ for filename in pathlib.Path(js_dynamic_dir).rglob('*.js'): if len(results) > 0: errors += 1 - print(f" > {{% trans %}} tag found at line {idx + 1}") + print(f" > prohibited {{% trans %}} tag found at line {idx + 1}") if errors > 0: print(f"Found {errors} incorrect template tags") From 27ec65a00217b4a60b94df12038dd1efd3636085 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 11:28:04 +1000 Subject: [PATCH 10/20] Add 'settings.js' which provides all settings (global and user) as a dynamic javascript file - Minimal database hits required --- InvenTree/InvenTree/urls.py | 1 + InvenTree/common/models.py | 40 ++++++++++++++++++- .../part/templatetags/inventree_extras.py | 18 +++++++++ InvenTree/templates/base.html | 7 +++- InvenTree/templates/js/dynamic/settings.js | 17 ++++++++ 5 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 InvenTree/templates/js/dynamic/settings.js diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 7d398c6e4f..d3f144800d 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -96,6 +96,7 @@ settings_urls = [ # These javascript files are served "dynamically" - i.e. rendered on demand dynamic_javascript_urls = [ url(r'^inventree.js', DynamicJsView.as_view(template_name='js/dynamic/inventree.js'), name='inventree.js'), + url(r'^settings.js', DynamicJsView.as_view(template_name='js/dynamic/settings.js'), name='settings.js'), ] # These javascript files are pased through the Django translation layer diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 72e8cf8060..5131248db4 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -38,6 +38,45 @@ class BaseInvenTreeSetting(models.Model): class Meta: abstract = True + @classmethod + def allValues(cls, user=None): + """ + Return a dict of "all" defined global settings. + + This performs a single database lookup, + and then any settings which are not *in* the database + are assigned their default values + """ + + keys = set() + settings = [] + + results = cls.objects.all() + + if user is not None: + results = results.filter(user=user) + + # Query the database + for setting in results: + settings.append({ + "key": setting.key.upper(), + "value": setting.value + }) + + keys.add(setting.key.upper()) + + # Specify any "default" values which are not in the database + for key in cls.GLOBAL_SETTINGS.keys(): + + if key.upper() not in keys: + + settings.append({ + "key": key.upper(), + "value": cls.get_setting_default(key) + }) + + return settings + @classmethod def get_setting_name(cls, key): """ @@ -739,7 +778,6 @@ class InvenTreeSetting(BaseInvenTreeSetting): help_text=_('Settings key (must be unique - case insensitive'), ) - class InvenTreeUserSetting(BaseInvenTreeSetting): """ An InvenTreeSetting object with a usercontext diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index f930fa4467..5ebc939305 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -207,6 +207,24 @@ def settings_value(key, *args, **kwargs): return InvenTreeSetting.get_setting(key) +@register.simple_tag() +def user_settings(user, *args, **kwargs): + """ + Return all USER settings as a key:value dict + """ + + return InvenTreeUserSetting.allValues(user=user) + + +@register.simple_tag() +def global_settings(*args, **kwargs): + """ + Return all GLOBAL InvenTree settings as a key:value dict + """ + + return InvenTreeSetting.allValues() + + @register.simple_tag() def get_color_theme_css(username): try: diff --git a/InvenTree/templates/base.html b/InvenTree/templates/base.html index 72f20404aa..22955e208b 100644 --- a/InvenTree/templates/base.html +++ b/InvenTree/templates/base.html @@ -145,8 +145,11 @@ - - + + + + + diff --git a/InvenTree/templates/js/dynamic/settings.js b/InvenTree/templates/js/dynamic/settings.js new file mode 100644 index 0000000000..ad4e297c4a --- /dev/null +++ b/InvenTree/templates/js/dynamic/settings.js @@ -0,0 +1,17 @@ +{% load inventree_extras %} +// InvenTree settings + +{% user_settings request.user as USER_SETTINGS %} +{% global_settings as GLOBAL_SETTINGS %} + +var user_settings = { + {% for setting in USER_SETTINGS %} + {{ setting.key }}: {{ setting.value }}, + {% endfor %} +}; + +var global_settings = { + {% for setting in GLOBAL_SETTINGS %} + {{ setting.key }}: {{ setting.value }}, + {% endfor %} +}; \ No newline at end of file From a222efda33dd73eb7a31dcbba43f568ad73c91e2 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 11:43:50 +1000 Subject: [PATCH 11/20] Fix rendering issues --- InvenTree/common/models.py | 48 ++++++++++++++++--- InvenTree/templates/js/dynamic/settings.js | 4 +- .../templates/js/translated/table_filters.js | 1 + 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 5131248db4..3defdc3c13 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -75,6 +75,28 @@ class BaseInvenTreeSetting(models.Model): "value": cls.get_setting_default(key) }) + # Enforce javascript formatting + for idx, setting in enumerate(settings): + + key = setting['key'] + value = setting['value'] + + validator = cls.get_setting_validator(key) + + # Convert to javascript compatible booleans + if cls.validator_is_bool(validator): + value = 'true' if value else 'false' + + # Numerical values remain the same + elif cls.validator_is_int(validator): + pass + + # Wrap strings with quotes + else: + value = f"'{value}'" + + setting["value"] = value + return settings @classmethod @@ -407,13 +429,7 @@ class BaseInvenTreeSetting(models.Model): validator = self.__class__.get_setting_validator(self.key) - if validator == bool: - return True - - if type(validator) in [list, tuple]: - for v in validator: - if v == bool: - return True + return self.__class__.validator_is_bool(validator) def as_bool(self): """ @@ -424,6 +440,19 @@ class BaseInvenTreeSetting(models.Model): return InvenTree.helpers.str2bool(self.value) + @classmethod + def validator_is_bool(cls, validator): + + if validator == bool: + return True + + if type(validator) in [list, tuple]: + for v in validator: + if v == bool: + return True + + return False + def is_int(self): """ Check if the setting is required to be an integer value: @@ -431,6 +460,11 @@ class BaseInvenTreeSetting(models.Model): validator = self.__class__.get_setting_validator(self.key) + return self.__class__.validator_is_int(validator) + + @classmethod + def validator_is_int(cls, validator): + if validator == int: return True diff --git a/InvenTree/templates/js/dynamic/settings.js b/InvenTree/templates/js/dynamic/settings.js index ad4e297c4a..4cc824ed6c 100644 --- a/InvenTree/templates/js/dynamic/settings.js +++ b/InvenTree/templates/js/dynamic/settings.js @@ -6,12 +6,12 @@ var user_settings = { {% for setting in USER_SETTINGS %} - {{ setting.key }}: {{ setting.value }}, + {{ setting.key }}: {{ setting.value|safe }}, {% endfor %} }; var global_settings = { {% for setting in GLOBAL_SETTINGS %} - {{ setting.key }}: {{ setting.value }}, + {{ setting.key }}: {{ setting.value|safe }}, {% endfor %} }; \ No newline at end of file diff --git a/InvenTree/templates/js/translated/table_filters.js b/InvenTree/templates/js/translated/table_filters.js index 78632d6d56..930e19313d 100644 --- a/InvenTree/templates/js/translated/table_filters.js +++ b/InvenTree/templates/js/translated/table_filters.js @@ -147,6 +147,7 @@ function getAvailableTableFilters(tableKey) { title: '{% trans "Depleted" %}', description: '{% trans "Show stock items which are depleted" %}', }, + {% settings_value "STOCK_ENABLE_EXPIRY" as expiry %} {% if expiry %} expired: { From 28bf5bfdbc3f592d6f52fb45969d075eee86f15d Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 11:52:50 +1000 Subject: [PATCH 12/20] Fix table_filters.js --- InvenTree/common/models.py | 2 +- .../templates/js/translated/table_filters.js | 34 +++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 3defdc3c13..935990e074 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -85,7 +85,7 @@ class BaseInvenTreeSetting(models.Model): # Convert to javascript compatible booleans if cls.validator_is_bool(validator): - value = 'true' if value else 'false' + value = value.lower() # Numerical values remain the same elif cls.validator_is_int(validator): diff --git a/InvenTree/templates/js/translated/table_filters.js b/InvenTree/templates/js/translated/table_filters.js index 930e19313d..9e173a7b37 100644 --- a/InvenTree/templates/js/translated/table_filters.js +++ b/InvenTree/templates/js/translated/table_filters.js @@ -121,7 +121,8 @@ function getAvailableTableFilters(tableKey) { // Filters for the "Stock" table if (tableKey == 'stock') { - return { + + var filters = { active: { type: 'bool', title: '{% trans "Active parts" %}', @@ -147,20 +148,6 @@ function getAvailableTableFilters(tableKey) { title: '{% trans "Depleted" %}', description: '{% trans "Show stock items which are depleted" %}', }, - - {% settings_value "STOCK_ENABLE_EXPIRY" as expiry %} - {% if expiry %} - expired: { - type: 'bool', - title: '{% trans "Expired" %}', - description: '{% trans "Show stock items which have expired" %}', - }, - stale: { - type: 'bool', - title: '{% trans "Stale" %}', - description: '{% trans "Show stock which is close to expiring" %}', - }, - {% endif %} in_stock: { type: 'bool', title: '{% trans "In Stock" %}', @@ -217,6 +204,23 @@ function getAvailableTableFilters(tableKey) { description: '{% trans "Show stock items which have a purchase price set" %}', }, }; + + // Optional filters if stock expiry functionality is enabled + if (global_settings.STOCK_ENABLE_EXPIRY) { + filters.expired = { + type: 'bool', + title: '{% trans "Expired" %}', + description: '{% trans "Show stock items which have expired" %}', + }; + + filters.stale = { + type: 'bool', + title: '{% trans "Stale" %}', + description: '{% trans "Show stock which is close to expiring" %}', + }; + } + + return filters; } // Filters for the 'stock test' table From ba5479090aec3dc51c41946f5d5e38934cbb6dce Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 11:54:04 +1000 Subject: [PATCH 13/20] Fix nav.js --- InvenTree/InvenTree/urls.py | 2 +- InvenTree/templates/js/{translated => dynamic}/nav.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename InvenTree/templates/js/{translated => dynamic}/nav.js (100%) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index d3f144800d..54f79bab19 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -97,6 +97,7 @@ settings_urls = [ dynamic_javascript_urls = [ url(r'^inventree.js', DynamicJsView.as_view(template_name='js/dynamic/inventree.js'), name='inventree.js'), url(r'^settings.js', DynamicJsView.as_view(template_name='js/dynamic/settings.js'), name='settings.js'), + url(r'^nav.js', DynamicJsView.as_view(template_name='js/dynamic/nav.js'), name='nav.js'), ] # These javascript files are pased through the Django translation layer @@ -113,7 +114,6 @@ translated_javascript_urls = [ url(r'^label.js', DynamicJsView.as_view(template_name='js/translated/label.js'), name='label.js'), url(r'^model_renderers.js', DynamicJsView.as_view(template_name='js/translated/model_renderers.js'), name='model_renderers.js'), url(r'^modals.js', DynamicJsView.as_view(template_name='js/translated/modals.js'), name='modals.js'), - url(r'^nav.js', DynamicJsView.as_view(template_name='js/translated/nav.js'), name='nav.js'), url(r'^order.js', DynamicJsView.as_view(template_name='js/translated/order.js'), name='order.js'), url(r'^part.js', DynamicJsView.as_view(template_name='js/translated/part.js'), name='part.js'), url(r'^report.js', DynamicJsView.as_view(template_name='js/translated/report.js'), name='report.js'), diff --git a/InvenTree/templates/js/translated/nav.js b/InvenTree/templates/js/dynamic/nav.js similarity index 100% rename from InvenTree/templates/js/translated/nav.js rename to InvenTree/templates/js/dynamic/nav.js From 6fe5f0e0e678a85fa3822ade1d586a52dade9606 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 11:58:32 +1000 Subject: [PATCH 14/20] Fixes for order.js --- InvenTree/InvenTree/urls.py | 4 ++-- InvenTree/templates/base.html | 4 ++-- .../templates/js/{translated => dynamic}/calendar.js | 0 InvenTree/templates/js/translated/order.js | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) rename InvenTree/templates/js/{translated => dynamic}/calendar.js (100%) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 54f79bab19..71f6388c68 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -96,8 +96,9 @@ settings_urls = [ # These javascript files are served "dynamically" - i.e. rendered on demand dynamic_javascript_urls = [ url(r'^inventree.js', DynamicJsView.as_view(template_name='js/dynamic/inventree.js'), name='inventree.js'), - url(r'^settings.js', DynamicJsView.as_view(template_name='js/dynamic/settings.js'), name='settings.js'), + url(r'^calendar.js', DynamicJsView.as_view(template_name='js/dynamic/calendar.js'), name='calendar.js'), url(r'^nav.js', DynamicJsView.as_view(template_name='js/dynamic/nav.js'), name='nav.js'), + url(r'^settings.js', DynamicJsView.as_view(template_name='js/dynamic/settings.js'), name='settings.js'), ] # These javascript files are pased through the Django translation layer @@ -107,7 +108,6 @@ translated_javascript_urls = [ url(r'^barcode.js', DynamicJsView.as_view(template_name='js/translated/barcode.js'), name='barcode.js'), url(r'^bom.js', DynamicJsView.as_view(template_name='js/translated/bom.js'), name='bom.js'), url(r'^build.js', DynamicJsView.as_view(template_name='js/translated/build.js'), name='build.js'), - url(r'^calendar.js', DynamicJsView.as_view(template_name='js/translated/calendar.js'), name='calendar.js'), url(r'^company.js', DynamicJsView.as_view(template_name='js/translated/company.js'), name='company.js'), url(r'^filters.js', DynamicJsView.as_view(template_name='js/translated/filters.js'), name='filters.js'), url(r'^forms.js', DynamicJsView.as_view(template_name='js/translated/forms.js'), name='forms.js'), diff --git a/InvenTree/templates/base.html b/InvenTree/templates/base.html index 22955e208b..6f0cbe4acd 100644 --- a/InvenTree/templates/base.html +++ b/InvenTree/templates/base.html @@ -147,6 +147,8 @@ + + @@ -155,12 +157,10 @@ - - diff --git a/InvenTree/templates/js/translated/calendar.js b/InvenTree/templates/js/dynamic/calendar.js similarity index 100% rename from InvenTree/templates/js/translated/calendar.js rename to InvenTree/templates/js/dynamic/calendar.js diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 7091eb0577..86035c5c47 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -9,7 +9,7 @@ function createSalesOrder(options={}) { method: 'POST', fields: { reference: { - prefix: '{% settings_value "SALESORDER_REFERENCE_PREFIX" %}', + prefix: global_settings.SALESORDER_REFERENCE_PREFIX, }, customer: { value: options.customer, @@ -40,7 +40,7 @@ function createPurchaseOrder(options={}) { method: 'POST', fields: { reference: { - prefix: "{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}", + prefix: global_settings.PURCHASEORDER_REFERENCE_PREFIX, }, supplier: { value: options.supplier, @@ -214,7 +214,7 @@ function loadPurchaseOrderTable(table, options) { switchable: false, formatter: function(value, row, index, field) { - var prefix = "{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}"; + var prefix = global_settings.PURCHASEORDER_REFERENCE_PREFIX; if (prefix) { value = `${prefix}${value}`; @@ -309,7 +309,7 @@ function loadSalesOrderTable(table, options) { title: '{% trans "Sales Order" %}', formatter: function(value, row, index, field) { - var prefix = "{% settings_value 'SALESORDER_REFERENCE_PREFIX' %}"; + var prefix = global_settings.SALESORDER_REFERENCE_PREFIX; if (prefix) { value = `${prefix}${value}`; @@ -423,7 +423,7 @@ function loadSalesOrderAllocationTable(table, options={}) { switchable: false, formatter: function(value, row) { - var prefix = "{% settings_value 'SALESORDER_REFERENCE_PREFIX' %}"; + var prefix = global_settings.SALESORDER_REFERENCE_PREFIX; var ref = `${prefix}${row.order_detail.reference}`; From 4381a16b0e5aa0ad36e2deca26c676ac5bde5398 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 12:31:07 +1000 Subject: [PATCH 15/20] Template cleanup --- InvenTree/build/templates/build/index.html | 2 +- InvenTree/common/models.py | 1 + .../templates/company/company_base.html | 21 +++++----- .../order/templates/order/order_base.html | 2 +- .../templates/order/sales_order_base.html | 2 +- .../order/templates/order/sales_orders.html | 2 +- InvenTree/part/templates/part/part_base.html | 21 +++++----- InvenTree/templates/js/dynamic/inventree.js | 6 +-- InvenTree/templates/js/translated/build.js | 6 +-- InvenTree/templates/js/translated/stock.js | 41 ++++++++++--------- 10 files changed, 50 insertions(+), 54 deletions(-) diff --git a/InvenTree/build/templates/build/index.html b/InvenTree/build/templates/build/index.html index d5e6484d56..6b0d9163eb 100644 --- a/InvenTree/build/templates/build/index.html +++ b/InvenTree/build/templates/build/index.html @@ -82,7 +82,7 @@ }, { success: function(response) { - var prefix = '{% settings_value "BUILDORDER_REFERENCE_PREFIX" %}'; + var prefix = global_settings.BUILDORDER_REFERENCE_PREFIX; for (var idx = 0; idx < response.length; idx++) { diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 935990e074..53c6c03df7 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -812,6 +812,7 @@ class InvenTreeSetting(BaseInvenTreeSetting): help_text=_('Settings key (must be unique - case insensitive'), ) + class InvenTreeUserSetting(BaseInvenTreeSetting): """ An InvenTreeSetting object with a usercontext diff --git a/InvenTree/company/templates/company/company_base.html b/InvenTree/company/templates/company/company_base.html index 3b8f1e7734..c50a9490f0 100644 --- a/InvenTree/company/templates/company/company_base.html +++ b/InvenTree/company/templates/company/company_base.html @@ -198,17 +198,16 @@ ); }); - {% settings_value "INVENTREE_DOWNLOAD_FROM_URL" as allow_download %} + if (global_settings.INVENTREE_DOWNLOAD_FROM_URL) { - {% if allow_download %} - $('#company-image-url').click(function() { - launchModalForm( - '{% url "company-image-download" company.id %}', - { - reload: true, - } - ) - }); - {% endif %} + $('#company-image-url').click(function() { + launchModalForm( + '{% url "company-image-download" company.id %}', + { + reload: true, + } + ) + }); + } {% endblock %} \ No newline at end of file diff --git a/InvenTree/order/templates/order/order_base.html b/InvenTree/order/templates/order/order_base.html index 11396e07a7..0d46207c33 100644 --- a/InvenTree/order/templates/order/order_base.html +++ b/InvenTree/order/templates/order/order_base.html @@ -164,7 +164,7 @@ $("#edit-order").click(function() { constructForm('{% url "api-po-detail" order.pk %}', { fields: { reference: { - prefix: "{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}", + prefix: global_settings.PURCHASEORDER_REFERENCE_PREFIX, }, {% if order.lines.count == 0 and order.status == PurchaseOrderStatus.PENDING %} supplier: { diff --git a/InvenTree/order/templates/order/sales_order_base.html b/InvenTree/order/templates/order/sales_order_base.html index 60099a2578..6f8c422f7a 100644 --- a/InvenTree/order/templates/order/sales_order_base.html +++ b/InvenTree/order/templates/order/sales_order_base.html @@ -157,7 +157,7 @@ $("#edit-order").click(function() { constructForm('{% url "api-so-detail" order.pk %}', { fields: { reference: { - prefix: "{% settings_value 'SALESORDER_REFERENCE_PREFIX' %}", + prefix: global_settings.SALESORDER_REFERENCE_PREFIX, }, {% if order.lines.count == 0 and order.status == SalesOrderStatus.PENDING %} customer: { diff --git a/InvenTree/order/templates/order/sales_orders.html b/InvenTree/order/templates/order/sales_orders.html index d4ebbd4ca8..71ffbc212f 100644 --- a/InvenTree/order/templates/order/sales_orders.html +++ b/InvenTree/order/templates/order/sales_orders.html @@ -67,7 +67,7 @@ { success: function(response) { - var prefix = '{% settings_value "SALESORDER_REFERENCE_PREFIX" %}'; + var prefix = global_settings.SALESORDER_REFERENCE_PREFIX; for (var idx = 0; idx < response.length; idx++) { var order = response[idx]; diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index 9fe6c7b486..d7b196917d 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -394,17 +394,16 @@ {% if roles.part.change %} - {% settings_value "INVENTREE_DOWNLOAD_FROM_URL" as allow_download %} - {% if allow_download %} - $("#part-image-url").click(function() { - launchModalForm( - '{% url "part-image-download" part.id %}', - { - reload: true, - } - ); - }); - {% endif %} + if (global_settings.INVENTREE_DOWNLOAD_FROM_URL) { + $("#part-image-url").click(function() { + launchModalForm( + '{% url "part-image-download" part.id %}', + { + reload: true, + } + ); + }); + } $("#part-image-select").click(function() { launchModalForm("{% url 'part-image-select' part.id %}", diff --git a/InvenTree/templates/js/dynamic/inventree.js b/InvenTree/templates/js/dynamic/inventree.js index aa87008fbe..21e667adcb 100644 --- a/InvenTree/templates/js/dynamic/inventree.js +++ b/InvenTree/templates/js/dynamic/inventree.js @@ -91,11 +91,7 @@ function inventreeDocReady() { url: '/api/part/', data: { search: request.term, - {% if request.user %} - limit: {% settings_value 'SEARCH_PREVIEW_RESULTS' user=request.user %}, - {% else %} - limit: 25, - {% endif %} + limit: user_settings.SEARCH_PREVIEW_RESULTS, offset: 0 }, success: function (data) { diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js index f43de6ec2b..4b8cd47eb5 100644 --- a/InvenTree/templates/js/translated/build.js +++ b/InvenTree/templates/js/translated/build.js @@ -5,7 +5,7 @@ function buildFormFields() { return { reference: { - prefix: "{% settings_value 'BUILDORDER_REFERENCE_PREFIX' %}", + prefix: global_settings.BUILDORDER_REFERENCE_PREFIX, }, title: {}, part: {}, @@ -232,7 +232,7 @@ function loadBuildOrderAllocationTable(table, options={}) { switchable: false, title: '{% trans "Build Order" %}', formatter: function(value, row) { - var prefix = "{% settings_value 'BUILDORDER_REFERENCE_PREFIX' %}"; + var prefix = global_settings.BUILDORDER_REFERENCE_PREFIX; var ref = `${prefix}${row.build_detail.reference}`; @@ -848,7 +848,7 @@ function loadBuildTable(table, options) { switchable: true, formatter: function(value, row, index, field) { - var prefix = "{% settings_value 'BUILDORDER_REFERENCE_PREFIX' %}"; + var prefix = global_settings.BUILDORDER_REFERENCE_PREFIX; if (prefix) { value = `${prefix}${value}`; diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index a2015797fe..c573694724 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -6,8 +6,6 @@ * Requires api.js to be loaded first */ -{% settings_value 'BARCODE_ENABLE' as barcodes %} - function stockStatusCodes() { return [ {% for code in StockStatus.list %} @@ -1037,7 +1035,7 @@ function loadStockTable(table, options) { if (row.purchase_order_reference) { - var prefix = '{% settings_value "PURCHASEORDER_REFERENCE_PREFIX" %}'; + var prefix = global_settings.PURCHASEORDER_REFERENCE_PREFIX; text = prefix + row.purchase_order_reference; } @@ -1090,15 +1088,18 @@ function loadStockTable(table, options) { } */ + var buttons = [ + '#stock-print-options', + '#stock-options'; + ]; + + if (global_settings.BARCODE_ENABLE) { + buttons.push('#stock-barcode-options'); + } + linkButtonsToSelection( table, - [ - '#stock-print-options', - {% if barcodes %} - '#stock-barcode-options', - {% endif %} - '#stock-options', - ] + buttons, ); @@ -1138,19 +1139,19 @@ function loadStockTable(table, options) { printTestReports(items); }) - {% if barcodes %} - $('#multi-item-barcode-scan-into-location').click(function() { - var selections = $('#stock-table').bootstrapTable('getSelections'); + if (global_settings.BARCODE_ENABLE) { + $('#multi-item-barcode-scan-into-location').click(function() { + var selections = $('#stock-table').bootstrapTable('getSelections'); - var items = []; + var items = []; - selections.forEach(function(item) { - items.push(item.pk); - }) + selections.forEach(function(item) { + items.push(item.pk); + }) - scanItemsIntoLocation(items); - }); - {% endif %} + scanItemsIntoLocation(items); + }); + } $('#multi-item-stocktake').click(function() { stockAdjustment('count'); From 7756c766c3f74a0095d17a3bd8325fbefd30d19c Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 12:35:21 +1000 Subject: [PATCH 16/20] Fix stock.js --- InvenTree/order/templates/order/purchase_orders.html | 2 +- InvenTree/templates/js/translated/stock.js | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/InvenTree/order/templates/order/purchase_orders.html b/InvenTree/order/templates/order/purchase_orders.html index de1c0dd8a9..daefbc0e00 100644 --- a/InvenTree/order/templates/order/purchase_orders.html +++ b/InvenTree/order/templates/order/purchase_orders.html @@ -66,7 +66,7 @@ }, { success: function(response) { - var prefix = '{% settings_value "PURCHASEORDER_REFERENCE_PREFIX" %}'; + var prefix = global_settings.PURCHASEORDER_REFERENCE_PREFIX; for (var idx = 0; idx < response.length; idx++) { diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index c573694724..826048471d 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -702,8 +702,7 @@ function loadStockTable(table, options) { name: 'stock', original: original, showColumns: true, - {% settings_value 'STOCK_GROUP_BY_PART' as group_by_part %} - {% if group_by_part %} + {% if False %} groupByField: options.groupByField || 'part', groupBy: grouping, groupByFormatter: function(field, id, data) { @@ -1009,14 +1008,13 @@ function loadStockTable(table, options) { title: '{% trans "Stocktake" %}', sortable: true, }, - {% settings_value "STOCK_ENABLE_EXPIRY" as expiry %} - {% if expiry %} { field: 'expiry_date', title: '{% trans "Expiry Date" %}', sortable: true, + visible: global_settings.STOCK_ENABLE_EXPIRY, + switchable: global_settings.STOCK_ENABLE_EXPIRY, }, - {% endif %} { field: 'updated', title: '{% trans "Last Updated" %}', @@ -1090,7 +1088,7 @@ function loadStockTable(table, options) { var buttons = [ '#stock-print-options', - '#stock-options'; + '#stock-options', ]; if (global_settings.BARCODE_ENABLE) { From dd12a593f4ffd6e1999e03ce4de807fbf0430595 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 16:37:34 +1000 Subject: [PATCH 17/20] Specify how many workers to use --- InvenTree/InvenTree/settings.py | 15 ++++++++++++++- docker/Dockerfile | 4 ++++ docker/gunicorn.conf.py | 18 +++++++++++++++++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index f9ecaa394d..9e0fa128c9 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -12,6 +12,7 @@ database setup in this file. """ import logging +from multiprocessing import Value import os import random import string @@ -347,10 +348,22 @@ REST_FRAMEWORK = { WSGI_APPLICATION = 'InvenTree.wsgi.application' +background_workers = os.environ.get('INVENTREE_BACKGROUND_WORKERS', None) + +if background_workers is not None: + try: + background_workers = int(background_workers) + except ValueError: + background_workers = None + +if background_workers is None: + # Sensible default? + background_workers = 4 + # django-q configuration Q_CLUSTER = { 'name': 'InvenTree', - 'workers': 4, + 'workers': background_workers, 'timeout': 90, 'retry': 120, 'queue_limit': 50, diff --git a/docker/Dockerfile b/docker/Dockerfile index 72e6acc8ca..b6005ea49c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -27,6 +27,10 @@ ENV INVENTREE_MEDIA_ROOT="${INVENTREE_DATA_DIR}/media" ENV INVENTREE_CONFIG_FILE="${INVENTREE_DATA_DIR}/config.yaml" ENV INVENTREE_SECRET_KEY_FILE="${INVENTREE_DATA_DIR}/secret_key.txt" +# Worker configuration (can be altered by user) +ENV INVENTREE_GUNICORN_WORKERS="4" +ENV INVENTREE_BACKGROUND_WORKERS="4" + # Default web server port is 8000 ENV INVENTREE_WEB_PORT="8000" diff --git a/docker/gunicorn.conf.py b/docker/gunicorn.conf.py index 1071c2d745..758d85b8ee 100644 --- a/docker/gunicorn.conf.py +++ b/docker/gunicorn.conf.py @@ -1,6 +1,22 @@ import multiprocessing +import os +import logging -workers = multiprocessing.cpu_count() * 2 + 1 + +logger = logging.get('inventree') + +workers = os.environ.get('INVENTREE_GUNICORN_WORKERS', None) + +if workers is not None: + try: + workers = int(workers) + except ValueError: + workers = None + +if workers is None: + workers = multiprocessing.cpu_count() * 2 + 1 + +logger.info(f"Starting gunicorn server with {workers} workers") max_requests = 1000 max_requests_jitter = 50 From 542c204ca074db0a97d12a72db044a59243ffbd1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 16:39:51 +1000 Subject: [PATCH 18/20] PEP fixes --- InvenTree/InvenTree/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 9e0fa128c9..4543b873bd 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -12,7 +12,7 @@ database setup in this file. """ import logging -from multiprocessing import Value + import os import random import string From c39e3aaa8210a3dc75a6e6ae78a69312ff73da68 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 29 Jul 2021 17:52:24 +1000 Subject: [PATCH 19/20] Bug fix --- InvenTree/common/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 53c6c03df7..54dc21c1b5 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -85,7 +85,7 @@ class BaseInvenTreeSetting(models.Model): # Convert to javascript compatible booleans if cls.validator_is_bool(validator): - value = value.lower() + value = str(value).lower() # Numerical values remain the same elif cls.validator_is_int(validator): From d9f4c34a42c886ccfba231e8ae81b2d298cf154c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 29 Jul 2021 23:44:52 +1000 Subject: [PATCH 20/20] logging.get -> logging.getLogger --- docker/gunicorn.conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/gunicorn.conf.py b/docker/gunicorn.conf.py index 758d85b8ee..67b098fcb4 100644 --- a/docker/gunicorn.conf.py +++ b/docker/gunicorn.conf.py @@ -3,7 +3,7 @@ import os import logging -logger = logging.get('inventree') +logger = logging.getLogger('inventree') workers = os.environ.get('INVENTREE_GUNICORN_WORKERS', None)