From 4f61a95d83cab65ece213377a25711e24542e9ce Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Tue, 21 Mar 2017 13:35:00 +0100 Subject: [PATCH] refactor invoiceworker to be run by uwsgi, support graceful shutdown, and support reloading code without exiting --- src/shop/invoiceworker.py | 65 +++++++++ .../management/commands/invoice-worker.py | 137 ++++-------------- 2 files changed, 97 insertions(+), 105 deletions(-) create mode 100644 src/shop/invoiceworker.py diff --git a/src/shop/invoiceworker.py b/src/shop/invoiceworker.py new file mode 100644 index 00000000..8d60db13 --- /dev/null +++ b/src/shop/invoiceworker.py @@ -0,0 +1,65 @@ +from django.core.files import File +from django.conf import settings +from django.utils import timezone +from shop.pdf import generate_pdf_letter +from shop.email import send_invoice_email, send_creditnote_email +from shop.models import Order, CustomOrder, Invoice, CreditNote +from decimal import Decimal +import logging + + +def run_invoice_worker(): + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + # check if we need to generate any invoices for shop orders + for order in Order.objects.filter(paid=True, invoice__isnull=True): + # generate invoice for this Order + Invoice.objects.create(order=order) + logger.info('Generated Invoice object for %s' % order) + + # check if we need to generate any invoices for custom orders + for customorder in CustomOrder.objects.filter(invoice__isnull=True): + # generate invoice for this CustomOrder + Invoice.objects.create(customorder=customorder) + logger.info('Generated Invoice object for %s' % customorder) + + # check if we need to generate any pdf invoices + for invoice in Invoice.objects.filter(pdf=''): + # put the dict with data for the pdf together + formatdict = { + 'invoice': invoice, + } + + # generate the pdf + try: + if invoice.customorder: + template='pdf/custominvoice.html' + else: + template='pdf/invoice.html' + pdffile = generate_pdf_letter( + filename=invoice.filename, + template=template, + formatdict=formatdict, + ) + logger.info('Generated pdf for invoice %s' % invoice) + except Exception as E: + logger.error('ERROR: Unable to generate PDF file for invoice #%s. Error: %s' % (invoice.pk, E)) + continue + + # so, do we have a pdf? + if not pdffile: + logger.error('ERROR: Unable to generate PDF file for invoice #%s' % invoice.pk) + continue + + # update invoice object with the file + invoice.pdf.save(invoice.filename, File(pdffile)) + invoice.save() + + ############################################################### + # check if we need to send out any invoices (only for shop orders, and only where pdf has been generated) + for invoice in Invoice.objects.filter(order__isnull=False, sent_to_customer=False).exclude(pdf=''): + # send the email + if send_invoice_email(invoice=invoice): + logger.info('OK: Invoice email sent to %s' % invoice.order.user.email) + diff --git a/src/shop/management/commands/invoice-worker.py b/src/shop/management/commands/invoice-worker.py index 2ad55e88..7ff6d6a6 100644 --- a/src/shop/management/commands/invoice-worker.py +++ b/src/shop/management/commands/invoice-worker.py @@ -1,119 +1,46 @@ from django.core.management.base import BaseCommand -from django.core.files import File -from django.conf import settings -from django.utils import timezone -from shop.pdf import generate_pdf_letter -from shop.email import send_invoice_email, send_creditnote_email -from shop.models import Order, CustomOrder, Invoice, CreditNote +from shop import invoiceworker from time import sleep -from decimal import Decimal +import signal, sys +import logging class Command(BaseCommand): args = 'none' help = 'Generate invoices and credit notes, and email invoices that have not been sent yet' + exit = False - def output(self, message): - self.stdout.write('%s: %s' % (timezone.now().strftime("%Y-%m-%d %H:%M:%S"), message)) + def __init__(self): + logging.basicConfig(level=logging.INFO) + self.logger = logging.getLogger('bornhack.%s' % __name__) + + def reload_worker_code(self, signum, frame): + self.logger.info("Reloading shop.invoiceworker module...") + reload(invoiceworker) + self.logger.info("Done reloading shop.invoiceworker module") + + def clean_exit(self, signum, frame): + self.logger.info("SIGTERM received, exiting gracefully soon...") + self.exit = True def handle(self, *args, **options): - self.output('Invoice worker running...') + self.logger.info("Connecting signals...") + signal.signal(signal.SIGHUP, self.reload_worker_code) + signal.signal(signal.SIGTERM, self.clean_exit) + signal.signal(signal.SIGINT, self.clean_exit) + + self.logger.info("Entering main loop...") while True: - # check if we need to generate any invoices for shop orders - for order in Order.objects.filter(paid=True, invoice__isnull=True): - # generate invoice for this Order - Invoice.objects.create(order=order) - self.output('Generated Invoice object for %s' % order) + # run invoiceworker + invoiceworker.run_invoice_worker() - # check if we need to generate any invoices for custom orders - for customorder in CustomOrder.objects.filter(invoice__isnull=True): - # generate invoice for this CustomOrder - Invoice.objects.create(customorder=customorder) - self.output('Generated Invoice object for %s' % customorder) - - # check if we need to generate any pdf invoices - for invoice in Invoice.objects.filter(pdf=''): - # put the dict with data for the pdf together - formatdict = { - 'invoice': invoice, - } - - # generate the pdf - try: - if invoice.customorder: - template='pdf/custominvoice.html' - else: - template='pdf/invoice.html' - pdffile = generate_pdf_letter( - filename=invoice.filename, - template=template, - formatdict=formatdict, - ) - self.output('Generated pdf for invoice %s' % invoice) - except Exception as E: - self.output('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.output('ERROR: Unable to generate PDF file for invoice #%s' % invoice.pk) - continue - - # update invoice object with the file - invoice.pdf.save(invoice.filename, File(pdffile)) - invoice.save() - - ############################################################### - # check if we need to send out any invoices (only for shop orders, and only where pdf has been generated) - for invoice in Invoice.objects.filter(order__isnull=False, sent_to_customer=False).exclude(pdf=''): - # send the email - if send_invoice_email(invoice=invoice): - self.output('OK: Invoice email sent to %s' % invoice.order.user.email) - invoice.sent_to_customer=True - invoice.save() + # sleep for 60 seconds, but check sys.exit every second + i = 0 + while i < 60: + if self.exit: + self.logger.info("Graceful exit requested, calling sys.exit(0) now") + sys.exit(0) else: - self.output('ERROR: Unable to send invoice email for order %s to %s' % (invoice.order.pk, invoice.order.user.email)) - - ############################################################### - # check if we need to generate any pdf creditnotes? - for creditnote in CreditNote.objects.filter(pdf=''): - # put the dict with data for the pdf together - formatdict = { - 'creditnote': creditnote, - } - - # generate the pdf - try: - pdffile = generate_pdf_letter( - filename=creditnote.filename, - template='pdf/creditnote.html', - formatdict=formatdict, - ) - self.output('Generated pdf for creditnote %s' % creditnote) - except Exception as E: - self.output('ERROR: Unable to generate PDF file for creditnote #%s. Error: %s' % (creditnote.pk, E)) - continue - - # so, do we have a pdf? - if not pdffile: - self.output('ERROR: Unable to generate PDF file for creditnote #%s' % creditnote.pk) - continue - - # update creditnote object with the file - creditnote.pdf.save(creditnote.filename, File(pdffile)) - creditnote.save() - - ############################################################### - # check if we need to send out any creditnotes (only where pdf has been generated) - for creditnote in CreditNote.objects.filter(sent_to_customer=False).exclude(pdf=''): - # send the email - if send_creditnote_email(creditnote=creditnote): - self.output('OK: Creditnote email sent to %s' % creditnote.user.email) - creditnote.sent_to_customer=True - creditnote.save() - else: - self.output('ERROR: Unable to send creditnote email for creditnote %s to %s' % (creditnote.pk, creditnote.user.email)) - - # pause for a bit - sleep(60) + i += 1 + sleep(1)