docx-templates

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Docx-Templates Skill

Docx-Templates 技能文档

Generate Word documents from templates using Jinja2 syntax. Create professional reports, contracts, and documents with dynamic content, loops, conditionals, and tables.
使用Jinja2语法从模板生成Word文档。可创建包含动态内容、循环、条件判断和表格的专业报告、合同及各类文档。

Quick Start

快速开始

bash
undefined
bash
undefined

Install docxtpl

安装docxtpl

pip install docxtpl
pip install docxtpl

Install with image support

安装带图片支持的版本

pip install docxtpl Pillow
pip install docxtpl Pillow

For Excel data sources

若需Excel数据源支持

pip install docxtpl openpyxl pandas
pip install docxtpl openpyxl pandas

Verify installation

验证安装

python -c "from docxtpl import DocxTemplate; print('docxtpl ready!')"
undefined
python -c "from docxtpl import DocxTemplate; print('docxtpl ready!')"
undefined

When to Use This Skill

适用场景

USE when:
  • Generating documents from templates with dynamic data
  • Creating mail merge documents from data sources
  • Building reports with loops and conditional sections
  • Need to maintain consistent formatting across generated documents
  • Generating contracts, invoices, letters from templates
  • Processing batch document generation from databases or spreadsheets
  • Templates need professional formatting preserved
  • Non-technical users maintain template design
DON'T USE when:
  • Building documents programmatically from scratch (use python-docx)
  • Need complex document manipulation beyond template filling
  • PDF output is the final format (generate docx then convert)
  • Templates require complex macros or VBA
  • Real-time collaborative editing needed
适合使用的场景:
  • 从模板生成带动态数据的文档
  • 从数据源创建邮件合并文档
  • 构建包含循环和条件区块的报告
  • 需要在生成的文档中保持统一格式
  • 从模板生成合同、发票、信件
  • 从数据库或电子表格批量生成文档
  • 需要保留模板的专业格式
  • 非技术人员可维护模板设计
不适合使用的场景:
  • 从零开始程序化构建文档(请使用python-docx)
  • 需要模板填充之外的复杂文档操作
  • 最终输出格式为PDF(先生成docx再转换)
  • 模板需要复杂宏或VBA
  • 需要实时协作编辑

Prerequisites

前置依赖

bash
undefined
bash
undefined

Core installation

核心安装

pip install docxtpl>=0.16.0
pip install docxtpl>=0.16.0

For image handling

图片处理支持

pip install docxtpl Pillow>=9.0.0
pip install docxtpl Pillow>=9.0.0

For data processing

数据处理支持

pip install docxtpl pandas>=2.0.0 openpyxl>=3.1.0
pip install docxtpl pandas>=2.0.0 openpyxl>=3.1.0

For database connections

数据库连接支持

pip install docxtpl sqlalchemy psycopg2-binary
pip install docxtpl sqlalchemy psycopg2-binary

All dependencies

安装所有依赖

pip install docxtpl Pillow pandas openpyxl sqlalchemy
undefined
pip install docxtpl Pillow pandas openpyxl sqlalchemy
undefined

Verify Installation

验证安装

python
from docxtpl import DocxTemplate, InlineImage
from docx.shared import Mm, Inches

print("docxtpl installed successfully!")
python
from docxtpl import DocxTemplate, InlineImage
from docx.shared import Mm, Inches

print("docxtpl 安装成功!")

Quick test

快速测试

template = DocxTemplate("template.docx")

template = DocxTemplate("template.docx")

context = {"name": "World"}

context = {"name": "World"}

template.render(context)

template.render(context)

template.save("output.docx")

template.save("output.docx")

undefined
undefined

Core Capabilities

核心功能

1. Basic Template Rendering

1. 基础模板渲染

Simple Variable Substitution:
python
"""
Basic template rendering with variable substitution.
"""
from docxtpl import DocxTemplate
from typing import Dict, Any
from pathlib import Path

def render_simple_template(
    template_path: str,
    output_path: str,
    context: Dict[str, Any]
) -> None:
    """
    Render a template with simple variable substitution.

    Args:
        template_path: Path to .docx template
        output_path: Path for output document
        context: Dictionary of values to substitute

    Template syntax:
        {{ variable_name }} - Simple variable
        {{ object.property }} - Nested property
    """
    # Load template
    template = DocxTemplate(template_path)

    # Render with context
    template.render(context)

    # Save output
    template.save(output_path)
    print(f"Document saved to {output_path}")


def create_sample_letter(output_path: str) -> None:
    """Create a sample letter using template rendering."""

    # First, create a simple template (normally you'd have this prepared)
    # Template content would have: {{ recipient_name }}, {{ date }}, etc.

    context = {
        "recipient_name": "John Smith",
        "recipient_title": "Director of Operations",
        "company_name": "Acme Corporation",
        "street_address": "123 Business Ave",
        "city_state_zip": "New York, NY 10001",
        "date": "January 17, 2026",
        "subject": "Project Proposal",
        "salutation": "Dear Mr. Smith",
        "body_paragraph_1": """
            We are pleased to submit our proposal for the infrastructure
            upgrade project. Our team has extensive experience in similar
            projects and we are confident we can deliver exceptional results.
        """.strip(),
        "body_paragraph_2": """
            The attached documents outline our approach, timeline, and
            budget estimates. We would welcome the opportunity to discuss
            this proposal at your convenience.
        """.strip(),
        "closing": "Sincerely",
        "sender_name": "Jane Doe",
        "sender_title": "Project Manager"
    }

    # Render template
    render_simple_template("letter_template.docx", output_path, context)
简单变量替换:
python
"""
使用变量替换进行基础模板渲染。
"""
from docxtpl import DocxTemplate
from typing import Dict, Any
from pathlib import Path

def render_simple_template(
    template_path: str,
    output_path: str,
    context: Dict[str, Any]
) -> None:
    """
    使用简单变量替换渲染模板。

    参数:
        template_path: .docx模板文件路径
        output_path: 输出文档路径
        context: 用于替换的键值对字典

    模板语法:
        {{ variable_name }} - 简单变量
        {{ object.property }} - 嵌套属性
    """
    # 加载模板
    template = DocxTemplate(template_path)

    # 使用上下文渲染
    template.render(context)

    # 保存输出
    template.save(output_path)
    print(f"文档已保存至 {output_path}")


def create_sample_letter(output_path: str) -> None:
    """使用模板渲染创建示例信件。"""

    # 首先创建简单模板(通常模板是预先准备好的)
    # 模板内容应包含: {{ recipient_name }}, {{ date }} 等变量

    context = {
        "recipient_name": "约翰·史密斯",
        "recipient_title": "运营总监",
        "company_name": "Acme公司",
        "street_address": "商业大道123号",
        "city_state_zip": "纽约州纽约市 10001",
        "date": "2026年1月17日",
        "subject": "项目提案",
        "salutation": "尊敬的史密斯先生:",
        "body_paragraph_1": """
            我们很高兴提交基础设施升级项目的提案。我们的团队在类似项目中拥有丰富经验,有信心交付卓越成果。
        """.strip(),
        "body_paragraph_2": """
            附件文档概述了我们的方法、时间线和预算估算。我们期待在您方便时讨论此提案。
        """.strip(),
        "closing": "此致",
        "sender_name": "简·多伊",
        "sender_title": "项目经理"
    }

    # 渲染模板
    render_simple_template("letter_template.docx", output_path, context)

Example context for a business report

业务报告示例上下文

report_context = { "report_title": "Q4 2025 Performance Report", "prepared_by": "Analytics Team", "date": "January 15, 2026", "executive_summary": "Strong performance across all metrics...", "total_revenue": "$2,450,000", "growth_rate": "15.3%", "customer_count": "1,250", "key_achievements": [ "Launched new product line", "Expanded to 3 new markets", "Achieved ISO certification" ] }

**Nested Object Access:**
```python
"""
Access nested objects and complex data structures in templates.
"""
from docxtpl import DocxTemplate
from dataclasses import dataclass, asdict
from typing import List, Dict, Optional
from datetime import date

@dataclass
class Address:
    """Address data structure."""
    street: str
    city: str
    state: str
    zip_code: str
    country: str = "USA"

    @property
    def full_address(self) -> str:
        return f"{self.street}\n{self.city}, {self.state} {self.zip_code}"


@dataclass
class Contact:
    """Contact information."""
    name: str
    email: str
    phone: str
    title: Optional[str] = None


@dataclass
class Company:
    """Company data structure."""
    name: str
    address: Address
    contacts: List[Contact]
    industry: str
    website: str

    def to_dict(self) -> Dict:
        """Convert to dictionary for template rendering."""
        return {
            "name": self.name,
            "address": asdict(self.address),
            "contacts": [asdict(c) for c in self.contacts],
            "industry": self.industry,
            "website": self.website
        }


def render_with_nested_data(
    template_path: str,
    output_path: str,
    company: Company
) -> None:
    """
    Render template with nested data structures.

    Template syntax for nested access:
        {{ company.name }}
        {{ company.address.street }}
        {{ company.contacts[0].email }}
    """
    template = DocxTemplate(template_path)

    context = {
        "company": company.to_dict(),
        "generated_date": date.today().strftime("%B %d, %Y")
    }

    template.render(context)
    template.save(output_path)
report_context = { "report_title": "2025年第四季度业绩报告", "prepared_by": "分析团队", "date": "2026年1月15日", "executive_summary": "所有指标均表现强劲...", "total_revenue": "2,450,000美元", "growth_rate": "15.3%", "customer_count": "1,250", "key_achievements": [ "推出新产品线", "拓展至3个新市场", "获得ISO认证" ] }

**嵌套对象访问:**
```python
"""
在模板中访问嵌套对象和复杂数据结构。
"""
from docxtpl import DocxTemplate
from dataclasses import dataclass, asdict
from typing import List, Dict, Optional
from datetime import date

@dataclass
class Address:
    """地址数据结构。"""
    street: str
    city: str
    state: str
    zip_code: str
    country: str = "美国"

    @property
    def full_address(self) -> str:
        return f"{self.street}\n{self.city}, {self.state} {self.zip_code}"


@dataclass
class Contact:
    """联系信息。"""
    name: str
    email: str
    phone: str
    title: Optional[str] = None


