Commits

Luke Plant committed 975ab4c

Implemented 'move to shelf' (and back) functionality for bookings

Comments (0)

Files changed (5)

cciw/bookings/models.py

     def get_query_set(self):
         return super(BookingManager, self).get_query_set().select_related('camp', 'account')
 
-    def ready_to_book(self, year):
-        qs = self.get_query_set().filter(camp__year__exact=year)
+    def ready_to_book(self, year, shelved=False):
+        qs = self.get_query_set().filter(camp__year__exact=year, shelved=shelved)
         return qs.filter(state=BOOKING_INFO_COMPLETE) | qs.filter(state=BOOKING_APPROVED)
 
 
     price_type = models.PositiveSmallIntegerField(choices=PRICE_TYPES)
     amount_due = models.DecimalField(decimal_places=2, max_digits=10)
 
+    # State - user driven
+    shelved = models.BooleanField(default=False)
+
     # State - internal
     state = models.IntegerField(choices=BOOKING_STATES)
     created = models.DateTimeField(default=datetime.now)

cciw/bookings/tests.py

         newpath = reverse('cciw.bookings.views.add_place')
         self.assertTrue(resp['Location'].endswith(newpath))
 
+    def test_move_to_shelf(self):
+        self.login()
+        self.create_place()
+        acc = BookingAccount.objects.get(email=self.email)
+        b = acc.bookings.all()[0]
+        self.assertEqual(b.shelved, False)
+        resp = self.client.post(reverse('cciw.bookings.views.list_bookings'))
+
+        # Move to shelf button should be there
+        self.assertContains(resp, "name=\"shelve_%s\"" % b.id)
+
+        # Now click it
+        resp2 = self.client.post(reverse('cciw.bookings.views.list_bookings'), {'shelve_%s' % b.id: '1'})
+
+        # Should be changed
+        b2 = acc.bookings.all()[0]
+        self.assertEqual(b2.shelved, True)
+
+        # Different button should appear
+        self.assertNotContains(resp2, "name=\"shelve_%s\"" % b.id)
+        self.assertContains(resp2, "name=\"unshelve_%s\"" % b.id)
+
+        self.assertContains(resp2, "<h2>Shelf</h2>")
+
+    def test_move_to_basket(self):
+        self.login()
+        self.create_place()
+        acc = BookingAccount.objects.get(email=self.email)
+        b = acc.bookings.all()[0]
+        b.shelved = True
+        b.save()
+
+        # Move to basket button should be there
+        resp = self.client.get(reverse('cciw.bookings.views.list_bookings'))
+        self.assertContains(resp, "name=\"unshelve_%s\"" % b.id)
+
+        # Now click it
+        resp2 = self.client.post(reverse('cciw.bookings.views.list_bookings'), {'unshelve_%s' % b.id: '1'})
+
+        # Should be changed
+        b2 = acc.bookings.all()[0]
+        self.assertEqual(b2.shelved, False)
+
+        # Shelf section should disappear.
+        self.assertNotContains(resp2, "<h2>Shelf</h2>")

cciw/bookings/views.py

 from decimal import Decimal
 from functools import wraps
 import os
+import re
 
 from django.conf import settings
 from django.core.urlresolvers import reverse, reverse_lazy
 
     def get_context_data(self, **kwargs):
         c = super(BookingListBookings, self).get_context_data(**kwargs)
-        new_bookings = list(self.request.booking_account.bookings.ready_to_book(get_thisyear()))
-
+        bookings = self.request.booking_account.bookings
+        basket_bookings = list(bookings.ready_to_book(get_thisyear()))
+        shelf_bookings = list(bookings.ready_to_book(get_thisyear(), shelved=True))
         # Now apply business rules and other custom processing
         total = Decimal('0.00')
         all_bookable = True
         all_unbookable = True
