mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-01 19:20:55 +00:00
Report merge
This commit is contained in:
@ -17,7 +17,7 @@ repos:
|
|||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: mixed-line-ending
|
- id: mixed-line-ending
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.11.0
|
rev: v0.11.6
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
args: [--preview]
|
args: [--preview]
|
||||||
@ -28,7 +28,7 @@ repos:
|
|||||||
--preview
|
--preview
|
||||||
]
|
]
|
||||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||||
rev: 0.6.6
|
rev: 0.6.14
|
||||||
hooks:
|
hooks:
|
||||||
- id: pip-compile
|
- id: pip-compile
|
||||||
name: pip-compile requirements-dev.in
|
name: pip-compile requirements-dev.in
|
||||||
@ -70,13 +70,13 @@ repos:
|
|||||||
src/frontend/vite.config.ts |
|
src/frontend/vite.config.ts |
|
||||||
)$
|
)$
|
||||||
- repo: https://github.com/biomejs/pre-commit
|
- repo: https://github.com/biomejs/pre-commit
|
||||||
rev: v1.9.4
|
rev: v2.0.0-beta.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: biome-check
|
- id: biome-check
|
||||||
additional_dependencies: ["@biomejs/biome@1.9.4"]
|
additional_dependencies: ["@biomejs/biome@1.9.4"]
|
||||||
files: ^src/frontend/.*\.(js|ts|tsx)$
|
files: ^src/frontend/.*\.(js|ts|tsx)$
|
||||||
- repo: https://github.com/gitleaks/gitleaks
|
- repo: https://github.com/gitleaks/gitleaks
|
||||||
rev: v8.24.0
|
rev: v8.24.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: gitleaks
|
- id: gitleaks
|
||||||
language_version: 1.23.6
|
language_version: 1.23.6
|
||||||
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -170,10 +170,12 @@ class ReportContextExtension(TypedDict):
|
|||||||
Attributes:
|
Attributes:
|
||||||
page_size: The page size of the report
|
page_size: The page size of the report
|
||||||
landscape: Boolean value, True if the report is in landscape mode
|
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
|
page_size: str
|
||||||
landscape: bool
|
landscape: bool
|
||||||
|
merge: bool
|
||||||
|
|
||||||
|
|
||||||
class ReportTemplateBase(MetadataMixin, InvenTree.models.InvenTreeModel):
|
class ReportTemplateBase(MetadataMixin, InvenTree.models.InvenTreeModel):
|
||||||
@ -366,6 +368,12 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
|
|||||||
help_text=_('Render report in landscape orientation'),
|
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:
|
def get_report_size(self) -> str:
|
||||||
"""Return the printable page size for this report."""
|
"""Return the printable page size for this report."""
|
||||||
try:
|
try:
|
||||||
@ -382,14 +390,21 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
|
|||||||
|
|
||||||
return page_size
|
return page_size
|
||||||
|
|
||||||
def get_context(self, instance, request=None, **kwargs):
|
def get_report_context(self):
|
||||||
"""Supply context data to the report template for rendering."""
|
"""Return report template context."""
|
||||||
base_context = super().get_context(instance, request)
|
|
||||||
report_context: ReportContextExtension = {
|
report_context: ReportContextExtension = {
|
||||||
'page_size': self.get_report_size(),
|
'page_size': self.get_report_size(),
|
||||||
'landscape': self.landscape,
|
'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}
|
context = {**base_context, **report_context}
|
||||||
|
|
||||||
# Pass the context through to the plugin registry for any additional information
|
# Pass the context through to the plugin registry for any additional information
|
||||||
@ -455,21 +470,23 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
|
|||||||
output.save()
|
output.save()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for instance in items:
|
if self.merge:
|
||||||
context = self.get_context(instance, request)
|
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:
|
if report_name is None:
|
||||||
report_name = self.generate_filename(context)
|
report_name = self.generate_filename(contexts)
|
||||||
|
|
||||||
# Render the report output
|
html = render_to_string(self.template_name, contexts, request)
|
||||||
try:
|
report = HTML(string=html).write_pdf()
|
||||||
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)
|
outputs.append(report)
|
||||||
|
|
||||||
@ -495,6 +512,47 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
|
|||||||
# Update the progress of the report generation
|
# Update the progress of the report generation
|
||||||
output.progress += 1
|
output.progress += 1
|
||||||
output.save()
|
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:
|
except Exception as exc:
|
||||||
# Something went wrong during the report generation process
|
# Something went wrong during the report generation process
|
||||||
|
@ -65,7 +65,12 @@ class ReportTemplateSerializer(ReportSerializerBase):
|
|||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
model = report.models.ReportTemplate
|
model = report.models.ReportTemplate
|
||||||
fields = [*ReportSerializerBase.base_fields(), 'page_size', 'landscape']
|
fields = [
|
||||||
|
*ReportSerializerBase.base_fields(),
|
||||||
|
'page_size',
|
||||||
|
'landscape',
|
||||||
|
'merge',
|
||||||
|
]
|
||||||
|
|
||||||
page_size = serializers.ChoiceField(
|
page_size = serializers.ChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -21,6 +21,12 @@ function ReportTemplateTable() {
|
|||||||
<YesNoButton value={instance.landscape} />
|
<YesNoButton value={instance.landscape} />
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
merge: {
|
||||||
|
label: t`Merge`,
|
||||||
|
modelRenderer: (instance: any) => (
|
||||||
|
<YesNoButton value={instance.merge} />
|
||||||
|
)
|
||||||
|
},
|
||||||
attach_to_model: {
|
attach_to_model: {
|
||||||
label: t`Attach to Model`,
|
label: t`Attach to Model`,
|
||||||
modelRenderer: (instance: any) => (
|
modelRenderer: (instance: any) => (
|
||||||
|
Reference in New Issue
Block a user