2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-05-23 09:35:30 +00:00

Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters
2022-11-05 01:07:23 +11:00
72 changed files with 16379 additions and 15116 deletions
+4 -2
View File
@@ -134,7 +134,7 @@ class InvenTreeAPITestCase(UserMixin, APITestCase):
if expected_code is not None: if expected_code is not None:
if response.status_code != expected_code: if response.status_code != expected_code:
print(f"Unexpected response at '{url}':") print(f"Unexpected response at '{url}': status_code = {response.status_code}")
print(response.data) print(response.data)
self.assertEqual(response.status_code, expected_code) self.assertEqual(response.status_code, expected_code)
@@ -143,11 +143,13 @@ class InvenTreeAPITestCase(UserMixin, APITestCase):
def post(self, url, data=None, expected_code=None, format='json'): def post(self, url, data=None, expected_code=None, format='json'):
"""Issue a POST request.""" """Issue a POST request."""
response = self.client.post(url, data=data, format=format)
# Set default value - see B006
if data is None: if data is None:
data = {} data = {}
response = self.client.post(url, data=data, format=format)
if expected_code is not None: if expected_code is not None:
if response.status_code != expected_code: if response.status_code != expected_code:
+4 -1
View File
@@ -2,11 +2,14 @@
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 78 INVENTREE_API_VERSION = 79
""" """
Increment this API version number whenever there is a significant change to the API that any clients need to know about Increment this API version number whenever there is a significant change to the API that any clients need to know about
v79 -> 2022-11-03 : https://github.com/inventree/InvenTree/pull/3895
- Add metadata to Company
v78 -> 2022-10-25 : https://github.com/inventree/InvenTree/pull/3854 v78 -> 2022-10-25 : https://github.com/inventree/InvenTree/pull/3854
- Make PartCategory to be filtered by name and description - Make PartCategory to be filtered by name and description
@@ -127,13 +127,6 @@ function inventreeDocReady() {
loadBrandIcon($(this), $(this).attr('brand_name')); loadBrandIcon($(this), $(this).attr('brand_name'));
}); });
// Callback for "admin view" button
$('#admin-button, .admin-button').click(function() {
var url = $(this).attr('url');
location.href = url;
});
// Display any cached alert messages // Display any cached alert messages
showCachedAlerts(); showCachedAlerts();
+6 -2
View File
@@ -178,11 +178,15 @@ class APITests(InvenTreeAPITestCase):
def test_with_roles(self): def test_with_roles(self):
"""Assign some roles to the user.""" """Assign some roles to the user."""
self.basicAuth() self.basicAuth()
response = self.get(reverse('api-user-roles'))
url = reverse('api-user-roles')
response = self.get(url)
self.assignRole('part.delete') self.assignRole('part.delete')
self.assignRole('build.change') self.assignRole('build.change')
response = self.get(reverse('api-user-roles'))
response = self.get(url)
roles = response.data['roles'] roles = response.data['roles']
+1 -1
View File
@@ -213,7 +213,7 @@ class BuildTest(BuildAPITest):
"location": 1, "location": 1,
"status": 50, # Item requires attention "status": 50, # Item requires attention
}, },
expected_code=201 expected_code=201,
) )
self.assertEqual(self.build.incomplete_outputs.count(), 0) self.assertEqual(self.build.incomplete_outputs.count(), 0)
+12 -1
View File
@@ -933,6 +933,17 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'validator': bool, 'validator': bool,
}, },
'BARCODE_INPUT_DELAY': {
'name': _('Barcode Input Delay'),
'description': _('Barcode input processing delay time'),
'default': 50,
'validator': [
int,
MinValueValidator(1),
],
'units': 'ms',
},
'BARCODE_WEBCAM_SUPPORT': { 'BARCODE_WEBCAM_SUPPORT': {
'name': _('Barcode Webcam Support'), 'name': _('Barcode Webcam Support'),
'description': _('Allow barcode scanning via webcam in browser'), 'description': _('Allow barcode scanning via webcam in browser'),
@@ -1321,7 +1332,7 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'PLUGIN_ON_STARTUP': { 'PLUGIN_ON_STARTUP': {
'name': _('Check plugins on startup'), 'name': _('Check plugins on startup'),
'description': _('Check that all plugins are installed on startup - enable in container enviroments'), 'description': _('Check that all plugins are installed on startup - enable in container environments'),
'default': False, 'default': False,
'validator': bool, 'validator': bool,
'requires_restart': True, 'requires_restart': True,
+18 -2
View File
@@ -10,7 +10,9 @@ from rest_framework import filters
from InvenTree.api import AttachmentMixin, ListCreateDestroyAPIView from InvenTree.api import AttachmentMixin, ListCreateDestroyAPIView
from InvenTree.filters import InvenTreeOrderingFilter from InvenTree.filters import InvenTreeOrderingFilter
from InvenTree.helpers import str2bool from InvenTree.helpers import str2bool
from InvenTree.mixins import ListCreateAPI, RetrieveUpdateDestroyAPI from InvenTree.mixins import (ListCreateAPI, RetrieveUpdateAPI,
RetrieveUpdateDestroyAPI)
from plugin.serializers import MetadataSerializer
from .models import (Company, ManufacturerPart, ManufacturerPartAttachment, from .models import (Company, ManufacturerPart, ManufacturerPartAttachment,
ManufacturerPartParameter, SupplierPart, ManufacturerPartParameter, SupplierPart,
@@ -83,6 +85,16 @@ class CompanyDetail(RetrieveUpdateDestroyAPI):
return queryset return queryset
class CompanyMetadata(RetrieveUpdateAPI):
"""API endpoint for viewing / updating Company metadata."""
def get_serializer(self, *args, **kwargs):
"""Return MetadataSerializer instance for a Company"""
return MetadataSerializer(Company, *args, **kwargs)
queryset = Company.objects.all()
class ManufacturerPartFilter(rest_filters.FilterSet): class ManufacturerPartFilter(rest_filters.FilterSet):
"""Custom API filters for the ManufacturerPart list endpoint.""" """Custom API filters for the ManufacturerPart list endpoint."""
@@ -460,7 +472,11 @@ company_api_urls = [
re_path(r'^.*$', SupplierPriceBreakList.as_view(), name='api-part-supplier-price-list'), re_path(r'^.*$', SupplierPriceBreakList.as_view(), name='api-part-supplier-price-list'),
])), ])),
re_path(r'^(?P<pk>\d+)/?', CompanyDetail.as_view(), name='api-company-detail'), re_path(r'^(?P<pk>\d+)/?', include([
re_path(r'^metadata/', CompanyMetadata.as_view(), name='api-company-metadata'),
re_path(r'^.*$', CompanyDetail.as_view(), name='api-company-detail'),
])),
re_path(r'^.*$', CompanyList.as_view(), name='api-company-list'), re_path(r'^.*$', CompanyList.as_view(), name='api-company-list'),
] ]
@@ -0,0 +1,18 @@
# Generated by Django 3.2.16 on 2022-11-02 17:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('company', '0048_auto_20220913_0312'),
]
operations = [
migrations.AddField(
model_name='company',
name='metadata',
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
),
]
+2 -1
View File
@@ -23,6 +23,7 @@ from common.settings import currency_code_default
from InvenTree.fields import InvenTreeURLField, RoundingDecimalField from InvenTree.fields import InvenTreeURLField, RoundingDecimalField
from InvenTree.models import InvenTreeAttachment, InvenTreeBarcodeMixin from InvenTree.models import InvenTreeAttachment, InvenTreeBarcodeMixin
from InvenTree.status_codes import PurchaseOrderStatus from InvenTree.status_codes import PurchaseOrderStatus
from plugin.models import MetadataMixin
def rename_company_image(instance, filename): def rename_company_image(instance, filename):
@@ -50,7 +51,7 @@ def rename_company_image(instance, filename):
return os.path.join(base, fn) return os.path.join(base, fn)
class Company(models.Model): class Company(MetadataMixin, models.Model):
"""A Company object represents an external company. """A Company object represents an external company.
It may be a supplier or a customer or a manufacturer (or a combination) It may be a supplier or a customer or a manufacturer (or a combination)
+1 -3
View File
@@ -67,9 +67,7 @@ class LabelPrintMixin:
plugin = registry.get_plugin(plugin_key) plugin = registry.get_plugin(plugin_key)
if plugin: if plugin:
config = plugin.plugin_config() if plugin.is_active():
if config and config.active:
# Only return the plugin if it is enabled! # Only return the plugin if it is enabled!
return plugin return plugin
else: else:
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+124 -110
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-25 10:54+0000\n" "POT-Creation-Date: 2022-10-28 07:18+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -59,35 +59,35 @@ msgstr ""
msgid "Provided value does not match required pattern: " msgid "Provided value does not match required pattern: "
msgstr "" msgstr ""
#: InvenTree/forms.py:133 #: InvenTree/forms.py:134
msgid "Enter password" msgid "Enter password"
msgstr "" msgstr ""
#: InvenTree/forms.py:134 #: InvenTree/forms.py:135
msgid "Enter new password" msgid "Enter new password"
msgstr "" msgstr ""
#: InvenTree/forms.py:143 #: InvenTree/forms.py:144
msgid "Confirm password" msgid "Confirm password"
msgstr "" msgstr ""
#: InvenTree/forms.py:144 #: InvenTree/forms.py:145
msgid "Confirm new password" msgid "Confirm new password"
msgstr "" msgstr ""
#: InvenTree/forms.py:148 #: InvenTree/forms.py:149
msgid "Old password" msgid "Old password"
msgstr "" msgstr ""
#: InvenTree/forms.py:177 #: InvenTree/forms.py:178
msgid "Email (again)" msgid "Email (again)"
msgstr "" msgstr ""
#: InvenTree/forms.py:181 #: InvenTree/forms.py:182
msgid "Email address confirmation" msgid "Email address confirmation"
msgstr "" msgstr ""
#: InvenTree/forms.py:202 #: InvenTree/forms.py:203
msgid "You must type the same email each time." msgid "You must type the same email each time."
msgstr "" msgstr ""
@@ -131,30 +131,35 @@ msgstr ""
msgid "Empty serial number string" msgid "Empty serial number string"
msgstr "" msgstr ""
#: InvenTree/helpers.py:633 #: InvenTree/helpers.py:640
msgid "Duplicate serial" msgid "Duplicate serial"
msgstr "" msgstr ""
#: InvenTree/helpers.py:662 #: InvenTree/helpers.py:673 InvenTree/helpers.py:708
#, python-brace-format #, python-brace-format
msgid "Invalid group range: {g}" msgid "Invalid group range: {g}"
msgstr "" msgstr ""
#: InvenTree/helpers.py:714 InvenTree/helpers.py:721 InvenTree/helpers.py:736 #: InvenTree/helpers.py:702
#, python-brace-format
msgid "Group range {g} exceeds allowed quantity ({q})"
msgstr ""
#: InvenTree/helpers.py:726 InvenTree/helpers.py:733 InvenTree/helpers.py:748
#, python-brace-format #, python-brace-format
msgid "Invalid group sequence: {g}" msgid "Invalid group sequence: {g}"
msgstr "" msgstr ""
#: InvenTree/helpers.py:746 #: InvenTree/helpers.py:758
msgid "No serial numbers found" msgid "No serial numbers found"
msgstr "" msgstr ""
#: InvenTree/helpers.py:749 #: InvenTree/helpers.py:761
#, python-brace-format #, python-brace-format
msgid "Number of unique serial numbers ({s}) must match quantity ({q})" msgid "Number of unique serial numbers ({s}) must match quantity ({q})"
msgstr "" msgstr ""
#: InvenTree/helpers.py:948 #: InvenTree/helpers.py:960
msgid "Remove HTML tags from this value" msgid "Remove HTML tags from this value"
msgstr "" msgstr ""
@@ -224,9 +229,9 @@ msgstr ""
msgid "File comment" msgid "File comment"
msgstr "" msgstr ""
#: InvenTree/models.py:422 InvenTree/models.py:423 common/models.py:1726 #: InvenTree/models.py:422 InvenTree/models.py:423 common/models.py:1733
#: common/models.py:1727 common/models.py:1950 common/models.py:1951 #: common/models.py:1734 common/models.py:1957 common/models.py:1958
#: common/models.py:2213 common/models.py:2214 part/models.py:2254 #: common/models.py:2220 common/models.py:2221 part/models.py:2254
#: part/models.py:2274 plugin/models.py:260 plugin/models.py:261 #: part/models.py:2274 plugin/models.py:260 plugin/models.py:261
#: report/templates/report/inventree_test_report_base.html:96 #: report/templates/report/inventree_test_report_base.html:96
#: templates/js/translated/stock.js:2649 #: templates/js/translated/stock.js:2649
@@ -266,7 +271,7 @@ msgstr ""
msgid "Invalid choice" msgid "Invalid choice"
msgstr "" msgstr ""
#: InvenTree/models.py:557 InvenTree/models.py:558 common/models.py:1936 #: InvenTree/models.py:557 InvenTree/models.py:558 common/models.py:1943
#: company/models.py:358 label/models.py:101 part/models.py:760 #: company/models.py:358 label/models.py:101 part/models.py:760
#: part/models.py:2432 plugin/models.py:94 report/models.py:152 #: part/models.py:2432 plugin/models.py:94 report/models.py:152
#: templates/InvenTree/settings/mixins/urls.html:13 #: templates/InvenTree/settings/mixins/urls.html:13
@@ -678,24 +683,24 @@ msgstr ""
msgid "Production" msgid "Production"
msgstr "" msgstr ""
#: InvenTree/validators.py:19 #: InvenTree/validators.py:20
msgid "Not a valid currency code" msgid "Not a valid currency code"
msgstr "" msgstr ""
#: InvenTree/validators.py:90 #: InvenTree/validators.py:91
#, python-brace-format #, python-brace-format
msgid "IPN must match regex pattern {pat}" msgid "IPN must match regex pattern {pat}"
msgstr "" msgstr ""
#: InvenTree/validators.py:132 InvenTree/validators.py:148 #: InvenTree/validators.py:133 InvenTree/validators.py:149
msgid "Overage value must not be negative" msgid "Overage value must not be negative"
msgstr "" msgstr ""
#: InvenTree/validators.py:150 #: InvenTree/validators.py:151
msgid "Overage must not exceed 100%" msgid "Overage must not exceed 100%"
msgstr "" msgstr ""
#: InvenTree/validators.py:157 #: InvenTree/validators.py:158
msgid "Invalid value for overage" msgid "Invalid value for overage"
msgstr "" msgstr ""
@@ -746,7 +751,8 @@ msgstr ""
#: order/templates/order/so_sidebar.html:13 #: order/templates/order/so_sidebar.html:13
#: part/templates/part/part_sidebar.html:22 templates/InvenTree/index.html:221 #: part/templates/part/part_sidebar.html:22 templates/InvenTree/index.html:221
#: templates/InvenTree/search.html:141 #: templates/InvenTree/search.html:141
#: templates/InvenTree/settings/sidebar.html:47 users/models.py:41 #: templates/InvenTree/settings/sidebar.html:47
#: templates/js/translated/search.js:254 users/models.py:41
msgid "Build Orders" msgid "Build Orders"
msgstr "" msgstr ""
@@ -1010,7 +1016,7 @@ msgstr ""
#: build/models.py:1359 build/serializers.py:192 #: build/models.py:1359 build/serializers.py:192
#: build/templates/build/build_base.html:85 #: build/templates/build/build_base.html:85
#: build/templates/build/detail.html:34 common/models.py:1758 #: build/templates/build/detail.html:34 common/models.py:1765
#: company/templates/company/supplier_part.html:341 order/models.py:911 #: company/templates/company/supplier_part.html:341 order/models.py:911
#: order/models.py:1437 order/serializers.py:1213 #: order/models.py:1437 order/serializers.py:1213
#: order/templates/order/order_wizard/match_parts.html:30 part/forms.py:40 #: order/templates/order/order_wizard/match_parts.html:30 part/forms.py:40
@@ -2321,7 +2327,7 @@ msgstr ""
msgid "Enable plugins to respond to internal events" msgid "Enable plugins to respond to internal events"
msgstr "" msgstr ""
#: common/models.py:1391 common/models.py:1719 #: common/models.py:1391 common/models.py:1726
msgid "Settings key (must be unique - case insensitive" msgid "Settings key (must be unique - case insensitive"
msgstr "" msgstr ""
@@ -2558,120 +2564,128 @@ msgid "Display companies in search preview window"
msgstr "" msgstr ""
#: common/models.py:1616 #: common/models.py:1616
msgid "Search Purchase Orders" msgid "Search Build Orders"
msgstr "" msgstr ""
#: common/models.py:1617 #: common/models.py:1617
msgid "Display purchase orders in search preview window" msgid "Display build orders in search preview window"
msgstr "" msgstr ""
#: common/models.py:1623 #: common/models.py:1623
msgid "Exclude Inactive Purchase Orders" msgid "Search Purchase Orders"
msgstr "" msgstr ""
#: common/models.py:1624 #: common/models.py:1624
msgid "Exclude inactive purchase orders from search preview window" msgid "Display purchase orders in search preview window"
msgstr "" msgstr ""
#: common/models.py:1630 #: common/models.py:1630
msgid "Search Sales Orders" msgid "Exclude Inactive Purchase Orders"
msgstr "" msgstr ""
#: common/models.py:1631 #: common/models.py:1631
msgid "Display sales orders in search preview window" msgid "Exclude inactive purchase orders from search preview window"
msgstr "" msgstr ""
#: common/models.py:1637 #: common/models.py:1637
msgid "Exclude Inactive Sales Orders" msgid "Search Sales Orders"
msgstr "" msgstr ""
#: common/models.py:1638 #: common/models.py:1638
msgid "Exclude inactive sales orders from search preview window" msgid "Display sales orders in search preview window"
msgstr "" msgstr ""
#: common/models.py:1644 #: common/models.py:1644
msgid "Search Preview Results" msgid "Exclude Inactive Sales Orders"
msgstr "" msgstr ""
#: common/models.py:1645 #: common/models.py:1645
msgid "Number of results to show in each section of the search preview window" msgid "Exclude inactive sales orders from search preview window"
msgstr "" msgstr ""
#: common/models.py:1651 #: common/models.py:1651
msgid "Show Quantity in Forms" msgid "Search Preview Results"
msgstr "" msgstr ""
#: common/models.py:1652 #: common/models.py:1652
msgid "Display available part quantity in some forms" msgid "Number of results to show in each section of the search preview window"
msgstr "" msgstr ""
#: common/models.py:1658 #: common/models.py:1658
msgid "Escape Key Closes Forms" msgid "Show Quantity in Forms"
msgstr "" msgstr ""
#: common/models.py:1659 #: common/models.py:1659
msgid "Use the escape key to close modal forms" msgid "Display available part quantity in some forms"
msgstr "" msgstr ""
#: common/models.py:1665 #: common/models.py:1665
msgid "Fixed Navbar" msgid "Escape Key Closes Forms"
msgstr "" msgstr ""
#: common/models.py:1666 #: common/models.py:1666
msgid "The navbar position is fixed to the top of the screen" msgid "Use the escape key to close modal forms"
msgstr "" msgstr ""
#: common/models.py:1672 #: common/models.py:1672
msgid "Date Format" msgid "Fixed Navbar"
msgstr "" msgstr ""
#: common/models.py:1673 #: common/models.py:1673
msgid "The navbar position is fixed to the top of the screen"
msgstr ""
#: common/models.py:1679
msgid "Date Format"
msgstr ""
#: common/models.py:1680
msgid "Preferred format for displaying dates" msgid "Preferred format for displaying dates"
msgstr "" msgstr ""
#: common/models.py:1687 part/templates/part/detail.html:41 #: common/models.py:1694 part/templates/part/detail.html:41
msgid "Part Scheduling" msgid "Part Scheduling"
msgstr "" msgstr ""
#: common/models.py:1688 #: common/models.py:1695
msgid "Display part scheduling information" msgid "Display part scheduling information"
msgstr "" msgstr ""
#: common/models.py:1694 #: common/models.py:1701
msgid "Table String Length" msgid "Table String Length"
msgstr "" msgstr ""
#: common/models.py:1695 #: common/models.py:1702
msgid "Maximimum length limit for strings displayed in table views" msgid "Maximimum length limit for strings displayed in table views"
msgstr "" msgstr ""
#: common/models.py:1759 #: common/models.py:1766
msgid "Price break quantity" msgid "Price break quantity"
msgstr "" msgstr ""
#: common/models.py:1766 company/serializers.py:372 #: common/models.py:1773 company/serializers.py:372
#: company/templates/company/supplier_part.html:346 order/models.py:952 #: company/templates/company/supplier_part.html:346 order/models.py:952
#: templates/js/translated/part.js:1103 templates/js/translated/part.js:2223 #: templates/js/translated/part.js:1103 templates/js/translated/part.js:2223
msgid "Price" msgid "Price"
msgstr "" msgstr ""
#: common/models.py:1767 #: common/models.py:1774
msgid "Unit price at specified quantity" msgid "Unit price at specified quantity"
msgstr "" msgstr ""
#: common/models.py:1927 common/models.py:2105 #: common/models.py:1934 common/models.py:2112
msgid "Endpoint" msgid "Endpoint"
msgstr "" msgstr ""
#: common/models.py:1928 #: common/models.py:1935
msgid "Endpoint at which this webhook is received" msgid "Endpoint at which this webhook is received"
msgstr "" msgstr ""
#: common/models.py:1937 #: common/models.py:1944
msgid "Name for this webhook" msgid "Name for this webhook"
msgstr "" msgstr ""
#: common/models.py:1942 part/models.py:935 plugin/models.py:100 #: common/models.py:1949 part/models.py:935 plugin/models.py:100
#: templates/js/translated/table_filters.js:34 #: templates/js/translated/table_filters.js:34
#: templates/js/translated/table_filters.js:112 #: templates/js/translated/table_filters.js:112
#: templates/js/translated/table_filters.js:324 #: templates/js/translated/table_filters.js:324
@@ -2679,67 +2693,67 @@ msgstr ""
msgid "Active" msgid "Active"
msgstr "" msgstr ""
#: common/models.py:1943 #: common/models.py:1950
msgid "Is this webhook active" msgid "Is this webhook active"
msgstr "" msgstr ""
#: common/models.py:1957 #: common/models.py:1964
msgid "Token" msgid "Token"
msgstr "" msgstr ""
#: common/models.py:1958 #: common/models.py:1965
msgid "Token for access" msgid "Token for access"
msgstr "" msgstr ""
#: common/models.py:1965 #: common/models.py:1972
msgid "Secret" msgid "Secret"
msgstr "" msgstr ""
#: common/models.py:1966 #: common/models.py:1973
msgid "Shared secret for HMAC" msgid "Shared secret for HMAC"
msgstr "" msgstr ""
#: common/models.py:2072 #: common/models.py:2079
msgid "Message ID" msgid "Message ID"
msgstr "" msgstr ""
#: common/models.py:2073 #: common/models.py:2080
msgid "Unique identifier for this message" msgid "Unique identifier for this message"
msgstr "" msgstr ""
#: common/models.py:2081 #: common/models.py:2088
msgid "Host" msgid "Host"
msgstr "" msgstr ""
#: common/models.py:2082 #: common/models.py:2089
msgid "Host from which this message was received" msgid "Host from which this message was received"
msgstr "" msgstr ""
#: common/models.py:2089 #: common/models.py:2096
msgid "Header" msgid "Header"
msgstr "" msgstr ""
#: common/models.py:2090 #: common/models.py:2097
msgid "Header of this message" msgid "Header of this message"
msgstr "" msgstr ""
#: common/models.py:2096 #: common/models.py:2103
msgid "Body" msgid "Body"
msgstr "" msgstr ""
#: common/models.py:2097 #: common/models.py:2104
msgid "Body of this message" msgid "Body of this message"
msgstr "" msgstr ""
#: common/models.py:2106 #: common/models.py:2113
msgid "Endpoint on which this message was received" msgid "Endpoint on which this message was received"
msgstr "" msgstr ""
#: common/models.py:2111 #: common/models.py:2118
msgid "Worked on" msgid "Worked on"
msgstr "" msgstr ""
#: common/models.py:2112 #: common/models.py:2119
msgid "Was the work on this message finished?" msgid "Was the work on this message finished?"
msgstr "" msgstr ""
@@ -3228,7 +3242,7 @@ msgstr ""
#: part/templates/part/detail.html:84 part/templates/part/part_sidebar.html:37 #: part/templates/part/detail.html:84 part/templates/part/part_sidebar.html:37
#: templates/InvenTree/index.html:252 templates/InvenTree/search.html:200 #: templates/InvenTree/index.html:252 templates/InvenTree/search.html:200
#: templates/InvenTree/settings/sidebar.html:49 #: templates/InvenTree/settings/sidebar.html:49
#: templates/js/translated/search.js:277 templates/navbar.html:50 #: templates/js/translated/search.js:293 templates/navbar.html:50
#: users/models.py:42 #: users/models.py:42
msgid "Purchase Orders" msgid "Purchase Orders"
msgstr "" msgstr ""
@@ -3251,7 +3265,7 @@ msgstr ""
#: part/templates/part/detail.html:107 part/templates/part/part_sidebar.html:41 #: part/templates/part/detail.html:107 part/templates/part/part_sidebar.html:41
#: templates/InvenTree/index.html:283 templates/InvenTree/search.html:220 #: templates/InvenTree/index.html:283 templates/InvenTree/search.html:220
#: templates/InvenTree/settings/sidebar.html:51 #: templates/InvenTree/settings/sidebar.html:51
#: templates/js/translated/search.js:301 templates/navbar.html:61 #: templates/js/translated/search.js:317 templates/navbar.html:61
#: users/models.py:43 #: users/models.py:43
msgid "Sales Orders" msgid "Sales Orders"
msgstr "" msgstr ""
@@ -3321,7 +3335,7 @@ msgstr ""
#: company/templates/company/manufacturer_part.html:136 #: company/templates/company/manufacturer_part.html:136
#: company/templates/company/manufacturer_part.html:183 #: company/templates/company/manufacturer_part.html:183
#: part/templates/part/detail.html:371 part/templates/part/detail.html:401 #: part/templates/part/detail.html:371 part/templates/part/detail.html:401
#: templates/js/translated/forms.js:458 templates/js/translated/helpers.js:36 #: templates/js/translated/forms.js:455 templates/js/translated/helpers.js:36
#: users/models.py:222 #: users/models.py:222
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
@@ -3570,7 +3584,7 @@ msgstr ""
msgid "New Customer" msgid "New Customer"
msgstr "" msgstr ""
#: company/views.py:52 templates/js/translated/search.js:254 #: company/views.py:52 templates/js/translated/search.js:270
msgid "Companies" msgid "Companies"
msgstr "" msgstr ""
@@ -4379,47 +4393,47 @@ msgstr ""
msgid "Updated {part} unit-price to {price} and quantity to {qty}" msgid "Updated {part} unit-price to {price} and quantity to {qty}"
msgstr "" msgstr ""
#: part/api.py:514 #: part/api.py:516
msgid "Incoming Purchase Order" msgid "Incoming Purchase Order"
msgstr "" msgstr ""
#: part/api.py:534 #: part/api.py:536
msgid "Outgoing Sales Order" msgid "Outgoing Sales Order"
msgstr "" msgstr ""
#: part/api.py:552 #: part/api.py:554
msgid "Stock produced by Build Order" msgid "Stock produced by Build Order"
msgstr "" msgstr ""
#: part/api.py:638 #: part/api.py:640
msgid "Stock required for Build Order" msgid "Stock required for Build Order"
msgstr "" msgstr ""
#: part/api.py:775 #: part/api.py:777
msgid "Valid" msgid "Valid"
msgstr "" msgstr ""
#: part/api.py:776 #: part/api.py:778
msgid "Validate entire Bill of Materials" msgid "Validate entire Bill of Materials"
msgstr "" msgstr ""
#: part/api.py:782 #: part/api.py:784
msgid "This option must be selected" msgid "This option must be selected"
msgstr "" msgstr ""
#: part/api.py:1205 #: part/api.py:1207
msgid "Must be greater than zero" msgid "Must be greater than zero"
msgstr "" msgstr ""
#: part/api.py:1209 #: part/api.py:1211
msgid "Must be a valid quantity" msgid "Must be a valid quantity"
msgstr "" msgstr ""
#: part/api.py:1224 #: part/api.py:1226
msgid "Specify location for initial part stock" msgid "Specify location for initial part stock"
msgstr "" msgstr ""
#: part/api.py:1255 part/api.py:1259 part/api.py:1274 part/api.py:1278 #: part/api.py:1257 part/api.py:1261 part/api.py:1276 part/api.py:1280
msgid "This field is required" msgid "This field is required"
msgstr "" msgstr ""
@@ -5755,23 +5769,23 @@ msgstr ""
msgid "No matching action found" msgid "No matching action found"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:52 plugin/base/barcodes/api.py:110 #: plugin/base/barcodes/api.py:54 plugin/base/barcodes/api.py:113
msgid "Must provide barcode_data parameter" msgid "Missing barcode data"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:82 #: plugin/base/barcodes/api.py:83
msgid "No match found for barcode data" msgid "No match found for barcode data"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:86 #: plugin/base/barcodes/api.py:87
msgid "Match found for barcode data" msgid "Match found for barcode data"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:125 #: plugin/base/barcodes/api.py:126
msgid "Barcode matches existing item" msgid "Barcode matches existing item"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:222 #: plugin/base/barcodes/api.py:223
msgid "No match found for provided value" msgid "No match found for provided value"
msgstr "" msgstr ""
@@ -7603,7 +7617,7 @@ msgstr ""
msgid "Please confirm that <a href=\"mailto:%(email)s\">%(email)s</a> is an email address for user %(user_display)s." msgid "Please confirm that <a href=\"mailto:%(email)s\">%(email)s</a> is an email address for user %(user_display)s."
msgstr "" msgstr ""
#: templates/account/email_confirm.html:22 templates/js/translated/forms.js:649 #: templates/account/email_confirm.html:22 templates/js/translated/forms.js:646
msgid "Confirm" msgid "Confirm"
msgstr "" msgstr ""
@@ -8653,61 +8667,61 @@ msgstr ""
msgid "Create filter" msgid "Create filter"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:372 templates/js/translated/forms.js:387 #: templates/js/translated/forms.js:369 templates/js/translated/forms.js:384
#: templates/js/translated/forms.js:401 templates/js/translated/forms.js:415 #: templates/js/translated/forms.js:398 templates/js/translated/forms.js:412
msgid "Action Prohibited" msgid "Action Prohibited"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:374 #: templates/js/translated/forms.js:371
msgid "Create operation not allowed" msgid "Create operation not allowed"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:389 #: templates/js/translated/forms.js:386
msgid "Update operation not allowed" msgid "Update operation not allowed"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:403 #: templates/js/translated/forms.js:400
msgid "Delete operation not allowed" msgid "Delete operation not allowed"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:417 #: templates/js/translated/forms.js:414
msgid "View operation not allowed" msgid "View operation not allowed"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:675 #: templates/js/translated/forms.js:672
msgid "Keep this form open" msgid "Keep this form open"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:776 #: templates/js/translated/forms.js:773
msgid "Enter a valid number" msgid "Enter a valid number"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:1269 templates/modals.html:19 #: templates/js/translated/forms.js:1266 templates/modals.html:19
#: templates/modals.html:43 #: templates/modals.html:43
msgid "Form errors exist" msgid "Form errors exist"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:1706 #: templates/js/translated/forms.js:1703
msgid "No results found" msgid "No results found"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:1922 templates/search.html:29 #: templates/js/translated/forms.js:1919 templates/search.html:29
msgid "Searching" msgid "Searching"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:2175 #: templates/js/translated/forms.js:2172
msgid "Clear input" msgid "Clear input"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:2641 #: templates/js/translated/forms.js:2638
msgid "File Column" msgid "File Column"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:2641 #: templates/js/translated/forms.js:2638
msgid "Field Name" msgid "Field Name"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:2653 #: templates/js/translated/forms.js:2650
msgid "Select Columns" msgid "Select Columns"
msgstr "" msgstr ""
@@ -9737,11 +9751,11 @@ msgstr ""
msgid "Sales Order(s) must be selected before printing report" msgid "Sales Order(s) must be selected before printing report"
msgstr "" msgstr ""
#: templates/js/translated/search.js:394 #: templates/js/translated/search.js:410
msgid "Minimize results" msgid "Minimize results"
msgstr "" msgstr ""
#: templates/js/translated/search.js:397 #: templates/js/translated/search.js:413
msgid "Remove results" msgid "Remove results"
msgstr "" msgstr ""
File diff suppressed because it is too large Load Diff
+124 -110
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-25 10:54+0000\n" "POT-Creation-Date: 2022-10-28 07:18+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -59,35 +59,35 @@ msgstr ""
msgid "Provided value does not match required pattern: " msgid "Provided value does not match required pattern: "
msgstr "" msgstr ""
#: InvenTree/forms.py:133 #: InvenTree/forms.py:134
msgid "Enter password" msgid "Enter password"
msgstr "" msgstr ""
#: InvenTree/forms.py:134 #: InvenTree/forms.py:135
msgid "Enter new password" msgid "Enter new password"
msgstr "" msgstr ""
#: InvenTree/forms.py:143 #: InvenTree/forms.py:144
msgid "Confirm password" msgid "Confirm password"
msgstr "" msgstr ""
#: InvenTree/forms.py:144 #: InvenTree/forms.py:145
msgid "Confirm new password" msgid "Confirm new password"
msgstr "" msgstr ""
#: InvenTree/forms.py:148 #: InvenTree/forms.py:149
msgid "Old password" msgid "Old password"
msgstr "" msgstr ""
#: InvenTree/forms.py:177 #: InvenTree/forms.py:178
msgid "Email (again)" msgid "Email (again)"
msgstr "" msgstr ""
#: InvenTree/forms.py:181 #: InvenTree/forms.py:182
msgid "Email address confirmation" msgid "Email address confirmation"
msgstr "" msgstr ""
#: InvenTree/forms.py:202 #: InvenTree/forms.py:203
msgid "You must type the same email each time." msgid "You must type the same email each time."
msgstr "" msgstr ""
@@ -131,30 +131,35 @@ msgstr ""
msgid "Empty serial number string" msgid "Empty serial number string"
msgstr "" msgstr ""
#: InvenTree/helpers.py:633 #: InvenTree/helpers.py:640
msgid "Duplicate serial" msgid "Duplicate serial"
msgstr "" msgstr ""
#: InvenTree/helpers.py:662 #: InvenTree/helpers.py:673 InvenTree/helpers.py:708
#, python-brace-format #, python-brace-format
msgid "Invalid group range: {g}" msgid "Invalid group range: {g}"
msgstr "" msgstr ""
#: InvenTree/helpers.py:714 InvenTree/helpers.py:721 InvenTree/helpers.py:736 #: InvenTree/helpers.py:702
#, python-brace-format
msgid "Group range {g} exceeds allowed quantity ({q})"
msgstr ""
#: InvenTree/helpers.py:726 InvenTree/helpers.py:733 InvenTree/helpers.py:748
#, python-brace-format #, python-brace-format
msgid "Invalid group sequence: {g}" msgid "Invalid group sequence: {g}"
msgstr "" msgstr ""
#: InvenTree/helpers.py:746 #: InvenTree/helpers.py:758
msgid "No serial numbers found" msgid "No serial numbers found"
msgstr "" msgstr ""
#: InvenTree/helpers.py:749 #: InvenTree/helpers.py:761
#, python-brace-format #, python-brace-format
msgid "Number of unique serial numbers ({s}) must match quantity ({q})" msgid "Number of unique serial numbers ({s}) must match quantity ({q})"
msgstr "" msgstr ""
#: InvenTree/helpers.py:948 #: InvenTree/helpers.py:960
msgid "Remove HTML tags from this value" msgid "Remove HTML tags from this value"
msgstr "" msgstr ""
@@ -224,9 +229,9 @@ msgstr ""
msgid "File comment" msgid "File comment"
msgstr "" msgstr ""
#: InvenTree/models.py:422 InvenTree/models.py:423 common/models.py:1726 #: InvenTree/models.py:422 InvenTree/models.py:423 common/models.py:1733
#: common/models.py:1727 common/models.py:1950 common/models.py:1951 #: common/models.py:1734 common/models.py:1957 common/models.py:1958
#: common/models.py:2213 common/models.py:2214 part/models.py:2254 #: common/models.py:2220 common/models.py:2221 part/models.py:2254
#: part/models.py:2274 plugin/models.py:260 plugin/models.py:261 #: part/models.py:2274 plugin/models.py:260 plugin/models.py:261
#: report/templates/report/inventree_test_report_base.html:96 #: report/templates/report/inventree_test_report_base.html:96
#: templates/js/translated/stock.js:2649 #: templates/js/translated/stock.js:2649
@@ -266,7 +271,7 @@ msgstr ""
msgid "Invalid choice" msgid "Invalid choice"
msgstr "" msgstr ""
#: InvenTree/models.py:557 InvenTree/models.py:558 common/models.py:1936 #: InvenTree/models.py:557 InvenTree/models.py:558 common/models.py:1943
#: company/models.py:358 label/models.py:101 part/models.py:760 #: company/models.py:358 label/models.py:101 part/models.py:760
#: part/models.py:2432 plugin/models.py:94 report/models.py:152 #: part/models.py:2432 plugin/models.py:94 report/models.py:152
#: templates/InvenTree/settings/mixins/urls.html:13 #: templates/InvenTree/settings/mixins/urls.html:13
@@ -678,24 +683,24 @@ msgstr ""
msgid "Production" msgid "Production"
msgstr "" msgstr ""
#: InvenTree/validators.py:19 #: InvenTree/validators.py:20
msgid "Not a valid currency code" msgid "Not a valid currency code"
msgstr "" msgstr ""
#: InvenTree/validators.py:90 #: InvenTree/validators.py:91
#, python-brace-format #, python-brace-format
msgid "IPN must match regex pattern {pat}" msgid "IPN must match regex pattern {pat}"
msgstr "" msgstr ""
#: InvenTree/validators.py:132 InvenTree/validators.py:148 #: InvenTree/validators.py:133 InvenTree/validators.py:149
msgid "Overage value must not be negative" msgid "Overage value must not be negative"
msgstr "" msgstr ""
#: InvenTree/validators.py:150 #: InvenTree/validators.py:151
msgid "Overage must not exceed 100%" msgid "Overage must not exceed 100%"
msgstr "" msgstr ""
#: InvenTree/validators.py:157 #: InvenTree/validators.py:158
msgid "Invalid value for overage" msgid "Invalid value for overage"
msgstr "" msgstr ""
@@ -746,7 +751,8 @@ msgstr ""
#: order/templates/order/so_sidebar.html:13 #: order/templates/order/so_sidebar.html:13
#: part/templates/part/part_sidebar.html:22 templates/InvenTree/index.html:221 #: part/templates/part/part_sidebar.html:22 templates/InvenTree/index.html:221
#: templates/InvenTree/search.html:141 #: templates/InvenTree/search.html:141
#: templates/InvenTree/settings/sidebar.html:47 users/models.py:41 #: templates/InvenTree/settings/sidebar.html:47
#: templates/js/translated/search.js:254 users/models.py:41
msgid "Build Orders" msgid "Build Orders"
msgstr "" msgstr ""
@@ -1010,7 +1016,7 @@ msgstr ""
#: build/models.py:1359 build/serializers.py:192 #: build/models.py:1359 build/serializers.py:192
#: build/templates/build/build_base.html:85 #: build/templates/build/build_base.html:85
#: build/templates/build/detail.html:34 common/models.py:1758 #: build/templates/build/detail.html:34 common/models.py:1765
#: company/templates/company/supplier_part.html:341 order/models.py:911 #: company/templates/company/supplier_part.html:341 order/models.py:911
#: order/models.py:1437 order/serializers.py:1213 #: order/models.py:1437 order/serializers.py:1213
#: order/templates/order/order_wizard/match_parts.html:30 part/forms.py:40 #: order/templates/order/order_wizard/match_parts.html:30 part/forms.py:40
@@ -2321,7 +2327,7 @@ msgstr ""
msgid "Enable plugins to respond to internal events" msgid "Enable plugins to respond to internal events"
msgstr "" msgstr ""
#: common/models.py:1391 common/models.py:1719 #: common/models.py:1391 common/models.py:1726
msgid "Settings key (must be unique - case insensitive" msgid "Settings key (must be unique - case insensitive"
msgstr "" msgstr ""
@@ -2558,120 +2564,128 @@ msgid "Display companies in search preview window"
msgstr "" msgstr ""
#: common/models.py:1616 #: common/models.py:1616
msgid "Search Purchase Orders" msgid "Search Build Orders"
msgstr "" msgstr ""
#: common/models.py:1617 #: common/models.py:1617
msgid "Display purchase orders in search preview window" msgid "Display build orders in search preview window"
msgstr "" msgstr ""
#: common/models.py:1623 #: common/models.py:1623
msgid "Exclude Inactive Purchase Orders" msgid "Search Purchase Orders"
msgstr "" msgstr ""
#: common/models.py:1624 #: common/models.py:1624
msgid "Exclude inactive purchase orders from search preview window" msgid "Display purchase orders in search preview window"
msgstr "" msgstr ""
#: common/models.py:1630 #: common/models.py:1630
msgid "Search Sales Orders" msgid "Exclude Inactive Purchase Orders"
msgstr "" msgstr ""
#: common/models.py:1631 #: common/models.py:1631
msgid "Display sales orders in search preview window" msgid "Exclude inactive purchase orders from search preview window"
msgstr "" msgstr ""
#: common/models.py:1637 #: common/models.py:1637
msgid "Exclude Inactive Sales Orders" msgid "Search Sales Orders"
msgstr "" msgstr ""
#: common/models.py:1638 #: common/models.py:1638
msgid "Exclude inactive sales orders from search preview window" msgid "Display sales orders in search preview window"
msgstr "" msgstr ""
#: common/models.py:1644 #: common/models.py:1644
msgid "Search Preview Results" msgid "Exclude Inactive Sales Orders"
msgstr "" msgstr ""
#: common/models.py:1645 #: common/models.py:1645
msgid "Number of results to show in each section of the search preview window" msgid "Exclude inactive sales orders from search preview window"
msgstr "" msgstr ""
#: common/models.py:1651 #: common/models.py:1651
msgid "Show Quantity in Forms" msgid "Search Preview Results"
msgstr "" msgstr ""
#: common/models.py:1652 #: common/models.py:1652
msgid "Display available part quantity in some forms" msgid "Number of results to show in each section of the search preview window"
msgstr "" msgstr ""
#: common/models.py:1658 #: common/models.py:1658
msgid "Escape Key Closes Forms" msgid "Show Quantity in Forms"
msgstr "" msgstr ""
#: common/models.py:1659 #: common/models.py:1659
msgid "Use the escape key to close modal forms" msgid "Display available part quantity in some forms"
msgstr "" msgstr ""
#: common/models.py:1665 #: common/models.py:1665
msgid "Fixed Navbar" msgid "Escape Key Closes Forms"
msgstr "" msgstr ""
#: common/models.py:1666 #: common/models.py:1666
msgid "The navbar position is fixed to the top of the screen" msgid "Use the escape key to close modal forms"
msgstr "" msgstr ""
#: common/models.py:1672 #: common/models.py:1672
msgid "Date Format" msgid "Fixed Navbar"
msgstr "" msgstr ""
#: common/models.py:1673 #: common/models.py:1673
msgid "The navbar position is fixed to the top of the screen"
msgstr ""
#: common/models.py:1679
msgid "Date Format"
msgstr ""
#: common/models.py:1680
msgid "Preferred format for displaying dates" msgid "Preferred format for displaying dates"
msgstr "" msgstr ""
#: common/models.py:1687 part/templates/part/detail.html:41 #: common/models.py:1694 part/templates/part/detail.html:41
msgid "Part Scheduling" msgid "Part Scheduling"
msgstr "" msgstr ""
#: common/models.py:1688 #: common/models.py:1695
msgid "Display part scheduling information" msgid "Display part scheduling information"
msgstr "" msgstr ""
#: common/models.py:1694 #: common/models.py:1701
msgid "Table String Length" msgid "Table String Length"
msgstr "" msgstr ""
#: common/models.py:1695 #: common/models.py:1702
msgid "Maximimum length limit for strings displayed in table views" msgid "Maximimum length limit for strings displayed in table views"
msgstr "" msgstr ""
#: common/models.py:1759 #: common/models.py:1766
msgid "Price break quantity" msgid "Price break quantity"
msgstr "" msgstr ""
#: common/models.py:1766 company/serializers.py:372 #: common/models.py:1773 company/serializers.py:372
#: company/templates/company/supplier_part.html:346 order/models.py:952 #: company/templates/company/supplier_part.html:346 order/models.py:952
#: templates/js/translated/part.js:1103 templates/js/translated/part.js:2223 #: templates/js/translated/part.js:1103 templates/js/translated/part.js:2223
msgid "Price" msgid "Price"
msgstr "" msgstr ""
#: common/models.py:1767 #: common/models.py:1774
msgid "Unit price at specified quantity" msgid "Unit price at specified quantity"
msgstr "" msgstr ""
#: common/models.py:1927 common/models.py:2105 #: common/models.py:1934 common/models.py:2112
msgid "Endpoint" msgid "Endpoint"
msgstr "" msgstr ""
#: common/models.py:1928 #: common/models.py:1935
msgid "Endpoint at which this webhook is received" msgid "Endpoint at which this webhook is received"
msgstr "" msgstr ""
#: common/models.py:1937 #: common/models.py:1944
msgid "Name for this webhook" msgid "Name for this webhook"
msgstr "" msgstr ""
#: common/models.py:1942 part/models.py:935 plugin/models.py:100 #: common/models.py:1949 part/models.py:935 plugin/models.py:100
#: templates/js/translated/table_filters.js:34 #: templates/js/translated/table_filters.js:34
#: templates/js/translated/table_filters.js:112 #: templates/js/translated/table_filters.js:112
#: templates/js/translated/table_filters.js:324 #: templates/js/translated/table_filters.js:324
@@ -2679,67 +2693,67 @@ msgstr ""
msgid "Active" msgid "Active"
msgstr "" msgstr ""
#: common/models.py:1943 #: common/models.py:1950
msgid "Is this webhook active" msgid "Is this webhook active"
msgstr "" msgstr ""
#: common/models.py:1957 #: common/models.py:1964
msgid "Token" msgid "Token"
msgstr "" msgstr ""
#: common/models.py:1958 #: common/models.py:1965
msgid "Token for access" msgid "Token for access"
msgstr "" msgstr ""
#: common/models.py:1965 #: common/models.py:1972
msgid "Secret" msgid "Secret"
msgstr "" msgstr ""
#: common/models.py:1966 #: common/models.py:1973
msgid "Shared secret for HMAC" msgid "Shared secret for HMAC"
msgstr "" msgstr ""
#: common/models.py:2072 #: common/models.py:2079
msgid "Message ID" msgid "Message ID"
msgstr "" msgstr ""
#: common/models.py:2073 #: common/models.py:2080
msgid "Unique identifier for this message" msgid "Unique identifier for this message"
msgstr "" msgstr ""
#: common/models.py:2081 #: common/models.py:2088
msgid "Host" msgid "Host"
msgstr "" msgstr ""
#: common/models.py:2082 #: common/models.py:2089
msgid "Host from which this message was received" msgid "Host from which this message was received"
msgstr "" msgstr ""
#: common/models.py:2089 #: common/models.py:2096
msgid "Header" msgid "Header"
msgstr "" msgstr ""
#: common/models.py:2090 #: common/models.py:2097
msgid "Header of this message" msgid "Header of this message"
msgstr "" msgstr ""
#: common/models.py:2096 #: common/models.py:2103
msgid "Body" msgid "Body"
msgstr "" msgstr ""
#: common/models.py:2097 #: common/models.py:2104
msgid "Body of this message" msgid "Body of this message"
msgstr "" msgstr ""
#: common/models.py:2106 #: common/models.py:2113
msgid "Endpoint on which this message was received" msgid "Endpoint on which this message was received"
msgstr "" msgstr ""
#: common/models.py:2111 #: common/models.py:2118
msgid "Worked on" msgid "Worked on"
msgstr "" msgstr ""
#: common/models.py:2112 #: common/models.py:2119
msgid "Was the work on this message finished?" msgid "Was the work on this message finished?"
msgstr "" msgstr ""
@@ -3228,7 +3242,7 @@ msgstr ""
#: part/templates/part/detail.html:84 part/templates/part/part_sidebar.html:37 #: part/templates/part/detail.html:84 part/templates/part/part_sidebar.html:37
#: templates/InvenTree/index.html:252 templates/InvenTree/search.html:200 #: templates/InvenTree/index.html:252 templates/InvenTree/search.html:200
#: templates/InvenTree/settings/sidebar.html:49 #: templates/InvenTree/settings/sidebar.html:49
#: templates/js/translated/search.js:277 templates/navbar.html:50 #: templates/js/translated/search.js:293 templates/navbar.html:50
#: users/models.py:42 #: users/models.py:42
msgid "Purchase Orders" msgid "Purchase Orders"
msgstr "" msgstr ""
@@ -3251,7 +3265,7 @@ msgstr ""
#: part/templates/part/detail.html:107 part/templates/part/part_sidebar.html:41 #: part/templates/part/detail.html:107 part/templates/part/part_sidebar.html:41
#: templates/InvenTree/index.html:283 templates/InvenTree/search.html:220 #: templates/InvenTree/index.html:283 templates/InvenTree/search.html:220
#: templates/InvenTree/settings/sidebar.html:51 #: templates/InvenTree/settings/sidebar.html:51
#: templates/js/translated/search.js:301 templates/navbar.html:61 #: templates/js/translated/search.js:317 templates/navbar.html:61
#: users/models.py:43 #: users/models.py:43
msgid "Sales Orders" msgid "Sales Orders"
msgstr "" msgstr ""
@@ -3321,7 +3335,7 @@ msgstr ""
#: company/templates/company/manufacturer_part.html:136 #: company/templates/company/manufacturer_part.html:136
#: company/templates/company/manufacturer_part.html:183 #: company/templates/company/manufacturer_part.html:183
#: part/templates/part/detail.html:371 part/templates/part/detail.html:401 #: part/templates/part/detail.html:371 part/templates/part/detail.html:401
#: templates/js/translated/forms.js:458 templates/js/translated/helpers.js:36 #: templates/js/translated/forms.js:455 templates/js/translated/helpers.js:36
#: users/models.py:222 #: users/models.py:222
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
@@ -3570,7 +3584,7 @@ msgstr ""
msgid "New Customer" msgid "New Customer"
msgstr "" msgstr ""
#: company/views.py:52 templates/js/translated/search.js:254 #: company/views.py:52 templates/js/translated/search.js:270
msgid "Companies" msgid "Companies"
msgstr "" msgstr ""
@@ -4379,47 +4393,47 @@ msgstr ""
msgid "Updated {part} unit-price to {price} and quantity to {qty}" msgid "Updated {part} unit-price to {price} and quantity to {qty}"
msgstr "" msgstr ""
#: part/api.py:514 #: part/api.py:516
msgid "Incoming Purchase Order" msgid "Incoming Purchase Order"
msgstr "" msgstr ""
#: part/api.py:534 #: part/api.py:536
msgid "Outgoing Sales Order" msgid "Outgoing Sales Order"
msgstr "" msgstr ""
#: part/api.py:552 #: part/api.py:554
msgid "Stock produced by Build Order" msgid "Stock produced by Build Order"
msgstr "" msgstr ""
#: part/api.py:638 #: part/api.py:640
msgid "Stock required for Build Order" msgid "Stock required for Build Order"
msgstr "" msgstr ""
#: part/api.py:775 #: part/api.py:777
msgid "Valid" msgid "Valid"
msgstr "" msgstr ""
#: part/api.py:776 #: part/api.py:778
msgid "Validate entire Bill of Materials" msgid "Validate entire Bill of Materials"
msgstr "" msgstr ""
#: part/api.py:782 #: part/api.py:784
msgid "This option must be selected" msgid "This option must be selected"
msgstr "" msgstr ""
#: part/api.py:1205 #: part/api.py:1207
msgid "Must be greater than zero" msgid "Must be greater than zero"
msgstr "" msgstr ""
#: part/api.py:1209 #: part/api.py:1211
msgid "Must be a valid quantity" msgid "Must be a valid quantity"
msgstr "" msgstr ""
#: part/api.py:1224 #: part/api.py:1226
msgid "Specify location for initial part stock" msgid "Specify location for initial part stock"
msgstr "" msgstr ""
#: part/api.py:1255 part/api.py:1259 part/api.py:1274 part/api.py:1278 #: part/api.py:1257 part/api.py:1261 part/api.py:1276 part/api.py:1280
msgid "This field is required" msgid "This field is required"
msgstr "" msgstr ""
@@ -5755,23 +5769,23 @@ msgstr ""
msgid "No matching action found" msgid "No matching action found"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:52 plugin/base/barcodes/api.py:110 #: plugin/base/barcodes/api.py:54 plugin/base/barcodes/api.py:113
msgid "Must provide barcode_data parameter" msgid "Missing barcode data"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:82 #: plugin/base/barcodes/api.py:83
msgid "No match found for barcode data" msgid "No match found for barcode data"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:86 #: plugin/base/barcodes/api.py:87
msgid "Match found for barcode data" msgid "Match found for barcode data"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:125 #: plugin/base/barcodes/api.py:126
msgid "Barcode matches existing item" msgid "Barcode matches existing item"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:222 #: plugin/base/barcodes/api.py:223
msgid "No match found for provided value" msgid "No match found for provided value"
msgstr "" msgstr ""
@@ -7603,7 +7617,7 @@ msgstr ""
msgid "Please confirm that <a href=\"mailto:%(email)s\">%(email)s</a> is an email address for user %(user_display)s." msgid "Please confirm that <a href=\"mailto:%(email)s\">%(email)s</a> is an email address for user %(user_display)s."
msgstr "" msgstr ""
#: templates/account/email_confirm.html:22 templates/js/translated/forms.js:649 #: templates/account/email_confirm.html:22 templates/js/translated/forms.js:646
msgid "Confirm" msgid "Confirm"
msgstr "" msgstr ""
@@ -8653,61 +8667,61 @@ msgstr ""
msgid "Create filter" msgid "Create filter"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:372 templates/js/translated/forms.js:387 #: templates/js/translated/forms.js:369 templates/js/translated/forms.js:384
#: templates/js/translated/forms.js:401 templates/js/translated/forms.js:415 #: templates/js/translated/forms.js:398 templates/js/translated/forms.js:412
msgid "Action Prohibited" msgid "Action Prohibited"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:374 #: templates/js/translated/forms.js:371
msgid "Create operation not allowed" msgid "Create operation not allowed"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:389 #: templates/js/translated/forms.js:386
msgid "Update operation not allowed" msgid "Update operation not allowed"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:403 #: templates/js/translated/forms.js:400
msgid "Delete operation not allowed" msgid "Delete operation not allowed"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:417 #: templates/js/translated/forms.js:414
msgid "View operation not allowed" msgid "View operation not allowed"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:675 #: templates/js/translated/forms.js:672
msgid "Keep this form open" msgid "Keep this form open"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:776 #: templates/js/translated/forms.js:773
msgid "Enter a valid number" msgid "Enter a valid number"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:1269 templates/modals.html:19 #: templates/js/translated/forms.js:1266 templates/modals.html:19
#: templates/modals.html:43 #: templates/modals.html:43
msgid "Form errors exist" msgid "Form errors exist"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:1706 #: templates/js/translated/forms.js:1703
msgid "No results found" msgid "No results found"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:1922 templates/search.html:29 #: templates/js/translated/forms.js:1919 templates/search.html:29
msgid "Searching" msgid "Searching"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:2175 #: templates/js/translated/forms.js:2172
msgid "Clear input" msgid "Clear input"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:2641 #: templates/js/translated/forms.js:2638
msgid "File Column" msgid "File Column"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:2641 #: templates/js/translated/forms.js:2638
msgid "Field Name" msgid "Field Name"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:2653 #: templates/js/translated/forms.js:2650
msgid "Select Columns" msgid "Select Columns"
msgstr "" msgstr ""
@@ -9737,11 +9751,11 @@ msgstr ""
msgid "Sales Order(s) must be selected before printing report" msgid "Sales Order(s) must be selected before printing report"
msgstr "" msgstr ""
#: templates/js/translated/search.js:394 #: templates/js/translated/search.js:410
msgid "Minimize results" msgid "Minimize results"
msgstr "" msgstr ""
#: templates/js/translated/search.js:397 #: templates/js/translated/search.js:413
msgid "Remove results" msgid "Remove results"
msgstr "" msgstr ""
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+124 -110
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-25 10:54+0000\n" "POT-Creation-Date: 2022-10-28 07:18+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -59,35 +59,35 @@ msgstr ""
msgid "Provided value does not match required pattern: " msgid "Provided value does not match required pattern: "
msgstr "" msgstr ""
#: InvenTree/forms.py:133 #: InvenTree/forms.py:134
msgid "Enter password" msgid "Enter password"
msgstr "" msgstr ""
#: InvenTree/forms.py:134 #: InvenTree/forms.py:135
msgid "Enter new password" msgid "Enter new password"
msgstr "" msgstr ""
#: InvenTree/forms.py:143 #: InvenTree/forms.py:144
msgid "Confirm password" msgid "Confirm password"
msgstr "" msgstr ""
#: InvenTree/forms.py:144 #: InvenTree/forms.py:145
msgid "Confirm new password" msgid "Confirm new password"
msgstr "" msgstr ""
#: InvenTree/forms.py:148 #: InvenTree/forms.py:149
msgid "Old password" msgid "Old password"
msgstr "" msgstr ""
#: InvenTree/forms.py:177 #: InvenTree/forms.py:178
msgid "Email (again)" msgid "Email (again)"
msgstr "" msgstr ""
#: InvenTree/forms.py:181 #: InvenTree/forms.py:182
msgid "Email address confirmation" msgid "Email address confirmation"
msgstr "" msgstr ""
#: InvenTree/forms.py:202 #: InvenTree/forms.py:203
msgid "You must type the same email each time." msgid "You must type the same email each time."
msgstr "" msgstr ""
@@ -131,30 +131,35 @@ msgstr ""
msgid "Empty serial number string" msgid "Empty serial number string"
msgstr "" msgstr ""
#: InvenTree/helpers.py:633 #: InvenTree/helpers.py:640
msgid "Duplicate serial" msgid "Duplicate serial"
msgstr "" msgstr ""
#: InvenTree/helpers.py:662 #: InvenTree/helpers.py:673 InvenTree/helpers.py:708
#, python-brace-format #, python-brace-format
msgid "Invalid group range: {g}" msgid "Invalid group range: {g}"
msgstr "" msgstr ""
#: InvenTree/helpers.py:714 InvenTree/helpers.py:721 InvenTree/helpers.py:736 #: InvenTree/helpers.py:702
#, python-brace-format
msgid "Group range {g} exceeds allowed quantity ({q})"
msgstr ""
#: InvenTree/helpers.py:726 InvenTree/helpers.py:733 InvenTree/helpers.py:748
#, python-brace-format #, python-brace-format
msgid "Invalid group sequence: {g}" msgid "Invalid group sequence: {g}"
msgstr "" msgstr ""
#: InvenTree/helpers.py:746 #: InvenTree/helpers.py:758
msgid "No serial numbers found" msgid "No serial numbers found"
msgstr "" msgstr ""
#: InvenTree/helpers.py:749 #: InvenTree/helpers.py:761
#, python-brace-format #, python-brace-format
msgid "Number of unique serial numbers ({s}) must match quantity ({q})" msgid "Number of unique serial numbers ({s}) must match quantity ({q})"
msgstr "" msgstr ""
#: InvenTree/helpers.py:948 #: InvenTree/helpers.py:960
msgid "Remove HTML tags from this value" msgid "Remove HTML tags from this value"
msgstr "" msgstr ""
@@ -224,9 +229,9 @@ msgstr ""
msgid "File comment" msgid "File comment"
msgstr "" msgstr ""
#: InvenTree/models.py:422 InvenTree/models.py:423 common/models.py:1726 #: InvenTree/models.py:422 InvenTree/models.py:423 common/models.py:1733
#: common/models.py:1727 common/models.py:1950 common/models.py:1951 #: common/models.py:1734 common/models.py:1957 common/models.py:1958
#: common/models.py:2213 common/models.py:2214 part/models.py:2254 #: common/models.py:2220 common/models.py:2221 part/models.py:2254
#: part/models.py:2274 plugin/models.py:260 plugin/models.py:261 #: part/models.py:2274 plugin/models.py:260 plugin/models.py:261
#: report/templates/report/inventree_test_report_base.html:96 #: report/templates/report/inventree_test_report_base.html:96
#: templates/js/translated/stock.js:2649 #: templates/js/translated/stock.js:2649
@@ -266,7 +271,7 @@ msgstr ""
msgid "Invalid choice" msgid "Invalid choice"
msgstr "" msgstr ""
#: InvenTree/models.py:557 InvenTree/models.py:558 common/models.py:1936 #: InvenTree/models.py:557 InvenTree/models.py:558 common/models.py:1943
#: company/models.py:358 label/models.py:101 part/models.py:760 #: company/models.py:358 label/models.py:101 part/models.py:760
#: part/models.py:2432 plugin/models.py:94 report/models.py:152 #: part/models.py:2432 plugin/models.py:94 report/models.py:152
#: templates/InvenTree/settings/mixins/urls.html:13 #: templates/InvenTree/settings/mixins/urls.html:13
@@ -678,24 +683,24 @@ msgstr ""
msgid "Production" msgid "Production"
msgstr "" msgstr ""
#: InvenTree/validators.py:19 #: InvenTree/validators.py:20
msgid "Not a valid currency code" msgid "Not a valid currency code"
msgstr "" msgstr ""
#: InvenTree/validators.py:90 #: InvenTree/validators.py:91
#, python-brace-format #, python-brace-format
msgid "IPN must match regex pattern {pat}" msgid "IPN must match regex pattern {pat}"
msgstr "" msgstr ""
#: InvenTree/validators.py:132 InvenTree/validators.py:148 #: InvenTree/validators.py:133 InvenTree/validators.py:149
msgid "Overage value must not be negative" msgid "Overage value must not be negative"
msgstr "" msgstr ""
#: InvenTree/validators.py:150 #: InvenTree/validators.py:151
msgid "Overage must not exceed 100%" msgid "Overage must not exceed 100%"
msgstr "" msgstr ""
#: InvenTree/validators.py:157 #: InvenTree/validators.py:158
msgid "Invalid value for overage" msgid "Invalid value for overage"
msgstr "" msgstr ""
@@ -746,7 +751,8 @@ msgstr ""
#: order/templates/order/so_sidebar.html:13 #: order/templates/order/so_sidebar.html:13
#: part/templates/part/part_sidebar.html:22 templates/InvenTree/index.html:221 #: part/templates/part/part_sidebar.html:22 templates/InvenTree/index.html:221
#: templates/InvenTree/search.html:141 #: templates/InvenTree/search.html:141
#: templates/InvenTree/settings/sidebar.html:47 users/models.py:41 #: templates/InvenTree/settings/sidebar.html:47
#: templates/js/translated/search.js:254 users/models.py:41
msgid "Build Orders" msgid "Build Orders"
msgstr "" msgstr ""
@@ -1010,7 +1016,7 @@ msgstr ""
#: build/models.py:1359 build/serializers.py:192 #: build/models.py:1359 build/serializers.py:192
#: build/templates/build/build_base.html:85 #: build/templates/build/build_base.html:85
#: build/templates/build/detail.html:34 common/models.py:1758 #: build/templates/build/detail.html:34 common/models.py:1765
#: company/templates/company/supplier_part.html:341 order/models.py:911 #: company/templates/company/supplier_part.html:341 order/models.py:911
#: order/models.py:1437 order/serializers.py:1213 #: order/models.py:1437 order/serializers.py:1213
#: order/templates/order/order_wizard/match_parts.html:30 part/forms.py:40 #: order/templates/order/order_wizard/match_parts.html:30 part/forms.py:40
@@ -2321,7 +2327,7 @@ msgstr ""
msgid "Enable plugins to respond to internal events" msgid "Enable plugins to respond to internal events"
msgstr "" msgstr ""
#: common/models.py:1391 common/models.py:1719 #: common/models.py:1391 common/models.py:1726
msgid "Settings key (must be unique - case insensitive" msgid "Settings key (must be unique - case insensitive"
msgstr "" msgstr ""
@@ -2558,120 +2564,128 @@ msgid "Display companies in search preview window"
msgstr "" msgstr ""
#: common/models.py:1616 #: common/models.py:1616
msgid "Search Purchase Orders" msgid "Search Build Orders"
msgstr "" msgstr ""
#: common/models.py:1617 #: common/models.py:1617
msgid "Display purchase orders in search preview window" msgid "Display build orders in search preview window"
msgstr "" msgstr ""
#: common/models.py:1623 #: common/models.py:1623
msgid "Exclude Inactive Purchase Orders" msgid "Search Purchase Orders"
msgstr "" msgstr ""
#: common/models.py:1624 #: common/models.py:1624
msgid "Exclude inactive purchase orders from search preview window" msgid "Display purchase orders in search preview window"
msgstr "" msgstr ""
#: common/models.py:1630 #: common/models.py:1630
msgid "Search Sales Orders" msgid "Exclude Inactive Purchase Orders"
msgstr "" msgstr ""
#: common/models.py:1631 #: common/models.py:1631
msgid "Display sales orders in search preview window" msgid "Exclude inactive purchase orders from search preview window"
msgstr "" msgstr ""
#: common/models.py:1637 #: common/models.py:1637
msgid "Exclude Inactive Sales Orders" msgid "Search Sales Orders"
msgstr "" msgstr ""
#: common/models.py:1638 #: common/models.py:1638
msgid "Exclude inactive sales orders from search preview window" msgid "Display sales orders in search preview window"
msgstr "" msgstr ""
#: common/models.py:1644 #: common/models.py:1644
msgid "Search Preview Results" msgid "Exclude Inactive Sales Orders"
msgstr "" msgstr ""
#: common/models.py:1645 #: common/models.py:1645
msgid "Number of results to show in each section of the search preview window" msgid "Exclude inactive sales orders from search preview window"
msgstr "" msgstr ""
#: common/models.py:1651 #: common/models.py:1651
msgid "Show Quantity in Forms" msgid "Search Preview Results"
msgstr "" msgstr ""
#: common/models.py:1652 #: common/models.py:1652
msgid "Display available part quantity in some forms" msgid "Number of results to show in each section of the search preview window"
msgstr "" msgstr ""
#: common/models.py:1658 #: common/models.py:1658
msgid "Escape Key Closes Forms" msgid "Show Quantity in Forms"
msgstr "" msgstr ""
#: common/models.py:1659 #: common/models.py:1659
msgid "Use the escape key to close modal forms" msgid "Display available part quantity in some forms"
msgstr "" msgstr ""
#: common/models.py:1665 #: common/models.py:1665
msgid "Fixed Navbar" msgid "Escape Key Closes Forms"
msgstr "" msgstr ""
#: common/models.py:1666 #: common/models.py:1666
msgid "The navbar position is fixed to the top of the screen" msgid "Use the escape key to close modal forms"
msgstr "" msgstr ""
#: common/models.py:1672 #: common/models.py:1672
msgid "Date Format" msgid "Fixed Navbar"
msgstr "" msgstr ""
#: common/models.py:1673 #: common/models.py:1673
msgid "The navbar position is fixed to the top of the screen"
msgstr ""
#: common/models.py:1679
msgid "Date Format"
msgstr ""
#: common/models.py:1680
msgid "Preferred format for displaying dates" msgid "Preferred format for displaying dates"
msgstr "" msgstr ""
#: common/models.py:1687 part/templates/part/detail.html:41 #: common/models.py:1694 part/templates/part/detail.html:41
msgid "Part Scheduling" msgid "Part Scheduling"
msgstr "" msgstr ""
#: common/models.py:1688 #: common/models.py:1695
msgid "Display part scheduling information" msgid "Display part scheduling information"
msgstr "" msgstr ""
#: common/models.py:1694 #: common/models.py:1701
msgid "Table String Length" msgid "Table String Length"
msgstr "" msgstr ""
#: common/models.py:1695 #: common/models.py:1702
msgid "Maximimum length limit for strings displayed in table views" msgid "Maximimum length limit for strings displayed in table views"
msgstr "" msgstr ""
#: common/models.py:1759 #: common/models.py:1766
msgid "Price break quantity" msgid "Price break quantity"
msgstr "" msgstr ""
#: common/models.py:1766 company/serializers.py:372 #: common/models.py:1773 company/serializers.py:372
#: company/templates/company/supplier_part.html:346 order/models.py:952 #: company/templates/company/supplier_part.html:346 order/models.py:952
#: templates/js/translated/part.js:1103 templates/js/translated/part.js:2223 #: templates/js/translated/part.js:1103 templates/js/translated/part.js:2223
msgid "Price" msgid "Price"
msgstr "" msgstr ""
#: common/models.py:1767 #: common/models.py:1774
msgid "Unit price at specified quantity" msgid "Unit price at specified quantity"
msgstr "" msgstr ""
#: common/models.py:1927 common/models.py:2105 #: common/models.py:1934 common/models.py:2112
msgid "Endpoint" msgid "Endpoint"
msgstr "" msgstr ""
#: common/models.py:1928 #: common/models.py:1935
msgid "Endpoint at which this webhook is received" msgid "Endpoint at which this webhook is received"
msgstr "" msgstr ""
#: common/models.py:1937 #: common/models.py:1944
msgid "Name for this webhook" msgid "Name for this webhook"
msgstr "" msgstr ""
#: common/models.py:1942 part/models.py:935 plugin/models.py:100 #: common/models.py:1949 part/models.py:935 plugin/models.py:100
#: templates/js/translated/table_filters.js:34 #: templates/js/translated/table_filters.js:34
#: templates/js/translated/table_filters.js:112 #: templates/js/translated/table_filters.js:112
#: templates/js/translated/table_filters.js:324 #: templates/js/translated/table_filters.js:324
@@ -2679,67 +2693,67 @@ msgstr ""
msgid "Active" msgid "Active"
msgstr "" msgstr ""
#: common/models.py:1943 #: common/models.py:1950
msgid "Is this webhook active" msgid "Is this webhook active"
msgstr "" msgstr ""
#: common/models.py:1957 #: common/models.py:1964
msgid "Token" msgid "Token"
msgstr "" msgstr ""
#: common/models.py:1958 #: common/models.py:1965
msgid "Token for access" msgid "Token for access"
msgstr "" msgstr ""
#: common/models.py:1965 #: common/models.py:1972
msgid "Secret" msgid "Secret"
msgstr "" msgstr ""
#: common/models.py:1966 #: common/models.py:1973
msgid "Shared secret for HMAC" msgid "Shared secret for HMAC"
msgstr "" msgstr ""
#: common/models.py:2072 #: common/models.py:2079
msgid "Message ID" msgid "Message ID"
msgstr "" msgstr ""
#: common/models.py:2073 #: common/models.py:2080
msgid "Unique identifier for this message" msgid "Unique identifier for this message"
msgstr "" msgstr ""
#: common/models.py:2081 #: common/models.py:2088
msgid "Host" msgid "Host"
msgstr "" msgstr ""
#: common/models.py:2082 #: common/models.py:2089
msgid "Host from which this message was received" msgid "Host from which this message was received"
msgstr "" msgstr ""
#: common/models.py:2089 #: common/models.py:2096
msgid "Header" msgid "Header"
msgstr "" msgstr ""
#: common/models.py:2090 #: common/models.py:2097
msgid "Header of this message" msgid "Header of this message"
msgstr "" msgstr ""
#: common/models.py:2096 #: common/models.py:2103
msgid "Body" msgid "Body"
msgstr "" msgstr ""
#: common/models.py:2097 #: common/models.py:2104
msgid "Body of this message" msgid "Body of this message"
msgstr "" msgstr ""
#: common/models.py:2106 #: common/models.py:2113
msgid "Endpoint on which this message was received" msgid "Endpoint on which this message was received"
msgstr "" msgstr ""
#: common/models.py:2111 #: common/models.py:2118
msgid "Worked on" msgid "Worked on"
msgstr "" msgstr ""
#: common/models.py:2112 #: common/models.py:2119
msgid "Was the work on this message finished?" msgid "Was the work on this message finished?"
msgstr "" msgstr ""
@@ -3228,7 +3242,7 @@ msgstr ""
#: part/templates/part/detail.html:84 part/templates/part/part_sidebar.html:37 #: part/templates/part/detail.html:84 part/templates/part/part_sidebar.html:37
#: templates/InvenTree/index.html:252 templates/InvenTree/search.html:200 #: templates/InvenTree/index.html:252 templates/InvenTree/search.html:200
#: templates/InvenTree/settings/sidebar.html:49 #: templates/InvenTree/settings/sidebar.html:49
#: templates/js/translated/search.js:277 templates/navbar.html:50 #: templates/js/translated/search.js:293 templates/navbar.html:50
#: users/models.py:42 #: users/models.py:42
msgid "Purchase Orders" msgid "Purchase Orders"
msgstr "" msgstr ""
@@ -3251,7 +3265,7 @@ msgstr ""
#: part/templates/part/detail.html:107 part/templates/part/part_sidebar.html:41 #: part/templates/part/detail.html:107 part/templates/part/part_sidebar.html:41
#: templates/InvenTree/index.html:283 templates/InvenTree/search.html:220 #: templates/InvenTree/index.html:283 templates/InvenTree/search.html:220
#: templates/InvenTree/settings/sidebar.html:51 #: templates/InvenTree/settings/sidebar.html:51
#: templates/js/translated/search.js:301 templates/navbar.html:61 #: templates/js/translated/search.js:317 templates/navbar.html:61
#: users/models.py:43 #: users/models.py:43
msgid "Sales Orders" msgid "Sales Orders"
msgstr "" msgstr ""
@@ -3321,7 +3335,7 @@ msgstr ""
#: company/templates/company/manufacturer_part.html:136 #: company/templates/company/manufacturer_part.html:136
#: company/templates/company/manufacturer_part.html:183 #: company/templates/company/manufacturer_part.html:183
#: part/templates/part/detail.html:371 part/templates/part/detail.html:401 #: part/templates/part/detail.html:371 part/templates/part/detail.html:401
#: templates/js/translated/forms.js:458 templates/js/translated/helpers.js:36 #: templates/js/translated/forms.js:455 templates/js/translated/helpers.js:36
#: users/models.py:222 #: users/models.py:222
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
@@ -3570,7 +3584,7 @@ msgstr ""
msgid "New Customer" msgid "New Customer"
msgstr "" msgstr ""
#: company/views.py:52 templates/js/translated/search.js:254 #: company/views.py:52 templates/js/translated/search.js:270
msgid "Companies" msgid "Companies"
msgstr "" msgstr ""
@@ -4379,47 +4393,47 @@ msgstr ""
msgid "Updated {part} unit-price to {price} and quantity to {qty}" msgid "Updated {part} unit-price to {price} and quantity to {qty}"
msgstr "" msgstr ""
#: part/api.py:514 #: part/api.py:516
msgid "Incoming Purchase Order" msgid "Incoming Purchase Order"
msgstr "" msgstr ""
#: part/api.py:534 #: part/api.py:536
msgid "Outgoing Sales Order" msgid "Outgoing Sales Order"
msgstr "" msgstr ""
#: part/api.py:552 #: part/api.py:554
msgid "Stock produced by Build Order" msgid "Stock produced by Build Order"
msgstr "" msgstr ""
#: part/api.py:638 #: part/api.py:640
msgid "Stock required for Build Order" msgid "Stock required for Build Order"
msgstr "" msgstr ""
#: part/api.py:775 #: part/api.py:777
msgid "Valid" msgid "Valid"
msgstr "" msgstr ""
#: part/api.py:776 #: part/api.py:778
msgid "Validate entire Bill of Materials" msgid "Validate entire Bill of Materials"
msgstr "" msgstr ""
#: part/api.py:782 #: part/api.py:784
msgid "This option must be selected" msgid "This option must be selected"
msgstr "" msgstr ""
#: part/api.py:1205 #: part/api.py:1207
msgid "Must be greater than zero" msgid "Must be greater than zero"
msgstr "" msgstr ""
#: part/api.py:1209 #: part/api.py:1211
msgid "Must be a valid quantity" msgid "Must be a valid quantity"
msgstr "" msgstr ""
#: part/api.py:1224 #: part/api.py:1226
msgid "Specify location for initial part stock" msgid "Specify location for initial part stock"
msgstr "" msgstr ""
#: part/api.py:1255 part/api.py:1259 part/api.py:1274 part/api.py:1278 #: part/api.py:1257 part/api.py:1261 part/api.py:1276 part/api.py:1280
msgid "This field is required" msgid "This field is required"
msgstr "" msgstr ""
@@ -5755,23 +5769,23 @@ msgstr ""
msgid "No matching action found" msgid "No matching action found"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:52 plugin/base/barcodes/api.py:110 #: plugin/base/barcodes/api.py:54 plugin/base/barcodes/api.py:113
msgid "Must provide barcode_data parameter" msgid "Missing barcode data"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:82 #: plugin/base/barcodes/api.py:83
msgid "No match found for barcode data" msgid "No match found for barcode data"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:86 #: plugin/base/barcodes/api.py:87
msgid "Match found for barcode data" msgid "Match found for barcode data"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:125 #: plugin/base/barcodes/api.py:126
msgid "Barcode matches existing item" msgid "Barcode matches existing item"
msgstr "" msgstr ""
#: plugin/base/barcodes/api.py:222 #: plugin/base/barcodes/api.py:223
msgid "No match found for provided value" msgid "No match found for provided value"
msgstr "" msgstr ""
@@ -7603,7 +7617,7 @@ msgstr ""
msgid "Please confirm that <a href=\"mailto:%(email)s\">%(email)s</a> is an email address for user %(user_display)s." msgid "Please confirm that <a href=\"mailto:%(email)s\">%(email)s</a> is an email address for user %(user_display)s."
msgstr "" msgstr ""
#: templates/account/email_confirm.html:22 templates/js/translated/forms.js:649 #: templates/account/email_confirm.html:22 templates/js/translated/forms.js:646
msgid "Confirm" msgid "Confirm"
msgstr "" msgstr ""
@@ -8653,61 +8667,61 @@ msgstr ""
msgid "Create filter" msgid "Create filter"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:372 templates/js/translated/forms.js:387 #: templates/js/translated/forms.js:369 templates/js/translated/forms.js:384
#: templates/js/translated/forms.js:401 templates/js/translated/forms.js:415 #: templates/js/translated/forms.js:398 templates/js/translated/forms.js:412
msgid "Action Prohibited" msgid "Action Prohibited"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:374 #: templates/js/translated/forms.js:371
msgid "Create operation not allowed" msgid "Create operation not allowed"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:389 #: templates/js/translated/forms.js:386
msgid "Update operation not allowed" msgid "Update operation not allowed"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:403 #: templates/js/translated/forms.js:400
msgid "Delete operation not allowed" msgid "Delete operation not allowed"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:417 #: templates/js/translated/forms.js:414
msgid "View operation not allowed" msgid "View operation not allowed"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:675 #: templates/js/translated/forms.js:672
msgid "Keep this form open" msgid "Keep this form open"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:776 #: templates/js/translated/forms.js:773
msgid "Enter a valid number" msgid "Enter a valid number"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:1269 templates/modals.html:19 #: templates/js/translated/forms.js:1266 templates/modals.html:19
#: templates/modals.html:43 #: templates/modals.html:43
msgid "Form errors exist" msgid "Form errors exist"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:1706 #: templates/js/translated/forms.js:1703
msgid "No results found" msgid "No results found"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:1922 templates/search.html:29 #: templates/js/translated/forms.js:1919 templates/search.html:29
msgid "Searching" msgid "Searching"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:2175 #: templates/js/translated/forms.js:2172
msgid "Clear input" msgid "Clear input"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:2641 #: templates/js/translated/forms.js:2638
msgid "File Column" msgid "File Column"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:2641 #: templates/js/translated/forms.js:2638
msgid "Field Name" msgid "Field Name"
msgstr "" msgstr ""
#: templates/js/translated/forms.js:2653 #: templates/js/translated/forms.js:2650
msgid "Select Columns" msgid "Select Columns"
msgstr "" msgstr ""
@@ -9737,11 +9751,11 @@ msgstr ""
msgid "Sales Order(s) must be selected before printing report" msgid "Sales Order(s) must be selected before printing report"
msgstr "" msgstr ""
#: templates/js/translated/search.js:394 #: templates/js/translated/search.js:410
msgid "Minimize results" msgid "Minimize results"
msgstr "" msgstr ""
#: templates/js/translated/search.js:397 #: templates/js/translated/search.js:413
msgid "Remove results" msgid "Remove results"
msgstr "" msgstr ""
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -48,7 +48,7 @@
<h4>{% trans "Extra Lines" %}</h4> <h4>{% trans "Extra Lines" %}</h4>
{% include "spacer.html" %} {% include "spacer.html" %}
<div class='btn-group' role='group'> <div class='btn-group' role='group'>
{% if roles.purchase_order.change and order.status == PurchaseOrderStatus.PENDING %} {% if roles.purchase_order.change %}
<button type='button' class='btn btn-success' id='new-po-extra-line'> <button type='button' class='btn btn-success' id='new-po-extra-line'>
<span class='fas fa-plus-circle'></span> {% trans "Add Extra Line" %} <span class='fas fa-plus-circle'></span> {% trans "Add Extra Line" %}
</button> </button>
@@ -42,7 +42,7 @@
<h4>{% trans "Extra Lines" %}</h4> <h4>{% trans "Extra Lines" %}</h4>
{% include "spacer.html" %} {% include "spacer.html" %}
<div class='btn-group' role='group'> <div class='btn-group' role='group'>
{% if roles.sales_order.change and order.is_pending %} {% if roles.sales_order.change %}
<button type='button' class='btn btn-success' id='new-so-extra-line'> <button type='button' class='btn btn-success' id='new-so-extra-line'>
<span class='fas fa-plus-circle'></span> {% trans "Add Extra Line" %} <span class='fas fa-plus-circle'></span> {% trans "Add Extra Line" %}
</button> </button>
@@ -0,0 +1,18 @@
# Generated by Django 3.2.16 on 2022-10-30 23:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('part', '0087_bomitem_consumable'),
]
operations = [
migrations.AlterField(
model_name='partparametertemplate',
name='name',
field=models.CharField(help_text='Parameter Name', max_length=100, unique=True, verbose_name='Name'),
),
]
+2 -8
View File
@@ -2383,10 +2383,7 @@ class PartTestTemplate(models.Model):
def validate_template_name(name): def validate_template_name(name):
"""Prevent illegal characters in "name" field for PartParameterTemplate.""" """Placeholder for legacy function used in migrations."""
for c in "\"\'`!?|": # noqa: P103
if c in str(name):
raise ValidationError(_(f"Illegal character in template name ({c})"))
class PartParameterTemplate(models.Model): class PartParameterTemplate(models.Model):
@@ -2431,10 +2428,7 @@ class PartParameterTemplate(models.Model):
max_length=100, max_length=100,
verbose_name=_('Name'), verbose_name=_('Name'),
help_text=_('Parameter Name'), help_text=_('Parameter Name'),
unique=True, unique=True
validators=[
validate_template_name,
]
) )
units = models.CharField(max_length=25, verbose_name=_('Units'), help_text=_('Parameter Units'), blank=True) units = models.CharField(max_length=25, verbose_name=_('Units'), help_text=_('Parameter Units'), blank=True)
+10 -5
View File
@@ -63,11 +63,6 @@
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if roles.part_category.add %}
<button class='btn btn-success' id='cat-create' title='{% trans "Create new part category" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Category" %}
</button>
{% endif %}
{% endblock %} {% endblock %}
{% block details_left %} {% block details_left %}
@@ -225,7 +220,17 @@
<div class='panel panel-hidden' id='panel-subcategories'> <div class='panel panel-hidden' id='panel-subcategories'>
<div class='panel-heading'> <div class='panel-heading'>
<div class='d-flex flex-wrap'>
<h4>{% trans "Subcategories" %}</h4> <h4>{% trans "Subcategories" %}</h4>
{% include "spacer.html" %}
<div class='btn-group' role='group'>
{% if roles.part_category.add %}
<button class='btn btn-success' id='cat-create' title='{% trans "Create new part category" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Category" %}
</button>
{% endif %}
</div>
</div>
</div> </div>
<div class='panel-content'> <div class='panel-content'>
<div id='subcategory-button-toolbar'> <div id='subcategory-button-toolbar'>
+2 -2
View File
@@ -593,7 +593,7 @@ class PartAPITest(InvenTreeAPITestCase):
{ {
'convert_from': variant.pk, 'convert_from': variant.pk,
}, },
expected_code=200 expected_code=200,
) )
# There should be the same number of results for each request # There should be the same number of results for each request
@@ -1854,7 +1854,7 @@ class BomItemTest(InvenTreeAPITestCase):
data={ data={
'validated': True, 'validated': True,
}, },
expected_code=200 expected_code=200,
) )
# Check that the expected response is returned # Check that the expected response is returned
+1 -1
View File
@@ -52,7 +52,7 @@ class PluginConfigAdmin(admin.ModelAdmin):
"""Custom admin with restricted id fields.""" """Custom admin with restricted id fields."""
readonly_fields = ["key", "name", ] readonly_fields = ["key", "name", ]
list_display = ['name', 'key', '__str__', 'active', 'is_sample'] list_display = ['name', 'key', '__str__', 'active', 'is_builtin', 'is_sample']
list_filter = ['active'] list_filter = ['active']
actions = [plugin_activate, plugin_deactivate, ] actions = [plugin_activate, plugin_deactivate, ]
inlines = [PluginSettingInline, ] inlines = [PluginSettingInline, ]
+2 -3
View File
@@ -7,7 +7,6 @@ The main code for plugin special sauce is in the plugin registry in `InvenTree/p
import logging import logging
from django.apps import AppConfig from django.apps import AppConfig
from django.conf import settings
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from maintenance_mode.core import set_maintenance_mode from maintenance_mode.core import set_maintenance_mode
@@ -26,7 +25,6 @@ class PluginAppConfig(AppConfig):
def ready(self): def ready(self):
"""The ready method is extended to initialize plugins.""" """The ready method is extended to initialize plugins."""
if settings.PLUGINS_ENABLED:
if not canAppAccessDatabase(allow_test=True, allow_plugins=True): if not canAppAccessDatabase(allow_test=True, allow_plugins=True):
logger.info("Skipping plugin loading sequence") # pragma: no cover logger.info("Skipping plugin loading sequence") # pragma: no cover
else: else:
@@ -52,8 +50,9 @@ class PluginAppConfig(AppConfig):
# check git version # check git version
registry.git_is_modern = check_git_version() registry.git_is_modern = check_git_version()
if not registry.git_is_modern: # pragma: no cover # simulating old git seems not worth it for coverage if not registry.git_is_modern: # pragma: no cover # simulating old git seems not worth it for coverage
log_error(_('Your enviroment has an outdated git version. This prevents InvenTree from loading plugin details.'), 'load') log_error(_('Your environment has an outdated git version. This prevents InvenTree from loading plugin details.'), 'load')
else: else:
logger.info("Plugins not enabled - skipping loading sequence") # pragma: no cover logger.info("Plugins not enabled - skipping loading sequence") # pragma: no cover
+7 -13
View File
@@ -11,8 +11,8 @@ from rest_framework.views import APIView
from InvenTree.helpers import hash_barcode from InvenTree.helpers import hash_barcode
from plugin import registry from plugin import registry
from plugin.builtin.barcodes.inventree_barcode import ( from plugin.builtin.barcodes.inventree_barcode import \
InvenTreeExternalBarcodePlugin, InvenTreeInternalBarcodePlugin) InvenTreeInternalBarcodePlugin
from users.models import RuleSet from users.models import RuleSet
@@ -53,11 +53,8 @@ class BarcodeScan(APIView):
if not barcode_data: if not barcode_data:
raise ValidationError({'barcode': _('Missing barcode data')}) raise ValidationError({'barcode': _('Missing barcode data')})
# Ensure that the default barcode handlers are run first # Note: the default barcode handlers are loaded (and thus run) first
plugins = [ plugins = registry.with_mixin('barcode')
InvenTreeInternalBarcodePlugin(),
InvenTreeExternalBarcodePlugin(),
] + registry.with_mixin('barcode')
barcode_hash = hash_barcode(barcode_data) barcode_hash = hash_barcode(barcode_data)
@@ -113,10 +110,7 @@ class BarcodeAssign(APIView):
raise ValidationError({'barcode': _('Missing barcode data')}) raise ValidationError({'barcode': _('Missing barcode data')})
# Here we only check against 'InvenTree' plugins # Here we only check against 'InvenTree' plugins
plugins = [ plugins = registry.with_mixin('barcode', builtin=True)
InvenTreeInternalBarcodePlugin(),
InvenTreeExternalBarcodePlugin(),
]
# First check if the provided barcode matches an existing database entry # First check if the provided barcode matches an existing database entry
for plugin in plugins: for plugin in plugins:
@@ -133,7 +127,7 @@ class BarcodeAssign(APIView):
valid_labels = [] valid_labels = []
for model in InvenTreeExternalBarcodePlugin.get_supported_barcode_models(): for model in InvenTreeInternalBarcodePlugin.get_supported_barcode_models():
label = model.barcode_model_type() label = model.barcode_model_type()
valid_labels.append(label) valid_labels.append(label)
@@ -188,7 +182,7 @@ class BarcodeUnassign(APIView):
"""Respond to a barcode unassign POST request""" """Respond to a barcode unassign POST request"""
# The following database models support assignment of third-party barcodes # The following database models support assignment of third-party barcodes
supported_models = InvenTreeExternalBarcodePlugin.get_supported_barcode_models() supported_models = InvenTreeInternalBarcodePlugin.get_supported_barcode_models()
supported_labels = [model.barcode_model_type() for model in supported_models] supported_labels = [model.barcode_model_type() for model in supported_models]
model_names = ', '.join(supported_labels) model_names = ', '.join(supported_labels)
+2 -3
View File
@@ -58,9 +58,8 @@ def register_event(event, *args, **kwargs):
if plugin.mixin_enabled('events'): if plugin.mixin_enabled('events'):
config = plugin.plugin_config() if plugin.is_active():
# Only allow event registering for 'active' plugins
if config and config.active:
logger.debug(f"Registering callback for plugin '{slug}'") logger.debug(f"Registering callback for plugin '{slug}'")
+1 -1
View File
@@ -30,7 +30,7 @@ def print_label(plugin_slug: str, pdf_data, filename=None, label_instance=None,
""" """
logger.info(f"Plugin '{plugin_slug}' is printing a label '{filename}'") logger.info(f"Plugin '{plugin_slug}' is printing a label '{filename}'")
plugin = registry.plugins.get(plugin_slug, None) plugin = registry.get_plugin(plugin_slug)
if plugin is None: # pragma: no cover if plugin is None: # pragma: no cover
logger.error(f"Could not find matching plugin for '{plugin_slug}'") logger.error(f"Could not find matching plugin for '{plugin_slug}'")
@@ -9,6 +9,8 @@ references model objects actually exist in the database.
import json import json
from django.utils.translation import gettext_lazy as _
from company.models import SupplierPart from company.models import SupplierPart
from InvenTree.helpers import hash_barcode from InvenTree.helpers import hash_barcode
from part.models import Part from part.models import Part
@@ -17,8 +19,14 @@ from plugin.mixins import BarcodeMixin
from stock.models import StockItem, StockLocation from stock.models import StockItem, StockLocation
class InvenTreeBarcodePlugin(BarcodeMixin, InvenTreePlugin): class InvenTreeInternalBarcodePlugin(BarcodeMixin, InvenTreePlugin):
"""Generic base class for handling InvenTree barcodes""" """Builtin BarcodePlugin for matching and generating internal barcodes."""
NAME = "InvenTreeBarcode"
TITLE = _("Inventree Barcodes")
DESCRIPTION = _("Provides native support for barcodes")
VERSION = "2.0.0"
AUTHOR = _("InvenTree contributors")
@staticmethod @staticmethod
def get_supported_barcode_models(): def get_supported_barcode_models():
@@ -58,58 +66,43 @@ class InvenTreeBarcodePlugin(BarcodeMixin, InvenTreePlugin):
return response return response
class InvenTreeInternalBarcodePlugin(InvenTreeBarcodePlugin):
"""Builtin BarcodePlugin for matching and generating internal barcodes."""
NAME = "InvenTreeInternalBarcode"
def scan(self, barcode_data): def scan(self, barcode_data):
"""Scan a barcode against this plugin. """Scan a barcode against this plugin.
Here we are looking for a dict object which contains a reference to a particular InvenTree database object Here we are looking for a dict object which contains a reference to a particular InvenTree database object
""" """
# Create hash from raw barcode data
barcode_hash = hash_barcode(barcode_data)
# Attempt to coerce the barcode data into a dict object
# This is the internal barcode representation that InvenTree uses
barcode_dict = None
if type(barcode_data) is dict: if type(barcode_data) is dict:
pass barcode_dict = barcode_data
elif type(barcode_data) is str: elif type(barcode_data) is str:
try: try:
barcode_data = json.loads(barcode_data) barcode_dict = json.loads(barcode_data)
except json.JSONDecodeError: except json.JSONDecodeError:
return None pass
else:
return None
if type(barcode_data) is not dict:
return None
if barcode_dict is not None and type(barcode_dict) is dict:
# Look for various matches. First good match will be returned # Look for various matches. First good match will be returned
for model in self.get_supported_barcode_models(): for model in self.get_supported_barcode_models():
label = model.barcode_model_type() label = model.barcode_model_type()
if label in barcode_data:
if label in barcode_dict:
try: try:
instance = model.objects.get(pk=barcode_data[label]) instance = model.objects.get(pk=barcode_dict[label])
return self.format_matched_response(label, model, instance) return self.format_matched_response(label, model, instance)
except (ValueError, model.DoesNotExist): except (ValueError, model.DoesNotExist):
pass pass
# If no "direct" hits are found, look for assigned third-party barcodes
class InvenTreeExternalBarcodePlugin(InvenTreeBarcodePlugin):
"""Builtin BarcodePlugin for matching arbitrary external barcodes."""
NAME = "InvenTreeExternalBarcode"
def scan(self, barcode_data):
"""Scan a barcode against this plugin.
Here we are looking for a dict object which contains a reference to a particular InvenTree databse object
"""
for model in self.get_supported_barcode_models(): for model in self.get_supported_barcode_models():
label = model.barcode_model_type() label = model.barcode_model_type()
barcode_hash = hash_barcode(barcode_data)
instance = model.lookup_barcode(barcode_hash) instance = model.lookup_barcode(barcode_hash)
if instance is not None: if instance is not None:
@@ -29,7 +29,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
'barcode': barcode_data, 'barcode': barcode_data,
'stockitem': 521 'stockitem': 521
}, },
expected_code=400 expected_code=400,
) )
self.assertIn('error', response.data) self.assertIn('error', response.data)
@@ -250,7 +250,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
) )
self.assertIn('success', response.data) self.assertIn('success', response.data)
self.assertEqual(response.data['plugin'], 'InvenTreeExternalBarcode') self.assertEqual(response.data['plugin'], 'InvenTreeBarcode')
self.assertEqual(response.data['part']['pk'], 1) self.assertEqual(response.data['part']['pk'], 1)
# Attempting to assign the same barcode to a different part should result in an error # Attempting to assign the same barcode to a different part should result in an error
@@ -347,7 +347,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
response = self.scan({'barcode': 'blbla=10004'}, expected_code=200) response = self.scan({'barcode': 'blbla=10004'}, expected_code=200)
self.assertEqual(response.data['barcode_data'], 'blbla=10004') self.assertEqual(response.data['barcode_data'], 'blbla=10004')
self.assertEqual(response.data['plugin'], 'InvenTreeExternalBarcode') self.assertEqual(response.data['plugin'], 'InvenTreeBarcode')
# Scan for a StockItem instance # Scan for a StockItem instance
si = stock.models.StockItem.objects.get(pk=1) si = stock.models.StockItem.objects.get(pk=1)
@@ -402,7 +402,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
self.assertEqual(response.data['stocklocation']['pk'], 5) self.assertEqual(response.data['stocklocation']['pk'], 5)
self.assertEqual(response.data['stocklocation']['api_url'], '/api/stock/location/5/') self.assertEqual(response.data['stocklocation']['api_url'], '/api/stock/location/5/')
self.assertEqual(response.data['stocklocation']['web_url'], '/stock/location/5/') self.assertEqual(response.data['stocklocation']['web_url'], '/stock/location/5/')
self.assertEqual(response.data['plugin'], 'InvenTreeInternalBarcode') self.assertEqual(response.data['plugin'], 'InvenTreeBarcode')
# Scan a Part object # Scan a Part object
response = self.scan( response = self.scan(
@@ -423,7 +423,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
) )
self.assertEqual(response.data['supplierpart']['pk'], 1) self.assertEqual(response.data['supplierpart']['pk'], 1)
self.assertEqual(response.data['plugin'], 'InvenTreeInternalBarcode') self.assertEqual(response.data['plugin'], 'InvenTreeBarcode')
self.assertIn('success', response.data) self.assertIn('success', response.data)
self.assertIn('barcode_data', response.data) self.assertIn('barcode_data', response.data)
@@ -27,8 +27,10 @@ class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
"""Core notification methods for InvenTree.""" """Core notification methods for InvenTree."""
NAME = "CoreNotificationsPlugin" NAME = "CoreNotificationsPlugin"
TITLE = _("InvenTree Notifications")
AUTHOR = _('InvenTree contributors') AUTHOR = _('InvenTree contributors')
DESCRIPTION = _('Integrated outgoing notificaton methods') DESCRIPTION = _('Integrated outgoing notificaton methods')
VERSION = "1.0.0"
SETTINGS = { SETTINGS = {
'ENABLE_NOTIFICATION_EMAILS': { 'ENABLE_NOTIFICATION_EMAILS': {
+10 -6
View File
@@ -158,16 +158,20 @@ class PluginConfig(models.Model):
@admin.display(boolean=True, description=_('Sample plugin')) @admin.display(boolean=True, description=_('Sample plugin'))
def is_sample(self) -> bool: def is_sample(self) -> bool:
"""Is this plugin a sample app?""" """Is this plugin a sample app?"""
# Loaded and active plugin
if isinstance(self.plugin, InvenTreePlugin):
return self.plugin.check_is_sample()
# If no plugin_class is available it can not be a sample
if not self.plugin: if not self.plugin:
return False return False
# Not loaded plugin return self.plugin.check_is_sample()
return self.plugin.check_is_sample() # pragma: no cover
@admin.display(boolean=True, description=_('Builtin Plugin'))
def is_builtin(self) -> bool:
"""Return True if this is a 'builtin' plugin"""
if not self.plugin:
return False
return self.plugin.check_is_builtin()
class PluginSetting(common.models.BaseInvenTreeSetting): class PluginSetting(common.models.BaseInvenTreeSetting):
+18 -3
View File
@@ -106,10 +106,15 @@ class MetaBase:
def is_active(self): def is_active(self):
"""Return True if this plugin is currently active.""" """Return True if this plugin is currently active."""
cfg = self.plugin_config()
if cfg: # Builtin plugins are always considered "active"
return cfg.active if self.is_builtin:
return True
config = self.plugin_config()
if config:
return config.active
else: else:
return False # pragma: no cover return False # pragma: no cover
@@ -300,6 +305,16 @@ class InvenTreePlugin(VersionMixin, MixinBase, MetaBase):
"""Is this plugin part of the samples?""" """Is this plugin part of the samples?"""
return self.check_is_sample() return self.check_is_sample()
@classmethod
def check_is_builtin(cls) -> bool:
"""Determine if a particular plugin class is a 'builtin' plugin"""
return str(cls.check_package_path()).startswith('plugin/builtin')
@property
def is_builtin(self) -> bool:
"""Is this plugin is builtin"""
return self.check_is_builtin()
@classmethod @classmethod
def check_package_path(cls): def check_package_path(cls):
"""Path to the plugin.""" """Path to the plugin."""
+24 -17
View File
@@ -108,9 +108,6 @@ class PluginsRegistry:
Args: Args:
full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False. full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False.
""" """
if not settings.PLUGINS_ENABLED:
# Plugins not enabled, do nothing
return # pragma: no cover
logger.info('Start loading plugins') logger.info('Start loading plugins')
@@ -167,9 +164,6 @@ class PluginsRegistry:
def unload_plugins(self): def unload_plugins(self):
"""Unload and deactivate all IntegrationPlugins.""" """Unload and deactivate all IntegrationPlugins."""
if not settings.PLUGINS_ENABLED:
# Plugins not enabled, do nothing
return # pragma: no cover
logger.info('Start unloading plugins') logger.info('Start unloading plugins')
@@ -187,6 +181,7 @@ class PluginsRegistry:
# remove maintenance # remove maintenance
if not _maintenance: if not _maintenance:
set_maintenance_mode(False) # pragma: no cover set_maintenance_mode(False) # pragma: no cover
logger.info('Finished unloading plugins') logger.info('Finished unloading plugins')
def reload_plugins(self, full_reload: bool = False): def reload_plugins(self, full_reload: bool = False):
@@ -210,8 +205,12 @@ class PluginsRegistry:
def plugin_dirs(self): def plugin_dirs(self):
"""Construct a list of directories from where plugins can be loaded""" """Construct a list of directories from where plugins can be loaded"""
# Builtin plugins are *always* loaded
dirs = ['plugin.builtin', ] dirs = ['plugin.builtin', ]
if settings.PLUGINS_ENABLED:
# Any 'external' plugins are only loaded if PLUGINS_ENABLED is set to True
if settings.TESTING or settings.DEBUG: if settings.TESTING or settings.DEBUG:
# If in TEST or DEBUG mode, load plugins from the 'samples' directory # If in TEST or DEBUG mode, load plugins from the 'samples' directory
dirs.append('plugin.samples') dirs.append('plugin.samples')
@@ -263,9 +262,6 @@ class PluginsRegistry:
def collect_plugins(self): def collect_plugins(self):
"""Collect plugins from all possible ways of loading. Returned as list.""" """Collect plugins from all possible ways of loading. Returned as list."""
if not settings.PLUGINS_ENABLED:
# Plugins not enabled, do nothing
return # pragma: no cover
collected_plugins = [] collected_plugins = []
@@ -293,6 +289,9 @@ class PluginsRegistry:
if modules: if modules:
[collected_plugins.append(item) for item in modules] [collected_plugins.append(item) for item in modules]
# From this point any plugins are considered "external" and only loaded if plugins are explicitly enabled
if settings.PLUGINS_ENABLED:
# Check if not running in testing mode and apps should be loaded from hooks # Check if not running in testing mode and apps should be loaded from hooks
if (not settings.PLUGIN_TESTING) or (settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP): if (not settings.PLUGIN_TESTING) or (settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP):
# Collect plugins from setup entry points # Collect plugins from setup entry points
@@ -335,7 +334,7 @@ class PluginsRegistry:
# endregion # endregion
# region registry functions # region registry functions
def with_mixin(self, mixin: str, active=None): def with_mixin(self, mixin: str, active=None, builtin=None):
"""Returns reference to all plugins that have a specified mixin enabled.""" """Returns reference to all plugins that have a specified mixin enabled."""
result = [] result = []
@@ -343,10 +342,13 @@ class PluginsRegistry:
if plugin.mixin_enabled(mixin): if plugin.mixin_enabled(mixin):
if active is not None: if active is not None:
# Filter by 'enabled' status # Filter by 'active' status of plugin
config = plugin.plugin_config() if active != plugin.is_active():
continue
if config.active != active: if builtin is not None:
# Filter by 'builtin' status of plugin
if builtin != plugin.is_builtin:
continue continue
result.append(plugin) result.append(plugin)
@@ -403,8 +405,14 @@ class PluginsRegistry:
# Append reference to plugin # Append reference to plugin
plg.db = plg_db plg.db = plg_db
# Always activate if testing # Check if this is a 'builtin' plugin
if settings.PLUGIN_TESTING or (plg_db and plg_db.active): builtin = plg.check_is_builtin()
# Determine if this plugin should be loaded:
# - If PLUGIN_TESTING is enabled
# - If this is a 'builtin' plugin
# - If this plugin has been explicitly enabled by the user
if settings.PLUGIN_TESTING or builtin or (plg_db and plg_db.active):
# Check if the plugin was blocked -> threw an error; option1: package, option2: file-based # Check if the plugin was blocked -> threw an error; option1: package, option2: file-based
if disabled and ((plg.__name__ == disabled) or (plg.__module__ == disabled)): if disabled and ((plg.__name__ == disabled) or (plg.__module__ == disabled)):
safe_reference(plugin=plg, key=plg_key, active=False) safe_reference(plugin=plg, key=plg_key, active=False)
@@ -498,10 +506,9 @@ class PluginsRegistry:
for _key, plugin in plugins: for _key, plugin in plugins:
if plugin.mixin_enabled('schedule'): if plugin.mixin_enabled('schedule'):
config = plugin.plugin_config()
if plugin.is_active():
# Only active tasks for plugins which are enabled # Only active tasks for plugins which are enabled
if config and config.active:
plugin.register_tasks() plugin.register_tasks()
task_keys += plugin.get_task_names() task_keys += plugin.get_task_names()
@@ -1,7 +1,7 @@
"""Unit tests for action plugins.""" """Unit tests for action plugins."""
from InvenTree.helpers import InvenTreeTestCase from InvenTree.helpers import InvenTreeTestCase
from plugin.builtin.action.simpleactionplugin import SimpleActionPlugin from plugin.samples.integration.simpleactionplugin import SimpleActionPlugin
class SimpleActionPluginTests(InvenTreeTestCase): class SimpleActionPluginTests(InvenTreeTestCase):
+23 -7
View File
@@ -28,25 +28,38 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
url = reverse('api-plugin-install') url = reverse('api-plugin-install')
# valid - Pypi # valid - Pypi
data = self.post(url, { data = self.post(
url,
{
'confirm': True, 'confirm': True,
'packagename': self.PKG_NAME 'packagename': self.PKG_NAME
}, expected_code=201).data },
expected_code=201,
).data
self.assertEqual(data['success'], True) self.assertEqual(data['success'], True)
# valid - github url # valid - github url
data = self.post(url, { data = self.post(
url,
{
'confirm': True, 'confirm': True,
'url': self.PKG_URL 'url': self.PKG_URL
}, expected_code=201).data },
expected_code=201,
).data
self.assertEqual(data['success'], True) self.assertEqual(data['success'], True)
# valid - github url and packagename # valid - github url and packagename
data = self.post(url, { data = self.post(
url,
{
'confirm': True, 'confirm': True,
'url': self.PKG_URL, 'url': self.PKG_URL,
'packagename': 'minimal', 'packagename': 'minimal',
}, expected_code=201).data },
expected_code=201,
).data
self.assertEqual(data['success'], True) self.assertEqual(data['success'], True)
# invalid tries # invalid tries
@@ -57,17 +70,20 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
data = self.post(url, { data = self.post(url, {
'confirm': True, 'confirm': True,
}, expected_code=400).data }, expected_code=400).data
self.assertEqual(data['url'][0].title().upper(), self.MSG_NO_PKG.upper()) self.assertEqual(data['url'][0].title().upper(), self.MSG_NO_PKG.upper())
self.assertEqual(data['packagename'][0].title().upper(), self.MSG_NO_PKG.upper()) self.assertEqual(data['packagename'][0].title().upper(), self.MSG_NO_PKG.upper())
# not confirmed # not confirmed
self.post(url, { self.post(url, {
'packagename': self.PKG_NAME 'packagename': self.PKG_NAME
}, expected_code=400).data }, expected_code=400)
data = self.post(url, { data = self.post(url, {
'packagename': self.PKG_NAME, 'packagename': self.PKG_NAME,
'confirm': False, 'confirm': False,
}, expected_code=400).data }, expected_code=400).data
self.assertEqual(data['confirm'][0].title().upper(), 'Installation not confirmed'.upper()) self.assertEqual(data['confirm'][0].title().upper(), 'Installation not confirmed'.upper())
def test_admin_action(self): def test_admin_action(self):
+1
View File
@@ -305,6 +305,7 @@ class TestReportTest(ReportTest):
InvenTreeSetting.set_setting('REPORT_ATTACH_TEST_REPORT', True, None) InvenTreeSetting.set_setting('REPORT_ATTACH_TEST_REPORT', True, None)
response = self.get(url, {'item': item.pk}, expected_code=200) response = self.get(url, {'item': item.pk}, expected_code=200)
headers = response.headers headers = response.headers
self.assertEqual(headers['Content-Type'], 'application/pdf') self.assertEqual(headers['Content-Type'], 'application/pdf')
+42 -12
View File
@@ -53,12 +53,21 @@
{% else %} {% else %}
<li><a class='dropdown-item' href='#' id='barcode-link'><span class='fas fa-link'></span> {% trans "Link Barcode" %}</a></li> <li><a class='dropdown-item' href='#' id='barcode-link'><span class='fas fa-link'></span> {% trans "Link Barcode" %}</a></li>
{% endif %} {% endif %}
{% if labels_enabled %} <li><a class='dropdown-item' href='#' id='barcode-scan-in-items' title='{% trans "Scan stock items into this location" %}'><span class='fas fa-boxes'></span> {% trans "Scan In Stock Items" %}</a></li>
<li><a class='dropdown-item' href='#' id='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</a></li> <li><a class='dropdown-item' href='#' id='barcode-scan-in-containers' title='{% trans "Scan stock container into this location" %}'><span class='fas fa-sitemap'></span> {% trans "Scan In Container" %}</a></li>
{% endif %}
<li><a class='dropdown-item' href='#' id='barcode-check-in'><span class='fas fa-arrow-right'></span> {% trans "Check-in Items" %}</a></li>
</ul> </ul>
</div> </div>
<!-- Printing action -->
{% if labels_enabled %}
<div class='btn-group' role='group'>
<button id='printing-options' title='{% trans "Printing actions" %}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'>
<span class='fas fa-print'></span> <span class='caret'></span>
</button>
<ul class='dropdown-menu'>
<li><a class='dropdown-item' href='#' id='print-label'><span class='fas fa-print'></span> {% trans "Print Label" %}</a>
</ul>
</div>
{% endif %}
<!-- Check permissions and owner --> <!-- Check permissions and owner -->
{% if user_owns_location %} {% if user_owns_location %}
{% if roles.stock.change %} {% if roles.stock.change %}
@@ -96,11 +105,6 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if user_owns_location and roles.stock_location.add %}
<button class='btn btn-success' id='location-create' type='button' title='{% trans "Create new stock location" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Location" %}
</button>
{% endif %}
{% endblock %} {% endblock %}
{% block details_left %} {% block details_left %}
@@ -203,7 +207,17 @@
<div class='panel panel-hidden' id='panel-sublocations'> <div class='panel panel-hidden' id='panel-sublocations'>
<div class='panel-heading'> <div class='panel-heading'>
<div class='d-flex flex-wrap'>
<h4>{% trans "Sublocations" %}</h4> <h4>{% trans "Sublocations" %}</h4>
{% include "spacer.html" %}
<div class='btn-group' role='group'>
{% if user_owns_location and roles.stock_location.add %}
<button class='btn btn-success' id='location-create' type='button' title='{% trans "Create new stock location" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Location" %}
</button>
{% endif %}
</div>
</div>
</div> </div>
<div class='panel-content'> <div class='panel-content'>
<div id='sublocation-button-toolbar'> <div id='sublocation-button-toolbar'>
@@ -284,13 +298,29 @@
{% endif %} {% endif %}
{% if location %} {% if location %}
$("#barcode-check-in").click(function() { $("#barcode-scan-in-items").click(function() {
barcodeCheckIn({{ location.id }}); barcodeCheckInStockItems({{ location.id }});
});
$('#barcode-scan-in-containers').click(function() {
barcodeCheckInStockLocations({{ location.id }},
{
onSuccess: function() {
showMessage(
'{% trans "Scanned stock container into this location" %}',
{
style: 'success',
}
);
$('#sublocation-table').bootstrapTable('refresh');
}
}
);
}); });
{% endif %} {% endif %}
$('#location-create').click(function () { $('#location-create').click(function () {
createStockLocation({ createStockLocation({
{% if location %} {% if location %}
parent: {{ location.pk }}, parent: {{ location.pk }},
@@ -13,6 +13,7 @@
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<tbody> <tbody>
{% include "InvenTree/settings/setting.html" with key="BARCODE_ENABLE" icon="fa-qrcode" %} {% include "InvenTree/settings/setting.html" with key="BARCODE_ENABLE" icon="fa-qrcode" %}
{% include "InvenTree/settings/setting.html" with key="BARCODE_INPUT_DELAY" icon="fa-hourglass-half" %}
{% include "InvenTree/settings/setting.html" with key="BARCODE_WEBCAM_SUPPORT" icon="fa-video" %} {% include "InvenTree/settings/setting.html" with key="BARCODE_WEBCAM_SUPPORT" icon="fa-video" %}
</tbody> </tbody>
</table> </table>
@@ -31,6 +31,8 @@
</table> </table>
</div> </div>
{% plugins_enabled as plug %}
<div class='panel-heading'> <div class='panel-heading'>
<div class='d-flex flex-wrap'> <div class='d-flex flex-wrap'>
<h4>{% trans "Plugins" %}</h4> <h4>{% trans "Plugins" %}</h4>
@@ -38,78 +40,46 @@
<div class='btn-group' role='group'> <div class='btn-group' role='group'>
{% url 'admin:plugin_pluginconfig_changelist' as url %} {% url 'admin:plugin_pluginconfig_changelist' as url %}
{% include "admin_button.html" with url=url %} {% include "admin_button.html" with url=url %}
{% if plug %}
<button class="btn btn-success" id="install-plugin" title="{% trans 'Install Plugin' %}"><span class='fas fa-plus-circle'></span> {% trans "Install Plugin" %}</button> <button class="btn btn-success" id="install-plugin" title="{% trans 'Install Plugin' %}"><span class='fas fa-plus-circle'></span> {% trans "Install Plugin" %}</button>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
{% if not plug %}
<div class='alert alert-warning alert-block'>
{% trans "External plugins are not enabled for this InvenTree installation" %}<br>
</div>
{% endif %}
<div class='table-responsive'> <div class='table-responsive'>
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<thead> <thead>
<tr> <tr>
<th>{% trans "Admin" %}</th>
<th>{% trans "Name" %}</th> <th>{% trans "Name" %}</th>
<th>{% trans "Key" %}</th>
<th>{% trans "Author" %}</th> <th>{% trans "Author" %}</th>
<th>{% trans "Date" %}</th> <th>{% trans "Date" %}</th>
<th>{% trans "Version" %}</th> <th>{% trans "Version" %}</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% plugin_list as pl_list %} {% plugin_list as pl_list %}
{% if pl_list %}
<tr><td colspan="6"><h6>{% trans 'Active plugins' %}</h6></td></tr>
{% for plugin_key, plugin in pl_list.items %} {% for plugin_key, plugin in pl_list.items %}
{% mixin_enabled plugin 'urls' as urls %} {% include "InvenTree/settings/plugin_details.html" with plugin=plugin plugin_key=plugin_key %}
{% mixin_enabled plugin 'settings' as settings %}
<tr>
<td>
{% if user.is_staff and perms.plugin.change_pluginconfig %}
{% url 'admin:plugin_pluginconfig_change' plugin.pk as url %}
{% include "admin_button.html" with url=url %}
{% endif %}
</td>
<td>{{ plugin.human_name }}<span class="text-muted"> - {{plugin_key}}</span>
{% define plugin.registered_mixins as mixin_list %}
{% if plugin.is_sample %}
<a class='sidebar-selector' id='select-plugin-{{plugin_key}}' data-bs-parent="#sidebar">
<span class='badge bg-info rounded-pill badge-right'>{% trans "Sample" %}</span>
</a>
{% endif %}
{% if mixin_list %}
{% for mixin in mixin_list %}
<a class='sidebar-selector' id='select-plugin-{{plugin_key}}' data-bs-parent="#sidebar">
<span class='badge bg-dark badge-right rounded-pill'>{{ mixin.human_name }}</span>
</a>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if plugin.website %}
<a href="{{ plugin.website }}"><span class="fas fa-globe"></span></a>
{% endif %}
</td>
<td>{{ plugin.author }}</td>
<td>{% render_date plugin.pub_date %}</td>
<td>{% if plugin.version %}{{ plugin.version }}{% endif %}</td>
</tr>
{% endfor %}
{% inactive_plugin_list as in_pl_list %} {% inactive_plugin_list as in_pl_list %}
{% if in_pl_list %} {% if in_pl_list %}
<tr><td colspan="5"></td></tr> <tr><td colspan="6"><h6>{% trans 'Inactive plugins' %}</h6></td></tr>
<tr><td colspan="5"><h6>{% trans 'Inactive plugins' %}</h6></td></tr>
{% for plugin_key, plugin in in_pl_list.items %} {% for plugin_key, plugin in in_pl_list.items %}
<tr> {% include "InvenTree/settings/plugin_details.html" with plugin=plugin plugin_key=plugin_key %}
<td>
{% if user.is_staff and perms.plugin.change_pluginconfig %}
{% url 'admin:plugin_pluginconfig_change' plugin.pk as url %}
{% include "admin_button.html" with url=url %}
{% endif %}
</td>
<td>{{plugin.name}}<span class="text-muted"> - {{plugin.key}}</span></td>
<td colspan="3"></td>
</tr>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</tbody> </tbody>
@@ -0,0 +1,75 @@
{% load inventree_extras %}
{% load i18n %}
<tr>
<td>
{% if plugin.is_active %}
<span class='fas fa-check-circle icon-green'></span>
{% else %}
<span class='fas fa-times-circle icon-red'></span>
{% endif %}
{% if plugin.human_name %}
{{ plugin.human_name }}
{% elif plugin.title %}
{{ plugin.title }}
{% elif plugin.name %}
{{ plugin.name }}
{% endif %}
{% define plugin.registered_mixins as mixin_list %}
{% if mixin_list %}
{% for mixin in mixin_list %}
<a class='sidebar-selector' id='select-plugin-{{plugin_key}}' data-bs-parent="#sidebar">
<span class='badge bg-dark badge-right rounded-pill'>{{ mixin.human_name }}</span>
</a>
{% endfor %}
{% endif %}
{% if plugin.is_builtin %}
<a class='sidebar-selector' id='select-plugin-{{ plugin_key }}' data-bs-parent='#sidebar'>
<span class='badge bg-success rounded-pill badge-right'>{% trans "Builtin" %}</span>
</a>
{% endif %}
{% if plugin.is_sample %}
<a class='sidebar-selector' id='select-plugin-{{plugin_key}}' data-bs-parent="#sidebar">
<span class='badge bg-info rounded-pill badge-right'>{% trans "Sample" %}</span>
</a>
{% endif %}
{% if plugin.website %}
<a href="{{ plugin.website }}"><span class="fas fa-globe"></span></a>
{% endif %}
</td>
<td>{{ plugin_key }}</td>
{% trans "Unvailable" as no_info %}
<td>
{% if plugin.author %}
{{ plugin.author }}
{% else %}
<em>{{ no_info }}</em>
{% endif %}
</td>
<td>
{% if plugin.pub_date %}
{% render_date plugin.pub_date %}
{% else %}
<em>{{ no_info }}</em>
{% endif %}
</td>
<td>
{% if plugin.version %}
{{ plugin.version }}
{% else %}
<em>{{ no_info }}</em>
{% endif %}
</td>
<td>
{% if user.is_staff and perms.plugin.change_pluginconfig %}
{% url 'admin:plugin_pluginconfig_change' plugin.pk as url %}
{% include "admin_button.html" with url=url %}
{% endif %}
</td>
</tr>
@@ -23,16 +23,16 @@
<td>{% trans "Name" %}</td> <td>{% trans "Name" %}</td>
<td>{{ plugin.human_name }}{% include "clip.html" %}</td> <td>{{ plugin.human_name }}{% include "clip.html" %}</td>
</tr> </tr>
<tr>
<td><span class='fas fa-user'></span></span></td>
<td>{% trans "Author" %}</td>
<td>{{ plugin.author }}{% include "clip.html" %}</td>
</tr>
<tr> <tr>
<td></td> <td></td>
<td>{% trans "Description" %}</td> <td>{% trans "Description" %}</td>
<td>{{ plugin.description }}{% include "clip.html" %}</td> <td>{{ plugin.description }}{% include "clip.html" %}</td>
</tr> </tr>
<tr>
<td><span class='fas fa-user'></span></span></td>
<td>{% trans "Author" %}</td>
<td>{{ plugin.author }}{% include "clip.html" %}</td>
</tr>
<tr> <tr>
<td><span class='fas fa-calendar-alt'></span></td> <td><span class='fas fa-calendar-alt'></span></td>
<td>{% trans "Date" %}</td> <td>{% trans "Date" %}</td>
@@ -94,7 +94,14 @@
<td>{% trans "Installation path" %}</td> <td>{% trans "Installation path" %}</td>
<td>{{ plugin.package_path }}</td> <td>{{ plugin.package_path }}</td>
</tr> </tr>
{% if plugin.is_package == False %} {% if plugin.is_package %}
{% elif plugin.is_builtin %}
<tr>
<td><span class='fas fa-check-circle icon-green'></span></td>
<td>{% trans "Builtin" %}</td>
<td>{% trans "This is a builtin plugin which cannot be disabled" %}</td>
</tr>
{% else %}
<tr> <tr>
<td><span class='fas fa-user'></span></td> <td><span class='fas fa-user'></span></td>
<td>{% trans "Commit Author" %}</td><td>{{ plugin.package.author }} - {{ plugin.package.mail }}{% include "clip.html" %}</td> <td>{% trans "Commit Author" %}</td><td>{{ plugin.package.author }} - {{ plugin.package.mail }}{% include "clip.html" %}</td>
@@ -42,8 +42,6 @@
{% include "InvenTree/settings/po.html" %} {% include "InvenTree/settings/po.html" %}
{% include "InvenTree/settings/so.html" %} {% include "InvenTree/settings/so.html" %}
{% plugins_enabled as plug %}
{% if plug %}
{% include "InvenTree/settings/plugin.html" %} {% include "InvenTree/settings/plugin.html" %}
{% plugin_list as pl_list %} {% plugin_list as pl_list %}
{% for plugin_key, plugin in pl_list.items %} {% for plugin_key, plugin in pl_list.items %}
@@ -51,7 +49,6 @@
{% include "InvenTree/settings/plugin_settings.html" %} {% include "InvenTree/settings/plugin_settings.html" %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endif %}
{% endif %} {% endif %}
@@ -51,10 +51,11 @@
{% trans "Sales Orders" as text %} {% trans "Sales Orders" as text %}
{% include "sidebar_item.html" with label='sales-order' text=text icon="fa-truck" %} {% include "sidebar_item.html" with label='sales-order' text=text icon="fa-truck" %}
{% plugins_enabled as plug %}
{% if plug %} {% trans "Plugin Settings" as text %}
{% include "sidebar_header.html" with text="Plugin Settings" %} {% include "sidebar_header.html" with text=text %}
{% include "sidebar_item.html" with label='plugin' text="Plugins" icon="fa-plug" %} {% trans "Plugins" as text %}
{% include "sidebar_item.html" with label='plugin' text=text icon="fa-plug" %}
{% plugin_list as pl_list %} {% plugin_list as pl_list %}
{% for plugin_key, plugin in pl_list.items %} {% for plugin_key, plugin in pl_list.items %}
@@ -62,6 +63,5 @@
{% include "sidebar_item.html" with label='plugin-'|add:plugin_key text=plugin.human_name %} {% include "sidebar_item.html" with label='plugin-'|add:plugin_key text=plugin.human_name %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endif %}
{% endif %} {% endif %}
+9 -1
View File
@@ -1,4 +1,12 @@
{% load inventree_extras %}
{% load i18n %} {% load i18n %}
<button id='admin-button' title='{% trans "View in administration panel" %}' type='button' class='btn btn-primary admin-button' url='{{ url }}'>
{% inventree_customize 'hide_admin_link' as hidden %}
{% if not hidden and user.is_staff %}
<a href='{{ url }}'>
<button id='admin-button' href='{{ url }}' title='{% trans "View in administration panel" %}' type='button' class='btn btn-primary admin-button'>
<span class='fas fa-user-shield'></span> <span class='fas fa-user-shield'></span>
</button> </button>
</a>
{% endif %}
+93 -13
View File
@@ -14,7 +14,8 @@
*/ */
/* exported /* exported
barcodeCheckIn, barcodeCheckInStockItems,
barcodeCheckInStockLocations,
barcodeScanDialog, barcodeScanDialog,
linkBarcodeDialog, linkBarcodeDialog,
scanItemsIntoLocation, scanItemsIntoLocation,
@@ -22,12 +23,14 @@
onBarcodeScanClicked, onBarcodeScanClicked,
*/ */
function makeBarcodeInput(placeholderText='', hintText='') { var barcodeInputTimer = null;
/*
* Generate HTML for a barcode input
*/
placeholderText = placeholderText || '{% trans "Scan barcode data here using wedge scanner" %}'; /*
* Generate HTML for a barcode scan input
*/
function makeBarcodeInput(placeholderText='', hintText='') {
placeholderText = placeholderText || '{% trans "Scan barcode data here using barcode scanner" %}';
hintText = hintText || '{% trans "Enter barcode data" %}'; hintText = hintText || '{% trans "Enter barcode data" %}';
@@ -43,7 +46,7 @@ function makeBarcodeInput(placeholderText='', hintText='') {
<span class='fas fa-qrcode'></span> <span class='fas fa-qrcode'></span>
</span> </span>
<input id='barcode' class='textinput textInput form-control' type='text' name='barcode' placeholder='${placeholderText}'> <input id='barcode' class='textinput textInput form-control' type='text' name='barcode' placeholder='${placeholderText}'>
<button id='barcode_scan_btn' type='button' class='btn btn-secondary' onclick='onBarcodeScanClicked()' style='display: none;'> <button title='{% trans "Scan barcode using connected webcam" %}' id='barcode_scan_btn' type='button' class='btn btn-secondary' onclick='onBarcodeScanClicked()' style='display: none;'>
<span class='fas fa-camera'></span> <span class='fas fa-camera'></span>
</button> </button>
</div> </div>
@@ -92,6 +95,9 @@ function onBarcodeScanCompleted(result, options) {
postBarcodeData(result.data, options); postBarcodeData(result.data, options);
} }
/*
* Construct a generic "notes" field for barcode scanning operations
*/
function makeNotesField(options={}) { function makeNotesField(options={}) {
var tooltip = options.tooltip || '{% trans "Enter optional notes for stock transfer" %}'; var tooltip = options.tooltip || '{% trans "Enter optional notes for stock transfer" %}';
@@ -199,6 +205,9 @@ function showBarcodeMessage(modal, message, style='danger') {
} }
/*
* Display an error message when the server indicates an error
*/
function showInvalidResponseError(modal, response, status) { function showInvalidResponseError(modal, response, status) {
showBarcodeMessage( showBarcodeMessage(
modal, modal,
@@ -207,6 +216,9 @@ function showInvalidResponseError(modal, response, status) {
} }
/*
* Enable (or disable) the barcode scanning input
*/
function enableBarcodeInput(modal, enabled=true) { function enableBarcodeInput(modal, enabled=true) {
var barcode = $(modal + ' #barcode'); var barcode = $(modal + ' #barcode');
@@ -218,6 +230,10 @@ function enableBarcodeInput(modal, enabled=true) {
barcode.focus(); barcode.focus();
} }
/*
* Extract scanned data from the barcode input
*/
function getBarcodeData(modal) { function getBarcodeData(modal) {
modal = modal || '#modal-form'; modal = modal || '#modal-form';
@@ -233,10 +249,10 @@ function getBarcodeData(modal) {
} }
function barcodeDialog(title, options={}) {
/* /*
* Handle a barcode display dialog. * Handle a barcode display dialog.
*/ */
function barcodeDialog(title, options={}) {
var modal = '#modal-form'; var modal = '#modal-form';
@@ -244,7 +260,6 @@ function barcodeDialog(title, options={}) {
var barcode = getBarcodeData(modal); var barcode = getBarcodeData(modal);
if (barcode && barcode.length > 0) { if (barcode && barcode.length > 0) {
postBarcodeData(barcode, options); postBarcodeData(barcode, options);
} }
} }
@@ -264,7 +279,15 @@ function barcodeDialog(title, options={}) {
event.preventDefault(); event.preventDefault();
if (event.which == 10 || event.which == 13) { if (event.which == 10 || event.which == 13) {
clearTimeout(barcodeInputTimer);
sendBarcode(); sendBarcode();
} else {
// Start a timer to automatically send barcode after input is complete
clearTimeout(barcodeInputTimer);
barcodeInputTimer = setTimeout(function() {
sendBarcode();
}, global_settings.BARCODE_INPUT_DELAY);
} }
}); });
@@ -305,9 +328,11 @@ function barcodeDialog(title, options={}) {
modalShowSubmitButton(modal, false); modalShowSubmitButton(modal, false);
} }
var details = options.details || '{% trans "Scan barcode data" %}';
var content = ''; var content = '';
content += `<div class='alert alert-info alert-block'>{% trans "Scan barcode data below" %}</div>`; content += `<div class='alert alert-info alert-block'>${details}</div>`;
content += `<div id='barcode-error-message'></div>`; content += `<div id='barcode-error-message'></div>`;
content += `<form class='js-modal-form' method='post'>`; content += `<form class='js-modal-form' method='post'>`;
@@ -431,7 +456,7 @@ function unlinkBarcode(data, options={}) {
/* /*
* Display dialog to check multiple stock items in to a stock location. * Display dialog to check multiple stock items in to a stock location.
*/ */
function barcodeCheckIn(location_id, options={}) { function barcodeCheckInStockItems(location_id, options={}) {
var modal = '#modal-form'; var modal = '#modal-form';
@@ -486,6 +511,7 @@ function barcodeCheckIn(location_id, options={}) {
$(modal + ' #barcode').focus(); $(modal + ' #barcode').focus();
// Callback to remove the scanned item from the table
$(modal + ' .button-item-remove').unbind('click').on('mouseup', function() { $(modal + ' .button-item-remove').unbind('click').on('mouseup', function() {
var pk = $(this).attr('pk'); var pk = $(this).attr('pk');
@@ -514,8 +540,9 @@ function barcodeCheckIn(location_id, options={}) {
var extra = makeNotesField(); var extra = makeNotesField();
barcodeDialog( barcodeDialog(
'{% trans "Check Stock Items into Location" %}', '{% trans "Scan Stock Items Into Location" %}',
{ {
details: '{% trans "Scan stock item barcode to check in to this location" %}',
headerContent: table, headerContent: table,
preShow: function() { preShow: function() {
modalSetSubmitText(modal, '{% trans "Check In" %}'); modalSetSubmitText(modal, '{% trans "Check In" %}');
@@ -609,7 +636,7 @@ function barcodeCheckIn(location_id, options={}) {
); );
} else { } else {
// Barcode does not match a stock item // Barcode does not match a stock item
showBarcodeMessage(modal, '{% trans "Barcode does not match Stock Item" %}', 'warning'); showBarcodeMessage(modal, '{% trans "Barcode does not match valid stock item" %}', 'warning');
} }
}, },
} }
@@ -617,6 +644,59 @@ function barcodeCheckIn(location_id, options={}) {
} }
/*
* Display dialog to scan stock locations into the current location
*/
function barcodeCheckInStockLocations(location_id, options={}) {
var modal = '#modal-form';
var header = '';
barcodeDialog(
'{% trans "Scan Stock Container Into Location" %}',
{
details: '{% trans "Scan stock container barcode to check in to this location" %}',
headerContent: header,
preShow: function() {
modalEnable(modal, false);
},
onShow: function() {
// TODO
},
onScan: function(response) {
if ('stocklocation' in response) {
var pk = response.stocklocation.pk;
var url = `/api/stock/location/${pk}/`;
// Move the scanned location into *this* location
inventreePut(
url,
{
parent: location_id,
},
{
method: 'PATCH',
success: function(response) {
$(modal).modal('hide');
handleFormSuccess(response, options);
},
error: function(xhr) {
$(modal).modal('hide');
showApiError(xhr, url);
},
}
);
} else {
// Barcode does not match a valid stock location
showBarcodeMessage(modal, '{% trans "Barcode does not match valid stock location" %}', 'warning');
}
}
}
);
}
/* /*
* Display dialog to check a single stock item into a stock location * Display dialog to check a single stock item into a stock location
*/ */
@@ -2473,9 +2473,6 @@ function loadPurchaseOrderExtraLineTable(table, options={}) {
setupFilterList('purchaseorderextraline', $(table), filter_target); setupFilterList('purchaseorderextraline', $(table), filter_target);
// Is the order pending?
var pending = options.status == {{ SalesOrderStatus.PENDING }};
// Table columns to display // Table columns to display
var columns = [ var columns = [
{ {
@@ -2555,7 +2552,6 @@ function loadPurchaseOrderExtraLineTable(table, options={}) {
title: '{% trans "Notes" %}', title: '{% trans "Notes" %}',
}); });
if (pending) {
columns.push({ columns.push({
field: 'buttons', field: 'buttons',
switchable: false, switchable: false,
@@ -2574,7 +2570,6 @@ function loadPurchaseOrderExtraLineTable(table, options={}) {
return html; return html;
} }
}); });
}
function reloadTable() { function reloadTable() {
$(table).bootstrapTable('refresh'); $(table).bootstrapTable('refresh');
@@ -4320,9 +4315,6 @@ function loadSalesOrderExtraLineTable(table, options={}) {
setupFilterList('salesorderextraline', $(table), filter_target); setupFilterList('salesorderextraline', $(table), filter_target);
// Is the order pending?
var pending = options.status == {{ SalesOrderStatus.PENDING }};
// Table columns to display // Table columns to display
var columns = [ var columns = [
{ {
@@ -4402,7 +4394,6 @@ function loadSalesOrderExtraLineTable(table, options={}) {
title: '{% trans "Notes" %}', title: '{% trans "Notes" %}',
}); });
if (pending) {
columns.push({ columns.push({
field: 'buttons', field: 'buttons',
switchable: false, switchable: false,
@@ -4421,7 +4412,6 @@ function loadSalesOrderExtraLineTable(table, options={}) {
return html; return html;
} }
}); });
}
function reloadTable() { function reloadTable() {
$(table).bootstrapTable('refresh'); $(table).bootstrapTable('refresh');
+20 -18
View File
@@ -1878,15 +1878,16 @@ function loadPartCategoryTable(table, options) {
}, },
event: () => { event: () => {
inventreeSave('category-tree-view', 0); inventreeSave('category-tree-view', 0);
table.bootstrapTable(
'refreshOptions', // Adjust table options
{ options.treeEnable = false;
treeEnable: false, options.serverSort = false;
serverSort: true, options.search = true;
search: true, options.pagination = true;
pagination: true,
} // Destroy and re-create the table
); table.bootstrapTable('destroy');
loadPartCategoryTable(table, options);
} }
}, },
{ {
@@ -1897,15 +1898,16 @@ function loadPartCategoryTable(table, options) {
}, },
event: () => { event: () => {
inventreeSave('category-tree-view', 1); inventreeSave('category-tree-view', 1);
table.bootstrapTable(
'refreshOptions', // Adjust table options
{ options.treeEnable = true;
treeEnable: true, options.serverSort = false;
serverSort: false, options.search = false;
search: false, options.pagination = false;
pagination: false,
} // Destroy and re-create the table
); table.bootstrapTable('destroy');
loadPartCategoryTable(table, options);
} }
} }
] : [], ] : [],
+20 -18
View File
@@ -2351,15 +2351,16 @@ function loadStockLocationTable(table, options) {
}, },
event: () => { event: () => {
inventreeSave('location-tree-view', 0); inventreeSave('location-tree-view', 0);
table.bootstrapTable(
'refreshOptions', // Adjust table options
{ options.treeEnable = false;
treeEnable: false, options.serverSort = true;
serverSort: true, options.search = true;
search: true, options.pagination = true;
pagination: true,
} // Destroy and re-create the table
); table.bootstrapTable('destroy');
loadStockLocationTable(table, options);
} }
}, },
{ {
@@ -2370,15 +2371,16 @@ function loadStockLocationTable(table, options) {
}, },
event: () => { event: () => {
inventreeSave('location-tree-view', 1); inventreeSave('location-tree-view', 1);
table.bootstrapTable(
'refreshOptions', // Adjust table options
{ options.treeEnable = true;
treeEnable: true, options.serverSort = false;
serverSort: false, options.search = false;
search: false, options.pagination = false;
pagination: false,
} // Destroy and re-create the table
); table.bootstrapTable('destroy');
loadStockLocationTable(table, options);
} }
} }
] : [], ] : [],
+1 -1
View File
@@ -14,5 +14,5 @@ INVENTREE_DB_PORT=5432
INVENTREE_DB_USER=pguser INVENTREE_DB_USER=pguser
INVENTREE_DB_PASSWORD=pgpassword INVENTREE_DB_PASSWORD=pgpassword
# Enable plugins? # Enable custom plugins?
INVENTREE_PLUGINS_ENABLED=True INVENTREE_PLUGINS_ENABLED=True
+1 -1
View File
@@ -41,7 +41,7 @@ INVENTREE_DB_PORT=5432
#INVENTREE_CACHE_HOST=inventree-cache #INVENTREE_CACHE_HOST=inventree-cache
#INVENTREE_CACHE_PORT=6379 #INVENTREE_CACHE_PORT=6379
# Enable plugins? # Enable custom plugins?
INVENTREE_PLUGINS_ENABLED=False INVENTREE_PLUGINS_ENABLED=False
# Image tag that should be used # Image tag that should be used