mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
0.4.1
This commit is contained in:
commit
0e59c15773
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -7,5 +7,5 @@
|
||||
*.yml text
|
||||
*.yaml text
|
||||
*.conf text
|
||||
*.sh text
|
||||
*.sh text eol=lf
|
||||
*.js text
|
28
.github/workflows/javascript.yaml
vendored
Normal file
28
.github/workflows/javascript.yaml
vendored
Normal file
@ -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
|
||||
|
@ -12,6 +12,7 @@ database setup in this file.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
@ -202,7 +203,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)
|
||||
@ -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,
|
||||
|
@ -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)),
|
||||
|
||||
|
@ -8,7 +8,7 @@ import re
|
||||
|
||||
import common.models
|
||||
|
||||
INVENTREE_SW_VERSION = "0.4.0"
|
||||
INVENTREE_SW_VERSION = "0.4.1"
|
||||
|
||||
INVENTREE_API_VERSION = 8
|
||||
|
||||
|
@ -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++) {
|
||||
|
||||
|
@ -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 = str(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
|
||||
|
||||
|
@ -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,23 @@ class CompanyDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
return queryset
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
class ManufacturerPartList(generics.ListCreateAPIView):
|
||||
""" API endpoint for list view of ManufacturerPart object
|
||||
|
||||
@ -98,6 +117,7 @@ class ManufacturerPartList(generics.ListCreateAPIView):
|
||||
)
|
||||
|
||||
serializer_class = ManufacturerPartSerializer
|
||||
filterset_class = ManufacturerPartFilter
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
|
||||
@ -115,45 +135,12 @@ 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,
|
||||
filters.OrderingFilter,
|
||||
]
|
||||
|
||||
filter_fields = [
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'manufacturer__name',
|
||||
'description',
|
||||
|
@ -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 %}
|
@ -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: {
|
||||
|
@ -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++) {
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -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];
|
||||
|
@ -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 %}",
|
||||
|
@ -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:
|
||||
|
@ -145,19 +145,22 @@
|
||||
<script type='text/javascript' src="{% static 'script/inventree/notification.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/inventree/sidenav.js' %}"></script>
|
||||
|
||||
<!-- translated -->
|
||||
<script type='text/javascript' src="{% i18n_static 'inventree.js' %}"></script>
|
||||
<!-- dynamic javascript templates -->
|
||||
<script type='text/javascript' src="{% url 'inventree.js' %}"></script>
|
||||
<script type='text/javascript' src="{% url 'calendar.js' %}"></script>
|
||||
<script type='text/javascript' src="{% url 'nav.js' %}"></script>
|
||||
<script type='text/javascript' src="{% url 'settings.js' %}"></script>
|
||||
|
||||
<!-- translated javascript templates-->
|
||||
<script type='text/javascript' src="{% i18n_static 'api.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'attachment.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'barcode.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'bom.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'build.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'calendar.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'company.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'filters.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'forms.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'label.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'nav.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'modals.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'model_renderers.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'order.js' %}"></script>
|
||||
|
@ -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) {
|
17
InvenTree/templates/js/dynamic/settings.js
Normal file
17
InvenTree/templates/js/dynamic/settings.js
Normal file
@ -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 %}
|
||||
};
|
@ -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}`;
|
@ -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}`;
|
||||
|
@ -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');
|
@ -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
|
121
ci/check_js_templates.py
Normal file
121
ci/check_js_templates.py
Normal file
@ -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)
|
@ -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"
|
||||
|
||||
|
@ -1,6 +1,22 @@
|
||||
import multiprocessing
|
||||
import os
|
||||
import logging
|
||||
|
||||
workers = multiprocessing.cpu_count() * 2 + 1
|
||||
|
||||
logger = logging.getLogger('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
|
||||
|
@ -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
|
||||
gunicorn -c $INVENTREE_HOME/gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:$INVENTREE_WEB_PORT
|
||||
|
@ -11,4 +11,4 @@ python3 manage.py wait_for_db
|
||||
sleep 10
|
||||
|
||||
# Now we can launch the background worker process
|
||||
python3 manage.py qcluster
|
||||
python3 manage.py qcluster
|
||||
|
Loading…
Reference in New Issue
Block a user