source: main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.txt @ 5121

Last change on this file since 5121 was 5118, checked in by uli, 15 years ago

Implement completely different design for access-codes and their batches. We drop using zope.catalogs here and care
for the limited set of operations we need on access codes and batches ourselves. This speeds up performance for batch
creation by factor 2 and uses only half the memory in ZODB than what was needed with old PIN implemenentation.

File size: 9.8 KB
Line 
1:mod:`waeup.sirp.accesscodes.accesscodes` -- access codes (aka PINs)
2********************************************************************
3
4.. module:: waeup.sirp.accesscodes.accesscodes
5
6Components that represent access codes and related.
7
8.. :doctest:
9
10About access-codes
11==================
12
13Access codes are ids used to grant first-time access to the system for
14students.
15
16They are normally not generated by third-party components but always
17part of batches.
18
19An access-code consists of three parts::
20
21    APP-12-0123456789
22    ^^^ ^^ ^^^^^^^^^^
23     A  B  C
24
25where ``A`` tells about the purpose of the code, ``B`` gives the
26number of batch the code belongs to (1 to 3 digits), and ``C`` is a
27unique random number of 10 digits.
28
29For the generation of the random number :mod:`waeup.sirp` requires a
30'urandom' entropy provider which is available with most standard
31Unix/Linux systems. This makes the generated numbers relatively
32secure, especially when compared with recent PHP-based applications.
33
34
35AccessCode
36==========
37
38.. class:: AccessCode(batch_serial, random_num[,invalidation_date=None[, student_id=None]])
39
40   You normally shouldn't create standalone access-codes. Use
41   instances of :class:`AccessCodeBatch` instead as they generate them
42   (in masses) and care for them.
43
44   Note, that :class:`AccessCode` instances are not persistent on
45   themselves. They have to be stored inside a persistent object (like
46   :class:`AccessCodeBatch`) to be kept.
47
48   The class implements
49   :mod:`waeup.sirp.accesscodes.interfaces.IAccessCode`:
50
51    >>> from waeup.sirp.accesscodes.interfaces import IAccessCode
52    >>> from waeup.sirp.accesscodes.accesscodes import AccessCode
53    >>> from zope.interface.verify import verifyClass
54    >>> verifyClass(IAccessCode, AccessCode)
55    True
56
57   .. attribute:: representation
58
59      The 'full' id of an access-code as described above. Something
60      like ``'APP-12-0123456789'``.
61
62      Read-only attribute.
63
64   .. attribute:: batch_serial
65
66      Serial number of this access-code inside the batch.
67
68      .. note:: XXX: Do we really need this?
69
70         Subject to be dropped.
71
72   .. attribute:: student_id
73
74      A string or ``None``. Set when an access-code is
75      invalidated. ``None`` by default.
76
77   .. attribute:: batch_prefix
78
79      The prefix of the batch the access-code belongs to.
80
81      Read-only attribute.
82
83   .. attribute:: batch_num
84
85      The number of the batch the access-code belongs to.
86
87      Read-only attribute.
88
89   .. attribute:: cost
90
91      What the access-code costs. A float.
92
93      Read-only attribute.
94
95   .. attribute:: invalidation_date
96
97      Python datetime when the access code was invalidated, or
98      ``None``. ``None`` by default.
99
100      Read-only attribute. Only batches are supposed to set this value.
101
102   Access codes that are not part of a batch, will give strange
103   representations:
104
105    >>> ac = AccessCode(None, '9999999999', 12.12)
106    >>> ac.representation
107    '--<10-DIGITS>'
108
109
110AccessCodeBatch
111===============
112
113.. class:: AccessCodeBatch(creation_date, creator, batch_prefix, cost, entry_num, num)
114
115   Create a batch of access-codes.
116
117   :param creation_date: python datetime
118   :param creator: creators user id
119   :type creator: string
120   :param batch_prefix: prefix of this batch
121   :param cost: cost per access code
122   :type cost: float
123   :param entry_num: number of access codes to create
124   :param num: number of this batch
125
126   A persistent :class:`grok.Model`. It implements
127   :class:`waeup.sirp.accesscodes.interfaces.IAccessCodeBatch`.
128
129   When creating a batch, all entries (access-codes) are generated as
130   well.
131
132   .. attribute:: creation_date
133
134      The datetime when the batch was created.
135
136   .. attribute:: creator
137
138      String with user id of the user that generated the batch.
139
140   .. attribute:: cost
141
142      Float representing the costs for a single access-code. All
143      entries inside the batch share the same cost.
144
145   .. attribute:: entry_num
146
147      Number of entries (access-codes) inside the batch.
148
149   .. attribute:: invalidated_num
150
151      Number of entries that were already invalidated.
152
153   .. attribute:: prefix
154
155      Prefix of the batch. This tells about the purpose of this batch.
156
157   .. attribute:: num
158
159      Number of this batch. For a certain prefix there can exist
160      several batches, which are numbered in increasing order. The
161      number is normally computed by the
162      :class:`AccessCodeBatchContainer` in which batches are
163      stored.
164
165      .. seealso:: :class:`AccessCodeBatchContainer`
166
167   .. method:: entries()
168
169      Get all accesscodes stored in the batch.
170
171      Returns a generator over all stored entries.
172
173   .. method:: getAccessCode(acesscode_id)
174
175      Get the :class:`AccessCode` object for the given
176      ``accesscode_id``.
177
178      Certain single access codes can be accessed inside a batch by
179      their representation (i.e. something like ``'APP-12-0123456789'``.
180
181      When a code cannot be found :exc:`KeyError` is raised.
182
183   .. method:: invalidate(ac_id[, student_id=None])
184
185      Invalidate the access-code with ID ``ac_id``.
186
187      Sets also the ``student_id`` attribute of the respective
188      :class:`AccessCode` entry.
189
190   .. method:: createCSVLogFile()
191
192      Create a CSV file with data in batch.
193
194      Data will not contain invalidation date nor student ids.  File
195      will be created in ``accesscodes`` subdir of data center storage
196      path.
197
198      Returns name of created file.
199
200   .. method:: archive()
201
202      Create a CSV file for archive. Archive files contain also
203      ``student_id`` and ``invalidation_date``.
204
205      Returns name of created file.
206
207
208Examples
209--------
210
211:class:`AccessCodeBatch` implements :class:`IAccessCodeBatch`:
212
213    >>> from waeup.sirp.accesscodes.interfaces import IAccessCodeBatch
214    >>> from waeup.sirp.accesscodes.accesscodes import AccessCodeBatch
215    >>> from zope.interface.verify import verifyClass
216    >>> verifyClass(IAccessCodeBatch, AccessCodeBatch)
217    True
218
219Creating a batch of three access-codes, with a cost of ``12.12`` per
220code, the batch prefix ``APP``, batch number ``10``, creator ID
221``Fred`` and some arbitrary creation date:
222
223    >>> import datetime
224    >>> from waeup.sirp.accesscodes.accesscodes import AccessCodeBatch
225    >>> batch = AccessCodeBatch(
226    ...   datetime.datetime(2009, 12, 23), 'Fred','APP', 12.12, 3, num=10)
227
228Getting all access-codes from a batch:
229
230    >>> ac_codes = batch.entries()
231    >>> ac_codes
232    <generator object at 0x...>
233
234    >>> [x.representation for x in ac_codes]
235    ['APP-10-<10-DIGITS>', 'APP-10-<10-DIGITS>', 'APP-10-<10-DIGITS>']
236
237    >>> [x for x in batch.entries()]
238    [<waeup.sirp...AccessCode object at 0x...>, ...]
239
240Getting a single entry from the batch:
241
242    >>> ac_id = list(batch.entries())[0].representation
243    >>> ac = batch.getAccessCode(ac_id)
244    >>> ac
245    <waeup.sirp...AccessCode object at 0x...>
246
247    >>> ac is list(batch.entries())[0]
248    True
249
250Trying to get a single not-existent entry from a batch:
251
252    >>> batch.getAccessCode('blah')
253    Traceback (most recent call last):
254    ...
255    KeyError: 'blah'
256
257Invalidating an entry:
258
259    >>> batch.invalidated_num
260    0
261
262    >>> str(ac.invalidation_date), str(ac.student_id)
263    ('None', 'None')
264
265    >>> batch.invalidate(ac_id)
266    >>> batch.invalidated_num
267    1
268
269    >>> ac.invalidation_date , str(ac.student_id)
270    (datetime.datetime(...), 'None')
271
272    >>> batch.invalidate(ac_id, 'some_user_id')
273    >>> ac.student_id
274    'some_user_id'
275
276
277Access code plugin
278==================
279
280.. class:: AccessCodePlugin
281
282  .. attribute:: grok.implements(IWAeUPSIRPPluggable)
283  .. attribute:: grok.name('accesscodes')
284
285  .. method:: setup(site, name, logger)
286
287     Create an accesscodebatch container in ``site``. Any events are
288     logged to ``logger``.
289
290  .. method:: update(site, name, logger)
291
292     Check if ``site`` contains an accesscodebatch container and add
293     it if missing. Any events are logged to ``logger``.
294
295  The AccessCodePlugin is available as a global named utility for the
296  IWAeUPSIRPPluggable interface named ``accesscodes``.
297
298  It is looked up by a university instance when created.
299
300    >>> from zope.component import getUtility
301    >>> from waeup.sirp.interfaces import IWAeUPSIRPPluggable
302    >>> plugin = getUtility(IWAeUPSIRPPluggable, name='accesscodes')
303    >>> plugin
304    <waeup.sirp.accesscodes.accesscodes.AccessCodePlugin object at 0x...>
305
306  It provides a `setup()` and an `update()` method. Both have to be
307  fed with a site object (the `IUniversity` instance to modify), a
308  logger and a name.
309
310  We create a faked site and a logger:
311
312    >>> import logging
313    >>> faked_site = dict()
314    >>> logger = logging.getLogger('ac_test')
315    >>> logger.setLevel(logging.DEBUG)
316    >>> ch = logging.FileHandler('ac_tests.log', 'w')
317    >>> ch.setLevel(logging.DEBUG)
318    >>> logger.addHandler(ch)
319
320  Now we can install our stuff in the faked site:
321
322    >>> plugin.setup(faked_site, 'blah', logger)
323
324  The faked site now has an access code container:
325
326    >>> faked_site.keys()
327    ['accesscodes']
328
329  The action is described in the log:
330
331    >>> print open('ac_tests.log', 'r').read()
332    Installed container for access code batches.
333
334  We can also update an existing site, by calling `update()`:
335
336    >>> plugin.update(faked_site, 'blah', logger)
337
338  There was nothing to do for the updater:
339
340    >>> print open('ac_tests.log', 'r').read()
341    Installed container for access code batches.
342    AccessCodePlugin: Updating site at {'accesscodes'...: Nothing to do.
343
344  But if we remove the created batch container and call the updater, it
345  will create a new one:
346
347    >>> del faked_site['accesscodes']
348    >>> plugin.update(faked_site, 'blah', logger)
349    >>> print open('ac_tests.log', 'r').read()
350    Installed container for access code batches.
351    AccessCodePlugin: Updating site at {'accesscodes'...: Nothing to do.
352    Updating site at {}. Installing access codes.
353    Installed container for access code batches.
354
355  Clean up:
356
357    >>> import os
358    >>> os.unlink('ac_tests.log')
Note: See TracBrowser for help on using the repository browser.