[5080] | 1 | :mod:`waeup.sirp.accesscodes.accesscodes` -- access codes (aka PINs) |
---|
| 2 | ******************************************************************** |
---|
[5068] | 3 | |
---|
[5080] | 4 | .. module:: waeup.sirp.accesscodes.accesscodes |
---|
[5068] | 5 | |
---|
[5080] | 6 | Components that represent access codes and related. |
---|
[5068] | 7 | |
---|
[5080] | 8 | .. :doctest: |
---|
[5068] | 9 | |
---|
[5118] | 10 | About access-codes |
---|
| 11 | ================== |
---|
[5068] | 12 | |
---|
[5118] | 13 | Access codes are ids used to grant first-time access to the system for |
---|
| 14 | students. |
---|
[5080] | 15 | |
---|
[5118] | 16 | They are normally not generated by third-party components but always |
---|
| 17 | part of batches. |
---|
[5068] | 18 | |
---|
[5118] | 19 | An access-code consists of three parts:: |
---|
[5087] | 20 | |
---|
[5118] | 21 | APP-12-0123456789 |
---|
| 22 | ^^^ ^^ ^^^^^^^^^^ |
---|
| 23 | A B C |
---|
[5087] | 24 | |
---|
[5118] | 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. |
---|
[5068] | 28 | |
---|
[5118] | 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. |
---|
[5068] | 33 | |
---|
| 34 | |
---|
[5118] | 35 | AccessCode |
---|
| 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 | |
---|
| 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 | |
---|
[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 | |
---|
| 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 | |
---|
[5122] | 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: |
---|
[5118] | 287 | |
---|
[5122] | 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 | |
---|
[5109] | 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') |
---|