【问题标题】:xhtml2pdf - Problem with displaying table rows using django forloop tagxhtml2pdf - 使用 django forloop 标签显示表格行的问题
【发布时间】:2026-02-19 05:10:01
【问题描述】:

我要做的是创建一个包含多个表格行的发票 pdf 文件。表格行将在 Django 中使用 for 循环创建。问题是 for 循环标记内的数据在 pdf 文件中不可见。 您可以查看下面的屏幕截图。 Django 正确呈现 invoice.html 模板,因此代码有效,但 pdf 文件包含没有任何表格行的空框架。要从 html 呈现 pdf,我使用的是 xhtml2pdf。

how django render the invoice.html template

how pdf file looks like

invoice.html

<html>
    <head>
        {% load static %}
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta charset="UTF-8">
      <style>
        @font-face {
            font-family: Roboto;
            src: url('{% static 'fonts/Roboto-Regular.ttf' %}');
        }

        @font-face {
            font-family: Roboto-bold;
            src: url('{% static 'fonts/Roboto-Bold.ttf' %}');
            font-weight: bold;
        }

        @page {
            size: a4 portrait;
            @frame header_frame {           /* Static Frame */
                -pdf-frame-content: frame_header_left;
                left: 50pt; width: 245pt; top: 30pt; height: 150pt;
            }
            @frame header_frame {           /* Static Frame */
                -pdf-frame-content: frame_header_right;
                left: 300pt; width: 245pt; top: 50pt; height: 150pt;
            }

            @frame content_frame {          /* Content Frame */
                -pdf-frame-content: frame_invoice_number;
                left: 50pt; width: 512pt; top: 170pt; height: 30pt;
            }

            @frame col1 {
                -pdf-frame-content: frame_col1;
                left: 50pt; width: 245pt; top: 220pt; height: 130pt;
            }

            @frame col2 {
                -pdf-frame-content: frame_col2;
                left: 300pt; width: 245pt; top: 220pt; height: 130pt;
            }

            @frame frame_services {
                -pdf-frame-content: frame_services;
                left: 50pt; width: 512pt; top: 380pt; height: 250pt;
                -pdf-frame-border: 1;
            }

            @frame content_frame {
                -pdf-frame-content: frame_summary;
                left: 465pt; width: 100pt; top: 590pt; height: 50pt;
            }

            @frame content_frame {
                -pdf-frame-content: frame_vat;
                left: 50pt; width: 512pt; top: 590pt; height: 150pt;
            }

            @frame content_frame {
                -pdf-frame-content: frame_signatures_left;
                left: 50pt; width: 140pt; top: 725pt; height: 70pt;
            }

            @frame content_frame {
                -pdf-frame-content: frame_signatures_right;
                left: 400pt; width: 140pt; top: 725pt; height: 70pt;
                left: 400pt; width: 140pt; top: 725pt; height: 70pt;
            }

            @frame content_frame {
                -pdf-frame-content: footer_content;
                left: 50pt; width: 512pt; top: 775pt; height: 50pt;
            }
    }

        body {
          background-color: white;
          font-family: "Roboto", sans-serif;
        }

        .right{
            font-size: 10px;
            text-align: right;

        }
        .left{
            text-align: left;
        }

        .invoice-number {
            font-size: 18px;
            font-family: "Roboto-bold", sans-serif;
        }

        .col-titles {
            font-size: 16px;
            text-decoration: underline;
            font-family: "Roboto-bold", sans-serif;
         }

         .footer {
            font-size: 8px;
            text-align: center;
         }

         p{
            font-size: 10px;
            line-height: 0;
         }

         table {
            border-bottom: 1px solid #ddd;
            text-align: center;
        }

        td, td {
            border-bottom: 1px solid #ddd;
            vertical-align: middle;
        }

        .summary{
             border-bottom: 1px solid #ddd;
        }

        .signatures {
            border-top: 1px solid black;
            font-size: 8px;
            text-align: center;
        }

         th {
            height: 36px;

         }
         td {
            height: 25px;
         }


      </style>
    </head>
    <body>
        <div>
            <div>
                <div id="frame_header_left" class="left">
                    <img src="{% static 'invoices/logo.png' %}" alt="logo" width="150" height="112">
                </div>
                <div id="frame_header_right" class="right">
                    <p>Miejsce wystawienia: Żabno</p>
                    <p>Data badania: {{ invoice.data_badania }}</p>
                    <p>Data wystawienia: {{ invoice.data_wystawienia_faktury }}</p>
                </div>
            </div>
            <div id="frame_invoice_number">
                <div class="invoice-number">
                    <h2>Faktura nr: {{ invoice.numer }}</h2>
                </div>
            </div>
            <div id="frame_col1">
                <p class="col-titles">Sprzedawca</p>
                <div>
                    <p>MEDIKAP</p>
                    <p>ul. Plac Grunwaldzki 15B, 33-240 Żabno</p>
                    <p>NIP: 999999999</p>
                    <p>REGON: 9999999</p>
                    <p>Bank: ING Bank Śląski</p>
                    <p>Nr konta: 12 1234 1234 1243 1243 214 1244</p>
                </div>
            </div>

            <div id="frame_col2">
                <p class="col-titles">Nabywca</p>
                <div>
                    <p> {{ invoice.firma}} </p>
                    <p> ul. {{ invoice.firma.ulica }} </p>
                    <p> {{ invoice.firma.kod_pocztowy }} {{ invoice.firma.miasto}}</p>
                    <p> NIP: {{ invoice.firma.nip }}</p>
                    <p> REGON: {{ invoice.firma.regon }}</p>
                    <p> forma płatności: {{ invoice.get_forma_platnosci_display}}</p>
                </div>
            </div>

            <div id="frame_services">
                <table>
                    <tr>
                        <th style="width: 50px;"> # </th>
                        <th style="width: 600px;"> Nazwa usługi</th>
                        <th style="width: 100px;"> Ilość</th>
                        <th style="width: 100px;"> Rabat[%]</th>
                        <th style="width: 100px;"> Cena usługi</th>
                        <th style="width: 100px;"> Wartość</th>
                        <th style="width: 100px;"> Wartość z rabatem:</th>
                    </tr>

                {% for service in services_items %}
                    <tr>
                        <td> test </td>
                        <td> test </td>
                    </tr>
                {% endfor %}
                </table>
            </div>
            <div id="frame_summary" class="summary">
                <p> : {{ service.get_total_value }} PLN</p>
                <p> Wartość z uwzględnieniem {{ invoice.rabat}}% rabatu: {{ discounted_value|floatformat:"-2" }} PLN</p>
            </div>

            <div id="frame_vat">
                <p> Podstawa zwolnienia z VAT: </p>
                <p> Zwolnienie ze względu na zakres wykonywanych czynności (art. 43 ust.1) pkt 19 Ustawy o VAT</p>
            </div>

            <div id="frame_signatures_left">
                <p class="signatures"> podpis osoby upoważnionej do odbioru faktury</p>
            </div>

            <div id="frame_signatures_right">
                <p class="signatures"> podpis osoby upoważnionej do wystawienia faktury</p>
            </div>

            <div id="footer_content" >
                <p class="footer">MEDIKAP Maria K.</p>
                <p class="footer">Plac Grunwaldzki 15B, 33-240 Żabno</p>
                <p class="footer">e-mail: gabinet.medikap@gmail.com tel: 539 993 332</p>
                <p class="footer">NIP: 9930212793 REGON: 852441210</p>
            </div>

        </div>
    </body>
