Дикий Григорий

Full-stack веб-разработчик

Интернет-магазин на Django. Генерация PDF. Часть 10

Когда пользователь оплатил за товары, ему нужно сгенерировать PDF файл с информацией о заказе. Существует несколько Python библиотек для работы с PDF файлами. Мы будем использовать библиотеку для конвертации HTML в PDF файл. Для этого нам нужно установить WeasyPrint.

$ pip3 install WeasyPrint

Теперь нам надо создать шаблон для нашего PDF файла. Для этого в директории orders в папке template/orders/order/ создадим файл pdf.html со следующим содержимым:

<html>
  <body>
    <h1>Онлайн-магазин</h1>
    <p>
      Номер заказа: <b>{{ order.id }}</b> <br>
      <span class="secondary">
        {{ order.created }}
      </span>
    </p>
    <h3>Покупатель</h3>
    <p>
      {{ order.first_name }} {{ order.last_name }}<br>
      {{ order.email }} <br>
      {{ order.address }} <br>
      {{ order.postal_code }}, {{ order.city }}
    </p>
    <h3>Купленные товары</h3>
    <table class="table">
      <thead>
        <tr>
          <th>Товар</th>
          <th>Стоймость</th>
          <th>Количество</th>
          <th>Всего</th>
        </tr>
      </thead>
      <tbody>
        {% for item in order.items.all %}
          <tr class="row{% cycle "1" "2" %}">
            <td>{{ item.product.name }}</td>
            <td>{{ item.price }}</td>
            <td>{{ item.quantity }}</td>
            <td>{{ item.get_cost }}</td>
          </tr>
        {% endfor %}
        <tr class="total">
          <td colspan="3">Всего:</td>
          <td class="num">{{ order.get_total_cost }}</td>
        </tr>
      </tbody>
    </table>
    <span class="{% if order.paid %}paid{% else %}pending{% endif %}">
      {% if order.paid %}Оплачен{% else %}Ожидает оплаты{% endif %}
    </span>
  </body>
</html>

Этот шаблон выводит основную информацию о покупателе и о товарах, купленных им в нашем онлайн-магазине. Также мы добавили сообщение, которое сообщает если был оплачен товар или он еще в статусе оплаты. Теперь нам надо во вьюхе настроить функцию для рендеринга в PDF этой HTML страницы, для этого откроем файл views.py в директории orders и добавим:

from django.conf import settings
from django.http import HttpResponse
from django.template.loader import render_to_string
import weasyprint


@staff_member_required
def AdminOrderPDF(request, order_id):
    order = get_object_or_404(Order, id=order_id)
    html = render_to_string('orders/order/pdf.html', {'order': order})
    response = HttpResponse(content_type='application/pdf')
    response['Content-Disposition'] = 'filename=order_{}.pdf'.format(order.id)
    weasyprint.HTML(string=html).write_pdf(response,
               stylesheets=[weasyprint.CSS(settings.STATIC_ROOT + 'css/bootstrap.min.css')])
    return response

Эта вьюха будет генерировать PDF файл из нашего HTML. Предварительно мы получаем нашу страницу через render_to_string(). В ответе от сервера response мы указываем имя файла, а также тип файла PDF. Также мы подключаем файл со стилями pdf.css для стилизации страницы. STATIC_ROOT нам нужно определить в settings.py следующим образом:

STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

Далее нам надо собрать все статические файлы в одно папку static при помощи команды:

$ python manage.py collectstatic

Теперь нужно настроить маршрутизатор для того, чтобы было возможно сделать PDF файл, для этого изменим файл urls.py в директории orders:

url(r'^admin/order/(?P<order_id>\d+)/pdf/$', views.AdminOrderPDF, name='AdminOrderPDF')

Теперь нам надо добавить возможность напечатать в PDF расширив нашу админ-панель. Для этого нужно изменить файл admin.py следующим образом:

def OrderPDF(obj):
    return format_html('<a href="{}">PDF</a>'.format(
        reverse('orders:AdminOrderPDF', args=[obj.id])
    ))
OrderPDF.short_description = 'В PDF'

B теперь добавим это поле в список отображаемых:

class OrderAdmin(admin.ModelAdmin):
    list_display = ['id', 'first_name', 'last_name', 'address','paid', 'created',
                    OrderDetail, OrderPDF]

Теперь зайдя в админ панель во вкладку заказы, вы должны увидеть наличие возможности сгенерировать PDF:

При нажатии на ссылку PDF вы получите:

Отправка PDF по email

Было бы хорошо отправить пользователю PDF файл о покупке через email. Для этого надо изменить файл signals.py в директории payment. Нужно импортировать следующие функции:

from django.conf import settings
from django.template.loader import render_to_string
from django.core.mail import EmailMessage
from io import BytesIO
import weasyprint

Теперь нужно отредактировать функцию PaymentNotification добавив после инструкции order.save() следующее:

....        
# Отправка Email
subject = 'Онлайн-магазин - заказ: {}'.format(order.id)
message = 'К email сообщению прикреплен PDF файл с информацией о вашем заказе.'
email = EmailMessage(subject, message, 'admin@mayshop.com', [order.email])

# Генерация PDF
html = render_to_string('orders/order/pdf.html', {'order': order})
out = BytesIO()
weasyprint.HTML(string=html).write_pdf(out,
stylesheets=[weasyprint.CSS(settings.STATIC_ROOT + 'css/bootstrap.min.css')])

# Прикрепляем pdf
email.attach('order_{}.pdf'.format(order.id), out.getvalue(), 'application/pdf')
email.send()
Учтите, что у нас не настроен SMTP для отправки сообщений. Для этого вы должны настроить его в settings.py.

Репозиторий проекта: github