Index: main/waeup.ikoba/trunk/src/waeup/ikoba/interfaces.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/interfaces.py	(revision 12300)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/interfaces.py	(revision 12306)
@@ -86,4 +86,9 @@
             SimpleTerm(value, value, title) for title, value in terms])
 
+currencies = SimpleIkobaVocabulary(
+    (_('Naira'),'naira'),
+    (_('USD'),'usd'),
+    (_('EUR'),'eur'),
+    )
 
 class ContextualDictSourceFactoryBase(SmartBasicContextualSourceFactory):
@@ -105,25 +110,4 @@
         utils = getUtility(IIkobaUtils)
         return getattr(utils, self.DICT_NAME)[value]
-
-class SubjectSource(BasicSourceFactory):
-    """A source for school subjects used in exam documentation.
-    """
-    def getValues(self):
-        subjects_dict = getUtility(IIkobaUtils).EXAM_SUBJECTS_DICT
-        return sorted(subjects_dict.keys())
-
-    def getTitle(self, value):
-        subjects_dict = getUtility(IIkobaUtils).EXAM_SUBJECTS_DICT
-        return "%s:" % subjects_dict[value]
-
-class GradeSource(BasicSourceFactory):
-    """A source for exam grades.
-    """
-    def getValues(self):
-        for entry in getUtility(IIkobaUtils).EXAM_GRADES:
-            yield entry[0]
-
-    def getTitle(self, value):
-        return dict(getUtility(IIkobaUtils).EXAM_GRADES)[value]
 
 # Define a validation method for email addresses
@@ -227,20 +211,25 @@
         return value
 
-class IResultEntry(Interface):
+class IProductOptionEntry(Interface):
     """A school grade entry.
     """
-    subject = schema.Choice(
-        title = _(u'Subject'),
-        source = SubjectSource(),
-        )
-    grade = schema.Choice(
-        title = _(u'Grade'),
-        source = GradeSource(),
-        )
-
-class IResultEntryField(IObject):
+    title = schema.TextLine(
+        title = _(u'Title'),
+        required = True,
+        )
+    fee = schema.Decimal(
+        title = _(u'Fee'),
+        required = False,
+        )
+    currency = schema.Choice(
+        title = _(u'Currency'),
+        vocabulary = currencies,
+        required = False,
+        )
+
+class IProductOptionEntryField(IObject):
     """A zope.schema-like field for usage in interfaces.
 
-    Marker interface to distuingish result entries from ordinary
+    Marker interface to distuingish product option entries from ordinary
     object fields. Needed for registration of widgets.
     """
@@ -252,6 +241,4 @@
     PORTAL_LANGUAGE = Attribute("Dict of global language setting")
     PREFERRED_LANGUAGES_DICT = Attribute("Dict of preferred languages")
-    EXAM_SUBJECTS_DICT = Attribute("Dict of examination subjects")
-    EXAM_GRADES = Attribute("Dict of examination grades")
     SYSTEM_MAX_LOAD = Attribute("Dict of maximum system loads.")
 
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/schoolgrades.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/schoolgrades.py	(revision 12300)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/schoolgrades.py	(revision 12306)
@@ -16,29 +16,33 @@
 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##
-"""Components representing and aggregating school grades.
+"""Components representing and aggregating product options.
 """
 import grok
+from decimal import Decimal
 from zope.formlib.interfaces import IInputWidget, IDisplayWidget
 from zope.publisher.interfaces.browser import IBrowserRequest
 from zope.schema.fieldproperty import FieldProperty
 from zope.schema import Object
-from waeup.ikoba.interfaces import IResultEntry, IResultEntryField
+from waeup.ikoba.interfaces import IProductOptionEntry, IProductOptionEntryField
 from waeup.ikoba.widgets.objectwidget import (
     IkobaObjectWidget, IkobaObjectDisplayWidget
     )
 
-class ResultEntry(grok.Model):
-    """A result entry contains a subject and a grade.
+class ProductOptionEntry(grok.Model):
+    """A product option entry contains a title, fee and currency.
     """
-    grok.implements(IResultEntry)
-    subject = FieldProperty(IResultEntry['subject'])
-    grade = FieldProperty(IResultEntry['grade'])
+    grok.implements(IProductOptionEntry)
+    title = FieldProperty(IProductOptionEntry['title'])
+    fee = FieldProperty(IProductOptionEntry['fee'])
+    currency = FieldProperty(IProductOptionEntry['currency'])
 
