Coverage for src/backend/InvenTree/report/templatetags/barcode.py: 21%
78 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
1"""Template tags for rendering various barcodes."""
3from django import template
4from django.utils.safestring import mark_safe
6import barcode as python_barcode
7import barcode.writer as python_barcode_writer
8import qrcode.constants as ECL
9from PIL import Image, ImageColor
10from qrcode.main import QRCode
12import report.helpers
14register = template.Library()
16QR_ECL_LEVEL_MAP = {
17 'L': ECL.ERROR_CORRECT_L,
18 'M': ECL.ERROR_CORRECT_M,
19 'Q': ECL.ERROR_CORRECT_Q,
20 'H': ECL.ERROR_CORRECT_H,
21}
24def image_data(img, fmt='PNG') -> str:
25 """Convert an image into HTML renderable data.
27 Returns a string ``data:image/FMT;base64,xxxxxxxxx`` which can be rendered to an <img> tag
28 """
29 return report.helpers.encode_image_base64(img, fmt)
32@register.simple_tag()
33def clean_barcode(data):
34 """Return a 'cleaned' string for encoding into a barcode / qrcode.
36 - This function sanitizes the data using nh3, and removes any malicious HTML content.
37 - Used to render raw barcode data into the rendered HTML templates
38 """
39 from InvenTree.helpers import strip_html_tags
41 cleaned_date = strip_html_tags(data)
43 # Remove back-tick character (prevent injection)
44 cleaned_date = cleaned_date.replace('`', '')
46 return mark_safe(cleaned_date)
49@register.simple_tag()
50def qrcode(data: str, **kwargs) -> str:
51 """Return a byte-encoded QR code image.
53 Arguments:
54 data: Data to encode
56 Keyword Arguments:
57 version (int): QR code version, (None to auto detect) (default = None)
58 error_correction (str): Error correction level (L: 7%, M: 15%, Q: 25%, H: 30%) (default = 'M')
59 box_size (int): pixel dimensions for one black square pixel in the QR code (default = 20)
60 border (int): count white QR square pixels around the qr code, needed as padding (default = 1)
61 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)
62 format (str): Image format (default = 'PNG')
63 fill_color (str): Fill color (default = "black")
64 back_color (str): Background color (default = "white")
66 Returns:
67 image (str): base64 encoded image data
69 """
70 data = str(data).strip()
72 if not data:
73 raise ValueError("No data provided to 'qrcode' template tag")
75 # Extract other arguments from kwargs
76 fill_color = kwargs.pop('fill_color', 'black')
77 back_color = kwargs.pop('back_color', 'white')
78 image_format = kwargs.pop('format', 'PNG')
79 optimize = kwargs.pop('optimize', 1)
81 # Construct QR code object
82 qr = QRCode(**{
83 'box_size': 20,
84 'border': 1,
85 'version': None,
86 **kwargs,
87 'error_correction': QR_ECL_LEVEL_MAP[kwargs.get('error_correction', 'M')],
88 })
89 qr.add_data(data, optimize=optimize)
90 qr.make(fit=False) # if version is None, it will automatically use fit=True
92 qri = qr.make_image(fill_color=fill_color, back_color=back_color)
94 # Render to byte-encoded image
95 return image_data(qri, fmt=image_format)
98@register.simple_tag()
99def barcode(data: str, barcode_class='code128', **kwargs) -> str:
100 """Render a 1D barcode.
102 Arguments:
103 data: Data to encode
104 barcode_class (str): The type of barcode to generate (default = 'code128')
106 Keyword Arguments:
107 format (str): Image format (default = 'PNG')
108 fill_color (str): Foreground color (default = 'black')
109 back_color (str): Background color (default = 'white')
110 scale (float): Scaling factor (default = 1)
112 Returns:
113 image (str): base64 encoded image data
114 """
115 data = str(data).strip()
117 if not data:
118 raise ValueError("No data provided to 'barcode' template tag")
120 constructor = python_barcode.get_barcode_class(barcode_class)
122 img_format = kwargs.pop('format', 'PNG')
124 data = str(data).zfill(constructor.digits)
126 writer = python_barcode_writer.ImageWriter
128 barcode_image = constructor(data, writer=writer())
130 image = barcode_image.render(writer_options=kwargs)
132 # Render to byte-encoded image
133 return image_data(image, fmt=img_format)
136@register.simple_tag()
137def datamatrix(data: str, **kwargs) -> str:
138 """Render a DataMatrix barcode.
140 Arguments:
141 data: Data to encode
143 Keyword Arguments:
144 fill_color (str): Foreground color (default = 'black')
145 back_color (str): Background color (default = 'white')
146 scale (float): Matrix scaling factor (default = 1)
147 border (int): Border width (default = 1)
149 Returns:
150 image (str): base64 encoded image data
151 """
152 from ppf.datamatrix.datamatrix import DataMatrix
154 data = str(data).strip()
156 if not data:
157 raise ValueError("No data provided to 'datamatrix' template tag")
159 dm = DataMatrix(data)
161 fill_color = kwargs.pop('fill_color', 'black')
162 back_color = kwargs.pop('back_color', 'white')
164 border = kwargs.pop('border', 1)
166 try:
167 border = int(border)
168 except Exception:
169 border = 1
171 border = max(0, border)
173 try:
174 fg = ImageColor.getcolor(fill_color, 'RGB')
175 except Exception:
176 fg = ImageColor.getcolor('black', 'RGB')
178 try:
179 bg = ImageColor.getcolor(back_color, 'RGB')
180 except Exception:
181 bg = ImageColor.getcolor('white', 'RGB')
183 scale = kwargs.pop('scale', 1)
185 height = len(dm.matrix) + 2 * border
186 width = len(dm.matrix[0]) + 2 * border
188 # Generate raw image from the matrix
189 img = Image.new('RGB', (width, height), color=bg)
191 for y, row in enumerate(dm.matrix):
192 for x, value in enumerate(row):
193 if value:
194 img.putpixel((x + border, y + border), fg)
196 if scale != 1:
197 img = img.resize((int(width * scale), int(height * scale)))
199 return image_data(img, fmt='PNG')