-        for b in new_bookings:
-            # decorate object with some attributes to make it easier in template
-            b.booking_problems = b.get_booking_problems()
-            b.bookable = len(b.booking_problems) == 0
-            b.manually_approved = b.state == BOOKING_APPROVED
-            if b.bookable:
-                all_unbookable = False
-            else:
-                all_bookable = False
+        for l in basket_bookings, shelf_bookings:
+            for b in l:
+                # decorate object with some attributes to make it easier in template
+                b.booking_problems = b.get_booking_problems()
+                b.bookable = len(b.booking_problems) == 0
+                b.manually_approved = b.state == BOOKING_APPROVED
 
-            # Where booking.price_type = PRICE_CUSTOM, and state is not approved,
-            # amount_due is meaningless. So we have a new attr, amount_due_normalised
-            if b.price_type == PRICE_CUSTOM and b.state != BOOKING_APPROVED:
-                b.amount_due_normalised = None
-            else:
-                b.amount_due_normalised = b.amount_due
+                # Where booking.price_type = PRICE_CUSTOM, and state is not approved,
+                # amount_due is meaningless. So we have a new attr, amount_due_normalised
+                if b.price_type == PRICE_CUSTOM and b.state != BOOKING_APPROVED:
+                    b.amount_due_normalised = None
+                else:
+                    b.amount_due_normalised = b.amount_due
 
-            if b.amount_due_normalised is None or total is None:
-                total = None
-            else:
-                total = total + b.amount_due_normalised
+                # For basket bookings only:
+                if not b.shelved:
+                    if b.bookable:
+                        all_unbookable = False
+                    else:
+                        all_bookable = False
 
-        c['new_bookings'] = new_bookings
+                    if b.amount_due_normalised is None or total is None:
+                        total = None
+                    else:
+                        total = total + b.amount_due_normalised
+
+        c['basket_bookings'] = basket_bookings
+        c['shelf_bookings'] = shelf_bookings
         c['all_bookable'] = all_bookable
         c['all_unbookable'] = all_unbookable
         c['total'] = total
     def post(self, request, *args, **kwargs):
         if 'add_another' in request.POST:
             return HttpResponseRedirect(reverse('cciw.bookings.views.add_place'))
+
+        bookings = request.booking_account.bookings
+        for k in request.POST.keys():
+            # handle shelve and unshelve buttons
+            m = re.match(r"^(un)?shelve_(\d+)", k)
+            if m is not None:
+                try:
+                    b_id = int(m.groups()[1])
+                    to_shelve = m.groups()[0] == None
+                    place = bookings.ready_to_book(get_thisyear(),
+                                                   shelved=(not to_shelve)).get(id=b_id)
+                    place.shelved = to_shelve
+                    place.save()
+                except (ValueError, Booking.DoesNotExist):
+                    pass
         return self.get(request, *args, **kwargs)
 
 

templates/cciw/bookings/list_bookings.html

 {% load static %}
 
 {% block content %}
-{% if new_bookings %}
+
+<form action="" method="POST">
+
+{% csrf_token %}
+
 <h2>Basket</h2>
 
+{% if basket_bookings %}
 <ul>
   <li>Review and press 'Book now' to book the places and pay for
   them online.</li>
   and paying.</li>
 </ul>
 
-<form action="" method="POST">
-{% csrf_token %}
-
 <table class="topheaders">
   <tr>
     <th scope="col">Name</th>
     <th scope="col">Actions</th>
   </tr>
 
