Index: main/waeup.custom/trunk/src/waeup/custom/applicants/applicant.py
===================================================================
--- main/waeup.custom/trunk/src/waeup/custom/applicants/applicant.py	(revision 8010)
+++ main/waeup.custom/trunk/src/waeup/custom/applicants/applicant.py	(revision 8012)
@@ -20,20 +20,27 @@
 from zope.interface import implementedBy
 from waeup.kofa.applicants.applicant import Applicant, ApplicantFactory
-from waeup.custom.applicants.interfaces import IPGApplicant, IPGApplicantEdit
+from waeup.kofa.applicants.interfaces import IApplicantEdit
+from waeup.kofa.utils.helpers import attrs_to_fields
+from waeup.custom.applicants.interfaces import(
+    IApplicant, IUGApplicantEdit, IPGApplicantEdit)
 
-class PGApplicant(Applicant):
-    grok.implements(IPGApplicant,IPGApplicantEdit)
-    grok.provides(IPGApplicant)
+class Applicant(Applicant):
 
-class PGApplicantFactory(ApplicantFactory):
-    """A factory for pg applicants.
+    grok.implements(IApplicant, IUGApplicantEdit, IPGApplicantEdit)
+    grok.provides(IApplicant)
+
+# Set all attributes of Applicant required in IApplicant as field
+# properties. Doing this, we do not have to set initial attributes
+# ourselves and as a bonus we get free validation when an attribute is
+# set.
+Applicant = attrs_to_fields(Applicant)
+
+class ApplicantFactory(ApplicantFactory):
+    """A factory for customized applicants.
     """
-    grok.name(u'waeup.PGApplicant')
-    title = u"Create a new pg applicant.",
-    description = u"This factory instantiates new pg applicant instances."
 
     def __call__(self, *args, **kw):
-        return PGApplicant()
+        return Applicant()
 
     def getInterfaces(self):
-        return implementedBy(PGApplicant)
+        return implementedBy(Applicant)
Index: main/waeup.custom/trunk/src/waeup/custom/applicants/batching.py
===================================================================
--- main/waeup.custom/trunk/src/waeup/custom/applicants/batching.py	(revision 8010)
+++ 	(revision )
@@ -1,32 +1,0 @@
-## $Id$
-##
-## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with this program; if not, write to the Free Software
-## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##
-"""Additional processors for applicants.
-"""
-
-import grok
-from waeup.kofa.applicants.batching import ApplicantProcessor
-from waeup.custom.applicants.interfaces import IPGApplicant
-
-class PGApplicantProcessor(ApplicantProcessor):
-    """A batch processor for IPGApplicant objects.
-    """
-    util_name = 'pgapplicantprocessor'
-    grok.name(util_name)
-    name = u'PG Applicant Processor'
-    iface = IPGApplicant
-    factory_name = 'waeup.PGApplicant'
Index: main/waeup.custom/trunk/src/waeup/custom/applicants/browser.py
===================================================================
--- main/waeup.custom/trunk/src/waeup/custom/applicants/browser.py	(revision 8012)
+++ main/waeup.custom/trunk/src/waeup/custom/applicants/browser.py	(revision 8012)
@@ -0,0 +1,103 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""UI components for basic applicants and related components.
+"""
+import grok
+from waeup.kofa.widgets.datewidget import (
+    FriendlyDateWidget, FriendlyDateDisplayWidget,
+    FriendlyDatetimeDisplayWidget)
+from waeup.kofa.widgets.phonewidget import PhoneWidget
+from waeup.kofa.applicants.browser import (ApplicantDisplayFormPage,
+    OnlinePaymentCallbackPage, ExportPDFPage, ApplicantManageFormPage)
+from waeup.kofa.applicants.viewlets import RequestCallbackActionButton
+from waeup.kofa.applicants.interfaces import IApplicant, IApplicantEdit
+from waeup.custom.applicants.interfaces import IPGApplicant, IUGApplicant
+from waeup.custom.interfaces import MessageFactory as _
+
+class RequestCallbackActionButton(RequestCallbackActionButton):
+    """ Do not display the base package callback button in custom pages.
+    """
+    @property
+    def target_url(self):
+        return ''
+
+class OnlinePaymentCallbackPage(OnlinePaymentCallbackPage):
+    """ Neutralize callback simulation view
+    """
+    def update(self):
+        return
+
+class ExportPDFPage(ExportPDFPage):
+    """Deliver a PDF slip of the context.
+    """
+    grok.context(IApplicant)
+    grok.name('application_slip.pdf')
+    grok.require('waeup.viewApplication')
+    form_fields = grok.AutoFields(IApplicant).omit(
+        'locked', 'course_admitted')
+    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
+    prefix = 'form'
+
+
+class ApplicantDisplayFormPage(ApplicantDisplayFormPage):
+
+    @property
+    def form_fields(self):
+        target = getattr(self.context.__parent__, 'prefix', None)
+        if target is not None and target.startswith('pg'):
+            form_fields = grok.AutoFields(IPGApplicant).omit(
+                'locked', 'course_admitted', 'password')
+        else:
+            form_fields = grok.AutoFields(IUGApplicant).omit(
+                'locked', 'course_admitted', 'password')
+        form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
+        return form_fields
+
+class ApplicantManageFormPage(ApplicantManageFormPage):
+    """A full edit view for applicant data.
+    """
+    grok.context(IApplicant)
+    grok.name('manage')
+    grok.require('waeup.manageApplication')
+    form_fields = grok.AutoFields(IApplicant)
+    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
+    form_fields['student_id'].for_display = True
+    form_fields['applicant_id'].for_display = True
+    form_fields['phone'].custom_widget = PhoneWidget
+    grok.template('applicanteditpage')
+    manage_applications = True
+    pnav = 3
+    display_actions = [[_('Save'), _('Final Submit')],
+        [_('Add online payment ticket'),_('Remove selected tickets')]]
+
+
+class ApplicantEditFormPage(ApplicantManageFormPage):
+    """An applicant-centered edit view for applicant data.
+    """
+    grok.context(IApplicantEdit)
+    grok.name('edit')
+    grok.require('waeup.handleApplication')
+    form_fields = grok.AutoFields(IApplicantEdit).omit(
+        'locked', 'course_admitted', 'student_id',
+        'screening_score', 'reg_number'
+        )
+    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
+    form_fields['phone'].custom_widget = PhoneWidget
+    form_fields['applicant_id'].for_display = True
+    grok.template('applicanteditpage')
+    manage_applications = False
Index: main/waeup.custom/trunk/src/waeup/custom/applicants/container.py
===================================================================
--- main/waeup.custom/trunk/src/waeup/custom/applicants/container.py	(revision 8010)
+++ 	(revision )
@@ -1,53 +1,0 @@
-## $Id$
-##
-## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with this program; if not, write to the Free Software
-## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##
-"""
-Additional containers for university applicants.
-"""
-
-import grok
-from zope.component.factory import Factory
-from zope.component.interfaces import IFactory
-from waeup.custom.interfaces import MessageFactory as _
-from waeup.kofa.utils.helpers import attrs_to_fields
-from waeup.kofa.applicants.container import (
-    ApplicantsContainer, generate_applicant_id)
-from waeup.kofa.applicants.interfaces import (
-    IApplicantsContainer, IApplicantsContainerAdd,
-    IApplicantsContainerProvider)
-from waeup.custom.applicants.interfaces import IPGApplicant
-
-class PGApplicantsContainer(ApplicantsContainer):
-    """A pg applicants container contains postgraduate applicants.
-    """
-    grok.implements(IApplicantsContainer,IApplicantsContainerAdd)
-    container_title = _(u'Postgraduates')
-    container_description = _(u'handles postgraduate applicants')
-    iface = IPGApplicant
-    factory_name = 'waeup.PGApplicant'
-
-PGApplicantsContainer = attrs_to_fields(PGApplicantsContainer)
-
-class PGApplicantsContainerProvider(grok.GlobalUtility):
-    """A utility that provides pg applicants containers.
-    """
-    grok.implements(IApplicantsContainerProvider)
-    grok.name('waeup.kofa.applicants.PGApplicantsContainer')
-    factory = PGApplicantsContainer
-
-factory = Factory(lambda : PGApplicantsContainer, 'PGApplicantsContainer')
-grok.global_utility(factory, IFactory, name='waeup.PGApplicantsContainer')
Index: main/waeup.custom/trunk/src/waeup/custom/applicants/interfaces.py
===================================================================
--- main/waeup.custom/trunk/src/waeup/custom/applicants/interfaces.py	(revision 8010)
+++ main/waeup.custom/trunk/src/waeup/custom/applicants/interfaces.py	(revision 8012)
@@ -19,5 +19,12 @@
 """
 
-from waeup.kofa.applicants.interfaces import IApplicant, IApplicantEdit
+from zope import schema
+from waeup.kofa.applicants.interfaces import IApplicant, IApplicantProcessData
+from waeup.custom.interfaces import MessageFactory as _
+
+class IUGApplicant(IApplicant):
+    """An undergraduate applicant.
+
+    """
 
 class IPGApplicant(IApplicant):
@@ -26,5 +33,28 @@
     """
 
