From 935af5f1882ba4993f7a8d7568e7833bf2027b85 Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Mon, 30 May 2016 16:58:55 +0200 Subject: [PATCH] commit a bunch of invoice related stuff - generate pdf invoice, save to archive, email to customer --- bornhack/settings/base.py | 2 + bornhack/settings/env.dist | 1 + bornhack/settings/production.py | 1 + shop/email.py | 68 ++++++++++++++++ shop/management/commands/invoice-worker.py | 48 ++++++++--- shop/models.py | 5 ++ shop/pdf.py | 95 ++++++++++++---------- 7 files changed, 162 insertions(+), 58 deletions(-) create mode 100644 shop/email.py diff --git a/bornhack/settings/base.py b/bornhack/settings/base.py index 3832ec40..0bad6474 100644 --- a/bornhack/settings/base.py +++ b/bornhack/settings/base.py @@ -103,3 +103,5 @@ TICKET_CATEGORY_ID = env('TICKET_CATEGORY_ID') COINIFY_API_KEY = env('COINIFY_API_KEY') COINIFY_API_SECRET = env('COINIFY_API_SECRET') + +PDF_ARCHIVE_PATH='/usr/local/www/pdf_arhcive/' \ No newline at end of file diff --git a/bornhack/settings/env.dist b/bornhack/settings/env.dist index cbad7012..2eb5071f 100644 --- a/bornhack/settings/env.dist +++ b/bornhack/settings/env.dist @@ -7,6 +7,7 @@ EMAIL_HOST_USER='mymailuser' EMAIL_HOST_PASSWORD='mymailpassword' EMAIL_USE_TLS=True EMAIL_FROM='noreply@example.com' +ARCHIVE_EMAIL='archive@example.com' EPAY_MERCHANT_NUMBER=something EPAY_MD5_SECRET=something TICKET_CATEGORY_ID='' diff --git a/bornhack/settings/production.py b/bornhack/settings/production.py index 98fabf07..750c07b5 100644 --- a/bornhack/settings/production.py +++ b/bornhack/settings/production.py @@ -20,6 +20,7 @@ EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD') EMAIL_USE_TLS = env('EMAIL_USE_TLS') DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL') SERVER_EMAIL = env('DEFAULT_FROM_EMAIL') +ARCHIVE_EMAIL = env('ARCHIVE_EMAIL') LOGGING = { 'version': 1, diff --git a/shop/email.py b/shop/email.py new file mode 100644 index 00000000..c679fc94 --- /dev/null +++ b/shop/email.py @@ -0,0 +1,68 @@ +from django.core.mail import EmailMultiAlternatives +from django.conf import settings +from django.template.loader import render_to_string + + +def send_email(emailtype, recipient, formatdict, subject, sender='BornHack ', attachment=None): + ### determine email type, set template and attachment vars + html_template=None + + if emailtype == 'invoice': + text_template = 'emails/invoice.txt' + html_template = 'emails/invoice.html' + attachment_filename = formatdict['attachmentname'] + elif emailtype == 'testmail': + text_template = 'emails/testmail.txt' + else: + print 'Unknown email type: %s' % emailtype + return False + + try: + ### put the basic email together + msg = EmailMultiAlternatives(subject, render_to_string(text_template, formatdict), sender, [recipient], [settings.ARCHIVE_EMAIL]) + + ### is there a html version of this email? + if html_template: + msg.attach_alternative(render_to_string(html_template, formatdict), 'text/html') + + ### is there an attachment to this mail? + if attachment: + msg.attach(attachment_filename, attachment, 'application/pdf') + + except Exception as E: + print 'exception while rendering email: %s' % E + return False + + ### send the email + msg.send() + + ### all good + return True + + +def send_invoice_email(invoice, attachment): + # put formatdict together + formatdict = { + 'order': invoice.order, + 'attachmentname': invoice.filename, + } + + subject = 'BornHack invoice %s' % order.pk + + # send mail + return send_email( + emailtype='invoice', + recipient=order.user.email, + formatdict=formatdict, + subject=subject, + sender='noreply@bornfiber.dk', + ) + + +def send_test_email(recipient): + return send_email( + emailtype='testmail', + recipient=recipient, + subject='testmail from bornhack website', + ) + diff --git a/shop/management/commands/invoice-worker.py b/shop/management/commands/invoice-worker.py index d42440db..1889c3f9 100644 --- a/shop/management/commands/invoice-worker.py +++ b/shop/management/commands/invoice-worker.py @@ -5,7 +5,7 @@ from shop.email import send_invoice_email class Command(BaseCommand): args = 'none' - help = 'Send out invoices that has not been sent yet' + help = 'Send out invoices that have not been sent yet' def handle(self, *args, **options): self.stdout.write('Invoice worker running...') @@ -14,25 +14,47 @@ class Command(BaseCommand): for order in Order.objects.filter(paid=True, invoice__isnull=True): ### generate invoice for this Order Invoice.objects.create(order=order) + self.stdout.write('Generated Invoice object for order %s' % order) + + ### check if we need to generate any pdf invoices + for invoice in Invoice.objects.filter(pdf_generated=False): + # put the dict with data for the pdf together + formatdict = { + 'invoice': invoice, + } + # generate the pdf + try: + pdffile = generate_pdf_letter( + filename=invoice.filename, + template='invoice.html', + formatdict=formatdict, + ) + self.stdout.write('Generated pdf for invoice %s' % invoice) + except Exception as E: + self.stdout.write('ERROR: Unable to generate PDF file for invoice #%s. Error: %s' % (invoice.pk, E)) + continue + # so, do we have a pdf? + if not pdffile: + self.stdout.write('ERROR: Unable to generate PDF file for invoice #%s' % invoice.pk) + continue + + # update invoice object + invoice.pdf_generated=True + invoice.save() ### check if we need to send out any invoices - for invoice in Invoice.objects.filter(sent_to_customer=False): - ### generate PDF invoice - try: - pdffile = generate_pdf_letter('invoice.html', formatdict) - except Exception as E: - self.stdout.write('ERROR: Unable to generate PDF file. Error: %s' % E) - continue - if not pdffile: - self.stdout.write('ERROR: Unable to generate PDF file') - continue + for invoice in Invoice.objects.filter(sent_to_customer=False, pdf_generated=True): + # read the pdf invoice from the archive + with open(settings.INVOICE_ARCHIVE_PATH+invoice.filename, 'r') as fh: + pdffile = fh.read() - if send_invoice_email(recipient=invoice.order.user.email, invoice=invoice, attachment=pdffile): + # send the email + if send_invoice_email(invoice=invoice, attachment=pdffile): self.stdout.write('OK: Invoice email sent to %s' % order.user.email) invoice.sent_to_customer=True invoice.save() else: - self.stdout.write('ERROR: Unable to send invoice email to %s' % order.user.email) + self.stdout.write('ERROR: Unable to send invoice email for order %s to %s' % (order.pk, order.user.email)) ### pause for a bit sleep(60) diff --git a/shop/models.py b/shop/models.py index 41c3f25c..e48d0ecb 100644 --- a/shop/models.py +++ b/shop/models.py @@ -252,6 +252,7 @@ class EpayPayment(CreatedUpdatedModel, UUIDModel): class Invoice(CreatedUpdatedModel): order = models.OneToOneField('shop.Order') + pdf_generated = models.BooleanField(default=False) sent_to_customer = models.BooleanField(default=False) def __str__(self): @@ -263,6 +264,10 @@ class Invoice(CreatedUpdatedModel): self.sent_to_customer, ) + @property + def filename(self): + return 'bornhack_invoice_%s.pdf' % self.pk + class CoinifyAPIInvoice(CreatedUpdatedModel): invoicejson = JSONField() diff --git a/shop/pdf.py b/shop/pdf.py index 077389f5..8a7c5cfa 100644 --- a/shop/pdf.py +++ b/shop/pdf.py @@ -1,45 +1,50 @@ -from wkhtmltopdf.views import PDFTemplateResponse -from PyPDF2 import PdfFileWriter, PdfFileReader -from django.test.client import RequestFactory -from django.conf import settings -import StringIO - -def generate_pdf_letter(template, formatdict): - ### produce text-only PDF from template - pdfgenerator = PDFTemplateResponse( - request=RequestFactory().get('/'), - template=template, - context=formatdict, - cmd_options={ - 'margin-top': 40, - 'margin-bottom': 50, - }, - ) - textonlypdf = StringIO.StringIO() - textonlypdf.write(pdfgenerator.rendered_content) - - ### create a blank pdf to work with - finalpdf = PdfFileWriter() - - ### open the text-only pdf - pdfreader = PdfFileReader(textonlypdf) - - ### get watermark from watermark file - watermark = PdfFileReader(open(settings.LETTERHEAD_PDF_PATH, 'rb')) - - ### add the watermark to all pages - for pagenum in xrange(pdfreader.getNumPages()): - page = pdfreader.getPage(pagenum) - try: - page.mergePage(watermark.getPage(0)) - except ValueError: - ### watermark pdf might be broken? - return False - ### add page to output - finalpdf.addPage(page) - - ### return a file object with the data - returnfile = StringIO.StringIO() - finalpdf.write(returnfile) - return returnfile - +from wkhtmltopdf.views import PDFTemplateResponse +from PyPDF2 import PdfFileWriter, PdfFileReader +from django.test.client import RequestFactory +from django.conf import settings +import StringIO + +def generate_pdf_letter(filename, template, formatdict): + ### produce text-only PDF from template + pdfgenerator = PDFTemplateResponse( + request=RequestFactory().get('/'), + template=template, + context=formatdict, + cmd_options={ + 'margin-top': 40, + 'margin-bottom': 50, + }, + ) + textonlypdf = StringIO.StringIO() + textonlypdf.write(pdfgenerator.rendered_content) + + ### create a blank pdf to work with + finalpdf = PdfFileWriter() + + ### open the text-only pdf + pdfreader = PdfFileReader(textonlypdf) + + ### get watermark from watermark file + watermark = PdfFileReader(open(settings.LETTERHEAD_PDF_PATH, 'rb')) + + ### add the watermark to all pages + for pagenum in xrange(pdfreader.getNumPages()): + page = pdfreader.getPage(pagenum) + try: + page.mergePage(watermark.getPage(0)) + except ValueError: + ### watermark pdf might be broken? + return False + ### add page to output + finalpdf.addPage(page) + + ### save the generated pdf to the archive + with open(settings.PDF_ARCHIVE_PATH+filename, 'w') as fh: + finalpdf.write(fh) + self.stdout.write('Saved pdf to archive: %s' % settings.PDF_ARCHIVE_PATH+filename) + + ### return a file object with the data + returnfile = StringIO.StringIO() + finalpdf.write(returnfile) + return returnfile +