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
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
[5109]105    >>> ac = AccessCode(None, '9999999999', 12.12)
106    >>> ac.representation
107    '--<10-DIGITS>'
108
[5118]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
[5122]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
[5118]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
[5122]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:
[5118]287
[5122]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
[5109]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.