-class IPGApplicantEdit(IApplicantEdit):
+    employer = schema.TextLine(
+        title = _(u'Employer'),
+        required = False,
+        readonly = False,
+        )
+
+class IApplicant(IUGApplicant,IPGApplicant):
+    """An interface for both types of applicants.
+
+    """
+
+class IUGApplicantEdit(IApplicantProcessData):
+    """An undergraduate applicant interface for editing.
+
+    Here we can repeat the fields from base data and set the
+    `required` and `readonly` attributes to True to further restrict
+    the data access. Or we can allow only certain certificates to be
+    selected by choosing the appropriate source.
+
+    We cannot omit fields here. This has to be done in the
+    respective form page.
+    """
+
+class IPGApplicantEdit(IApplicantProcessData):
     """A postgraduate applicant interface for editing.
 
Index: main/waeup.custom/trunk/src/waeup/custom/applicants/sample_applicant_data.csv
===================================================================
--- main/waeup.custom/trunk/src/waeup/custom/applicants/sample_applicant_data.csv	(revision 8010)
+++ 	(revision )
@@ -1,4 +1,0 @@
-container_code,reg_number,firstname,lastname,date_of_birth,email,course1,sex
-pg2011,1001,Aaren,Pieri,1990-01-02,xx@yy.zz,CERT1,m
-pg2011,1002,Aaren,Finau,1990-01-03,xx@yy.zz,CERT1,m
-pg2011,1003,Aaren,Berson,1990-01-04,xx@yy.zz,CERT1,m
Index: main/waeup.custom/trunk/src/waeup/custom/applicants/tests.py
===================================================================
--- main/waeup.custom/trunk/src/waeup/custom/applicants/tests.py	(revision 8010)
+++ main/waeup.custom/trunk/src/waeup/custom/applicants/tests.py	(revision 8012)
@@ -22,29 +22,20 @@
 from zope.component.hooks import setSite, clearSite
 from zope.component import createObject
+from zope.testbrowser.testing import Browser
 from waeup.kofa.app import University
 from waeup.kofa.university.faculty import Faculty
 from waeup.kofa.university.department import Department
 from waeup.kofa.testing import FunctionalTestCase
+from waeup.kofa.applicants.container import ApplicantsContainer
 from waeup.custom.testing import FunctionalLayer
 from waeup.kofa.interfaces import IBatchProcessor
-from waeup.kofa.applicants.applicant import Applicant
-from waeup.custom.applicants.container import PGApplicantsContainer
-from waeup.custom.applicants.applicant import PGApplicant
-from waeup.custom.applicants.batching import PGApplicantProcessor
-from waeup.custom.applicants.interfaces import IPGApplicant, IPGApplicantEdit
 
-APPLICANT_SAMPLE_DATA = open(
-    os.path.join(os.path.dirname(__file__), 'sample_applicant_data.csv'),
-    'rb').read()
-APPLICANT_HEADER_FIELDS = APPLICANT_SAMPLE_DATA.split(
-    '\n')[0].split(',')
-
-class PGApplicantProcessorTest(FunctionalTestCase):
-    """Perform some batching tests.
+class ApplicantUITest(FunctionalTestCase):
+    """Perform some browser tests.
     """
     layer = FunctionalLayer
 
     def setUp(self):
