Commits

Luke Plant committed 1e8e03e

Streamlined adding interface for adding Bookings, using an enhanced BookingAccount autocomplete widget

Also, related changes to admin fieldsets, showing/grouping fields as
appropriate.

Comments (0)

Files changed (5)

 SECRETARY_GROUP_NAME = 'Secretaries'
 OFFICER_GROUP_NAME = 'Officers'
 LEADER_GROUP_NAME = 'Leaders'
+BOOKING_SECRETARY_GROUP_NAME = 'Booking secretaries'
 
 
 def is_camp_admin(user):
     return (user.groups.filter(name=OFFICER_GROUP_NAME) |
             user.groups.filter(name=LEADER_GROUP_NAME)).exists()
 
+
+def is_booking_secretary(user):
+    return user.groups.filter(name=BOOKING_SECRETARY_GROUP_NAME).exists()
+

cciw/bookings/admin.py

+from autocomplete.fields import ModelChoiceField
 from django.contrib import admin
 from django import forms
 
 from cciw.bookings.models import Price, BookingAccount, Booking
 from cciw.cciwmain.common import get_thisyear
+from cciw.utils.views import close_window_response
+
+from .widgets import AccountAutoCompleteWidget
 
 class PriceAdmin(admin.ModelAdmin):
     list_display = ['price_type', 'year', 'price']
     list_display = ['id', 'name', 'email', 'post_code', 'phone_number']
     ordering = ['email']
     search_fields = ['email', 'name']
-    readonly_fields = ['total_received']
+    readonly_fields = ['first_login', 'last_login', 'total_received']
+    form = BookingAccountForm
+
+    def get_fieldsets(self, request, obj=None):
+        fieldsets = [
+            (None,
+             {'fields':
+                  ['name',
+                   'email',
+                   'address',
+                   'post_code',
+                   'phone_number',
+                   'share_phone_number',
+                   ]})
+            ]
+        if '_popup' not in request.GET:
+            fieldsets.append(
+                ('Automatically managed',
+                 {'fields':
+                      ['first_login',
+                       'last_login',
+                       'total_received',
+                       ]}))
+        return fieldsets
+
+    def response_change(self, request, obj):
+        # Little hack to allow popups for changing BookingAccount
+        if '_popup' in request.POST:
+            return close_window_response()
+        else:
+            return super(BookingAccountAdmin, self).response_change(request, obj)
 
 
 class YearFilter(admin.SimpleListFilter):
         return queryset.filter(camp__year__exact=val)
 
 
+account_autocomplete_field = \
+    lambda: ModelChoiceField('account',
+                             label='Account name',
+                             widget=AccountAutoCompleteWidget('account',
+                                                              attrs={'size':'70'}))
+
+class BookingAdminForm(forms.ModelForm):
+
+    account = account_autocomplete_field()
+
+    class Meta:
+        model = Booking
+
+
 class BookingAdmin(admin.ModelAdmin):
     def camp(obj):
         return "%s-%s" % (obj.camp.year, obj.camp.number)
     list_filter = [YearFilter, 'sex', 'price_type', 'serious_illness', 'south_wales_transport',
                    'state']
 
+    form = BookingAdminForm
+
+
+    fieldsets = (
+        ('Account',
+         {'fields':
+              ['account',
+               ],
+          'description': "Enter the account name, then choose from the suggestions, or choose 'New account' if there is no match. Use 'edit' to change the details of a selected account." }),
+        ('Camp',
+         {'fields':
+              ['camp']}),
+        ('Camper details',
+         {'fields':
+              ['name',
+               'sex',
+               'date_of_birth',
+               'address',
+              'post_code',
+               'phone_number',
+               'email',
+               ]}),
+        ('Church',
+         {'fields': ['church']}),
+        ('Contact details',
+         {'fields':
+              ['contact_name',
+               'contact_phone_number',
+               ]}),
+        ('Diet',
+         {'fields':
+              ['dietary_requirements']}),
+        ('GP details',
+         {'fields':
+              ['gp_name',
+               'gp_address',
+               'gp_phone_number',
+               ]}),
+        ('Medical details',
+         {'fields':
+              ['medical_card_number',
+               'last_tetanus_injection',
+               'allergies',
+               'regular_medication_required',
+               'illnesses',
+               'learning_difficulties',
+               'serious_illness',
+               ]}),
+        ('Camper/parent agree to terms',
+         {'fields':
+              ['agreement']}),
+        ('Price',
+         {'fields':
+              ['price_type',
+               'south_wales_transport',
+               'amount_due',
+               ]}),
+        ('Internal',
+         {'fields':
+              ['state',
+               'booking_expires',
+               'created',
+               'shelved']}),
+        )
 
 admin.site.register(Price, PriceAdmin)
 admin.site.register(BookingAccount, BookingAccountAdmin)

