Печатные формы

Печатные формы позволяют создавать PDF-документы на основе данных записей. Шаблон пишется на HTML + CSS + Jinja2, система подставляет данные и генерирует PDF.


Как работает генерация PDF

Платформа использует один из двух движков — в зависимости от того, что установлено на сервере:

Движок Приоритет Как определить
WeasyPrint Основной Заголовок ответа X-PDF-Engine: weasyprint
xhtml2pdf Фолбек Заголовок ответа X-PDF-Engine: xhtml2pdf

Увидеть заголовок: браузер → DevTools (F12) → Network → кликнуть на запрос PDF → Response Headers.


WeasyPrint — основной движок

Современный движок с полноценной поддержкой CSS. Верстается как обычный HTML — большинство привычных свойств работает предсказуемо.

Что работает

Что не работает

Правила верстки для 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 }.


xhtml2pdf — фолбек

Старый движок, работает без системных зависимостей. CSS поддержка ограничена примерно уровнем 2010 года. Используется автоматически если WeasyPrint не установлен.

Что работает надёжно

Что не работает

Правила верстки для xhtml2pdf

Ключевое правило — не использовать div для управления отступами и layout. Вместо этого:

<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