Skip to content

Datamatrix #8853

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/docs/assets/images/report/datamatrix.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/docs/assets/images/report/qrcode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions docs/docs/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,13 @@ def on_config(config, *args, **kwargs):
"""
rtd = os.environ.get('READTHEDOCS', False)

# Note: version selection is handled by RTD internally
# Check for 'versions.json' file
# If it does not exist, we need to fetch it from the RTD API
if os.path.exists(os.path.join(os.path.dirname(__file__), 'versions.json')):
print("Found 'versions.json' file")
else:
fetch_rtd_versions()
# if os.path.exists(os.path.join(os.path.dirname(__file__), 'versions.json')):
# print("Found 'versions.json' file")
# else:
# fetch_rtd_versions()

if rtd:
rtd_version = os.environ['READTHEDOCS_VERSION']
Expand Down
116 changes: 104 additions & 12 deletions docs/docs/report/barcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ title: Barcode Generation

Both [report](./report.md) and [label](./labels.md) templates can render custom barcode data to in-line images.

!!! info "img"
Barcode data must be rendered inside an `<img>` tag.
### Barcode Template Tags

Inside the template file (whether it be for printing a label or generating a custom report), the following code will need to be included at the top of the template file:
To use the barcode tags inside a label or report template, you must load the `barcode` template tags at the top of the template file:

```html
{% raw %}
Expand All @@ -18,12 +17,30 @@ Inside the template file (whether it be for printing a label or generating a cus
{% endraw %}
```

### 1D Barcode
### Barcode Image Data

The barcode template tags will generate an image tag with the barcode data encoded as a base64 image. The image data is intended to be rendered as an `img` tag:

```html
{% raw %}
{% load barcode %}
<img class='custom_class' src='{% barcode "12345678" %}'>
{% endraw %}
```

## 1D Barcode

!!! info "python-barcode"
One dimensional barcodes (e.g. Code128) are generated using the [python-barcode](https://pypi.org/project/python-barcode/) library.

To render a 1D barcode, use the `barcode` template tag, as shown in the example below:
To render a 1D barcode, use the `barcode` template tag:

::: report.templatetags.barcode.barcode
options:
show_docstring_description: False
show_source: False

### Example

```html
{% raw %}
Expand All @@ -36,6 +53,8 @@ To render a 1D barcode, use the `barcode` template tag, as shown in the example
{% endraw %}
```

### Additional Options

The default barcode renderer will generate a barcode using [Code128](https://en.wikipedia.org/wiki/Code_128) rendering. However [other barcode formats](https://python-barcode.readthedocs.io/en/stable/supported-formats.html) are also supported:

```html
Expand All @@ -58,29 +77,102 @@ You can also pass further [python-barcode](https://python-barcode.readthedocs.io
{% endraw %}
```

### QR-Code
## QR-Code

!!! info "qrcode"
Two dimensional QR codes are generated using the [qrcode](https://pypi.org/project/qrcode/) library.

To render a QR code, use the `qrcode` template tag:

::: report.templatetags.barcode.qrcode
options:
show_docstring_description: false
show_source: False

### Example

```html
{% raw %}
{% extends "label/label_base.html" %}

{% load barcode %}
{% load l10n i18n barcode %}

<img class='custom_qr_class' src='{% qrcode "Hello world!" %}'>
{% block style %}

.qr {
position: absolute;
left: 0mm;
top: 0mm;
{% localize off %}
height: {{ height }}mm;
width: {{ height }}mm;
{% endlocalize %}
}

{% endblock style %}

{% block content %}
<img class='qr' src='{% qrcode "Hello world!" fill_color="white" back_color="blue" %}'>
{% endblock content %}
{% endraw %}
```

Additional parameters can be passed to the `qrcode` function for rendering:
which produces the following output:

{% with id="qrcode", url="report/qrcode.png", description="QR Code" %}
{% include 'img.html' %}
{% endwith %}


!!! tip "Documentation"
Refer to the [qrcode library documentation](https://pypi.org/project/qrcode/) for more information


## Data Matrix

!!! info "ppf.datamatrix"
Data Matrix codes are generated using the [ppf.datamatrix](https://pypi.org/project/ppf-datamatrix/) library.

[Data Matrix Codes](https://en.wikipedia.org/wiki/Data_Matrix) provide an alternative to QR codes for encoding data in a two-dimensional matrix. To render a Data Matrix code, use the `datamatrix` template tag:

::: report.templatetags.barcode.datamatrix
options:
show_docstring_description: false
show_source: False

### Example

```html
{% raw %}
<img class='custom_qr_class' src='{% qrcode "Hello world!" fill_color="green" back_color="blue" %}'>
{% extends "label/label_base.html" %}

{% load l10n i18n barcode %}

{% block style %}

.qr {
position: absolute;
left: 0mm;
top: 0mm;
{% localize off %}
height: {{ height }}mm;
width: {{ height }}mm;
{% endlocalize %}
}

{% endblock style %}

{% block content %}


<img class='qr' src='{% datamatrix "Foo Bar" back_color="yellow" %}'>

{% endblock content %}
{% endraw %}
```

!!! tip "Documentation"
Refer to the [qrcode library documentation](https://pypi.org/project/qrcode/) for more information
which produces the following output:

{% with id="datamatrix", url="report/datamatrix.png", description="Datamatrix barcode" %}
{% include 'img.html' %}
{% endwith %}
2 changes: 1 addition & 1 deletion src/backend/InvenTree/report/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def report_page_size_default():
return page_size


def encode_image_base64(image, img_format: str = 'PNG'):
def encode_image_base64(image, img_format: str = 'PNG') -> str:
"""Return a base-64 encoded image which can be rendered in an <img> tag.

