Index: main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.txt
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.txt	(revision 5149)
+++ main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.txt	(revision 5150)
@@ -46,4 +46,13 @@
    themselves. They have to be stored inside a persistent object (like
    :class:`AccessCodeBatch`) to be kept.
+
+   Access-codes can have three states: unused, used, and
+   disabled. While still unused but enabled access-codes are reflected
+   by an empty ``invalidation_date`` (set to ``None``), an already
+   used (invalidated) code provides an invalidation date.
+
+   In case of misuse or similar cases access-codes can also be
+   completely disabled by setting the ``disabled`` attribute to
+   ``True``.
 
    The class implements
@@ -98,4 +107,14 @@
       Python datetime when the access code was invalidated, or
       ``None``. ``None`` by default.
+
+      If an access-code is disabled, this attribute contains the
+      datetime of disabling.
+
+      Read-only attribute. Only batches are supposed to set this value.
+
+   .. attribute:: disabled
+
+      Boolean, ``False`` by default. When set to ``True``, this
+      access-code should not be used any more.
 
       Read-only attribute. Only batches are supposed to set this value.
@@ -187,4 +206,15 @@
       When a code cannot be found :exc:`KeyError` is raised.
 
+   .. method:: getAccessCodeForStudentId(student_id)
+
+      Get the :class:`AccessCode` object for the given
+      ``student_id``.
+
+      Certain single access codes can be accessed inside a batch by
+      their student_id. The student_id must be some string; ``None``
+      is not a valid value.
+
+      When a code cannot be found :exc:`KeyError` is raised.
+
    .. method:: addAccessCode(serial_num, random_num)
 
@@ -202,4 +232,29 @@
       :class:`AccessCode` entry.
 
+   .. method:: disable(ac_id, user_id)
+
+      Disable the access-code with ID ``ac_id``.
+
+     ``user_id`` is the user ID of the user triggering the
+      process. Already disabled ACs are left untouched.
+
+      Sets also the ``student_id`` and ``invalidation_date``
+      attributes of the respective :class:`AccessCode` entry. While
+      ``student_id`` is set to the given ``user_id``,
+      ``invalidation_date`` is set to current datetime.
+
+      Disabled access codes are supposed not to be used any more at
+      all.
+
+   .. method:: enable(ac_id)
+
+      (Re-)enable the access-code with ID ``ac_id``.
+
+      This leaves the given AC in state ``unused``. Already enabled
+      ACs are left untouched.
+
+      Sets ``student_id`` and ``invalidation_date`` values of the
+      respective :class:`AccessCode` entry to ``None``.
+
    .. method:: createCSVLogFile()
 
@@ -218,4 +273,30 @@
 
       Returns name of created file.
+
+   .. method:: search(searchterm, searchtype)
+
+      Search the batch for entries that comply with ``searchterm`` and
+      ``searchtype``.
+
+      ``searchtype`` must be one of ``serial``, ``pin``, or
+      ``stud_id`` and specifies, what kind of data is contained in the
+      ``searchterm``.
+
+      - ``serial`` looks for the AC with the ``serial_batch`` set to
+        the given (integer) number in ``searchterm``. Here
+        ``searchterm`` must be an int number.
+
+      - ``pin`` looks for the AC with the ``representation`` set to
+        the given string in ``searchterm``. Here ``searchterm`` must
+        be a string containing the full AC-ID like
+        ``APP-1-0123456789``.
+
+      - ``stud_id`` looks for the AC with the ``student_id`` set to
+        the given string in ``searchterm``. Here ``searchterm`` must
+        be a string containing the full student ID.
+
+      Lookup is done via local BTrees and therefore pretty fast.
+
+      Returns a list of access-codes found or empty list.
 
 
@@ -288,4 +369,59 @@
     'some_user_id'
 
