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