mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +00:00 
			
		
		
		
	[Bug] Report fix (#9887)
* Improved mechanisms for data output - Log errors against output - Properly catch rendering errors * Updated error handling * Fix "render_date" tag * Update default report template - Make sure the helper is used * Bug fixes
This commit is contained in:
		| @@ -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): | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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.""" | ||||
|   | ||||
| @@ -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). | ||||
|   | ||||
| @@ -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.""" | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -19,7 +19,14 @@ | ||||
|  | ||||
| {% block page_content %} | ||||
|  | ||||
| <h3>{% trans "Line Items" %}</h3> | ||||
| <h4>{% trans "Order Details" %}</h4> | ||||
|  | ||||
| <ul> | ||||
|     <li>Issue Date: {% render_date order.issue_date %}</li> | ||||
|     <li>Target Date: {% render_date order.target_date %}</li> | ||||
| </ul> | ||||
|  | ||||
| <h4>{% trans "Line Items" %}</h4> | ||||
|  | ||||
| <table class='table table-striped table-condensed'> | ||||
|     <thead> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user