Changeset 5118 for main/waeup.sirp/trunk/src/waeup/sirp
- Timestamp:
- 3 Apr 2010, 14:10:12 (15 years ago)
- Location:
- main/waeup.sirp/trunk/src/waeup/sirp/accesscodes
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.py
r5116 r5118 4 4 import grok 5 5 import os 6 from BTrees.OIBTree import OIBTree 7 from datetime import datetime 6 8 from random import SystemRandom as random 7 9 from waeup.sirp.interfaces import IWAeUPSIRPPluggable … … 13 15 grok.name('waeup.manageACBatches') 14 16 15 class AccessCode(grok. Model):17 class AccessCode(grok.Context): 16 18 grok.implements(IAccessCode) 17 19 18 def __init__(self, batch_serial, random_num, cost,19 invalidation_date=None,student_id=None):20 def __init__(self, batch_serial, random_num, invalidation_date=None, 21 student_id=None): 20 22 self.batch_serial = batch_serial 21 23 self.random_num = random_num 22 self.cost = cost 23 self.invalidation_date = invalidation_date 24 self._invalidation_date = invalidation_date 24 25 self.student_id = student_id 25 26 … … 45 46 return self.batch.num 46 47 47 class AccessCodeBatch(grok.Container): 48 @property 49 def cost(self): 50 if self.batch is None: 51 return None 52 return self.batch.cost 53 54 @property 55 def invalidation_date(self): 56 # We define this as a property to make it unwritable. 57 # This attribute should be set by the surrounding batch only. 58 return self._invalidation_date 59 60 class AccessCodeBatch(grok.Model): 48 61 """A batch of access codes. 49 62 """ … … 59 72 self.entry_num = entry_num 60 73 self.num = num 61 62 def createEntries(self): 74 self.invalidated_num = 0 75 self._entries = list() 76 self._acids = OIBTree() 77 self._createEntries() 78 79 def _createEntries(self): 63 80 """Create the entries for this batch. 64 81 """ 65 rands = self.getNewRandomNum(num=self.entry_num) 66 for num in range(self.entry_num): 67 ac = AccessCode(num, rands[num], self.cost) 68 self[str(num)] = ac 82 for num, pin in enumerate(self._getNewRandomNum(num=self.entry_num)): 83 ac = AccessCode(num, pin) 84 ac.__parent__ = self 85 self._entries.append(ac) 86 self._acids.update({ac.representation: num}) 87 self._p_changed = True # XXX: most probably not needed. 69 88 return 70 71 def getNewRandomNum(self, num=1):89 90 def _getNewRandomNum(self, num=1): 72 91 """Create a set of ``num`` random numbers of 10 digits each. 73 92 74 93 The number is returned as string. 75 94 """ 76 results = {}77 while len(results) <num:95 curr = 1 96 while curr <= num: 78 97 pin = '' 79 98 for x in range(10): 80 99 pin += str(random().randint(0, 9)) 81 results[pin] = True 82 return results.keys() 83 84 def createCSVLogFile(self): 85 """Create a CSV file with data in batch. 86 87 Data will not contain invalidation date nor student ids. File 88 will be created in ``accesscodes`` subdir of data center 89 storage path. 90 91 Returns name of created file. 100 if '%s-%s-%s' % (self.prefix, self.num, pin) in self._acids: 101 # PIN already in use 102 continue 103 curr += 1 104 yield pin 105 106 def _getStoragePath(self): 107 """Get the directory, where we store all batch-related CSV files. 92 108 """ 93 109 site = grok.getSite() … … 96 112 if not os.path.exists(ac_storage): 97 113 os.mkdir(ac_storage) 114 return ac_storage 115 116 def entries(self): 117 """Get all entries of this batch as generator. 118 """ 119 for x in self._entries: 120 yield x 121 122 def getAccessCode(self, ac_id): 123 """Get the AccessCode with ID ``ac_id`` or ``KeyError``. 124 """ 125 return self._entries[self._acids[ac_id]] 126 127 def invalidate(self, ac_id, student_id=None): 128 """Invalidate the AC with ID ``ac_id``. 129 """ 130 num = self._acids[ac_id] 131 ac = self.getAccessCode(ac_id) 132 ac._invalidation_date = datetime.now() 133 ac.student_id = student_id 134 self.invalidated_num += 1 135 136 def createCSVLogFile(self): 137 """Create a CSV file with data in batch. 138 139 Data will not contain invalidation date nor student ids. File 140 will be created in ``accesscodes`` subdir of data center 141 storage path. 142 143 Returns name of created file. 144 """ 98 145 date = self.creation_date.strftime('%Y_%m_%d_%H_%M_%S') 146 ac_storage = self._getStoragePath() 99 147 csv_path = os.path.join( 100 148 ac_storage, '%s-%s-%s-%s.csv' % ( … … 104 152 writer.writerow(['serial', 'ac', 'cost']) 105 153 writer.writerow([self.prefix, str(self.num), "%0.2f" % self.cost]) 106 for key, value in self.items(): 154 155 for value in self._entries: 107 156 writer.writerow( 108 157 [str(value.batch_serial), str(value.representation)] 109 158 ) 159 site = grok.getSite() 110 160 logger = site.logger 111 161 logger.info( … … 115 165 return os.path.basename(csv_path) 116 166 117 167 def archive(self): 168 """Create a CSV file for archive. 169 """ 170 ac_storage = self._getStoragePath() 171 now = datetime.now() 172 timestamp = now.strftime('%Y_%m_%d_%H_%M_%S') 173 csv_path = os.path.join( 174 ac_storage, '%s-%s_archive-%s-%s.csv' % ( 175 self.prefix, self.num, timestamp, self.creator) 176 ) 177 writer = csv.writer(open(csv_path, 'w'), quoting=csv.QUOTE_ALL) 178 writer.writerow(['prefix', 'serial', 'ac', 'student', 'date']) 179 writer.writerow([self.prefix, '%0.2f' % self.cost, str(self.num), 180 str(self.entry_num)]) 181 for value in self._entries: 182 date = '' 183 stud_id = '' 184 if value.invalidation_date is not None: 185 date = value.invalidation_date.strftime( 186 '%Y-%m-%d-%H-%M-%S') 187 if stud_id is not None: 188 stud_id = value.student_id 189 writer.writerow([ 190 self.prefix, value.batch_serial, value.representation, 191 stud_id, date 192 ]) 193 return os.path.basename(csv_path) 194 118 195 class AccessCodeBatchContainer(grok.Container): 119 196 grok.implements(IAccessCodeBatchContainer) … … 125 202 key = "%s-%s" % (batch.prefix, batch.num) 126 203 self[key] = batch 127 batch.createEntries()128 204 self._p_changed = True 129 205 -
main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.txt
r5109 r5118 8 8 .. :doctest: 9 9 10 Access codes are created as parts of batches. We therefore have to 11 create a batch first. 12 13 Here we create a batch of three entries, with a cost of ``12.12`` per 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:: invalidate(ac_id[, student_id=None]) 184 185 Invalidate the access-code with ID ``ac_id``. 186 187 Sets also the ``student_id`` attribute of the respective 188 :class:`AccessCode` entry. 189 190 .. method:: createCSVLogFile() 191 192 Create a CSV file with data in batch. 193 194 Data will not contain invalidation date nor student ids. File 195 will be created in ``accesscodes`` subdir of data center storage 196 path. 197 198 Returns name of created file. 199 200 .. method:: archive() 201 202 Create a CSV file for archive. Archive files contain also 203 ``student_id`` and ``invalidation_date``. 204 205 Returns name of created file. 206 207 208 Examples 209 -------- 210 211 :class:`AccessCodeBatch` implements :class:`IAccessCodeBatch`: 212 213 >>> from waeup.sirp.accesscodes.interfaces import IAccessCodeBatch 214 >>> from waeup.sirp.accesscodes.accesscodes import AccessCodeBatch 215 >>> from zope.interface.verify import verifyClass 216 >>> verifyClass(IAccessCodeBatch, AccessCodeBatch) 217 True 218 219 Creating a batch of three access-codes, with a cost of ``12.12`` per 14 220 code, the batch prefix ``APP``, batch number ``10``, creator ID 15 221 ``Fred`` and some arbitrary creation date: … … 20 226 ... datetime.datetime(2009, 12, 23), 'Fred','APP', 12.12, 3, num=10) 21 227 22 The entries of a batch have to be created manually. This is, because 23 we cannot add persistent objects in a still not persisted container: 24 25 >>> batch.createEntries() 26 27 Now we have three accesscodes stored in the batch: 28 29 >>> sorted(list(batch.items())) 30 [(u'0', <waeup.sirp...AccessCode object at 0x...), ...] 31 32 As we can see, access codes entries can be found in a batch by their 33 (stringified) serial number. 34 35 Each accesscode has a representation: 36 37 >>> ac = batch['0'] 38 >>> ac.representation 39 'APP-10-<10-DIGITS>' 40 41 The main point about a batch is that it can act as a dictionary for 42 the generated access codes: 43 44 >>> ac_codes = list(batch.values()) 228 Getting all access-codes from a batch: 229 230 >>> ac_codes = batch.entries() 231 >>> ac_codes 232 <generator object at 0x...> 233 45 234 >>> [x.representation for x in ac_codes] 46 ['APP-10-...', 'APP-10-...', 'APP-10-...'] 47 48 `Accesscode` and `AccessCodeBatch` classes implement the respective 49 interfaces: 50 51 >>> from waeup.sirp.accesscodes.accesscodes import ( 52 ... AccessCode, AccessCodeBatch) 53 >>> from waeup.sirp.accesscodes.interfaces import( 54 ... IAccessCode, IAccessCodeBatch) 55 >>> from zope.interface.verify import verifyClass 56 >>> verifyClass(IAccessCode, AccessCode) 235 ['APP-10-<10-DIGITS>', 'APP-10-<10-DIGITS>', 'APP-10-<10-DIGITS>'] 236 237 >>> [x for x in batch.entries()] 238 [<waeup.sirp...AccessCode object at 0x...>, ...] 239 240 Getting a single entry from the batch: 241 242 >>> ac_id = list(batch.entries())[0].representation 243 >>> ac = batch.getAccessCode(ac_id) 244 >>> ac 245 <waeup.sirp...AccessCode object at 0x...> 246 247 >>> ac is list(batch.entries())[0] 57 248 True 58 249 59 >>> verifyClass(IAccessCodeBatch, AccessCodeBatch) 60 True 61 62 Access codes without that are not part of a batch, will give strange 63 representations: 64 65 >>> ac = AccessCode(None, '9999999999', 12.12) 66 >>> ac.representation 67 '--<10-DIGITS>' 250 Trying to get a single not-existent entry from a batch: 251 252 >>> batch.getAccessCode('blah') 253 Traceback (most recent call last): 254 ... 255 KeyError: 'blah' 256 257 Invalidating an entry: 258 259 >>> batch.invalidated_num 260 0 261 262 >>> str(ac.invalidation_date), str(ac.student_id) 263 ('None', 'None') 264 265 >>> batch.invalidate(ac_id) 266 >>> batch.invalidated_num 267 1 268 269 >>> ac.invalidation_date , str(ac.student_id) 270 (datetime.datetime(...), 'None') 271 272 >>> batch.invalidate(ac_id, 'some_user_id') 273 >>> ac.student_id 274 'some_user_id' 275 68 276 69 277 Access code plugin -
main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/browser_templates/batchcontainer.pt
r5103 r5118 4 4 The following batches are available: 5 5 </p> 6 <table> 7 <thead> 8 <tr> 9 <th>Prefix</th><th>Entries/(invalidated)</th><th>Cost</th> 10 <th>Created</th><th>Creator</th> 11 </tr> 12 </thead> 13 <tbody> 14 <tr tal:repeat="batch context/values" 15 tal:attributes="class python: repeat['batch'].odd() and 'even' or 'odd'"> 16 <td> 17 <span tal:replace="batch/prefix">APP</span> 18 - 19 <span tal:replace="batch/num">10</span> 20 </td> 21 <td> 22 <span tal:replace="batch/entry_num">1012</span> 23 / 24 <span tal:replace="python: view.invalidated(batch)">512</span> 25 </td> 26 <td tal:content="batch/cost">12.12</td> 27 <td tal:content="python: batch.creation_date.ctime()">2009/12/24</td> 28 <td tal:content="batch/creator">some user</td> 29 </tr> 30 <tr tal:condition="not: context/values"> 31 <td colspan="5"><b>No batches yet</b></td> 32 </tr> 33 </tbody> 34 </table> 6 <form method="POST"> 7 <table> 8 <thead> 9 <tr> 10 <th> </th> 11 <th>Prefix</th><th>Entries/(invalidated)</th><th>Cost</th> 12 <th>Created</th><th>Creator</th> 13 </tr> 14 </thead> 15 <tbody> 16 <tr tal:repeat="batch context/values" 17 tal:attributes="class python: repeat['batch'].odd() and 'even' or 'odd'"> 18 <td> 19 <input type="checkbox" name="batches" value="batch/prefix" 20 tal:attributes="value batch/__name__" /> 21 </td> 22 <td> 23 <span tal:replace="batch/prefix">APP</span> 24 - 25 <span tal:replace="batch/num">10</span> 26 </td> 27 <td> 28 <span tal:replace="batch/entry_num">1012</span> 29 / 30 <span tal:replace="batch/invalidated_num">512</span> 31 </td> 32 <td tal:content="batch/cost">12.12</td> 33 <td tal:content="python: batch.creation_date.ctime()">2009/12/24</td> 34 <td tal:content="batch/creator">some user</td> 35 </tr> 36 <tr tal:condition="not: context/values"> 37 <td colspan="5"><b>No batches yet</b></td> 38 </tr> 39 </tbody> 40 </table> 41 <input type="submit" name="archive" value="Archive" /> 42 <input type="submit" name="delete" value="Archive and Delete" /> 43 </form>
Note: See TracChangeset for help on using the changeset viewer.