Compare commits

...

21 Commits

Author SHA1 Message Date
github-actions[bot]
1d51b2a058 Email config fix (#5336) (#5338)
* Change for DEFAULT_FROM_EMAIL

- Use USERNAME if not specified

(cherry picked from commit 487ac917c90e9fe3da4effaa9326b707ceecd321)

* Email configuration fails if DEFAULT_FROM_EMAIL not set

(cherry picked from commit 01e573c3a2702e7c21ed13b0cb44280c89d3dee1)

* Docs update

(cherry picked from commit bfedb9cf87)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-25 11:45:17 +10:00
github-actions[bot]
08f9bebdf0 Fix admin url to point to right model (#5319) (#5321)
(cherry picked from commit 9b377ccfbf)

Co-authored-by: Marcel Pörner <me@nerade.de>
2023-07-23 22:38:27 +10:00
github-actions[bot]
6d6629f11c Stock installed table fix (#5305) (#5306)
* Prevent installed items from being hidden

* Fix parent / child relationship

(cherry picked from commit f70294b247)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-21 23:57:00 +10:00
github-actions[bot]
db88fbda11 Fix company index page title (#5288) (#5291)
(cherry picked from commit 3baa640d70)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-20 10:07:39 +10:00
github-actions[bot]
49c9b5b1aa Docker build: Update python deps (#5270) (#5271)
* Update python deps

* Update requirements.in

* Fix requirements-dev.txt

(cherry picked from commit b717011f06)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-18 20:11:29 +10:00
github-actions[bot]
e1a0e79ead Fix settings function callback (#5259) (#5262)
* fix settings function callback

* merge instance filters and passed keys

(cherry picked from commit df77305d60)

Co-authored-by: Matthias Mair <code@mjmair.com>
2023-07-17 20:23:44 +10:00
github-actions[bot]
ab22f2a04d Fix language code for pt-br (#5256) (#5257)
- Has to be lowercase in settings.py to work correctly

(cherry picked from commit 20b59c3575)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-16 19:37:27 +10:00
github-actions[bot]
8a58bf5ffa Only update theme if value provided (#5240) (#5241)
- Handles case where null or invalid value provided

(cherry picked from commit 41167f22c9)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-13 20:39:28 +10:00
Oliver
6730098bac Update version.py (#5238)
Bump version number to 0.12.2
2023-07-13 15:13:47 +10:00
github-actions[bot]
93b44ad8e6 fix typo (#5236) (#5237)
(cherry picked from commit bd1689095d)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-13 11:03:28 +10:00
github-actions[bot]
9b5e828b87 Protected settings fix (#5229) (#5231)
* Hide protected setting in settings view

* Implement custom serializer for setting value

- Return '***' if the setting is protected

* Implement to_internal_value

* Stringify

* Add protected setting to sample plugin

* Unit tests for plugin settings API

* Update unit test

(cherry picked from commit 01f2aa5f74)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-12 16:31:28 +10:00
github-actions[bot]
cf5d637678 Add missing callback for attachment delete button (#5219) (#5220)
(cherry picked from commit b3dcc28bd9)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-11 11:30:57 +10:00
github-actions[bot]
feb2acf668 Fix link to SalesOrder in stock history table (#5210) (#5211)
(cherry picked from commit 8fb7612894)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-10 13:23:09 +10:00
Oliver
0017570dd3 Bump version number to 0.12.1 (#5201) 2023-07-07 14:25:30 +10:00
github-actions[bot]
4c41a50bb1 Fix allocation check for completing build order (#5199) (#5200)
- Allocation check only applies to untracked line items

(cherry picked from commit 1f81daadf6)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-07 13:48:18 +10:00
github-actions[bot]
eab3fdcf2c Fix quantity aggregation for stock table (#5188) (#5190)
* Fix quantity aggregation for stock table

- Stock quantity can only be added together if units are the same

* Add stock total footer to part table

(cherry picked from commit 773dd3b210)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-06 12:55:22 +10:00
github-actions[bot]
c59eee7359 Param fix (#5183) (#5184)
* Handle AttributeError in convert_physical_value

* Added new unit test

(cherry picked from commit 9abcc0ec34)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-06 11:11:27 +10:00
github-actions[bot]
4a5ebf8f01 Handle exception when creating default labels (#5163) (#5166)
* Handle exception when creating default labels

- Running workers in parallel may cause race conditions
- Catch any exception which is raised

* Prevent password from being logged

* Update default timeout for docker

(cherry picked from commit 8b730884d7)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-04 22:54:21 +10:00
github-actions[bot]
698798fee7 Order table improvements (#5151) (#5152)
- prevent "double loading" of order tables

(cherry picked from commit 17c2070503)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-04 16:23:43 +10:00
github-actions[bot]
2660889879 Rendering fix for build allocation table (#5145) (#5149)
- Fix link to part
- Fix link to stock item

(cherry picked from commit 5f61b5f120)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-07-04 13:40:40 +10:00
github-actions[bot]
01aaf95a0e fix: add missing build model property (#5127) (#5132)
* fix: add missing virtual build property

* chore: improve docstring

(cherry picked from commit 2e7c86ff92)

Co-authored-by: Mark Oude Elberink <mark@oude-elberink.de>
2023-07-03 10:04:02 +10:00
30 changed files with 280 additions and 67 deletions

View File

@@ -195,8 +195,8 @@ class InvenTreeConfig(AppConfig):
else:
new_user = user.objects.create_superuser(add_user, add_email, add_password)
logger.info(f'User {str(new_user)} was created!')
except IntegrityError as _e:
logger.warning(f'The user "{add_user}" could not be created due to the following error:\n{str(_e)}')
except IntegrityError:
logger.warning(f'The user "{add_user}" could not be created')
# do not try again
settings.USER_ADDED = True

View File

@@ -91,7 +91,7 @@ def convert_physical_value(value: str, unit: str = None):
# At this point we *should* have a valid pint value
# To double check, look at the maginitude
float(val.magnitude)
except (TypeError, ValueError):
except (TypeError, ValueError, AttributeError):
error = _('Provided value is not a valid number')
except (pint.errors.UndefinedUnitError, pint.errors.DefinitionSyntaxError):
error = _('Provided value has an invalid unit')

View File

@@ -17,6 +17,7 @@ def is_email_configured():
NOTE: This does not check if the configuration is valid!
"""
configured = True
testing = settings.TESTING
if InvenTree.ready.isInTestMode():
return False
@@ -28,17 +29,24 @@ def is_email_configured():
configured = False
# Display warning unless in test mode
if not settings.TESTING: # pragma: no cover
if not testing: # pragma: no cover
logger.debug("EMAIL_HOST is not configured")
# Display warning unless in test mode
if not settings.EMAIL_HOST_USER and not settings.TESTING: # pragma: no cover
if not settings.EMAIL_HOST_USER and not testing: # pragma: no cover
logger.debug("EMAIL_HOST_USER is not configured")
# Display warning unless in test mode
if not settings.EMAIL_HOST_PASSWORD and not settings.TESTING: # pragma: no cover
if not settings.EMAIL_HOST_PASSWORD and testing: # pragma: no cover
logger.debug("EMAIL_HOST_PASSWORD is not configured")
# Email sender must be configured
if not settings.DEFAULT_FROM_EMAIL:
configured = False
if not testing: # pragma: no cover
logger.warning("DEFAULT_FROM_EMAIL is not configured")
return configured

View File

@@ -757,14 +757,14 @@ LANGUAGES = [
('no', _('Norwegian')),
('pl', _('Polish')),
('pt', _('Portuguese')),
('pt-BR', _('Portuguese (Brazilian)')),
('pt-br', _('Portuguese (Brazilian)')),
('ru', _('Russian')),
('sl', _('Slovenian')),
('sv', _('Swedish')),
('th', _('Thai')),
('tr', _('Turkish')),
('vi', _('Vietnamese')),
('zh-hans', _('Chinese')),
('zh-hans', _('Chinese (Simplified)')),
]
# Testing interface translations
@@ -822,6 +822,10 @@ EMAIL_USE_SSL = get_boolean_setting('INVENTREE_EMAIL_SSL', 'email.ssl', False)
DEFAULT_FROM_EMAIL = get_setting('INVENTREE_EMAIL_SENDER', 'email.sender', '')
# If "from" email not specified, default to the username
if not DEFAULT_FROM_EMAIL:
DEFAULT_FROM_EMAIL = get_setting('INVENTREE_EMAIL_USERNAME', 'email.username', '')
EMAIL_USE_LOCALTIME = False
EMAIL_TIMEOUT = 60

View File

@@ -56,6 +56,23 @@ class ConversionTest(TestCase):
q = InvenTree.conversion.convert_physical_value(val).to_base_units()
self.assertEqual(q.magnitude, expected)
def test_invalid_values(self):
"""Test conversion of invalid inputs"""
inputs = [
'-',
';;',
'-x',
'?',
'--',
'+',
'++',
]
for val in inputs:
with self.assertRaises(ValidationError):
InvenTree.conversion.convert_physical_value(val)
class ValidatorTest(TestCase):
"""Simple tests for custom field validators."""

View File

@@ -18,7 +18,7 @@ from dulwich.repo import NotGitRepository, Repo
from .api_version import INVENTREE_API_VERSION
# InvenTree software version
INVENTREE_SW_VERSION = "0.12.0"
INVENTREE_SW_VERSION = "0.12.2"
# Discover git
try:

View File

@@ -640,8 +640,12 @@ class AppearanceSelectView(RedirectView):
user_theme = common_models.ColorTheme()
user_theme.user = request.user
user_theme.name = theme
user_theme.save()
if theme:
try:
user_theme.name = theme
user_theme.save()
except Exception:
pass
return redirect(reverse_lazy('settings'))

View File

@@ -361,6 +361,11 @@ class Build(MPTTModel, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.
return self.build_lines.filter(bom_item__sub_part__trackable=False)
@property
def are_untracked_parts_allocated(self):
"""Returns True if all untracked parts are allocated for this BuildOrder."""
return self.is_fully_allocated(tracked=False)
def has_untracked_line_items(self):
"""Returns True if this BuildOrder has non trackable BomItems."""
return self.has_untracked_line_items.count() > 0

View File

@@ -630,7 +630,7 @@ class BuildCompleteSerializer(serializers.Serializer):
return {
'overallocated': build.is_overallocated(),
'allocated': build.is_fully_allocated(),
'allocated': build.are_untracked_parts_allocated,
'remaining': build.remaining,
'incomplete': build.incomplete_count,
}
@@ -663,7 +663,7 @@ class BuildCompleteSerializer(serializers.Serializer):
"""Check if the 'accept_unallocated' field is required"""
build = self.context['build']
if not build.is_fully_allocated() and not value:
if not build.are_untracked_parts_allocated and not value:
raise ValidationError(_('Required stock has not been fully allocated'))
return value

View File

@@ -190,7 +190,7 @@ class BaseInvenTreeSetting(models.Model):
kwargs: Keyword arguments to pass to the function
"""
# Get action
setting = self.get_setting_definition(self.key, *args, **kwargs)
setting = self.get_setting_definition(self.key, *args, **{**self.get_filters_for_instance(), **kwargs})
settings_fnc = setting.get(reference, None)
# Execute if callable

View File

@@ -13,6 +13,25 @@ from InvenTree.serializers import (InvenTreeImageSerializerField,
InvenTreeModelSerializer)
class SettingsValueField(serializers.Field):
"""Custom serializer field for a settings value."""
def get_attribute(self, instance):
"""Return the object instance, not the attribute value."""
return instance
def to_representation(self, instance):
"""Return the value of the setting:
- Protected settings are returned as '***'
"""
return '***' if instance.protected else str(instance.value)
def to_internal_value(self, data):
"""Return the internal value of the setting"""
return str(data)
class SettingsSerializer(InvenTreeModelSerializer):
"""Base serializer for a settings object."""
@@ -30,6 +49,8 @@ class SettingsSerializer(InvenTreeModelSerializer):
api_url = serializers.CharField(read_only=True)
value = SettingsValueField()
def get_choices(self, obj):
"""Returns the choices available for a given item."""
results = []
@@ -45,16 +66,6 @@ class SettingsSerializer(InvenTreeModelSerializer):
return results
def get_value(self, obj):
"""Make sure protected values are not returned."""
# never return protected values
if obj.protected:
result = '***'
else:
result = obj.value
return result
class GlobalSettingsSerializer(SettingsSerializer):
"""Serializer for the InvenTreeSetting model."""

View File

@@ -5,7 +5,7 @@
{% load inventree_extras %}
{% block page_title %}
{% inventree_title %} | {% trans "Supplier List" %}
{% inventree_title %}{% if title %} | {{ title }}{% endif %}
{% endblock page_title %}
{% block heading %}

View File

@@ -27,7 +27,7 @@
{% block actions %}
{% if user.is_staff and perms.company.change_company %}
{% url 'admin:company_supplierpart_change' part.pk as url %}
{% url 'admin:company_manufacturerpart_change' part.pk as url %}
{% include "admin_button.html" with url=url %}
{% endif %}
{% if roles.purchase_order.change %}

View File

@@ -182,13 +182,15 @@ class LabelConfig(AppConfig):
logger.info(f"Creating entry for {model} '{label['name']}'")
model.objects.create(
name=label['name'],
description=label['description'],
label=filename,
filters='',
enabled=True,
width=label['width'],
height=label['height'],
)
return
try:
model.objects.create(
name=label['name'],
description=label['description'],
label=filename,
filters='',
enabled=True,
width=label['width'],
height=label['height'],
)
except Exception:
logger.warning(f"Failed to create label '{label['name']}'")

View File

@@ -72,6 +72,12 @@ class SampleIntegrationPlugin(AppMixin, SettingsMixin, UrlsMixin, NavigationMixi
'description': 'Select a part object from the database',
'model': 'part.part',
},
'PROTECTED_SETTING': {
'name': 'Protected Setting',
'description': 'A protected setting, hidden from the UI',
'default': 'ABC-123',
'protected': True,
}
}
NAVIGATION = [

View File

@@ -193,3 +193,76 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
with self.assertRaises(NotFound) as exc:
check_plugin(plugin_slug=None, plugin_pk='123')
self.assertEqual(str(exc.exception.detail), "Plugin '123' not installed")
def test_plugin_settings(self):
"""Test plugin settings access via the API"""
# Ensure we have superuser permissions
self.user.is_superuser = True
self.user.save()
# Activate the 'sample' plugin via the API
cfg = PluginConfig.objects.filter(key='sample').first()
url = reverse('api-plugin-detail-activate', kwargs={'pk': cfg.pk})
self.client.patch(url, {}, expected_code=200)
# Valid plugin settings endpoints
valid_settings = [
'SELECT_PART',
'API_KEY',
'NUMERICAL_SETTING',
]
for key in valid_settings:
response = self.get(
reverse('api-plugin-setting-detail', kwargs={
'plugin': 'sample',
'key': key
}))
self.assertEqual(response.data['key'], key)
# Test that an invalid setting key raises a 404 error
response = self.get(
reverse('api-plugin-setting-detail', kwargs={
'plugin': 'sample',
'key': 'INVALID_SETTING'
}),
expected_code=404
)
# Test that a protected setting returns hidden value
response = self.get(
reverse('api-plugin-setting-detail', kwargs={
'plugin': 'sample',
'key': 'PROTECTED_SETTING'
}),
expected_code=200
)
self.assertEqual(response.data['value'], '***')
# Test that we can update a setting value
response = self.patch(
reverse('api-plugin-setting-detail', kwargs={
'plugin': 'sample',
'key': 'NUMERICAL_SETTING'
}),
{
'value': 456
},
expected_code=200
)
self.assertEqual(response.data['value'], '456')
# Retrieve the value again
response = self.get(
reverse('api-plugin-setting-detail', kwargs={
'plugin': 'sample',
'key': 'NUMERICAL_SETTING'
}),
expected_code=200
)
self.assertEqual(response.data['value'], '456')

View File

@@ -22,7 +22,9 @@
{{ setting.description }}
</td>
<td>
{% if setting.is_bool %}
{% if setting.protected %}
<span style='color: red;'>***</span> <span class='fas fa-lock icon-red'></span>
{% elif setting.is_bool %}
{% include "InvenTree/settings/setting_boolean.html" %}
{% else %}
<div id='setting-{{ setting.pk }}'>

View File

@@ -281,10 +281,20 @@ function loadAttachmentTable(url, options) {
sidePagination: 'server',
onPostBody: function() {
// Add callback for 'delete' button
if (permissions.delete) {
$(table).find('.button-attachment-delete').click(function() {
let pk = $(this).attr('pk');
let attachments = $(table).bootstrapTable('getRowByUniqueId', pk);
deleteAttachments([attachments], url, options);
});
}
// Add callback for 'edit' button
if (permissions.change) {
$(table).find('.button-attachment-edit').click(function() {
var pk = $(this).attr('pk');
let pk = $(this).attr('pk');
constructForm(`${url}${pk}/`, {
fields: {

View File

@@ -2173,9 +2173,6 @@ function loadBuildTable(table, options) {
customView: function(data) {
return `<div id='build-order-calendar'></div>`;
},
onRefresh: function() {
loadBuildTable(table, options);
},
onLoadSuccess: function() {
if (tree_enable) {
@@ -2255,16 +2252,16 @@ function renderBuildLineAllocationTable(element, build_line, options={}) {
{
field: 'part',
title: '{% trans "Part" %}',
formatter: function(value, row) {
formatter: function(_value, row) {
let html = imageHoverIcon(row.part_detail.thumbnail);
html += renderLink(row.part_detail.full_name, `/part/${value}/`);
html += renderLink(row.part_detail.full_name, `/part/${row.part_detail.pk}/`);
return html;
}
},
{
field: 'quantity',
title: '{% trans "Allocated Quantity" %}',
formatter: function(value, row) {
formatter: function(_value, row) {
let text = '';
let url = '';
let serial = row.serial;
@@ -2294,8 +2291,8 @@ function renderBuildLineAllocationTable(element, build_line, options={}) {
title: '{% trans "Location" %}',
formatter: function(value, row) {
if (row.location_detail) {
var text = shortenString(row.location_detail.pathstring);
var url = `/stock/location/${row.location}/`;
let text = shortenString(row.location_detail.pathstring);
let url = `/stock/location/${row.location_detail.pk}/`;
return renderLink(text, url);
} else {

View File

@@ -1404,6 +1404,7 @@ function createPartParameter(part_id, options={}) {
function editPartParameter(param_id, options={}) {
options.fields = partParameterFields();
options.title = '{% trans "Edit Parameter" %}';
options.focus = 'data';
options.processBeforeUpload = function(data) {
// Convert data to string
@@ -2367,6 +2368,38 @@ function loadPartTable(table, url, options={}) {
});
return text;
},
footerFormatter: function(data) {
// Display "total" stock quantity of all rendered rows
// Requires that all parts have the same base units!
let total = 0;
let units = new Set();
data.forEach(function(row) {
units.add(row.units || null);
if (row.total_in_stock != null) {
total += row.total_in_stock;
}
});
if (data.length == 0) {
return '-';
} else if (units.size > 1) {
return '-';
} else {
let output = `${total}`;
if (units.size == 1) {
let unit = units.values().next().value;
if (unit) {
output += ` [${unit}]`;
}
}
return output;
}
}
});
@@ -2442,6 +2475,7 @@ function loadPartTable(table, url, options={}) {
showColumns: true,
showCustomView: grid_view,
showCustomViewButton: false,
showFooter: true,
onPostBody: function() {
grid_view = inventreeLoad('part-grid-view') == 1;
if (grid_view) {

View File

@@ -1759,9 +1759,6 @@ function loadPurchaseOrderTable(table, options) {
customView: function(data) {
return `<div id='purchase-order-calendar'></div>`;
},
onRefresh: function() {
loadPurchaseOrderTable(table, options);
},
onLoadSuccess: function() {
if (display_mode == 'calendar') {

View File

@@ -262,9 +262,6 @@ function loadReturnOrderTable(table, options={}) {
formatNoMatches: function() {
return '{% trans "No return orders found" %}';
},
onRefresh: function() {
loadReturnOrderTable(table, options);
},
onLoadSuccess: function() {
// TODO
},

View File

@@ -735,9 +735,6 @@ function loadSalesOrderTable(table, options) {
customView: function(data) {
return `<div id='purchase-order-calendar'></div>`;
},
onRefresh: function() {
loadSalesOrderTable(table, options);
},
onLoadSuccess: function() {
if (display_mode == 'calendar') {

View File

@@ -2068,13 +2068,36 @@ function loadStockTable(table, options) {
// Display "total" stock quantity of all rendered rows
let total = 0;
// Keep track of the whether all units are the same
// If different units are found, we cannot aggregate the quantities
let units = new Set();
data.forEach(function(row) {
units.add(row.part_detail.units || null);
if (row.quantity != null) {
total += row.quantity;
}
});
return total;
if (data.length == 0) {
return '-';
} else if (units.size > 1) {
return '-';
} else {
let output = `${total}`;
if (units.size == 1) {
let unit = units.values().next().value;
if (unit) {
output += ` [${unit}]`;
}
}
return output;
}
}
};
@@ -2352,6 +2375,10 @@ function loadStockTable(table, options) {
let row = table.bootstrapTable('getRowByUniqueId', stock_item);
row.installed_items_received = true;
for (let ii = 0; ii < response.length; ii++) {
response[ii].belongs_to_item = stock_item;
}
table.bootstrapTable('updateByUniqueId', stock_item, row, true);
table.bootstrapTable('append', response);
@@ -2367,6 +2394,7 @@ function loadStockTable(table, options) {
}
let parent_id = 'top-level';
let loaded = false;
table.inventreeTable({
method: 'get',
@@ -2382,13 +2410,25 @@ function loadStockTable(table, options) {
showFooter: true,
columns: columns,
treeEnable: show_installed_items,
rootParentId: parent_id,
parentIdField: 'belongs_to',
rootParentId: show_installed_items ? parent_id : null,
parentIdField: show_installed_items ? 'belongs_to_item' : null,
uniqueId: 'pk',
idField: 'pk',
treeShowField: 'part',
onPostBody: function() {
treeShowField: show_installed_items ? 'part' : null,
onLoadSuccess: function(data) {
let records = data.results || data;
// Set the 'parent' ID for each root item
if (!loaded && show_installed_items) {
for (let i = 0; i < records.length; i++) {
records[i].belongs_to_item = parent_id;
}
loaded = true;
$(table).bootstrapTable('load', records);
}
},
onPostBody: function() {
if (show_installed_items) {
table.treegrid({
treeColumn: 1,
@@ -2618,7 +2658,7 @@ function loadStockLocationTable(table, options) {
} else {
html += `
<a href='#' pk='${row.pk}' class='load-sub-location'>
<span class='fas fa-sync-alt' title='{% trans "Load Subloactions" %}'></span>
<span class='fas fa-sync-alt' title='{% trans "Load Sublocations" %}'></span>
</a> `;
}
}
@@ -2809,7 +2849,7 @@ function loadStockTrackingTable(table, options) {
if (details.salesorder_detail) {
html += renderLink(
details.salesorder_detail.reference,
`/order/sales-order/${details.salesorder}`
`/order/sales-order/${details.salesorder}/`
);
} else {
html += `<em>{% trans "Sales Order no longer exists" %}</em>`;

View File

@@ -42,7 +42,7 @@ INVENTREE_DB_PORT=5432
#INVENTREE_CACHE_PORT=6379
# Options for gunicorn server
INVENTREE_GUNICORN_TIMEOUT=30
INVENTREE_GUNICORN_TIMEOUT=90
# Enable custom plugins?
INVENTREE_PLUGINS_ENABLED=False

View File

@@ -2,7 +2,7 @@
# Basic package requirements
invoke>=1.4.0 # Invoke build tool
pyyaml>=6.0
pyyaml>=6.0.1
setuptools==65.6.3
wheel>=0.37.0

View File

@@ -155,9 +155,16 @@ The following email settings are available:
| INVENTREE_EMAIL_PASSWORD | email.password | Email account password | *Not specified* |
| INVENTREE_EMAIL_TLS | email.tls | Enable TLS support | False |
| INVENTREE_EMAIL_SSL | email.ssl | Enable SSL support | False |
| INVENTREE_EMAIL_SENDER | email.sender | Name of sender | *Not specified* |
| INVENTREE_EMAIL_SENDER | email.sender | Sending email address | *Not specified* |
| INVENTREE_EMAIL_PREFIX | email.prefix | Prefix for subject text | [InvenTree] |
### Sender Email
The "sender" email address is the address from which InvenTree emails are sent (by default) and must be specified for outgoing emails to function:
!!! info "Fallback"
If `INVENTREE_EMAIL_SENDER` is not provided, the system will fall back to `INVENTREE_EMAIL_USERNAME` (if the username is a valid email address)
## Supported Currencies
The currencies supported by InvenTree must be specified in the [configuration file](#configuration-file).

View File

@@ -86,7 +86,7 @@ pytz==2023.3
# via
# -c requirements.txt
# django
pyyaml==6.0
pyyaml==6.0.1
# via
# -c requirements.txt
# pre-commit

View File

@@ -40,6 +40,7 @@ pillow # Image manipulation
pint==0.21 # Unit conversion # FIXED 2023-05-30 breaks tests https://github.com/matmair/InvenTree/actions/runs/5095665936/jobs/9160852560
python-barcode[images] # Barcode generator
python-dotenv # Environment variable management
pyyaml>=6.0.1 # YAML parsing
qrcode[pil] # QR code generator
rapidfuzz==0.7.6 # Fuzzy string matching
regex # Advanced regular expressions

View File

@@ -239,8 +239,9 @@ pytz==2023.3
# django-dbbackup
# djangorestframework
# icalendar
pyyaml==6.0
pyyaml==6.0.1
# via
# -r requirements.in
# drf-spectacular
# tablib
qrcode[pil]==7.4.2