Index: main/waeup.kofa/trunk/src/waeup/kofa/university/batching.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/university/batching.py	(revision 8301)
+++ main/waeup.kofa/trunk/src/waeup/kofa/university/batching.py	(revision 8302)
@@ -26,5 +26,8 @@
 import grok
 from zope.interface import Interface
-from waeup.kofa.interfaces import IBatchProcessor
+from zope.component import queryUtility
+from zope.schema import getFields
+from zope.catalog.interfaces import ICatalog
+from waeup.kofa.interfaces import IBatchProcessor, IGNORE_MARKER, FatalCSVError
 from waeup.kofa.university.interfaces import (
     IFacultiesContainer, IFaculty, ICourse, IDepartment, ICertificate,
@@ -184,32 +187,75 @@
     iface = ICertificate
 
-    location_fields = ['code', 'faculty_code', 'department_code']
+    location_fields = ['code']
     factory_name = 'waeup.Certificate'
 
     mode = None
 
-    def parentsExist(self, row, site):
+    @property
+    def available_fields(self):
+        fields = getFields(self.iface)
+        return sorted(list(set(
+            ['faculty_code','department_code'] + fields.keys())))
+
+    def checkHeaders(self, headerfields, mode='create'):
+        req = self.req[mode]
+        # Check for required fields...
+        for field in req:
+            if not field in headerfields:
+                raise FatalCSVError(
+                    "Need at least columns %s for import!" %
+                    ', '.join(["'%s'" % x for x in req]))
+        # Check for double fields. Cannot happen because this error is
+        # already catched in views
+        not_ignored_fields = [x for x in headerfields
+                              if not x.startswith('--')]
+        if len(set(not_ignored_fields)) < len(not_ignored_fields):
+            raise FatalCSVError(
+                "Double headers: each column name may only appear once.")
+        if mode == 'create':
+            if not 'faculty_code' in headerfields \
+                and not 'department_code' in headerfields :
+                raise FatalCSVError(
+                    "Need at least columns faculty_code and department_code")
+        return True
+
+    def parentsExist(self, row, site):
+        return self.getParent(row,site) is not None
+
+    def entryExists(self, row, site):
+        parent = self.getParent(row, site)
+        if parent is not None:
+            return row['code'] in parent.keys()
+        return False
+
+    def getParent(self, row, site):
         if not 'faculties' in site.keys():
-            return False
-        if not row['faculty_code'] in site['faculties'].keys():
-            return False
-        faculty = site['faculties'][row['faculty_code']]
-        return row['department_code'] in faculty.keys()
-
-    def entryExists(self, row, site):
-        if not self.parentsExist(row, site):
-            return False
-        parent = self.getParent(row, site)
-        return row['code'] in parent.keys()
-
-    def getParent(self, row, site):
-        dept = site['faculties'][row['faculty_code']][row['department_code']]
-        return dept.certificates
-
-    def getEntry(self, row, site):
-        if not self.entryExists(row, site):
-            return None
-        parent = self.getParent(row, site)
-        return parent.get(row['code'])
+            return None
+        # If both faculty and department codes are provided, use
+        # these to get parent.
+        if row.get('faculty_code',None) not in (None, IGNORE_MARKER) and \
+            row.get('department_code',None) not in (None, IGNORE_MARKER):
+            if not row['faculty_code'] in site['faculties'].keys():
+                return None
+            faculty = site['faculties'][row['faculty_code']]
+            if not row['department_code'] in faculty.keys():
+                return None
+            dept = faculty[row['department_code']]
+            return dept.certificates
+        # If department code or faculty code is missing,
+        # use catalog to get parent. Makes only sense in update mode but
+        # does also work in create mode.
+        cat = queryUtility(ICatalog, name='certificates_catalog')
+        results = list(
+            cat.searchResults(code=(row['code'], row['code'])))
+        if results:
+            return results[0].__parent__
+        return None
+
+    def getEntry(self, row, site):
+        parent = self.getParent(row, site)
+        if parent is not None:
+            return parent.get(row['code'])
+        return None
 
     def addEntry(self, obj, row, site):
