Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/applicant.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/applicant.py	(revision 15941)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/applicant.py	(revision 15943)
@@ -253,11 +253,11 @@
         """
         file_store = getUtility(IExtFileStore)
-        filenames = getUtility(IApplicantsUtils).FILENAMES
+        filenames = getUtility(IApplicantsUtils).ADDITIONAL_FILES
         for filename in filenames:
-            appl_file = file_store.getFileByContext(self, attr=filename)
+            appl_file = file_store.getFileByContext(self, attr=filename[1])
             if appl_file is None:
                 return
             stud_file_id = IFileStoreNameChooser(student).chooseName(
-                attr=filename)
+                attr=filename[1])
             file_store.createFile(stud_file_id, appl_file)
         return
@@ -456,6 +456,7 @@
     file_store.deleteFileByContext(applicant)
     # Remove all other files too
-
-
+    filenames = getUtility(IApplicantsUtils).ADDITIONAL_FILES
+    for filename in filenames:
+        file_store.deleteFileByContext(applicant, attr=filename[1])
     # Remove global role
     role_manager = IPrincipalRoleManager(grok.getSite())
Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py	(revision 15941)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py	(revision 15943)
@@ -588,4 +588,18 @@
 
     @property
+    def file_links(self):
+        html = ''
+        file_store = getUtility(IExtFileStore)
+        additional_files = getUtility(IApplicantsUtils).ADDITIONAL_FILES
+        for filename in additional_files:
+            pdf = getUtility(IExtFileStore).getFileByContext(
+                self.context, attr=filename[1])
+            if pdf:
+                html += '<a href="%s">%s</a>, ' % (self.url(
+                    self.context, filename[1]), filename[0])
+        html = html.strip(', ')
+        return html
+
+    @property
     def display_payments(self):
         if self.context.payments:
@@ -939,4 +953,28 @@
     return True
 
+def handle_file_upload(upload, context, view, attr=None):
+    """Handle upload of applicant files.
+
+    Returns `True` in case of success or `False`.
+
+    Please note that file pointer passed in (`upload`) most probably
+    points to end of file when leaving this function.
+    """
+    size = file_size(upload)
+    max_upload_size = 1024 * getUtility(IStudentsUtils).MAX_KB
+    if size > max_upload_size:
+        view.flash(_('Uploaded file is too big!'))
+        return False
+    dummy, ext = os.path.splitext(upload.filename)
+    ext.lower()
+    if ext != '.pdf':
+        view.flash(_('pdf file extension expected.'))
+        return False
+    upload.seek(0) # file pointer moved when determining size
+    store = getUtility(IExtFileStore)
+    file_id = IFileStoreNameChooser(context).chooseName(attr=attr)
+    store.createFile(file_id, upload)
+    return True
+
 class ApplicantManageFormPage(KofaEditFormPage):
     """A full edit view for applicant data.
@@ -1008,4 +1046,18 @@
             if self.upload_success:
                 self.context.writeLogMessage(self, 'saved: passport')
+        file_store = getUtility(IExtFileStore)
+        self.additional_files = getUtility(IApplicantsUtils).ADDITIONAL_FILES
+        for filename in self.additional_files:
+            upload = self.request.form.get(filename[1], None)
+            if upload:
+                # We got a fresh file upload
+                success = handle_file_upload(
+                    upload, self.context, self, attr=filename[1])
+                if success:
+                    self.context.writeLogMessage(
+                        self, 'saved: %s' % filename[1])
+                else:
+                    self.upload_success = False
+        self.max_file_upload_size = 1024 * getUtility(IStudentsUtils).MAX_KB
         return
 
@@ -1674,2 +1726,21 @@
         self.redirect(self.application_url())
         return
+
+class AdditionalFile(grok.View):
+    """Renders additional pdf files for applicants.
+    This is a baseclass.
+    """
+    grok.baseclass()
+    grok.context(IApplicant)
+    grok.require('waeup.viewApplication')
+
+    def render(self):
+        pdf = getUtility(IExtFileStore).getFileByContext(
+            self.context, attr=self.__name__)
+        self.response.setHeader('Content-Type', 'application/pdf')
+        return pdf
+
+class TestFile(AdditionalFile):
+    """Renders testfile.pdf.
+    """
+    grok.name('testfile.pdf')
Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/applicantdisplaypage.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/applicantdisplaypage.pt	(revision 15941)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/applicantdisplaypage.pt	(revision 15943)
@@ -36,5 +36,17 @@
           <tal:password replace="view/hasPassword" />
       </td>
