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

Last change on this file since 5121 was 5121, checked in by uli, 15 years ago

Support adding of single AccessCodes? in batches.

File size: 7.5 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 '%s-%s-%s' % (self.prefix, self.num, pin) in self._acids:
98                # PIN already in use
99                continue
100            curr += 1
101            yield pin
102
103    def _getStoragePath(self):
104        """Get the directory, where we store all batch-related CSV files.
105        """
106        site = grok.getSite()
107        storagepath = site['datacenter'].storage
108        ac_storage = os.path.join(storagepath, 'accesscodes')
109        if not os.path.exists(ac_storage):
110            os.mkdir(ac_storage)
111        return ac_storage
112
113    def entries(self):
114        """Get all entries of this batch as generator.
115        """
116        for x in self._entries:
117            yield x
118           
119    def getAccessCode(self, ac_id):
120        """Get the AccessCode with ID ``ac_id`` or ``KeyError``.
121        """
122        return self._entries[self._acids[ac_id]]
123
124    def addAccessCode(self, num, pin):
125        """Add an access-code.
126        """
127        ac = AccessCode(num, pin)
128        ac.__parent__ = self
129        self._entries.append(ac)
130        self._acids.update({ac.representation: num})
131        return
132
133    def invalidate(self, ac_id, student_id=None):
134        """Invalidate the AC with ID ``ac_id``.
135        """
136        num = self._acids[ac_id]
137        ac = self.getAccessCode(ac_id)
138        ac._invalidation_date = datetime.now()
139        ac.student_id = student_id
140        self.invalidated_num += 1
141
142    def createCSVLogFile(self):
143        """Create a CSV file with data in batch.
144
145        Data will not contain invalidation date nor student ids.  File
146        will be created in ``accesscodes`` subdir of data center
147        storage path.
148
149        Returns name of created file.
150        """
151        date = self.creation_date.strftime('%Y_%m_%d_%H_%M_%S')
152        ac_storage = self._getStoragePath()
153        csv_path = os.path.join(
154            ac_storage, '%s-%s-%s-%s.csv' % (
155                self.prefix, self.num, date, self.creator)
156            )
157        writer = csv.writer(open(csv_path, 'w'), quoting=csv.QUOTE_ALL)
158        writer.writerow(['serial', 'ac', 'cost'])
159        writer.writerow([self.prefix, str(self.num), "%0.2f" % self.cost])
160
161        for value in self._entries:
162            writer.writerow(
163                [str(value.batch_serial), str(value.representation)]
164                )
165        site = grok.getSite()
166        logger = site.logger
167        logger.info(
168            "Created batch %s-%s" % (self.prefix, self.num))
169        logger.info(
170            "Written batch CSV to %s" % csv_path)
171        return os.path.basename(csv_path)
172
173    def archive(self):
174        """Create a CSV file for archive.
175        """
176        ac_storage = self._getStoragePath()
177        now = datetime.now()
178        timestamp = now.strftime('%Y_%m_%d_%H_%M_%S')
179        csv_path = os.path.join(
180            ac_storage, '%s-%s_archive-%s-%s.csv' % (
181                self.prefix, self.num, timestamp, self.creator)
182            )
183        writer = csv.writer(open(csv_path, 'w'), quoting=csv.QUOTE_ALL)
184        writer.writerow(['prefix', 'serial', 'ac', 'student', 'date'])
185        writer.writerow([self.prefix, '%0.2f' % self.cost, str(self.num),
186                         str(self.entry_num)])
187        for value in self._entries:
188            date = ''
189            stud_id = ''
190            if value.invalidation_date is not None:
191                date = value.invalidation_date.strftime(
192                    '%Y-%m-%d-%H-%M-%S')
193            if stud_id is not None:
194                stud_id = value.student_id
195            writer.writerow([
196                    self.prefix, value.batch_serial, value.representation,
197                    stud_id, date
198                    ])
199        return os.path.basename(csv_path)
200
201class AccessCodeBatchContainer(grok.Container):
202    grok.implements(IAccessCodeBatchContainer)
203
204    def addBatch(self, batch):
205        """Add a batch.
206        """
207        batch.num = self.getNum(batch.prefix)
208        key = "%s-%s" % (batch.prefix, batch.num)
209        self[key] = batch
210        self._p_changed = True
211
212    def getNum(self, prefix):
213        """Get next unused num for given prefix.
214        """
215        num = 1
216        while self.get('%s-%s' % (prefix, num), None) is not None:
217            num += 1
218        return num
219
220class AccessCodePlugin(grok.GlobalUtility):
221    grok.name('accesscodes')
222    grok.implements(IWAeUPSIRPPluggable)
223
224    def setup(self, site, name, logger):
225        site['accesscodes'] = AccessCodeBatchContainer()
226        logger.info('Installed container for access code batches.')
227        return
228
229    def update(self, site, name, logger):
230        if not 'accesscodes' in site.keys():
231            logger.info('Updating site at %s. Installing access codes.' % (
232                    site,))
233            self.setup(site, name, logger)
234        else:
235            logger.info(
236                'AccessCodePlugin: Updating site at %s: Nothing to do.' % (
237                    site, ))
238        return
Note: See TracBrowser for help on using the repository browser.