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

Last change on this file since 5135 was 5132, checked in by uli, 15 years ago

Support reimport of AC batches.

File size: 11.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')
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   .. method:: getImportFiles()
333
334      Get a list of basenames of available import files, suitable for
335      feeding :meth:`reimport`.
336
337   .. method:: reimport(filename, creator=u'UNKNOWN')
338
339      Reimport a CSV log file of previously created AC batch.
340
341      ``filename`` is the name (basename) of the file residing in the
342      accesscode storage's ``import`` directory.``creator`` is the
343      user ID of the current user.
344
345
346Access code plugin
347==================
348
349.. class:: AccessCodePlugin
350
351  .. attribute:: grok.implements(IWAeUPSIRPPluggable)
352  .. attribute:: grok.name('accesscodes')
353
354  .. method:: setup(site, name, logger)
355
356     Create an accesscodebatch container in ``site``. Any events are
357     logged to ``logger``.
358
359  .. method:: update(site, name, logger)
360
361     Check if ``site`` contains an accesscodebatch container and add
362     it if missing. Any events are logged to ``logger``.
363
364  The AccessCodePlugin is available as a global named utility for the
365  IWAeUPSIRPPluggable interface named ``accesscodes``.
366
367  It is looked up by a university instance when created.
368
369    >>> from zope.component import getUtility
370    >>> from waeup.sirp.interfaces import IWAeUPSIRPPluggable
371    >>> plugin = getUtility(IWAeUPSIRPPluggable, name='accesscodes')
372    >>> plugin
373    <waeup.sirp.accesscodes.accesscodes.AccessCodePlugin object at 0x...>
374
375  It provides a `setup()` and an `update()` method. Both have to be
376  fed with a site object (the `IUniversity` instance to modify), a
377  logger and a name.
378
379  We create a faked site and a logger:
380
381    >>> import logging
382    >>> faked_site = dict()
383    >>> logger = logging.getLogger('ac_test')
384    >>> logger.setLevel(logging.DEBUG)
385    >>> ch = logging.FileHandler('ac_tests.log', 'w')
386    >>> ch.setLevel(logging.DEBUG)
387    >>> logger.addHandler(ch)
388
389  Now we can install our stuff in the faked site:
390
391    >>> plugin.setup(faked_site, 'blah', logger)
392
393  The faked site now has an access code container:
394
395    >>> faked_site.keys()
396    ['accesscodes']
397
398  The action is described in the log:
399
400    >>> print open('ac_tests.log', 'r').read()
401    Installed container for access code batches.
402
403  We can also update an existing site, by calling `update()`:
404
405    >>> plugin.update(faked_site, 'blah', logger)
406
407  There was nothing to do for the updater:
408
409    >>> print open('ac_tests.log', 'r').read()
410    Installed container for access code batches.
411    AccessCodePlugin: Updating site at {'accesscodes'...: Nothing to do.
412
413  But if we remove the created batch container and call the updater, it
414  will create a new one:
415
416    >>> del faked_site['accesscodes']
417    >>> plugin.update(faked_site, 'blah', logger)
418    >>> print open('ac_tests.log', 'r').read()
419    Installed container for access code batches.
420    AccessCodePlugin: Updating site at {'accesscodes'...: Nothing to do.
421    Updating site at {}. Installing access codes.
422    Installed container for access code batches.
423
424  Clean up:
425
426    >>> import os
427    >>> os.unlink('ac_tests.log')
Note: See TracBrowser for help on using the repository browser.