source: main/waeup.sirp/trunk/src/waeup/sirp/applicants/jambtables/jambtables.py @ 5742

Last change on this file since 5742 was 5722, checked in by uli, 14 years ago
  • Add a jambdata plugin that creates jambdata folder on creation/updates of a waeup.sirp site.
  • Implement some basic root for jambdata.
File size: 7.8 KB
Line 
1##
2## jambtables.py
3## Login : <uli@pu.smp.net>
4## Started on  Tue Jun 22 06:31:42 2010 Uli Fouquet
5## $Id$
6##
7## Copyright (C) 2010 Uli Fouquet
8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published by
10## the Free Software Foundation; either version 2 of the License, or
11## (at your option) any later version.
12##
13## This program is distributed in the hope that it will be useful,
14## but WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16## GNU General Public License for more details.
17##
18## You should have received a copy of the GNU General Public License
19## along with this program; if not, write to the Free Software
20## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21##
22"""WAeUP SIRP support for JAMB tables.
23
24JAMB tables are datasets delivered by JAMB.
25"""
26import csv
27import os
28import shutil
29import tempfile
30import grok
31
32from datetime import datetime
33from BTrees.OOBTree import OOBTree
34from BTrees.Length import Length
35
36from waeup.sirp.interfaces import IWAeUPSIRPPluggable
37from waeup.sirp.applicants.jambtables.interfaces import (
38    IJAMBDataTable, IJAMBDataRoot
39    )
40
41#: The header fields required for a valid JAMB table CSV file.
42JAMB_DATA_HEADERS = [
43    'firstname', 'lastname', 'middlenames', 'screening_type',
44    'screening_venue', 'reg_no', 'sex', 'course1', 'jamb_state',
45    'screening_date', 'jamb_lga', 'fst_sit_fname', 'date_of_birth']
46
47def filter_data(datadict):
48    """Filter items whose key contains 'ignore'
49
50    The function will remove all fields whose name start with
51    ``ignore``. All data will be convertetd to unicode except
52    ``data_of_birth``, which is turned into a `dateteime` object.
53    """
54    keys = datadict.keys()
55    for key in keys:
56        if not 'ignore' in key:
57            continue
58        del datadict[key]
59    for name in JAMB_DATA_HEADERS:
60        datadict[name] = unicode(datadict[name])
61    datadict['date_of_birth'] = datetime.strptime(
62        datadict['date_of_birth'],
63        '%d/%m/%Y'
64        ).date()
65   
66    return datadict
67
68
69class JAMBDataTable(grok.Model):
70    """A data table that contains JAMB data.
71
72    JAMB data tables are plain but fast as they store nearly no data
73    inside the ZODB. All data is held on-disk in CSV tables.
74
75    As a consequence these tables are read-only.
76    """
77    #: Implement :class:`IJAMBDataTable`
78    grok.implements(IJAMBDataTable)
79
80    #: The datetime when data was imported.
81    import_datetime = None
82
83    #: The username of the person that initiated the last import.
84    importer_username = None
85   
86    def __init__(self):
87        super(JAMBDataTable, self).__init__()
88        self._datafile_path = None
89        self._data_len = 0
90        self._temporary = False
91        return
92
93    def __del__(self):
94        self.clear()
95
96    def __iter__(self):
97        reader = None
98        if self._datafile_path is not None:
99            reader = csv.DictReader(open(self._datafile_path, 'rb'))
100        if reader is None:
101            raise StopIteration
102        for line in reader:
103            data_dict = filter_data(line)
104            yield data_dict
105
106    def keys(self):
107        """Get iterator over all registration numbers stored in table.
108        """
109        for item in self:
110            yield item['reg_no']
111
112    def items(self):
113        """Get tuples of registration number and datasets for each entry in
114           data table.
115        """
116        for item in self:
117            yield (item['reg_no'], item)
118           
119    def clear(self):
120        """Remove all existing entries.
121
122        Unsets also the :attr:`import_datetime` and
123        :attr:`importer_username` attributes.
124        """
125        self.import_datetime = None
126        self.importer_username = None
127        if self._datafile_path is None:
128            return
129        if self._temporary:
130            if not os.path.exists(self._datafile_path):
131                return
132            shutil.rmtree(os.path.dirname(self._datafile_path))
133        self._datafile_path = None
134           
135    def importFromCSV(self, filepath, username=None):
136        """Importing data from a CSV file means to copy the source to a safe
137           location.
138
139           If the username is set, it will be stored as well in
140           :attr:`importer_username`.
141        """
142        self.clear()
143        self.import_datetime = datetime.now()
144        self.importer_username = None
145        if username is not None:
146            self.importer_username = unicode(username)
147        self._copyDataFile(filepath)
148
149    def _copyDataFile(self, path):
150        """Copy file in path to the JAMBData storage.
151
152        See :meth:`_getJAMBTableStorage`.
153        """
154        storage = self._getJAMBTableStorage()
155        self._datafile_path = os.path.join(
156            storage, os.path.basename(path)
157            )
158        shutil.copy2(path, self._datafile_path)
159        return
160
161    def _getJAMBTableStorage(self):
162        """Get a path to store copies of datatables.
163
164        We normally store data in a ``jambdata`` subdir of datacenter,
165        but if none exists, we create a temporary dir and set
166        `temporary` to ``True``.
167
168        Any not existent directory is created.
169
170        Note, that temporary dirs will be deleted when the
171        JAMBDataTable object is destroyed.
172
173        Returns absolute path to the JAMB data storage.
174        """
175        site = grok.getSite()
176        if site is None:
177            jambtable_storage = tempfile.mkdtemp()
178            self._temporary = True
179        else:
180            datacenter = site['datacenter']
181            jambtable_storage = os.path.join(datacenter.storage, 'jambdata')
182        if not os.path.isdir(jambtable_storage):
183            os.mkdir(jambtable_storage)
184        return os.path.abspath(jambtable_storage)
185
186class JAMBDataRoot(grok.Container):
187    grok.implements(IJAMBDataRoot)
188   
189class JAMBDataPlugin(grok.GlobalUtility):
190    """A WAeUPSIRPPlugin that creates a JAMB data root in portal.
191
192    This plugin should be called by a typical
193    `waeup.sirp.app.Universtiy` instance on creation time. The
194    :meth:`update` method normally can also be triggered manually over
195    the main site configuration.
196
197    Implements :class:`waeup.sirp.interfaces.IWAeUPSIRPPluggable`
198    """
199    grok.name('jambdata')
200    grok.implements(IWAeUPSIRPPluggable)
201    log_prefix = 'JAMBDataPlugin'
202
203    def setup(self, site, name, logger):
204        """Create a new :class:`ApplicantsRoot` instance in `site`.
205        """
206        site['jambdata'] = JAMBDataRoot()
207        logger.info(
208            '%s: Installed JAMB data root.' % (self.log_prefix,)
209            )
210        return
211
212    def update(self, site, name, logger):
213        """Update site wide ``jambdata`` root.
214
215        If the site already contains a suitable ``jambdata`` root,
216        leave it that way. If not create one and delete the old one if
217        appropriate.
218        """
219        jamb_folder = site.get('jambdata', None)
220        site_name = getattr(site, '__name__', '<Unnamed Site>')
221        if IJAMBDataRoot.providedBy(jamb_folder):
222            # JAMB data storage up to date. Return.
223            logger.info(
224                '%s: Updating site at %s: Nothing to do.' % (
225                    self.log_prefix, site_name,)
226                )
227            return
228        elif jamb_folder is not None:
229            # JAMB data storage needs update. Remove old instance.
230            logger.warn(
231                '%s: Outdated JAMB data folder detected at site %s.'
232                'Removing it.' % (self.log_prefix, site_name)
233                    )
234            del site['jambdata']
235        # Add new applicants.
236        logger.info(
237            '%s: Updating site at %s. Installing '
238            'JAMB data folder.' % (self.log_prefix, site_name,)
239            )
240        self.setup(site, name, logger)
241        return
Note: See TracBrowser for help on using the repository browser.