mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-01 11:10:54 +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>
|
||||
|
@ -3,7 +3,7 @@ import { apiUrl } from '@lib/functions/Api';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useDocumentVisibility } from '@mantine/hooks';
|
||||
import { notifications, showNotification } from '@mantine/notifications';
|
||||
import { IconCircleCheck } from '@tabler/icons-react';
|
||||
import { IconCircleCheck, IconExclamationCircle } from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ProgressBar } from '../components/items/ProgressBar';
|
||||
@ -50,7 +50,22 @@ export default function useDataOutput({
|
||||
.then((response) => {
|
||||
const data = response?.data ?? {};
|
||||
|
||||
if (data.complete) {
|
||||
if (!!data.errors || !!data.error) {
|
||||
setLoading(false);
|
||||
|
||||
const error: string =
|
||||
data?.error ?? data?.errors?.error ?? t`Process failed`;
|
||||
|
||||
notifications.update({
|
||||
id: `data-output-${id}`,
|
||||
loading: false,
|
||||
icon: <IconExclamationCircle />,
|
||||
autoClose: 2500,
|
||||
title: title,
|
||||
message: error,
|
||||
color: 'red'
|
||||
});
|
||||
} else if (data.complete) {
|
||||
setLoading(false);
|
||||
notifications.update({
|
||||
id: `data-output-${id}`,
|
||||
@ -66,16 +81,6 @@ export default function useDataOutput({
|
||||
const url = generateUrl(data.output);
|
||||
window.open(url.toString(), '_blank');
|
||||
}
|
||||
} else if (!!data.error) {
|
||||
setLoading(false);
|
||||
notifications.update({
|
||||
id: `data-output-${id}`,
|
||||
loading: false,
|
||||
autoClose: 2500,
|
||||
title: title,
|
||||
message: t`Process failed`,
|
||||
color: 'red'
|
||||
});
|
||||
} else {
|
||||
notifications.update({
|
||||
id: `data-output-${id}`,
|
||||
|
Reference in New Issue
Block a user