Index: main/waeup.sirp/trunk/src/waeup/sirp/students/batching.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/students/batching.py	(revision 6848)
+++ main/waeup.sirp/trunk/src/waeup/sirp/students/batching.py	(revision 6849)
@@ -8,11 +8,15 @@
 """
 import grok
+import csv
+import copy
 from zope.interface import Interface
 from zope.schema import getFields
 from zope.component import queryUtility
 from zope.catalog.interfaces import ICatalog
-from waeup.sirp.interfaces import IBatchProcessor, FatalCSVError
+from waeup.sirp.interfaces import (
+    IBatchProcessor, FatalCSVError, IObjectConverter)
 from waeup.sirp.students.interfaces import (
-    IStudent, IStudentStudyCourse, IStudentStudyCourseImport)
+    IStudent, IStudentStudyCourse, IStudentStudyCourseImport,
+    IStudentUpdateByRegNo, IStudentUpdateByMatricNo)
 from waeup.sirp.utils.batching import BatchProcessor
 
@@ -29,5 +33,5 @@
     iface = IStudent
 
-    location_fields = ['student_id',]
+    location_fields = []
     factory_name = 'waeup.Student'
 
@@ -35,24 +39,50 @@
 
     @property
-    def req(self):
-        result = dict(
-            create = self.required_fields,
-            update = self.location_fields,
-            remove = self.location_fields,
-        )
-        return result
+    def available_fields(self):
+        result = []
+        return sorted(list(set(
+            ['student_id','reg_number','matric_number'] + getFields(
+                self.iface).keys())))
+
+    def checkHeaders(self, headerfields, mode='create'):
+        if not 'reg_number' in headerfields and not 'student_id' in headerfields and not 'matric_number' in headerfields:
+            raise FatalCSVError(
+                "Need at least columns student_id or reg_number or matric_number for import!")
+        if mode == 'create':
+            for field in self.required_fields:
+                if not field in headerfields:
+                    raise FatalCSVError(
+                        "Need at least columns %s for import!" %
+                        ', '.join(["'%s'" % x for x in self.required_fields]))
+        # Check for fields to be ignored...
+        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.")
+        return True
 
     def parentsExist(self, row, site):
         return 'students' in site.keys()
+
+    def getLocator(self, row):
+        if 'student_id' in row.keys() and row['student_id']:
+            return 'student_id'
+        elif 'reg_number' in row.keys() and row['reg_number']:
+            return 'reg_number'
+        elif 'matric_number' in row.keys() and row['matric_number']:
+            return 'matric_number'
+        else:
+            return None
 
     # The entry never exists in create mode.
     def entryExists(self, row, site):
         if not 'students' in site.keys():
-            return False
-        if 'student_id' in row.keys() and row['student_id']:
+            return None
+        if self.getLocator(row) == 'student_id':
             if row['student_id'] in site['students']:
                 student = site['students'][row['student_id']]
                 return student
-        elif 'reg_number' in row.keys() and row['reg_number']:
+        elif self.getLocator(row) == 'reg_number':
             reg_number = row['reg_number']
             cat = queryUtility(ICatalog, name='students_catalog')
@@ -61,6 +91,5 @@
             if results:
                 return results[0]
-        elif 'matric_number' in row.keys() and row['matric_number']:
-            #import pdb; pdb.set_trace()
+        elif self.getLocator(row) == 'matric_number':
             matric_number = row['matric_number']
             cat = queryUtility(ICatalog, name='students_catalog')
@@ -69,5 +98,5 @@
             if results:
                 return results[0]
-        return False
+        return None
 
     def getParent(self, row, site):
@@ -88,4 +117,34 @@
             del parent[student.student_id]
         pass
+
+    def getMapping(self, path, headerfields, mode):
+        """Get a mapping from CSV file headerfields to actually used fieldnames.
+        """
+        result = dict()
+        reader = csv.reader(open(path, 'rb'))
+        raw_header = reader.next()
+        for num, field in enumerate(headerfields):
+            if field not in ['student_id', 'reg_number', 'matric_number'] and mode == 'remove':
+                continue
+            if field == u'--IGNORE--':
+                # Skip ignored columns in failed and finished data files.
+                continue
+            result[raw_header[num]] = field
+        return result
+
+    def checkConversion(self, row, mode='create'):
+        """Validates all values in row.
+        """
+        if mode in ['update', 'remove']:
+            if self.getLocator(row) == 'reg_number':
+                iface = IStudentUpdateByRegNo
+            elif self.getLocator(row) == 'matric_number':
+                iface = IStudentUpdateByMatricNo
+        else:
+            iface = self.iface
+        converter = IObjectConverter(iface)
+        errs, inv_errs, conv_dict =  converter.fromStringDict(
+            row, self.factory_name)
+        return errs, inv_errs, conv_dict
 
 class StudentStudyCourseProcessor(BatchProcessor):
@@ -102,4 +161,6 @@
     factory_name = 'waeup.StudentStudyCourse'
 
+    location_fields = []
+
     mode = None
 
@@ -125,5 +186,5 @@
     def parentsExist(self, row, site):
         if not 'students' in site.keys():
-            return False
+            return None
         if 'student_id' in row.keys() and row['student_id']:
             if row['student_id'] in site['students']:
@@ -132,4 +193,5 @@
         elif 'reg_number' in row.keys() and row['reg_number']:
             reg_number = row['reg_number']
+            #import pdb; pdb.set_trace()
             cat = queryUtility(ICatalog, name='students_catalog')
             results = list(
@@ -144,13 +206,13 @@
             if results:
                 return results[0]
-        return False
+        return None
 
     def entryExists(self, row, site):
         student = self.parentsExist(row, site)
         if not student:
-            return False
+            return None
         if 'studycourse' in student:
             return student
-        return False
+        return None
 
     def getEntry(self, row, site):
Index: main/waeup.sirp/trunk/src/waeup/sirp/students/interfaces.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/students/interfaces.py	(revision 6848)
+++ main/waeup.sirp/trunk/src/waeup/sirp/students/interfaces.py	(revision 6849)
@@ -100,5 +100,5 @@
     student_id = schema.TextLine(
         title = u'Student ID',
-        required = True,
+        required = False,
         )
 
@@ -165,4 +165,24 @@
     """Representation of a student.
     """
+
+class IStudentUpdateByRegNo(IStudent):
+    """Representation of a student. Skip regular reg_number validation.
+    """
+
+    reg_number = schema.TextLine(
+        title = u'Registration Number',
+        default = None,
+        required = False,
+        )
+
+class IStudentUpdateByMatricNo(IStudent):
+    """Representation of a student. Skip regular matric_number validation.
+    """
+
+    matric_number = schema.TextLine(
+        title = u'Matriculation Number',
+        default = None,
+        required = False,
+        )
 
 class IStudentStudyCourse(IWAeUPObject):