@dataclass
class Company:
    """公司数据结构。"""
    name: str
    address: Address
    contacts: List[Contact]
    industry: str
    website: str

    def to_dict(self) -> Dict:
        """转换为字典用于模板渲染。"""
        return {
            "name": self.name,
            "address": asdict(self.address),
            "contacts": [asdict(c) for c in self.contacts],
            "industry": self.industry,
            "website": self.website
        }


def render_with_nested_data(
    template_path: str,
    output_path: str,
    company: Company
) -> None:
    """
    使用嵌套数据结构渲染模板。

    嵌套访问的模板语法:
        {{ company.name }}
        {{ company.address.street }}
        {{ company.contacts[0].email }}
    """
    template = DocxTemplate(template_path)

    context = {
        "company": company.to_dict(),
        "generated_date": date.today().strftime("%Y年%m月%d日")
    }

    template.render(context)
    template.save(output_path)

Example usage

示例用法

company = Company( name="TechCorp Industries", address=Address( street="456 Innovation Blvd", city="San Francisco", state="CA", zip_code="94105" ), contacts=[ Contact( name="Alice Johnson", email="alice@techcorp.com", phone="555-0101", title="CEO" ), Contact( name="Bob Williams", email="bob@techcorp.com", phone="555-0102", title="CTO" ) ], industry="Technology", website="https://techcorp.com" )
company = Company( name="TechCorp工业公司", address=Address( street="创新大道456号", city="旧金山", state="CA", zip_code="94105" ), contacts=[ Contact( name="爱丽丝·约翰逊", email="alice@techcorp.com", phone="555-0101", title="首席执行官" ), Contact( name="鲍勃·威廉姆斯", email="bob@techcorp.com", phone="555-0102", title="首席技术官" ) ], industry="科技行业", website="https://techcorp.com" )

render_with_nested_data("company_profile.docx", "techcorp_profile.docx", company)

render_with_nested_data("company_profile.docx", "techcorp_profile.docx", company)

undefined
undefined

2. Loops and Iterations

2. 循环与迭代

Rendering Lists and Tables:
python
"""
Use loops to render lists, tables, and repeated content.
"""
from docxtpl import DocxTemplate
from typing import List, Dict, Any
from dataclasses import dataclass
from decimal import Decimal

@dataclass
class LineItem:
    """Invoice line item."""
    description: str
    quantity: int
    unit_price: Decimal
    discount: Decimal = Decimal("0")

    @property
    def subtotal(self) -> Decimal:
        return self.quantity * self.unit_price * (1 - self.discount / 100)


@dataclass
class Invoice:
    """Invoice data structure."""
    invoice_number: str
    date: str
    due_date: str
    client_name: str
    client_address: str
    items: List[LineItem]
    tax_rate: Decimal = Decimal("8.5")
    notes: str = ""

    @property
    def subtotal(self) -> Decimal:
        return sum(item.subtotal for item in self.items)

    @property
    def tax_amount(self) -> Decimal:
        return self.subtotal * self.tax_rate / 100

    @property
    def total(self) -> Decimal:
        return self.subtotal + self.tax_amount

    def to_context(self) -> Dict[str, Any]:
        """Convert to template context."""
        return {
            "invoice_number": self.invoice_number,
            "date": self.date,
            "due_date": self.due_date,
            "client_name": self.client_name,
            "client_address": self.client_address,
            "items": [
                {
                    "description": item.description,
                    "quantity": item.quantity,
                    "unit_price": f"${item.unit_price:.2f}",
                    "discount": f"{item.discount}%" if item.discount else "",
                    "subtotal": f"${item.subtotal:.2f}"
                }
                for item in self.items
            ],
            "subtotal": f"${self.subtotal:.2f}",
            "tax_rate": f"{self.tax_rate}%",
            "tax_amount": f"${self.tax_amount:.2f}",
            "total": f"${self.total:.2f}",
            "notes": self.notes
        }


def render_invoice(
    template_path: str,
    output_path: str,
    invoice: Invoice
) -> None:
    """
    Render invoice template with line items.

    Template syntax for loops:
        {%tr for item in items %}
        {{ item.description }} | {{ item.quantity }} | {{ item.unit_price }}
        {%tr endfor %}

    Note: {%tr %} is for table rows, {%p %} for paragraphs
    """
    template = DocxTemplate(template_path)
    context = invoice.to_context()
    template.render(context)
    template.save(output_path)


def render_list_document(
    template_path: str,
    output_path: str,
    items: List[str],
    title: str
) -> None:
    """
    Render document with bullet list.

    Template syntax for paragraph loops:
        {%p for item in items %}
        - {{ item }}
        {%p endfor %}
    """
    template = DocxTemplate(template_path)

    context = {
        "title": title,
        "items": items,
        "item_count": len(items)
    }

    template.render(context)
    template.save(output_path)
渲染列表和表格:
python
"""
使用循环渲染列表、表格和重复内容。
"""
from docxtpl import DocxTemplate
from typing import List, Dict, Any
from dataclasses import dataclass
from decimal import Decimal

@dataclass
class LineItem:
    """发票行项目。"""
    description: str
    quantity: int
    unit_price: Decimal
    discount: Decimal = Decimal("0")

    @property
    def subtotal(self) -> Decimal:
        return self.quantity * self.unit_price * (1 - self.discount / 100)


@dataclass
class Invoice:
    """发票数据结构。"""
    invoice_number: str
    date: str
    due_date: str
    client_name: str
    client_address: str
    items: List[LineItem]
    tax_rate: Decimal = Decimal("8.5")
    notes: str = ""

    @property
    def subtotal(self) -> Decimal:
        return sum(item.subtotal for item in self.items)

    @property
    def tax_amount(self) -> Decimal:
        return self.subtotal * self.tax_rate / 100

    @property
    def total(self) -> Decimal:
        return self.subtotal + self.tax_amount

    def to_context(self) -> Dict[str, Any]:
        """转换为模板上下文。"""
        return {
            "invoice_number": self.invoice_number,
            "date": self.date,
            "due_date": self.due_date,
            "client_name": self.client_name,
            "client_address": self.client_address,
            "items": [
                {
                    "description": item.description,
                    "quantity": item.quantity,
                    "unit_price": f"${item.unit_price:.2f}",
                    "discount": f"{item.discount}%" if item.discount else "",
                    "subtotal": f"${item.subtotal:.2f}"
                }
                for item in self.items
            ],
            "subtotal": f"${self.subtotal:.2f}",
            "tax_rate": f"{self.tax_rate}%",
            "tax_amount": f"${self.tax_amount:.2f}",
            "total": f"${self.total:.2f}",
            "notes": self.notes
        }


def render_invoice(
    template_path: str,
    output_path: str,
    invoice: Invoice
) -> None:
    """
    使用行项目渲染发票模板。

    循环的模板语法:
        {%tr for item in items %}
        {{ item.description }} | {{ item.quantity }} | {{ item.unit_price }}
        {%tr endfor %}

    注意: {%tr %} 用于表格行, {%p %} 用于段落
    """
    template = DocxTemplate(template_path)
    context = invoice.to_context()
    template.render(context)
    template.save(output_path)


def render_list_document(
    template_path: str,
    output_path: str,
    items: List[str],
    title: str
) -> None:
    """
    渲染包含项目符号列表的文档。

    段落循环的模板语法:
        {%p for item in items %}
        - {{ item }}
        {%p endfor %}
    """
    template = DocxTemplate(template_path)

    context = {
        "title": title,
        "items": items,
        "item_count": len(items)
    }

    template.render(context)
    template.save(output_path)

Example: Create invoice

示例: 创建发票

invoice = Invoice( invoice_number="INV-2026-0042", date="January 17, 2026", due_date="February 16, 2026", client_name="Acme Corp", client_address="123 Main St\nNew York, NY 10001", items=[ LineItem("Consulting Services", 40, Decimal("150.00")), LineItem("Software License", 1, Decimal("500.00")), LineItem("Training Session", 8, Decimal("100.00"), Decimal("10")), ], notes="Payment due within 30 days. Thank you for your business!" )
invoice = Invoice( invoice_number="INV-2026-0042", date="2026年1月17日", due_date="2026年2月16日", client_name="Acme公司", client_address="主街123号\n纽约州纽约市 10001", items=[ LineItem("咨询服务", 40, Decimal("150.00")), LineItem("软件许可证", 1, Decimal("500.00")), LineItem("培训课程", 8, Decimal("100.00"), Decimal("10")), ], notes="请在30天内付款。感谢您的合作!" )

render_invoice("invoice_template.docx", "invoice_output.docx", invoice)

render_invoice("invoice_template.docx", "invoice_output.docx", invoice)

def render_nested_loops( template_path: str, output_path: str, departments: List[Dict] ) -> None: """ Render template with nested loops.
Template syntax:
    {%p for dept in departments %}
    Department: {{ dept.name }}
    {%p for emp in dept.employees %}
    - {{ emp.name }} ({{ emp.role }})
    {%p endfor %}
    {%p endfor %}
"""
template = DocxTemplate(template_path)

context = {
    "company_name": "TechCorp",
    "departments": departments
}

template.render(context)
template.save(output_path)
def render_nested_loops( template_path: str, output_path: str, departments: List[Dict] ) -> None: """ 渲染包含嵌套循环的模板。
模板语法:
    {%p for dept in departments %}
    部门: {{ dept.name }}
    {%p for emp in dept.employees %}
    - {{ emp.name }} ({{ emp.role }})
    {%p endfor %}
    {%p endfor %}
"""
template = DocxTemplate(template_path)

context = {
    "company_name": "TechCorp",
    "departments": departments
}

template.render(context)
template.save(output_path)

Example data for nested loops

嵌套循环示例数据

departments = [ { "name": "Engineering", "head": "Alice Johnson", "employees": [ {"name": "Bob Smith", "role": "Senior Developer"}, {"name": "Carol White", "role": "Developer"}, {"name": "David Brown", "role": "QA Engineer"} ] }, { "name": "Marketing", "head": "Eve Davis", "employees": [ {"name": "Frank Miller", "role": "Marketing Manager"}, {"name": "Grace Lee", "role": "Content Writer"} ] } ]
undefined
departments = [ { "name": "工程部", "head": "爱丽丝·约翰逊", "employees": [ {"name": "鲍勃·史密斯", "role": "高级开发工程师"}, {"name": "卡罗尔·怀特", "role": "开发工程师"}, {"name": "大卫·布朗", "role": "QA工程师"} ] }, { "name": "市场部", "head": "伊芙·戴维斯", "employees": [ {"name": "弗兰克·米勒", "role": "市场经理"}, {"name": "格蕾丝·李", "role": "内容撰稿人"} ] } ]
undefined

