2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-30 04:26:44 +00:00
Oliver aa39582d89
Report printing refactor (#7074)
* Adds a new "generic" ReportTemplate model

* expose API endpoints

* Update model / migrations / serializer

* Add new mixin class to existing database models

* - Add detail view for report template
- Revert filters field behaviour

* Filter report list by provided item IDs

- Greatly simplify filtering logic compared to existing implemetation
- Expose to API schema

* Create data migration for converting *old* report templates

* Ignore internal reports for data migration

* Add report mixin to StockLocation model

* Provide model choices in admin interface

* Offload context data generation to the model classes

* Remove old report template models

* Refactor JS code in CUI

* Fix for API filtering

* Add data migration to delete old models

* Remove dead URL

* Updates

* Construct sample report templates on app start

* Bump API version

* Typo fix

* Fix incorrect context calls

* Add new LabelTemplate model

- ReportTemplate and LabelTemplate share common base
- Refactor previous migration

* Expose to admin interface

* Add in extra context from existing label models

* Add migration to create LabelTemplate instances from existing labels

* Add API endpoints for listing and updating LabelTemplate objects

* Adjust 'upload_to' path

* Refactor label printing

* Move default label templates

* Update API endpoints

* Update migrations

* Handle LookupError in migration

* Redirect the "label" API endpoint

* Add new model for handling result of template printing

* Refactor LabelPrinting mixin

* Unlink "labels" app entirely

* Fix typo

* Record 'plugin' used to generate a particular output

* Fix imports

* Generate label print response

- Still not good yet

* Refactoring label printing in CUI

* add "items" count to TemplateOutput model

* Fix for InvenTreeLabelSheetPlugin

* Remove old "label" app

* Make request object optional

* Fix filename generation

* Add help text for "model_type"

* Simplify TemplateTable

* Tweak TemplateTable

* Get template editor to display template data again

* Stringify template name

- Important, otherwise you get a TypeError instead of TemplateDoesNotExist

* Add hooks to reset plugin state

* fix context for StockLocation model

* Tweak log messages

* Fix incorrect serializer

* Cleanup TemplateTable

* Fix broken import

* Filter by target model type

* Remove manual file operations

* Update old migrations

- Remove references to functions that no longer exist

* Refactor asset / snippet uploading

* Update comments

* Retain original filename when editing templatese

* Cleanup

* Refactor model type filter to use new hook

* Add placeholder actions for printing labels and reports

* Improve hookiness

* Add new ReportOutput class

* Report printing works from PUI now!

* More inspired filename pattern for generated reports

* Fix template preview window

- Use new "output" response field across the board

* Remove outdated task

* Update data migration to use raw SQL

- If the 'labels' app is no longer available, this will fail
- So, use raw SQL instead

* Add more API endpoint defs

* Adds placeholder API endpoint for label printing

* Expose plugin field to the printing endpoint

* Adds plugin model type

* Hook to print labels

* Refactor action dropdown items

* Refactor report printing for CUI

* Refactor label print for CUI

- Still needs to handle custom printing options for plugin

* Fix migration

* Update ModelType dict

* playwright test fix

* Unit test fixes

* Fix model ruleset associations

* Fix for report.js

* Add support for "dynamic" fields in metadata.py

* Add in custom fields based on plugin

* Refactoring

* Reset plugin on form close

* Set custom timeout values

* Update migration

- Not atomic

* Cleanup

* Implement more printing actions

* Reduce timeout

* Unit test updates

* Fix part serializers

* Label printing works in CUI again

* js linting

* Update <ActionDropdown>

* Fix for label printing API endpoint

* Fix filterselectdrawer

* Improve button rendering

* Allow printing from StockLocationTable

* Add aria-labels to modal form fields

* Add test for printing stock item labels from table

* Add test for report printing

* Add unit testing for report template editing / preview

* Message refactor

* Refactor InvenTreeReportMixin class

* Update playwright test

* Update 'verbose_name' for a number of models

* Additional admin filtering

* Playwright test updates

* Run checks against new python lib branch

(temporary, will be reverted)

* remove old app reference

* fix testing ref

* fix app init

* remove old tests

* Revert custom target branch

* Expose label and report output objects to API

* refactor

* fix a few tests

* factor plugin_ref out

* fix options testing

* Update table field header

* re-enable full options testing

* fix missing plugin matching

* disable call assert

* Add custom related field for PluginConfig

- Uses 'key' rather than 'pk'
- Revert label print plugin to use slug

* Add support for custom pk field in metadata

* switch to labels for testing

* re-align report testing code

* disable version check

* fix url

* Implement lazy loading

* Allow blank plugin for printing

- Uses the builtin label printer if not specified

* Add printing actions for StockItem

* Fix for metadata helper

* Use key instead of pk in printing actions

* Support non-standard pk values in RelatedModelField

* pass context data to report serializers

* disable template / item discovery

* fix call

* Tweak unit test

* Run python checks against specific branch

* Add task for running docs server

- Option to compile schema as part of task

* Custom branch no longer needed

* Starting on documentation updates

* fix tests for reports

* fix label testing

* Update template context variables

* Refactor report context documentation

* Documentation cleanup

* Docs cleanup

* Include sample report files

* Fix links

* Link cleanup

* Integrate plugin example code into docs

* Code cleanup

* Fix type annotation

* Revert deleted variable

* remove templatetype

* remove unused imports

* extend context testing

* test if plg can print

* re-enable version check

* Update unit tests

* Fix test

* Adjust unit test

* Add debug statement to test

* Fix unit test

- Labels get printed against LabelTemplate items, duh

* Unit test update

* Unit test updates

* Test update

* Patch fix for <PartColumn> component

* Fix ReportSerialierBase class

- Re-initialize field options if not already set

* Fix unit test for sqlite

* Fix kwargs for non-blocking label printing

* Update playwright tests

* Tweak unit test

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
2024-05-22 10:17:01 +10:00

7.9 KiB

title
title
Label Mixin

LabelPrintingMixin

The LabelPrintingMixin class allows plugins to provide custom label printing functionality. The specific implementation of a label printing plugin is quite flexible, allowing for the following functions (as a starting point):

  • Printing a single label to a file, and allowing user to download
  • Combining multiple labels onto a single page
  • Supporting proprietary label sheet formats
  • Offloading label printing to an external printer

Entry Point

When printing labels against a particular plugin, the entry point is the print_labels method. The default implementation of this method iterates over each of the provided items, renders a PDF, and calls the print_label method for each item, providing the rendered PDF data.

Both the print_labels and print_label methods may be overridden by a plugin, allowing for complex functionality to be achieved.

For example, the print_labels method could be reimplemented to merge all labels into a single larger page, and return a single page for printing.

Return Type

The print_labels method must return a JsonResponse object. If the method does not return such a response, an error will be raised by the server.

File Generation

If the label printing plugin generates a real file, it should be stored as a LabelOutput instance in the database, and returned in the JsonResponse result under the 'file' key.

For example, the built-in InvenTreeLabelPlugin plugin generates a PDF file which contains all the provided labels concatenated together. A snippet of the code is shown below (refer to the source code for full details):

# Save the generated file to the database
output = LabelOutput.objects.create(
    label=output_file,
    user=request.user
)

return JsonResponse({
    'file': output.label.url,
    'success': True,
    'message': f'{len(items)} labels generated'
})

Background Printing

For some label printing processes (such as offloading printing to an external networked printer) it may be preferable to utilize the background worker process, and not block the front-end server. The plugin provides an easy method to offload printing to the background thread.

Simply override the class attribute BLOCKING_PRINT as follows:

class MyPrinterPlugin(LabelPrintingMixin, InvenTreePlugin):
    BLOCKING_PRINT = False

If the print_labels method is not changed, this will run the print_label method in a background worker thread.

!!! info "Example Plugin" Check out the inventree-brother-plugin which provides native support for the Brother QL and PT series of networked label printers

!!! tip "Custom Code" If your plugin overrides the print_labels method, you will have to ensure that the label printing is correctly offloaded to the background worker. Look at the offload_label method of the plugin mixin class for how this can be achieved.

Printing options

A printing plugin can define custom options as a serializer class called PrintingOptionsSerializer that get shown on the printing screen and get passed to the print_labels/print_label function as a kwarg called printing_options. This can be used to e.g. let the user dynamically select the orientation of the label, the color mode, ... for each print job. The following simple example shows how to implement an orientation select. For more information about how to define fields, refer to the django rest framework (DRF) documentation.

from rest_framework import serializers

class MyLabelPrinter(LabelPrintingMixin, InvenTreePlugin):
    ...

    class PrintingOptionsSerializer(serializers.Serializer):
        orientation = serializers.ChoiceField(choices=[
            ("landscape", "Landscape"),
            ("portrait", "Portrait"),
        ])

    def print_label(self, **kwargs):
        print(kwargs["printing_options"]) # -> {"orientation": "landscape"}
        ...

!!! tip "Dynamically return a serializer instance" If your plugin wants to dynamically expose options based on the request, you can implement the get_printing_options_serializer function which by default returns an instance of the PrintingOptionsSerializer class if defined.

Helper Methods

The plugin class provides a number of additional helper methods which may be useful for generating labels:

Method Description
render_to_pdf Render label template to an in-memory PDF object
render_to_html Render label template to a raw HTML string
render_to_png Convert PDF data to an in-memory PNG image

!!! info "Use the Source" These methods are available for more complex implementations - refer to the source code for more information!

Merging Labels

To merge (combine) multiple labels into a single output (for example printing multiple labels on a single sheet of paper), the plugin must override the print_labels method and implement the required functionality.

Integration

Web Integration

If label printing plugins are enabled, they are able to be used directly from the InvenTree web interface:

{% with id="label_print", url="plugin/print_label_select_plugin.png", description="Print label via plugin" %} {% include 'img.html' %} {% endwith %}

App Integration

Label printing plugins also allow direct printing of labels via the mobile app

Implementation

Plugins which implement the LabelPrintingMixin mixin class can be implemented by simply providing a print_label method.

Simple Example

from dummy_printer import printer_backend

class MyLabelPrinter(LabelPrintingMixin, InvenTreePlugin):
    """
    A simple example plugin which provides support for a dummy printer.

    A more complex plugin would communicate with an actual printer!
    """

    NAME = "MyLabelPrinter"
    SLUG = "mylabel"
    TITLE = "A dummy printer"

    # Set BLOCKING_PRINT to false to return immediately
    BLOCKING_PRINT = False

    def print_label(self, **kwargs):
        """
        Send the label to the printer

        kwargs:
            pdf_file: The PDF file object of the rendered label (WeasyTemplateResponse object)
            pdf_data: Raw PDF data of the rendered label
            filename: The filename of this PDF label
            label_instance: The instance of the label model which triggered the print_label() method
            item_instance: The instance of the database model against which the label is printed
            user: The user who triggered this print job
            width: The expected width of the label (in mm)
            height: The expected height of the label (in mm)
            printing_options: The printing options set for this print job defined in the PrintingOptionsSerializer
        """

        width = kwargs['width']
        height = kwargs['height']

        # This dummy printer supports printing of raw image files
        printer_backend.print(png_file, w=width, h=height)

Default Plugin

InvenTree supplies the InvenTreeLabelPlugin out of the box, which generates a PDF file which is then available for immediate download by the user.

The default plugin also features a DEBUG mode which generates a raw HTML output, rather than PDF. This can be handy for tracking down any template rendering errors in your labels.

::: plugin.builtin.labels.inventree_label.InvenTreeLabelPlugin options: show_bases: False show_root_heading: False show_root_toc_entry: False show_source: True members: []

Available Data

The label data are supplied to the plugin in both PDF and PNG formats. This provides compatibility with a great range of label printers "out of the box". Conversion to other formats, if required, is left as an exercise for the plugin developer.

Other arguments provided to the print_label function are documented in the code sample above.