From ea6bdd3ca6223aed07719d695709d915f6c1c077 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 27 May 2024 16:55:08 +1000 Subject: [PATCH 1/9] Updates for metadata information (#7343) * Updates for metadata information - Override 'label' values with 'verbose_name' values - Only if 'label' is *not* translated, but 'verbose_name' is - Allows the translated model fields name to be pushed through to the metadata framework * Remove unused import * Add unit testing for metadata lookup * Update serializer: allow 'category' to be blank * Bump API version * Fix for unit test --- .../InvenTree/InvenTree/api_version.py | 5 +- src/backend/InvenTree/InvenTree/metadata.py | 55 +++++++++++++++---- src/backend/InvenTree/part/serializers.py | 4 +- src/backend/InvenTree/part/test_api.py | 47 +++++++++++++++- 4 files changed, 96 insertions(+), 15 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 0ad025b8a7df..195b1631c3c8 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,12 +1,15 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 201 +INVENTREE_API_VERSION = 202 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v202 - 2024-05-27 : https://github.com/inventree/InvenTree/pull/7343 + - Adjust "required" attribute of Part.category field to be optional + v201 - 2024-05-21 : https://github.com/inventree/InvenTree/pull/7074 - Major refactor of the report template / report printing interface - This is a *breaking change* to the report template API diff --git a/src/backend/InvenTree/InvenTree/metadata.py b/src/backend/InvenTree/InvenTree/metadata.py index 242516f59b4b..36edbde6b892 100644 --- a/src/backend/InvenTree/InvenTree/metadata.py +++ b/src/backend/InvenTree/InvenTree/metadata.py @@ -115,6 +115,31 @@ def determine_metadata(self, request, view): return metadata + def override_value(self, field_name, field_value, model_value): + """Override a value on the serializer with a matching value for the model. + + This is used to override the serializer values with model values, + if (and *only* if) the model value should take precedence. + + The values are overridden under the following conditions: + - field_value is None + - model_value is callable, and field_value is not (this indicates that the model value is translated) + - model_value is not a string, and field_value is a string (this indicates that the model value is translated) + """ + if model_value and not field_value: + return model_value + + if field_value and not model_value: + return field_value + + if callable(model_value) and not callable(field_value): + return model_value + + if type(model_value) is not str and type(field_value) is str: + return model_value + + return field_value + def get_serializer_info(self, serializer): """Override get_serializer_info so that we can add 'default' values to any fields whose Meta.model specifies a default value.""" self.serializer = serializer @@ -134,7 +159,12 @@ def get_serializer_info(self, serializer): model_class = None # Attributes to copy extra attributes from the model to the field (if they don't exist) - extra_attributes = ['help_text', 'max_length'] + # Note that the attributes may be named differently on the underlying model! + extra_attributes = { + 'help_text': 'help_text', + 'max_length': 'max_length', + 'label': 'verbose_name', + } try: model_class = serializer.Meta.model @@ -165,10 +195,12 @@ def get_serializer_info(self, serializer): elif name in model_default_values: serializer_info[name]['default'] = model_default_values[name] - for attr in extra_attributes: - if attr not in serializer_info[name]: - if hasattr(field, attr): - serializer_info[name][attr] = getattr(field, attr) + for field_key, model_key in extra_attributes.items(): + field_value = serializer_info[name].get(field_key, None) + model_value = getattr(field, model_key, None) + + if value := self.override_value(name, field_value, model_value): + serializer_info[name][field_key] = value # Iterate through relations for name, relation in model_fields.relations.items(): @@ -186,13 +218,12 @@ def get_serializer_info(self, serializer): relation.model_field.get_limit_choices_to() ) - for attr in extra_attributes: - if attr not in serializer_info[name] and hasattr( - relation.model_field, attr - ): - serializer_info[name][attr] = getattr( - relation.model_field, attr - ) + for field_key, model_key in extra_attributes.items(): + field_value = serializer_info[name].get(field_key, None) + model_value = getattr(relation.model_field, model_key, None) + + if value := self.override_value(name, field_value, model_value): + serializer_info[name][field_key] = value if name in model_default_values: serializer_info[name]['default'] = model_default_values[name] diff --git a/src/backend/InvenTree/part/serializers.py b/src/backend/InvenTree/part/serializers.py index e2182b163541..3df52b22cac2 100644 --- a/src/backend/InvenTree/part/serializers.py +++ b/src/backend/InvenTree/part/serializers.py @@ -851,7 +851,9 @@ def get_starred(self, part) -> bool: starred = serializers.SerializerMethodField() # PrimaryKeyRelated fields (Note: enforcing field type here results in much faster queries, somehow...) - category = serializers.PrimaryKeyRelatedField(queryset=PartCategory.objects.all()) + category = serializers.PrimaryKeyRelatedField( + queryset=PartCategory.objects.all(), required=False, allow_null=True + ) # Pricing fields pricing_min = InvenTree.serializers.InvenTreeMoneySerializer( diff --git a/src/backend/InvenTree/part/test_api.py b/src/backend/InvenTree/part/test_api.py index e4372f379b97..faaacfb0a5a9 100644 --- a/src/backend/InvenTree/part/test_api.py +++ b/src/backend/InvenTree/part/test_api.py @@ -541,13 +541,58 @@ def test_part(self): category = actions['category'] self.assertEqual(category['type'], 'related field') - self.assertTrue(category['required']) + self.assertFalse(category['required']) self.assertFalse(category['read_only']) self.assertEqual(category['label'], 'Category') self.assertEqual(category['model'], 'partcategory') self.assertEqual(category['api_url'], reverse('api-part-category-list')) self.assertEqual(category['help_text'], 'Part category') + def test_part_label_translation(self): + """Test that 'label' values are correctly translated.""" + response = self.options(reverse('api-part-list')) + + labels = { + 'IPN': 'IPN', + 'category': 'Category', + 'assembly': 'Assembly', + 'ordering': 'On Order', + 'stock_item_count': 'Stock Items', + } + + help_text = { + 'IPN': 'Internal Part Number', + 'active': 'Is this part active?', + 'barcode_hash': 'Unique hash of barcode data', + 'category': 'Part category', + } + + # Check basic return values + for field, value in labels.items(): + self.assertEqual(response.data['actions']['POST'][field]['label'], value) + + for field, value in help_text.items(): + self.assertEqual( + response.data['actions']['POST'][field]['help_text'], value + ) + + # Check again, with a different locale + response = self.options( + reverse('api-part-list'), headers={'Accept-Language': 'de'} + ) + + translated = { + 'IPN': 'IPN (Interne Produktnummer)', + 'category': 'Kategorie', + 'assembly': 'Baugruppe', + 'ordering': 'Bestellt', + 'stock_item_count': 'Lagerartikel', + } + + for field, value in translated.items(): + label = response.data['actions']['POST'][field]['label'] + self.assertEqual(label, value) + def test_category(self): """Test the PartCategory API OPTIONS endpoint.""" actions = self.getActions(reverse('api-part-category-list')) From bda237a13fb335c34c5afbb54630c66d82a64cd8 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 27 May 2024 17:07:57 +1000 Subject: [PATCH 2/9] Fix for 'restore' command (#7348) - Fix typo --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 7c6cebca608a..3ef8e47300bb 100644 --- a/tasks.py +++ b/tasks.py @@ -409,7 +409,7 @@ def restore( ignore_database=False, ): """Restore the database and media files.""" - base_cmd = '--no-input --uncompress -v 2' + base_cmd = '--noinput --uncompress -v 2' if path: base_cmd += f' -I {path}' From bdebf878c367b2fefd870d74c049c2522bac08e7 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 27 May 2024 09:49:05 +0200 Subject: [PATCH 3/9] Fixes for installer (#7344) * - move reqs file to contrib - detect previously used python version - safe extra requirements to INSTALLER_EXTRA * add missing fi * move site setting --- .github/workflows/docker.yaml | 2 +- .github/workflows/qc_checks.yaml | 8 ++--- .github/workflows/release.yaml | 2 +- .pre-commit-config.yaml | 4 +-- {.github => contrib/dev_reqs}/requirements.in | 0 .../dev_reqs}/requirements.txt | 2 +- contrib/packager.io/functions.sh | 36 ++++++++++++++++++- contrib/packager.io/postinstall.sh | 2 ++ 8 files changed, 46 insertions(+), 10 deletions(-) rename {.github => contrib/dev_reqs}/requirements.in (100%) rename {.github => contrib/dev_reqs}/requirements.txt (99%) diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 40437b413dd0..df945dc432d2 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -73,7 +73,7 @@ jobs: python-version: ${{ env.python_version }} - name: Version Check run: | - pip install --require-hashes -r .github/requirements.txt + pip install --require-hashes -r contrib/dev_reqs/requirements.txt python3 .github/scripts/version_check.py echo "git_commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index a6efc32d2b76..e1ed0a7a0aa1 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -104,7 +104,7 @@ jobs: uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # pin@v3.0.1 - name: Check Version run: | - pip install --require-hashes -r .github/requirements.txt + pip install --require-hashes -r contrib/dev_reqs/requirements.txt python3 .github/scripts/version_check.py mkdocs: @@ -122,7 +122,7 @@ jobs: python-version: ${{ env.python_version }} - name: Check Config run: | - pip install --require-hashes -r .github/requirements.txt + pip install --require-hashes -r contrib/dev_reqs/requirements.txt pip install --require-hashes -r docs/requirements.txt python docs/ci/check_mkdocs_config.py - name: Check Links @@ -168,7 +168,7 @@ jobs: - name: Download public schema if: needs.paths-filter.outputs.api == 'false' run: | - pip install --require-hashes -r .github/requirements.txt >/dev/null 2>&1 + pip install --require-hashes -r contrib/dev_reqs/requirements.txt >/dev/null 2>&1 version="$(python3 .github/scripts/version_check.py only_version 2>&1)" echo "Version: $version" url="https://raw.githubusercontent.com/inventree/schema/main/export/${version}/api.yaml" @@ -187,7 +187,7 @@ jobs: id: version if: github.ref == 'refs/heads/master' && needs.paths-filter.outputs.api == 'true' run: | - pip install --require-hashes -r .github/requirements.txt >/dev/null 2>&1 + pip install --require-hashes -r contrib/dev_reqs/requirements.txt >/dev/null 2>&1 version="$(python3 .github/scripts/version_check.py only_version 2>&1)" echo "Version: $version" echo "version=$version" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index bc63b1a71845..f93c99a1ec5e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,7 +18,7 @@ jobs: uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # pin@v4.1.6 - name: Version Check run: | - pip install --require-hashes -r .github/requirements.txt + pip install --require-hashes -r contrib/dev_reqs/requirements.txt python3 .github/scripts/version_check.py - name: Push to Stable Branch uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6cace6d1476c..6c99a6f6d591 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,8 +39,8 @@ repos: files: src/backend/requirements\.(in|txt)$ - id: pip-compile name: pip-compile requirements.txt - args: [.github/requirements.in, -o, .github/requirements.txt,--python-version=3.9, --no-strip-extras, --generate-hashes] - files: .github/requirements\.(in|txt)$ + args: [contrib/dev_reqs/requirements.in, -o, contrib/dev_reqs/requirements.txt,--python-version=3.9, --no-strip-extras, --generate-hashes] + files: contrib/dev_reqs/requirements\.(in|txt)$ - id: pip-compile name: pip-compile requirements.txt args: [docs/requirements.in, -o, docs/requirements.txt,--python-version=3.9, --no-strip-extras, --generate-hashes] diff --git a/.github/requirements.in b/contrib/dev_reqs/requirements.in similarity index 100% rename from .github/requirements.in rename to contrib/dev_reqs/requirements.in diff --git a/.github/requirements.txt b/contrib/dev_reqs/requirements.txt similarity index 99% rename from .github/requirements.txt rename to contrib/dev_reqs/requirements.txt index 23393a4a952e..c4099a325e7d 100644 --- a/.github/requirements.txt +++ b/contrib/dev_reqs/requirements.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile .github/requirements.in -o .github/requirements.txt --python-version=3.9 --no-strip-extras --generate-hashes +# uv pip compile contrib/dev_reqs/requirements.in -o contrib/dev_reqs/requirements.txt --python-version=3.9 --no-strip-extras --generate-hashes certifi==2024.2.2 \ --hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f \ --hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1 diff --git a/contrib/packager.io/functions.sh b/contrib/packager.io/functions.sh index 8f7f47cc66b3..bb236be77d1e 100755 --- a/contrib/packager.io/functions.sh +++ b/contrib/packager.io/functions.sh @@ -44,6 +44,18 @@ function detect_ip() { echo "IP address is ${INVENTREE_IP}" } +function detect_python() { + # Detect if there is already a python version installed in /opt/inventree/env/lib + if test -f "${APP_HOME}/env/bin/python"; then + echo "# Python environment already present" + # Extract earliest python version initialised from /opt/inventree/env/lib + SETUP_PYTHON=$(ls -1 ${APP_HOME}/env/bin/python* | sort | head -n 1) + echo "# Found earliest version: ${SETUP_PYTHON}" + else + echo "# No python environment found - using ${SETUP_PYTHON}" + fi +} + function get_env() { envname=$1 @@ -90,7 +102,7 @@ function detect_envs() { echo "# Using existing config file: ${INVENTREE_CONFIG_FILE}" # Install parser - pip install --require-hashes -r ${APP_HOME}/.github/requirements.txt -q + pip install --require-hashes -r ${APP_HOME}/contrib/dev_reqs/requirements.txt -q # Load config local CONF=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml) @@ -163,12 +175,20 @@ function create_initscripts() { sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && ${SETUP_PYTHON} -m venv env" sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && env/bin/pip install invoke wheel" + # Check INSTALLER_EXTRA exists and load it + if test -f "${APP_HOME}/INSTALLER_EXTRA"; then + echo "# Loading extra packages from INSTALLER_EXTRA" + source ${APP_HOME}/INSTALLER_EXTRA + fi + if [ -n "${SETUP_EXTRA_PIP}" ]; then echo "# Installing extra pip packages" if [ -n "${SETUP_DEBUG}" ]; then echo "# Extra pip packages: ${SETUP_EXTRA_PIP}" fi sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && env/bin/pip install ${SETUP_EXTRA_PIP}" + # Write extra packages to INSTALLER_EXTRA + echo "SETUP_EXTRA_PIP='${SETUP_EXTRA_PIP}'" >>${APP_HOME}/INSTALLER_EXTRA fi fi @@ -283,6 +303,20 @@ function set_env() { chown ${APP_USER}:${APP_GROUP} ${DATA_DIR} ${INVENTREE_CONFIG_FILE} } +function set_site() { + # Ensure IP is known + if [ -z "${INVENTREE_IP}" ]; then + echo "# No IP address found - skipping" + return + fi + + # Check if INVENTREE_SITE_URL in inventree config + if [ -z "$(inventree config:get INVENTREE_SITE_URL)" ]; then + echo "# Setting up InvenTree site URL" + inventree config:set INVENTREE_SITE_URL=http://${INVENTREE_IP} + fi +} + function final_message() { echo -e "####################################################################################" echo -e "This InvenTree install uses nginx, the settings for the webserver can be found in" diff --git a/contrib/packager.io/postinstall.sh b/contrib/packager.io/postinstall.sh index 03230b955a84..89c88833b100 100755 --- a/contrib/packager.io/postinstall.sh +++ b/contrib/packager.io/postinstall.sh @@ -33,6 +33,7 @@ detect_envs detect_docker detect_initcmd detect_ip +detect_python # create processes create_initscripts @@ -45,6 +46,7 @@ update_or_install if [ "${SETUP_CONF_LOADED}" = "true" ]; then set_env fi +set_site start_inventree # show info From 9047e325a939797766326814103c6161073f261e Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 27 May 2024 19:08:19 +1000 Subject: [PATCH 4/9] Bug fix for label printing with plugin on CUI (#7324) * Bug fix for label printing with plugin on CUI - Missed edge case in recent refactor * Fix typo * Support non-pk fields * fix a number of typos * js fixes --- src/backend/InvenTree/report/api.py | 6 +++--- .../templates/js/translated/forms.js | 14 ++++++++++---- .../templates/js/translated/label.js | 19 ++++++++++--------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/backend/InvenTree/report/api.py b/src/backend/InvenTree/report/api.py index d710359b5107..1782d95ed90d 100644 --- a/src/backend/InvenTree/report/api.py +++ b/src/backend/InvenTree/report/api.py @@ -150,7 +150,7 @@ def get_plugin_class(self, plugin_slug: str, raise_error=False): """Return the plugin class for the given plugin key.""" from plugin.models import PluginConfig - if plugin_slug is None: + if not plugin_slug: # Use the default label printing plugin plugin_slug = InvenTreeLabelPlugin.NAME.lower() @@ -196,10 +196,10 @@ def get_serializer(self, *args, **kwargs): # Plugin information provided? if self.request: - plugin_key = self.request.data.get('plugin', None) + plugin_key = self.request.data.get('plugin', '') # Legacy url based lookup if not plugin_key: - plugin_key = self.request.query_params.get('plugin', None) + plugin_key = self.request.query_params.get('plugin', '') plugin = self.get_plugin_class(plugin_key) plugin_serializer = self.get_plugin_serializer(plugin) diff --git a/src/backend/InvenTree/templates/js/translated/forms.js b/src/backend/InvenTree/templates/js/translated/forms.js index dd187dc82905..56ae5a2c3c41 100644 --- a/src/backend/InvenTree/templates/js/translated/forms.js +++ b/src/backend/InvenTree/templates/js/translated/forms.js @@ -2030,7 +2030,7 @@ function initializeRelatedField(field, fields, options={}) { // Each 'row' must have the 'id' attribute for (var idx = 0; idx < data.length; idx++) { - data[idx].id = data[idx].pk; + data[idx].id = data[idx][field.pk_field ?? 'pk']; } // Ref: https://select2.org/data-sources/formats @@ -2054,7 +2054,9 @@ function initializeRelatedField(field, fields, options={}) { data = item.element.instance; } - if (!data.pk) { + const pkField = field.pk_field ?? 'pk'; + + if (!data[pkField]) { return $(searching()); } @@ -2075,6 +2077,8 @@ function initializeRelatedField(field, fields, options={}) { // Or, use the raw 'item' data as a backup var data = item; + const pkField = field.pk_field ?? 'pk'; + if (item.element && item.element.instance) { data = item.element.instance; } @@ -2084,7 +2088,7 @@ function initializeRelatedField(field, fields, options={}) { field.onSelect(data, field, options); } - if (!data.pk) { + if (!data[pkField]) { return field.placeholder || ''; } @@ -2246,7 +2250,9 @@ function setRelatedFieldData(name, data, options={}) { var select = getFormFieldElement(name, options); - var option = new Option(name, data.pk, true, true); + const pkField = options?.fields[name]?.pk_field ?? 'pk'; + + var option = new Option(name, data[pkField], true, true); // Assign the JSON data to the 'instance' attribute, // so we can access and render it later diff --git a/src/backend/InvenTree/templates/js/translated/label.js b/src/backend/InvenTree/templates/js/translated/label.js index 0366f5b8b1ff..fff34aecf677 100644 --- a/src/backend/InvenTree/templates/js/translated/label.js +++ b/src/backend/InvenTree/templates/js/translated/label.js @@ -48,7 +48,7 @@ const defaultLabelTemplates = { */ function printLabels(options) { - let pluginId = -1; + let plugin_name = ''; if (!options.items || options.items.length == 0) { showAlertDialog( @@ -67,14 +67,13 @@ function printLabels(options) { items: item_string, }; - function getPrintingFields(plugin_id, callback) { - let url = '{% url "api-label-print" %}' + `?plugin=${plugin_id}`; + function getPrintingFields(plugin_slug, callback) { + + let url = '{% url "api-label-print" %}' + `?plugin=${plugin_slug}`; inventreeGet( url, - { - plugin: plugin_id, - }, + {}, { method: 'OPTIONS', success: function(response) { @@ -88,11 +87,11 @@ function printLabels(options) { // Callback when a particular label printing plugin is selected function onPluginSelected(value, name, field, formOptions) { - if (value == pluginId) { + if (value == plugin_name) { return; } - pluginId = value; + plugin_name = value; // Request new printing options for the selected plugin getPrintingFields(value, function(fields) { @@ -108,7 +107,9 @@ function printLabels(options) { const baseFields = { template: {}, - plugin: {}, + plugin: { + idField: 'key', + }, items: {} }; From b402836dc4e4a8c28e124f6a1db402c0f6fba3c9 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 27 May 2024 19:12:29 +1000 Subject: [PATCH 5/9] Label printing bug fixes (#7329) * Hide printing actions button for top level stock location * Fix typo * Add another check for null * Remove debug statement * Prevent display of multiple form-error notifications * Fix pickling for print offloading * Reset selected plugin * Exclude the entire 'context' object when pickling a printing job --- docs/docs/report/context_variables.md | 1 - .../InvenTree/plugin/base/label/mixins.py | 9 +++- src/backend/InvenTree/report/models.py | 1 - src/backend/InvenTree/stock/models.py | 2 +- .../components/buttons/PrintingActions.tsx | 53 ++++++++++--------- src/frontend/src/components/forms/ApiForm.tsx | 4 +- .../src/pages/stock/LocationDetail.tsx | 1 + 7 files changed, 41 insertions(+), 30 deletions(-) diff --git a/docs/docs/report/context_variables.md b/docs/docs/report/context_variables.md index 10180a05047b..e6fe9bb69845 100644 --- a/docs/docs/report/context_variables.md +++ b/docs/docs/report/context_variables.md @@ -16,7 +16,6 @@ In addition to the model-specific context variables, the following global contex | base_url | The base URL for the InvenTree instance | | date | Current date, represented as a Python datetime.date object | | datetime | Current datetime, represented as a Python datetime object | -| request | The Django request object associated with the printing process | | template | The report template instance which is being rendered against | | template_description | Description of the report template | | template_name | Name of the report template | diff --git a/src/backend/InvenTree/plugin/base/label/mixins.py b/src/backend/InvenTree/plugin/base/label/mixins.py index 68236ec29fa2..a60c1b0d7129 100644 --- a/src/backend/InvenTree/plugin/base/label/mixins.py +++ b/src/backend/InvenTree/plugin/base/label/mixins.py @@ -147,6 +147,9 @@ def print_labels( N = len(items) + if N <= 0: + raise ValidationError(_('No items provided to print')) + # Generate a label output for each provided item for item in items: context = label.get_context(item, request) @@ -177,9 +180,13 @@ def print_labels( self.print_label(**print_args) else: # Offload the print task to the background worker + # Exclude the 'pdf_file' object - cannot be pickled + print_args.pop('pdf_file', None) + + # Exclude the 'context' object - cannot be pickled + print_args.pop('context', None) - kwargs.pop('pdf_file', None) offload_task(plugin_label.print_label, self.plugin_slug(), **print_args) # Update the progress of the print job diff --git a/src/backend/InvenTree/report/models.py b/src/backend/InvenTree/report/models.py index 053e6cdaebee..05584b55884d 100644 --- a/src/backend/InvenTree/report/models.py +++ b/src/backend/InvenTree/report/models.py @@ -246,7 +246,6 @@ def base_context(self, request=None): 'base_url': get_base_url(request=request), 'date': InvenTree.helpers.current_date(), 'datetime': InvenTree.helpers.current_time(), - 'request': request, 'template': self, 'template_description': self.description, 'template_name': self.name, diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 17a4385a39a0..f1787fb34f3d 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -2158,7 +2158,7 @@ def testResultMap(self, **kwargs): def testResultList(self, **kwargs): """Return a list of test-result objects for this StockItem.""" - return self.testResultMap(**kwargs).values() + return list(self.testResultMap(**kwargs).values()) def requiredTestStatus(self): """Return the status of the tests required for this StockItem. diff --git a/src/frontend/src/components/buttons/PrintingActions.tsx b/src/frontend/src/components/buttons/PrintingActions.tsx index 81c5d8ddd69e..3f14e49eebab 100644 --- a/src/frontend/src/components/buttons/PrintingActions.tsx +++ b/src/frontend/src/components/buttons/PrintingActions.tsx @@ -15,11 +15,13 @@ import { ActionDropdown } from '../items/ActionDropdown'; export function PrintingActions({ items, + hidden, enableLabels, enableReports, modelType }: { items: number[]; + hidden?: boolean; enableLabels?: boolean; enableReports?: boolean; modelType?: ModelType; @@ -79,8 +81,6 @@ export function PrintingActions({ mixin: 'labels' }, onValueChange: (value: string, record?: any) => { - console.log('onValueChange:', value, record); - if (record?.key && record?.key != pluginKey) { setPluginKey(record.key); } @@ -100,6 +100,7 @@ export function PrintingActions({ }, successMessage: t`Label printing completed successfully`, onFormSuccess: (response: any) => { + setPluginKey(''); if (!response.complete) { // TODO: Periodically check for completion (requires server-side changes) notifications.show({ @@ -164,28 +165,30 @@ export function PrintingActions({ } return ( - <> - {reportModal.modal} - {labelModal.modal} - } - disabled={!enabled} - actions={[ - { - name: t`Print Labels`, - icon: , - onClick: () => labelModal.open(), - hidden: !enableLabels - }, - { - name: t`Print Reports`, - icon: , - onClick: () => reportModal.open(), - hidden: !enableReports - } - ]} - /> - + !hidden && ( + <> + {reportModal.modal} + {labelModal.modal} + } + disabled={!enabled} + actions={[ + { + name: t`Print Labels`, + icon: , + onClick: () => labelModal.open(), + hidden: !enableLabels + }, + { + name: t`Print Reports`, + icon: , + onClick: () => reportModal.open(), + hidden: !enableReports + } + ]} + /> + + ) ); } diff --git a/src/frontend/src/components/forms/ApiForm.tsx b/src/frontend/src/components/forms/ApiForm.tsx index 2317f3e8e01b..c9b660174eb7 100644 --- a/src/frontend/src/components/forms/ApiForm.tsx +++ b/src/frontend/src/components/forms/ApiForm.tsx @@ -144,10 +144,12 @@ export function OptionsApiForm({ if (error.response) { invalidResponse(error.response.status); } else { + notifications.hide('form-error'); notifications.show({ title: t`Form Error`, message: error.message, - color: 'red' + color: 'red', + id: 'form-error' }); } return false; diff --git a/src/frontend/src/pages/stock/LocationDetail.tsx b/src/frontend/src/pages/stock/LocationDetail.tsx index e3c83dea6d23..462faa7075c7 100644 --- a/src/frontend/src/pages/stock/LocationDetail.tsx +++ b/src/frontend/src/pages/stock/LocationDetail.tsx @@ -294,6 +294,7 @@ export default function Stock() {