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

Last change on this file since 5130 was 5128, checked in by uli, 15 years ago

Update tests.

File size: 11.4 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')
106    >>> ac.representation
107    '--<10-DIGITS>'
108
109   Also the ``cost`` will not be set:
110
111    >>> ac.cost is None
112    True
113
114
115AccessCodeBatch
116===============
117
118.. class:: AccessCodeBatch(creation_date, creator, batch_prefix, cost, entry_num, num)
119
120   Create a batch of access-codes.
121
122   :param creation_date: python datetime
123   :param creator: creators user id
124   :type creator: string
125   :param batch_prefix: prefix of this batch
126   :param cost: cost per access code
127   :type cost: float
128   :param entry_num: number of access codes to create
129   :param num: number of this batch
130
131   A persistent :class:`grok.Model`. It implements
132   :class:`waeup.sirp.accesscodes.interfaces.IAccessCodeBatch`.
133
134   When creating a batch, all entries (access-codes) are generated as
135   well.
136
137   .. attribute:: creation_date
138
139      The datetime when the batch was created.
140
141   .. attribute:: creator
142
143      String with user id of the user that generated the batch.
144
145   .. attribute:: cost
146
147      Float representing the costs for a single access-code. All
148      entries inside the batch share the same cost.
149
150   .. attribute:: entry_num
151
152      Number of entries (access-codes) inside the batch.
153
154   .. attribute:: invalidated_num
155
156      Number of entries that were already invalidated.
157
158   .. attribute:: prefix
159
160      Prefix of the batch. This tells about the purpose of this batch.
161
162   .. attribute:: num
163
164      Number of this batch. For a certain prefix there can exist
165      several batches, which are numbered in increasing order. The
166      number is normally computed by the
167      :class:`AccessCodeBatchContainer` in which batches are
168      stored.
169
170      .. seealso:: :class:`AccessCodeBatchContainer`
171
172   .. method:: entries()
173
174      Get all accesscodes stored in the batch.
175
176      Returns a generator over all stored entries.
177
178   .. method:: getAccessCode(acesscode_id)
179
180      Get the :class:`AccessCode` object for the given
181      ``accesscode_id``.
182
183      Certain single access codes can be accessed inside a batch by
184      their representation (i.e. something like ``'APP-12-0123456789'``.
185
186      When a code cannot be found :exc:`KeyError` is raised.
187
188   .. method:: addAccessCode(serial_num, random_num)
189
190      Add an access code to the batch.
191
192      ``serial_num`` denotes the serial number of the new access-code
193      inside the batch. ``random_num`` is a string of 10 digits unique
194      in this batch.
195
196   .. method:: invalidate(ac_id[, student_id=None])
197
198      Invalidate the access-code with ID ``ac_id``.
199
200      Sets also the ``student_id`` attribute of the respective
201      :class:`AccessCode` entry.
202
203   .. method:: createCSVLogFile()
204
205      Create a CSV file with data in batch.
206
207      Data will not contain invalidation date nor student ids.  File
208      will be created in ``accesscodes`` subdir of data center storage
209      path.
210
211      Returns name of created file.
212
213   .. method:: archive()
214
215      Create a CSV file for archive. Archive files contain also
216      ``student_id`` and ``invalidation_date``.
217
218      Returns name of created file.
219
220
221Examples
222--------
223
224:class:`AccessCodeBatch` implements :class:`IAccessCodeBatch`:
225
226    >>> from waeup.sirp.accesscodes.interfaces import IAccessCodeBatch
227    >>> from waeup.sirp.accesscodes.accesscodes import AccessCodeBatch
228    >>> from zope.interface.verify import verifyClass
229    >>> verifyClass(IAccessCodeBatch, AccessCodeBatch)
230    True
231
232Creating a batch of three access-codes, with a cost of ``12.12`` per
233code, the batch prefix ``APP``, batch number ``10``, creator ID
234``Fred`` and some arbitrary creation date:
235
236    >>> import datetime
237    >>> from waeup.sirp.accesscodes.accesscodes import AccessCodeBatch
238    >>> batch = AccessCodeBatch(
239    ...   datetime.datetime(2009, 12, 23), 'Fred','APP', 12.12, 3, num=10)
240
241Getting all access-codes from a batch:
242
243    >>> ac_codes = batch.entries()
244    >>> ac_codes
245    <generator object at 0x...>
246
247    >>> [x.representation for x in ac_codes]
248    ['APP-10-<10-DIGITS>', 'APP-10-<10-DIGITS>', 'APP-10-<10-DIGITS>']
249
250    >>> [x for x in batch.entries()]
251    [<waeup.sirp...AccessCode object at 0x...>, ...]
252
253Getting a single entry from the batch:
254
255    >>> ac_id = list(batch.entries())[0].representation
256    >>> ac = batch.getAccessCode(ac_id)
257    >>> ac
258    <waeup.sirp...AccessCode object at 0x...>
259
260    >>> ac is list(batch.entries())[0]
261    True
262
263Trying to get a single not-existent entry from a batch:
264
265    >>> batch.getAccessCode('blah')
266    Traceback (most recent call last):
267    ...
268    KeyError: 'blah'
269
270Invalidating an entry:
271
272    >>> batch.invalidated_num
273    0
274
275    >>> str(ac.invalidation_date), str(ac.student_id)
276    ('None', 'None')
277
278    >>> batch.invalidate(ac_id)
279    >>> batch.invalidated_num
280    1
281
282    >>> ac.invalidation_date , str(ac.student_id)
283    (datetime.datetime(...), 'None')
284
285    >>> batch.invalidate(ac_id, 'some_user_id')
286    >>> ac.student_id
287    'some_user_id'
288
289Access codes get their ``cost`` from the batch they belong to. Note,
290that it is advisable to print costs always using a format, as Python
291floats are often represented by irritating values:
292
293    >>> ac.cost
294    12.119999999999999
295
296    >>> print "%0.2f" % ac.cost
297    12.12
298
299
300AccessCodeBatchContainer
301========================
302
303.. class:: AccessCodeBatchContainer()
304
305   A container for access code batches.
306
307   .. method:: addBatch(batch)
308
309      Add a batch in this container. You should make sure, that
310      entries in the given batch are set correctly.
311
312   .. method:: createBatch(creation_date, creator, batch_prefix, cost, entry_num)
313
314      Create a batch inside this container. Returns the batch created.
315
316      :param creation_date: python datetime
317      :param creator: creators user id
318      :type creator: string
319      :param batch_prefix: prefix of this batch
320      :param cost: cost per access code
321      :type cost: float
322      :param entry_num: number of access codes to create
323
324   .. method:: getNum(prefix)
325
326      Get next unused num for a new batch and a given prefix.
327
328      Batches for a given prefix are numerated. Whenever a new batch
329      is created and other batches inside the container already have
330      the same prefix, the new one will get the lowest unused number.
331
332
333Access code plugin
334==================
335
336.. class:: AccessCodePlugin
337
338  .. attribute:: grok.implements(IWAeUPSIRPPluggable)
339  .. attribute:: grok.name('accesscodes')
340
341  .. method:: setup(site, name, logger)
342
343     Create an accesscodebatch container in ``site``. Any events are
344     logged to ``logger``.
345
346  .. method:: update(site, name, logger)
347
348     Check if ``site`` contains an accesscodebatch container and add
349     it if missing. Any events are logged to ``logger``.
350
351  The AccessCodePlugin is available as a global named utility for the
352  IWAeUPSIRPPluggable interface named ``accesscodes``.
353
354  It is looked up by a university instance when created.
355
356    >>> from zope.component import getUtility
357    >>> from waeup.sirp.interfaces import IWAeUPSIRPPluggable
358    >>> plugin = getUtility(IWAeUPSIRPPluggable, name='accesscodes')
359    >>> plugin
360    <waeup.sirp.accesscodes.accesscodes.AccessCodePlugin object at 0x...>
361
362  It provides a `setup()` and an `update()` method. Both have to be
363  fed with a site object (the `IUniversity` instance to modify), a
364  logger and a name.
365
366  We create a faked site and a logger:
367
368    >>> import logging
369    >>> faked_site = dict()
370    >>> logger = logging.getLogger('ac_test')
371    >>> logger.setLevel(logging.DEBUG)
372    >>> ch = logging.FileHandler('ac_tests.log', 'w')
373    >>> ch.setLevel(logging.DEBUG)
374    >>> logger.addHandler(ch)
375
376  Now we can install our stuff in the faked site:
377
378    >>> plugin.setup(faked_site, 'blah', logger)
379
380  The faked site now has an access code container:
381
382    >>> faked_site.keys()
383    ['accesscodes']
384
385  The action is described in the log:
386
387    >>> print open('ac_tests.log', 'r').read()
388    Installed container for access code batches.
389
390  We can also update an existing site, by calling `update()`:
391
392    >>> plugin.update(faked_site, 'blah', logger)
393
394  There was nothing to do for the updater:
395
396    >>> print open('ac_tests.log', 'r').read()
397    Installed container for access code batches.
398    AccessCodePlugin: Updating site at {'accesscodes'...: Nothing to do.
399
400  But if we remove the created batch container and call the updater, it
401  will create a new one:
402
403    >>> del faked_site['accesscodes']
404    >>> plugin.update(faked_site, 'blah', logger)
405    >>> print open('ac_tests.log', 'r').read()
406    Installed container for access code batches.
407    AccessCodePlugin: Updating site at {'accesscodes'...: Nothing to do.
408    Updating site at {}. Installing access codes.
409    Installed container for access code batches.
410
411  Clean up:
412
413    >>> import os
414    >>> os.unlink('ac_tests.log')
Note: See TracBrowser for help on using the repository browser.