InvenTree Label Sheet 插件实战:单张标签纸网格排版 PDF 打印指南
【免费下载链接】InvenTreeOpen Source Inventory Management System项目地址: https://gitcode.com/GitHub_Trending/in/InvenTree
本篇指南围绕 InvenTree 内置的InvenTree Label Sheet Plugin(
InvenTreeLabelSheet)展开,讲解如何将多枚标签按规则网格自动排列到单张标准标签纸上并生成 PDF。读完本文,你将掌握该插件的启用机制、五个打印选项(页面大小、跳过标签、边框、横向、页边距)的实际含义与取值约束、DEBUG 模式的使用场景,以及从标签模板到网格排版再到 PDF 渲染的完整实现链路。
插件概览:什么是 Label Sheet 插件
InvenTree Label Sheet Plugin是 InvenTree 提供的内置标签打印插件,核心功能是"把多枚标签合并到一张更大的纸张上,按规则网格(regular grid)排布,最终输出一个 PDF 文件"。这一能力在以下场景中非常实用:
- 使用 A4、Letter 等标准整页标签纸(如预切 21×10 或 24×8 的空白标签纸)时,把一批零件、库存项(StockItem)的标签一次性铺满整张纸;
- 需要按 3 列 × 8 行这类固定网格打印标签时,避免"一枚标签一页"造成的纸张浪费;
- 需要跳过部分标签位置(例如标签纸首行已被手工占用)时进行偏移打印。
该插件通过 LabelPrintingMixin 标签打印混入类 提供自定义打印支持,是 InvenTree 标签打印插件体系中"合并多标签到单页"这一典型用法的内置实现。
从源码结构看,插件由 label_sheet.py 实现,位于plugin/builtin/labels/目录下,与默认 PDF 标签插件 inventree_label.py(InvenTreeLabel)、机器标签插件 inventree_machine.py 并列。
启用机制:强制启用的内置插件
Label Sheet 插件是一个强制(mandatory)插件,始终处于启用状态,无需手动安装或激活。在源码中这一特性体现为:
- 类声明:
class InvenTreeLabelSheetPlugin(LabelPrintingMixin, SettingsMixin, InvenTreePlugin); - 插件元数据:
NAME = 'InvenTreeLabelSheet'、TITLE = 'InvenTree Label Sheet Printer'、DESCRIPTION = 'Arrays multiple labels onto a single sheet'、VERSION = '1.0.1'; - 关键行为标记:
BLOCKING_PRINT = True,表示打印任务会同步阻塞前端服务器直至完成(不会卸载到后台 worker),保证 PDF 立即可下载。
因此你不需要任何安装步骤,只要 InvenTree 正常运行,该插件就会出现在可选的标签打印插件列表中。
插件设置:DEBUG 调试模式
插件通过SettingsMixin暴露了一个插件级设置项DEBUG:
SETTINGS = { 'DEBUG': { 'name': _('Debug mode'), 'description': _('Enable debug mode - returns raw HTML instead of PDF'), 'validator': bool, 'default': False, } }| 设置键 | 名称 | 默认值 | 说明 |
|---|---|---|---|
DEBUG | Debug mode | False | 开启后插件返回原始 HTML(labels.html)而非 PDF |
该模式仅供开发和测试使用,不应在生产环境中启用:
- 开启后,打印任务返回的是未经 PDF 渲染的原始 HTML 文档(见下节源码中
str2bool(self.get_setting('DEBUG'))分支); - 由于跳过 WeasyPrint 渲染,可能无法生成有效的 PDF 文件,不能用于实际标签打印;
- 典型用途:排查标签模板的渲染错误,例如检查模板变量未解析、CSS 未生效等模板层问题。
在 InvenTree 后台的插件设置界面中,该项显示为开关形式的 "Debug mode" 字段,其描述文字为 "Enable debug mode - returns raw HTML instead of PDF"。
使用方式:在打印对话框中选用插件
打印标签时,从插件列表中选择InvenTreeLabelSheet选项,插件便会将所选对象的标签排版到单张纸上,并生成包含全部标签的 PDF 文件供下载。
打印对话框为本次打印任务提供了一组额外的自定义选项,由插件内部的LabelPrintingOptionsSerializer定义。
五个打印选项详解
| 选项字段 | 类型 | 默认值 | 取值范围/约束 | 说明 |
|---|---|---|---|---|
page_size | ChoiceField | A4 | A4/A3/Legal/Letter | 标签页的纸张规格 |
skip | IntegerField | 0 | 最小 0,最大 500 | 打印标签页时跳过的标签数量(用于位置偏移) |
border | BooleanField | False | True/False | 是否为每枚标签打印 1px 黑色边框(便于裁剪定位) |
landscape | BooleanField | False | True/False | 是否以横向模式打印标签页(宽高互换) |
margin | IntegerField | 10 | 最小 0(单位 mm) | 页面四周的页边距,单位为毫米 |
各选项在打印对话框中的含义与界面截图一致:顶部为模板(Template)与打印插件(Printing Plugin)选择;下方依次是Page Size(页面大小,默认 A4)、Skip Labels(跳过标签,默认 0)、Border(边框,默认关闭)、Landscape(横向,默认关闭),底部为 Cancel / Print 按钮。
page_size的可选值来自 report.helpers.report_page_size_options(),与报表系统共用同一套页面规格定义;其实际尺寸(单位 mm)由 report.helpers.page_sizes() 给出:
| 页面代码 | 尺寸(mm) |
|---|---|
A4 | 210 × 297 |
A3 | 297 × 420 |
Legal | 215.9 × 355.6 |
Letter | 215.9 × 279.4 |
若传入未知的页面代码,page_size()会记录警告并默认回退到 A4(见 report/helpers.py)。
打印选项在任务中的传递
这些选项在print_labels方法中通过kwargs['printing_options']读取(序列化器被赋给类属性PrintingOptionsSerializer = LabelPrintingOptionsSerializer,由 LabelPrintingMixin 机制注入打印界面并随任务下发):
printing_options = kwargs['printing_options'] page_size_code = printing_options.get('page_size', 'A4') landscape = printing_options.get('landscape', False) border = printing_options.get('border', False) skip = int(printing_options.get('skip', 0)) margin = printing_options.get('margin', 10)实现原理:从网格计算到 PDF 渲染
InvenTreeLabelSheetPlugin对 LabelPrintingMixin 的入口方法print_labels进行了整体重写(源码注释明确说明 "we override the entire print_labels method"),以完成网格排版。整个过程可分为四个阶段。
阶段一:计算可用空间与网格行列数
# 获取页面尺寸(mm) page_size = report.helpers.page_size(page_size_code) page_width, page_height = page_size # 横向模式交换宽高 if landscape: page_width, page_height = page_height, page_width # 扣除页边距后的可用空间 available_width = page_width - (2 * margin) available_height = page_height - (2 * margin) # 按标签实际尺寸计算行列数(向下取整) n_cols = math.floor(available_width / label.width) n_rows = math.floor(available_height / label.height) n_cells = n_cols * n_rows if n_cells == 0: raise ValidationError(_('Label is too large for page size'))要点:
- 网格行列数完全由"标签模板的
width/height(mm)"与"扣除边距后的页面尺寸"决定,因此不必手动配置每页多少枚标签; - 当标签尺寸大于页面可用空间时(
n_cells == 0),插件会抛出ValidationError,提示 "Label is too large for page size"; - 从源码结构可以推断,调整
margin或切换landscape会直接改变可用空间,进而影响单页标签数量。
阶段二:跳过标签与分页切片
# 在标签列表前插入 skip 个空占位 items = [None] * skip + list(items) n_labels = len(items) # 文档级数据 document_data = { 'border': border, 'landscape': landscape, 'page_width': page_width, 'page_height': page_height, 'label_width': label.width, 'label_height': label.height, 'n_labels': n_labels, 'n_pages': math.ceil(n_labels / n_cells), 'n_cols': n_cols, 'n_rows': n_rows, 'margin': margin, }随后按每页n_cells枚进行切片并逐页渲染,同时更新打印进度:
while idx < n_labels: if page := self.print_page(label, items[idx : idx + n_cells], request, **document_data): pages.append(page) idx += n_cells output.progress += 1 output.save()若最终没有任何页面生成(例如所有标签渲染失败),则抛出ValidationError('No labels were generated')。
阶段三:单页网格渲染
print_page方法把一页内的标签渲染成一张 HTML 表格(class='label-sheet-table'):
- 按
n_rows×n_cols双重循环生成<tr>/<td>单元格; - 每个单元格的 CSS 类带有行号与列号(
label-sheet-row-{row}、label-sheet-col-{col}),用于后续精确绝对定位; - 被跳过的标签(
items[idx] is None)渲染为空单元格(label-sheet-cell-skip),不打印任何内容,从而实现偏移打印; - 正常标签通过
label.render_as_string(items[idx], request, insert_page_style=False)渲染为 HTML 片段——注意这里禁用了模板的@page样式,避免与整页的页面样式冲突; - 单枚标签渲染抛出的异常会被捕获并记录日志,同时该单元格渲染为红色错误块(
label-sheet-cell-error,background-color: #F00),便于在 DEBUG 阶段定位问题模板。
阶段四:整页 CSS 定位与 PDF 生成
wrap_pages方法将各页 HTML 拼接为单一文档,并在<head>中注入整页样式:
- 每个单元格的绝对定位偏移按"行号 × 标签高度"、"列号 × 标签宽度"逐行逐列生成(单位 mm):
.label-sheet-row-{row} { top: {row * label_height}mm; }.label-sheet-col-{col} { left: {col * label_width}mm; }
@page规则按page_width×page_height(mm)设定纸张大小,margin按选项设定;- 表格
page-break-after: always实现每页强制分页,border-spacing: 0mm、padding: 0mm保证网格对齐; - 开启
border选项时,单元格获得1px solid #000边框(关闭时0mm无边框)。
最后按 DEBUG 设置分派输出:
if str2bool(self.get_setting('DEBUG')): generated_file = ContentFile(html_data, 'labels.html') # 原始 HTML else: html = weasyprint.HTML(string=html_data, url_fetcher=InvenTreeURLFetcher()) document = html.render().write_pdf() generated_file = ContentFile(document, 'labels.pdf') # 渲染 PDF output.mark_complete(progress=n_labels, output=generated_file)正常模式下,HTML 通过WeasyPrint渲染为 PDF(使用InvenTreeURLFetcher作为 URL 抓取器,支持标签模板中引用的资源),产物以labels.pdf命名保存并标记任务完成。
与默认标签插件的对比
为了更清晰地理解 Label Sheet 插件的定位,这里将其与内置的默认 PDF 标签插件InvenTreeLabel进行对比:
| 维度 | InvenTreeLabel(默认) | InvenTreeLabelSheet(本文主题) |
|---|---|---|
| 实现文件 | inventree_label.py | label_sheet.py |
| 输出形态 | 每枚标签渲染后,通过PdfWriter拼接为纵向串联的单页序列PDF | 多枚标签按网格排列在单张纸上,生成整页 PDF |
| 实现方式 | 沿用混入类默认的print_label逐枚渲染流程,before_printing/get_generated_file拼接结果 | 整体重写print_labels,自行计算网格、分页与定位 |
| 打印选项 | 仅 DEBUG 设置,无额外打印选项 | DEBUG 设置 + 五个打印选项(页面大小/跳过/边框/横向/页边距) |
| 适用场景 | 单标签独立成页、标签尺寸较大 | 标准整页标签纸批量铺排 |
两者都实现LabelPrintingMixin与SettingsMixin,均提供 DEBUG 原始 HTML 输出模式,且都设置BLOCKING_PRINT = True同步返回打印结果。
总结
InvenTree Label Sheet 插件以"整页网格排版"的方式扩展了默认标签打印能力:用户只需选择模板与插件、按需调整五个打印选项,即可在 A4/Letter 等标准纸上批量打印标签。其核心价值在于免手动配置网格——行列数完全由标签尺寸、页面尺寸与页边距自动推导,同时通过skip选项解决标签纸偏移问题,通过 DEBUG 模式辅助模板排错。如需深入了解插件混入类的通用机制(如自定义打印选项序列化器、print_label与print_labels的分工、BLOCKING_PRINT的异步打印行为),可继续阅读 标签打印混入类文档,或直接阅读 label_sheet.py 源码。
【免费下载链接】InvenTreeOpen Source Inventory Management System项目地址: https://gitcode.com/GitHub_Trending/in/InvenTree
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考