Luke Plant avatar Luke Plant committed 7e2aa70

Added 'expire_bookings' management command (to run as a cron job)

Comments (0)

Files changed (7)

cciw/bookings/email.py

     body = loader.render_to_string('cciw/bookings/place_confirmed_email.txt', c)
     subject = "CCIW booking - place confirmed"
     mail.send_mail(subject, body, settings.SERVER_EMAIL, [account.email])
+
+
+def send_booking_expiry_mail(account, bookings, expired):
+    if account.email == '':
+        return
+
+    c = {
+        'url_start': site_address_url_start(),
+        'account': account,
+        'bookings': bookings,
+        'expired': expired,
+        }
+    body = loader.render_to_string('cciw/bookings/place_expired_mail.txt', c)
+    if expired:
+        subject = "CCIW booking - booking expired"
+    else:
+        subject = "CCIW booking - booking expiry warning"
+    mail.send_mail(subject, body, settings.SERVER_EMAIL, [account.email])
Add a comment to this file

cciw/bookings/management/__init__.py

Empty file added.

Add a comment to this file

cciw/bookings/management/commands/__init__.py

Empty file added.

cciw/bookings/management/commands/expire_bookings.py

+from datetime import datetime, timedelta
+
+from django.core.management.base import BaseCommand
+
+from cciw.bookings.models import Booking
+from cciw.bookings.email import send_booking_expiry_mail
+
+class Command(BaseCommand):
+
+    def handle(self, *args, **options):
+
+        now = datetime.now()
+        nowplus12h = now + timedelta(0.5)
+
+        unconfirmed = Booking.objects.unconfirmed().order_by('account')
+        to_warn = unconfirmed.filter(booking_expires__lt=nowplus12h)
+        to_expire = unconfirmed.filter(booking_expires__lt=now)
+
+        # We do the 'to_expire' first, so we don't warn those that have already
+        # expired (works since query sets are lazy)
+        for booking_set, expired in [(to_expire, True),
+                                     (to_warn, False)]:
+            groups = []
+            last_account_id = None
+            for b in booking_set:
+                if last_account_id is None or b.account_id != last_account_id:
+                    group = []
+                    groups.append(group)
+                group.append(b)
+                last_account_id = b.account_id
+
+            for group in groups:
+                if expired:
+                    for b in group:
+                        b.expire()
+                        b.save()
+                send_booking_expiry_mail(group[0].account, group, expired)

cciw/bookings/models.py

     def confirm(self):
         self.booking_expires = None
 
+    def expire(self):
+        self.booking_expires = None
+        self.state = BOOKING_INFO_COMPLETE
+
     def is_user_editable(self):
         return self.state == BOOKING_INFO_COMPLETE
 

cciw/bookings/tests.py

 from django.test import TestCase
 from django.utils import simplejson
 
+from cciw.bookings.management.commands.expire_bookings import Command as ExpireBookingsCommand
 from cciw.bookings.models import BookingAccount, Price, Booking, book_basket_now
 from cciw.bookings.models import PRICE_FULL, PRICE_2ND_CHILD, PRICE_3RD_CHILD, PRICE_CUSTOM, PRICE_SOUTH_WALES_TRANSPORT, BOOKING_APPROVED, BOOKING_INFO_COMPLETE, BOOKING_BOOKED
 from cciw.cciwmain.common import get_thisyear
         resp2 = self.client.get(reverse('cciw.bookings.views.account_overview'))
         self.assertEqual(resp2.status_code, 302)
 