-    def __init__(self, subject=None, grade=None):
-        super(ResultEntry, self).__init__()
-        if subject is not None:
-            self.subject = subject
-        if grade is not None:
-            self.grade = grade
+    def __init__(self, title=None, fee=None, currency=None):
+        super(ProductOptionEntry, self).__init__()
+        if title is not None:
+            self.title = title
+        if fee is not None:
+            self.fee = fee
+        if currency is not None:
+            self.currency = currency
         return
 
@@ -46,11 +50,13 @@
         """A string representation that can be used in exports.
 
-        Returned is a unicode string of format ``(u'<SUBJ>',u'<GRADE>')``.
+        Returned is a unicode string of format ``(u'<TITLE>',u'<FEE>',u'<CURR>')``.
         """
-        return unicode((self.subject, self.grade))
+        string = u"(u'%s', u'%s', u'%s')" % (self.title, self.fee, self.currency)
+        string = string.replace("u'None'", "None")
+        return string
 
     @classmethod
     def from_string(cls, string):
-        """Create new ResultEntry instance based on `string`.
+        """Create new ProductOptionEntry instance based on `string`.
 
         The string is expected to be in format as delivered by
@@ -59,13 +65,17 @@
         This is a classmethod. This means, you normally will call::
 
-          ResultEntry.from_string(mystring)
+          ProductOptionEntry.from_string(mystring)
 
-        i.e. use the `ResultEntry` class, not an instance thereof.
+        i.e. use the `ProductOptionEntry` class, not an instance thereof.
         """
         string = string.replace("u''", "None")
-        subject, grade = eval(string)
-        return cls(subject, grade)
+        title, fee, currency = eval(string)
+        try:
+            fee = Decimal(fee)
+        except TypeError:
+            pass
+        return cls(title, fee, currency)
 
-class ResultEntryField(Object):
+class ProductOptionEntryField(Object):
     """A zope.schema-like field for usage in interfaces.
 
@@ -74,23 +84,23 @@
 
       class IMyInterface(Interface):
