Returning PDF through Django
September 30, 2011
I wanted to allow PDFs to be downloaded from my Django application. I wanted to log who did the download, and when it happened, and I also wanted to make sure that I stamped the PDF file with the username downloading, and then encrypted the file so it couldn't be changed.
Django View
Here's the view code. This returns a 404 if the key is invalid, or if it has expired, and then uses a helper class (PartPdf
) to return the PDF file directly from the function. We also set a Content-Disposition
header so that we control the filename that the PDF is saved as.
def download_pdf(request, pPdfKey): """ Download a pdf """ try: lKey = PdfDownloadKey.objects.filter(key=pPdfKey).select_related()[0] except IndexError: raise Http404 if lKey.expires < datetime.now(): return render_auth(request, 'pdf/expired.html') lResponse = HttpResponse(PartPdf(lKey.file.filename, pPdfKey, request), mimetype='application/pdf') lResponse['Content-Disposition'] = 'inline; filename="' + lKey.file.filename + '"' return lResponse
PartPdf Object
This object provides the logging functionality for the PDF, and also stamps it with the username then encrypts it to prevent changes. This class inherits directly from file
and is responsible for reading the file from disk and returning it.
class PartPdf(file): def __init__(self, pFilename, pPdfKey, pRequest, *args, **kwargs): self.filename = pFilename self.filepath = "%s/%s" % (settings.PDF_FILE_PATH, self.filename) self.request = pRequest self.key = pPdfKey lStampedFilepath = self.stamp_pdf() super(PartPdf, self).__init__(lStampedFilepath, *args, **kwargs) def stamp_pdf(self): """ Stamp pdf with current username """ # extract metadata lUsername = self.request.user.username lMetaDataFilename = "/tmp/%s_%s.metadata" % (self.filename, lUsername) lCommand = "pdftk %s dump_data output %s" % (self.filepath, lMetaDataFilename) os.system(lCommand) lMetaDataFile = open(lMetaDataFilename, "a") lMetaDataFile.write("InfoKey: user\n") lMetaDataFile.write("InfoValue: %s\n" % lUsername) lMetaDataFile.close() lOutputFilename = "/tmp/%s_%s" % (self.filename, lUsername) lCommand = "pdftk %s update_info %s output %s owner_pw PASSWORD allow printing" % (self.filepath, lMetaDataFilename, lOutputFilename) os.system(lCommand) def close(self): """ File has completed downloading so log it """ super(PartPdf, self).close() lCounter = PdfDownloadLog() lCounter.user = self.request.user lCounter.ip = self.request.META['REMOTE_ADDR'] lCounter.user_agent = self.request.META['HTTP_USER_AGENT'] lDownloadKey = PdfDownloadKey.objects.filter(key=self.key)[0] if lDownloadKey.downloads_available > 0: lDownloadKey.downloads_available -= 1 lDownloadKey.save() lCounter.key = lDownloadKey lCounter.save() lStampedFilepath = "/tmp/%s_%s" % (self.filename, self.request.user.username) os.remove(lStampedFilepath) os.remove("%s.metadata" % lStampedFilepath)
Testing
This project has 100% coverage, and I wanted to ensure I kept that. Here's the test code that makes sure the file can be downloaded and is logged correctly in the database.
lDownloadLogCountBefore = PdfDownloadLog.objects.all().count() response = c.get('/pdf/FRED/', {}, REMOTE_ADDR='127.1.2.3', HTTP_USER_AGENT='Useragent') self.assertEquals(response.status_code, 200) lContent = response.content self.assertIsNotNone(lContent) response.close() lDownloadLogCountAfter = PdfDownloadLog.objects.all().count() self.assertEquals(lDownloadLogCountBefore + 1, lDownloadLogCountAfter)