-        super(PGApplicantProcessorTest, self).setUp()
+        super(ApplicantUITest, self).setUp()
         # Setup a sample site for each test
         app = University()
@@ -62,8 +53,13 @@
         setSite(app)
 
-        # Add an applicants container
-        self.container = PGApplicantsContainer()
-        self.container.code = u'pg2011'
-        self.app['applicants']['pg2011'] = self.container
+        # Add an two different applicants containers
+        self.pgcontainer = ApplicantsContainer()
+        self.pgcontainer.code = u'pg2011'
+        self.pgcontainer.prefix = u'pg'
+        self.app['applicants']['pg2011'] = self.pgcontainer
+        self.ugcontainer = ApplicantsContainer()
+        self.ugcontainer.code = u'app2011'
+        self.ugcontainer.prefix = u'app'
+        self.app['applicants']['app2011'] = self.ugcontainer
 
         # Populate university
@@ -78,54 +74,47 @@
             self.certificate)
 
-        # Add applicant with subobjects
-        applicant = PGApplicant()
-        applicant.firstname = u'Anna'
-        applicant.lastname = u'Tester'
-        self.app['applicants']['pg2011'].addApplicant(applicant)
-        self.application_number = applicant.application_number
-        self.applicant = self.app['applicants']['pg2011'][
-            self.application_number]
-        self.processor = PGApplicantProcessor()
-        self.workdir = tempfile.mkdtemp()
-        self.csv_file = os.path.join(self.workdir, 'sample_applicant_data.csv')
-        open(self.csv_file, 'wb').write(APPLICANT_SAMPLE_DATA)
+        # Add (customized) applicants
+        pgapplicant = createObject(u'waeup.Applicant')
+        pgapplicant.firstname = u'Anna'
+        pgapplicant.lastname = u'Post'
+        self.app['applicants']['pg2011'].addApplicant(pgapplicant)
+        self.pgapplication_number = pgapplicant.application_number
+        self.pgapplicant = self.app['applicants']['pg2011'][
+            self.pgapplication_number]
+
+        ugapplicant = createObject(u'waeup.Applicant')
+        ugapplicant.firstname = u'Klaus'
+        ugapplicant.lastname = u'Under'
+        self.app['applicants']['app2011'].addApplicant(ugapplicant)
+        self.ugapplication_number = ugapplicant.application_number
+        self.ugapplicant = self.app['applicants']['app2011'][
+            self.ugapplication_number]
+
+        self.browser = Browser()
+        self.browser.handleErrors = False
 
     def tearDown(self):