Arguments:
Expand Down
116 changes: 103 additions & 13 deletions src/backend/InvenTree/report/templatetags/barcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import barcode as python_barcode
import qrcode.constants as ECL
from PIL import Image, ImageColor
from qrcode.main import QRCode

import report.helpers
Expand All @@ -19,7 +20,7 @@
}


def image_data(img, fmt='PNG'):
def image_data(img, fmt='PNG') -> str:
"""Convert an image into HTML renderable data.

Returns a string ``data:image/FMT;base64,xxxxxxxxx`` which can be rendered to an <img> tag
Expand All @@ -45,26 +46,31 @@ def clean_barcode(data):


@register.simple_tag()
def qrcode(data, **kwargs):
def qrcode(data: str, **kwargs) -> str:
"""Return a byte-encoded QR code image.

Arguments:
data: Data to encode

Keyword Arguments:
version: QR code version, (None to auto detect) (default = None)
error_correction: Error correction level (L: 7%, M: 15%, Q: 25%, H: 30%) (default = 'M')
box_size: pixel dimensions for one black square pixel in the QR code (default = 20)
border: count white QR square pixels around the qr code, needed as padding (default = 1)
optimize: data will be split into multiple chunks of at least this length using different modes (text, alphanumeric, binary) to optimize the QR code size. Set to `0` to disable. (default = 1)
format: Image format (default = 'PNG')
fill_color: Fill color (default = "black")
back_color: Background color (default = "white")
version (int): QR code version, (None to auto detect) (default = None)
error_correction (str): Error correction level (L: 7%, M: 15%, Q: 25%, H: 30%) (default = 'M')
box_size (int): pixel dimensions for one black square pixel in the QR code (default = 20)
border (int): count white QR square pixels around the qr code, needed as padding (default = 1)
optimize (int): data will be split into multiple chunks of at least this length using different modes (text, alphanumeric, binary) to optimize the QR code size. Set to `0` to disable. (default = 1)
format (str): Image format (default = 'PNG')
fill_color (str): Fill color (default = "black")
back_color (str): Background color (default = "white")

Returns:
base64 encoded image data
image (str): base64 encoded image data

"""
data = str(data).strip()

if not data:
raise ValueError("No data provided to 'qrcode' template tag")

# Extract other arguments from kwargs
fill_color = kwargs.pop('fill_color', 'black')
back_color = kwargs.pop('back_color', 'white')
Expand All @@ -89,8 +95,26 @@ def qrcode(data, **kwargs):


@register.simple_tag()
def barcode(data, barcode_class='code128', **kwargs):
"""Render a barcode."""
def barcode(data: str, barcode_class='code128', **kwargs) -> str:
"""Render a 1D barcode.

Arguments:
data: Data to encode

Keyword Arguments:
format (str): Image format (default = 'PNG')
fill_color (str): Foreground color (default = 'black')
back_color (str): Background color (default = 'white')
scale (float): Scaling factor (default = 1)

