Preserve validation details when rendering labels (#12874)

* Preserve label rendering validation errors

* Preserve label validation diagnostics before output deletion

Keep logging validation errors in both rendering wrappers before re-raising the original exception. LabelTemplate.print deletes failed DataOutput records, so the error log preserves the diagnostic after the result is removed.

Update the mocked tests to require logging while retaining the original validation details.

* Apply Ruff preview formatting to label rendering tests
This commit is contained in:
Petr Ledvina
2026-09-22 22:12:47 +10:00
committed by GitHub
parent 9daf4f8efe
commit 5edf72e084
2 changed files with 67 additions and 0 deletions
@@ -51,6 +51,9 @@ class LabelPrintingMixin:
""" """
try: try:
return label.render(instance, request=request, user=user) return label.render(instance, request=request, user=user)
except ValidationError:
log_error('render_to_pdf', plugin=self.slug)
raise
except Exception: except Exception:
log_error('render_to_pdf', plugin=self.slug) log_error('render_to_pdf', plugin=self.slug)
raise ValidationError(_('Error rendering label to PDF')) raise ValidationError(_('Error rendering label to PDF'))
@@ -68,6 +71,9 @@ class LabelPrintingMixin:
""" """
try: try:
return label.render_as_string(instance, request=request, user=user) return label.render_as_string(instance, request=request, user=user)
except ValidationError:
log_error('render_to_html', plugin=self.slug)
raise
except Exception: except Exception:
log_error('render_to_html', plugin=self.slug) log_error('render_to_html', plugin=self.slug)
raise ValidationError(_('Error rendering label to HTML')) raise ValidationError(_('Error rendering label to HTML'))
@@ -5,6 +5,8 @@ import os
from unittest import mock from unittest import mock
from django.apps import apps from django.apps import apps
from django.core.exceptions import ValidationError
from django.test import SimpleTestCase
from django.urls import reverse from django.urls import reverse
from pdfminer.high_level import extract_text from pdfminer.high_level import extract_text
@@ -24,6 +26,65 @@ from report.tests import PrintTestMixins
from stock.models import StockItem, StockLocation from stock.models import StockItem, StockLocation
class LabelRenderingTests(SimpleTestCase):
"""Test error handling in the label rendering wrappers."""
def setUp(self):
"""Create a label plugin without loading the plugin registry."""
class TestLabelPlugin(LabelPrintingMixin, InvenTreePlugin):
NAME = 'Test Label Printer'
self.plugin = TestLabelPlugin()
@mock.patch('plugin.base.label.mixins.log_error')
def test_validation_errors(self, log_error):
"""Log validation errors while preserving messages, codes, and parameters."""
for wrapper, renderer in [
('render_to_pdf', 'render'),
('render_to_html', 'render_as_string'),
]:
with self.subTest(wrapper=wrapper):
log_error.reset_mock()
error = ValidationError({
'serial': ValidationError(
'Missing serial number for %(part)s',
code='missing_serial',
params={'part': 'Test part'},
)
})
label = mock.Mock(spec=LabelTemplate)
getattr(label, renderer).side_effect = error
with self.assertRaises(ValidationError) as raised:
getattr(self.plugin, wrapper)(label, mock.sentinel.instance, None)
self.assertIs(raised.exception, error)
self.assertEqual(
raised.exception.message_dict,
{'serial': ['Missing serial number for Test part']},
)
log_error.assert_called_once_with(wrapper, plugin=self.plugin.slug)
@mock.patch('plugin.base.label.mixins.log_error')
def test_unexpected_errors(self, log_error):
"""Log unexpected errors and return the existing generic messages."""
for wrapper, renderer, message in [
('render_to_pdf', 'render', 'Error rendering label to PDF'),
('render_to_html', 'render_as_string', 'Error rendering label to HTML'),
]:
with self.subTest(wrapper=wrapper):
log_error.reset_mock()
label = mock.Mock(spec=LabelTemplate)
getattr(label, renderer).side_effect = RuntimeError('Rendering failed')
with self.assertRaises(ValidationError) as raised:
getattr(self.plugin, wrapper)(label, mock.sentinel.instance, None)
self.assertEqual(raised.exception.messages, [message])
log_error.assert_called_once_with(wrapper, plugin=self.plugin.slug)
class LabelMixinTests(PrintTestMixins, InvenTreeAPITestCase): class LabelMixinTests(PrintTestMixins, InvenTreeAPITestCase):
"""Test that the Label mixin operates correctly.""" """Test that the Label mixin operates correctly."""