Skip to content

Barcode Mixin

Barcode Plugins

InvenTree supports decoding of arbitrary barcode data and generation of internal barcode formats via a Barcode Plugin interface. Barcode data POSTed to the /api/barcode/ endpoint will be supplied to all loaded barcode plugins, and the first plugin to successfully interpret the barcode data will return a response to the client.

InvenTree can generate native QR codes to represent database objects (e.g. a single StockItem). This barcode can then be used to perform quick lookup of a stock item or location in the database. A client application (for example the InvenTree mobile app) scans a barcode, and sends the barcode data to the InvenTree server. The server then uses the InvenTreeBarcodePlugin (found at src/backend/InvenTree/plugin/builtin/barcodes/inventree_barcode.py) to decode the supplied barcode data.

Any third-party barcodes can be decoded by writing a matching plugin to decode the barcode data. These plugins could then perform a server-side action or render a JSON response back to the client for further action.

Some examples of possible uses for barcode integration:

  • Stock lookup by scanning a barcode on a box of items
  • Receiving goods against a PurchaseOrder by scanning a supplier barcode
  • Perform a stock adjustment action (e.g. take 10 parts from stock whenever a barcode is scanned)

Barcode data are POSTed to the server as follows:

POST {
    barcode_data: "[(>someBarcodeDataWhichThePluginKnowsHowToDealWith"
}

Builtin Plugin

The InvenTree server includes a builtin barcode plugin which can generate and decode the QR codes. This plugin is enabled by default.

Builtin BarcodePlugin for matching and generating internal barcodes.

Source code in src/backend/InvenTree/plugin/builtin/barcodes/inventree_barcode.py
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
class InvenTreeInternalBarcodePlugin(SettingsMixin, BarcodeMixin, InvenTreePlugin):
    """Builtin BarcodePlugin for matching and generating internal barcodes."""

    NAME = 'InvenTreeBarcode'
    TITLE = _('InvenTree Barcodes')
    DESCRIPTION = _('Provides native support for barcodes')
    VERSION = '2.1.0'
    AUTHOR = _('InvenTree contributors')

    SETTINGS = {
        'INTERNAL_BARCODE_FORMAT': {
            'name': _('Internal Barcode Format'),
            'description': _('Select an internal barcode format'),
            'choices': [
                ('json', _('JSON barcodes (human readable)')),
                ('short', _('Short barcodes (space optimized)')),
            ],
            'default': 'json',
        },
        'SHORT_BARCODE_PREFIX': {
            'name': _('Short Barcode Prefix'),
            'description': _(
                'Customize the prefix used for short barcodes, may be useful for environments with multiple InvenTree instances'
            ),
            'default': 'INV-',
        },
    }

    def format_matched_response(self, label, model, instance):
        """Format a response for the scanned data."""
        return {label: instance.format_matched_response()}

    def scan(self, barcode_data):
        """Scan a barcode against this plugin.

        Here we are looking for a dict object which contains a reference to a particular InvenTree database object
        """
        # Internal Barcodes - Short Format
        # Attempt to match the barcode data against the short barcode format
        prefix = cast(str, self.get_setting('SHORT_BARCODE_PREFIX'))
        if type(barcode_data) is str and (
            m := re.match(
                f'^{re.escape(prefix)}([0-9A-Z $%*+-.\\/:]{"{2}"})(\\d+)$', barcode_data
            )
        ):
            model_type_code, pk = m.groups()

            supported_models_map = (
                plugin.base.barcodes.helper.get_supported_barcode_model_codes_map()
            )
            model = supported_models_map.get(model_type_code, None)

            if model is None:
                return None

            label = model.barcode_model_type()

            try:
                instance = model.objects.get(pk=int(pk))
                return self.format_matched_response(label, model, instance)
            except (ValueError, model.DoesNotExist):
                pass

        # Internal Barcodes - JSON Format
        # Attempt to coerce the barcode data into a dict object
        # This is the internal JSON barcode representation that InvenTree uses
        barcode_dict = None

        if type(barcode_data) is dict:
            barcode_dict = barcode_data
        elif type(barcode_data) is str:
            try:
                barcode_dict = json.loads(barcode_data)
            except json.JSONDecodeError:
                pass

        supported_models = plugin.base.barcodes.helper.get_supported_barcode_models()

        if barcode_dict is not None and type(barcode_dict) is dict:
            # Look for various matches. First good match will be returned
            for model in supported_models:
                label = model.barcode_model_type()

                if label in barcode_dict:
                    try:
                        pk = int(barcode_dict[label])
                        instance = model.objects.get(pk=pk)
                        return self.format_matched_response(label, model, instance)
                    except (ValueError, model.DoesNotExist):
                        pass

        # External Barcodes (Linked barcodes)
        # Create hash from raw barcode data
        barcode_hash = hash_barcode(barcode_data)

        # If no "direct" hits are found, look for assigned third-party barcodes
        for model in supported_models:
            label = model.barcode_model_type()

            instance = model.lookup_barcode(barcode_hash)

            if instance is not None:
                return self.format_matched_response(label, model, instance)

    def generate(self, model_instance: InvenTreeBarcodeMixin):
        """Generate a barcode for a given model instance."""
        barcode_format = self.get_setting('INTERNAL_BARCODE_FORMAT')

        if barcode_format == 'json':
            return json.dumps({model_instance.barcode_model_type(): model_instance.pk})

        if barcode_format == 'short':
            prefix = self.get_setting('SHORT_BARCODE_PREFIX')
            model_type_code = model_instance.barcode_model_type_code()

            return f'{prefix}{model_type_code}{model_instance.pk}'

        return None

Example Plugin

Please find below a very simple example that is used to return a part if the barcode starts with PART-

from plugin import InvenTreePlugin
from plugin.mixins import BarcodeMixin
from part.models import Part

class InvenTreeBarcodePlugin(BarcodeMixin, InvenTreePlugin):

    NAME = "MyBarcode"
    TITLE = "My Barcodes"
    DESCRIPTION = "support for barcodes"
    VERSION = "0.0.1"
    AUTHOR = "Michael"

    def scan(self, barcode_data):
        if barcode_data.startswith("PART-"):
            try:
                pk = int(barcode_data.split("PART-")[1])
                instance = Part.objects.get(pk=pk)
                label = Part.barcode_model_type()

                return {label: instance.format_matched_response()}
            except Part.DoesNotExist:
                pass

To try it just copy the file to src/InvenTree/plugins and restart the server. Open the scan barcode window and start to scan codes or type in text manually. Each time the timeout is hit the plugin will execute and printout the result. The timeout can be changed in Settings->Barcode Support->Barcode Input Delay.

Custom Internal Format

To implement a custom internal barcode format, the generate(...) method from the Barcode Mixin needs to be overridden. Then the plugin can be selected at System Settings > Barcodes > Barcode Generation Plugin.

from InvenTree.models import InvenTreeBarcodeMixin
from plugin import InvenTreePlugin
from plugin.mixins import BarcodeMixin

class InvenTreeBarcodePlugin(BarcodeMixin, InvenTreePlugin):
    NAME = "MyInternalBarcode"
    TITLE = "My Internal Barcodes"
    DESCRIPTION = "support for custom internal barcodes"
    VERSION = "0.0.1"
    AUTHOR = "InvenTree contributors"

    def generate(self, model_instance: InvenTreeBarcodeMixin):
        return f'{model_instance.barcode_model_type()}: {model_instance.pk}'

Scanning implementation required

The parsing of the custom format needs to be implemented too, so that the scanning of the generated QR codes resolves to the correct part.