-    <tr>
+    </tr>
+    <tal:files>
+      <tr>
+        <td class="separator" colspan=2>
+          Files
+        </td>
+      </tr>
+      <tr>
+        <td colspan=2>
+          <span tal:replace="structure view/file_links" />
+        </td>
+      </tr>
+    </tal:files>
   </tbody>
 </table>
Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/applicanteditpage.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/applicanteditpage.pt	(revision 15941)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/applicanteditpage.pt	(revision 15943)
@@ -43,4 +43,33 @@
           <span tal:replace="view/max_upload_size">10 KB</span>
       </tr>
+
+      <tal:files>
+        <tr tal:repeat="filename view/additional_files">
+          <td class="fieldname">
+            <span tal:replace="python:filename[0]">FILENAME</span>
+          </td>
+          <td>
+            <p tal:condition="python:view.file_exists(filename[1])">
+              <a tal:attributes="href python:filename[1]"i18n:translate="">
+                Download pdf file
+              </a>
+            </p>
+            <div class="input-group half">
+              <div class="input-group-btn">
+                <div class="btn btn-default btn-file">
+                  Select&hellip;
+                <input type="file" tal:attributes="name python:filename[1]" />
+                </div>
+              </div>
+              <input type="text" class="form-control" readonly>
+            </div>
+            <span i18n:translate="">
+              PDF files only. Max. file size:
+            </span>
+            <span tal:replace="view/max_file_upload_size">10 KB</span>
+          </td>
+        </tr>
+      </tal:files>
+
       <tr tal:condition="view/manage_applications">
         <td class="fieldname" i18n:translate="">Password:</td>
Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_browser.py	(revision 15941)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_browser.py	(revision 15943)
@@ -1278,4 +1278,56 @@
             '<option selected="selected" value="CERT1">'
             in self.browser.contents)
+
+    def test_upload_testfile(self):
+        self.login()
+        self.browser.open(self.edit_path)
+        self.fill_correct_values() # fill other fields with correct values       
+        # Create a pseudo file with acceptable size
+        pdf_content = 'A' * 1024 * 300  # A string of 300 KB size
+        pseudo_pdf = StringIO(pdf_content)
+        ctrl = self.browser.getControl(name='testfile.pdf')
+        file_ctrl = ctrl.mech_control
+        file_ctrl.add_file(pseudo_pdf, filename='testfile.pdf')
+        self.browser.getControl("Save").click() # submit form
+        self.assertTrue('Uploaded file is too big!'
+            in self.browser.contents)
+        pdf_content = 'A' * 1024 * 200  # A string of 300 KB size
+        pseudo_pdf = StringIO(pdf_content)
+        ctrl = self.browser.getControl(name='testfile.pdf')
+        file_ctrl = ctrl.mech_control
+        file_ctrl.add_file(pseudo_pdf, filename='testfile.pdf')
+        self.browser.getControl("Save").click() # submit form
+        # The file has been successfully uploaded
+        self.assertTrue('Form has been saved.' in self.browser.contents)
+        # There is really a file stored for the applicant
+        storage = getUtility(IExtFileStore)
+        file_id = IFileStoreNameChooser(self.applicant).chooseName(
+            attr='testfile.pdf')
+        # The stored file can be fetched
+        fd = storage.getFile(file_id)
+        file_len = len(fd.read())
+        self.assertEqual(file_len, 204800)
+        # A file link is displayed on the edit view ...
+        self.browser.open(self.edit_path)
+        self.assertTrue('<a href="testfile.pdf">' in self.browser.contents)
+        # ... and on the dislay view
+        self.browser.open(self.view_path)
+        self.assertTrue('testfile.pdf">Test File</a>'
+            in self.browser.contents)
+        # Adding file is properly logged
+        logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'applicants.log')
+        logcontent = open(logfile).read()
+        self.assertTrue(
+            '%s - applicants.browser.ApplicantEditFormPage'
+            ' - %s - saved: testfile.pdf'
+            % (self.applicant.applicant_id, self.applicant.applicant_id)
+            in logcontent)
+        # When an applicant is removed, also the pdf files are gone.
+        del self.applicantscontainer[self.applicant.application_number]
+        fd = storage.getFile(file_id)
+        self.assertTrue(fd is None)
+        return
+
 
 class ApplicantRegisterTests(ApplicantsFullSetup):
Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/utils.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/utils.py	(revision 15941)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/utils.py	(revision 15943)
@@ -48,7 +48,7 @@
       }
 
-    #: A tuple of names of files to be copied over
-    #: to students section.
-    FILENAMES = ('testfile.pdf',)
+    #: A tuple of tuple of file names to be uploaded by applicants and copied 
+    #: over to the students section.
+    ADDITIONAL_FILES = (('Test File','testfile.pdf'),)
 
     def setPaymentDetails(self, container, payment, applicant):
