1 | Access Codes (aka PINs) |
---|
2 | *********************** |
---|
3 | |
---|
4 | .. module:: waeup.kofa.accesscodes.accesscode |
---|
5 | |
---|
6 | Components that represent access codes and related. |
---|
7 | |
---|
8 | .. :NOdoctest: |
---|
9 | .. :NOlayer: waeup.kofa.testing.KofaUnitTestLayer |
---|
10 | |
---|
11 | About Access Codes |
---|
12 | ================== |
---|
13 | |
---|
14 | Access codes are ids used to grant first-time access to the system for |
---|
15 | students. |
---|
16 | |
---|
17 | They are normally not generated by third-party components but always |
---|
18 | part of batches. |
---|
19 | |
---|
20 | An access-code consists of three parts:: |
---|
21 | |
---|
22 | APP-12-0123456789 |
---|
23 | ^^^ ^^ ^^^^^^^^^^ |
---|
24 | A B C |
---|
25 | |
---|
26 | where ``A`` tells about the purpose of the code, ``B`` gives the |
---|
27 | number of batch the code belongs to (1 to 3 digits), and ``C`` is a |
---|
28 | unique random number of 10 digits. |
---|
29 | |
---|
30 | For the generation of the random number :mod:`waeup.kofa` requires a |
---|
31 | 'urandom' entropy provider which is available with most standard |
---|
32 | Unix/Linux systems. This makes the generated numbers relatively |
---|
33 | secure, especially when compared with recent PHP-based applications. |
---|
34 | |
---|
35 | |
---|
36 | Access Code |
---|
37 | =========== |
---|
38 | |
---|
39 | .. class:: AccessCode(batch_serial, random_num[,invalidation_date=None[, student_id=None]]) |
---|
40 | |
---|
41 | You normally shouldn't create standalone access codes. Use |
---|
42 | instances of :class:`AccessCodeBatch` instead as they generate them |
---|
43 | (in masses) and care for them. |
---|
44 | |
---|
45 | Note, that :class:`AccessCode` instances are not persistent on |
---|
46 | themselves. They have to be stored inside a persistent object (like |
---|
47 | :class:`AccessCodeBatch`) to be kept. |
---|
48 | |
---|
49 | Access codes can have three states: unused, used, and |
---|
50 | disabled. While still unused but enabled access codes are reflected |
---|
51 | by an empty ``invalidation_date`` (set to ``None``), an already |
---|
52 | used (invalidated) code provides an invalidation date. |
---|
53 | |
---|
54 | In case of misuse or similar cases access codes can also be |
---|
55 | completely disabled by setting the ``disabled`` attribute to |
---|
56 | ``True``. |
---|
57 | |
---|
58 | The class implements |
---|
59 | :mod:`waeup.kofa.accesscodes.interfaces.IAccessCode`: |
---|
60 | |
---|
61 | >>> from waeup.kofa.accesscodes.interfaces import IAccessCode |
---|
62 | >>> from waeup.kofa.accesscodes.accesscodes import AccessCode |
---|
63 | >>> from zope.interface.verify import verifyClass |
---|
64 | >>> verifyClass(IAccessCode, AccessCode) |
---|
65 | True |
---|
66 | |
---|
67 | .. attribute:: representation |
---|
68 | |
---|
69 | The 'full' id of an access-code as described above. Something |
---|
70 | like ``'APP-12-0123456789'``. |
---|
71 | |
---|
72 | Read-only attribute. |
---|
73 | |
---|
74 | .. attribute:: batch_serial |
---|
75 | |
---|
76 | Serial number of this access-code inside the batch. |
---|
77 | |
---|
78 | .. note:: XXX: Do we really need this? |
---|
79 | |
---|
80 | Subject to be dropped. |
---|
81 | |
---|
82 | .. attribute:: student_id |
---|
83 | |
---|
84 | A string or ``None``. Set when an access-code is |
---|
85 | invalidated. ``None`` by default. |
---|
86 | |
---|
87 | .. attribute:: batch_prefix |
---|
88 | |
---|
89 | The prefix of the batch the access-code belongs to. |
---|
90 | |
---|
91 | Read-only attribute. |
---|
92 | |
---|
93 | .. attribute:: batch_num |
---|
94 | |
---|
95 | The number of the batch the access-code belongs to. |
---|
96 | |
---|
97 | Read-only attribute. |
---|
98 | |
---|
99 | .. attribute:: cost |
---|
100 | |
---|
101 | What the access-code costs. A float. |
---|
102 | |
---|
103 | Read-only attribute. |
---|
104 | |
---|
105 | .. attribute:: invalidation_date |
---|
106 | |
---|
107 | Python datetime when the access code was invalidated, or |
---|
108 | ``None``. ``None`` by default. |
---|
109 | |
---|
110 | If an access-code is disabled, this attribute contains the |
---|
111 | datetime of disabling. |
---|
112 | |
---|
113 | Read-only attribute. Only batches are supposed to set this value. |
---|
114 | |
---|
115 | .. attribute:: disabled |
---|
116 | |
---|
117 | Boolean, ``False`` by default. When set to ``True``, this |
---|
118 | access-code should not be used any more. |
---|
119 | |
---|
120 | Read-only attribute. Only batches are supposed to set this value. |
---|
121 | |
---|
122 | Access codes that are not part of a batch, will give strange |
---|
123 | representations: |
---|
124 | |
---|
125 | >>> ac = AccessCode(None, '9999999999') |
---|
126 | >>> ac.representation |
---|
127 | '--<10-DIGITS>' |
---|
128 | |
---|
129 | Also the ``cost`` will not be set: |
---|
130 | |
---|
131 | >>> ac.cost is None |
---|
132 | True |
---|
133 | |
---|
134 | |
---|
135 | Access Code Batch |
---|
136 | ================= |
---|
137 | |
---|
138 | .. class:: AccessCodeBatch(creation_date, creator, batch_prefix, cost, entry_num, num) |
---|
139 | |
---|
140 | Create a batch of access codes. |
---|
141 | |
---|
142 | :param creation_date: python datetime |
---|
143 | :param creator: creators user id |
---|
144 | :type creator: string |
---|
145 | :param batch_prefix: prefix of this batch |
---|
146 | :param cost: cost per access code |
---|
147 | :type cost: float |
---|
148 | :param entry_num: number of access codes to create |
---|
149 | :param num: number of this batch |
---|
150 | |
---|
151 | A persistent :class:`grok.Model`. It implements |
---|
152 | :class:`waeup.kofa.accesscodes.interfaces.IAccessCodeBatch`. |
---|
153 | |
---|
154 | When creating a batch, all entries (access codes) are generated as |
---|
155 | well. |
---|
156 | |
---|
157 | .. attribute:: creation_date |
---|
158 | |
---|
159 | The datetime when the batch was created. |
---|
160 | |
---|
161 | .. attribute:: creator |
---|
162 | |
---|
163 | String with user id of the user that generated the batch. |
---|
164 | |
---|
165 | .. attribute:: cost |
---|
166 | |
---|
167 | Float representing the costs for a single access-code. All |
---|
168 | entries inside the batch share the same cost. |
---|
169 | |
---|
170 | .. attribute:: entry_num |
---|
171 | |
---|
172 | Number of entries (access codes) inside the batch. |
---|
173 | |
---|
174 | .. attribute:: invalidated_num |
---|
175 | |
---|
176 | Number of entries that were already invalidated. |
---|
177 | |
---|
178 | .. attribute:: prefix |
---|
179 | |
---|
180 | Prefix of the batch. This tells about the purpose of this batch. |
---|
181 | |
---|
182 | .. attribute:: num |
---|
183 | |
---|
184 | Number of this batch. For a certain prefix there can exist |
---|
185 | several batches, which are numbered in increasing order. The |
---|
186 | number is normally computed by the |
---|
187 | :class:`AccessCodeBatchContainer` in which batches are |
---|
188 | stored. |
---|
189 | |
---|
190 | .. seealso:: :class:`AccessCodeBatchContainer` |
---|
191 | |
---|
192 | .. method:: entries() |
---|
193 | |
---|
194 | Get all accesscodes stored in the batch. |
---|
195 | |
---|
196 | Returns a generator over all stored entries. |
---|
197 | |
---|
198 | .. method:: getAccessCode(acesscode_id) |
---|
199 | |
---|
200 | Get the :class:`AccessCode` object for the given |
---|
201 | ``accesscode_id``. |
---|
202 | |
---|
203 | Certain single access codes can be accessed inside a batch by |
---|
204 | their representation (i.e. something like ``'APP-12-0123456789'``. |
---|
205 | |
---|
206 | When a code cannot be found :exc:`KeyError` is raised. |
---|
207 | |
---|
208 | .. method:: getAccessCodeForStudentId(student_id) |
---|
209 | |
---|
210 | Get the :class:`AccessCode` object for the given |
---|
211 | ``student_id``. |
---|
212 | |
---|
213 | Certain single access codes can be accessed inside a batch by |
---|
214 | their student_id. The student_id must be some string; ``None`` |
---|
215 | is not a valid value. |
---|
216 | |
---|
217 | When a code cannot be found :exc:`KeyError` is raised. |
---|
218 | |
---|
219 | .. method:: addAccessCode(serial_num, random_num) |
---|
220 | |
---|
221 | Add an access code to the batch. |
---|
222 | |
---|
223 | ``serial_num`` denotes the serial number of the new access-code |
---|
224 | inside the batch. ``random_num`` is a string of 10 digits unique |
---|
225 | in this batch. |
---|
226 | |
---|
227 | .. method:: invalidate(ac_id[, student_id=None]) |
---|
228 | |
---|
229 | Invalidate the access-code with ID ``ac_id``. |
---|
230 | |
---|
231 | Sets also the ``student_id`` attribute of the respective |
---|
232 | :class:`AccessCode` entry. |
---|
233 | |
---|
234 | .. method:: disable(ac_id, user_id) |
---|
235 | |
---|
236 | Disable the access-code with ID ``ac_id``. |
---|
237 | |
---|
238 | ``user_id`` is the user ID of the user triggering the |
---|
239 | process. Already disabled ACs are left untouched. |
---|
240 | |
---|
241 | Sets also the ``student_id`` and ``invalidation_date`` |
---|
242 | attributes of the respective :class:`AccessCode` entry. While |
---|
243 | ``student_id`` is set to the given ``user_id``, |
---|
244 | ``invalidation_date`` is set to current datetime. |
---|
245 | |
---|
246 | Disabled access codes are supposed not to be used any more at |
---|
247 | all. |
---|
248 | |
---|
249 | .. method:: enable(ac_id) |
---|
250 | |
---|
251 | (Re-)enable the access-code with ID ``ac_id``. |
---|
252 | |
---|
253 | This leaves the given AC in state ``unused``. Already enabled |
---|
254 | ACs are left untouched. |
---|
255 | |
---|
256 | Sets ``student_id`` and ``invalidation_date`` values of the |
---|
257 | respective :class:`AccessCode` entry to ``None``. |
---|
258 | |
---|
259 | .. method:: createCSVLogFile() |
---|
260 | |
---|
261 | Create a CSV file with data in batch. |
---|
262 | |
---|
263 | Data will not contain invalidation date nor student ids. File |
---|
264 | will be created in ``accesscodes`` subdir of data center storage |
---|
265 | path. |
---|
266 | |
---|
267 | Returns name of created file. |
---|
268 | |
---|
269 | .. method:: archive() |
---|
270 | |
---|
271 | Create a CSV file for archive. Archive files contain also |
---|
272 | ``student_id`` and ``invalidation_date``. |
---|
273 | |
---|
274 | Returns name of created file. |
---|
275 | |
---|
276 | .. method:: search(searchterm, searchtype) |
---|
277 | |
---|
278 | Search the batch for entries that comply with ``searchterm`` and |
---|
279 | ``searchtype``. |
---|
280 | |
---|
281 | ``searchtype`` must be one of ``serial``, ``pin``, or |
---|
282 | ``stud_id`` and specifies, what kind of data is contained in the |
---|
283 | ``searchterm``. |
---|
284 | |
---|
285 | - ``serial`` looks for the AC with the ``serial_batch`` set to |
---|
286 | the given (integer) number in ``searchterm``. Here |
---|
287 | ``searchterm`` must be an int number. |
---|
288 | |
---|
289 | - ``pin`` looks for the AC with the ``representation`` set to |
---|
290 | the given string in ``searchterm``. Here ``searchterm`` must |
---|
291 | be a string containing the full AC-ID like |
---|
292 | ``APP-1-0123456789``. |
---|
293 | |
---|
294 | - ``stud_id`` looks for the AC with the ``student_id`` set to |
---|
295 | the given string in ``searchterm``. Here ``searchterm`` must |
---|
296 | be a string containing the full student ID. |
---|
297 | |
---|
298 | Lookup is done via local BTrees and therefore pretty fast. |
---|
299 | |
---|
300 | Returns a list of access codes found or empty list. |
---|
301 | |
---|
302 | |
---|
303 | Examples |
---|
304 | -------- |
---|
305 | |
---|
306 | :class:`AccessCodeBatch` implements :class:`IAccessCodeBatch`: |
---|
307 | |
---|
308 | >>> from waeup.kofa.accesscodes.interfaces import IAccessCodeBatch |
---|
309 | >>> from waeup.kofa.accesscodes.accesscodes import AccessCodeBatch |
---|
310 | >>> from zope.interface.verify import verifyClass |
---|
311 | >>> verifyClass(IAccessCodeBatch, AccessCodeBatch) |
---|
312 | True |
---|
313 | |
---|
314 | Creating a batch of three access codes, with a cost of ``12.12`` per |
---|
315 | code, the batch prefix ``APP``, batch number ``10``, creator ID |
---|
316 | ``Fred`` and some arbitrary creation date: |
---|
317 | |
---|
318 | >>> import datetime |
---|
319 | >>> from waeup.kofa.accesscodes.accesscodes import AccessCodeBatch |
---|
320 | >>> batch = AccessCodeBatch( |
---|
321 | ... datetime.datetime(2009, 12, 23), 'Fred','APP', 12.12, 3, num=10) |
---|
322 | |
---|
323 | Getting all access codes from a batch: |
---|
324 | |
---|
325 | >>> ac_codes = batch.entries() |
---|
326 | >>> ac_codes |
---|
327 | <generator object...at 0x...> |
---|
328 | |
---|
329 | >>> [x.representation for x in ac_codes] |
---|
330 | ['APP-10-<10-DIGITS>', 'APP-10-<10-DIGITS>', 'APP-10-<10-DIGITS>'] |
---|
331 | |
---|
332 | >>> [x for x in batch.entries()] |
---|
333 | [<waeup.kofa...AccessCode object at 0x...>, ...] |
---|
334 | |
---|
335 | Getting a single entry from the batch: |
---|
336 | |
---|
337 | >>> ac_id = list(batch.entries())[0].representation |
---|
338 | >>> ac = batch.getAccessCode(ac_id) |
---|
339 | >>> ac |
---|
340 | <waeup.kofa...AccessCode object at 0x...> |
---|
341 | |
---|
342 | >>> ac is list(batch.entries())[0] |
---|
343 | True |
---|
344 | |
---|
345 | Trying to get a single not-existent entry from a batch: |
---|
346 | |
---|
347 | >>> batch.getAccessCode('blah') |
---|
348 | Traceback (most recent call last): |
---|
349 | ... |
---|
350 | KeyError: 'blah' |
---|
351 | |
---|
352 | Invalidating an entry: |
---|
353 | |
---|
354 | >>> batch.invalidated_num |
---|
355 | 0 |
---|
356 | |
---|
357 | # |
---|
358 | # >>> str(ac.invalidation_date), str(ac.student_id) |
---|
359 | # ('None', 'None') |
---|
360 | # |
---|
361 | |
---|
362 | >>> batch.invalidate(ac_id) |
---|
363 | >>> batch.invalidated_num |
---|
364 | 1 |
---|
365 | |
---|
366 | # >>> ac.invalidation_date , str(ac.student_id) |
---|
367 | # (datetime.datetime(...), 'None') |
---|
368 | # |
---|
369 | # >>> batch.invalidate(ac_id, 'some_user_id') |
---|
370 | # >>> ac.student_id |
---|
371 | # 'some_user_id' |
---|
372 | # |
---|
373 | #Getting a single entry by student_id: |
---|
374 | # |
---|
375 | # >>> batch.getAccessCodeForStudentId('some_user_id') |
---|
376 | # <waeup.kofa...AccessCode object at 0x...> |
---|
377 | # |
---|
378 | #Non-existent values will cause a :exc:`KeyError`: |
---|
379 | # |
---|
380 | # >>> batch.getAccessCodeForStudentId('non-existing') |
---|
381 | # Traceback (most recent call last): |
---|
382 | # ... |
---|
383 | # KeyError: 'non-existing' |
---|
384 | # |
---|
385 | #Already enabled entries will be left untouched when trying to renable |
---|
386 | #them: |
---|
387 | |
---|
388 | # >>> batch.enable(ac_id) |
---|
389 | # >>> ac.student_id |
---|
390 | # 'some_user_id' |
---|
391 | |
---|
392 | Disabling an entry: |
---|
393 | |
---|
394 | # >>> batch.disabled_num |
---|
395 | # 0 |
---|
396 | # |
---|
397 | # >>> ac.disabled |
---|
398 | # False |
---|
399 | # |
---|
400 | # >>> batch.disable(ac_id, 'some userid') |
---|
401 | # >>> ac.disabled |
---|
402 | # True |
---|
403 | # |
---|
404 | # >>> ac.student_id |
---|
405 | # 'some userid' |
---|
406 | # |
---|
407 | # >>> batch.disabled_num |
---|
408 | # 1 |
---|
409 | |
---|
410 | Already disabled entries will not be disabled again: |
---|
411 | |
---|
412 | # >>> batch.disable(ac_id, 'other userid') |
---|
413 | # >>> ac.student_id |
---|
414 | # 'some userid' |
---|
415 | |
---|
416 | Reenabling an entry: |
---|
417 | |
---|
418 | # >>> batch.enable(ac_id) |
---|
419 | # >>> ac.disabled |
---|
420 | # False |
---|
421 | # |
---|
422 | # >>> ac.student_id is None |
---|
423 | # True |
---|
424 | # |
---|
425 | # >>> ac.invalidation_date is None |
---|
426 | # True |
---|
427 | |
---|
428 | Access codes get their ``cost`` from the batch they belong to. Note, |
---|
429 | that it is advisable to print costs always using a format, as Python |
---|
430 | floats are often represented by irritating values: |
---|
431 | |
---|
432 | >>> ac.cost |
---|
433 | 12.119999999999999 |
---|
434 | |
---|
435 | >>> print "%0.2f" % ac.cost |
---|
436 | 12.12 |
---|
437 | |
---|
438 | Searching for serials: |
---|
439 | |
---|
440 | >>> result = batch.search(0, 'serial') |
---|
441 | >>> result |
---|
442 | [<waeup.kofa...AccessCode object at 0x...>] |
---|
443 | |
---|
444 | >>> result[0].batch_serial |
---|
445 | 0 |
---|
446 | |
---|
447 | Searching for AC-IDs: |
---|
448 | |
---|
449 | >>> result = batch.search(ac.representation, 'pin') |
---|
450 | >>> result[0].representation |
---|
451 | 'APP-...-...' |
---|
452 | |
---|
453 | Searching for student IDs: |
---|
454 | |
---|
455 | # >>> batch.invalidate(ac_id, 'some_new_user_id') |
---|
456 | # >>> result = batch.search('some_new_user_id', 'stud_id') |
---|
457 | # >>> result[0].student_id |
---|
458 | # 'some_new_user_id' |
---|
459 | |
---|
460 | Searching for not existing entries will return empty lists: |
---|
461 | |
---|
462 | >>> batch.search(12, 'serial') |
---|
463 | [] |
---|
464 | |
---|
465 | >>> batch.search('not-a-valid-pin', 'pin') |
---|
466 | [] |
---|
467 | |
---|
468 | # >>> batch.search('not-a-student-id', 'stud_id') |
---|
469 | # [] |
---|
470 | |
---|
471 | # >>> batch.search('blah', 'not-a-valid-searchtype') |
---|
472 | # [] |
---|
473 | |
---|
474 | Access Code Batch Container |
---|
475 | =========================== |
---|
476 | |
---|
477 | .. class:: AccessCodeBatchContainer() |
---|
478 | |
---|
479 | A container for access code batches. |
---|
480 | |
---|
481 | .. method:: addBatch(batch) |
---|
482 | |
---|
483 | Add a batch in this container. You should make sure, that |
---|
484 | entries in the given batch are set correctly. |
---|
485 | |
---|
486 | .. method:: createBatch(creation_date, creator, batch_prefix, cost, entry_num) |
---|
487 | |
---|
488 | Create a batch inside this container. Returns the batch created. |
---|
489 | |
---|
490 | :param creation_date: python datetime |
---|
491 | :param creator: creators user id |
---|
492 | :type creator: string |
---|
493 | :param batch_prefix: prefix of this batch |
---|
494 | :param cost: cost per access code |
---|
495 | :type cost: float |
---|
496 | :param entry_num: number of access codes to create |
---|
497 | |
---|
498 | .. method:: getNum(prefix) |
---|
499 | |
---|
500 | Get next unused num for a new batch and a given prefix. |
---|
501 | |
---|
502 | Batches for a given prefix are numerated. Whenever a new batch |
---|
503 | is created and other batches inside the container already have |
---|
504 | the same prefix, the new one will get the lowest unused number. |
---|
505 | |
---|
506 | .. method:: getImportFiles() |
---|
507 | |
---|
508 | Get a list of basenames of available import files, suitable for |
---|
509 | feeding :meth:`reimport`. |
---|
510 | |
---|
511 | .. method:: reimport(filename, creator=u'UNKNOWN') |
---|
512 | |
---|
513 | Reimport a CSV log file of previously created AC batch. |
---|
514 | |
---|
515 | ``filename`` is the name (basename) of the file residing in the |
---|
516 | accesscode storage's ``import`` directory.``creator`` is the |
---|
517 | user ID of the current user. |
---|
518 | |
---|
519 | .. method:: search(search_term, search_type) |
---|
520 | |
---|
521 | Look in all contained batches for access codes that comply with |
---|
522 | the given parameters. |
---|
523 | |
---|
524 | ``search_type`` must be one of ``serial``, ``pin``, or |
---|
525 | ``stud_id`` and specifies, what kind of data is contained in the |
---|
526 | ``search_term``. |
---|
527 | |
---|
528 | - ``serial`` looks for the AC with the ``serial_batch`` set to |
---|
529 | the given (integer) number in ``search_term``. Here |
---|
530 | ``search_term`` can be an integer or a string. If it is a |
---|
531 | string it will be converted to an int. |
---|
532 | |
---|
533 | - ``pin`` looks for the AC with the ``representation`` set to |
---|
534 | the given string in ``search_term``. Here ``search_term`` must |
---|
535 | be a string containing the full AC-ID like |
---|
536 | ``APP-1-0123456789``. |
---|
537 | |
---|
538 | - ``stud_id`` looks for the AC with the ``student_id`` set to |
---|
539 | the given string in ``search_term``. Here ``search_term`` must |
---|
540 | be a string containing the full student ID. |
---|
541 | |
---|
542 | Examples |
---|
543 | -------- |
---|
544 | |
---|
545 | Creating a batch container: |
---|
546 | |
---|
547 | >>> from waeup.kofa.accesscodes.accesscodes import ( |
---|
548 | ... AccessCodeBatchContainer) |
---|
549 | >>> container = AccessCodeBatchContainer() |
---|
550 | |
---|
551 | Creating batches inside the container: |
---|
552 | |
---|
553 | >>> from datetime import datetime |
---|
554 | >>> batch1 = container.createBatch( |
---|
555 | ... datetime.now(), 'some userid', 'FOO', 10.12, 5) |
---|
556 | |
---|
557 | >>> batch2 = container.createBatch( |
---|
558 | ... datetime.now(), 'other userid', 'BAR', 1.95, 5) |
---|
559 | |
---|
560 | Searching the container for batch serials: |
---|
561 | |
---|
562 | >>> container.search(1, 'serial') |
---|
563 | [<waeup.kofa...AccessCode object at 0x...>, |
---|
564 | <waeup.kofa...AccessCode object at 0x...>] |
---|
565 | |
---|
566 | >>> container.search('not-a-number', 'serial') |
---|
567 | [] |
---|
568 | |
---|
569 | >>> result = container.search('1', 'serial') |
---|
570 | >>> result |
---|
571 | [<waeup.kofa...AccessCode object at 0x...>, |
---|
572 | <waeup.kofa...AccessCode object at 0x...>] |
---|
573 | |
---|
574 | Searching for ACs: |
---|
575 | |
---|
576 | >>> ac = result[0] |
---|
577 | >>> container.search(ac.representation, 'pin') |
---|
578 | [<waeup.kofa...AccessCode object at 0x...>] |
---|
579 | |
---|
580 | Searching for student IDs: |
---|
581 | |
---|
582 | # >>> ac.__parent__.invalidate( |
---|
583 | # ... ac.representation, 'some_user') |
---|
584 | # >>> container.search('some_user', 'stud_id') |
---|
585 | # [<waeup.kofa...AccessCode object at 0x...>] |
---|
586 | |
---|
587 | |
---|
588 | Access Code Plugin |
---|
589 | ================== |
---|
590 | |
---|
591 | .. class:: AccessCodePlugin |
---|
592 | |
---|
593 | A `waeup.kofa` plugin that updates existing Kofa university |
---|
594 | instances so that they provide support for access codes. |
---|
595 | |
---|
596 | .. attribute:: grok.implements(IKofaPluggable) |
---|
597 | .. attribute:: grok.name('accesscodes') |
---|
598 | |
---|
599 | .. method:: setup(site, name, logger) |
---|
600 | |
---|
601 | Create an accesscodebatch container in ``site``. Any events are |
---|
602 | logged to ``logger``. |
---|
603 | |
---|
604 | .. method:: update(site, name, logger) |
---|
605 | |
---|
606 | Check if ``site`` contains an accesscodebatch container and add |
---|
607 | it if missing. Any events are logged to ``logger``. |
---|
608 | |
---|
609 | The AccessCodePlugin is available as a global named utility for the |
---|
610 | IKofaPluggable interface named ``accesscodes``. |
---|
611 | |
---|
612 | It is looked up by a university instance when created, so that this |
---|
613 | instance has nothing to know about accesscodes at all and can |
---|
614 | although provide support for them. |
---|
615 | |
---|
616 | >>> from zope.component import getUtility |
---|
617 | >>> from waeup.kofa.interfaces import IKofaPluggable |
---|
618 | >>> plugin = getUtility(IKofaPluggable, name='accesscodes') |
---|
619 | >>> plugin |
---|
620 | <waeup.kofa.accesscodes.accesscodes.AccessCodePlugin object at 0x...> |
---|
621 | |
---|
622 | It provides a `setup()` and an `update()` method. Both have to be |
---|
623 | fed with a site object (the `IUniversity` instance to modify), a |
---|
624 | logger and a name. |
---|
625 | |
---|
626 | We create a faked site and a logger: |
---|
627 | |
---|
628 | >>> import logging |
---|
629 | >>> faked_site = dict() |
---|
630 | >>> logger = logging.getLogger('ac_test') |
---|
631 | >>> logger.setLevel(logging.DEBUG) |
---|
632 | >>> ch = logging.FileHandler('ac_tests.log', 'w') |
---|
633 | >>> ch.setLevel(logging.DEBUG) |
---|
634 | >>> logger.addHandler(ch) |
---|
635 | |
---|
636 | Now we can install our stuff in the faked site: |
---|
637 | |
---|
638 | >>> plugin.setup(faked_site, 'blah', logger) |
---|
639 | |
---|
640 | The faked site now has an access code container: |
---|
641 | |
---|
642 | >>> faked_site.keys() |
---|
643 | ['accesscodes'] |
---|
644 | |
---|
645 | The action is described in the log: |
---|
646 | |
---|
647 | >>> print open('ac_tests.log', 'r').read() |
---|
648 | Installed container for access code batches. |
---|
649 | |
---|
650 | We can also update an existing site, by calling `update()`: |
---|
651 | |
---|
652 | >>> plugin.update(faked_site, 'blah', logger) |
---|
653 | |
---|
654 | There was nothing to do for the updater: |
---|
655 | |
---|
656 | >>> print open('ac_tests.log', 'r').read() |
---|
657 | Installed container for access code batches. |
---|
658 | AccessCodePlugin: Updating site at {'accesscodes'...: Nothing to do. |
---|
659 | |
---|
660 | But if we remove the created batch container and call the updater, it |
---|
661 | will create a new one: |
---|
662 | |
---|
663 | >>> del faked_site['accesscodes'] |
---|
664 | >>> plugin.update(faked_site, 'blah', logger) |
---|
665 | >>> print open('ac_tests.log', 'r').read() |
---|
666 | Installed container for access code batches. |
---|
667 | AccessCodePlugin: Updating site at {'accesscodes'...: Nothing to do. |
---|
668 | Updating site at {}. Installing access codes. |
---|
669 | Installed container for access code batches. |
---|
670 | |
---|
671 | Clean up: |
---|
672 | |
---|
673 | >>> import os |
---|
674 | >>> os.unlink('ac_tests.log') |
---|