Index: /main/waeup.fceokene/trunk/src/waeup/fceokene/applicants/browser.py
===================================================================
--- /main/waeup.fceokene/trunk/src/waeup/fceokene/applicants/browser.py	(revision 18155)
+++ /main/waeup.fceokene/trunk/src/waeup/fceokene/applicants/browser.py	(revision 18156)
@@ -192,5 +192,6 @@
             form_fields['matric_number'].for_display = True
             form_fields['subj_comb'].field.required = True
-            form_fields['school_tpu'].field.required = True
+            #form_fields['school_tpu'].field.required = True
+            form_fields['zone_tpu'].field.required = True
             return form_fields
         if target == 'utp':
@@ -244,10 +245,48 @@
         return False
 
+    MAX_ZONE = {
+        'z1': 120,
+        'z2': 24,
+        'z3': 16,
+        'z4': 22,
+        'z5': 4,
+        'z6': 10,
+        'z7': 22,
+        'z8': 40,
+        'z9': 10,
+        'z10': 22,
+        'z11': 48,
+        'z12': 6,
+        'z13': 0,
+        'z14': 0,
+        'z15': 6,
+        }
+
+    def _students_per_zone_exceeded(self, data):
+        container = self.context.__parent__
+        counter_zone = 0
+        for appl in container.values():
+            zone = data.get('zone_tpu').split('_')[0]
+            max_zone = self.MAX_ZONE[zone]
+            if appl != self.context \
+                and appl.state in (SUBMITTED, ADMITTED, NOT_ADMITTED, CREATED):
+                if getattr(appl, 'zone_tpu').split(
+                    '_')[0] == data.get('zone_tpu').split('_')[0]:
+                    counter_zone += 1
+                if counter_zone == max_zone:
+                    return True
+        return False
+
     def dataNotComplete(self, data):
         target = getattr(self.context.__parent__, 'prefix', None)
-        if target in ('tpu', 'utp'):
+        if target in ('utp',):
             if self._students_per_school_exceeded(data):
                 return ("1st Choice School: "
                         "Maximum number of applications per school exceeded. "
+                        "Please select another first choice school.")
+        if target in ('tpu',):
+            if self._students_per_zone_exceeded(data):
+                return ("1st Choice School Zone: "
+                        "Maximum number of applications per zone exceeded. "
                         "Please select another first choice school.")
         super(CustomApplicantEditFormPage, self).dataNotComplete(data)
Index: /main/waeup.fceokene/trunk/src/waeup/fceokene/applicants/interfaces.py
===================================================================
--- /main/waeup.fceokene/trunk/src/waeup/fceokene/applicants/interfaces.py	(revision 18155)
+++ /main/waeup.fceokene/trunk/src/waeup/fceokene/applicants/interfaces.py	(revision 18156)
@@ -46,4 +46,5 @@
 from waeup.fceokene.applicants.schools_tpu import SCHOOLS_TPU
 from waeup.fceokene.applicants.schools_utp import SCHOOLS_UTP
+from waeup.fceokene.applicants.zones import ZONES_TPU
 from waeup.fceokene.interfaces import MessageFactory as _
 
@@ -92,4 +93,23 @@
             SCHOOLS_TPU[value][2],)
 
