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

1"""Template tags for rendering various barcodes.""" 

2 

3from django import template 

4from django.utils.safestring import mark_safe 

5 

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 

11 

12import report.helpers 

13 

14register = template.Library() 

15 

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} 

22 

23 

24def image_data(img, fmt='PNG') -> str: 

25 """Convert an image into HTML renderable data. 

26 

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) 

30 

31 

32@register.simple_tag() 

33def clean_barcode(data): 

34 """Return a 'cleaned' string for encoding into a barcode / qrcode. 

35 

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 

40 

41 cleaned_date = strip_html_tags(data) 

42 

43 # Remove back-tick character (prevent injection) 

44 cleaned_date = cleaned_date.replace('`', '') 

45 

46 return mark_safe(cleaned_date) 

47 

48 

49@register.simple_tag() 

50def qrcode(data: str, **kwargs) -> str: 

51 """Return a byte-encoded QR code image. 

52 

53 Arguments: 

54 data: Data to encode 

55 

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") 

65 

66 Returns: 

67 image (str): base64 encoded image data 

68 

69 """ 

70 data = str(data).strip() 

71 

72 if not data: 

73 raise ValueError("No data provided to 'qrcode' template tag") 

74 

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) 

80 

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 

91 

92 qri = qr.make_image(fill_color=fill_color, back_color=back_color) 

93 

94 # Render to byte-encoded image 

95 return image_data(qri, fmt=image_format) 

96 

97 

98@register.simple_tag() 

99def barcode(data: str, barcode_class='code128', **kwargs) -> str: 

100 """Render a 1D barcode. 

101 

102 Arguments: 

103 data: Data to encode 

104 barcode_class (str): The type of barcode to generate (default = 'code128') 

105 

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) 

111 

112 Returns: 

113 image (str): base64 encoded image data 

114 """ 

115 data = str(data).strip() 

116 

117 if not data: 

118 raise ValueError("No data provided to 'barcode' template tag") 

119 

120 constructor = python_barcode.get_barcode_class(barcode_class) 

121 

122 img_format = kwargs.pop('format', 'PNG') 

123 

124 data = str(data).zfill(constructor.digits) 

125 

126 writer = python_barcode_writer.ImageWriter 

127 

128 barcode_image = constructor(data, writer=writer()) 

129 

130 image = barcode_image.render(writer_options=kwargs) 

131 

132 # Render to byte-encoded image 

133 return image_data(image, fmt=img_format) 

134 

135 

136@register.simple_tag() 

137def datamatrix(data: str, **kwargs) -> str: 

138 """Render a DataMatrix barcode. 

139 

140 Arguments: 

141 data: Data to encode 

142 

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) 

148 

149 Returns: 

150 image (str): base64 encoded image data 

151 """ 

152 from ppf.datamatrix.datamatrix import DataMatrix 

153 

154 data = str(data).strip() 

155 

156 if not data: 

157 raise ValueError("No data provided to 'datamatrix' template tag") 

158 

159 dm = DataMatrix(data) 

160 

161 fill_color = kwargs.pop('fill_color', 'black') 

162 back_color = kwargs.pop('back_color', 'white') 

163 

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

165 

166 try: 

167 border = int(border) 

168 except Exception: 

169 border = 1 

170 

171 border = max(0, border) 

172 

173 try: 

174 fg = ImageColor.getcolor(fill_color, 'RGB') 

175 except Exception: 

176 fg = ImageColor.getcolor('black', 'RGB') 

177 

178 try: 

179 bg = ImageColor.getcolor(back_color, 'RGB') 

180 except Exception: 

181 bg = ImageColor.getcolor('white', 'RGB') 

182 

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

184 

185 height = len(dm.matrix) + 2 * border 

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

187 

188 # Generate raw image from the matrix 

189 img = Image.new('RGB', (width, height), color=bg) 

190 

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) 

195 

196 if scale != 1: 

197 img = img.resize((int(width * scale), int(height * scale))) 

198 

199 return image_data(img, fmt='PNG')