Returns:
image (str): base64 encoded image data
"""
data = str(data).strip()

if not data:
raise ValueError("No data provided to 'barcode' template tag")

constructor = python_barcode.get_barcode_class(barcode_class)

img_format = kwargs.pop('format', 'PNG')
Expand All @@ -105,3 +129,69 @@ def barcode(data, barcode_class='code128', **kwargs):

# Render to byte-encoded image
return image_data(image, fmt=img_format)


@register.simple_tag()
def datamatrix(data: str, **kwargs) -> str:
"""Render a DataMatrix barcode.

Arguments:
data: Data to encode

Keyword Arguments:
fill_color (str): Foreground color (default = 'black')
back_color (str): Background color (default = 'white')
scale (float): Matrix scaling factor (default = 1)
border (int): Border width (default = 1)

Returns:
image (str): base64 encoded image data
"""
from ppf.datamatrix import DataMatrix

data = str(data).strip()

if not data:
raise ValueError("No data provided to 'datamatrix' template tag")

dm = DataMatrix(data)

fill_color = kwargs.pop('fill_color', 'black')
back_color = kwargs.pop('back_color', 'white')

border = kwargs.pop('border', 1)

try:
border = int(border)
except Exception:
border = 1

border = max(0, border)

try:
fg = ImageColor.getcolor(fill_color, 'RGB')
except Exception:
fg = ImageColor.getcolor('black', 'RGB')

try:
bg = ImageColor.getcolor(back_color, 'RGB')
except Exception:
bg = ImageColor.getcolor('white', 'RGB')

scale = kwargs.pop('scale', 1)

height = len(dm.matrix) + 2 * border
width = len(dm.matrix[0]) + 2 * border

# Generate raw image from the matrix
img = Image.new('RGB', (width, height), color=bg)

for y, row in enumerate(dm.matrix):
for x, value in enumerate(row):
if value:
img.putpixel((x + border, y + border), fg)

if scale != 1:
img = img.resize((int(width * scale), int(height * scale)))

return image_data(img, fmt='PNG')
29 changes: 29 additions & 0 deletions src/backend/InvenTree/report/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ def test_barcode(self):
self.assertIsInstance(barcode, str)
self.assertTrue(barcode.startswith('data:image/bmp;'))

# Test empty tag
with self.assertRaises(ValueError):
barcode_tags.barcode('')

def test_qrcode(self):
"""Test the qrcode generation tag."""
# Test with default settings
Expand All @@ -256,6 +260,31 @@ def test_qrcode(self):
self.assertTrue(qrcode.startswith('data:image/bmp;'))
self.assertEqual(len(qrcode), 309720)

# Test empty tag
with self.assertRaises(ValueError):
barcode_tags.qrcode('')

def test_datamatrix(self):
"""Test the datamatrix generation tag."""
# Test with default settings
datamatrix = barcode_tags.datamatrix('hello world')
self.assertEqual(
datamatrix,
'data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAIAAADZrBkAAAAAlElEQVR4nJ1TQQ7AIAgri///cncw6wroEseBgEFbCgZJnNsFICKOPAAIjeSM5T11IznK5f5WRMgnkhP9JfCcTC/MxFZ5hxLOgqrn3o/z/OqtsNpdSL31Iu9W4Dq8Sulu+q5Nuqa3XYOdnuidlICPpXhZVBruyzAKSZehT+yNlzvZQcq6JiW7Ni592swf/43kdlDfdgMk1eOtR7kWpAAAAABJRU5ErkJggg==',
)

datamatrix = barcode_tags.datamatrix(
'hello world', border=3, fill_color='red', back_color='blue'
)
self.assertEqual(
datamatrix,
'data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAIAAABL1vtsAAAAqElEQVR4nN1UQQ6AMAgrxv9/GQ9mpJYSY/QkBxM3KLUUA0i8i+1l/dcQiXj09CwSEU2aQJ7nE8ou2faVUXoPZSEkq+dZKVxWg4UqxUHnVdkp6IdwMXMulGvzNBDMk4WwPSrUF3LNnQNZBJmOsZaVXa44QSEKnvWb5mIgKon1E1H6aPyOcIa15uhONP9aR4hSCiGmYAoYpj4uO+vK4+ybMhr8Nkjmn/z4Dvoldi8uJu4iAAAAAElFTkSuQmCC',
)

# Test empty tag
with self.assertRaises(ValueError):
barcode_tags.datamatrix('')


class ReportTest(InvenTreeAPITestCase):
"""Base class for unit testing reporting models."""
Expand Down
Loading
Loading