Index: main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/__init__.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/__init__.py	(revision 5068)
+++ main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/__init__.py	(revision 5068)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.py	(revision 5068)
+++ main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.py	(revision 5068)
@@ -0,0 +1,66 @@
+"""Components to handle access codes.
+"""
+import grok
+from random import SystemRandom as random
+from waeup.sirp.accesscodes.interfaces import IAccessCode, IAccessCodeBatch
+
+class AccessCode(grok.Model):
+    grok.implements(IAccessCode)
+
+    def __init__(self, batch_serial, batch_prefix, batch_num, random_num,
+                 cost, invalidation_date=None, student_id=None):
+        self.batch_serial = batch_serial
+        self.batch_prefix = batch_prefix.upper()
+        self.batch_num = str(batch_num)
+        self.random_num = random_num
+        self.cost = cost
+        self.invalidation_date = invalidation_date
+        self.student_id = student_id
+
+    @property
+    def representation(self):
+        return '%s-%s-%s' % (
+            self.batch_prefix, self.batch_num, self.random_num)
+
+
+class AccessCodeBatch(object):
+    """A batch of access codes.
+    """
+    grok.implements(IAccessCodeBatch)
+
+    def __init__(self, creation_date, creator, batch_num, batch_prefix, cost,
+                 entry_num):
+        self.creation_date = creation_date
+        self.creator = creator
+        self.batch_num = batch_num
+        self.batch_prefix = batch_prefix
+        self.cost = cost
+        self.entry_num = entry_num
+        self._used = []
+
+    def __iter__(self):
+        if len(self._used) >= self.entry_num:
+            for num in range(self.entry_num):
+                random_num = self._used[num]
+                ac = AccessCode(len(self._used), self.batch_prefix,
+                                self.batch_num, random_num, self.cost)
+                yield ac
+        else:
+            while len(self._used) < self.entry_num:
+                random_num = self.getNewRandomNum()
+                ac = AccessCode(len(self._used), self.batch_prefix,
+                                self.batch_num, random_num, self.cost)
+                self._used.append(random_num)
+                yield ac
+        
+    def getNewRandomNum(self):
+        """Create a random number of 10 digits.
+
+        The number is returned as string.
+        """
+        result = ''
+        while result == '' or result in self._used:
+            result = ''
+            for x in range(10):
+                result += str(random().randint(0, 9))
+        return result
Index: main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.txt
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.txt	(revision 5068)
+++ main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.txt	(revision 5068)
@@ -0,0 +1,56 @@
+Accesscodes
+***********
+
+:doctest:
+
+We can create accesscodes:
+
+    >>> from waeup.sirp.accesscodes.accesscodes import AccessCode
+    >>> ac = AccessCode(0, 'APP', 12, '0123456789', 12.12)
+    >>> ac.representation
+    'APP-12-0123456789'
+
+We can create batches. Here we create a batch of three entries, with a
+cost of 12.12 per code, the batch prefix 'APP', batch number 12,
+creator ID 'Fred' and some arbitrary creation date:
+
+    >>> import datetime
+    >>> from waeup.sirp.accesscodes.accesscodes import AccessCodeBatch
+    >>> batch = AccessCodeBatch(
+    ...   datetime.datetime(2009, 12, 23), 'Fred', 10, 'APP', 12.12, 3)
+
+The main point about a batch is that it can act as an iterator over
+the generated access codes. The codes are generated on-the-fly when
+iterating over the batch for the first time:
+
+    >>> ac_codes = [x.representation for x in batch]
+    >>> ac_codes
+    ['APP-10-...', 'APP-10-...', 'APP-10-...']
+
+When we ask for the access codes again, we will get equal
+entries. 'Equal' means: we will get same representations, but not
+neccessarily the same objects:
+
+    >>> ac_codes2 = [x.representation for x in batch]
+    >>> ac_codes2 == ac_codes
+    True
+
+    >>> ac_codes[0] == ac_codes2[0]
+    True
+
+    >>> ac_codes[0] is ac_codes2[0]
+    False
+
+`Accesscode` and `AccessCodeBatch` classes implement the respective
+interfaces:
+
+    >>> from waeup.sirp.accesscodes.accesscodes import (
+    ...   AccessCode, AccessCodeBatch)
+    >>> from waeup.sirp.accesscodes.interfaces import(
+    ...   IAccessCode, IAccessCodeBatch)
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(IAccessCode, AccessCode)
+    True
+
+    >>> verifyClass(IAccessCodeBatch, AccessCodeBatch)
+    True
Index: main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/interfaces.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/interfaces.py	(revision 5068)
+++ main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/interfaces.py	(revision 5068)
@@ -0,0 +1,54 @@
+"""Interfaces of access code related components.
+"""
+
+from zope import schema
+from zope.interface import Interface
+
+class IAccessCode(Interface):
+    """An access code.
+    """
+    batch_serial = schema.Int(
+        title = u'Serial number inside batch',
+        )
+    batch_prefix = schema.TextLine(
+        title = u'Prefix inside batch',
+        )
+    batch_num = schema.Int(
+        title = u'Batch number',
+        )
+    random_num = schema.TextLine(
+        title = u'Random part of access code.',
+        )
+    cost = schema.Float(
+        title = u'Cost of access code',
+        )
+    invalidation_date = schema.Datetime(
+        title = u'Datetime of invalidation',
+        required = False,
+        default = None,
+        )
+    student_id = schema.TextLine(
+        title = u'Student ID or registration number',
+        required = False,
+        default = None,
+        )
+
+class IAccessCodeBatch(Interface):
+    """A factory for batches of access codes.
+    """
+    creation_date = schema.Date(
+        title = u'Creation date',
+        )
+    creator = schema.TextLine(
+        title = u'Batch creator',
+        )
+    batch_prefix = schema.TextLine(
+        title = u'Prefix inside batch',
+        )
+    batch_num = schema.Int(
+        title = u'Batch number',
+        )
+
+    def __iter__():
+        """Iterate over all access codes in this batch.
+        """
