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

Last change on this file since 5127 was 5122, checked in by uli, 15 years ago

Update tests:

  • Check costs
  • Add docs for addAccessCode()
File size: 10.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:: addAccessCode(serial_num, random_num)
184
185      Add an access code to the batch.
186
187      ``serial_num`` denotes the serial number of the new access-code
188      inside the batch. ``random_num`` is a string of 10 digits unique
189      in this batch.
190
191   .. method:: invalidate(ac_id[, student_id=None])
192
193      Invalidate the access-code with ID ``ac_id``.
194
195      Sets also the ``student_id`` attribute of the respective
196      :class:`AccessCode` entry.
197
198   .. method:: createCSVLogFile()
199
200      Create a CSV file with data in batch.
201
202      Data will not contain invalidation date nor student ids.  File
203      will be created in ``accesscodes`` subdir of data center storage
204      path.
205
206      Returns name of created file.
207
208   .. method:: archive()
209
210      Create a CSV file for archive. Archive files contain also
211      ``student_id`` and ``invalidation_date``.
212
213      Returns name of created file.
214
215
216Examples
217--------
218
219:class:`AccessCodeBatch` implements :class:`IAccessCodeBatch`:
220
221    >>> from waeup.sirp.accesscodes.interfaces import IAccessCodeBatch
222    >>> from waeup.sirp.accesscodes.accesscodes import AccessCodeBatch
223    >>> from zope.interface.verify import verifyClass
224    >>> verifyClass(IAccessCodeBatch, AccessCodeBatch)
225    True
226
227Creating a batch of three access-codes, with a cost of ``12.12`` per
228code, the batch prefix ``APP``, batch number ``10``, creator ID
229``Fred`` and some arbitrary creation date:
230
231    >>> import datetime
232    >>> from waeup.sirp.accesscodes.accesscodes import AccessCodeBatch
233    >>> batch = AccessCodeBatch(
234    ...   datetime.datetime(2009, 12, 23), 'Fred','APP', 12.12, 3, num=10)
235
236Getting all access-codes from a batch:
237
238    >>> ac_codes = batch.entries()
239    >>> ac_codes
240    <generator object at 0x...>
241
242    >>> [x.representation for x in ac_codes]
243    ['APP-10-<10-DIGITS>', 'APP-10-<10-DIGITS>', 'APP-10-<10-DIGITS>']
244
245    >>> [x for x in batch.entries()]
246    [<waeup.sirp...AccessCode object at 0x...>, ...]
247
248Getting a single entry from the batch:
249
250    >>> ac_id = list(batch.entries())[0].representation
251    >>> ac = batch.getAccessCode(ac_id)
252    >>> ac
253    <waeup.sirp...AccessCode object at 0x...>
254
255    >>> ac is list(batch.entries())[0]
256    True
257
258Trying to get a single not-existent entry from a batch:
259
260    >>> batch.getAccessCode('blah')
261    Traceback (most recent call last):
262    ...
263    KeyError: 'blah'
264
265Invalidating an entry:
266
267    >>> batch.invalidated_num
268    0
269
270    >>> str(ac.invalidation_date), str(ac.student_id)
271    ('None', 'None')
272
273    >>> batch.invalidate(ac_id)
274    >>> batch.invalidated_num
275    1
276
277    >>> ac.invalidation_date , str(ac.student_id)
278    (datetime.datetime(...), 'None')
279
280    >>> batch.invalidate(ac_id, 'some_user_id')
281    >>> ac.student_id
282    'some_user_id'
283
284Access codes get their ``cost`` from the batch they belong to. Note,
285that it is advisable to print costs always using a format, as Python
286floats are often represented by irritating values:
287
288    >>> ac.cost
289    12.119999999999999
290
291    >>> print "%0.2f" % ac.cost
292    12.12
293
294
295AccessCodeBatchContainer
296========================
297
298.. class:: AccessCodeBatchContainer()
299
300   A container for access code batches.
301
302   .. method:: addBatch(batch)
303
304      Add a batch in this container.
305
306   .. method:: getNum(prefix)
307
308      Get next unused num for a new batch and a given prefix.
309
310      Batches for a given prefix are numerated. Whenever a new batch
311      is created and other batches inside the container already have
312      the same prefix, the new one will get the lowest unused number.
313
314
315Access code plugin
316==================
317
318.. class:: AccessCodePlugin
319
320  .. attribute:: grok.implements(IWAeUPSIRPPluggable)
321  .. attribute:: grok.name('accesscodes')
322
323  .. method:: setup(site, name, logger)
324
325     Create an accesscodebatch container in ``site``. Any events are
326     logged to ``logger``.
327
328  .. method:: update(site, name, logger)
329
330     Check if ``site`` contains an accesscodebatch container and add
331     it if missing. Any events are logged to ``logger``.
332
333  The AccessCodePlugin is available as a global named utility for the
334  IWAeUPSIRPPluggable interface named ``accesscodes``.
335
336  It is looked up by a university instance when created.
337
338    >>> from zope.component import getUtility
339    >>> from waeup.sirp.interfaces import IWAeUPSIRPPluggable
340    >>> plugin = getUtility(IWAeUPSIRPPluggable, name='accesscodes')
341    >>> plugin
342    <waeup.sirp.accesscodes.accesscodes.AccessCodePlugin object at 0x...>
343
344  It provides a `setup()` and an `update()` method. Both have to be
345  fed with a site object (the `IUniversity` instance to modify), a
346  logger and a name.
347
348  We create a faked site and a logger:
349
350    >>> import logging
351    >>> faked_site = dict()
352    >>> logger = logging.getLogger('ac_test')
353    >>> logger.setLevel(logging.DEBUG)
354    >>> ch = logging.FileHandler('ac_tests.log', 'w')
355    >>> ch.setLevel(logging.DEBUG)
356    >>> logger.addHandler(ch)
357
358  Now we can install our stuff in the faked site:
359
360    >>> plugin.setup(faked_site, 'blah', logger)
361
362  The faked site now has an access code container:
363
364    >>> faked_site.keys()
365    ['accesscodes']
366
367  The action is described in the log:
368
369    >>> print open('ac_tests.log', 'r').read()
370    Installed container for access code batches.
371
372  We can also update an existing site, by calling `update()`:
373
374    >>> plugin.update(faked_site, 'blah', logger)
375
376  There was nothing to do for the updater:
377
378    >>> print open('ac_tests.log', 'r').read()
379    Installed container for access code batches.
380    AccessCodePlugin: Updating site at {'accesscodes'...: Nothing to do.
381
382  But if we remove the created batch container and call the updater, it
383  will create a new one:
384
385    >>> del faked_site['accesscodes']
386    >>> plugin.update(faked_site, 'blah', logger)
387    >>> print open('ac_tests.log', 'r').read()
388    Installed container for access code batches.
389    AccessCodePlugin: Updating site at {'accesscodes'...: Nothing to do.
390    Updating site at {}. Installing access codes.
391    Installed container for access code batches.
392
393  Clean up:
394
395    >>> import os
396    >>> os.unlink('ac_tests.log')
Note: See TracBrowser for help on using the repository browser.