Index: /main/waeup.kofa/trunk/CHANGES.txt
===================================================================
--- /main/waeup.kofa/trunk/CHANGES.txt	(revision 14637)
+++ /main/waeup.kofa/trunk/CHANGES.txt	(revision 14638)
@@ -4,4 +4,6 @@
 1.6.dev0 (unreleased)
 =======================
+
+* Add `course_category` attribute to certificate courses.
 
 * Simplify configuration of maximum or minimum total credits.
@@ -140,5 +142,5 @@
 
 * Configure transfer payments and let students enter their desired
-  study course. Save entered text in p_item attribute.
+  study course. Save entered text in `p_item` attribute.
 
 * Add further permissions to the local `ApplicationsManager` role and
@@ -324,5 +326,5 @@
 * Show flash message on general search page for the academic section.
 
-* Fix CertificateCourseProcessor. Mandatory is not a required field
+* Fix `CertificateCourseProcessor`. Mandatory is not a required field
   and might be missing in import files. Improve logging. Extend and
   fix CertCourseProcessorTests.
Index: /main/waeup.kofa/trunk/docs/source/userdocs/academics.rst
===================================================================
--- /main/waeup.kofa/trunk/docs/source/userdocs/academics.rst	(revision 14637)
+++ /main/waeup.kofa/trunk/docs/source/userdocs/academics.rst	(revision 14638)
@@ -173,8 +173,11 @@
 above). A former course cannot be part of a curriculum.
 
-Certificate courses have two more attributes: `level` (integer) and
-`mandatory` (boolean). Simply put, certificate courses carry the
-information at which level a certain course can or has to be taken
-to meet the current curriculum.
+Certificate courses have three more attributes: `level` (integer),
+`mandatory` (boolean) and `course_category` (choice). Simply put,
+certificate courses carry the information at which level a certain
+course can or has to be taken to meet the current curriculum. Course
+categories are not available in the base package but can easily be
+defined in custom packages. Some universities distinguish e.g. core,
+required and elective courses and need this information in reports.
 
 No local role can be assigned at certificate course tree level.
Index: /main/waeup.kofa/trunk/src/waeup/kofa/app.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/app.py	(revision 14637)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/app.py	(revision 14638)
@@ -88,4 +88,6 @@
         getUtility(IKofaPluggable, name='certificates').update(
             self, 'certificates', self.logger)
+        getUtility(IKofaPluggable, name='certcourses').update(
+            self, 'certcourses', self.logger)
         return
 attrs_to_fields(University)
Index: /main/waeup.kofa/trunk/src/waeup/kofa/browser/templates/certificatecoursepage.pt
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/browser/templates/certificatecoursepage.pt	(revision 14637)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/browser/templates/certificatecoursepage.pt	(revision 14638)
@@ -16,5 +16,5 @@
     </tr>
     <tr>
-      <td i18n:translate="">Provided by:</td>
+      <td class="fieldname" i18n:translate="">Provided by:</td>
       <td>
       <span tal:content="python: context.course.__parent__.__parent__.longtitle">DEPARTMENT</span>
@@ -28,4 +28,8 @@
     </tr>
     <tr>
+      <td i18n:translate="">Course Category:</td>
+      <td tal:content="context/course_category">NONE</td>
+    </tr>
+    <tr>
       <td i18n:translate="">Required course:</td>
       <td tal:content="context/mandatory">REQUIRED</td>
Index: /main/waeup.kofa/trunk/src/waeup/kofa/university/certificate.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/university/certificate.py	(revision 14637)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/university/certificate.py	(revision 14638)
@@ -95,9 +95,10 @@
         return "%s (%s)" % (self.title,self.code)
 
-    def addCertCourse(self, course, level=100, mandatory=True):
+    def addCertCourse(self, course, level=100,
+                      mandatory=True, course_category=None):
         """Add a certificate course.
         """
         code = "%s_%s" % (course.code, level)
-        self[code] = CertificateCourse(course, level, mandatory)
+        self[code] = CertificateCourse(course, level, mandatory, course_category)
         self[code].__parent__ = self
         self[code].__name__ = code
@@ -156,8 +157,10 @@
     grok.implements(ICertificateCourse)
 
-    def __init__(self, course=None, level=100, mandatory=True):
+    def __init__(self, course=None, level=100,
+                 mandatory=True, course_category=None):
         self.course = course
         self.level = level
         self.mandatory = mandatory
+        self.course_category = course_category
 
     def getCourseCode(self):
@@ -249,2 +252,33 @@
                     pass
         return
+
+class CertificateCoursesPlugin(grok.GlobalUtility):
+    """A plugin that updates certificate courses.
+    """
+
+    grok.implements(IKofaPluggable)
+    grok.name('certcourses')
+
+    deprecated_attributes = []
+
+    def setup(self, site, name, logger):
+        return
+
+    def update(self, site, name, logger):
+        cat = getUtility(ICatalog, name='certcourses_catalog')
+        results = cat.apply({'course_code':(None,None)})
+        uidutil = getUtility(IIntIds, context=cat)
+        items = getFields(ICertificateCourse).items()
+        for r in results:
+            o = uidutil.getObject(r)
+            # Add new attributes
+            for i in items:
+                if not hasattr(o,i[0]):
+                    setattr(o,i[0],i[1].missing_value)
+            # Remove deprecated attributes
+            for i in self.deprecated_attributes:
+                try:
+                    delattr(o,i)
+                except AttributeError:
+                    pass
+        return
Index: /main/waeup.kofa/trunk/src/waeup/kofa/university/interfaces.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/university/interfaces.py	(revision 14637)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/university/interfaces.py	(revision 14638)
@@ -31,4 +31,5 @@
     SemesterSource,
     DegreeSource,
+    CourseCategorySource
     )
 
@@ -329,4 +330,10 @@
         )
 
+    course_category = schema.Choice(
+        title = _(u'Course Category'),
+        source = CourseCategorySource(),
+        required = False,
+        )
+
     mandatory = schema.Bool(
         title = _(u'Registration required'),
Index: /main/waeup.kofa/trunk/src/waeup/kofa/university/vocabularies.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/university/vocabularies.py	(revision 14637)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/university/vocabularies.py	(revision 14638)
@@ -94,4 +94,10 @@
         return "%s - %s" % (value.code, value.title[:64])
 
+class CourseCategorySource(ContextualDictSourceFactoryBase):
+    """A course category source provides additional information on
+    the course.
+    """
+    #: name of dict to deliver from kofa utils.
+    DICT_NAME = 'COURSE_CATEGORY_DICT'
 
 
Index: /main/waeup.kofa/trunk/src/waeup/kofa/utils/utils.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/utils/utils.py	(revision 14637)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/utils/utils.py	(revision 14638)
@@ -158,4 +158,7 @@
         }
 
+    COURSE_CATEGORY_DICT = {
+        }
+
     SPECIAL_HANDLING_DICT = {
         'regular': 'Regular Hostel',