-          my_result_entry = ResultEntryField()
+          my_option_entry = ProductOptionEntryField()
 
     Default widgets are registered to render result entry fields.
     """
-    grok.implements(IResultEntryField)
+    grok.implements(IProductOptionEntryField)
 
     def __init__(self, **kw):
-        super(ResultEntryField, self).__init__(IResultEntry, **kw)
+        super(ProductOptionEntryField, self).__init__(IProductOptionEntry, **kw)
         return
 
-# register IkobaObjectWidgets as default widgets for IResultEntryFields
-@grok.adapter(IResultEntryField, IBrowserRequest)
+# register IkobaObjectWidgets as default widgets for IProductOptionEntryFields
+@grok.adapter(IProductOptionEntryField, IBrowserRequest)
 @grok.implementer(IInputWidget)
 def result_entry_input_widget(obj, req):
-    return IkobaObjectWidget(obj, req, ResultEntry)
+    return IkobaObjectWidget(obj, req, ProductOptionEntry)
 
-# register a display widget for IResultEntryFields
-@grok.adapter(IResultEntryField, IBrowserRequest)
+# register a display widget for IProductOptionEntryFields
+@grok.adapter(IProductOptionEntryField, IBrowserRequest)
 @grok.implementer(IDisplayWidget)
 def result_entry_display_widget(obj, req):
-    return IkobaObjectDisplayWidget(obj, req, ResultEntry)
+    return IkobaObjectDisplayWidget(obj, req, ProductOptionEntry)
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/tests/test_schoolgrades.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/tests/test_schoolgrades.py	(revision 12300)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/tests/test_schoolgrades.py	(revision 12306)
@@ -1,12 +1,13 @@
 # Tests for schoolgrades module.
 import unittest
+from decimal import Decimal
 from zope.component import getGlobalSiteManager
 from zope.interface.verify import verifyObject, verifyClass
-from zope.schema.interfaces import ConstraintNotSatisfied
-from waeup.ikoba.interfaces import IResultEntry, IResultEntryField, IIkobaUtils
-from waeup.ikoba.schoolgrades import ResultEntry, ResultEntryField
+from zope.schema.interfaces import ConstraintNotSatisfied, WrongType
+from waeup.ikoba.interfaces import IProductOptionEntry, IProductOptionEntryField, IIkobaUtils
+from waeup.ikoba.schoolgrades import ProductOptionEntry, ProductOptionEntryField
 from waeup.ikoba.utils.utils import IkobaUtils
 
-class ResultEntryTests(unittest.TestCase):
+class ProductOptionEntryTests(unittest.TestCase):
 
     def setUp(self):
@@ -14,6 +15,7 @@
         self.gsm = getGlobalSiteManager()
         self.gsm.registerUtility(self.utils, IIkobaUtils)
-        self.valid_subj = self.utils.EXAM_SUBJECTS_DICT.keys()[0]
-        self.valid_grade = self.utils.EXAM_GRADES[0][0]
+        self.valid_title = u'Option 1'
+        self.valid_fee = Decimal(55.5)
+        self.valid_currency = 'usd'
         return
 
@@ -24,63 +26,64 @@
     def test_ifaces(self):
         # make sure we implement the promised interfaces.
-        obj = ResultEntry()
-        verifyObject(IResultEntry, obj)
-        verifyClass(IResultEntry, ResultEntry)
+        obj = ProductOptionEntry()
+        verifyObject(IProductOptionEntry, obj)
+        verifyClass(IProductOptionEntry, ProductOptionEntry)
         return
 
     def test_init(self):
         # we can pass initial values
-        item1 = ResultEntry()
-        item2 = ResultEntry(self.valid_subj, self.valid_grade)
-        self.assertTrue(item1.subject is None)
-        self.assertTrue(item1.grade is None)
-        self.assertEqual(item2.subject, self.valid_subj)
-        self.assertEqual(item2.grade, self.valid_grade)
+        item1 = ProductOptionEntry()
+        item2 = ProductOptionEntry(
+            self.valid_title, self.valid_fee, self.valid_currency)
+        self.assertTrue(item1.title is None)
+        self.assertTrue(item1.fee is None)
+        self.assertTrue(item1.currency is None)
+        self.assertEqual(item2.title, self.valid_title)
+        self.assertEqual(item2.fee, self.valid_fee)
+        self.assertEqual(item2.currency, self.valid_currency)
         return
 
     def test_illegal_value(self):
-        # we do not accept values not stored in IkobaUtils
-        item = ResultEntry()
+        # we do not accept values which do not meet the interface
+        item = ProductOptionEntry()
         self.assertRaises(
-            ConstraintNotSatisfied, ResultEntry, 'invalid', 'invalid')
-        self.assertRaises(
-            ConstraintNotSatisfied, ResultEntry, 'invalid')
-        self.assertRaises(
-            ConstraintNotSatisfied, setattr, item, 'subject', 'blah')
-        self.assertRaises(
-            ConstraintNotSatisfied, setattr, item, 'grade', 'blah')
+            WrongType, ProductOptionEntry, 6, 500, 'invalid')
         return
 
     def test_to_string(self):
         # the string representation is handy for export
-        item1 = ResultEntry()
-        item2 = ResultEntry(self.valid_subj, self.valid_grade)
-        self.assertEqual(item1.to_string(), u"(None, None)")
-        self.assertEqual(item2.to_string(), u"('%s', '%s')" % (
-            self.valid_subj, self.valid_grade))
+        item1 = ProductOptionEntry()
+        item2 = ProductOptionEntry(
+            self.valid_title, self.valid_fee, self.valid_currency)
+        self.assertEqual(item1.to_string(), u"(None, None, None)")
+        self.assertEqual(item2.to_string(), u"(u'%s', u'%s', u'%s')" % (
+            self.valid_title, self.valid_fee, self.valid_currency))
         return
 
     def test_from_string(self):
         # we can create new result entries based on strings
-        myinput = u"(u'%s',u'%s')" % (
-            self.valid_subj, self.valid_grade)
-        item1 = ResultEntry.from_string(myinput)
-        item2 = ResultEntry.from_string(u"(u'',u'')")
-        item3 = ResultEntry.from_string(u"(None, None)")
-        self.assertEqual(item1.subject, self.valid_subj)
-        self.assertEqual(item1.grade, self.valid_grade)
-        self.assertTrue(item2.subject is None)
-        self.assertTrue(item2.grade is None)
-        self.assertTrue(item3.subject is None)
-        self.assertTrue(item3.grade is None)
+        myinput = u"(u'%s',u'%s',u'%s')" % (
+            self.valid_title, self.valid_fee, self.valid_currency)
+        item1 = ProductOptionEntry.from_string(myinput)
+        item2 = ProductOptionEntry.from_string(u"(u'', u'', u'')")
+        item3 = ProductOptionEntry.from_string(u"(None, None, None)")
+        self.assertEqual(item1.title, self.valid_title)
+        self.assertEqual(item1.fee, self.valid_fee)
+        self.assertEqual(item1.currency, self.valid_currency)
+        self.assertTrue(item2.title is None)
+        self.assertTrue(item2.fee is None)
+        self.assertEqual(item2.currency, None)
+        self.assertTrue(item3.title is None)
+        self.assertTrue(item3.fee is None)
+        self.assertEqual(item3.currency, None)
         return
 
-class ResultEntryFieldTests(unittest.TestCase):
+class ProductOptionEntryFieldTests(unittest.TestCase):
 
     def test_ifaces(self):
         # make sure we implement the promised interfaces.
-        obj = ResultEntryField()
-        verifyObject(IResultEntryField, obj)
-        verifyClass(IResultEntryField, ResultEntryField)
+        obj = ProductOptionEntryField()
+        verifyObject(IProductOptionEntryField, obj)
+        verifyClass(IProductOptionEntryField, ProductOptionEntryField)
         return
 
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/utils/converters.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/utils/converters.py	(revision 12300)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/utils/converters.py	(revision 12306)
@@ -30,8 +30,8 @@
 from zope.schema.interfaces import IList
 from waeup.ikoba.interfaces import (
-    IObjectConverter, IResultEntryField, IFieldConverter, SubjectSource,
-    GradeSource, DELETION_MARKER, IGNORE_MARKER)
+    IObjectConverter, IProductOptionEntryField, IFieldConverter,
+    DELETION_MARKER, IGNORE_MARKER)
 from waeup.ikoba.schema.interfaces import IPhoneNumber
-from waeup.ikoba.schoolgrades import ResultEntry
+from waeup.ikoba.schoolgrades import ProductOptionEntry
 
 class ExtendedCheckBoxWidget(CheckBoxWidget):
@@ -175,36 +175,35 @@
         return result
 
-class ResultEntryConverter(grok.Adapter):
-    grok.context(IResultEntryField)
+class ProductOptionEntryConverter(grok.Adapter):
+    grok.context(IProductOptionEntryField)
     grok.implements(IFieldConverter)
 
     def request_data(self, name, value, schema_field, prefix='',
                      mode='create'):
-        """Turn CSV values into ResultEntry-compatible form data.
-
-        Expects as `value` a _string_ like ``(u'mysubject',
-        u'mygrade')`` and turns it into some dict like::
+        """Turn CSV values into ProductOptionEntry-compatible form data.
+
+        Expects as `value` a _string_ like ``(u'mytitle',
+        u'myfee')`` and turns it into some dict like::
 
           {
-            'form.grade.subject': u'9234896395...',
-            'form.grade.grade': u'7e67e9e777..'
+            'form.option.title': u'9234896395...',
+            'form.option.fee': 7698769
+            'form.option.currency': u'7e67e9e777..'
             }
 
         where the values are tokens from appropriate sources.
 
-        Such dicts can be transformed into real ResultEntry objects by
+        Such dicts can be transformed into real ProductOptionEntry objects by
         input widgets used in converters.
         """
         try:
-            entry = ResultEntry.from_string(value)
-            subj, grade = entry.subject, entry.grade
+            entry = ProductOptionEntry.from_string(value)
+            title, fee, currency = entry.title, entry.fee, entry.currency
         except:
             return {prefix: value}
-        # web forms send tokens instead of real values
-        s_token = SubjectSource().factory.getToken(subj)
-        g_token = GradeSource().factory.getToken(grade)
         result = {
-            "%ssubject" % (prefix): s_token,
-            "%sgrade" % (prefix): g_token,
+            "%stitle" % (prefix): title,
+            "%sfee" % (prefix): fee,
+            "%scurrency" % (prefix): currency,
             }
         return result
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/utils/tests/test_converters.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/utils/tests/test_converters.py	(revision 12300)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/utils/tests/test_converters.py	(revision 12306)
@@ -23,4 +23,5 @@
 import tempfile
 import unittest
+from decimal import Decimal
 from zope import schema
 from zope.component import provideUtility
@@ -35,10 +36,10 @@
 from waeup.ikoba.testing import FunctionalLayer, FunctionalTestCase
 from waeup.ikoba.interfaces import (
-    SimpleIkobaVocabulary, SubjectSource, GradeSource, IFieldConverter,
+    SimpleIkobaVocabulary, IFieldConverter,
     DELETION_MARKER, IGNORE_MARKER)
