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
Line 
1"""Components to handle access codes.
2"""
3import csv
4import grok
5import os
6from BTrees.OIBTree import OIBTree
7from datetime import datetime
8from random import SystemRandom as random
9from waeup.sirp.interfaces import IWAeUPSIRPPluggable
10from waeup.sirp.accesscodes.interfaces import (
11    IAccessCode, IAccessCodeBatch, IAccessCodeBatchContainer
12    )
13
14class ManageACBatches(grok.Permission):
15    grok.name('waeup.manageACBatches')
16
17class AccessCode(grok.Context):
18    grok.implements(IAccessCode)
19
20    def __init__(self, batch_serial, random_num, invalidation_date=None,
21                 student_id=None):
22        self.batch_serial = batch_serial
23        self.random_num = random_num
24        self._invalidation_date = invalidation_date
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
32    @property
33    def batch(self):
34        return getattr(self, '__parent__', None)
35
36    @property
37    def batch_prefix(self):
38        if self.batch is None:
39            return ''
40        return self.batch.prefix
41
42    @property
43    def batch_num(self):
44        if self.batch is None:
45            return ''
46        return self.batch.num
47
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):
61    """A batch of access codes.
62    """
63    grok.implements(IAccessCodeBatch)
64
65    def __init__(self, creation_date, creator, batch_prefix, cost,
66                 entry_num, num=None):
67        super(AccessCodeBatch, self).__init__()
68        self.creation_date = creation_date
69        self.creator = creator
70        self.prefix = batch_prefix.upper()
71        self.cost = cost
72        self.entry_num = entry_num
73        self.num = num
74        self.invalidated_num = 0
75        self._entries = list()
76        self._acids = OIBTree()
77        self._createEntries()
78
79    def _createEntries(self):
80        """Create the entries for this batch.
81        """
82        for num, pin in enumerate(self._getNewRandomNum(num=self.entry_num)):
83            self.addAccessCode(num, pin)
84        self._p_changed = True # XXX: most probably not needed.
85        return
86
87    def _getNewRandomNum(self, num=1):
88        """Create a set of ``num`` random numbers of 10 digits each.
89
90        The number is returned as string.
91        """
92        curr = 1
93        while curr <= num:
94            pin = ''
95            for x in range(10):
96                pin += str(random().randint(0, 9))
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
101
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
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
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
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')
151        ac_storage = self._getStoragePath()
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])
159
160        for value in self._entries:
161            writer.writerow(
162                [str(value.batch_serial), str(value.representation)]
163                )
164        site = grok.getSite()
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)
170        return os.path.basename(csv_path)
171
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,
193                    value.student_id, date
194                    ])
195        return os.path.basename(csv_path)
196
197class AccessCodeBatchContainer(grok.Container):
198    grok.implements(IAccessCodeBatchContainer)
199
200    def addBatch(self, batch):
201        """Add a batch.
202        """
203        batch.num = self.getNum(batch.prefix)
204        key = "%s-%s" % (batch.prefix, batch.num)
205        self[key] = batch
206        self._p_changed = True
207
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       
218    def getNum(self, prefix):
219        """Get next unused num for given prefix.
220        """
221        num = 1
222        while self.get('%s-%s' % (prefix, num), None) is not None:
223            num += 1
224        return num
225
226class AccessCodePlugin(grok.GlobalUtility):
227    grok.name('accesscodes')
228    grok.implements(IWAeUPSIRPPluggable)
229
230    def setup(self, site, name, logger):
231        site['accesscodes'] = AccessCodeBatchContainer()
232        logger.info('Installed container for access code batches.')
233        return
234
235    def update(self, site, name, logger):
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.