3. Conditional Content

3. 条件内容

If-Else Logic in Templates:
python
"""
Use conditionals to include or exclude content based on data.
"""
from docxtpl import DocxTemplate
from typing import Dict, Any, Optional
from dataclasses import dataclass
from enum import Enum

class ContractType(Enum):
    FULL_TIME = "full_time"
    PART_TIME = "part_time"
    CONTRACTOR = "contractor"


class ConfidentialityLevel(Enum):
    STANDARD = "standard"
    HIGH = "high"
    RESTRICTED = "restricted"


@dataclass
class EmployeeContract:
    """Employee contract data."""
    employee_name: str
    position: str
    department: str
    start_date: str
    contract_type: ContractType
    salary: float
    bonus_eligible: bool
    stock_options: Optional[int] = None
    confidentiality_level: ConfidentialityLevel = ConfidentialityLevel.STANDARD
    probation_period_months: int = 3
    remote_work_allowed: bool = False
    relocation_package: bool = False

    def to_context(self) -> Dict[str, Any]:
        """Convert to template context with conditional flags."""
        return {
            "employee_name": self.employee_name,
            "position": self.position,
            "department": self.department,
            "start_date": self.start_date,
            "salary": f"${self.salary:,.2f}",

            # Contract type flags for conditionals
            "is_full_time": self.contract_type == ContractType.FULL_TIME,
            "is_part_time": self.contract_type == ContractType.PART_TIME,
            "is_contractor": self.contract_type == ContractType.CONTRACTOR,
            "contract_type_display": self.contract_type.value.replace("_", " ").title(),

            # Benefit flags
            "bonus_eligible": self.bonus_eligible,
            "has_stock_options": self.stock_options is not None,
            "stock_options": self.stock_options,

            # Additional terms
            "confidentiality_level": self.confidentiality_level.value,
            "is_high_confidentiality": self.confidentiality_level in [
                ConfidentialityLevel.HIGH,
                ConfidentialityLevel.RESTRICTED
            ],
            "probation_period_months": self.probation_period_months,
            "remote_work_allowed": self.remote_work_allowed,
            "relocation_package": self.relocation_package
        }


def render_contract(
    template_path: str,
    output_path: str,
    contract: EmployeeContract
) -> None:
    """
    Render contract with conditional sections.

    Template syntax for conditionals:
        {% if is_full_time %}
        Full-time benefits section...
        {% endif %}

        {% if bonus_eligible %}
        Bonus clause...
        {% else %}
        Standard compensation only.
        {% endif %}

        {% if is_high_confidentiality %}
        Additional NDA requirements...
        {% endif %}
    """
    template = DocxTemplate(template_path)
    context = contract.to_context()
    template.render(context)
    template.save(output_path)


def render_with_conditions(
    template_path: str,
    output_path: str,
    data: Dict[str, Any]
) -> None:
    """
    Render template with various conditional patterns.

    Supported conditional patterns:
        {% if condition %} ... {% endif %}
        {% if condition %} ... {% else %} ... {% endif %}
        {% if condition %} ... {% elif other %} ... {% else %} ... {% endif %}
        {% if value > 100 %} ... {% endif %}
        {% if value in list %} ... {% endif %}
    """
    template = DocxTemplate(template_path)
    template.render(data)
    template.save(output_path)
模板中的If-Else逻辑:
python
"""
使用条件判断根据数据包含或排除内容。
"""
from docxtpl import DocxTemplate
from typing import Dict, Any, Optional
from dataclasses import dataclass
from enum import Enum

class ContractType(Enum):
    FULL_TIME = "full_time"
    PART_TIME = "part_time"
    CONTRACTOR = "contractor"


class ConfidentialityLevel(Enum):
    STANDARD = "standard"
    HIGH = "high"
    RESTRICTED = "restricted"


@dataclass
class EmployeeContract:
    """员工合同数据。"""
    employee_name: str
    position: str
    department: str
    start_date: str
    contract_type: ContractType
    salary: float
    bonus_eligible: bool
    stock_options: Optional[int] = None
    confidentiality_level: ConfidentialityLevel = ConfidentialityLevel.STANDARD
    probation_period_months: int = 3
    remote_work_allowed: bool = False
    relocation_package: bool = False

    def to_context(self) -> Dict[str, Any]:
        """转换为包含条件标志的模板上下文。"""
        return {
            "employee_name": self.employee_name,
            "position": self.position,
            "department": self.department,
            "start_date": self.start_date,
            "salary": f"${self.salary:,.2f}",

            # 合同类型标志用于条件判断
            "is_full_time": self.contract_type == ContractType.FULL_TIME,
            "is_part_time": self.contract_type == ContractType.PART_TIME,
            "is_contractor": self.contract_type == ContractType.CONTRACTOR,
            "contract_type_display": self.contract_type.value.replace("_", " ").title(),

            # 福利标志
            "bonus_eligible": self.bonus_eligible,
            "has_stock_options": self.stock_options is not None,
            "stock_options": self.stock_options,

            # 附加条款
            "confidentiality_level": self.confidentiality_level.value,
            "is_high_confidentiality": self.confidentiality_level in [
                ConfidentialityLevel.HIGH,
                ConfidentialityLevel.RESTRICTED
            ],
            "probation_period_months": self.probation_period_months,
            "remote_work_allowed": self.remote_work_allowed,
            "relocation_package": self.relocation_package
        }


def render_contract(
    template_path: str,
    output_path: str,
    contract: EmployeeContract
) -> None:
    """
    渲染包含条件区块的合同。

    条件判断的模板语法:
        {% if is_full_time %}
        全职福利部分...
        {% endif %}

        {% if bonus_eligible %}
        奖金条款...
        {% else %}
        仅包含标准薪酬。
        {% endif %}

        {% if is_high_confidentiality %}
        附加NDA要求...
        {% endif %}
    """
    template = DocxTemplate(template_path)
    context = contract.to_context()
    template.render(context)
    template.save(output_path)


def render_with_conditions(
    template_path: str,
    output_path: str,
    data: Dict[str, Any]
) -> None:
    """
    使用多种条件模式渲染模板。

    支持的条件模式:
        {% if condition %} ... {% endif %}
        {% if condition %} ... {% else %} ... {% endif %}
        {% if condition %} ... {% elif other %} ... {% else %} ... {% endif %}
        {% if value > 100 %} ... {% endif %}
        {% if value in list %} ... {% endif %}
    """
    template = DocxTemplate(template_path)
    template.render(data)
    template.save(output_path)

Example contract

示例合同

contract = EmployeeContract( employee_name="John Doe", position="Senior Software Engineer", department="Engineering", start_date="February 1, 2026", contract_type=ContractType.FULL_TIME, salary=150000, bonus_eligible=True, stock_options=5000, confidentiality_level=ConfidentialityLevel.HIGH, remote_work_allowed=True )
contract = EmployeeContract( employee_name="约翰·多伊", position="高级软件工程师", department="工程部", start_date="2026年2月1日", contract_type=ContractType.FULL_TIME, salary=150000, bonus_eligible=True, stock_options=5000, confidentiality_level=ConfidentialityLevel.HIGH, remote_work_allowed=True )

render_contract("contract_template.docx", "john_doe_contract.docx", contract)

render_contract("contract_template.docx", "john_doe_contract.docx", contract)

def create_conditional_report( template_path: str, output_path: str, performance_data: Dict ) -> None: """ Create report with conditional formatting based on performance.
Template example:
    Performance Score: {{ score }}

    {% if score >= 90 %}
    OUTSTANDING PERFORMANCE
    {% elif score >= 75 %}
    MEETS EXPECTATIONS
    {% elif score >= 60 %}
    NEEDS IMPROVEMENT
    {% else %}
    PERFORMANCE PLAN REQUIRED
    {% endif %}

    {% if has_warnings %}
    Warnings:
    {%p for warning in warnings %}
    - {{ warning }}
    {%p endfor %}
    {% endif %}
"""
template = DocxTemplate(template_path)

# Add computed flags to context
score = performance_data.get("score", 0)
context = {
    **performance_data,
    "performance_level": (
        "Outstanding" if score >= 90 else
        "Meets Expectations" if score >= 75 else
        "Needs Improvement" if score >= 60 else
        "Below Expectations"
    ),
    "has_warnings": len(performance_data.get("warnings", [])) > 0
}

template.render(context)
template.save(output_path)
undefined
def create_conditional_report( template_path: str, output_path: str, performance_data: Dict ) -> None: """ 根据绩效数据创建带条件格式的报告。
模板示例:
    绩效得分: {{ score }}

    {% if score >= 90 %}
    绩效优秀
    {% elif score >= 75 %}
    符合预期
    {% elif score >= 60 %}
    需要改进
    {% else %}
    需要绩效改进计划
    {% endif %}

    {% if has_warnings %}
    警告:
    {%p for warning in warnings %}
    - {{ warning }}
    {%p endfor %}
    {% endif %}
"""
template = DocxTemplate(template_path)

# 向上下文添加计算标志
score = performance_data.get("score", 0)
context = {
    **performance_data,
    "performance_level": (
        "优秀" if score >= 90 else
        "符合预期" if score >= 75 else
        "需要改进" if score >= 60 else
        "未达预期"
    ),
    "has_warnings": len(performance_data.get("warnings", [])) > 0
}

template.render(context)
template.save(output_path)
undefined

4. Table Generation

4. 表格生成

Dynamic Tables with Data:
python
"""
Generate tables dynamically from data.
"""
from docxtpl import DocxTemplate
from typing import List, Dict, Any
import pandas as pd

def render_data_table(
    template_path: str,
    output_path: str,
    headers: List[str],
    rows: List[List[Any]],
    table_title: str = ""
) -> None:
    """
    Render a simple data table.

    Template structure:
        {{ table_title }}

        | Header 1 | Header 2 | Header 3 |
        |----------|----------|----------|
        {%tr for row in rows %}
        | {{ row[0] }} | {{ row[1] }} | {{ row[2] }} |
        {%tr endfor %}
    """
    template = DocxTemplate(template_path)

    # Convert rows to list of dicts for easier template access
    row_dicts = []
    for row in rows:
        row_dict = {f"col{i}": val for i, val in enumerate(row)}
        row_dicts.append(row_dict)

    context = {
        "table_title": table_title,
        "headers": headers,
        "rows": row_dicts,
        "column_count": len(headers)
    }

    template.render(context)
    template.save(output_path)


