mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-02 05:26:45 +00:00
Datamatrix (#8853)
* Implement datamatrix barcode generation * Update documentation * Update package requirements * Add unit test * Raise error on empty barcode data * Update docs/hooks.py
This commit is contained in:
parent
9138e31e58
commit
c815455461
BIN
docs/docs/assets/images/report/datamatrix.png
Normal file
BIN
docs/docs/assets/images/report/datamatrix.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
BIN
docs/docs/assets/images/report/qrcode.png
Normal file
BIN
docs/docs/assets/images/report/qrcode.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
@ -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']
|
||||
|
@ -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 %}
|
||||
@ -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 %}
|
||||
@ -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
|
||||
@ -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:
|
||||
|
||||
```html
|
||||
{% raw %}
|
||||
::: report.templatetags.barcode.qrcode
|
||||
options:
|
||||
show_docstring_description: false
|
||||
show_source: False
|
||||
|
||||
{% load barcode %}
|
||||
|
||||
<img class='custom_qr_class' src='{% qrcode "Hello world!" %}'>
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
Additional parameters can be passed to the `qrcode` function for rendering:
|
||||
### 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='{% qrcode "Hello world!" fill_color="white" back_color="blue" %}'>
|
||||
{% endblock content %}
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
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 %}
|
||||
{% 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 %}
|
||||
```
|
||||
|
||||
which produces the following output:
|
||||
|
||||
{% with id="datamatrix", url="report/datamatrix.png", description="Datamatrix barcode" %}
|
||||
{% include 'img.html' %}
|
||||
{% endwith %}
|
||||
|
@ -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:
|
||||
|
@ -5,6 +5,7 @@ from django.utils.safestring import mark_safe
|
||||
|
||||
import barcode as python_barcode
|
||||
import qrcode.constants as ECL
|
||||
from PIL import Image, ImageColor
|
||||
from qrcode.main import QRCode
|
||||
|
||||
import report.helpers
|
||||
@ -19,7 +20,7 @@ QR_ECL_LEVEL_MAP = {
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
@ -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')
|
||||
@ -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')
|
||||
@ -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')
|
||||
|
@ -240,6 +240,10 @@ class BarcodeTagTest(TestCase):
|
||||
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
|
||||
@ -256,6 +260,31 @@ class BarcodeTagTest(TestCase):
|
||||
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."""
|
||||
|
@ -40,6 +40,7 @@ pdf2image # PDF to image conversion
|
||||
pillow # Image manipulation
|
||||
pint # Unit conversion
|
||||
pip-licenses # License information for installed packages
|
||||
ppf.datamatrix # Data Matrix barcode generator
|
||||
python-barcode[images] # Barcode generator
|
||||
python-dotenv # Environment variable management
|
||||
pyyaml>=6.0.1 # YAML parsing
|
||||
|
@ -1192,6 +1192,10 @@ platformdirs==4.3.6 \
|
||||
--hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \
|
||||
--hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb
|
||||
# via pint
|
||||
ppf-datamatrix==0.2 \
|
||||
--hash=sha256:819be65eae444b760e178d5761853f78f8e5fca14fec2809b5e3369978fa9244 \
|
||||
--hash=sha256:8f034d9c90e408f60f8b10a273baab81014c9a81c983dc1ebdc31d4ca5ac5582
|
||||
# via -r src/backend/requirements.in
|
||||
prettytable==3.12.0 \
|
||||
--hash=sha256:77ca0ad1c435b6e363d7e8623d7cc4fcf2cf15513bf77a1c1b2e814930ac57cc \
|
||||
--hash=sha256:f04b3e1ba35747ac86e96ec33e3bb9748ce08e254dc2a1c6253945901beec804
|
||||
|
Loading…
x
Reference in New Issue
Block a user