-from waeup.ikoba.schoolgrades import ResultEntryField
+from waeup.ikoba.schoolgrades import ProductOptionEntryField
 from waeup.ikoba.utils.converters import (
     IObjectConverter, IFieldConverter, DefaultFieldConverter,
-    ListFieldConverter, PhoneNumberFieldConverter, ResultEntryConverter,
+    ListFieldConverter, PhoneNumberFieldConverter, ProductOptionEntryConverter,
     DefaultObjectConverter)
 from waeup.ikoba.utils.helpers import attrs_to_fields
@@ -91,7 +92,7 @@
         vocabulary = car_nums,
         )
-    grades = schema.List(
-        title = u'School Grades',
-        value_type = ResultEntryField(),
+    options = schema.List(
+        title = u'Options',
+        value_type = ProductOptionEntryField(),
         required = True,
         default = [],
@@ -136,5 +137,5 @@
         obj2 = ListFieldConverter(None)
         obj3 = PhoneNumberFieldConverter(None)
-        obj4 = ResultEntryConverter(None)
+        obj4 = ProductOptionEntryConverter(None)
         verify.verifyObject(IFieldConverter, obj1)
         verify.verifyObject(IFieldConverter, obj2)
@@ -144,5 +145,5 @@
         verify.verifyClass(IFieldConverter, ListFieldConverter)
         verify.verifyClass(IFieldConverter, PhoneNumberFieldConverter)
-        verify.verifyClass(IFieldConverter, ResultEntryConverter)
+        verify.verifyClass(IFieldConverter, ProductOptionEntryConverter)
         return
 
@@ -384,22 +385,20 @@
         return
 
-    def test_list_of_resultentries(self):
+    def test_list_of_optionentries(self):
         # We can handle lists of result entries
         converter = IObjectConverter(IContact)
         # get currently valid values
-        s_src, g_src = SubjectSource(), GradeSource()
-        s_val1, s_val2 = list(s_src.factory.getValues())[0:2]
-        g_val1, g_val2 = list(g_src.factory.getValues())[0:2]
-        req_string = u"[('%s', '%s'), ('%s', '%s')]" % (
-                s_val1, g_val1, s_val2, g_val2)
-        err, inv_err, data = converter.fromStringDict(
-            {"grades": req_string,
-             },
-            'contact')
-        result_grades = data['grades']
-        self.assertTrue(isinstance(result_grades, list))
-        self.assertEqual(len(result_grades), 2)
-        self.assertEqual(result_grades[0].subject, s_val1)
-        self.assertEqual(result_grades[0].grade, g_val1)
+        req_string = u"[(u'T1', u'55.6', u'usd'), (u'T2', u'66.6', u'eur')]"
+        err, inv_err, data = converter.fromStringDict(
+            {"options": req_string,}, 'contact')
+        result_options = data['options']
+        self.assertTrue(isinstance(result_options, list))
+        self.assertEqual(len(result_options), 2)
+        self.assertEqual(result_options[0].title, 'T1')
+        self.assertEqual(result_options[0].fee, Decimal('55.6'))
+        self.assertEqual(result_options[0].currency, 'usd')
+        self.assertEqual(result_options[1].title, 'T2')
+        self.assertEqual(result_options[1].fee, Decimal('66.6'))
+        self.assertEqual(result_options[1].currency, 'eur')
         return
 
@@ -420,8 +419,8 @@
         converter = IObjectConverter(IContact)
         err, inv_err, data = converter.fromStringDict(
-            {"grades": DELETION_MARKER,
+            {"options": DELETION_MARKER,
              "friends": DELETION_MARKER},
             'contact', mode='update')
         # grades are about to be set to default, friends to None
-        self.assertEqual(data, {'grades': [], 'friends': None})
-        return
+        self.assertEqual(data, {'options': [], 'friends': None})
+        return
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/utils/utils.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/utils/utils.py	(revision 12300)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/utils/utils.py	(revision 12306)
@@ -107,17 +107,4 @@
         'no': 'no contract',
         }
-
-    EXAM_SUBJECTS_DICT = {
-        'math': 'Mathematics',
-        'computer_science': 'Computer Science',
-        }
-
-    #: Exam grades. The tuple is sorted as it should be displayed in
-    #: select boxes.
-    EXAM_GRADES = (
-        ('A', 'Best'),
-        ('B', 'Better'),
-        ('C', 'Good'),
-        )
 
     PAYMENT_CATEGORIES = {
