Fabio
Fabio

Reputation: 3067

Django/reportlab: Save generated pdf directly to FileField in AWS s3

I am writing a Django app, for which I need to generate a PDF, using reportlab, and save it to a FileField. My problem is that my media is stored in S3, and I don't know how to save this pdf directly to S3.

I am creating the pdf locally then reading it and saving to the FileField in s3, then deleting the local version, but I'm wondering if there's a better solution, where I can save the file directly to S3.

here's how I generate the contract now:

#temporary folder name to generate contract locally
folder_name = (
     '/tmp/'
     + '/contracts/'
     + gig.identifier
     )
mkdir_p(folder_name)
address = (
     folder_name
     + '/contract_'
     + lang
     + '.pdf'
 )
doc = SimpleDocTemplate(
     address,
     pagesize=A4,
     rightMargin=72,
     leftMargin=72,
     topMargin=120,
     bottomMargin=18
 )
doc.build(Story,
          canvasmaker=LogoCanvas)
local_file = open(address)
pdf_file = File(local_file)
contract = Contract.objects.create(lang=lang, gig=gig)
contract.contract_file.save('contract_'+lang+'.pdf', pdf_file)
contract.save()
#remove temporary folder
shutil.rmtree(folder_name)
return True

and the Contract model is defined like this:

class Contract(models.Model):
    gig = models.ForeignKey(Gig, related_name='contracts')
    lang = models.CharField(max_length=10, default='')
    contract_file = models.FileField(
        upload_to=get_contract_path,
        blank=True,
        null=True,
        default=None
    )

Upvotes: 4

Views: 4205

Answers (3)

Islam Murtazaev
Islam Murtazaev

Reputation: 1778

You can save generated pdfs directly to S3.

I use boto3 library to take care of storage in s3 and I think you are using the same approach.

from api.models import Entity
from django.core.files.base import ContentFile

entity = Entity.objects.create(**create_params)
pdf = render_to_pdf('invoice.html') # returns BytesIO
entity.pdf_field.save('invoice.pdf', ContentFile(pdf.getvalue()), save=True) 

Upvotes: 2

fannik
fannik

Reputation: 1153

You could use the standard Python module tempfile (import tempfile)

tempfile.TemporaryFile(mode='w+b', buffering=None, encoding=None, newline=None, suffix=None, prefix=None, dir=None)

Return a file-like object that can be used as a temporary storage area. The file is created securely, using the same rules as mkstemp(). It will be destroyed as soon as it is closed (including an implicit close when the object is garbage collected). Under Unix, the directory entry for the file is either not created at all or is removed immediately after the file is created. Other platforms do not support this; your code should not rely on a temporary file created using this function having or not having a visible name in the file system.

https://docs.python.org/3/library/tempfile.html Your using of the tempfile object is absolutely similar to regular file.

By the way you owe me 25$ =)

Upvotes: 0

raneq
raneq

Reputation: 127

You can use BytesIO for creating an in-memory stream instead of a file stream.

However, you still need to make reportlab to use that stream instead of a file.

Relying on the userguide, at 2.2 More about the Canvas, Canvas constructor has a filename parameter that can be both a string or a file descriptor. I have tested it with a SimpleDocTemplate, so it should work for you:

import io
stream = io.BytesIO()
doc = SimpleDocTemplate(
     stream,
     pagesize=A4,
     rightMargin=72,
     leftMargin=72,
     topMargin=120,
     bottomMargin=18
 )
doc.build(Story,
          canvasmaker=LogoCanvas)
pdf_buffer = stream.getBuffer()

I don't know S3, but I'm confident that this is enough to solve your problem. For instance, if you would like now to write this pdf to a file, you would do:

file = open("contract.pdf", "wb")
file.write(pdf_buffer)

Upvotes: 1

Related Questions