</html>

views.py/DetailsInvoice

class DetailsInvoice(generic.View):
    template_name = 'invoices/invoice_detail.html'
    form_class = DetailInvoiceForm
    success_url = reverse_lazy("invoices:list")

    def get(self, request, invoice_id):
        current_invoice = get_object_or_404(Invoice, id=invoice_id)
        form = self.form_class(instance=current_invoice)
        request.session['invoice_id'] = current_invoice.id

        services = Service.objects.all()
        all_service_items = ServiceItem.objects.all().filter(faktura = current_invoice).order_by('usluga')

        context = {
            'invoice': current_invoice,
            'form' : form,
            'services' : services,
            'services_items' : all_service_items,
        }

        return render(request, self.template_name, context)

    def post(self, request, invoice_id):

        current_invoice = get_object_or_404(Invoice, id=invoice_id)
        form = self.form_class(request.POST, instance=current_invoice)

        all_service_items = ServiceItem.objects.all().filter(faktura=current_invoice)

        context = {
            'invoice' : current_invoice,
        }

        pdf = render_to_pdf('invoices/invoice.html', context)
        services_assigned_to_invoice = current_invoice.uslugi.all()

        if 'update-data' in request.POST and form.is_valid():
            for service in all_service_items:
                service_item = get_object_or_404(ServiceItem, id=service.id)
                quantity_input = request.POST.get('quantity-' + str(service.id))
                discount_input = request.POST.get('discount-' + str(service.id))

                service_item.ilosc = int(quantity_input)
                service_item.rabat = int(discount_input)
                service_item.save()

            form.save()

            for service_item in all_service_items:
                if service_item.usluga not in services_assigned_to_invoice:
                    service_item.delete()

            for single_service in services_assigned_to_invoice:
                new_service_item, created = ServiceItem.objects.get_or_create(usluga=single_service, faktura=current_invoice)

            messages.success(request, 'Pomyślnie zaktualizowane dane')
            return HttpResponseRedirect(self.request.META.get('HTTP_REFERER'))

        if 'view-pdf' in request.POST:
            return HttpResponse(pdf, content_type='application/pdf')

        if 'download-pdf' in request.POST:
            response = HttpResponse(pdf, content_type='application/pdf')
            filename = f"Faktura {current_invoice.numer}.pdf"
            content = "attachment; filename={}".format(filename)
            response['Content-Disposition'] = content
            return response
        else:
            return redirect('invoices:list')