+
+class TestExpireBookingsCommand(CreatePlaceMixin, TestCase):
+
+    fixtures = ['basic']
+
+    def test_just_created(self):
+        """
+        Test no mail if just created
+        """
+        self.login()
+        self.create_place()
+
+        acc = self.get_account()
+        book_basket_now(acc.bookings.basket(get_thisyear()))
+
+        mail.outbox = []
+
+        ExpireBookingsCommand().handle()
+        self.assertEqual(len(mail.outbox), 0)
+
+    def test_warning(self):
+        """
+        Test that we get a warning email after 12 hours
+        """
+        self.login()
+        self.create_place()
+
+        acc = self.get_account()
+        book_basket_now(acc.bookings.basket(get_thisyear()))
+        b = acc.bookings.all()[0]
+        b.booking_expires = b.booking_expires - timedelta(0.6)
+        b.save()
+
+        mail.outbox = []
+        ExpireBookingsCommand().handle()
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertTrue("warning" in mail.outbox[0].subject)
+
+        b = acc.bookings.all()[0]
+        self.assertNotEqual(b.booking_expires, None)
+        self.assertEqual(b.state, BOOKING_BOOKED)
+
+    def test_expires(self):
+        """
+        Test that we get an expiry email after 24 hours
+        """
+        self.login()
+        self.create_place()
+
+        acc = self.get_account()
+        book_basket_now(acc.bookings.basket(get_thisyear()))
+        b = acc.bookings.all()[0]
+        b.booking_expires = b.booking_expires - timedelta(1.01)
+        b.save()
+
+        mail.outbox = []
+        ExpireBookingsCommand().handle()
+        # NB - should get one, not two (shouldn't get warning)
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertTrue("expired" in mail.outbox[0].subject)
+        self.assertTrue("have expired" in mail.outbox[0].body)
+
+        b = acc.bookings.all()[0]
+        self.assertEqual(b.booking_expires, None)
+        self.assertEqual(b.state, BOOKING_INFO_COMPLETE)
+
+    def test_grouping(self):
+        """
+        Test the emails are grouped as we expect
+        """
+        self.login()
+        self.create_place({'name':'Child One'})
+        self.create_place({'name':'Child Two'})
+
+        acc = self.get_account()
+        book_basket_now(acc.bookings.basket(get_thisyear()))
+        acc.bookings.update(booking_expires = datetime.now() - timedelta(1))
+
+        mail.outbox = []
+        ExpireBookingsCommand().handle()
+
+        # Should get one, not two, because they will be grouped.
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertTrue("expired" in mail.outbox[0].subject)
+        self.assertTrue("have expired" in mail.outbox[0].body)
+        self.assertTrue("Child One" in mail.outbox[0].body)
+        self.assertTrue("Child Two" in mail.outbox[0].body)
+
+        for b in acc.bookings.all():
+            self.assertEqual(b.booking_expires, None)
+            self.assertEqual(b.state, BOOKING_INFO_COMPLETE)

templates/cciw/bookings/place_expired_mail.txt

+{% load url from future %}{% autoescape off %}
+Dear {{ account.name }},
+{% if expired %}
+The following place(s) on CCIW camps have expired because no payment was
+received within 24 hours of booking online.
+{% else %}
+You have booked the following place(s) on CCIW camps, but have not yet
+paid:
+{% endif %}
+----------------------------------------
+{% for b in bookings %}
+Name: {{ b.name }}
+Camp: {{ b.camp }}
+Dates: {{ b.camp.start_date|date:"d F" }} to {{ b.camp.end_date|date:"d F Y" }}
+{% endfor %}
+----------------------------------------
+{% if expired %}
+The places have been moved back to your 'basket'. If you still want them,
+you can book and pay here:
+
+{{ url_start }}{% url 'cciw.bookings.views.list_bookings' %}
+
+{% else %}
+If you do not pay within 12 hours, the bookings will expire so that
+other people will be able to take the places.
+
+Please go to the following URL to pay online:
+
+{{ url_start }}{% url 'cciw.bookings.views.pay' %}
+{% endif %}
+If you think you have in fact paid, or have had some other problem
+with the web site that is preventing you from paying, please reply to
+this email and let us know, so that we can look into it.
+
+Thanks,
+
+The cciw.co.uk team.
+
+{% endautoescape %}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.