def render_pandas_table(
    template_path: str,
    output_path: str,
    df: pd.DataFrame,
    title: str = ""
) -> None:
    """
    Render a pandas DataFrame as a table.

    Template:
        {%tr for row in data %}
        {%tc for cell in row %}{{ cell }}{%tc endfor %}
        {%tr endfor %}
    """
    template = DocxTemplate(template_path)

    # Convert DataFrame to list of dicts
    headers = df.columns.tolist()
    data = df.values.tolist()

    context = {
        "title": title,
        "headers": headers,
        "data": data,
        "row_count": len(data),
        "col_count": len(headers)
    }

    template.render(context)
    template.save(output_path)


def render_grouped_table(
    template_path: str,
    output_path: str,
    grouped_data: Dict[str, List[Dict]]
) -> None:
    """
    Render table with grouped rows and subtotals.

    Template:
        {%tr for group_name, items in groups.items() %}
        {{ group_name }} ({{ items|length }} items)
        {%tr for item in items %}
        | {{ item.name }} | {{ item.value }} |
        {%tr endfor %}
        Subtotal: {{ group_subtotals[group_name] }}
        {%tr endfor %}
    """
    template = DocxTemplate(template_path)

    # Calculate subtotals
    subtotals = {}
    for group, items in grouped_data.items():
        subtotals[group] = sum(item.get("value", 0) for item in items)

    context = {
        "groups": grouped_data,
        "group_subtotals": subtotals,
        "total": sum(subtotals.values())
    }

    template.render(context)
    template.save(output_path)


class TableBuilder:
    """
    Builder for complex table structures.
    """

    def __init__(self):
        self.headers: List[str] = []
        self.rows: List[Dict] = []
        self.footer_row: Dict = {}
        self.title: str = ""

    def set_title(self, title: str) -> 'TableBuilder':
        """Set table title."""
        self.title = title
        return self

    def set_headers(self, headers: List[str]) -> 'TableBuilder':
        """Set column headers."""
        self.headers = headers
        return self

    def add_row(self, **kwargs) -> 'TableBuilder':
        """Add a data row."""
        self.rows.append(kwargs)
        return self

    def add_rows_from_dicts(self, rows: List[Dict]) -> 'TableBuilder':
        """Add multiple rows from list of dicts."""
        self.rows.extend(rows)
        return self

    def add_rows_from_dataframe(self, df: pd.DataFrame) -> 'TableBuilder':
        """Add rows from DataFrame."""
        self.headers = df.columns.tolist()
        self.rows = df.to_dict('records')
        return self

    def set_footer(self, **kwargs) -> 'TableBuilder':
        """Set footer row (e.g., totals)."""
        self.footer_row = kwargs
        return self

    def auto_calculate_footer(self, columns: List[str], operation: str = "sum") -> 'TableBuilder':
        """Auto-calculate footer values."""
        for col in columns:
            values = [row.get(col, 0) for row in self.rows if isinstance(row.get(col), (int, float))]
            if operation == "sum":
                self.footer_row[col] = sum(values)
            elif operation == "avg":
                self.footer_row[col] = sum(values) / len(values) if values else 0
            elif operation == "count":
                self.footer_row[col] = len(values)

        return self

    def to_context(self) -> Dict[str, Any]:
        """Convert to template context."""
        return {
            "table_title": self.title,
            "headers": self.headers,
            "rows": self.rows,
            "footer": self.footer_row,
            "has_footer": bool(self.footer_row),
            "row_count": len(self.rows)
        }

    def render(self, template_path: str, output_path: str) -> None:
        """Render to document."""
        template = DocxTemplate(template_path)
        template.render(self.to_context())
        template.save(output_path)
基于数据的动态表格:
python
"""
从数据动态生成表格。
"""
from docxtpl import DocxTemplate
from typing import List, Dict, Any
import pandas as pd

def render_data_table(
    template_path: str,
    output_path: str,
    headers: List[str],
    rows: List[List[Any]],
    table_title: str = ""
) -> None:
    """
    渲染简单数据表格。

    模板结构:
        {{ table_title }}

        | 标题1 | 标题2 | 标题3 |
        |----------|----------|----------|
        {%tr for row in rows %}
        | {{ row[0] }} | {{ row[1] }} | {{ row[2] }} |
        {%tr endfor %}
    """
    template = DocxTemplate(template_path)

    # 将行转换为字典列表以便模板访问
    row_dicts = []
    for row in rows:
        row_dict = {f"col{i}": val for i, val in enumerate(row)}
        row_dicts.append(row_dict)

    context = {
        "table_title": table_title,
        "headers": headers,
        "rows": row_dicts,
        "column_count": len(headers)
    }

    template.render(context)
    template.save(output_path)


def render_pandas_table(
    template_path: str,
    output_path: str,
    df: pd.DataFrame,
    title: str = ""
) -> None:
    """
 将pandas DataFrame渲染为表格。

    模板:
        {%tr for row in data %}
        {%tc for cell in row %}{{ cell }}{%tc endfor %}
        {%tr endfor %}
    """
    template = DocxTemplate(template_path)

    # 将DataFrame转换为字典列表
    headers = df.columns.tolist()
    data = df.values.tolist()

    context = {
        "title": title,
        "headers": headers,
        "data": data,
        "row_count": len(data),
        "col_count": len(headers)
    }

    template.render(context)
    template.save(output_path)


def render_grouped_table(
    template_path: str,
    output_path: str,
    grouped_data: Dict[str, List[Dict]]
) -> None:
    """
    渲染包含分组行和小计的表格。

    模板:
        {%tr for group_name, items in groups.items() %}
        {{ group_name }} ({{ items|length }} 项)
        {%tr for item in items %}
        | {{ item.name }} | {{ item.value }} |
        {%tr endfor %}
        小计: {{ group_subtotals[group_name] }}
        {%tr endfor %}
    """
    template = DocxTemplate(template_path)

    # 计算小计
    subtotals = {}
    for group, items in grouped_data.items():
        subtotals[group] = sum(item.get("value", 0) for item in items)

    context = {
        "groups": grouped_data,
        "group_subtotals": subtotals,
        "total": sum(subtotals.values())
    }

    template.render(context)
    template.save(output_path)


class TableBuilder:
    """
    复杂表格结构构建器。
    """

    def __init__(self):
        self.headers: List[str] = []
        self.rows: List[Dict] = []
        self.footer_row: Dict = {}
        self.title: str = ""

    def set_title(self, title: str) -> 'TableBuilder':
        """设置表格标题。"""
        self.title = title
        return self

    def set_headers(self, headers: List[str]) -> 'TableBuilder':
        """设置列标题。"""
        self.headers = headers
        return self

    def add_row(self, **kwargs) -> 'TableBuilder':
        """添加数据行。"""
        self.rows.append(kwargs)
        return self

    def add_rows_from_dicts(self, rows: List[Dict]) -> 'TableBuilder':
        """从字典列表添加多行数据。"""
        self.rows.extend(rows)
        return self

    def add_rows_from_dataframe(self, df: pd.DataFrame) -> 'TableBuilder':
        """从DataFrame添加行。"""
        self.headers = df.columns.tolist()
        self.rows = df.to_dict('records')
        return self

    def set_footer(self, **kwargs) -> 'TableBuilder':
        """设置页脚行(如总计)。"""
        self.footer_row = kwargs
        return self

    def auto_calculate_footer(self, columns: List[str], operation: str = "sum") -> 'TableBuilder':
        """自动计算页脚值。"""
        for col in columns:
            values = [row.get(col, 0) for row in self.rows if isinstance(row.get(col), (int, float))]
            if operation == "sum":
                self.footer_row[col] = sum(values)
            elif operation == "avg":
                self.footer_row[col] = sum(values) / len(values) if values else 0
            elif operation == "count":
                self.footer_row[col] = len(values)

        return self

    def to_context(self) -> Dict[str, Any]:
        """转换为模板上下文。"""
        return {
            "table_title": self.title,
            "headers": self.headers,
            "rows": self.rows,
            "footer": self.footer_row,
            "has_footer": bool(self.footer_row),
            "row_count": len(self.rows)
        }

    def render(self, template_path: str, output_path: str) -> None:
        """渲染为文档。"""
        template = DocxTemplate(template_path)
        template.render(self.to_context())
        template.save(output_path)

Example usage

示例用法

builder = TableBuilder() builder.set_title("Monthly Sales Report") builder.set_headers(["Product", "Units", "Revenue", "Margin"]) builder.add_row(Product="Widget A", Units=100, Revenue=5000, Margin=25) builder.add_row(Product="Widget B", Units=150, Revenue=7500, Margin=30) builder.add_row(Product="Widget C", Units=75, Revenue=3750, Margin=22) builder.auto_calculate_footer(["Units", "Revenue"], operation="sum")
builder = TableBuilder() builder.set_title("月度销售报告") builder.set_headers(["产品", "数量", "收入", "利润率"]) builder.add_row(Product="Widget A", Units=100, Revenue=5000, Margin=25) builder.add_row(Product="Widget B", Units=150, Revenue=7500, Margin=30) builder.add_row(Product="Widget C", Units=75, Revenue=3750, Margin=22) builder.auto_calculate_footer(["Units", "Revenue"], operation="sum")

builder.render("sales_table_template.docx", "sales_report.docx")

builder.render("sales_table_template.docx", "sales_report.docx")

undefined
undefined

5. Image Insertion

5. 图片插入

Adding Images to Templates:
python
"""
Insert images into templates with proper sizing.
"""
from docxtpl import DocxTemplate, InlineImage
from docx.shared import Mm, Inches, Cm
from pathlib import Path
from typing import Optional, Union
from io import BytesIO
import requests

def add_image_to_template(
    template_path: str,
    output_path: str,
    image_path: str,
    context: dict,
    width: Optional[Union[Mm, Inches, Cm]] = None,
    height: Optional[Union[Mm, Inches, Cm]] = None
) -> None:
    """
    Add an image to a template.

    Template syntax:
        {{ image }}

    Args:
        template_path: Path to template
        output_path: Path for output
        image_path: Path to image file
        context: Additional context data
        width: Image width (optional)
        height: Image height (optional)
    """
    template = DocxTemplate(template_path)

    # Create InlineImage
    image = InlineImage(
        template,
        image_path,
        width=width,
        height=height
    )

    # Add image to context
    context["image"] = image

    template.render(context)
    template.save(output_path)


