diff --git a/src/backend/InvenTree/InvenTree/middleware.py b/src/backend/InvenTree/InvenTree/middleware.py index adf2e60193..df14ba3810 100644 --- a/src/backend/InvenTree/InvenTree/middleware.py +++ b/src/backend/InvenTree/InvenTree/middleware.py @@ -169,17 +169,12 @@ class InvenTreeExceptionProcessor(ExceptionProcessor): def process_exception(self, request, exception): """Check if kind is ignored before processing.""" - kind, info, data = sys.exc_info() + kind, _info, _data = sys.exc_info() # Check if the error is on the ignore list if kind in settings.IGNORED_ERRORS: return - import traceback - - from django.views.debug import ExceptionReporter - - from error_report.models import Error from error_report.settings import ERROR_DETAIL_SETTINGS # Error reporting is disabled @@ -194,15 +189,10 @@ class InvenTreeExceptionProcessor(ExceptionProcessor): if len(path) > 200: path = path[:195] + '...' - error = Error.objects.create( - kind=kind.__name__, - html=ExceptionReporter(request, kind, info, data).get_traceback_html(), - path=path, - info=info, - data='\n'.join(traceback.format_exception(kind, info, data)), - ) + # Pass off to the exception reporter + from InvenTree.exceptions import log_error - error.save() + log_error(path) class InvenTreeRequestCacheMiddleware(MiddlewareMixin): diff --git a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py index 2327c2854a..10c3c74783 100644 --- a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py +++ b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py @@ -38,7 +38,7 @@ def decimal(x, *args, **kwargs): return InvenTree.helpers.decimal2string(x) -@register.simple_tag(takes_context=True) +@register.simple_tag() def render_date(date_object): """Renders a date object as a string.""" if date_object is None: diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py index 06ffb2a992..3a006fb962 100644 --- a/src/backend/InvenTree/common/models.py +++ b/src/backend/InvenTree/common/models.py @@ -24,6 +24,7 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.humanize.templatetags.humanize import naturaltime from django.core.cache import cache from django.core.exceptions import ValidationError +from django.core.files.base import ContentFile from django.core.files.storage import default_storage from django.core.mail import EmailMultiAlternatives, get_connection from django.core.mail.utils import DNS_NAME @@ -2424,6 +2425,39 @@ class DataOutput(models.Model): errors = models.JSONField(blank=True, null=True) + def mark_complete(self, progress: int = 100, output: Optional[ContentFile] = None): + """Mark the data output generation process as complete. + + Arguments: + progress (int, optional): Progress percentage of the data output generation. Defaults to 100. + output (ContentFile, optional): The generated output file. Defaults to None. + """ + self.complete = True + self.progress = progress + self.output = output + self.save() + + def mark_failure( + self, error: Optional[str] = None, error_dict: Optional[dict] = None + ): + """Log an error message to the errors field. + + Arguments: + error (str, optional): Error message to log. Defaults to None. + error_dict (dict): Dictionary containing error messages. Defaults to None. + """ + self.complete = False + self.output = None + + if error_dict is not None: + self.errors = error_dict + elif error is not None: + self.errors = {'error': str(error)} + else: + self.errors = {'error': str(_('An error occurred'))} + + self.save() + # region Email class Priority(models.IntegerChoices): diff --git a/src/backend/InvenTree/data_exporter/mixins.py b/src/backend/InvenTree/data_exporter/mixins.py index af3ddc9fa1..24e6b7c684 100644 --- a/src/backend/InvenTree/data_exporter/mixins.py +++ b/src/backend/InvenTree/data_exporter/mixins.py @@ -391,10 +391,7 @@ class DataExportViewMixin: raise ValidationError(_('Error occurred during data export')) # Update the output object with the exported data - output.progress = 100 - output.complete = True - output.output = ContentFile(datafile, filename) - output.save() + output.mark_complete(output=ContentFile(datafile, filename)) def get(self, request, *args, **kwargs): """Override the GET method to determine export options.""" diff --git a/src/backend/InvenTree/plugin/base/label/mixins.py b/src/backend/InvenTree/plugin/base/label/mixins.py index e53f526ae7..9166a87dd8 100644 --- a/src/backend/InvenTree/plugin/base/label/mixins.py +++ b/src/backend/InvenTree/plugin/base/label/mixins.py @@ -188,14 +188,10 @@ class LabelPrintingMixin: output.progress += 1 output.save() + generated_file = self.get_generated_file(**print_args) + # Mark the output as complete - output.complete = True - output.progress = N - - # Add in the generated file (if applicable) - output.output = self.get_generated_file(**print_args) - - output.save() + output.mark_complete(progress=N, output=generated_file) def get_generated_file(self, **kwargs): """Return the generated file for download (or None, if this plugin does not generate a file output). diff --git a/src/backend/InvenTree/plugin/builtin/labels/inventree_machine.py b/src/backend/InvenTree/plugin/builtin/labels/inventree_machine.py index f0b4add8ce..2fece00a05 100644 --- a/src/backend/InvenTree/plugin/builtin/labels/inventree_machine.py +++ b/src/backend/InvenTree/plugin/builtin/labels/inventree_machine.py @@ -113,9 +113,7 @@ class InvenTreeLabelPlugin(LabelPrintingMixin, InvenTreePlugin): # Inform the user that the process has been offloaded to the printer if output: - output.output = None - output.complete = True - output.save() + output.mark_complete() class PrintingOptionsSerializer(serializers.Serializer): """Printing options serializer that adds a machine select and the machines options.""" diff --git a/src/backend/InvenTree/plugin/builtin/labels/label_sheet.py b/src/backend/InvenTree/plugin/builtin/labels/label_sheet.py index 497afd5ade..0d004333ad 100644 --- a/src/backend/InvenTree/plugin/builtin/labels/label_sheet.py +++ b/src/backend/InvenTree/plugin/builtin/labels/label_sheet.py @@ -165,17 +165,14 @@ class InvenTreeLabelSheetPlugin(LabelPrintingMixin, SettingsMixin, InvenTreePlug if str2bool(self.get_setting('DEBUG')): # In debug mode return with the raw HTML - output.output = ContentFile(html_data, 'labels.html') + generated_file = ContentFile(html_data, 'labels.html') else: # Render HTML to PDF html = weasyprint.HTML(string=html_data) document = html.render().write_pdf() + generated_file = ContentFile(document, 'labels.pdf') - output.output = ContentFile(document, 'labels.pdf') - - output.progress = n_labels - output.complete = True - output.save() + output.mark_complete(progress=n_labels, output=generated_file) def print_page(self, label: LabelTemplate, items: list, request, **kwargs): """Generate a single page of labels. diff --git a/src/backend/InvenTree/report/models.py b/src/backend/InvenTree/report/models.py index bfa3bc15d2..1e26114e5e 100644 --- a/src/backend/InvenTree/report/models.py +++ b/src/backend/InvenTree/report/models.py @@ -14,7 +14,7 @@ from django.core.files.storage import default_storage from django.core.validators import FileExtensionValidator, MinValueValidator from django.db import models from django.template import Context, Template -from django.template.exceptions import TemplateDoesNotExist +from django.template.exceptions import TemplateDoesNotExist, TemplateSyntaxError from django.template.loader import render_to_string from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -46,6 +46,17 @@ except OSError as err: # pragma: no cover logger = structlog.getLogger('inventree') +def log_report_error(*args, **kwargs): + """Log an error message when a report fails to render.""" + try: + do_log = get_global_setting('REPORT_LOG_ERRORS', backup_value=True) + except Exception: + do_log = True + + if do_log: + InvenTree.exceptions.log_error(*args, **kwargs) + + def rename_template(instance, filename): """Function to rename a report template once uploaded. @@ -528,7 +539,17 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase): report = self.render(instance, request, contexts) except TemplateDoesNotExist as e: t_name = str(e) or self.template - raise ValidationError(f'Template file {t_name} does not exist') + msg = f'Template file {t_name} does not exist' + output.mark_failure(error=msg) + raise ValidationError(msg) + except TemplateSyntaxError as e: + msg = _('Template syntax error') + output.mark_failure(msg) + raise ValidationError(f'{msg}: {e!s}') + except Exception as e: + msg = _('Error rendering report') + output.mark_failure(msg) + raise ValidationError(f'{msg}: {e!s}') outputs.append(report) self.handle_attachment( @@ -554,7 +575,17 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase): report = self.render(instance, request, None) except TemplateDoesNotExist as e: t_name = str(e) or self.template - raise ValidationError(f'Template file {t_name} does not exist') + msg = f'Template file {t_name} does not exist' + output.mark_failure(error=msg) + raise ValidationError(msg) + except TemplateSyntaxError as e: + msg = _('Template syntax error') + output.mark_failure(error=_('Template syntax error')) + raise ValidationError(f'{msg}: {e!s}') + except Exception as e: + msg = _('Error rendering report') + output.mark_failure(error=msg) + raise ValidationError(f'{msg}: {e!s}') outputs.append(report) @@ -569,8 +600,7 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase): except Exception as exc: # Something went wrong during the report generation process - if get_global_setting('REPORT_LOG_ERRORS', backup_value=True): - InvenTree.exceptions.log_error('print', plugin=self.slug) + log_report_error('ReportTemplate.print') raise ValidationError({ 'error': _('Error generating report'), @@ -601,13 +631,15 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase): data = pdf_file.getvalue() pdf_file.close() except Exception: - InvenTree.exceptions.log_error('print', plugin=self.slug) - data = None + log_report_error('ReportTemplate.print') + msg = _('Error merging report outputs') + output.mark_failure(error=msg) + raise ValidationError(msg) # Save the generated report to the database - output.complete = True - output.output = ContentFile(data, report_name) - output.save() + generated_file = ContentFile(data, report_name) + + output.mark_complete(output=generated_file) return output diff --git a/src/backend/InvenTree/report/templates/report/inventree_purchase_order_report.html b/src/backend/InvenTree/report/templates/report/inventree_purchase_order_report.html index 44f3714e3a..732925e3e7 100644 --- a/src/backend/InvenTree/report/templates/report/inventree_purchase_order_report.html +++ b/src/backend/InvenTree/report/templates/report/inventree_purchase_order_report.html @@ -19,7 +19,14 @@ {% block page_content %} -