跳至主要内容

Odoo 报告分页符不起作用 — 如何立即修复 2026

Odoo 报告忽略了 2019 年 17 月 18 日的分页符?使用完整的生产指南修复 CondPageBreak、t-call=web.external_layout 和 CSS 打印规则。

E
ECOSIRE Research and Development Team
|2026年5月4日6 分钟阅读1.2k 字数|

本文目前仅提供英文版本。翻译即将推出。

Odoo prints PDF reports with wkhtmltopdf, which ignores the CSS3 break-before: page. Put page-break-before: always on the element that starts the new page, keep it inside the report's page wrapper, and use page-break-inside: avoid on a table that fits on one page.

You add <div class="page-break"/> to an Odoo report. The PDF renders the content but ignores the break — content flows continuously across what should be page boundaries. Or the inverse: every section forces a new page, including ones that should fit. wkhtmltopdf and web.external_layout interact in subtle ways that make page breaks one of the most asked-about parts of Odoo report design on 17.0/18.0/19.0.

Quick Fix

Use the CSS page-break-before or page-break-after properties, not the <div class="page-break"/> shortcut:

<!-- Before each major section -->
<div style="page-break-before: always"/>

For conditional page breaks (only break if too little space remains), use page-break-inside:

<table style="page-break-inside: avoid">
    <!-- table contents stay on one page if possible -->
</table>

Why This Happens

wkhtmltopdf supports CSS print media queries and the page-break-* family of properties, but with quirks:

  1. <div class="page-break"/> is an Odoo convention — Odoo's web.external_layout styles map this to page-break-before: always. If your template wraps the page-break div incorrectly (outside the <div class="page"> wrapper), the style does not apply.
  2. page-break-before on the first element in a wrapper does nothing — the renderer is already at a page boundary.
  3. Tables and page breaks. page-break-inside: avoid on a table works only if the table fits on a page; tables larger than one page break wherever fits.
  4. Float and absolute positioning mess with page breaks. Floats can flow off the bottom of one page and reappear at the top of another in unexpected places.
  5. wkhtmltopdf's outdated WebKit does not support break-before: page (the modern CSS3 syntax) — only page-break-before: always (the legacy syntax).

Step-by-Step Diagnosis

1. Render to HTML to inspect:

http://localhost:8069/report/html/sale.report_saleorder/14?debug=1

In the browser DevTools, look at where page-break-* styles are computed. If the style is not always, the rule did not match.

2. Check wkhtmltopdf's print CSS support.

echo '<html><body><div style="page-break-before: always">Page 2</div></body></html>' > test.html
wkhtmltopdf --print-media-type test.html out.pdf

If the PDF has only one page, wkhtmltopdf is ignoring the break. Try --enable-internal-links and other flags.

3. Inspect Odoo's rendered template. Find where <div class="page-break"/> appears in the composed HTML. It should be a sibling of <div class="page">, not nested inside.

4. Check CSS specificity. A more specific rule may be overriding your page-break:

.row > div { page-break-before: avoid; }   /* defeats your "always" rule */

5. Test with a minimum reproduction. A QWeb template with three <div class="page"> wrappers, one page-break between each, and only "Hello" text in each. If this works, the issue is CSS in the rest of your template.

Permanent Fix

For unconditional breaks between sections:

<template id="report_priority_document">
    <t t-call="web.html_container">
        <t t-foreach="docs" t-as="o">
            <t t-call="web.external_layout">
                <div class="page">
                    <h2>Order: <span t-field="o.name"/></h2>

                    <!-- First-page content -->
                    <table>...</table>
                </div>
            </t>

            <!-- Force a page break -->
            <div style="page-break-before: always"/>

            <t t-call="web.external_layout">
                <div class="page">
                    <h2>Continuation: <span t-field="o.name"/></h2>
                    <!-- Second-page content -->
                </div>
            </t>
        </t>
    </t>
</template>

The page break sits between two <t t-call="web.external_layout"> blocks, each rendering its own page header/footer.

For conditional breaks (keep table together if possible):

<table class="table" style="page-break-inside: avoid">
    <thead>
        <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
    </thead>
    <tbody>
        <tr t-foreach="o.order_line" t-as="line">
            <td><span t-field="line.product_id.name"/></td>
            <td><span t-field="line.product_uom_qty"/></td>
            <td><span t-field="line.price_unit"/></td>
        </tr>
    </tbody>