def add_image_from_url(
    template: DocxTemplate,
    url: str,
    width: Optional[Mm] = None
) -> InlineImage:
    """
    Create InlineImage from URL.

    Args:
        template: DocxTemplate instance
        url: Image URL
        width: Desired width

    Returns:
        InlineImage object
    """
    response = requests.get(url)
    response.raise_for_status()

    image_stream = BytesIO(response.content)

    return InlineImage(
        template,
        image_stream,
        width=width
    )


def render_document_with_images(
    template_path: str,
    output_path: str,
    data: dict,
    images: dict
) -> None:
    """
    Render document with multiple images.

    Template:
        Company Logo: {{ logo }}

        Product Images:
        {% for product in products %}
        {{ product.name }}: {{ product.image }}
        {% endfor %}
    """
    template = DocxTemplate(template_path)

    # Process images
    context = data.copy()

    for key, image_info in images.items():
        if isinstance(image_info, str):
            # Simple path
            context[key] = InlineImage(template, image_info, width=Mm(50))
        elif isinstance(image_info, dict):
            # Dict with path and dimensions
            context[key] = InlineImage(
                template,
                image_info["path"],
                width=image_info.get("width"),
                height=image_info.get("height")
            )

    template.render(context)
    template.save(output_path)


class ImageHandler:
    """
    Handle images for template rendering.
    """

    def __init__(self, template: DocxTemplate):
        self.template = template
        self._images: dict = {}

    def add_image(
        self,
        key: str,
        source: Union[str, BytesIO],
        width: Optional[int] = None,
        height: Optional[int] = None,
        unit: str = "mm"
    ) -> 'ImageHandler':
        """
        Add an image to the handler.

        Args:
            key: Context key for the image
            source: File path or BytesIO stream
            width: Width in specified units
            height: Height in specified units
            unit: Unit type ('mm', 'inches', 'cm')
        """
        # Convert units
        if unit == "mm":
            w = Mm(width) if width else None
            h = Mm(height) if height else None
        elif unit == "inches":
            w = Inches(width) if width else None
            h = Inches(height) if height else None
        elif unit == "cm":
            w = Cm(width) if width else None
            h = Cm(height) if height else None
        else:
            w = h = None

        self._images[key] = InlineImage(
            self.template,
            source,
            width=w,
            height=h
        )

        return self

    def add_image_from_url(
        self,
        key: str,
        url: str,
        width: int = 50,
        unit: str = "mm"
    ) -> 'ImageHandler':
        """Add image from URL."""
        response = requests.get(url)
        response.raise_for_status()

        image_stream = BytesIO(response.content)

        return self.add_image(key, image_stream, width=width, unit=unit)

    def get_context(self) -> dict:
        """Get images as context dictionary."""
        return self._images.copy()
向模板添加图片:
python
"""
向模板中插入图片并设置合适尺寸。
"""
from docxtpl import DocxTemplate, InlineImage
from docx.shared import Mm, Inches, Cm
from pathlib import Path
from typing import Optional, Union
from io import BytesIO
import requests

def add_image_to_template(
    template_path: str,
    output_path: str,
    image_path: str,
    context: dict,
    width: Optional[Union[Mm, Inches, Cm]] = None,
    height: Optional[Union[Mm, Inches, Cm]] = None
) -> None:
    """
    向模板添加图片。

    模板语法:
        {{ image }}

    参数:
        template_path: 模板文件路径
        output_path: 输出文件路径
        image_path: 图片文件路径
        context: 额外上下文数据
        width: 图片宽度(可选)
        height: 图片高度(可选)
    """
    template = DocxTemplate(template_path)

    # 创建InlineImage
    image = InlineImage(
        template,
        image_path,
        width=width,
        height=height
    )

    # 将图片添加到上下文
    context["image"] = image

    template.render(context)
    template.save(output_path)


def add_image_from_url(
    template: DocxTemplate,
    url: str,
    width: Optional[Mm] = None
) -> InlineImage:
    """
    从URL创建InlineImage。

    参数:
        template: DocxTemplate实例
        url: 图片URL
        width: 期望宽度

    返回:
        InlineImage对象
    """
    response = requests.get(url)
    response.raise_for_status()

    image_stream = BytesIO(response.content)

    return InlineImage(
        template,
        image_stream,
        width=width
    )


def render_document_with_images(
    template_path: str,
    output_path: str,
    data: dict,
    images: dict
) -> None:
    """
    渲染包含多张图片的文档。

    模板:
        公司Logo: {{ logo }}

        产品图片:
        {% for product in products %}
        {{ product.name }}: {{ product.image }}
        {% endfor %}
    """
    template = DocxTemplate(template_path)

    # 处理图片
    context = data.copy()

    for key, image_info in images.items():
        if isinstance(image_info, str):
            # 简单路径
            context[key] = InlineImage(template, image_info, width=Mm(50))
        elif isinstance(image_info, dict):
            # 包含路径和尺寸的字典
            context[key] = InlineImage(
                template,
                image_info["path"],
                width=image_info.get("width"),
                height=image_info.get("height")
            )

    template.render(context)
    template.save(output_path)


class ImageHandler:
    """
    处理模板渲染中的图片。
    """

    def __init__(self, template: DocxTemplate):
        self.template = template
        self._images: dict = {}

    def add_image(
        self,
        key: str,
        source: Union[str, BytesIO],
        width: Optional[int] = None,
        height: Optional[int] = None,
        unit: str = "mm"
    ) -> 'ImageHandler':
        """
        向处理器添加图片。

        参数:
            key: 图片在上下文中的键名
            source: 文件路径或BytesIO流
            width: 指定单位下的宽度
            height: 指定单位下的高度
            unit: 单位类型 ('mm', 'inches', 'cm')
        """
        # 转换单位
        if unit == "mm":
            w = Mm(width) if width else None
            h = Mm(height) if height else None
        elif unit == "inches":
            w = Inches(width) if width else None
            h = Inches(height) if height else None
        elif unit == "cm":
            w = Cm(width) if width else None
            h = Cm(height) if height else None
        else:
            w = h = None

        self._images[key] = InlineImage(
            self.template,
            source,
            width=w,
            height=h
        )

        return self

    def add_image_from_url(
        self,
        key: str,
        url: str,
        width: int = 50,
        unit: str = "mm"
    ) -> 'ImageHandler':
        """从URL添加图片。"""
        response = requests.get(url)
        response.raise_for_status()

        image_stream = BytesIO(response.content)

        return self.add_image(key, image_stream, width=width, unit=unit)

    def get_context(self) -> dict:
        """获取图片的上下文字典。"""
        return self._images.copy()

Example usage

示例用法

def create_product_catalog( template_path: str, output_path: str, products: List[Dict] ) -> None: """Create product catalog with images.""" template = DocxTemplate(template_path) handler = ImageHandler(template)
# Process products and add images
processed_products = []
for i, product in enumerate(products):
    if "image_path" in product:
        handler.add_image(
            f"product_image_{i}",
            product["image_path"],
            width=60,
            unit="mm"
        )
        product["image"] = handler._images[f"product_image_{i}"]
    processed_products.append(product)

context = {
    "catalog_title": "2026 Product Catalog",
    "products": processed_products,
    **handler.get_context()
}

template.render(context)
template.save(output_path)
undefined
def create_product_catalog( template_path: str, output_path: str, products: List[Dict] ) -> None: """创建包含图片的产品目录。""" template = DocxTemplate(template_path) handler = ImageHandler(template)
# 处理产品并添加图片
processed_products = []
for i, product in enumerate(products):
    if "image_path" in product:
        handler.add_image(
            f"product_image_{i}",
            product["image_path"],
            width=60,
            unit="mm"
        )
        product["image"] = handler._images[f"product_image_{i}"]
    processed_products.append(product)

context = {
    "catalog_title": "2026产品目录",
    "products": processed_products,
    **handler.get_context()
}

template.render(context)
template.save(output_path)
undefined

6. Mail Merge and Batch Generation

6. 邮件合并与批量生成

Generating Multiple Documents:
python
"""
Generate multiple documents from a template with different data.
"""
from docxtpl import DocxTemplate
from typing import List, Dict, Any, Iterator
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
import csv
import json
import pandas as pd

def mail_merge_from_list(
    template_path: str,
    output_dir: str,
    records: List[Dict[str, Any]],
    filename_field: str = "id"
) -> List[str]:
    """
    Generate documents for multiple records.

    Args:
        template_path: Path to template
        output_dir: Directory for output files
        records: List of data records
        filename_field: Field to use for output filename

    Returns:
        List of generated file paths
    """
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    generated_files = []

    for record in records:
        # Load fresh template for each document
        template = DocxTemplate(template_path)

        # Generate filename
        filename = f"{record.get(filename_field, 'document')}.docx"
        file_path = output_path / filename

        # Render and save
        template.render(record)
        template.save(str(file_path))

        generated_files.append(str(file_path))

    print(f"Generated {len(generated_files)} documents in {output_dir}")
    return generated_files