-{% for b in new_bookings %}
-  <tr>
-    <td>
-      {{ b.name }}
-    </td>
-    <td>
-      <a href="{% url 'cciw.cciwmain.views.camps.detail' year=b.camp.year number=b.camp.number %}">Camp {{ b.camp.number }}, {{ b.camp.leaders_formatted }}</a>,
-      {{ b.camp.start_date|date:"j M Y" }}
-    </td>
-    <td>
-      {% if b.amount_due_normalised|default_if_none:"None" == "None" %}TBA{% else %}£{{ b.amount_due_normalised }}{% endif %}
-    </td>
-    <td rowspan="2" class="sectionbottom">
-      <a href="{% url 'cciw.bookings.views.edit_place' id=b.id %}">View/edit</a>
-    </td>
-  </tr>
-  <tr class="sectionbottom">
-      <td><b>Status:</b></td>
-      <td colspan="2">
-        {% if b.bookable %}
-          <img src="{% static "admin/img/icon-yes.gif" %}"> This place can be booked
-          {% if b.manually_approved %}<b> - MANUALLY APPROVED</b>{% endif %}
-        {% else %}
-          <img src="{% static "admin/img/icon-no.gif" %}"> This place cannot be booked:
-          <ul>
-          {% for p in b.booking_problems %}
-            <li>{{ p }}</li>
-          {% endfor %}
-          </ul>
-        {% endif %}
-      </td>
-    </tr>
-  </tr>
+{% for b in basket_bookings %}
+  {% include "cciw/bookings/place_details_row_inc.html" with b=b basket=1 %}
 {% endfor %}
   <tr>
     <td colspan="2" style="text-align:right; border: 0px;"><b>Total:</b></td>
 </p>
 {% if not all_bookable %}
   {% if all_unbookable %}
-    {% if new_bookings|length > 1 %}
+    {% if basket_bookings|length > 1 %}
        <p>These places cannot be booked for the reasons described above.
        You will need to wait for manual approval of the places.</p>
     {% else %}
   {% endif %}
 {% endif %}
 
-</form>
+{% else %}
+
+<p>Nothing in your basket.</p>
+
+{% endif %} {# basket_bookings #}
+
+{% if shelf_bookings %}
+
+<h2>Shelf</h2>
+
+<p>These place details are 'on the shelf' for later use - move them to the
+  basket in order to book them.</p>
+
+<table class="topheaders">
+  <tr>
+    <th scope="col">Name</th>
+    <th scope="col">Camp</th>
+    <th scope="col">Price</th>
+    <th scope="col">Actions</th>
+  </tr>
+{% for b in shelf_bookings %}
+  {% include "cciw/bookings/place_details_row_inc.html" with b=b shelf=1 %}
+{% endfor %}
+</table>
 
 
 {% endif %}
 
+</form>
+
+
 {% endblock %}

templates/cciw/bookings/place_details_row_inc.html

+{% load url from future %}
+{% load static %}
+  <tr>
+    <td>
+      {{ b.name }}
+    </td>
+    <td>
+      <a href="{% url 'cciw.cciwmain.views.camps.detail' year=b.camp.year number=b.camp.number %}">Camp {{ b.camp.number }}, {{ b.camp.leaders_formatted }}</a>,
+      {{ b.camp.start_date|date:"j M Y" }}
+    </td>
+    <td>
+      {% if b.amount_due_normalised|default_if_none:"None" == "None" %}TBA{% else %}£{{ b.amount_due_normalised }}{% endif %}
+    </td>
+    <td rowspan="2" class="sectionbottom" style="text-align:center;">
+      <a href="{% url 'cciw.bookings.views.edit_place' id=b.id %}">View/edit</a><br/>
+      {% if basket %}
+        <input type="submit" name="shelve_{{ b.id }}" value="Save for later">
+      {% endif %}
+      {% if shelf %}
+        <input type="submit" name="unshelve_{{ b.id }}" value="Move to basket">
+      {% endif %}
+    </td>
+  </tr>
+  <tr class="sectionbottom">
+      <td><b>Status:</b></td>
+      <td colspan="2">
+        {% if b.bookable %}
+          <img src="{% static "admin/img/icon-yes.gif" %}"> This place can be booked
+          {% if b.manually_approved %}<b> - MANUALLY APPROVED</b>{% endif %}
+        {% else %}
+          <img src="{% static "admin/img/icon-no.gif" %}"> This place cannot be booked:
+          <ul>
+          {% for p in b.booking_problems %}
+            <li>{{ p }}</li>
+          {% endfor %}
+          </ul>
+        {% endif %}
+      </td>
+    </tr>
+  </tr>