Печатные формы позволяют создавать PDF-документы на основе данных записей. Шаблон пишется на HTML + CSS + Jinja2, система подставляет данные и генерирует PDF.
Платформа использует один из двух движков — в зависимости от того, что установлено на сервере:
| Движок | Приоритет | Как определить |
|---|---|---|
| WeasyPrint | Основной | Заголовок ответа X-PDF-Engine: weasyprint |
| xhtml2pdf | Фолбек | Заголовок ответа X-PDF-Engine: xhtml2pdf |
Увидеть заголовок: браузер → DevTools (F12) → Network → кликнуть на запрос PDF → Response Headers.
Современный движок с полноценной поддержкой CSS. Верстается как обычный HTML — большинство привычных свойств работает предсказуемо.
margin, padding, border на любых элементах включая divdisplay: flex, flex-direction, justify-content, align-items, gap)box-sizing: border-box)border-collapse, border-spacing)@page — размер страницы, поля, колонтитулы через @framepage-break-before, page-break-after, page-break-inside: avoid@font-face с локальными шрифтамиposition: fixed — игнорируетсяposition: absolute — работает только внутри @page (колонтитулы), не в обычном потокеbox-shadow, text-shadow — не поддерживаютсяopacity — не работает@font-face с локальным файлом или подключённым шрифтом@media print — не нужен, WeasyPrint и так рендерит «для печати»<style>
@page {
size: A4 portrait;
margin: 2cm 1.5cm;
}
body {
font-family: 'DejaVu Sans', Arial, sans-serif;
font-size: 11pt;
margin: 0;
padding: 0;
}
h1 { font-size: 16pt; text-align: center; margin-bottom: 12pt; }
p { margin: 0 0 6pt 0; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ccc; padding: 4pt 8pt; }
th { background: #f5f5f5; font-weight: bold; }
</style>
Главное правило: не нужен блок .page { width: 210mm; padding: 20mm; } — поля задаются через @page { margin }, а размер страницы — через @page { size }.
Старый движок, работает без системных зависимостей. CSS поддержка ограничена примерно уровнем 2010 года. Используется автоматически если WeasyPrint не установлен.
margin-top/bottom/left/right и padding непосредственно на p, h1–h6, td, th, li@page { size, margin } — поля и размер страницыborder, border-collapse, padding на ячейкахfont-size, font-weight, color, text-align, line-height, text-decorationwidth, height на таблицах и ячейкахpage-break-before, page-break-afterdisplay: block/inline/nonemargin и padding на div — ведут себя непредсказуемо: margin на внешнем div игнорируется если внутри есть p или другой блок; padding на div применяется к дочерним элементам вместо самого divmargin: auto для горизонтального центрирования — не работаетbox-shadow, text-shadow — не поддерживаютсяposition: absolute/fixed — не работаетborder-radius — не поддерживаетсяbackground на div — часто игнорируетсяКлючевое правило — не использовать div для управления отступами и layout. Вместо этого:
@page { margin }margin-top/bottom на p, h1–h6 напрямуюtd { padding }<style>
@page {
size: A4 portrait;
margin: 2cm 1.5cm;
}
body {
font-family: 'DejaVu Sans', sans-serif;
font-size: 11pt;
}
h1 { font-size: 16pt; text-align: center; margin-top: 0; margin-bottom: 10pt; }
p { margin: 0 0 6pt 0; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ccc; padding: 4pt 8pt; }
th { background: #f5f5f5; font-weight: bold; }
</style>
Если неизвестно какой движок установлен на сервере — используйте этот подход: таблицы вместо flexbox, отступы только на текстовых элементах, поля через @page.
<style>
@page {
size: A4 portrait;
margin: 2cm 1.5cm;
}
body {
font-family: 'DejaVu Sans', Arial, sans-serif;
font-size: 11pt;
margin: 0;
padding: 0;
}
h1 { font-size: 16pt; text-align: center; margin-top: 0; margin-bottom: 10pt; }
h2 { font-size: 13pt; margin-top: 12pt; margin-bottom: 6pt; }
p { margin: 0 0 5pt 0; }
.row { width: 100%; border-collapse: collapse; }
.row td { padding: 0 0 4pt 0; vertical-align: top; }
.label { color: #666; width: 40%; }
table.data { width: 100%; border-collapse: collapse; margin-top: 8pt; }
table.data th,
table.data td { border: 1px solid #ccc; padding: 4pt 8pt; }
table.data th { background: #f5f5f5; font-weight: bold; text-align: left; }
</style>
<h1>{{ record.id }}</h1>
<p style="text-align: center; margin-bottom: 16pt;">
Дата: {{ record.created_at | format_datetime }}
</p>
{# Поля шапки через таблицу — работает в обоих движках #}
<table class="row">
<tbody>
<tr>
<td class="label">Клиент:</td>
<td>{{ client }}</td>
</tr>
<tr>
<td class="label">Сумма:</td>
<td>{{ amount | format_money }}</td>
</tr>
</tbody>
</table>
{# Табличная часть #}
{% if table.positions %}
<table class="data">
<thead>
<tr>
<th>№</th>
<th>Наименование</th>
<th>Кол-во</th>
<th>Сумма</th>
</tr>
</thead>
<tbody>
{% for row in table.positions %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ row.name }}</td>
<td>{{ row.qty }}</td>
<td>{{ row.amount | format_money }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
| Переменная | Что содержит |
|---|---|
record.id |
Числовой id записи |
record.status |
Статус записи |
record.created_at |
Дата создания |
record.posted_at |
Дата проведения |
имя_поля |
Значение поля шапки по системному имени |
имя_поля_raw |
Сырое значение (для reference — id, для файлов — список) |
table.имя_секции |
Список строк табличной части |
table.имя_секции[N].имя_колонки |
Значение ячейки |
| Синтаксис | Результат |
|---|---|
{{ value | format_date }} |
31.12.2026 |
{{ value | format_datetime }} |
31.12.2026 10:00 |
{{ value | format_money }} |
1 234,56 ₽ |
{{ value | format_number }} |
1 234,56 |
{{ value | nl2br }} |
Переносы строк → <br> |
{{ sum_col(table.positions, 'amount') }} |
Сумма колонки таблицы |
{{ coalesce(field_a, field_b, 'Не указано') }} |
Первое непустое значение |
{{ now() | format_date }} |
Текущая дата |
Платформа Уни · v0.9.9.8