Reputation: 3067
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
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
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
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