渲染函数

def render_to_pdf(template_src, context_dict={}):
    template = get_template(template_src)
    html = template.render(context_dict)
    result = BytesIO()
    pdf = pisa.pisaDocument(BytesIO(html.encode("utf-8")), result, link_callback=link_callback)
    if not pdf.err:
        return HttpResponse(result.getvalue(), content_type='application/pdf')
    return None

【问题讨论】:

  • 请显示您调用xhtml2pdf的代码部分以创建您的PDF。
  • 我更新了我的第一篇文章,因此您可以使用 ctrl+F 与:pdf = render_to_pdf('invoices/invoice.html', context)。在我的文章的最后,你还会发现一个渲染函数。

标签: django xhtml2pdf


【解决方案1】:

您传递给 PDF 的上下文变量与传递给 HTML 模板的上下文变量不同。传递给您的 HTML 模板:

    context = {
        'invoice': current_invoice,
        'form' : form,
        'services' : services,
        'services_items' : all_service_items,
    }

虽然您只是传递给您的 PDF:

    context = {
        'invoice' : current_invoice,
    }

services_items 是您的 PDF 模板似乎缺少的那个。因此,因为for service in services_items 在您的 PDF 模板中是一个空/不存在的列表,所以它不会呈现任何行。将来您可以通过在您的 for 循环中添加 {% empty %} 部分来检查这一点:

            {% for service in services_items %}
                <tr>
                    <td> test </td>
                    <td> test </td>
                </tr>
            {% empty %}
                No items!
            {% endfor %}

【讨论】:

  • 哇。谢谢。这太容易了……顺便说一句,在 get 和 post 方法中进行相同的查询以接收 all_service_items 是一种正确的方法吗?或者也许有更简单的方法来做到这一点?
  • @smg 你可以创建一个单独的函数来返回 all_service_items 并在你需要的地方调用它。