</table>

page-break-inside: avoid keeps the table on one page if it fits.

For row-level breaks (break before this row):

<tr style="page-break-before: always" t-if="line.is_section_break">
    <td colspan="3"><b><span t-field="line.name"/></b></td>
</tr>

For headers that should repeat on each page:

<table>
    <thead style="display: table-header-group">
        <tr><th>Item</th><th>Qty</th></tr>
    </thead>
    <tbody>
        <!-- rows -->
    </tbody>
</table>

display: table-header-group makes the <thead> repeat on every page wkhtmltopdf renders for the table.

For complex flows, use <div class="page"> per logical page. Odoo's external_layout wrapping each <div class="page"> gives you per-page header/footer plus implicit page boundaries. This is the most reliable structure.

How to Prevent It

  • Always test with multi-page data. Reports with one item fit on one page; you only see page-break bugs when there are 50 items. QA with realistic record counts.
  • Avoid <div class="page-break"/> shorthand. Use explicit style="page-break-before: always". The shorthand depends on Odoo's CSS, which can change between versions.
  • Use legacy page-break-* syntax. wkhtmltopdf 0.12.6 does not support break-before: page (CSS3). Stick with page-break-before: always.
  • One <div class="page"> per logical page. If your report has an order summary page and an order detail page, two <div class="page"> wrappers is the cleanest structure.
  • CI test page count. A test that generates a PDF and asserts page count using pdfinfo or PyPDF catches break regressions.
  • Print-stylesheet review. When inheriting web.external_layout styles, ensure your CSS does not accidentally set page-break-before: avoid on elements that should break.

Frequently Asked Questions

Can I number pages in an Odoo report?

Yes, in web.external_layout footer:

<div class="footer">
    Page <span class="page"/> of <span class="topage"/>
</div>

wkhtmltopdf replaces class="page" with the current page and class="topage" with the total at PDF generation time. Standard wkhtmltopdf magic.

Why does page-break-after: always get ignored on the last element?

Because there is no content after it to push to a new page. The renderer correctly drops the break. If you need an empty trailing page (rare), add a hidden div with content after the break.

Can I use modern CSS like break-before: page?

Not safely. wkhtmltopdf 0.12.6's WebKit is from 2014 and does not support the CSS3 fragmentation properties consistently. Use page-break-* syntax for predictable behaviour.

My table breaks in the middle of a row. How do I fix it?

Add style="page-break-inside: avoid" to the <tr>. If rows are too tall to ever fit on a single page, this falls back to row-internal breaks anyway. For uniformity, set the rule on tr globally in your stylesheet:

@media print { tr { page-break-inside: avoid; } }

Can I force a specific page count, like always 3 pages?

Not directly through CSS. The closest pattern is to author three explicit <div class="page"> wrappers and pad them with empty space if needed. For form-style documents (purchase orders, invoices) where you need a fixed structure, this manual approach is more reliable than computed.

How do I add a watermark across all pages?

Use CSS position: fixed with @media print:

.watermark {
    position: fixed;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%) rotate(-30deg);
    font-size: 4em; opacity: 0.1; color: red;
    z-index: 9999;
}

wkhtmltopdf renders fixed-position elements once per page, exactly the watermark behaviour you want. Inline this CSS in the report's QWeb to keep it self-contained.

Page numbering shows wrong total — why?

<span class="topage"/> is replaced with the total page count after wkhtmltopdf finishes laying out. If your template includes table-of-contents-style placeholders that change page count after replacement, totals drift. Run with --javascript-delay 1000 to give wkhtmltopdf time to settle the layout before computing totals.


Need help with a tricky Odoo error? ECOSIRE's Odoo experts have shipped 215+ modules — get expert help.

分享:
E

作者

ECOSIRE Research and Development Team

技术写作

ECOSIRE 技术写作团队的内容涵盖 Odoo ERP、Shopify 电子商务、AI 智能体、Power BI 分析、GoHighLevel 自动化以及企业软件最佳实践。我们的指南帮助企业做出明智的技术决策。

ECOSIRE

使用 Odoo ERP 转变您的业务

专家 Odoo 实施、定制和支持可简化您的运营。