1 | :mod:`waeup.sirp.accesscodes.accesscodes` -- access codes (aka PINs) |
---|
2 | ******************************************************************** |
---|
3 | |
---|
4 | .. module:: waeup.sirp.accesscodes.accesscodes |
---|
5 | |
---|
6 | Components that represent access codes and related. |
---|
7 | |
---|
8 | .. :doctest: |
---|
9 | |
---|
10 | About access-codes |
---|
11 | ================== |
---|
12 | |
---|
13 | Access codes are ids used to grant first-time access to the system for |
---|
14 | students. |
---|
15 | |
---|
16 | They are normally not generated by third-party components but always |
---|
17 | part of batches. |
---|
18 | |
---|
19 | An access-code consists of three parts:: |
---|
20 | |
---|
21 | APP-12-0123456789 |
---|
22 | ^^^ ^^ ^^^^^^^^^^ |
---|
23 | A B C |
---|
24 | |
---|
25 | where ``A`` tells about the purpose of the code, ``B`` gives the |
---|
26 | number of batch the code belongs to (1 to 3 digits), and ``C`` is a |
---|
27 | unique random number of 10 digits. |
---|
28 | |
---|
29 | For the generation of the random number :mod:`waeup.sirp` requires a |
---|
30 | 'urandom' entropy provider which is available with most standard |
---|
31 | Unix/Linux systems. This makes the generated numbers relatively |
---|
32 | secure, especially when compared with recent PHP-based applications. |
---|
33 | |
---|
34 | |
---|
35 | AccessCode |
---|
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 | |
---|
110 | AccessCodeBatch |
---|
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 | |
---|
216 | Examples |
---|
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 | |
---|
227 | Creating a batch of three access-codes, with a cost of ``12.12`` per |
---|
228 | code, 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 | |
---|
236 | Getting 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 | |
---|
248 | Getting 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 | |
---|
258 | Trying 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 | |
---|
265 | Invalidating 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 | |
---|
284 | Access codes get their ``cost`` from the batch they belong to. Note, |
---|
285 | that it is advisable to print costs always using a format, as Python |
---|
286 | floats 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 | |
---|
295 | AccessCodeBatchContainer |
---|
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 | |
---|
315 | Access 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') |
---|