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

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

When archiving, we don't need to format the student_id.

File size: 7.4 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            if value.invalidation_date is not None:
190                date = value.invalidation_date.strftime(
191                    '%Y-%m-%d-%H-%M-%S')
192            writer.writerow([
193                    self.prefix, value.batch_serial, value.representation,
194                    value.student_id, date
195                    ])
196        return os.path.basename(csv_path)
197
198class AccessCodeBatchContainer(grok.Container):
199    grok.implements(IAccessCodeBatchContainer)
200
201    def addBatch(self, batch):
202        """Add a batch.
203        """
204        batch.num = self.getNum(batch.prefix)
205        key = "%s-%s" % (batch.prefix, batch.num)
206        self[key] = batch
207        self._p_changed = True
208
209    def getNum(self, prefix):
210        """Get next unused num for given prefix.
211        """
212        num = 1
213        while self.get('%s-%s' % (prefix, num), None) is not None:
214            num += 1
215        return num
216
217class AccessCodePlugin(grok.GlobalUtility):
218    grok.name('accesscodes')
219    grok.implements(IWAeUPSIRPPluggable)
220
221    def setup(self, site, name, logger):
222        site['accesscodes'] = AccessCodeBatchContainer()
223        logger.info('Installed container for access code batches.')
224        return
225
226    def update(self, site, name, logger):
227        if not 'accesscodes' in site.keys():
228            logger.info('Updating site at %s. Installing access codes.' % (
229                    site,))
230            self.setup(site, name, logger)
231        else:
232            logger.info(
233                'AccessCodePlugin: Updating site at %s: Nothing to do.' % (
234                    site, ))
235        return
Note: See TracBrowser for help on using the repository browser.