cciw/bookings/models.py

     amount_due = models.DecimalField(decimal_places=2, max_digits=10)
 
     # State - user driven
-    shelved = models.BooleanField(default=False)
+    shelved = models.BooleanField(default=False, help_text=
+                                  u"Used by user to put on 'shelf'")
 
     # State - internal
     state = models.IntegerField(choices=BOOKING_STATES,
                                 help_text=mark_safe(
-            "<ul><li>Move to 'Manually approved' to allow user to book and pay</li>"
-            "<li>To book, move to 'Booked' <b>and</b> ensure 'Booking expires' is empty</li>"
-            "</ul>"))
+            u"<ul>"
+            u"<li>To book, set to 'Booked' <b>and</b> ensure 'Booking expires' is empty</li>"
+            u"<li>For people paying online and needing custom discounts, set to 'Manually approved' to allow them to book and pay</li>"
+            u"<li>If there are queries before it can be booked, set to 'Information complete'</li>"
+            u"</ul>"))
 
     created = models.DateTimeField(default=datetime.now)
     booking_expires = models.DateTimeField(null=True, blank=True)

cciw/bookings/widgets.py

+from autocomplete.widgets import AutoCompleteWidget
+
+
+class AccountAutoCompleteWidget(AutoCompleteWidget):
+    AC_TEMPLATE = u'''
+        <input type="hidden" name="%(name)s" id="id_hidden_%(name)s" value="%(hidden_value)s" />
+        <input type="text" value="%(value)s" %(attrs)s />
+        <a href="javascript:void(0)" class="add-another" id="add_id_account"> New account </a> | 
+
+        <a href="javascript:void(0)" class="add-another" id="edit_id_account"> Edit </a>
+
+
+<script type="text/javascript">
+var %(var_name)s = new autocomplete("%(name)s", "%(url)s", %(force_selection)s);
+
+// For 'add another', we need slightly customised behaviour instead of showAddAnotherPopup
+
+function showAddAnotherAccountPopup(ev) {
+    ev.preventDefault();
+    var name = 'id_account';
+    name = id_to_windowname(name);
+    var href = '/admin/bookings/bookingaccount/add/?_popup=1&name=' + encodeURIComponent($('#id_account').val());
+    var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
+    win.focus();
+}
+
+function showEditAccountPopup(ev) {
+    ev.preventDefault();
+    var name = 'id_account';
+    name = id_to_windowname(name);
+    var account_id = $('#id_hidden_account').val();
+    if (/^\d+$/.test(account_id)) {
+        var href = '/admin/bookings/bookingaccount/' + account_id + '/?_popup=1';
+        var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
+    } else {
+        alert('No account selected');
+    }
+    win.focus();
+}
+
+
+// Hack: we need dismissAddAnotherPopup to do something different,
+// so we monkey patch it.
+
+var originalDismissAddAnotherPopup = window.dismissAddAnotherPopup;
+window.dismissAddAnotherPopup = function(win, newId, newRepr) {
+    newId = html_unescape(newId);
+    newRepr = html_unescape(newRepr);
+    var name = windowname_to_id(win.name);
+    var elem = document.getElementById(name);
+    if (name == 'id_account') {
+        $('#id_hidden_account').val(newId);
+        $('#id_account').val(newRepr);
+        win.close();
+    } else {
+        originalDismissAddAnotherPopup(win, newId, newRepr);
+    }
+}
+
+$(document).ready(function(ev){
+    $('#add_id_account').click(showAddAnotherAccountPopup);
+    $('#edit_id_account').click(showEditAccountPopup);
+    // autocomplete doesn't do quite what we want with focusout:
+    $('#id_account').unbind('focusout');
+});
+
+</script>
+'''
+
+    class Media:
+        extend = False
+        css = {'all': ('https://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css',),
+               }
+        js = (
+            "https://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.js",
+            "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.js",
+            "js/jquery_autocomplete.js")
+
 from django.views.generic.base import RedirectView
 
 import cciw.auth
+from cciw.bookings.models import BookingAccount
 
 handler404 = 'cciw.cciwmain.views.handler404'
 
     auth=lambda request: request.user.is_authenticated() and cciw.auth.is_camp_admin(request.user)
     )
 
+autocomplete.register(
+    id='account',
+    queryset=BookingAccount.objects.all().order_by('name', 'post_code'),
+    fields=('name__icontains',),
+    limit=20,
+    label=lambda acc: unicode(acc),
+    auth=lambda request: request.user.is_authenticated and cciw.auth.is_booking_secretary(request.user)
+    )
+
 urlpatterns = patterns('',
     (r'^booking/', include('cciw.bookings.urls')),
     # Plug in the password reset views