2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-01 19:20:55 +00:00

Report merge

This commit is contained in:
Tristan Le
2025-04-18 16:24:36 +00:00
parent 67bdf3162a
commit a92a1e134a
5 changed files with 111 additions and 20 deletions

View File

@ -17,7 +17,7 @@ repos:
- id: check-yaml
- id: mixed-line-ending
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.0
rev: v0.11.6
hooks:
- id: ruff-format
args: [--preview]
@ -28,7 +28,7 @@ repos:
--preview
]
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.6.6
rev: 0.6.14
hooks:
- id: pip-compile
name: pip-compile requirements-dev.in
@ -70,13 +70,13 @@ repos:
src/frontend/vite.config.ts |
)$
- repo: https://github.com/biomejs/pre-commit
rev: v1.9.4
rev: v2.0.0-beta.1
hooks:
- id: biome-check
additional_dependencies: ["@biomejs/biome@1.9.4"]
files: ^src/frontend/.*\.(js|ts|tsx)$
- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.0
rev: v8.24.3
hooks:
- id: gitleaks
language_version: 1.23.6

View File

@ -0,0 +1,22 @@
# Generated by Django 4.2.20 on 2025-04-03 01:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("report", "0029_remove_reportoutput_template_and_more"),
]
operations = [
migrations.AddField(
model_name="reporttemplate",
name="merge",
field=models.BooleanField(
default=False,
help_text="Render a single report against selected items",
verbose_name="Merge",
),
),
]

View File

@ -170,10 +170,12 @@ class ReportContextExtension(TypedDict):
Attributes:
page_size: The page size of the report
landscape: Boolean value, True if the report is in landscape mode
merge: Boolean value, True if the a single report is generated against multiple items
"""
page_size: str
landscape: bool
merge: bool
class ReportTemplateBase(MetadataMixin, InvenTree.models.InvenTreeModel):
@ -366,6 +368,12 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
help_text=_('Render report in landscape orientation'),
)
merge = models.BooleanField(
default=False,
verbose_name=_('Merge'),
help_text=_('Render a single report against selected items'),
)
def get_report_size(self) -> str:
"""Return the printable page size for this report."""
try:
@ -382,14 +390,21 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
return page_size
def get_context(self, instance, request=None, **kwargs):
"""Supply context data to the report template for rendering."""
base_context = super().get_context(instance, request)
def get_report_context(self):
"""Return report template context."""
report_context: ReportContextExtension = {
'page_size': self.get_report_size(),
'landscape': self.landscape,
'merge': self.merge,
}
return report_context
def get_context(self, instance, request=None, **kwargs):
"""Supply context data to the report template for rendering."""
base_context = super().get_context(instance, request)
report_context: ReportContextExtension = self.get_report_context()
context = {**base_context, **report_context}
# Pass the context through to the plugin registry for any additional information
@ -455,21 +470,23 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
output.save()
try:
for instance in items:
context = self.get_context(instance, request)
if self.merge:
base_context = super().base_context(request)
report_context = self.get_report_context()
item_contexts = []
for instance in items:
item_contexts.append(instance.report_context())
contexts = {
**base_context,
**report_context,
'instances': item_contexts,
}
if report_name is None:
report_name = self.generate_filename(context)
report_name = self.generate_filename(contexts)
# Render the report output
try:
if debug_mode:
report = self.render_as_string(instance, request)
else:
report = self.render(instance, request)
except TemplateDoesNotExist as e:
t_name = str(e) or self.template
raise ValidationError(f'Template file {t_name} does not exist')
html = render_to_string(self.template_name, contexts, request)
report = HTML(string=html).write_pdf()
outputs.append(report)
@ -495,6 +512,47 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
# Update the progress of the report generation
output.progress += 1
output.save()
else:
for instance in items:
context = self.get_context(instance, request)
if report_name is None:
report_name = self.generate_filename(context)
# Render the report output
try:
if debug_mode:
report = self.render_as_string(instance, request)
else:
report = self.render(instance, request)
except TemplateDoesNotExist as e:
t_name = str(e) or self.template
raise ValidationError(f'Template file {t_name} does not exist')
outputs.append(report)
# Attach the generated report to the model instance (if required)
if self.attach_to_model and not debug_mode:
instance.create_attachment(
attachment=ContentFile(report, report_name),
comment=_(f'Report generated from template {self.name}'),
upload_user=request.user
if request and request.user.is_authenticated
else None,
)
# Provide generated report to any interested plugins
for plugin in report_plugins:
try:
plugin.report_callback(self, instance, report, request)
except Exception:
InvenTree.exceptions.log_error(
f'plugins.{plugin.slug}.report_callback'
)
# Update the progress of the report generation
output.progress += 1
output.save()
except Exception as exc:
# Something went wrong during the report generation process

View File

@ -65,7 +65,12 @@ class ReportTemplateSerializer(ReportSerializerBase):
"""Metaclass options."""
model = report.models.ReportTemplate
fields = [*ReportSerializerBase.base_fields(), 'page_size', 'landscape']
fields = [
*ReportSerializerBase.base_fields(),
'page_size',
'landscape',
'merge',
]
page_size = serializers.ChoiceField(
required=False,

View File

@ -21,6 +21,12 @@ function ReportTemplateTable() {
<YesNoButton value={instance.landscape} />
)
},
merge: {
label: t`Merge`,
modelRenderer: (instance: any) => (
<YesNoButton value={instance.merge} />
)
},
attach_to_model: {
label: t`Attach to Model`,
modelRenderer: (instance: any) => (