From 68d1682000be69b275bf45debe4a0cdbdd1f985b Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 20 Apr 2023 14:21:22 +1000 Subject: [PATCH] Report Plugins (#4643) * Add plugin mixin class for extending reports * Expose report context to the plugin system * Add an example mixin for adding context data to a report * Add the 'request' object to the plugin code --- .../plugin/base/integration/ReportMixin.py | 35 +++++++++++++++++ InvenTree/plugin/mixins/__init__.py | 2 + InvenTree/plugin/registry.py | 1 + .../integration/report_plugin_sample.py | 38 +++++++++++++++++++ InvenTree/report/models.py | 7 ++++ 5 files changed, 83 insertions(+) create mode 100644 InvenTree/plugin/base/integration/ReportMixin.py create mode 100644 InvenTree/plugin/samples/integration/report_plugin_sample.py diff --git a/InvenTree/plugin/base/integration/ReportMixin.py b/InvenTree/plugin/base/integration/ReportMixin.py new file mode 100644 index 0000000000..10ea3906cf --- /dev/null +++ b/InvenTree/plugin/base/integration/ReportMixin.py @@ -0,0 +1,35 @@ +"""Plugin mixin class for ReportContextMixin""" + + +class ReportMixin: + """Mixin which provides additional context to generated reports. + + This plugin mixin acts as a "shim" when generating reports, + can can add extra context data to a report template. + + Useful for custom report generation where the report template + needs some extra information which is not provided by default, + or some complex logic to generate the report. + """ + + class MixinMeta: + """Meta options for this mixin.""" + + MIXIN_NAME = 'ReportContext' + + def __init__(self): + """Register mixin.""" + super().__init__() + self.add_mixin('report', True, __class__) + + def add_report_context(self, instance, request, context): + """Add extra context to the provided report instance. + + By default, this method does nothing. + + Args: + instance: The report instance to add context to + request: The request object which initiated the report generation + context: The context dictionary to add to + """ + pass diff --git a/InvenTree/plugin/mixins/__init__.py b/InvenTree/plugin/mixins/__init__.py index 7fba7d1110..62330c5bbe 100644 --- a/InvenTree/plugin/mixins/__init__.py +++ b/InvenTree/plugin/mixins/__init__.py @@ -10,6 +10,7 @@ from ..base.integration.AppMixin import AppMixin from ..base.integration.mixins import (APICallMixin, NavigationMixin, PanelMixin, SettingsContentMixin, ValidationMixin) +from ..base.integration.ReportMixin import ReportMixin from ..base.integration.ScheduleMixin import ScheduleMixin from ..base.integration.SettingsMixin import SettingsMixin from ..base.integration.UrlsMixin import UrlsMixin @@ -22,6 +23,7 @@ __all__ = [ 'EventMixin', 'LabelPrintingMixin', 'NavigationMixin', + 'ReportMixin', 'ScheduleMixin', 'SettingsContentMixin', 'SettingsMixin', diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index 1e86f1ff9b..0b546fca47 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -39,6 +39,7 @@ class PluginsRegistry: from .base.integration.ScheduleMixin import ScheduleMixin from .base.integration.SettingsMixin import SettingsMixin from .base.integration.UrlsMixin import UrlsMixin + DEFAULT_MIXIN_ORDER = [SettingsMixin, ScheduleMixin, AppMixin, UrlsMixin] def __init__(self, mixin_order: list = None) -> None: diff --git a/InvenTree/plugin/samples/integration/report_plugin_sample.py b/InvenTree/plugin/samples/integration/report_plugin_sample.py new file mode 100644 index 0000000000..f5433a7a39 --- /dev/null +++ b/InvenTree/plugin/samples/integration/report_plugin_sample.py @@ -0,0 +1,38 @@ +"""Sample plugin for extending reporting functionality""" + +import random + +from plugin import InvenTreePlugin +from plugin.mixins import ReportMixin +from report.models import PurchaseOrderReport + + +class SampleReportPlugin(ReportMixin, InvenTreePlugin): + """Sample plugin which provides extra context data to a report""" + + NAME = "Report Plugin" + SLUG = "reportexample" + TITLE = "Sample Report Plugin" + DESCRIPTION = "A sample plugin which provides extra context data to a report" + VERSION = "1.0" + + def some_custom_function(self): + """Some custom function which is not required for the plugin to function""" + return random.randint(0, 100) + + def add_report_context(self, instance, request, context): + """Add example content to the report instance""" + + # We can add any extra context data we want to the report + + # Generate a random string of data + context['random_text'] = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz', k=20)) + + # Call a custom method + context['random_int'] = self.some_custom_function() + + # We can also add extra data to the context which is specific to the report type + context['is_purchase_order'] = isinstance(instance, PurchaseOrderReport) + + # We can also use the 'request' object to add extra context data + context['request_method'] = request.method diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index c9a5f5d15e..5baf5be4c6 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -22,6 +22,7 @@ import part.models import stock.models from InvenTree.helpers import validateFilterString from InvenTree.models import MetadataMixin +from plugin.registry import registry try: from django_weasyprint import WeasyTemplateResponseMixin @@ -212,6 +213,12 @@ class ReportTemplateBase(MetadataMixin, ReportBase): context['request'] = request context['user'] = request.user + # Pass the context through to any active reporting plugins + plugins = registry.with_mixin('report') + + for plugin in plugins: + plugin.add_report_context(self, request, context) + return context def generate_filename(self, request, **kwargs):