+Getting a single entry by student_id:
+
+    >>> batch.getAccessCodeForStudentId('some_user_id')
+    <waeup.sirp...AccessCode object at 0x...>
+
+Non-existent values will cause a :exc:`KeyError`:
+
+    >>> batch.getAccessCodeForStudentId('non-existing')
+    Traceback (most recent call last):
+    ...
+    KeyError: 'non-existing'
+
+Already enabled entries will be left untouched when trying to renable
+them:
+
+    >>> batch.enable(ac_id)
+    >>> ac.student_id
+    'some_user_id'
+
+Disabling an entry:
+
+    >>> batch.disabled_num
+    0
+
+    >>> ac.disabled
+    False
+
+    >>> batch.disable(ac_id, 'some userid')
+    >>> ac.disabled
+    True
+
+    >>> ac.student_id
+    'some userid'
+
+    >>> batch.disabled_num
+    1
+
+Already disabled entries will not be disabled again:
+
+    >>> batch.disable(ac_id, 'other userid')
+    >>> ac.student_id
+    'some userid'
+
+Reenabling an entry:
+
+    >>> batch.enable(ac_id)
+    >>> ac.disabled
+    False
+
+    >>> ac.student_id is None
+    True
+
+    >>> ac.invalidation_date is None
+    True
+
 Access codes get their ``cost`` from the batch they belong to. Note,
 that it is advisable to print costs always using a format, as Python
@@ -298,4 +434,39 @@
     12.12
 
+Searching for serials:
+
+    >>> result = batch.search(0, 'serial')
+    >>> result
+    [<waeup.sirp...AccessCode object at 0x...>]
+
+    >>> result[0].batch_serial
+    0
+
+Searching for AC-IDs:
+
+    >>> result = batch.search(ac.representation, 'pin')
+    >>> result[0].representation
+    'APP-...-...'
+
+Searching for student IDs:
+
+    >>> batch.invalidate(ac_id, 'some_new_user_id')
+    >>> result = batch.search('some_new_user_id', 'stud_id')
+    >>> result[0].student_id
+    'some_new_user_id'
+
+Searching for not existing entries will return empty lists:
+
+    >>> batch.search(12, 'serial')
+    []
+
+    >>> batch.search('not-a-valid-pin', 'pin')
+    []
+
+    >>> batch.search('not-a-student-id', 'stud_id')
+    []
+
+    >>> batch.search('blah', 'not-a-valid-searchtype')
+    []
 
 AccessCodeBatchContainer
@@ -344,4 +515,72 @@
       user ID of the current user.
 
+   .. method:: search(search_term, search_type)
+
+      Look in all contained batches for access codes that comply with
+      the given parameters.
+
+      ``search_type`` must be one of ``serial``, ``pin``, or
+      ``stud_id`` and specifies, what kind of data is contained in the
+      ``search_term``.
+
+      - ``serial`` looks for the AC with the ``serial_batch`` set to
+        the given (integer) number in ``search_term``. Here
+        ``search_term`` can be an integer or a string. If it is a
+        string it will be converted to an int.
+
+      - ``pin`` looks for the AC with the ``representation`` set to
+        the given string in ``search_term``. Here ``search_term`` must
+        be a string containing the full AC-ID like
+        ``APP-1-0123456789``.
+
+      - ``stud_id`` looks for the AC with the ``student_id`` set to
+        the given string in ``search_term``. Here ``search_term`` must
+        be a string containing the full student ID.
+
+Examples:
+---------
+
+Creating a batch container:
+
+    >>> from waeup.sirp.accesscodes.accesscodes import (
+    ...   AccessCodeBatchContainer)
+    >>> container = AccessCodeBatchContainer()
+
+Creating batches inside the container:
+
+    >>> from datetime import datetime
+    >>> batch1 = container.createBatch(
+    ...   datetime.now(), 'some userid', 'FOO', 10.12, 5)
+
+    >>> batch2 = container.createBatch(
+    ...   datetime.now(), 'other userid', 'BAR', 1.95, 5)
+
+Searching the container for batch serials:
+
+    >>> container.search(1, 'serial')
+    [<waeup.sirp...AccessCode object at 0x...>,
+     <waeup.sirp...AccessCode object at 0x...>]
+
+    >>> container.search('not-a-number', 'serial')
+    []
+
+    >>> result = container.search('1', 'serial')
+    >>> result
+    [<waeup.sirp...AccessCode object at 0x...>,
+     <waeup.sirp...AccessCode object at 0x...>]
+
+Searching for ACs:
+
+    >>> ac = result[0]
+    >>> container.search(ac.representation, 'pin')
+    [<waeup.sirp...AccessCode object at 0x...>]
+
+Searching for student IDs:
+
+    >>> ac.__parent__.invalidate(
+    ...   ac.representation, 'some_user')
+    >>> container.search('some_user', 'stud_id')
+    [<waeup.sirp...AccessCode object at 0x...>]
+
 
 Access code plugin
