Search code examples
djangopython-3.xdjango-templatesxhtml2pdf

Management of pdf with django through a template and sent as an attachment of an electronic mail


I am creating a site for student registrations with the django framework.

I have arrived at a place where I have to display the form to the user and I receive the information entered after treatment I have to send an email to the user user containing the information he has entered by putting them in a PDF which I must generate from a template containing the context dictionary.

For the moment the PDF that I can generate is possible only by sending it to the browser through the function HttpResponse, but the problem with that is that it is the user who decides to download it or not and I have more possibility to put the hand on the pdf to send it as an attachment.

I thought to open a database where there will be a field of type models.FileField, generate the pdf with the context dictionary through the library xhtml2pdf that I could save in the database with a signal and then send the email by taking the file as attachment.

None of the methods I tried to achieve this are successful for the moment. Here is my code:

utils.py

from io import BytesIO
from django.http import HttpResponse
from django.template.loader import get_template

from xhtml2pdf import pisa

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("ISO-8859-1")), result)
    if not pdf.err:
        return HttpResponse(result.getvalue(), content_type="application/pdf")
    return None

models.py

from django.conf import settings
from django.db.models.signals import pre_save, post_save

from django.core.mail import EmailMessage, EmailMultiAlternatives

from django.db import models

# Create your models here.


class MyModel(models.Model):
    order_id    = models.CharField(max_length=255)
    nom         = models.CharField(max_length=255)
    email       = models.EmailField()
    pdf         = models.FileField(upload_to="pdfs", null=True, blank=True)

def create_order_id(instance, new_order_id=None):

    order_id = instance.id

    if new_order_id is not None:
        order_id = new_order_id


    qs = MyModel.objects.filter(order_id=order_id)
    exists = qs.exists()

    if exists:
        new_order_id = "%s-%s" %(order_id.first().id)
        return create_order_id(instance, new_order_id)


    return order_id


def pre_save_receiver(sender, instance, *args, **kwargs):
    if not instance.order_id :
        instance.new_order_id = create_order_id(instance)




def send_mail_insciption(instance):

    subject = "Thank you"
    from_email = settings.EMAIL_HOST_USER
    to_email = [instance.email]
    body = "Votre inscription"

    email_pdf = EmailMultiAlternatives(

        subject = subject,
        body =body,
        from_email = from_email,
        to=to_email,    
        )


    email_pdf.attach_alternative(instance.pdf, "application/pdf")
    email_pdf.send()


def post_save_receiver(sender, instance, *args, **kwargs):
    send_mail_insciption(instance)




pre_save.connect(pre_save_receiver, sender=MyModel )
post_save.connect(post_save_receiver, sender=MyModel)


#views.py

def pdf_genarete(request):

    form = MyModelForm(request.POST or None)

    if form.is_valid():

        nom = form.cleaned_data.get("nom")
        email = form.cleaned_data.get("email")
        obj = MyModel.objects.create(nom=nom, email=email)

        context = {"models_instance": obj}

        pdf = render_to_pdf("pdfapp/template_pdf.html", context)
        filename = "mypdf_{}.pdf".format(obj.order_id)
        pdf.save()
        obj.pdf.save(filename, File(BytesIO(pdf.content)))

        return redirect(reverse("home"))

    return render(request, "pdfapp/formulaire.html", {"form": form})

After execution here is the error on the terminal:

 File "/home/michel/saintexupery/env/lib/python3.6/site-packages/django/db/models/base.py", line 769, in save_base
    update_fields=update_fields, raw=raw, using=using,
  File "/home/michel/saintexupery/env/lib/python3.6/site-packages/django/dispatch/dispatcher.py", line 178, in send
    for receiver in self._live_receivers(sender)
  File "/home/michel/saintexupery/env/lib/python3.6/site-packages/django/dispatch/dispatcher.py", line 178, in <listcomp>
    for receiver in self._live_receivers(sender)
  File "/home/michel/saintexupery/saintexupry/pdfapp/models.py", line 69, in post_save_receiver
    send_mail_insciption(instance)
  File "/home/michel/saintexupery/saintexupry/pdfapp/models.py", line 65, in send_mail_insciption
    email_pdf.send()
  File "/home/michel/saintexupery/env/lib/python3.6/site-packages/django/core/mail/message.py", line 294, in send
    return self.get_connection(fail_silently).send_messages([self])
  File "/home/michel/saintexupery/env/lib/python3.6/site-packages/django/core/mail/backends/smtp.py", line 110, in send_messages
    sent = self._send(message)
  File "/home/michel/saintexupery/env/lib/python3.6/site-packages/django/core/mail/backends/smtp.py", line 124, in _send
    message = email_message.message()
  File "/home/michel/saintexupery/env/lib/python3.6/site-packages/django/core/mail/message.py", line 254, in message
    msg = self._create_message(msg)
  File "/home/michel/saintexupery/env/lib/python3.6/site-packages/django/core/mail/message.py", line 440, in _create_message
    return self._create_attachments(self._create_alternatives(msg))
  File "/home/michel/saintexupery/env/lib/python3.6/site-packages/django/core/mail/message.py", line 450, in _create_alternatives
    msg.attach(self._create_mime_attachment(*alternative))
  File "/home/michel/saintexupery/env/lib/python3.6/site-packages/django/core/mail/message.py", line 393, in _create_mime_attachment
    Encoders.encode_base64(attachment)
  File "/usr/lib/python3.6/email/encoders.py", line 32, in encode_base64
    encdata = str(_bencode(orig), 'ascii')
  File "/home/michel/saintexupery/env/lib/python3.6/base64.py", line 534, in encodebytes
    _input_type_check(s)
  File "/home/michel/saintexupery/env/lib/python3.6/base64.py", line 520, in _input_type_check
    raise TypeError(msg) from err
TypeError: expected bytes-like object, not FieldFile

Solution

  • I your code you have this part:

    def send_mail_insciption(instance):
        ...
        email_pdf.attach_alternative(instance.pdf, "application/pdf")
        email_pdf.send()
    

    The error message (and traceback) say that the email expects bytes as attachment, but instance.pdf is a FieldFile.

    Try converting the file to bytes, maybe using instance.pdf.read() and let us know if that solves the problem.