def mail_merge_from_csv(
    template_path: str,
    csv_path: str,
    output_dir: str,
    filename_field: str = "id"
) -> List[str]:
    """
    Generate documents from CSV data source.

    Args:
        template_path: Path to template
        csv_path: Path to CSV file
        output_dir: Directory for output files
        filename_field: Field to use for output filename

    Returns:
        List of generated file paths
    """
    with open(csv_path, 'r', newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        records = list(reader)

    return mail_merge_from_list(template_path, output_dir, records, filename_field)


def mail_merge_from_excel(
    template_path: str,
    excel_path: str,
    output_dir: str,
    sheet_name: str = None,
    filename_field: str = "id"
) -> List[str]:
    """
    Generate documents from Excel data source.

    Args:
        template_path: Path to template
        excel_path: Path to Excel file
        output_dir: Directory for output files
        sheet_name: Sheet to read (default: first sheet)
        filename_field: Field to use for output filename

    Returns:
        List of generated file paths
    """
    df = pd.read_excel(excel_path, sheet_name=sheet_name)
    records = df.to_dict('records')

    return mail_merge_from_list(template_path, output_dir, records, filename_field)


def mail_merge_parallel(
    template_path: str,
    output_dir: str,
    records: List[Dict[str, Any]],
    filename_field: str = "id",
    max_workers: int = 4
) -> List[str]:
    """
    Generate documents in parallel for better performance.

    Args:
        template_path: Path to template
        output_dir: Directory for output files
        records: List of data records
        filename_field: Field to use for output filename
        max_workers: Maximum parallel workers

    Returns:
        List of generated file paths
    """
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    def generate_single(record: Dict) -> str:
        """Generate a single document."""
        template = DocxTemplate(template_path)
        filename = f"{record.get(filename_field, 'document')}.docx"
        file_path = output_path / filename

        template.render(record)
        template.save(str(file_path))

        return str(file_path)

    generated_files = []

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {executor.submit(generate_single, r): r for r in records}

        for future in as_completed(futures):
            try:
                result = future.result()
                generated_files.append(result)
            except Exception as e:
                record = futures[future]
                print(f"Error generating document for {record.get(filename_field)}: {e}")

    print(f"Generated {len(generated_files)} documents")
    return generated_files


class MailMergeGenerator:
    """
    Full-featured mail merge generator.
    """

    def __init__(self, template_path: str):
        self.template_path = template_path
        self._validate_template()

    def _validate_template(self) -> None:
        """Validate template file exists."""
        if not Path(self.template_path).exists():
            raise FileNotFoundError(f"Template not found: {self.template_path}")

    def _get_template_variables(self) -> List[str]:
        """Extract variable names from template."""
        template = DocxTemplate(self.template_path)
        return list(template.get_undeclared_template_variables())

    def validate_data(self, records: List[Dict]) -> Dict[str, List]:
        """
        Validate data against template variables.

        Returns:
            Dict with 'missing' and 'extra' variable lists
        """
        template_vars = set(self._get_template_variables())

        if not records:
            return {"missing": list(template_vars), "extra": []}

        data_vars = set(records[0].keys())

        return {
            "missing": list(template_vars - data_vars),
            "extra": list(data_vars - template_vars)
        }

    def generate(
        self,
        output_dir: str,
        records: List[Dict],
        filename_pattern: str = "{id}",
        parallel: bool = False,
        max_workers: int = 4
    ) -> Dict[str, Any]:
        """
        Generate documents with full reporting.

        Args:
            output_dir: Output directory
            records: Data records
            filename_pattern: Pattern for filenames (e.g., "{name}_{date}")
            parallel: Use parallel processing
            max_workers: Number of parallel workers

        Returns:
            Generation report
        """
        output_path = Path(output_dir)
        output_path.mkdir(parents=True, exist_ok=True)

        results = {
            "total": len(records),
            "successful": 0,
            "failed": 0,
            "files": [],
            "errors": []
        }

        def process_record(record: Dict) -> Dict:
            """Process single record."""
            try:
                template = DocxTemplate(self.template_path)

                # Generate filename from pattern
                filename = filename_pattern.format(**record) + ".docx"
                file_path = output_path / filename

                template.render(record)
                template.save(str(file_path))

                return {"success": True, "file": str(file_path)}
            except Exception as e:
                return {"success": False, "error": str(e), "record": record}

        if parallel and len(records) > 1:
            with ThreadPoolExecutor(max_workers=max_workers) as executor:
                futures = [executor.submit(process_record, r) for r in records]
                for future in as_completed(futures):
                    result = future.result()
                    if result["success"]:
                        results["successful"] += 1
                        results["files"].append(result["file"])
                    else:
                        results["failed"] += 1
                        results["errors"].append(result)
        else:
            for record in records:
                result = process_record(record)
                if result["success"]:
                    results["successful"] += 1
                    results["files"].append(result["file"])
                else:
                    results["failed"] += 1
                    results["errors"].append(result)

        return results
生成多份文档:
python
"""
使用不同数据从模板生成多份文档。
"""
from docxtpl import DocxTemplate
from typing import List, Dict, Any, Iterator
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
import csv
import json
import pandas as pd

def mail_merge_from_list(
    template_path: str,
    output_dir: str,
    records: List[Dict[str, Any]],
    filename_field: str = "id"
) -> List[str]:
    """
    为多条记录生成文档。

    参数:
        template_path: 模板文件路径
        output_dir: 输出目录
        records: 数据记录列表
        filename_field: 用于生成输出文件名的字段

    返回:
    生成的文件路径列表
    """
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    generated_files = []

    for record in records:
        # 为每份文档加载新模板
        template = DocxTemplate(template_path)

        # 生成文件名
        filename = f"{record.get(filename_field, 'document')}.docx"
        file_path = output_path / filename

        # 渲染并保存
        template.render(record)
        template.save(str(file_path))

        generated_files.append(str(file_path))

    print(f"已在 {output_dir} 中生成 {len(generated_files)} 份文档")
    return generated_files


def mail_merge_from_csv(
    template_path: str,
    csv_path: str,
    output_dir: str,
    filename_field: str = "id"
) -> List[str]:
    """
    从CSV数据源生成文档。

    参数:
        template_path: 模板文件路径
        csv_path: CSV文件路径
        output_dir: 输出目录
        filename_field: 用于生成输出文件名的字段

    返回:
        生成的文件路径列表
    """
    with open(csv_path, 'r', newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        records = list(reader)

    return mail_merge_from_list(template_path, output_dir, records, filename_field)


def mail_merge_from_excel(
    template_path: str,
    excel_path: str,
    output_dir: str,
    sheet_name: str = None,
    filename_field: str = "id"
) -> List[str]:
    """
    从Excel数据源生成文档。

    参数:
        template_path: 模板文件路径
        excel_path: Excel文件路径
        output_dir: 输出目录
        sheet_name: 要读取的工作表(默认: 第一个工作表)
        filename_field: 用于生成输出文件名的字段

    返回:
        生成的文件路径列表
    """
    df = pd.read_excel(excel_path, sheet_name=sheet_name)
    records = df.to_dict('records')

    return mail_merge_from_list(template_path, output_dir, records, filename_field)


def mail_merge_parallel(
    template_path: str,
    output_dir: str,
    records: List[Dict[str, Any]],
    filename_field: str = "id",
    max_workers: int = 4
) -> List[str]:
    """
    使用并行处理提升批量生成性能。

    参数:
        template_path: 模板文件路径
        output_dir: 输出目录
        records: 数据记录列表
        filename_field: 用于生成输出文件名的字段
        max_workers: 最大并行工作线程数

    返回:
        生成的文件路径列表
    """
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    def generate_single(record: Dict) -> str:
        """生成单份文档。"""
        template = DocxTemplate(template_path)
        filename = f"{record.get(filename_field, 'document')}.docx"
        file_path = output_path / filename

        template.render(record)
        template.save(str(file_path))

        return str(file_path)

    generated_files = []

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {executor.submit(generate_single, r): r for r in records}

        for future in as_completed(futures):
            try:
                result = future.result()
                generated_files.append(result)
            except Exception as e:
                record = futures[future]
                print(f"为 {record.get(filename_field)} 生成文档时出错: {e}")

    print(f"已生成 {len(generated_files)} 份文档")
    return generated_files


class MailMergeGenerator:
    """
    功能完整的邮件合并生成器。
    """

    def __init__(self, template_path: str):
        self.template_path = template_path
        self._validate_template()

    def _validate_template(self) -> None:
        """验证模板文件是否存在。"""
        if not Path(self.template_path).exists():
            raise FileNotFoundError(f"未找到模板: {self.template_path}")

    def _get_template_variables(self) -> List[str]:
        """从模板中提取变量名。"""
        template = DocxTemplate(self.template_path)
        return list(template.get_undeclared_template_variables())

    def validate_data(self, records: List[Dict]) -> Dict[str, List]:
        """
        验证数据与模板变量是否匹配。

        返回:
            包含'missing'和'extra'变量列表的字典
        """
        template_vars = set(self._get_template_variables())

        if not records:
            return {"missing": list(template_vars), "extra": []}

        data_vars = set(records[0].keys())

        return {
            "missing": list(template_vars - data_vars),
            "extra": list(data_vars - template_vars)
        }

    def generate(
        self,
        output_dir: str,
        records: List[Dict],
        filename_pattern: str = "{id}",
        parallel: bool = False,
        max_workers: int = 4
    ) -> Dict[str, Any]:
        """
        生成文档并返回完整报告。

        参数:
            output_dir: 输出目录
            records: 数据记录
            filename_pattern: 文件名模式(如 "{name}_{date}")
            parallel: 是否使用并行处理
            max_workers: 并行工作线程数

        返回:
            生成报告
        """
        output_path = Path(output_dir)
        output_path.mkdir(parents=True, exist_ok=True)

        results = {
            "total": len(records),
            "successful": 0,
            "failed": 0,
            "files": [],
            "errors": []
        }

        def process_record(record: Dict) -> Dict:
            """处理单条记录。"""
            try:
                template = DocxTemplate(self.template_path)

                # 从模式生成文件名
                filename = filename_pattern.format(**record) + ".docx"
                file_path = output_path / filename

                template.render(record)
                template.save(str(file_path))

                return {"success": True, "file": str(file_path)}
            except Exception as e:
                return {"success": False, "error": str(e), "record": record}

        if parallel and len(records) > 1:
            with ThreadPoolExecutor(max_workers=max_workers) as executor:
                futures = [executor.submit(process_record, r) for r in records]
                for future in as_completed(futures):
                    result = future.result()
                    if result["success"]:
                        results["successful"] += 1
                        results["files"].append(result["file"])
                    else:
                        results["failed"] += 1
                        results["errors"].append(result)
        else:
            for record in records:
                result = process_record(record)
                if result["success"]:
                    results["successful"] += 1
                    results["files"].append(result["file"])
                else:
                    results["failed"] += 1
                    results["errors"].append(result)

        return results

Example usage

示例用法

def generate_personalized_letters(): """Generate personalized letters for recipients.""" recipients = [ { "id": "001", "name": "John Smith", "title": "Mr.", "company": "Acme Corp", "address": "123 Main St", "city": "New York", "offer_amount": "$50,000" }, { "id": "002", "name": "Jane Doe", "title": "Ms.", "company": "TechStart Inc", "address": "456 Oak Ave", "city": "San Francisco", "offer_amount": "$75,000" } ]
generator = MailMergeGenerator("offer_letter_template.docx")

# Validate data
validation = generator.validate_data(recipients)
if validation["missing"]:
    print(f"Warning: Missing variables: {validation['missing']}")

# Generate documents
results = generator.generate(
    output_dir="generated_letters",
    records=recipients,
    filename_pattern="{id}_{name}",
    parallel=True
)

print(f"Generated: {results['successful']}/{results['total']}")
if results["errors"]:
    for error in results["errors"]:
        print(f"Error: {error}")
def generate_personalized_letters(): """为收件人生成个性化信件。""" recipients = [ { "id": "001", "name": "约翰·史密斯", "title": "先生", "company": "Acme公司", "address": "主街123号", "city": "纽约", "offer_amount": "$50,000" }, { "id": "002", "name": "简·多伊", "title": "女士", "company": "TechStart公司", "address": "橡树大道456号", "city": "旧金山", "offer_amount": "$75,000" } ]
generator = MailMergeGenerator("offer_letter_template.docx")

# 验证数据
validation = generator.validate_data(recipients)
if validation["missing"]:
    print(f"警告: 缺少变量: {validation['missing']}")

# 生成文档
results = generator.generate(
    output_dir="generated_letters",
    records=recipients,
    filename_pattern="{id}_{name}",
    parallel=True
)

print(f"已生成: {results['successful']}/{results['total']}")
if results["errors"]:
    for error in results["errors"]:
        print(f"错误: {error}")

generate_personalized_letters()

generate_personalized_letters()

undefined
undefined

Integration Examples

集成示例

Database Integration

数据库集成

python
"""
Generate documents from database queries.
"""
from docxtpl import DocxTemplate
from typing import List, Dict
import sqlite3
from sqlalchemy import create_engine, text
import pandas as pd

def generate_from_database(
    template_path: str,
    output_dir: str,
    db_connection: str,
    query: str,
    filename_field: str = "id"
) -> List[str]:
    """
    Generate documents from database query results.

    Args:
        template_path: Path to template
        output_dir: Output directory
        db_connection: Database connection string
        query: SQL query to fetch data
        filename_field: Field for output filename

    Returns:
        List of generated file paths
    """
    # Connect and fetch data
    engine = create_engine(db_connection)

    with engine.connect() as conn:
        result = conn.execute(text(query))
        records = [dict(row._mapping) for row in result]

    # Generate documents
    return mail_merge_from_list(template_path, output_dir, records, filename_field)


def generate_customer_reports(
    template_path: str,
    output_dir: str,
    db_path: str
) -> Dict:
    """Generate customer reports from SQLite database."""

    conn = sqlite3.connect(db_path)
    conn.row_factory = sqlite3.Row

    # Fetch customers with their orders
    query = """
    SELECT
        c.id,
        c.name,
        c.email,
        c.address,
        COUNT(o.id) as order_count,
        SUM(o.total) as total_spent
    FROM customers c
    LEFT JOIN orders o ON c.id = o.customer_id
    GROUP BY c.id
    """

    cursor = conn.cursor()
    cursor.execute(query)

    records = []
    for row in cursor.fetchall():
        record = dict(row)

        # Fetch order details for each customer
        cursor.execute(
            "SELECT * FROM orders WHERE customer_id = ?",
            (record["id"],)
        )
        record["orders"] = [dict(r) for r in cursor.fetchall()]
        records.append(record)

    conn.close()

    # Generate reports
    generated = mail_merge_from_list(
        template_path,
        output_dir,
        records,
        filename_field="id"
    )

    return {
        "total_customers": len(records),
        "generated_reports": len(generated),
        "files": generated
    }
python
"""
从数据库查询结果生成文档。
"""
from docxtpl import DocxTemplate
from typing import List, Dict
import sqlite3
from sqlalchemy import create_engine, text
import pandas as pd

def generate_from_database(
    template_path: str,
    output_dir: str,
    db_connection: str,
    query: str,
    filename_field: str = "id"
) -> List[str]:
    """
    从数据库查询结果生成文档。

    参数:
        template_path: 模板文件路径
        output_dir: 输出目录
        db_connection: 数据库连接字符串
        query: 用于获取数据的SQL查询语句
        filename_field: 用于生成输出文件名的字段

    返回:
        生成的文件路径列表
    """
    # 连接并获取数据
    engine = create_engine(db_connection)

    with engine.connect() as conn:
        result = conn.execute(text(query))
        records = [dict(row._mapping) for row in result]

    # 生成文档
    return mail_merge_from_list(template_path, output_dir, records, filename_field)


def generate_customer_reports(
    template_path: str,
    output_dir: str,
    db_path: str
) -> Dict:
    """从SQLite数据库生成客户报告。"""

    conn = sqlite3.connect(db_path)
    conn.row_factory = sqlite3.Row

    # 获取客户及其订单信息
    query = """
    SELECT
        c.id,
        c.name,
        c.email,
        c.address,
        COUNT(o.id) as order_count,
        SUM(o.total) as total_spent
    FROM customers c
    LEFT JOIN orders o ON c.id = o.customer_id
    GROUP BY c.id
    """

    cursor = conn.cursor()
    cursor.execute(query)

    records = []
    for row in cursor.fetchall():
        record = dict(row)

        # 获取每个客户的订单详情
        cursor.execute(
            "SELECT * FROM orders WHERE customer_id = ?",
            (record["id"],)
        )
        record["orders"] = [dict(r) for r in cursor.fetchall()]
        records.append(record)

    conn.close()

    # 生成报告
    generated = mail_merge_from_list(
        template_path,
        output_dir,
        records,
        filename_field="id"
    )

    return {
        "total_customers": len(records),
        "generated_reports": len(generated),
        "files": generated
    }

FastAPI Service

FastAPI服务

python
"""
Document generation service with FastAPI.
"""
from fastapi import FastAPI, HTTPException, UploadFile, File
from fastapi.responses import FileResponse
from pydantic import BaseModel
from typing import Dict, Any, List, Optional
from docxtpl import DocxTemplate
from pathlib import Path
import tempfile
import uuid

app = FastAPI(title="Document Generation Service")
python
"""
基于FastAPI的文档生成服务。
"""
from fastapi import FastAPI, HTTPException, UploadFile, File
from fastapi.responses import FileResponse
from pydantic import BaseModel
from typing import Dict, Any, List, Optional
from docxtpl import DocxTemplate
from pathlib import Path
import tempfile
import uuid

app = FastAPI(title="文档生成服务")

Template storage

模板存储目录

TEMPLATES_DIR = Path("./templates") OUTPUT_DIR = Path("./generated") OUTPUT_DIR.mkdir(exist_ok=True)
class GenerateRequest(BaseModel): """Request model for document generation.""" template_name: str data: Dict[str, Any] output_filename: Optional[str] = None
class BatchGenerateRequest(BaseModel): """Request for batch generation.""" template_name: str records: List[Dict[str, Any]] filename_field: str = "id"
@app.get("/templates") async def list_templates(): """List available templates.""" templates = [] for f in TEMPLATES_DIR.glob("*.docx"): templates.append({ "name": f.stem, "filename": f.name }) return {"templates": templates}
@app.post("/generate") async def generate_document(request: GenerateRequest): """Generate a single document.""" template_path = TEMPLATES_DIR / f"{request.template_name}.docx"
if not template_path.exists():
    raise HTTPException(404, f"Template '{request.template_name}' not found")

try:
    template = DocxTemplate(str(template_path))
    template.render(request.data)

    # Generate output filename
    output_name = request.output_filename or f"{uuid.uuid4()}.docx"
    if not output_name.endswith(".docx"):
        output_name += ".docx"

    output_path = OUTPUT_DIR / output_name
    template.save(str(output_path))

    return FileResponse(
        str(output_path),
        media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        filename=output_name
    )

except Exception as e:
    raise HTTPException(500, f"Generation failed: {str(e)}")
@app.post("/generate/batch") async def generate_batch(request: BatchGenerateRequest): """Generate multiple documents.""" template_path = TEMPLATES_DIR / f"{request.template_name}.docx"
if not template_path.exists():
    raise HTTPException(404, f"Template '{request.template_name}' not found")

generated = []
errors = []

for record in request.records:
    try:
        template = DocxTemplate(str(template_path))
        template.render(record)

        filename = f"{record.get(request.filename_field, uuid.uuid4())}.docx"
        output_path = OUTPUT_DIR / filename

        template.save(str(output_path))
        generated.append(filename)

    except Exception as e:
        errors.append({
            "record": record.get(request.filename_field),
            "error": str(e)
        })

return {
    "generated": len(generated),
    "failed": len(errors),
    "files": generated,
    "errors": errors
}
@app.post("/templates/upload") async def upload_template(file: UploadFile = File(...)): """Upload a new template.""" if not file.filename.endswith(".docx"): raise HTTPException(400, "Only .docx files are supported")
template_path = TEMPLATES_DIR / file.filename

content = await file.read()
with open(template_path, "wb") as f:
    f.write(content)

return {"message": f"Template '{file.filename}' uploaded", "name": Path(file.filename).stem}
TEMPLATES_DIR = Path("./templates") OUTPUT_DIR = Path("./generated") OUTPUT_DIR.mkdir(exist_ok=True)
class GenerateRequest(BaseModel): """文档生成请求模型。""" template_name: str data: Dict[str, Any] output_filename: Optional[str] = None
class BatchGenerateRequest(BaseModel): """批量生成请求模型。""" template_name: str records: List[Dict[str, Any]] filename_field: str = "id"
@app.get("/templates") async def list_templates(): """列出可用模板。""" templates = [] for f in TEMPLATES_DIR.glob("*.docx"): templates.append({ "name": f.stem, "filename": f.name }) return {"templates": templates}
@app.post("/generate") async def generate_document(request: GenerateRequest): """生成单份文档。""" template_path = TEMPLATES_DIR / f"{request.template_name}.docx"
if not template_path.exists():
    raise HTTPException(404, f"未找到模板 '{request.template_name}'")

try:
    template = DocxTemplate(str(template_path))
    template.render(request.data)

    # 生成输出文件名
    output_name = request.output_filename or f"{uuid.uuid4()}.docx"
    if not output_name.endswith(".docx"):
        output_name += ".docx"

    output_path = OUTPUT_DIR / output_name
    template.save(str(output_path))

    return FileResponse(
        str(output_path),
        media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        filename=output_name
    )

except Exception as e:
    raise HTTPException(500, f"生成失败: {str(e)}")
@app.post("/generate/batch") async def generate_batch(request: BatchGenerateRequest): """批量生成文档。""" template_path = TEMPLATES_DIR / f"{request.template_name}.docx"
if not template_path.exists():
    raise HTTPException(404, f"未找到模板 '{request.template_name}'")

generated = []
errors = []

for record in request.records:
    try:
        template = DocxTemplate(str(template_path))
        template.render(record)

        filename = f"{record.get(request.filename_field, uuid.uuid4())}.docx"
        output_path = OUTPUT_DIR / filename

        template.save(str(output_path))
        generated.append(filename)

    except Exception as e:
        errors.append({
            "record": record.get(request.filename_field),
            "error": str(e)
        })

return {
    "generated": len(generated),
    "failed": len(errors),
    "files": generated,
    "errors": errors
}
@app.post("/templates/upload") async def upload_template(file: UploadFile = File(...)): """上传新模板。""" if not file.filename.endswith(".docx"): raise HTTPException(400, "仅支持 .docx 文件")
template_path = TEMPLATES_DIR / file.filename

content = await file.read()
with open(template_path, "wb") as f:
    f.write(content)

return {"message": f"模板 '{file.filename}' 上传成功", "name": Path(file.filename).stem}

Run with: uvicorn service:app --reload

运行命令: uvicorn service:app --reload

undefined
undefined

Best Practices

最佳实践

1. Template Design

1. 模板设计

python
"""Best practices for template design."""
python
"""模板设计最佳实践。"""

DO: Use meaningful variable names

推荐: 使用有意义的变量名

good_context = { "customer_name": "John Smith", "invoice_date": "2026-01-17", "total_amount": "$1,500.00" }
good_context = { "customer_name": "约翰·史密斯", "invoice_date": "2026-01-17", "total_amount": "$1,500.00" }

DON'T: Use cryptic names

不推荐: 使用晦涩的变量名

bad_context = { "cn": "John Smith", "d": "2026-01-17", "t": "$1,500.00" }
bad_context = { "cn": "约翰·史密斯", "d": "2026-01-17", "t": "$1,500.00" }

DO: Organize context with nested objects

推荐: 使用嵌套对象组织上下文

organized_context = { "customer": { "name": "John Smith", "email": "john@example.com", "address": { "street": "123 Main St", "city": "New York" } }, "invoice": { "number": "INV-001", "date": "2026-01-17", "items": [...] } }
organized_context = { "customer": { "name": "约翰·史密斯", "email": "john@example.com", "address": { "street": "主街123号", "city": "纽约" } }, "invoice": { "number": "INV-001", "date": "2026-01-17", "items": [...] } }

DO: Include computed values

推荐: 包含计算后的值

def prepare_context(data: dict) -> dict: """Prepare context with computed values.""" context = data.copy()
# Add computed fields
if "items" in context:
    context["item_count"] = len(context["items"])
    context["subtotal"] = sum(i["total"] for i in context["items"])

# Add display flags
context["has_discount"] = context.get("discount", 0) > 0

return context
undefined
def prepare_context(data: dict) -> dict: """准备包含计算值的上下文。""" context = data.copy()
# 添加计算字段
if "items" in context:
    context["item_count"] = len(context["items"])
    context["subtotal"] = sum(i["total"] for i in context["items"])

# 添加显示标志
context["has_discount"] = context.get("discount", 0) > 0

return context
undefined

2. Error Handling

2. 错误处理

python
"""Robust error handling for template rendering."""
from typing import Tuple, Optional

def safe_render(
    template_path: str,
    output_path: str,
    context: dict
) -> Tuple[bool, Optional[str]]:
    """
    Safely render template with error handling.

    Returns:
        Tuple of (success, error_message)
    """
    try:
        # Validate template exists
        if not Path(template_path).exists():
            return False, f"Template not found: {template_path}"

        # Load and render
        template = DocxTemplate(template_path)

        # Check for missing variables
        required_vars = template.get_undeclared_template_variables()
        missing = [v for v in required_vars if v not in context]
        if missing:
            return False, f"Missing variables: {missing}"

        template.render(context)

        # Ensure output directory exists
        Path(output_path).parent.mkdir(parents=True, exist_ok=True)

        template.save(output_path)
        return True, None

    except Exception as e:
        return False, str(e)
python
"""模板渲染的健壮错误处理。"""
from typing import Tuple, Optional

def safe_render(
    template_path: str,
    output_path: str,
    context: dict
) -> Tuple[bool, Optional[str]]:
    """
    安全渲染模板并处理错误。

    返回:
        (成功状态, 错误信息) 元组
    """
    try:
        # 验证模板是否存在
        if not Path(template_path).exists():
            return False, f"未找到模板: {template_path}"

        # 加载并渲染
        template = DocxTemplate(template_path)

        # 检查缺失变量
        required_vars = template.get_undeclared_template_variables()
        missing = [v for v in required_vars if v not in context]
        if missing:
            return False, f"缺少变量: {missing}"

        template.render(context)

        # 确保输出目录存在
        Path(output_path).parent.mkdir(parents=True, exist_ok=True)

        template.save(output_path)
        return True, None

    except Exception as e:
        return False, str(e)

3. Performance Optimization

3. 性能优化

python
"""Optimize batch document generation."""
python
"""优化批量文档生成性能。"""

DO: Use parallel processing for large batches

推荐: 对大批量任务使用并行处理

def optimized_batch_generation( template_path: str, records: List[Dict], output_dir: str, batch_size: int = 100 ) -> List[str]: """Generate documents in optimized batches.""" from concurrent.futures import ThreadPoolExecutor
def process_batch(batch_records):
    results = []
    for record in batch_records:
        template = DocxTemplate(template_path)
        template.render(record)
        output_path = Path(output_dir) / f"{record['id']}.docx"
        template.save(str(output_path))
        results.append(str(output_path))
    return results

# Process in batches
all_results = []
batches = [records[i:i+batch_size] for i in range(0, len(records), batch_size)]

with ThreadPoolExecutor(max_workers=4) as executor:
    futures = [executor.submit(process_batch, b) for b in batches]
    for future in futures:
        all_results.extend(future.result())

return all_results
undefined
def optimized_batch_generation( template_path: str, records: List[Dict], output_dir: str, batch_size: int = 100 ) -> List[str]: """使用优化的批量方式生成文档。""" from concurrent.futures import ThreadPoolExecutor
def process_batch(batch_records):
    results = []
    for record in batch_records:
        template = DocxTemplate(template_path)
        template.render(record)
        output_path = Path(output_dir) / f"{record['id']}.docx"
        template.save(str(output_path))
        results.append(str(output_path))
    return results

# 分批处理
all_results = []
batches = [records[i:i+batch_size] for i in range(0, len(records), batch_size)]

with ThreadPoolExecutor(max_workers=4) as executor:
    futures = [executor.submit(process_batch, b) for b in batches]
    for future in futures:
        all_results.extend(future.result())

return all_results
undefined

Troubleshooting

故障排除

Template Variables Not Rendering

模板变量未渲染

python
undefined
python
undefined

Problem: Variables appear as {{ variable }} in output

问题: 变量在输出中显示为 {{ variable }}

Solution: Check variable syntax and context keys

解决方案: 检查变量语法和上下文键名

def diagnose_template(template_path: str, context: dict): """Diagnose template rendering issues.""" template = DocxTemplate(template_path)
# Get expected variables
expected = template.get_undeclared_template_variables()
print(f"Template expects: {expected}")

# Check context
provided = set(context.keys())
print(f"Context provides: {provided}")

# Find mismatches
missing = expected - provided
if missing:
    print(f"MISSING: {missing}")
undefined
def diagnose_template(template_path: str, context: dict): """诊断模板渲染问题。""" template = DocxTemplate(template_path)
# 获取期望的变量
expected = template.get_undeclared_template_variables()
print(f"模板期望变量: {expected}")

# 检查上下文
provided = set(context.keys())
print(f"上下文提供变量: {provided}")

# 找出不匹配项
missing = expected - provided
if missing:
    print(f"缺失变量: {missing}")
undefined

Loop Not Iterating

循环未迭代

python
undefined
python
undefined

Problem: Loop content not appearing

问题: 循环内容未显示

Solution: Verify loop syntax and data structure

解决方案: 验证循环语法和数据结构

CORRECT loop syntax in template:

正确的模板循环语法:

{%tr for item in items %}

{%tr for item in items %}

{{ item.name }}

{{ item.name }}

{%tr endfor %}

{%tr endfor %}

Ensure data is a list

确保数据是列表类型

context = { "items": [ {"name": "Item 1"}, # Must be dict or object {"name": "Item 2"} ] }
undefined
context = { "items": [ {"name": "项目1"}, # 必须是字典或对象 {"name": "项目2"} ] }
undefined

Image Not Appearing

图片未显示

python
undefined
python
undefined

Problem: Image placeholder shows error

问题: 图片占位符显示错误

Solution: Verify image path and format

解决方案: 验证图片路径和格式

from pathlib import Path
def validate_image(image_path: str) -> bool: """Validate image file.""" path = Path(image_path)
if not path.exists():
    print(f"Image not found: {image_path}")
    return False

valid_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.bmp'}
if path.suffix.lower() not in valid_extensions:
    print(f"Invalid format: {path.suffix}")
    return False

return True
undefined
from pathlib import Path
def validate_image(image_path: str) -> bool: """验证图片文件。""" path = Path(image_path)
if not path.exists():
    print(f"未找到图片: {image_path}")
    return False

valid_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.bmp'}
if path.suffix.lower() not in valid_extensions:
    print(f"无效格式: {path.suffix}")
    return False

return True
undefined

Resources

资源

Version History

版本历史

  • 1.0.0 (2026-01-17): Initial release with template rendering, loops, conditionals, tables, images, mail merge

This skill provides comprehensive patterns for template-based document generation with docxtpl, refined from production document automation workflows.
  • 1.0.0 (2026-01-17): 初始版本,支持模板渲染、循环、条件判断、表格、图片插入和邮件合并

本技能文档提供了基于docxtpl的模板化文档生成的全面实践方案,源自生产环境中的文档自动化工作流。