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
|
*.yml text
|
||||||
*.yaml text
|
*.yaml text
|
||||||
*.conf text
|
*.conf text
|
||||||
*.sh text
|
*.sh text eol=lf
|
||||||
*.js text
|
*.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 logging
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
@ -202,7 +203,7 @@ STATICFILES_DIRS = [
|
|||||||
|
|
||||||
# Translated Template settings
|
# Translated Template settings
|
||||||
STATICFILES_I18_PREFIX = 'i18n'
|
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_I18_TRG = STATICFILES_DIRS[0] + '_' + STATICFILES_I18_PREFIX
|
||||||
STATICFILES_DIRS.append(STATICFILES_I18_TRG)
|
STATICFILES_DIRS.append(STATICFILES_I18_TRG)
|
||||||
STATICFILES_I18_TRG = os.path.join(STATICFILES_I18_TRG, STATICFILES_I18_PREFIX)
|
STATICFILES_I18_TRG = os.path.join(STATICFILES_I18_TRG, STATICFILES_I18_PREFIX)
|
||||||
@ -347,10 +348,22 @@ REST_FRAMEWORK = {
|
|||||||
|
|
||||||
WSGI_APPLICATION = 'InvenTree.wsgi.application'
|
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
|
# django-q configuration
|
||||||
Q_CLUSTER = {
|
Q_CLUSTER = {
|
||||||
'name': 'InvenTree',
|
'name': 'InvenTree',
|
||||||
'workers': 4,
|
'workers': background_workers,
|
||||||
'timeout': 90,
|
'timeout': 90,
|
||||||
'retry': 120,
|
'retry': 120,
|
||||||
'queue_limit': 50,
|
'queue_limit': 50,
|
||||||
|
@ -93,28 +93,33 @@ settings_urls = [
|
|||||||
url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/settings.html'), name='settings'),
|
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 = [
|
dynamic_javascript_urls = [
|
||||||
url(r'^api.js', DynamicJsView.as_view(template_name='js/api.js'), name='api.js'),
|
url(r'^inventree.js', DynamicJsView.as_view(template_name='js/dynamic/inventree.js'), name='inventree.js'),
|
||||||
url(r'^attachment.js', DynamicJsView.as_view(template_name='js/attachment.js'), name='attachment.js'),
|
url(r'^calendar.js', DynamicJsView.as_view(template_name='js/dynamic/calendar.js'), name='calendar.js'),
|
||||||
url(r'^barcode.js', DynamicJsView.as_view(template_name='js/barcode.js'), name='barcode.js'),
|
url(r'^nav.js', DynamicJsView.as_view(template_name='js/dynamic/nav.js'), name='nav.js'),
|
||||||
url(r'^bom.js', DynamicJsView.as_view(template_name='js/bom.js'), name='bom.js'),
|
url(r'^settings.js', DynamicJsView.as_view(template_name='js/dynamic/settings.js'), name='settings.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'),
|
# These javascript files are pased through the Django translation layer
|
||||||
url(r'^filters.js', DynamicJsView.as_view(template_name='js/filters.js'), name='filters.js'),
|
translated_javascript_urls = [
|
||||||
url(r'^forms.js', DynamicJsView.as_view(template_name='js/forms.js'), name='forms.js'),
|
url(r'^api.js', DynamicJsView.as_view(template_name='js/translated/api.js'), name='api.js'),
|
||||||
url(r'^inventree.js', DynamicJsView.as_view(template_name='js/inventree.js'), name='inventree.js'),
|
url(r'^attachment.js', DynamicJsView.as_view(template_name='js/translated/attachment.js'), name='attachment.js'),
|
||||||
url(r'^label.js', DynamicJsView.as_view(template_name='js/label.js'), name='label.js'),
|
url(r'^barcode.js', DynamicJsView.as_view(template_name='js/translated/barcode.js'), name='barcode.js'),
|
||||||
url(r'^model_renderers.js', DynamicJsView.as_view(template_name='js/model_renderers.js'), name='model_renderers.js'),
|
url(r'^bom.js', DynamicJsView.as_view(template_name='js/translated/bom.js'), name='bom.js'),
|
||||||
url(r'^modals.js', DynamicJsView.as_view(template_name='js/modals.js'), name='modals.js'),
|
url(r'^build.js', DynamicJsView.as_view(template_name='js/translated/build.js'), name='build.js'),
|
||||||
url(r'^nav.js', DynamicJsView.as_view(template_name='js/nav.js'), name='nav.js'),
|
url(r'^company.js', DynamicJsView.as_view(template_name='js/translated/company.js'), name='company.js'),
|
||||||
url(r'^order.js', DynamicJsView.as_view(template_name='js/order.js'), name='order.js'),
|
url(r'^filters.js', DynamicJsView.as_view(template_name='js/translated/filters.js'), name='filters.js'),
|
||||||
url(r'^part.js', DynamicJsView.as_view(template_name='js/part.js'), name='part.js'),
|
url(r'^forms.js', DynamicJsView.as_view(template_name='js/translated/forms.js'), name='forms.js'),
|
||||||
url(r'^report.js', DynamicJsView.as_view(template_name='js/report.js'), name='report.js'),
|
url(r'^label.js', DynamicJsView.as_view(template_name='js/translated/label.js'), name='label.js'),
|
||||||
url(r'^stock.js', DynamicJsView.as_view(template_name='js/stock.js'), name='stock.js'),
|
url(r'^model_renderers.js', DynamicJsView.as_view(template_name='js/translated/model_renderers.js'), name='model_renderers.js'),
|
||||||
url(r'^tables.js', DynamicJsView.as_view(template_name='js/tables.js'), name='tables.js'),
|
url(r'^modals.js', DynamicJsView.as_view(template_name='js/translated/modals.js'), name='modals.js'),
|
||||||
url(r'^table_filters.js', DynamicJsView.as_view(template_name='js/table_filters.js'), name='table_filters.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 = [
|
urlpatterns = [
|
||||||
@ -123,7 +128,8 @@ urlpatterns = [
|
|||||||
url(r'^supplier-part/', include(supplier_part_urls)),
|
url(r'^supplier-part/', include(supplier_part_urls)),
|
||||||
|
|
||||||
# "Dynamic" javascript files which are rendered using InvenTree templating.
|
# "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)),
|
url(r'^common/', include(common_urls)),
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import re
|
|||||||
|
|
||||||
import common.models
|
import common.models
|
||||||
|
|
||||||
INVENTREE_SW_VERSION = "0.4.0"
|
INVENTREE_SW_VERSION = "0.4.1"
|
||||||
|
|
||||||
INVENTREE_API_VERSION = 8
|
INVENTREE_API_VERSION = 8
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
success: function(response) {
|
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++) {
|
for (var idx = 0; idx < response.length; idx++) {
|
||||||
|
|
||||||
|
@ -38,6 +38,67 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
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
|
@classmethod
|
||||||
def get_setting_name(cls, key):
|
def get_setting_name(cls, key):
|
||||||
"""
|
"""
|
||||||
@ -368,13 +429,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
validator = self.__class__.get_setting_validator(self.key)
|
validator = self.__class__.get_setting_validator(self.key)
|
||||||
|
|
||||||
if validator == bool:
|
return self.__class__.validator_is_bool(validator)
|
||||||
return True
|
|
||||||
|
|
||||||
if type(validator) in [list, tuple]:
|
|
||||||
for v in validator:
|
|
||||||
if v == bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def as_bool(self):
|
def as_bool(self):
|
||||||
"""
|
"""
|
||||||
@ -385,6 +440,19 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
return InvenTree.helpers.str2bool(self.value)
|
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):
|
def is_int(self):
|
||||||
"""
|
"""
|
||||||
Check if the setting is required to be an integer value:
|
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)
|
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:
|
if validator == int:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ Provides a JSON API for the Company app
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
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 filters
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
|
|
||||||
@ -84,6 +86,23 @@ class CompanyDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
return queryset
|
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):
|
class ManufacturerPartList(generics.ListCreateAPIView):
|
||||||
""" API endpoint for list view of ManufacturerPart object
|
""" API endpoint for list view of ManufacturerPart object
|
||||||
|
|
||||||
@ -98,6 +117,7 @@ class ManufacturerPartList(generics.ListCreateAPIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
serializer_class = ManufacturerPartSerializer
|
serializer_class = ManufacturerPartSerializer
|
||||||
|
filterset_class = ManufacturerPartFilter
|
||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
def get_serializer(self, *args, **kwargs):
|
||||||
|
|
||||||
@ -115,45 +135,12 @@ class ManufacturerPartList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
return self.serializer_class(*args, **kwargs)
|
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 = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
DjangoFilterBackend,
|
||||||
filters.SearchFilter,
|
filters.SearchFilter,
|
||||||
filters.OrderingFilter,
|
filters.OrderingFilter,
|
||||||
]
|
]
|
||||||
|
|
||||||
filter_fields = [
|
|
||||||
]
|
|
||||||
|
|
||||||
search_fields = [
|
search_fields = [
|
||||||
'manufacturer__name',
|
'manufacturer__name',
|
||||||
'description',
|
'description',
|
||||||
|
@ -198,9 +198,8 @@
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
{% settings_value "INVENTREE_DOWNLOAD_FROM_URL" as allow_download %}
|
if (global_settings.INVENTREE_DOWNLOAD_FROM_URL) {
|
||||||
|
|
||||||
{% if allow_download %}
|
|
||||||
$('#company-image-url').click(function() {
|
$('#company-image-url').click(function() {
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
'{% url "company-image-download" company.id %}',
|
'{% url "company-image-download" company.id %}',
|
||||||
@ -209,6 +208,6 @@
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
{% endif %}
|
}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -164,7 +164,7 @@ $("#edit-order").click(function() {
|
|||||||
constructForm('{% url "api-po-detail" order.pk %}', {
|
constructForm('{% url "api-po-detail" order.pk %}', {
|
||||||
fields: {
|
fields: {
|
||||||
reference: {
|
reference: {
|
||||||
prefix: "{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}",
|
prefix: global_settings.PURCHASEORDER_REFERENCE_PREFIX,
|
||||||
},
|
},
|
||||||
{% if order.lines.count == 0 and order.status == PurchaseOrderStatus.PENDING %}
|
{% if order.lines.count == 0 and order.status == PurchaseOrderStatus.PENDING %}
|
||||||
supplier: {
|
supplier: {
|
||||||
|
@ -66,7 +66,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
success: function(response) {
|
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++) {
|
for (var idx = 0; idx < response.length; idx++) {
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ $("#edit-order").click(function() {
|
|||||||
constructForm('{% url "api-so-detail" order.pk %}', {
|
constructForm('{% url "api-so-detail" order.pk %}', {
|
||||||
fields: {
|
fields: {
|
||||||
reference: {
|
reference: {
|
||||||
prefix: "{% settings_value 'SALESORDER_REFERENCE_PREFIX' %}",
|
prefix: global_settings.SALESORDER_REFERENCE_PREFIX,
|
||||||
},
|
},
|
||||||
{% if order.lines.count == 0 and order.status == SalesOrderStatus.PENDING %}
|
{% if order.lines.count == 0 and order.status == SalesOrderStatus.PENDING %}
|
||||||
customer: {
|
customer: {
|
||||||
|
@ -67,7 +67,7 @@
|
|||||||
{
|
{
|
||||||
success: function(response) {
|
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++) {
|
for (var idx = 0; idx < response.length; idx++) {
|
||||||
var order = response[idx];
|
var order = response[idx];
|
||||||
|
@ -394,8 +394,7 @@
|
|||||||
|
|
||||||
{% if roles.part.change %}
|
{% if roles.part.change %}
|
||||||
|
|
||||||
{% settings_value "INVENTREE_DOWNLOAD_FROM_URL" as allow_download %}
|
if (global_settings.INVENTREE_DOWNLOAD_FROM_URL) {
|
||||||
{% if allow_download %}
|
|
||||||
$("#part-image-url").click(function() {
|
$("#part-image-url").click(function() {
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
'{% url "part-image-download" part.id %}',
|
'{% url "part-image-download" part.id %}',
|
||||||
@ -404,7 +403,7 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
{% endif %}
|
}
|
||||||
|
|
||||||
$("#part-image-select").click(function() {
|
$("#part-image-select").click(function() {
|
||||||
launchModalForm("{% url 'part-image-select' part.id %}",
|
launchModalForm("{% url 'part-image-select' part.id %}",
|
||||||
|
@ -207,6 +207,24 @@ def settings_value(key, *args, **kwargs):
|
|||||||
return InvenTreeSetting.get_setting(key)
|
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()
|
@register.simple_tag()
|
||||||
def get_color_theme_css(username):
|
def get_color_theme_css(username):
|
||||||
try:
|
try:
|
||||||
|
@ -145,19 +145,22 @@
|
|||||||
<script type='text/javascript' src="{% static 'script/inventree/notification.js' %}"></script>
|
<script type='text/javascript' src="{% static 'script/inventree/notification.js' %}"></script>
|
||||||
<script type='text/javascript' src="{% static 'script/inventree/sidenav.js' %}"></script>
|
<script type='text/javascript' src="{% static 'script/inventree/sidenav.js' %}"></script>
|
||||||
|
|
||||||
<!-- translated -->
|
<!-- dynamic javascript templates -->
|
||||||
<script type='text/javascript' src="{% i18n_static 'inventree.js' %}"></script>
|
<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 'api.js' %}"></script>
|
||||||
<script type='text/javascript' src="{% i18n_static 'attachment.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 'barcode.js' %}"></script>
|
||||||
<script type='text/javascript' src="{% i18n_static 'bom.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 '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 'company.js' %}"></script>
|
||||||
<script type='text/javascript' src="{% i18n_static 'filters.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 'forms.js' %}"></script>
|
||||||
<script type='text/javascript' src="{% i18n_static 'label.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 'modals.js' %}"></script>
|
||||||
<script type='text/javascript' src="{% i18n_static 'model_renderers.js' %}"></script>
|
<script type='text/javascript' src="{% i18n_static 'model_renderers.js' %}"></script>
|
||||||
<script type='text/javascript' src="{% i18n_static 'order.js' %}"></script>
|
<script type='text/javascript' src="{% i18n_static 'order.js' %}"></script>
|
||||||
|
@ -91,11 +91,7 @@ function inventreeDocReady() {
|
|||||||
url: '/api/part/',
|
url: '/api/part/',
|
||||||
data: {
|
data: {
|
||||||
search: request.term,
|
search: request.term,
|
||||||
{% if request.user %}
|
limit: user_settings.SEARCH_PREVIEW_RESULTS,
|
||||||
limit: {% settings_value 'SEARCH_PREVIEW_RESULTS' user=request.user %},
|
|
||||||
{% else %}
|
|
||||||
limit: 25,
|
|
||||||
{% endif %}
|
|
||||||
offset: 0
|
offset: 0
|
||||||
},
|
},
|
||||||
success: function (data) {
|
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() {
|
function buildFormFields() {
|
||||||
return {
|
return {
|
||||||
reference: {
|
reference: {
|
||||||
prefix: "{% settings_value 'BUILDORDER_REFERENCE_PREFIX' %}",
|
prefix: global_settings.BUILDORDER_REFERENCE_PREFIX,
|
||||||
},
|
},
|
||||||
title: {},
|
title: {},
|
||||||
part: {},
|
part: {},
|
||||||
@ -232,7 +232,7 @@ function loadBuildOrderAllocationTable(table, options={}) {
|
|||||||
switchable: false,
|
switchable: false,
|
||||||
title: '{% trans "Build Order" %}',
|
title: '{% trans "Build Order" %}',
|
||||||
formatter: function(value, row) {
|
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}`;
|
var ref = `${prefix}${row.build_detail.reference}`;
|
||||||
|
|
||||||
@ -848,7 +848,7 @@ function loadBuildTable(table, options) {
|
|||||||
switchable: true,
|
switchable: true,
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
|
|
||||||
var prefix = "{% settings_value 'BUILDORDER_REFERENCE_PREFIX' %}";
|
var prefix = global_settings.BUILDORDER_REFERENCE_PREFIX;
|
||||||
|
|
||||||
if (prefix) {
|
if (prefix) {
|
||||||
value = `${prefix}${value}`;
|
value = `${prefix}${value}`;
|
@ -9,7 +9,7 @@ function createSalesOrder(options={}) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
fields: {
|
fields: {
|
||||||
reference: {
|
reference: {
|
||||||
prefix: '{% settings_value "SALESORDER_REFERENCE_PREFIX" %}',
|
prefix: global_settings.SALESORDER_REFERENCE_PREFIX,
|
||||||
},
|
},
|
||||||
customer: {
|
customer: {
|
||||||
value: options.customer,
|
value: options.customer,
|
||||||
@ -40,7 +40,7 @@ function createPurchaseOrder(options={}) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
fields: {
|
fields: {
|
||||||
reference: {
|
reference: {
|
||||||
prefix: "{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}",
|
prefix: global_settings.PURCHASEORDER_REFERENCE_PREFIX,
|
||||||
},
|
},
|
||||||
supplier: {
|
supplier: {
|
||||||
value: options.supplier,
|
value: options.supplier,
|
||||||
@ -214,7 +214,7 @@ function loadPurchaseOrderTable(table, options) {
|
|||||||
switchable: false,
|
switchable: false,
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
|
|
||||||
var prefix = "{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}";
|
var prefix = global_settings.PURCHASEORDER_REFERENCE_PREFIX;
|
||||||
|
|
||||||
if (prefix) {
|
if (prefix) {
|
||||||
value = `${prefix}${value}`;
|
value = `${prefix}${value}`;
|
||||||
@ -309,7 +309,7 @@ function loadSalesOrderTable(table, options) {
|
|||||||
title: '{% trans "Sales Order" %}',
|
title: '{% trans "Sales Order" %}',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
|
|
||||||
var prefix = "{% settings_value 'SALESORDER_REFERENCE_PREFIX' %}";
|
var prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
|
||||||
|
|
||||||
if (prefix) {
|
if (prefix) {
|
||||||
value = `${prefix}${value}`;
|
value = `${prefix}${value}`;
|
||||||
@ -423,7 +423,7 @@ function loadSalesOrderAllocationTable(table, options={}) {
|
|||||||
switchable: false,
|
switchable: false,
|
||||||
formatter: function(value, row) {
|
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}`;
|
var ref = `${prefix}${row.order_detail.reference}`;
|
||||||
|
|
@ -6,8 +6,6 @@
|
|||||||
* Requires api.js to be loaded first
|
* Requires api.js to be loaded first
|
||||||
*/
|
*/
|
||||||
|
|
||||||
{% settings_value 'BARCODE_ENABLE' as barcodes %}
|
|
||||||
|
|
||||||
function stockStatusCodes() {
|
function stockStatusCodes() {
|
||||||
return [
|
return [
|
||||||
{% for code in StockStatus.list %}
|
{% for code in StockStatus.list %}
|
||||||
@ -704,8 +702,7 @@ function loadStockTable(table, options) {
|
|||||||
name: 'stock',
|
name: 'stock',
|
||||||
original: original,
|
original: original,
|
||||||
showColumns: true,
|
showColumns: true,
|
||||||
{% settings_value 'STOCK_GROUP_BY_PART' as group_by_part %}
|
{% if False %}
|
||||||
{% if group_by_part %}
|
|
||||||
groupByField: options.groupByField || 'part',
|
groupByField: options.groupByField || 'part',
|
||||||
groupBy: grouping,
|
groupBy: grouping,
|
||||||
groupByFormatter: function(field, id, data) {
|
groupByFormatter: function(field, id, data) {
|
||||||
@ -1011,14 +1008,13 @@ function loadStockTable(table, options) {
|
|||||||
title: '{% trans "Stocktake" %}',
|
title: '{% trans "Stocktake" %}',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
{% settings_value "STOCK_ENABLE_EXPIRY" as expiry %}
|
|
||||||
{% if expiry %}
|
|
||||||
{
|
{
|
||||||
field: 'expiry_date',
|
field: 'expiry_date',
|
||||||
title: '{% trans "Expiry Date" %}',
|
title: '{% trans "Expiry Date" %}',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
|
visible: global_settings.STOCK_ENABLE_EXPIRY,
|
||||||
|
switchable: global_settings.STOCK_ENABLE_EXPIRY,
|
||||||
},
|
},
|
||||||
{% endif %}
|
|
||||||
{
|
{
|
||||||
field: 'updated',
|
field: 'updated',
|
||||||
title: '{% trans "Last Updated" %}',
|
title: '{% trans "Last Updated" %}',
|
||||||
@ -1037,7 +1033,7 @@ function loadStockTable(table, options) {
|
|||||||
|
|
||||||
if (row.purchase_order_reference) {
|
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;
|
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(
|
linkButtonsToSelection(
|
||||||
table,
|
table,
|
||||||
[
|
buttons,
|
||||||
'#stock-print-options',
|
|
||||||
{% if barcodes %}
|
|
||||||
'#stock-barcode-options',
|
|
||||||
{% endif %}
|
|
||||||
'#stock-options',
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
@ -1138,7 +1137,7 @@ function loadStockTable(table, options) {
|
|||||||
printTestReports(items);
|
printTestReports(items);
|
||||||
})
|
})
|
||||||
|
|
||||||
{% if barcodes %}
|
if (global_settings.BARCODE_ENABLE) {
|
||||||
$('#multi-item-barcode-scan-into-location').click(function() {
|
$('#multi-item-barcode-scan-into-location').click(function() {
|
||||||
var selections = $('#stock-table').bootstrapTable('getSelections');
|
var selections = $('#stock-table').bootstrapTable('getSelections');
|
||||||
|
|
||||||
@ -1150,7 +1149,7 @@ function loadStockTable(table, options) {
|
|||||||
|
|
||||||
scanItemsIntoLocation(items);
|
scanItemsIntoLocation(items);
|
||||||
});
|
});
|
||||||
{% endif %}
|
}
|
||||||
|
|
||||||
$('#multi-item-stocktake').click(function() {
|
$('#multi-item-stocktake').click(function() {
|
||||||
stockAdjustment('count');
|
stockAdjustment('count');
|
@ -121,7 +121,8 @@ function getAvailableTableFilters(tableKey) {
|
|||||||
|
|
||||||
// Filters for the "Stock" table
|
// Filters for the "Stock" table
|
||||||
if (tableKey == 'stock') {
|
if (tableKey == 'stock') {
|
||||||
return {
|
|
||||||
|
var filters = {
|
||||||
active: {
|
active: {
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
title: '{% trans "Active parts" %}',
|
title: '{% trans "Active parts" %}',
|
||||||
@ -147,19 +148,6 @@ function getAvailableTableFilters(tableKey) {
|
|||||||
title: '{% trans "Depleted" %}',
|
title: '{% trans "Depleted" %}',
|
||||||
description: '{% trans "Show stock items which are 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: {
|
in_stock: {
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
title: '{% trans "In Stock" %}',
|
title: '{% trans "In Stock" %}',
|
||||||
@ -216,6 +204,23 @@ function getAvailableTableFilters(tableKey) {
|
|||||||
description: '{% trans "Show stock items which have a purchase price set" %}',
|
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
|
// 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_CONFIG_FILE="${INVENTREE_DATA_DIR}/config.yaml"
|
||||||
ENV INVENTREE_SECRET_KEY_FILE="${INVENTREE_DATA_DIR}/secret_key.txt"
|
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
|
# Default web server port is 8000
|
||||||
ENV INVENTREE_WEB_PORT="8000"
|
ENV INVENTREE_WEB_PORT="8000"
|
||||||
|
|
||||||
|
@ -1,6 +1,22 @@
|
|||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
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
|
workers = multiprocessing.cpu_count() * 2 + 1
|
||||||
|
|
||||||
|
logger.info(f"Starting gunicorn server with {workers} workers")
|
||||||
|
|
||||||
max_requests = 1000
|
max_requests = 1000
|
||||||
max_requests_jitter = 50
|
max_requests_jitter = 50
|
||||||
|
Loading…
Reference in New Issue
Block a user