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/.github/workflows/javascript.yaml b/.github/workflows/javascript.yaml
new file mode 100644
index 0000000000..908a87e31c
--- /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:
+
+ javascript:
+ 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..71f6388c68 100644
--- a/InvenTree/InvenTree/urls.py
+++ b/InvenTree/InvenTree/urls.py
@@ -93,28 +93,33 @@ 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'),
+ 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
+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'^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'^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 +128,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/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 72e8cf8060..53c6c03df7 100644
--- a/InvenTree/common/models.py
+++ b/InvenTree/common/models.py
@@ -38,6 +38,67 @@ 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)
+ })
+
+ # 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 = value.lower()
+
+ # 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
def get_setting_name(cls, key):
"""
@@ -368,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):
"""
@@ -385,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:
@@ -392,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/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/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/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/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..6f0cbe4acd 100644
--- a/InvenTree/templates/base.html
+++ b/InvenTree/templates/base.html
@@ -145,19 +145,22 @@
-
-
+
+
+
+
+
+
+
-
-
diff --git a/InvenTree/templates/js/calendar.js b/InvenTree/templates/js/dynamic/calendar.js
similarity index 100%
rename from InvenTree/templates/js/calendar.js
rename to InvenTree/templates/js/dynamic/calendar.js
diff --git a/InvenTree/templates/js/inventree.js b/InvenTree/templates/js/dynamic/inventree.js
similarity index 97%
rename from InvenTree/templates/js/inventree.js
rename to InvenTree/templates/js/dynamic/inventree.js
index aa87008fbe..21e667adcb 100644
--- a/InvenTree/templates/js/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/nav.js b/InvenTree/templates/js/dynamic/nav.js
similarity index 100%
rename from InvenTree/templates/js/nav.js
rename to InvenTree/templates/js/dynamic/nav.js
diff --git a/InvenTree/templates/js/dynamic/settings.js b/InvenTree/templates/js/dynamic/settings.js
new file mode 100644
index 0000000000..4cc824ed6c
--- /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|safe }},
+ {% endfor %}
+};
+
+var global_settings = {
+ {% for setting in GLOBAL_SETTINGS %}
+ {{ setting.key }}: {{ setting.value|safe }},
+ {% endfor %}
+};
\ No newline at end of file
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 99%
rename from InvenTree/templates/js/build.js
rename to InvenTree/templates/js/translated/build.js
index f43de6ec2b..4b8cd47eb5 100644
--- a/InvenTree/templates/js/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/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/order.js b/InvenTree/templates/js/translated/order.js
similarity index 96%
rename from InvenTree/templates/js/order.js
rename to InvenTree/templates/js/translated/order.js
index 7091eb0577..86035c5c47 100644
--- a/InvenTree/templates/js/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}`;
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 98%
rename from InvenTree/templates/js/stock.js
rename to InvenTree/templates/js/translated/stock.js
index a2015797fe..826048471d 100644
--- a/InvenTree/templates/js/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 %}
@@ -704,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) {
@@ -1011,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" %}',
@@ -1037,7 +1033,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 +1086,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 +1137,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');
diff --git a/InvenTree/templates/js/table_filters.js b/InvenTree/templates/js/translated/table_filters.js
similarity index 97%
rename from InvenTree/templates/js/table_filters.js
rename to InvenTree/templates/js/translated/table_filters.js
index 78632d6d56..9e173a7b37 100644
--- a/InvenTree/templates/js/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,19 +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" %}',
@@ -216,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
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..e3c1f0148f
--- /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} contains prohibited template tag '{tag}'")
+ err_count += 1
+
+ if tag == 'trans':
+ has_trans = True
+
+ if not has_trans:
+ print(f" > file is missing 'trans' tags")
+ 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" > prohibited {{% trans %}} tag found at line {idx + 1}")
+
+if errors > 0:
+ print(f"Found {errors} incorrect template tags")
+
+sys.exit(errors)
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