To generate a dynamic stamp or watermark which includes the username and time, we use reportlab.
One hurdle was to read the PDF file (in order to write that to a temporary file on the filesystem). The PDF files in our product are contained in a custom content type, in an array field of files. In order to correctly read the PDF from a field, i had to call str(field).
Below is some code from our product's wfsubscribers.py. This file and the code framework was generated by ArchGenXML, where we created a workflow transition script.
##code-section module-header #fill in your manual code here from DateTime import DateTime import os, sys from Products.CMFCore.utils import getToolByName import tempfile from reportlab.pdfgen.canvas import Canvas from reportlab.lib.units import cm ##/code-section module-header ... def approveDocument(obj, event): """generated workflow subscriber.""" # do only change the code section inside this function. if not event.transition \ or event.transition.id not in ['approve'] \ or obj != event.object: return ##code-section approveDocument #fill in your manual code here # create stamp file tmp = tempfile.mkdtemp() stamppath = "%s/stamp.pdf" % tmp canvas = Canvas(stamppath) canvas.setFontSize(0.5*cm) canvas.setFillColorRGB(1,0.75,0, alpha=0.75) user_id = event.status.get('actor') mtool = getToolByName(obj, 'portal_membership') member = mtool.getMemberById(user_id) fullname = member.getProperty('fullname') now = DateTime() message = "Approved by %s [%s] on %s" % (fullname, user_id, now.strftime('%Y/%m/%d %H:%M') ) canvas.drawString(1*cm, 1*cm, message) canvas.showPage() canvas.save() # iterate over documents fields = obj.getField('documents').Schema().fields() file_fields = [field for field in fields if field.type == 'file'] for i, field in enumerate(file_fields): # check if document is a pdf content_type = field.getContentType(obj) if content_type != 'application/pdf': obj.plone_log("File in field %s is not a PDF, skipping." % field) return # get raw file document = obj.getDocuments()[i] assert document.filename == field.getFilename(obj) file = str(field.getRaw(obj)) # write pdf document to tmp file infile_name = tmp + '/infile.pdf' infile = open(infile_name, 'wb') infile.write(str(file)) infile.close() # create stamped version of PDF outfile_name = tmp + '/outfile.pdf' command_line = 'pdftk %s stamp %s output %s' % (infile_name, stamppath, outfile_name) args = shlex.split(command_line) stampresult = subprocess.Popen(args) # wait for stamping to finish before going further sts = os.waitpid(stampresult.pid, 0) # read stamped pdf from file outfile = open(outfile_name,'r') stamped = outfile.read() outfile.close() # replace existing PDF with stamped version document.update_data(stamped) # clean up command_line = 'rm -rf %s' % tmp args = shlex.split(command_line) p = subprocess.Popen(args) return ##/code-section approveDocument