-        super(PGApplicantProcessorTest, self).tearDown()
-        shutil.rmtree(self.workdir)
+        super(ApplicantUITest, self).tearDown()
         shutil.rmtree(self.dc_root)
         clearSite()
         return
 
-    def test_interfaces(self):
-        # Make sure we fulfill the interface contracts.
-        assert verifyObject(IBatchProcessor, self.processor) is True
-        assert verifyClass(
-            IBatchProcessor, PGApplicantProcessor) is True
-        assert verifyObject(IPGApplicant, self.applicant) is True
-        assert verifyClass(
-            IPGApplicant, PGApplicant) is True
-        assert verifyObject(IPGApplicantEdit, self.applicant) is True
-        assert verifyClass(
-            IPGApplicantEdit, PGApplicant) is True
+    def test_manage_and_view_applicant(self):
+        # Managers can manage pg applicants
+        pgapplicant_path = ('http://localhost/app/applicants/pg2011/%s'
+            % self.pgapplication_number)
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(pgapplicant_path)
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        # The IPGApplicant interface is really used
+        self.assertTrue('Employer' in self.browser.contents)
+        # If we move the applicant to the container,
+        # the Employer field disappears
+        ugapplicant_path = ('http://localhost/app/applicants/app2011/%s'
+            % self.ugapplication_number)
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(ugapplicant_path)
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
 
-    def test_wrong_child(self):
-        applicant = Applicant()
-        applicant.firstname = u'Klaus'
-        applicant.lastname = u'Tester'
-        self.assertRaises(
-            TypeError, self.app['applicants']['pg2011'].addApplicant, applicant)
-
-    def test_import(self):
-        num, num_warns, fin_file, fail_file = self.processor.doImport(
-            self.csv_file, APPLICANT_HEADER_FIELDS)
-        self.assertEqual(num_warns,0)
-        keys = self.app['applicants']['pg2011'].keys()
-        assert len(keys) == 4
-        container = self.app['applicants']['pg2011']
-        assert  container.__implemented__.__name__ == (
-            'waeup.custom.applicants.container.PGApplicantsContainer')
-        applicant = container[keys[0]]
-        assert applicant.__implemented__.__name__ == (
-            'waeup.custom.applicants.applicant.PGApplicant')
-        shutil.rmtree(os.path.dirname(fin_file))
+        self.assertFalse('Employer' in self.browser.contents)
+        return
Index: main/waeup.custom/trunk/src/waeup/custom/applicants/utils.py
===================================================================
--- main/waeup.custom/trunk/src/waeup/custom/applicants/utils.py	(revision 8010)
+++ main/waeup.custom/trunk/src/waeup/custom/applicants/utils.py	(revision 8012)
@@ -36,4 +36,5 @@
         'ct': ['Certificate Programmes', 'CTP'],
         'dp': ['Diploma Programmes', 'DPP'],
-        'pce': ['PCE Screening', 'PCE']
+        'pce': ['PCE Screening', 'PCE'],
+        'pg': ['Postgraduate Programmes', 'PCE']
         }
