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
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))
[5118]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
[5073]102
[5118]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
[5121]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
[5118]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
[5110]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')
[5118]152        ac_storage = self._getStoragePath()
[5110]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])
[5118]160
161        for value in self._entries:
[5110]162            writer.writerow(
163                [str(value.batch_serial), str(value.representation)]
164                )
[5118]165        site = grok.getSite()
[5112]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)
[5110]171        return os.path.basename(csv_path)
172
[5118]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
[5079]201class AccessCodeBatchContainer(grok.Container):
202    grok.implements(IAccessCodeBatchContainer)
[5073]203
[5079]204    def addBatch(self, batch):
[5086]205        """Add a batch.
206        """
207        batch.num = self.getNum(batch.prefix)
208        key = "%s-%s" % (batch.prefix, batch.num)
[5079]209        self[key] = batch
[5086]210        self._p_changed = True
[5079]211
[5086]212    def getNum(self, prefix):
213        """Get next unused num for given prefix.
214        """
215        num = 1
[5116]216        while self.get('%s-%s' % (prefix, num), None) is not None:
[5086]217            num += 1
218        return num
[5095]219
[5073]220class AccessCodePlugin(grok.GlobalUtility):
221    grok.name('accesscodes')
222    grok.implements(IWAeUPSIRPPluggable)
223
224    def setup(self, site, name, logger):
[5079]225        site['accesscodes'] = AccessCodeBatchContainer()
226        logger.info('Installed container for access code batches.')
227        return
[5073]228
229    def update(self, site, name, logger):
[5107]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.