source: main/waeup.sirp/trunk/src/waeup/sirp/accesscodes/accesscodes.py @ 5127

Last change on this file since 5127 was 5127, checked in by uli, 15 years ago
  • Reorder statements in PIN generation to get better test-coverage.
  • Add createBatch method for batch containers.
File size: 7.8 KB
RevLine 
[5068]1"""Components to handle access codes.
2"""
[5110]3import csv
[5068]4import grok
[5110]5import os
[5118]6from BTrees.OIBTree import OIBTree
7from datetime import datetime
[5068]8from random import SystemRandom as random
[5073]9from waeup.sirp.interfaces import IWAeUPSIRPPluggable
[5079]10from waeup.sirp.accesscodes.interfaces import (
11    IAccessCode, IAccessCodeBatch, IAccessCodeBatchContainer
12    )
[5068]13
[5102]14class ManageACBatches(grok.Permission):
15    grok.name('waeup.manageACBatches')
16
[5118]17class AccessCode(grok.Context):
[5068]18    grok.implements(IAccessCode)
19
[5118]20    def __init__(self, batch_serial, random_num, invalidation_date=None,
21                 student_id=None):
[5068]22        self.batch_serial = batch_serial
23        self.random_num = random_num
[5118]24        self._invalidation_date = invalidation_date
[5068]25        self.student_id = student_id
26
27    @property
28    def representation(self):
29        return '%s-%s-%s' % (
30            self.batch_prefix, self.batch_num, self.random_num)
31
[5079]32    @property
33    def batch(self):
34        return getattr(self, '__parent__', None)
[5086]35
[5079]36    @property
37    def batch_prefix(self):
38        if self.batch is None:
39            return ''
40        return self.batch.prefix
[5086]41
[5079]42    @property
43    def batch_num(self):
44        if self.batch is None:
45            return ''
46        return self.batch.num
[5068]47
[5118]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   
60class AccessCodeBatch(grok.Model):
[5068]61    """A batch of access codes.
62    """
63    grok.implements(IAccessCodeBatch)
64
[5086]65    def __init__(self, creation_date, creator, batch_prefix, cost,
66                 entry_num, num=None):
[5079]67        super(AccessCodeBatch, self).__init__()
[5068]68        self.creation_date = creation_date
69        self.creator = creator
[5116]70        self.prefix = batch_prefix.upper()
[5068]71        self.cost = cost
72        self.entry_num = entry_num
[5086]73        self.num = num
[5118]74        self.invalidated_num = 0
75        self._entries = list()
76        self._acids = OIBTree()
77        self._createEntries()
[5079]78
[5118]79    def _createEntries(self):
[5079]80        """Create the entries for this batch.
81        """
[5118]82        for num, pin in enumerate(self._getNewRandomNum(num=self.entry_num)):
[5121]83            self.addAccessCode(num, pin)
84        self._p_changed = True # XXX: most probably not needed.
[5112]85        return
[5118]86
87    def _getNewRandomNum(self, num=1):
[5112]88        """Create a set of ``num`` random numbers of 10 digits each.
[5086]89
[5068]90        The number is returned as string.
91        """
[5118]92        curr = 1
93        while curr <= num:
[5112]94            pin = ''
[5068]95            for x in range(10):
[5112]96                pin += str(random().randint(0, 9))
[5127]97            if not '%s-%s-%s' % (self.prefix, self.num, pin) in self._acids:
98                curr += 1
99                yield pin
100            # PIN already in use
[5073]101
[5118]102    def _getStoragePath(self):
103        """Get the directory, where we store all batch-related CSV files.
104        """
105        site = grok.getSite()
106        storagepath = site['datacenter'].storage
107        ac_storage = os.path.join(storagepath, 'accesscodes')
108        if not os.path.exists(ac_storage):
109            os.mkdir(ac_storage)
110        return ac_storage
111
112    def entries(self):
113        """Get all entries of this batch as generator.
114        """
115        for x in self._entries:
116            yield x
117           
118    def getAccessCode(self, ac_id):
119        """Get the AccessCode with ID ``ac_id`` or ``KeyError``.
120        """
121        return self._entries[self._acids[ac_id]]
122
[5121]123    def addAccessCode(self, num, pin):
124        """Add an access-code.
125        """
126        ac = AccessCode(num, pin)
127        ac.__parent__ = self
128        self._entries.append(ac)
129        self._acids.update({ac.representation: num})
130        return
131
[5118]132    def invalidate(self, ac_id, student_id=None):
133        """Invalidate the AC with ID ``ac_id``.
134        """
135        num = self._acids[ac_id]
136        ac = self.getAccessCode(ac_id)
137        ac._invalidation_date = datetime.now()
138        ac.student_id = student_id
139        self.invalidated_num += 1
140
[5110]141    def createCSVLogFile(self):
142        """Create a CSV file with data in batch.
143
144        Data will not contain invalidation date nor student ids.  File
145        will be created in ``accesscodes`` subdir of data center
146        storage path.
147
148        Returns name of created file.
149        """
150        date = self.creation_date.strftime('%Y_%m_%d_%H_%M_%S')
[5118]151        ac_storage = self._getStoragePath()
[5110]152        csv_path = os.path.join(
153            ac_storage, '%s-%s-%s-%s.csv' % (
154                self.prefix, self.num, date, self.creator)
155            )
156        writer = csv.writer(open(csv_path, 'w'), quoting=csv.QUOTE_ALL)
157        writer.writerow(['serial', 'ac', 'cost'])
158        writer.writerow([self.prefix, str(self.num), "%0.2f" % self.cost])
[5118]159
160        for value in self._entries:
[5110]161            writer.writerow(
162                [str(value.batch_serial), str(value.representation)]
163                )
[5118]164        site = grok.getSite()
[5112]165        logger = site.logger
166        logger.info(
167            "Created batch %s-%s" % (self.prefix, self.num))
168        logger.info(
169            "Written batch CSV to %s" % csv_path)
[5110]170        return os.path.basename(csv_path)
171
[5118]172    def archive(self):
173        """Create a CSV file for archive.
174        """
175        ac_storage = self._getStoragePath()
176        now = datetime.now()
177        timestamp = now.strftime('%Y_%m_%d_%H_%M_%S')
178        csv_path = os.path.join(
179            ac_storage, '%s-%s_archive-%s-%s.csv' % (
180                self.prefix, self.num, timestamp, self.creator)
181            )
182        writer = csv.writer(open(csv_path, 'w'), quoting=csv.QUOTE_ALL)
183        writer.writerow(['prefix', 'serial', 'ac', 'student', 'date'])
184        writer.writerow([self.prefix, '%0.2f' % self.cost, str(self.num),
185                         str(self.entry_num)])
186        for value in self._entries:
187            date = ''
188            if value.invalidation_date is not None:
189                date = value.invalidation_date.strftime(
190                    '%Y-%m-%d-%H-%M-%S')
191            writer.writerow([
192                    self.prefix, value.batch_serial, value.representation,
[5123]193                    value.student_id, date
[5118]194                    ])
195        return os.path.basename(csv_path)
196
[5079]197class AccessCodeBatchContainer(grok.Container):
198    grok.implements(IAccessCodeBatchContainer)
[5073]199
[5079]200    def addBatch(self, batch):
[5086]201        """Add a batch.
202        """
203        batch.num = self.getNum(batch.prefix)
204        key = "%s-%s" % (batch.prefix, batch.num)
[5079]205        self[key] = batch
[5086]206        self._p_changed = True
[5079]207
[5127]208    def createBatch(self, creation_date, creator, batch_prefix, cost,
209                    entry_num):
210        """Create and add a batch.
211        """
212        batch_num = self.getNum(batch_prefix)
213        batch = AccessCodeBatch(creation_date, creator, batch_prefix,
214                                cost, entry_num, num=batch_num)
215        self.addBatch(batch)
216        return batch
217       
[5086]218    def getNum(self, prefix):
219        """Get next unused num for given prefix.
220        """
221        num = 1
[5116]222        while self.get('%s-%s' % (prefix, num), None) is not None:
[5086]223            num += 1
224        return num
[5095]225
[5073]226class AccessCodePlugin(grok.GlobalUtility):
227    grok.name('accesscodes')
228    grok.implements(IWAeUPSIRPPluggable)
229
230    def setup(self, site, name, logger):
[5079]231        site['accesscodes'] = AccessCodeBatchContainer()
232        logger.info('Installed container for access code batches.')
233        return
[5073]234
235    def update(self, site, name, logger):
[5107]236        if not 'accesscodes' in site.keys():
237            logger.info('Updating site at %s. Installing access codes.' % (
238                    site,))
239            self.setup(site, name, logger)
240        else:
241            logger.info(
242                'AccessCodePlugin: Updating site at %s: Nothing to do.' % (
243                    site, ))
244        return
Note: See TracBrowser for help on using the repository browser.