+class TPUZoneSource(BasicSourceFactory):
+    """A source that delivers all kinds of TPU zones.
+    """
+    def getValues(self):
+        sorted_items = sorted(
+            ZONES_TPU.items(),
+            key=lambda element: int(element[1][0].split()[1]))
+        return [item[0] for item in sorted_items]
+
+    def getToken(self, value):
+        return value
+
+    def getTitle(self, value):
+        if not ZONES_TPU.get(value, None):
+            return u"none-existent"
+        return u"%s: %s" % (
+            ZONES_TPU[value][0],
+            ZONES_TPU[value][1],)
+
 class UTPSchoolSource(BasicSourceFactory):
     """A source that delivers all kinds of UTP schools.
@@ -192,7 +212,13 @@
         )
 
-    school_tpu = schema.Choice(
-        title = _(u'1st Choice TPZ and School'),
-        source = TPUSchoolSource(),
+    #school_tpu = schema.Choice(
+    #    title = _(u'1st Choice TPZ and School'),
+    #    source = TPUSchoolSource(),
+    #    required = False,
+    #    )
+
+    zone_tpu = schema.Choice(
+        title = _(u'1st Choice TPZ'),
+        source = TPUZoneSource(),
         required = False,
         )
Index: /main/waeup.fceokene/trunk/src/waeup/fceokene/applicants/tests/test_browser.py
===================================================================
--- /main/waeup.fceokene/trunk/src/waeup/fceokene/applicants/tests/test_browser.py	(revision 18155)
+++ /main/waeup.fceokene/trunk/src/waeup/fceokene/applicants/tests/test_browser.py	(revision 18156)
@@ -218,5 +218,5 @@
         self.assertEqual(self.browser.headers['Status'], '200 Ok')
         
-    def test_final_submit_tpu(self):
+    def disable_test_final_submit_tpu(self):
         # Create NCE certificate
         certificate1 = createObject('waeup.Certificate')
@@ -297,4 +297,53 @@
         self.browser.getControl("Finally Submit").click()
         self.assertEqual(self.tpuapplicant.state, 'submitted')
+        return
+
+    def test_final_submit_tpu(self):
+        # Create NCE certificate
+        certificate1 = createObject('waeup.Certificate')
+        certificate1.application_category = 'basic'
+        certificate1.code = 'NCECERT1'
+        certificate1.title = 'NCECert1'
+        certificate1.start_level = 100
+        certificate1.end_level = 500
+        certificate1.study_mode = u'nce_ft'
+        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
+            certificate1)
+        IWorkflowState(self.tpuapplicant).setState('paid')
+        self.browser.open(self.login_path)
+        self.browser.getControl(
+            name="form.login").value = self.tpuapplicant.applicant_id
+        self.browser.getControl(name="form.password").value = 'apwd'
+        self.browser.getControl("Login").click()
+        self.browser.open(self.tpuapplicant_path + '/edit')
+        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
+        self.browser.getControl(name="form.zone_tpu").value = ['z5_001']
+        self.browser.getControl(name="form.subj_comb").value = ['NCECERT1']
+        image = open(SAMPLE_IMAGE, 'rb')
+        ctrl = self.browser.getControl(name='form.passport')
+        file_ctrl = ctrl.mech_control
+        file_ctrl.add_file(image, filename='myphoto.jpg')
+        self.browser.getControl(name="confirm_passport").value = True
+        self.browser.getControl("Save").click()
+        self.browser.getControl("Finally Submit").click()
+        self.assertTrue(
+            'Application submitted' in self.browser.contents)
+        # Create 5 applicants who already selected z5_001
+        for i in range(1,6):
+            dummy = createObject(u'waeup.Applicant')
+            dummy.firstname = u'John'
+            dummy.lastname = u'Doe %s' %i
+            dummy.zone_tpu = 'z5_001'
+            self.app['applicants'][
+                self.tpucontainer.code].addApplicant(dummy)
+            IWorkflowState(dummy).setState('submitted')
+        IWorkflowState(self.tpuapplicant).setState('paid')
+        self.tpuapplicant.locked = False
+        self.browser.open(self.tpuapplicant_path + '/edit')
+        self.browser.getControl(name="form.zone_tpu").value = ['z5_001']
+        self.browser.getControl("Finally Submit").click()
+        self.assertTrue("Maximum number of applications per zone exceeded."
+            in self.browser.contents)
+        self.assertEqual(self.tpuapplicant.state, 'paid')
         return
 
Index: /main/waeup.fceokene/trunk/src/waeup/fceokene/applicants/zones.py
===================================================================
--- /main/waeup.fceokene/trunk/src/waeup/fceokene/applicants/zones.py	(revision 18155)
+++ /main/waeup.fceokene/trunk/src/waeup/fceokene/applicants/zones.py	(revision 18156)
@@ -1,78 +1,78 @@
-ZONES = {
-	'tpu0001': ('Zone 1','Otite'),
-	'tpu0002': ('Zone 1','Nagazi'),
-	'tpu0003': ('Zone 1','Nagazi-Eba'),
-	'tpu0004': ('Zone 1','Kabba Junction'),
-	'tpu0005': ('Zone 1','Oziokutu'),
-	'tpu0006': ('Zone 1','Ogaminana'),
-	'tpu0007': ('Zone 1','Ozuri'),
-	'tpu0008': ('Zone 1','Obangede'),
-	'tpu0009': ('Zone 1','Camp 2'),
-	'tpu0010': ('Zone 1','Itakpe'),
-	'tpu0011': ('Zone 1','Aku'),
-	'tpu0012': ('Zone 1','Osara'),
-	'tpu0013': ('Zone 2','Okunchi'),
-	'tpu0014': ('Zone 2','Ozuwaya'),
-	'tpu0015': ('Zone 2','Lafia'),
-	'tpu0016': ('Zone 2','Agassa'),
-	'tpu0017': ('Zone 2','Inike-Etahi'),
-	'tpu0018': ('Zone 2','Checkpoint'),
-	'tpu0019': ('Zone 2','Otutu'),
-	'tpu0020': ('Zone 3','Kuroko'),
-	'tpu0021': ('Zone 3','Inoziomi'),
-	'tpu0022': ('Zone 3','Karaworo'),
-	'tpu0023': ('Zone 3','Iresuha'),
-	'tpu0024': ('Zone 3','Ebogogo'),
-	'tpu0025': ('Zone 3','Eika-Ohizenyi'),
-	'tpu0026': ('Zone 3','GRA'),
-	'tpu0027': ('Zone 4','Okengwe'),
-	'tpu0028': ('Zone 4','Ihima'),
-	'tpu0029': ('Zone 4','Adavi-Eba'),
-	'tpu0030': ('Zone 4','Ege'),
-	'tpu0031': ('Zone 5','Adogo'),
-	'tpu0032': ('Zone 5','Eganyi'),
-	'tpu0033': ('Zone 5','Ajaokuta Township'),
-	'tpu0034': ('Zone 6','Magongo'),
-	'tpu0035': ('Zone 6','Ogori'),
-	'tpu0036': ('Zone 6','Ibillo'),
-	'tpu0037': ('Zone 6','Lampese'),
-	'tpu0038': ('Zone 6','Ekpe'),
-	'tpu0039': ('Zone 7','Kabba'),
-	'tpu0040': ('Zone 7','Ayetoro Gbede'),
-	'tpu0041': ('Zone 7','Okoro Gbede'),
-	'tpu0042': ('Zone 7','Araromi Gbede'),
-	'tpu0043': ('Zone 7','Aiyegunle Gbede'),
-	'tpu0044': ('Zone 7','Odokoro Gbede'),
-	'tpu0045': ('Zone 8','Oka-Akoko'),
-	'tpu0046': ('Zone 8','Ifira-Akoko'),
-	'tpu0047': ('Zone 8','Akungba-Akoko'),
-	'tpu0048': ('Zone 8','Ikare-Akoko'),
-	'tpu0049': ('Zone 8','Okeagbe-Akoko'),
-	'tpu0050': ('Zone 8','Ogbagi-Akoko'),
-	'tpu0051': ('Zone 8','Supare-Akoko'),
-	'tpu0052': ('Zone 8','Oba-Akoko'),
-	'tpu0053': ('Zone 8','Ese-Akoko'),
-	'tpu0054': ('Zone 8','Ose'),
-	'tpu0055': ('Zone 9','Isanlu'),
-	'tpu0056': ('Zone 9','Takete Isao'),
-	'tpu0057': ('Zone 10','Iyara'),
-	'tpu0058': ('Zone 10','Egbede'),
-	'tpu0059': ('Zone 10','Egga'),
-	'tpu0060': ('Zone 10','Ekinrin Adde'),
-	'tpu0061': ('Zone 10','Iyamoye'),
-	'tpu0062': ('Zone 10','Omuo-Ekiti'),
-	'tpu0063': ('Zone 11','Zariagi-Kabba Junction'),
-	'tpu0064': ('Zone 11','Kaduna Junction'),
-	'tpu0065': ('Zone 11','Zango-Lokoja'),
-	'tpu0066': ('Zone 11','Lokoja'),
-	'tpu0067': ('Zone 11','Ganaja Village'),
-	'tpu0068': ('Zone 11','Koton-Karfe'),
-	'tpu0069': ('Zone 11','Gegu-Beki'),
-	'tpu0070': ('Zone 11','Orehi'),
-	'tpu0071': ('Zone 11','Ahoko'),
-	'tpu0072': ('Zone 11','Robomi'),
-	'tpu0073': ('Zone 11','Obajana'),
-	'tpu0074': ('Zone 12','Itobe'),
-	'tpu0075': ('Zone 15','Okpella'),
-	'tpu0076': ('Zone 15','Auchi'),
+ZONES_TPU = {
+	'z1_001': ('Zone 1','Otite'),
+	'z1_002': ('Zone 1','Nagazi'),
+	'z1_003': ('Zone 1','Nagazi-Eba'),
+	'z1_004': ('Zone 1','Kabba Junction'),
+	'z1_005': ('Zone 1','Oziokutu'),
+	'z1_006': ('Zone 1','Ogaminana'),
+	'z1_007': ('Zone 1','Ozuri'),
+	'z1_008': ('Zone 1','Obangede'),
+	'z1_009': ('Zone 1','Camp 2'),
+	'z1_010': ('Zone 1','Itakpe'),
+	'z1_011': ('Zone 1','Aku'),
+	'z1_012': ('Zone 1','Osara'),
+	'z2_001': ('Zone 2','Okunchi'),
+	'z2_002': ('Zone 2','Ozuwaya'),
+	'z2_003': ('Zone 2','Lafia'),
+	'z2_004': ('Zone 2','Agassa'),
+	'z2_005': ('Zone 2','Inike-Etahi'),
+	'z2_006': ('Zone 2','Checkpoint'),
+	'z2_007': ('Zone 2','Otutu'),
+	'z3_001': ('Zone 3','Kuroko'),
+	'z3_002': ('Zone 3','Inoziomi'),
+	'z3_003': ('Zone 3','Karaworo'),
+	'z3_004': ('Zone 3','Iresuha'),
+	'z3_005': ('Zone 3','Ebogogo'),
+	'z3_006': ('Zone 3','Eika-Ohizenyi'),
+	'z3_007': ('Zone 3','GRA'),
+	'z4_001': ('Zone 4','Okengwe'),
+	'z4_002': ('Zone 4','Ihima'),
+	'z4_003': ('Zone 4','Adavi-Eba'),
+	'z4_004': ('Zone 4','Ege'),
+	'z5_001': ('Zone 5','Adogo'),
+	'z5_002': ('Zone 5','Eganyi'),
+	'z5_003': ('Zone 5','Ajaokuta Township'),
+	'z6_001': ('Zone 6','Magongo'),
+	'z6_002': ('Zone 6','Ogori'),
+	'z6_003': ('Zone 6','Ibillo'),
+	'z6_004': ('Zone 6','Lampese'),
+	'z6_005': ('Zone 6','Ekpe'),
+	'z7_001': ('Zone 7','Kabba'),
+	'z7_002': ('Zone 7','Ayetoro Gbede'),
+	'z7_003': ('Zone 7','Okoro Gbede'),
+	'z7_004': ('Zone 7','Araromi Gbede'),
+	'z7_005': ('Zone 7','Aiyegunle Gbede'),
+	'z7_006': ('Zone 7','Odokoro Gbede'),
+	'z8_001': ('Zone 8','Oka-Akoko'),
+	'z8_002': ('Zone 8','Ifira-Akoko'),
+	'z8_003': ('Zone 8','Akungba-Akoko'),
+	'z8_004': ('Zone 8','Ikare-Akoko'),
+	'z8_005': ('Zone 8','Okeagbe-Akoko'),
+	'z8_006': ('Zone 8','Ogbagi-Akoko'),
+	'z8_007': ('Zone 8','Supare-Akoko'),
+	'z8_008': ('Zone 8','Oba-Akoko'),
+	'z8_009': ('Zone 8','Ese-Akoko'),
+	'z8_010': ('Zone 8','Ose'),
+	'z9_001': ('Zone 9','Isanlu'),
+	'z9_002': ('Zone 9','Takete Isao'),
+	'z10_001': ('Zone 10','Iyara'),
+	'z10_002': ('Zone 10','Egbede'),
+	'z10_003': ('Zone 10','Egga'),
+	'z10_004': ('Zone 10','Ekinrin Adde'),
+	'z10_005': ('Zone 10','Iyamoye'),
+	'z10_006': ('Zone 10','Omuo-Ekiti'),
+	'z11_001': ('Zone 11','Zariagi-Kabba Junction'),
+	'z11_002': ('Zone 11','Kaduna Junction'),
+	'z11_003': ('Zone 11','Zango-Lokoja'),
+	'z11_004': ('Zone 11','Lokoja'),
+	'z11_005': ('Zone 11','Ganaja Village'),
+	'z11_006': ('Zone 11','Koton-Karfe'),
+	'z11_007': ('Zone 11','Gegu-Beki'),
+	'z11_008': ('Zone 11','Orehi'),
+	'z11_009': ('Zone 11','Ahoko'),
+	'z11_010': ('Zone 11','Robomi'),
+	'z11_011': ('Zone 11','Obajana'),
+	'z12_001': ('Zone 12','Itobe'),
+	'z15_001': ('Zone 15','Okpella'),
+	'z15_002': ('Zone 15','Auchi'),
 	}
