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

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

Update tests.

File size: 11.4 KB
RevLine 
[5080]1:mod:`waeup.sirp.accesscodes.accesscodes` -- access codes (aka PINs)
2********************************************************************
[5068]3
[5080]4.. module:: waeup.sirp.accesscodes.accesscodes
[5068]5
[5080]6Components that represent access codes and related.
[5068]7
[5080]8.. :doctest:
[5068]9
[5118]10About access-codes
11==================
[5068]12
[5118]13Access codes are ids used to grant first-time access to the system for
14students.
[5080]15
[5118]16They are normally not generated by third-party components but always
17part of batches.
[5068]18
[5118]19An access-code consists of three parts::
[5087]20
[5118]21    APP-12-0123456789
22    ^^^ ^^ ^^^^^^^^^^
23     A  B  C
[5087]24
[5118]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.
[5068]28
[5118]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.
[5068]33
34
[5118]35AccessCode
36==========
[5068]37
[5118]38.. class:: AccessCode(batch_serial, random_num[,invalidation_date=None[, student_id=None]])
[5068]39
[5118]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.
[5068]43
[5118]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.
[5080]47
[5118]48   The class implements
49   :mod:`waeup.sirp.accesscodes.interfaces.IAccessCode`:
[5068]50
[5118]51    >>> from waeup.sirp.accesscodes.interfaces import IAccessCode
52    >>> from waeup.sirp.accesscodes.accesscodes import AccessCode
[5068]53    >>> from zope.interface.verify import verifyClass
54    >>> verifyClass(IAccessCode, AccessCode)
55    True
56
[5118]57   .. attribute:: representation
[5109]58
[5118]59      The 'full' id of an access-code as described above. Something
60      like ``'APP-12-0123456789'``.
[5109]61
[5118]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
[5128]105    >>> ac = AccessCode(None, '9999999999')
[5109]106    >>> ac.representation
107    '--<10-DIGITS>'
108
[5128]109   Also the ``cost`` will not be set:
[5118]110
[5128]111    >>> ac.cost is None
112    True
113
114
[5118]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
[5122]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
[5118]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
[5122]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:
[5118]292
[5122]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
[5128]309      Add a batch in this container. You should make sure, that
310      entries in the given batch are set correctly.
[5122]311
[5128]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
[5122]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
[5109]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.