Luke Plant avatar Luke Plant committed 366d7d5

Migrated Booking.name to Booking.first_name and last_name fields

Comments (0)

Files changed (7)

cciw/bookings/admin.py

     def camp(obj):
         return "%s-%s" % (obj.camp.year, obj.camp.number)
     camp.admin_order_field = 'camp__year'
-    list_display = ['name', 'sex', 'account', camp, 'state', 'confirmed_booking']
+    list_display = ['first_name', 'last_name', 'sex', 'account', camp, 'state', 'confirmed_booking']
     del camp
-    search_fields = ['name']
+    search_fields = ['first_name', 'last_name']
     ordering = ['-camp__year', 'camp__number']
     date_hierarchy = 'created'
     list_filter = [YearFilter, 'sex', 'price_type', 'serious_illness', 'south_wales_transport',
               ['camp']}),
         ('Camper details',
          {'fields':
-              ['name',
+              ['first_name',
+               'last_name',
                'sex',
                'date_of_birth',
                'address',

cciw/bookings/forms.py

         fields = [
             'camp',
             'price_type',
-            'name',
+            'first_name',
+            'last_name',
             'sex',
             'date_of_birth',
             'address',

cciw/bookings/migrations/0009_auto__del_field_booking_name__add_field_booking_first_name__add_field_.py

+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Deleting field 'Booking.name'
+        db.delete_column('bookings_booking', 'name')
+
+        # Adding field 'Booking.first_name'
+        db.add_column('bookings_booking', 'first_name', self.gf('django.db.models.fields.CharField')(default='', max_length=100), keep_default=False)
+
+        # Adding field 'Booking.last_name'
+        db.add_column('bookings_booking', 'last_name', self.gf('django.db.models.fields.CharField')(default='', max_length=100), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Adding field 'Booking.name'
+        db.add_column('bookings_booking', 'name', self.gf('django.db.models.fields.CharField')(default='', max_length=100), keep_default=False)
+
+        # Deleting field 'Booking.first_name'
+        db.delete_column('bookings_booking', 'first_name')
+
+        # Deleting field 'Booking.last_name'
+        db.delete_column('bookings_booking', 'last_name')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'bookings.booking': {
+            'Meta': {'ordering': "['-created']", 'object_name': 'Booking'},
+            'account': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bookings'", 'to': "orm['bookings.BookingAccount']"}),
+            'address': ('django.db.models.fields.TextField', [], {}),
+            'agreement': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'allergies': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'amount_due': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}),
+            'booking_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'camp': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bookings'", 'to': "orm['cciwmain.Camp']"}),
+            'church': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'contact_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'contact_phone_number': ('django.db.models.fields.CharField', [], {'max_length': '22'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'date_of_birth': ('django.db.models.fields.DateField', [], {}),
+            'dietary_requirements': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'gp_address': ('django.db.models.fields.TextField', [], {}),
+            'gp_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'gp_phone_number': ('django.db.models.fields.CharField', [], {'max_length': '22'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'illnesses': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'last_tetanus_injection': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'learning_difficulties': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'medical_card_number': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'phone_number': ('django.db.models.fields.CharField', [], {'max_length': '22', 'blank': 'True'}),
+            'post_code': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
+            'price_type': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
+            'regular_medication_required': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'serious_illness': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'sex': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+            'shelved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'south_wales_transport': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'state': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'bookings.bookingaccount': {
+            'Meta': {'unique_together': "[('name', 'post_code'), ('name', 'email')]", 'object_name': 'BookingAccount'},
+            'address': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_login': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'phone_number': ('django.db.models.fields.CharField', [], {'max_length': '22', 'blank': 'True'}),
+            'post_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'share_phone_number': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'total_received': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '10', 'decimal_places': '2'})
+        },
+        'bookings.chequepayment': {
+            'Meta': {'object_name': 'ChequePayment'},
+            'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['bookings.BookingAccount']"}),
+            'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'bookings.payment': {
+            'Meta': {'object_name': 'Payment'},
+            'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['bookings.BookingAccount']"}),
+            'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'origin_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'origin_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'processed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'})
+        },
+        'bookings.price': {
+            'Meta': {'unique_together': "(['year', 'price_type'],)", 'object_name': 'Price'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'price': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}),
+            'price_type': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
+            'year': ('django.db.models.fields.PositiveSmallIntegerField', [], {})
+        },
+        'bookings.refundpayment': {
+            'Meta': {'object_name': 'RefundPayment'},
+            'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['bookings.BookingAccount']"}),
+            'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'cciwmain.camp': {
+            'Meta': {'ordering': "['-year', 'number']", 'unique_together': "(('year', 'number'),)", 'object_name': 'Camp'},
+            'admins': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'camps_as_admin'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
+            'chaplain': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'camps_as_chaplain'", 'null': 'True', 'to': "orm['cciwmain.Person']"}),
+            'end_date': ('django.db.models.fields.DateField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'leaders': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'camps_as_leader'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['cciwmain.Person']"}),
+            'max_campers': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '80'}),
+            'max_female_campers': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '60'}),
+            'max_male_campers': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '60'}),
+            'maximum_age': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
+            'minimum_age': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
+            'number': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
+            'officers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'through': "orm['officers.Invitation']", 'symmetrical': 'False'}),
+            'online_applications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'previous_camp': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'next_camps'", 'null': 'True', 'to': "orm['cciwmain.Camp']"}),
+            'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cciwmain.Site']"}),
+            'start_date': ('django.db.models.fields.DateField', [], {}),
+            'year': ('django.db.models.fields.PositiveSmallIntegerField', [], {})
+        },
+        'cciwmain.person': {
+            'Meta': {'ordering': "('name',)", 'object_name': 'Person'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'info': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'cciwmain.site': {
+            'Meta': {'object_name': 'Site'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'info': ('django.db.models.fields.TextField', [], {}),
+            'long_name': ('django.db.models.fields.CharField', [], {'max_length': "'50'"}),
+            'short_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': "'25'"}),
+            'slug_name': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'unique': 'True', 'max_length': "'25'", 'blank': 'True'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'officers.invitation': {
+            'Meta': {'ordering': "('-camp__year', 'officer__first_name', 'officer__last_name')", 'unique_together': "(('officer', 'camp'),)", 'object_name': 'Invitation'},
+            'camp': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cciwmain.Camp']"}),
+            'date_added': ('django.db.models.fields.DateField', [], {'default': 'datetime.date.today'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'notes': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'officer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        }
+    }
+
+    complete_apps = ['bookings']

cciw/bookings/models.py

 
     # Booking details - from user
     camp = models.ForeignKey(Camp, related_name='bookings')
-    name = models.CharField(max_length=100)
+    first_name = models.CharField(max_length=100)
+    last_name = models.CharField(max_length=100)
     sex = models.CharField(max_length=1, choices=SEXES)
     date_of_birth = models.DateField()
     address = models.TextField()
         return u"%s, %s-%s, %s" % (self.name, self.camp.year, self.camp.number,
                                   self.account)
 
+
+    @property
+    def name(self):
+        return u"%s %s" % (self.first_name, self.last_name)
+
     ### Main business rules here ###
 
     def confirmed_booking(self):
     def get_booking_warnings(self, booking_sec=False):
         warnings = []
 
-        if self.account.bookings.filter(name=self.name, camp=self.camp).exclude(id=self.id):
+        if self.account.bookings.filter(first_name=self.first_name, last_name=self.last_name, camp=self.camp).exclude(id=self.id):
             warnings.append(u"You have entered another set of place details for a camper "
                             u"called '%s' on camp %d. Please ensure you don't book multiple "
                             u"places for the same camper!" % (self.name, self.camp.number))
 
         if self.price_type == PRICE_FULL:
             full_pricers = self.account.bookings.basket(self.camp.year)\
-                .filter(price_type=PRICE_FULL).order_by('name')
+                .filter(price_type=PRICE_FULL).order_by('first_name', 'last_name')
             if len(full_pricers) > 1:
                 names = [b.name for b in full_pricers]
                 pretty_names = u', '.join(names[1:]) + u" and " + names[0]
 
         if self.price_type == PRICE_2ND_CHILD:
             second_childers = self.account.bookings.basket(self.camp.year)\
-                .filter(price_type=PRICE_2ND_CHILD).order_by('name')
+                .filter(price_type=PRICE_2ND_CHILD).order_by('first_name', 'last_name')
             if len(second_childers) > 1:
                 names = [b.name for b in second_childers]
                 pretty_names = u', '.join(names[1:]) + u" and " + names[0]

cciw/bookings/tests.py

     def place_details(self):
         return {
             'camp': self.camp.id,
-            'name': u'Frédéric Bloggs',
+            'first_name': u'Frédéric',
+            'last_name': u'Bloggs',
             'sex': 'm',
             'date_of_birth': '%d-01-01' % (get_thisyear() - 14),
             'address': 'x',
         b = acc.bookings.all()[0]
 
         data = self.place_details.copy()
-        data['name'] = "A New Name"
+        data['first_name'] = "A New Name"
         resp = self.client.post(reverse('cciw.bookings.views.edit_place', kwargs={'id':str(b.id)}), data)
         self.assertEqual(resp.status_code, 302)
         newpath = reverse('cciw.bookings.views.list_bookings')
         self.assertTrue(resp['Location'].endswith(newpath))
 
         # Did we alter it?
-        self.assertEqual(acc.bookings.all()[0].name, "A New Name")
+        self.assertEqual(acc.bookings.all()[0].first_name, "A New Name")
 
     def test_edit_booked(self):
         """
 
             # Attempt a post
             data = self.place_details.copy()
-            data['name'] = "A New Name"
+            data['first_name'] = "A New Name"
             resp = self.client.post(reverse('cciw.bookings.views.edit_place', kwargs={'id':str(b.id)}), data)
             # Check we didn't alter it
-            self.assertNotEqual(acc.bookings.all()[0].name, "A New Name")
+            self.assertNotEqual(acc.bookings.all()[0].first_name, "A New Name")
 
 
 class TestListBookings(CreatePlaceMixin, TestCase):
         # Test the error we get for more than one problem booking
         self.login()
         self.create_place({'price_type': PRICE_CUSTOM})
-        self.create_place({'name': 'Another Child',
+        self.create_place({'first_name': 'Another',
+                           'last_name': 'Child',
                            'price_type': PRICE_CUSTOM})
         resp = self.client.get(self.url)
         self.assertEqual(200, resp.status_code)
         # Test the message we get if one place is bookable and the other is not
         self.login()
         self.create_place() # bookable
-        self.create_place({'name': 'Another Child',
+        self.create_place({'first_name': 'Another',
+                           'last_name': 'Child',
                            'price_type': PRICE_CUSTOM}) # not bookable
         resp = self.client.get(self.url)
         self.assertEqual(200, resp.status_code)
     def test_total(self):
         self.login()
         self.create_place()
-        self.create_place({'name': 'Another Child'})
+        self.create_place({'first_name': 'Another',
+                           'last_name': 'Child'})
 
         resp = self.client.get(self.url)
         self.assertEqual(200, resp.status_code)
         # manually approved places should appear as OK to book
         self.login()
         self.create_place() # bookable
-        self.create_place({'name': 'Another Child',
+        self.create_place({'first_name': 'Another',
+                           'last_name': 'Child',
                            'price_type': PRICE_CUSTOM}) # not bookable
         Booking.objects.filter(price_type=PRICE_CUSTOM).update(state=BOOKING_APPROVED,
                                                                amount_due=Decimal('0.01'))
     def test_warn_about_multiple_full_price(self):
         self.login()
         self.create_place()
-        self.create_place({'name': 'Mary Bloggs'})
+        self.create_place({'first_name': 'Mary',
+                           'last_name': 'Bloggs'})
 
         resp = self.client.get(self.url)
         self.assertContains(resp, "You have multiple places at 'Full price")
         self.assertContains(resp, ENABLED_BOOK_NOW_BUTTON)
 
         # Check for more than 2
-        self.create_place({'name': 'Peter Bloggs'})
+        self.create_place({'first_name': 'Peter',
+                           'last_name': 'Bloggs'})
         resp = self.client.get(self.url)
         self.assertContains(resp, "If Mary Bloggs, Peter Bloggs and Frédéric Bloggs")
 
     def test_warn_about_multiple_2nd_child(self):
         self.login()
         self.create_place()
-        self.create_place({'name': 'Mary Bloggs',
+        self.create_place({'first_name': 'Mary',
+                           'last_name': 'Bloggs',
                            'price_type': PRICE_2ND_CHILD})
-        self.create_place({'name': 'Peter Bloggs',
+        self.create_place({'first_name': 'Peter',
+                           'last_name': 'Bloggs',
                            'price_type': PRICE_2ND_CHILD})
 
         resp = self.client.get(self.url)
         self.assertContains(resp, ENABLED_BOOK_NOW_BUTTON)
 
 
-        self.create_place({'name': 'Zac Bloggs',
+        self.create_place({'first_name': 'Zac',
+                           'last_name': 'Bloggs',
                            'price_type': PRICE_2ND_CHILD})
         resp = self.client.get(self.url)
         self.assertContains(resp, "2 are eligible")
     def test_only_one_email_for_multiple_places(self):
         self.login()
         self.create_place()
-        self.create_place({'name': 'Another Child'})
+        self.create_place({'first_name': 'Another',
+                           'last_name': 'Child'})
 
         acc = self.get_account()
         book_basket_now(acc.bookings.basket(self.camp.year))
 
         self.assertEqual(mail.outbox[0].subject, "CCIW booking - place confirmed")
         self.assertEqual(mail.outbox[0].to, [self.email])
-        self.assertTrue(self.place_details['name'] in mail.outbox[0].body)
+        self.assertTrue(self.place_details['first_name'] in mail.outbox[0].body)
         self.assertTrue('Another Child' in mail.outbox[0].body)
 
     def test_concurrent_save(self):
         self.create_place()
         resp = self.client.get(reverse('cciw.bookings.views.places_json'))
         json = simplejson.loads(resp.content)
-        self.assertEqual(json['places'][0]['name'], self.place_details['name'])
+        self.assertEqual(json['places'][0]['first_name'], self.place_details['first_name'])
 
     def test_places_json_with_exclusion(self):
         self.login()
         acc.receive_payment(acc.bookings.all()[0].amount_due)
 
         # Book another
-        self.create_place({'name': 'Another Child'})
+        self.create_place({'first_name': 'Another',
+                           'last_name': 'Child'})
         book_basket_now(acc.bookings.basket(self.camp.year))
 
         # 3rd place, not booked at all
-        self.create_place({'name': '3rd child'})
+        self.create_place({'first_name': '3rd',
+                           'last_name': 'child'})
 
         # 4th place, cancelled
-        self.create_place({'name': '4th child'})
-        b = acc.bookings.get(name='4th child')
+        self.create_place({'first_name': '4th',
+                           'last_name': 'child'})
+        b = acc.bookings.get(first_name='4th', last_name='child')
         b.state = BOOKING_CANCELLED
         b.auto_set_amount_due()
         b.save()
         self.assertEqual(resp.status_code, 200)
 
         # Confirmed place
-        self.assertContains(resp, self.place_details['name'])
+        self.assertContains(resp, self.place_details['first_name'])
 
 
         # Booked place
         Test the emails are grouped as we expect
         """
         self.login()
-        self.create_place({'name':'Child One'})
-        self.create_place({'name':'Child Two'})
+        self.create_place({'first_name':'Child',
+                           'last_name': 'One'})
+        self.create_place({'first_name':'Child',
+                           'last_name': 'Two'})
 
         acc = self.get_account()
         book_basket_now(acc.bookings.basket(get_thisyear()))

cciw/bookings/views.py

 # Public attributes - i.e. that the account holder is allowed to see
 BOOKING_PLACE_PUBLIC_ATTRS = [
     'id',
-    'name',
+    'first_name',
+    'last_name',
     'sex',
     'date_of_birth',
     'address',

templates/cciw/bookings/add_place.html

        ];
 
        var all_attrs = [].concat(address_attrs, gp_info_attrs, [
-           'name',
+           'first_name',
+           'last_name',
            'sex',
            'date_of_birth',
            'church',
 
 <h2>Camper details</h2>
  <input type="submit" id="id_use_existing_btn" value="Use previous data" style="display:none; float: right;">
-{% cciw_form_field form 'name' 'Name' %}
+{% cciw_form_field form 'first_name' 'First name' %}
+{% cciw_form_field form 'last_name' 'Surname' %}
 {% cciw_form_field form 'sex' 'Sex' %}
 {% cciw_form_field form 'date_of_birth' 'Date of birth' %}
 {% cciw_form_field form 'address' 'Address' %} <input style="display:none;" type="submit" id="id_use_account_1_btn" value="Use account address">
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.