# -*- mode: python; mode: fold; -*-
# (C) Copyright 2005 The WAeUP group  <http://www.waeup.org>
# Author: Joachim Schmitz (js@aixtraware.de)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
#
# $Id: WAeUPTool.py 4558 2009-12-16 09:56:53Z henrik $
"""The WAeUP Tool Box.
"""

from AccessControl import ClassSecurityInfo
from Acquisition import aq_inner
from Acquisition import aq_parent
from Globals import DTMLFile
from Globals import InitializeClass
from OFS.SimpleItem import SimpleItem
from zExceptions import BadRequest

from Products.CMFCore.utils import getToolByName
from Products.CPSSchemas.DataStructure import DataStructure
from Products.CPSSchemas.DataModel import DataModel
from Products.CPSSchemas.StorageAdapter import MappingStorageAdapter
from Products.CMFCore.ActionProviderBase import ActionProviderBase
from Products.CMFCore.permissions import View
from Products.ZCatalog.ZCatalog import ZCatalog
from Products.CMFCore.permissions import ModifyPortalContent
from Products.CMFCore.permissions import ManagePortal
from Products.CMFCore.utils import UniqueObject
from Products.CMFCore.URLTool import URLTool
from Products.CMFCore.utils import getToolByName
from Students import makeCertificateCode
from Globals import package_home,INSTANCE_HOME
from WAeUPImport import ApplicationImport,CertificateImport,CertificateCourseImport
from WAeUPImport import CourseImport,CourseResultImport,StudentStudyLevelImport,PaymentImport
from WAeUPImport import DepartmentImport,FacultyImport,StudentImport,VerdictImport
from utils import makeDigest
import DateTime,time
import logging
import transaction
import csv,re,os,sys
import md5
from shutil import copy2,copy
from Products.AdvancedQuery import Eq, Between, Le,In

p_home = package_home(globals())
i_home = INSTANCE_HOME
images_base = os.path.join(i_home,"images")
EMPTY = 'XXX'

def getImagesDir(student_id):
    return os.path.join("%s" % images_base,student_id[0],student_id)

def getObject(object,name):
    if object.hasObject(name):
        return getattr(object,name)
    return None

class WAeUPTool(UniqueObject, SimpleItem, ActionProviderBase):
    """WAeUP tool"""

    id = 'waeup_tool'
    meta_type = 'WAeUP Tool'
    _actions = ()
    security = ClassSecurityInfo()
    security.declareObjectProtected(View)
    manage_options = ( ActionProviderBase.manage_options
                     + SimpleItem.manage_options
                     )

    security.declareProtected(View,'re_split') ###(
    def re_split(self,split_string,string):
        return re.split(split_string,string)
    ###)

    security.declareProtected(View,'difference') ###(
    def difference(self,l1,l2):
        return set(l1).difference(set(l2))
    ###)

    def rwrite(self,s): ###(
        response = self.REQUEST.RESPONSE
        response.setHeader('Content-type','text/html; charset=ISO-8859-15')
        response.write("%s<br />\r\n" % s)
    ###)

    def addtodict(self,d,key,item): ###(
        d[key].append(item)
        return d[key]
    ###)

    def sleep(self,secs): ###(
        "sleep"
        import time
        time.sleep(secs)
        return
    ###)

    security.declareProtected(View,'updateRoleMappingsFor') ###(
    def updateRoleMappingsFor(self,wf_definition,ob):
        "do so for public"
        wf_def = getattr(self.portal_workflow,wf_definition)
        wf_def.updateRoleMappingsFor(ob)
    ###)

    security.declareProtected(View,'getStatesLgas') ###(
    def getStatesLgas(self):
        """return lga info"""
        voc = getattr(self.portal_vocabularies,'local_gov_areas')
        states = []
        lgas  = []
        d = {}
        wd = {}
        for k,v in voc.items():
            parts = v.split(' / ')
            if len(parts) == 1:
                state = parts[0].lower()
                lga = ""
            elif len(parts) == 2:
                state = "_".join(re.split('[^a-zA-Z0-9/]',parts[0].lower()))
                lga = "-".join(re.split('[^a-zA-Z0-9/]',parts[1].lower()))
            else:
                continue
            if state not in states:
                states.append(state)
            if lga not in lgas:
                lgas.append(lga)
            words = re.split('[^a-zA-Z0-9/]',k)
            words.sort()
            wd[k] = words
            d[k] = v
        mapping = {}
        mapping['word_dict'] = wd
        mapping['lga_dict'] = d
        mapping['states'] = states
        mapping['lgas'] = lgas
        return mapping
    ###)

    security.declareProtected(View,'findLga') ###(
    def findLga(self,words,words_dict):
        words = re.split('[^a-zA-Z0-9/]',words)
        lga_words = []
        for word in words:
            if word:
                lga_words += word.strip().lower(),
        lga_words.sort()
        state_lga = ''
        while not state_lga:
            for k,l in words_dict.items():
                if lga_words == l:
                    state_lga = k
                    break
            break
        return state_lga
    ###)
    security.declareProtected(View,'getAccessInfo') ###(
    def getAccessInfo(self,context):
        "return a dict with access_info"
        logger = logging.getLogger('WAeUPTool.getAccessInfo')
        mtool = self.portal_membership
        member = mtool.getAuthenticatedMember()
        member_id = str(member)
        info = {}
        is_anonymous = info['is_anonymous'] = mtool.isAnonymousUser()
        is_student = info['is_student'] = ord(member_id[1]) > 48 and ord(member_id[1]) <= 57
        is_staff = info['is_staff'] = not is_anonymous and not is_student
        roles = member.getRolesInContext(context)
        info['is_sectionofficer'] = not is_student and ("SectionOfficer" in roles or
                                                        "SectionManager" in roles or
                                                        "Manager" in roles)
        info['is_clearanceofficer'] = not is_student and ("ClearanceOfficer" in roles)
        #is_allowed = info['is_allowed'] = not is_anonymous
        requested_id = context.getStudentId()
        student_id  = None
        if not is_anonymous and requested_id:
            if (is_student and member_id == requested_id) or is_staff:
                student_id = requested_id
            else:    
                logger.info('%s tried to access %s of %s' % (member_id,context.portal_type,requested_id))
        info['student_id'] = student_id
        return info
    ###)

    security.declareProtected(ModifyPortalContent,'openLog') ###(
    def openLog(self,name):
        """open a log file"""
        version = 1
        path = "%s/log/%s_%d.log" % (i_home,name,version)
        while os.path.exists(path):
            version += 1
            path = "%s/log/%s_%d.log" % (i_home,name,version)
        log = open(path,"w")
        return log
    ###)

    security.declareProtected(ModifyPortalContent,'bypassQueueCatalog') ###(
    def bypassQueueCatalog(self,enable=True):
        """bypass the QueueCatalog by setting all indexes to process imediate,
        if enable is True (default) the old settings are restored
        """

    ###)

    security.declareProtected(ModifyPortalContent,'measureOaT') ###(
    def measureOaT(self,method="a",probe="1000",nr_pts="1"):
        """measure Object access Time"""
        import random
        if hasattr(self,'portal_catalog_real'):
            aq_portal = self.portal_catalog_real.evalAdvancedQuery
        else:
            aq_portal = self.portal_catalog.evalAdvancedQuery
        nr_pts = int(nr_pts)
        probe = int(probe)
        intervall = probe/10
        objects = ("application","clearance","personal")
        portal_types = ("StudentApplication","StudentClearance","StudentPersonal")
        #i = random.randrange(num_objects)
        count = 0
        found = 0
        not_found = 0
        t_found = 0
        t_not_found = 0
        time_found = time_not_found = 0.0
        t_time_found = t_time_not_found = 0.0
        accessed = []
        t_min = 1000
        t_max = 0
        #import pdb;pdb.set_trace()
        students = self.portal_catalog(portal_type="Student")
        num_students = len(students)
        if method == "d":
            query = Eq('path','/uniben/campus/students') & In('portal_type',portal_types[:nr_pts])
            res = aq_portal(query)
            brains = {}
            for r in res:
                sid = r.relative_path.split('/')[-2]
                if brains.has_key(sid):
                    brains[sid][r.portal_type] = r
                else:
                    brains[sid] = {r.portal_type : r}
            brains_list = brains.keys()
            num_objects = len(brains_list)
        else:
            num_objects = num_students
        print "="*40
        print "method: %s probes: %d nr_pts: %d num_objects: %d" % (method,
                                                                        probe,
                                                                        nr_pts,
                                                                        num_objects)
        print "nr found/not time found/not min/max"
        elapse = time.time()
        i_elapse = time.time()
        c_elapse = time.clock()
        for c in range(1,probe + 1):
            i = random.randrange(num_objects)
            if method in ('a','b','c'):
                student_brain = students[i]
            elif method == "d":
                #import pdb;pdb.set_trace()
                student_brain = brains[brains_list[i]]
            if method == "c":
                query = Eq('path',student_brain.getPath()) & In('portal_type',portal_types[:nr_pts])
                res = aq_portal(query)
                this_portal_types = [r.portal_type for r in res]
            for i in range(nr_pts):
                oid = objects[i]
                if method == "a":
                    try:
                        student_path = student_brain.getPath()
                        path = "%s/%s" % (student_path,oid)
                        doc = self.unrestrictedTraverse(path).getContent()
                        found += 1
                        i_time = time.time() - i_elapse
                        time_found += i_time
                    except:
                        not_found += 1
                        i_time = time.time() - i_elapse
                        time_not_found += i_time
                        pass
                elif method == "b":
                    try:
                        student_object = student_brain.getObject()
                        doc = getattr(student_object,oid).getContent()
                        found += 1
                        i_time = time.time() - i_elapse
                        time_found += i_time
                    except:
                        i_time = time.time() - i_elapse
                        time_not_found += i_time
                        not_found += 1
                        pass
                elif method == "c":
                    if portal_types[i] in this_portal_types:
                        found += 1
                        doc = res[this_portal_types.index(portal_types[i])].getObject().getContent()
                        i_time = time.time() - i_elapse
                        time_found += i_time
                    else:
                        not_found += 1
                        i_time = time.time() - i_elapse
                        time_not_found += i_time
                elif method == "d":
                    if student_brain.has_key(portal_types[i]):
                        found += 1
                        doc = student_brain[portal_types[i]].getObject().getContent()
                        i_time = time.time() - i_elapse
                        time_found += i_time
                    else:
                        not_found += 1
                        i_time = time.time() - i_elapse
                        time_not_found += i_time
                i_elapse = time.time()
            if c and (c % intervall == 0):
                #i_time = time.time() - i_elapse
                t_per = 0.0
                if found:
                    t_per = time_found/found
                if t_per > t_max:
                    t_max = t_per
                if t_per > 0.0 and t_per < t_min:
                    t_min = t_per
                itf = 0.0
                if found:
                    itf = time_found/found
                itnf = 0.0
                if not_found :
                    itnf = time_not_found / not_found
                interval_time = time_found + time_not_found
                s = "%(c)d: %(found)d/%(not_found)d " % vars()
                s += "%(interval_time)6.2f %(itf)6.4f/%(itnf)6.4f " % vars()
                s += "%(t_min)6.4f/%(t_max)6.4f" %  vars()
                print s
                t_found += found
                t_not_found += not_found
                t_time_found += time_found
                t_time_not_found += time_not_found
                time_found = time_not_found = 0.0
                found = not_found = 0
        # t_found += found
        # t_not_found += not_found
        elapse = time.time() - elapse
        itf = 0.0
        if t_found:
            itf = t_time_found/t_found
        itnf = 0.0
        if t_not_found:
            itnf = t_time_not_found / t_not_found
        #c_elapse = time.clock() - c_elapse
        s = "%(probe)d: %(t_found)d/%(t_not_found)d " % vars()
        s += "%(elapse)6.2f %(itf)6.4f/%(itnf)6.4f " % vars()
        s += "%(t_min)6.4f/%(t_max)6.4f" %  vars()
        print "-"*40
        print s
        rel_found = float(t_found)/probe
        rel_not_found = float(t_not_found)/probe
        estimated_total_time = num_objects*(rel_found*itf + rel_not_found*itnf)
        print estimated_total_time
    ###)

    security.declareProtected(ModifyPortalContent,'writeLog') ###(
    def writeLog(self,logfile,s):
        """write to the log file"""
        logfile.write(s)
    ###)

    def generateStudentId(self,letter): ###(
        import random
        logger = logging.getLogger('WAeUPTool.generateStudentId')
        r = random
        if letter == '?':
            letter= r.choice('ABCDEFGHKLMNPQRSTUVWXY')
        sid = "%c%d" % (letter,r.randint(99999,1000000))
        students = self.portal_url.getPortalObject().campus.students
        while self.students_catalog(id = sid) or self.waeup_tool.picturePathExists(sid) or self.removed_student_ids(id = sid):
            if self.waeup_tool.picturePathExists(sid):
                logger.info('picture path %s exists or in ' % sid)
            sid = "%c%d" % (letter,r.randint(99999,1000000))
        return sid
    ###)

    def generatePassword(self,s=None): ###(
        import random
        r = random
        ##if letter not in ('ABCDEFGIHKLMNOPQRSTUVWXY'):
        if s is None:
            s = 'abcdefghklmnpqrstuvwxy23456789'
        pw = ''
        while len(pw) < 6:
            pw += r.choice(s)
        return pw
    ###)

    security.declareProtected(ModifyPortalContent, 'dumpSchoolfeePayments') ###(
    def dumpSchoolfeePayments(self):
        "dump paid schoolfees"
        mtool = self.portal_membership
        member = mtool.getAuthenticatedMember()
        logger = logging.getLogger('WAeUPTool.dumpSchoolfees')
        aq_student = self.students_catalog.evalAdvancedQuery
        query = In('review_state',('schoolfee_paid',
                                   'courses_registered',
                                   'courses_validated',
                                   ))
        res = aq_student(query)
        #import pdb;pdb.set_trace()
        l = []
        logger.info("start for %d" % len(res))
        count = 1
        log_after = 100
        for student in res:
            if not count % log_after:
                logger.info("processed %d total %d" % (log_after,count))
            count += 1
            fee_dict =self.getSchoolFee(student)
            fulltime = student.mode.endswith('_ft')
            d = {}
            d['student_id'] = student.id
            d['name'] = student.name
            d['amount'] = fee_dict.get(new_returning)
            l += d,
        csv_name = self.dumpListToCSV(l,'payments')
        logger.info('%s dumped payments to %s' % (member,export_file))
    ###)

    security.declarePublic('dumpListToCSV') ###(
    def dumpListToCSV(self,l,filename,fields=None):
        """dump a list of dicts to a CSV file"""
        current = DateTime.DateTime().strftime("%d-%m-%y_%H_%M_%S")
        export_file = "%s/export/%s_%s.csv" % (i_home,filename,current,)
        if fields is None:
            fields = l[0].keys()
        headline = ','.join(fields)
        out = open(export_file,"wb")
        out.write(headline +'\n')
        out.close()
        out = open(export_file,"a")
        csv_writer = csv.DictWriter(out,fields,)
        csv_writer.writerows(l)
        return export_file
    ###)

    security.declareProtected(ManagePortal, 'listMembers') ###(
    def listMembers(self):
        "list all members"
        mtool = self.portal_membership
        member = mtool.getAuthenticatedMember()
        logger = logging.getLogger('WAeUPTool.listMembers')
        if str(member) not in ('admin','joachim'):
            logger.info('%s tried to list members' % (member))
            return None
        members = self.portal_directories.members
        all = members.listEntryIdsAndTitles()
        l = []
        for user_id,name in all:
            d = {}
            d['user_id'] = user_id
            d['name'] = name
            d['pw'] = getattr(getattr(members,user_id),'password')
            d['email'] = getattr(getattr(members,user_id),'email')
            d['groups'] = " ".join(getattr(getattr(members,user_id),'groups'))
            d['roles'] = " ".join(getattr(getattr(members,user_id),'roles'))
            l += d,
        current = DateTime.DateTime().strftime("%d-%m-%y_%H_%M_%S")
        export_file = "%s/export/member_list_%s.csv" % (i_home,current,)
        logger.info('%s dumped member list to %s' % (member,export_file))
        fields = l[0].keys()
        headline = ','.join(fields)
        out = open(export_file,"wb")
        out.write(headline +'\n')
        out.close()
        out = open(export_file,"a")
        csv_writer = csv.DictWriter(out,fields,)
        csv_writer.writerows(l)
    ###)

    security.declareProtected(ManagePortal, 'listStudents') ###(
    def listStudents(self):
        "list all students"
        mtool = self.portal_membership
        member = mtool.getAuthenticatedMember()
        logger = logging.getLogger('WAeUPTool.listStudents')
        if str(member) not in ('admin','joachim'):
            logger.info('%s tried to list students' % (member))
            return None
        students = self.portal_directories.students
        all = students.listEntryIdsAndTitles()
        l = []
        for user_id,name in all:
            d = {}
            d['user_id'] = user_id
            d['name'] = name
            d['pw'] = getattr(getattr(students,user_id),'password')
            d['email'] = getattr(getattr(students,user_id),'email')
            d['groups'] = " ".join(getattr(getattr(students,user_id),'groups'))
            d['roles'] = " ".join(getattr(getattr(students,user_id),'roles'))
            l += d,
        current = DateTime.DateTime().strftime("%d-%m-%y_%H_%M_%S")
        export_file = "%s/export/student_list_%s.csv" % (i_home,current,)
        logger.info('%s dumped student list to %s' % (member,export_file))
        fields = l[0].keys()
        headline = ','.join(fields)
        out = open(export_file,"wb")
        out.write(headline +'\n')
        out.close()
        out = open(export_file,"a")
        csv_writer = csv.DictWriter(out,fields,)
        csv_writer.writerows(l)
    ###)

    security.declareProtected(ManagePortal, 'removeDeletedDocIds') ###(
    def removeDeletedDocIds(self, max=1000):
        """
        remove deleted docids from repository commit after max
        """
        logger = logging.getLogger('WAeUPTool.removeDeletedDocIds')
        repository = getToolByName(self, 'portal_repository')
        pxtool = getToolByName(self, 'portal_proxies')
        logger.info('start')
        pxtool_infos = pxtool.getRevisionsUsed()
        logger.info('found  %d used revisions ' % (len(pxtool_infos)))

        nb_revs = 0
        docids_d = {} # all docids
        unused_docids_d = {} # all docids that are unused
        ids_unused_revs_docids = [] # ids for revs of unused docids
        unused_ids = [] # ids for unused revs
        total = 0
        idlist = repository.objectIds()
        to_delete = 0
        found = False
        for id in idlist:
            docid, rev = repository._splitId(id)
            if docid is None:
                logger.info("invalid doc_id %s" % docid)
                continue
            nb_revs += 1
            if not pxtool_infos.has_key(docid):
                found = True
                to_delete += 1
                unused_ids.append(id)
            elif not pxtool_infos[docid].has_key(rev):
                found = True
                to_delete += 1
                unused_ids.append(id)
            if found and not to_delete % max:
                found = False
                #import pdb;pdb.set_trace()
                repository.manage_delObjects(unused_ids)
                transaction.commit()
                logger.info('removed %d total %d unused docids ' % (max,to_delete))
        else:
            if unused_ids:
                repository.manage_delObjects(unused_ids)
                transaction.commit()
        logger.info('finished removing %d unused docids ' % (to_delete))
    ###)

    security.declarePublic('getCredential') ###(
    def getCredential(self,student_id):
        student_entry = getattr(self.portal_directories.students,student_id,None)
        #import pdb;pdb.set_trace()
        #if not self.isStaff():
        #    mtool = self.portal_membership
        #    member = mtool.getAuthenticatedMember()
        #    logger = logging.getLogger('WAeUPTool.getCredential')
        #    logger.info('%s tried to access password of %s' % (member,student_id))
        #    return None
        if student_entry is None:
            return "not set"
        return getattr(student_entry,"password","not set")
    ###)

    security.declarePublic('checkPassword') ###(
    def checkPassword(self,student_id,password):
        student_entry = getattr(self.portal_directories.students,student_id,None)
        if student_entry is None:
            return False
        return getattr(student_entry,"password","not set") == password
    ###)

    security.declarePublic('checkGenericPassword') ###(
    def checkGenericPassword(self,member_id):
        member_entry = getattr(self.portal_directories.members,member_id,None)
        if member_entry is None:
            return False
        ltool = getToolByName(self, 'portal_layouts')
        unsecure_words = ltool._getOb('members')['w__password'].check_words
        password = getattr(member_entry,"password","not set")
        is_unsecure = password in unsecure_words
        if is_unsecure:
            logger = logging.getLogger('WAeUPTool.checkGenericPassword')
            logger.info('Member %s tried to log in with unsecure password %s' %(member_id,password))
        return is_unsecure
    ###)

    security.declarePublic('editPassword') ###(
    def editPassword(self,student_id,password):
        "edit a student password"
        student_entry = getattr(self.portal_directories.students,student_id,None)
        if student_entry is None:
            return
        setattr(student_entry,'password',password)
    ###)

    security.declareProtected(ModifyPortalContent,'doCommit') ###(
    def doCommit(self,logger=None):
        "commit some transactions"
        transaction.commit()
    ###)

    security.declarePublic('loadStudentFoto') ###(
    def loadStudentFoto(self,student,filename,folder):
        "return a student passport picture"
        #import pdb;pdb.set_trace()
        picture ="%s/import/%s/%s" % (i_home,folder,filename)
        student_id = student.getId()
        images_dir = getImagesDir(student_id)
        if not os.path.exists(images_dir):
            os.mkdir(images_dir)
        image_name = os.path.join(images_dir,"passport_%(student_id)s.jpg" % vars())
        for extension in ('.jpg','.JPG'):
            fullname = "%(picture)s%(extension)s" % vars()
            if os.path.exists(fullname):
                copy2(fullname,image_name)
                return "successfully copied passport picture"
        return "passport picture not found: %s.jpg or .JPG" % picture
    ###)


    security.declareProtected(ModifyPortalContent,'createOne') ###(
    def createOne(self,students_folder,student_brain,letter,commit=False):
        sid = self.waeup_tool.generateStudentId(letter)
        students_folder.invokeFactory('Student', sid)
        student = getattr(students_folder,sid)
        self.portal_workflow.doActionFor(student,'return')
        student.manage_setLocalRoles(sid, ['Owner',])
        matric_no = student_brain.matric_no
        jamb_reg_no = student_brain.Entryregno
        self.students_catalog.addRecord(id = sid,
                                           matric_no = matric_no,
                                           jamb_reg_no = jamb_reg_no,
                                           sex = student_brain.Sex == "F",
                                           name = "%s %s %s" % (student_brain.Firstname,
                                                                student_brain.Middlename,
                                                                student_brain.Lastname)
                                        )
        if commit:
            transaction.commit()
        return sid,jamb_reg_no
    ###)

    # Functions without docstring cannot be called from outside (via the URL), 
    # thus we can declare addStudent public.
    security.declarePublic('addStudent') ###(
    def addStudent(self,dict):
        students_folder = self.portal_url.getPortalObject().campus.students
        sid = self.waeup_tool.generateStudentId('?')
        students_folder.invokeFactory('Student', sid)
        student_obj = getattr(students_folder,sid)
        f2t = StudentImport.field2types_student
        #from pdb import set_trace; set_trace()
        d = {}
        #d['jamb_sex']  = 'M'
        #if dict.get('sex'):
        #    d['jamb_sex']  = 'F'

        entry_session = dict.get('entry_session')
        if entry_session == self.getSessionId()[0]:
            wfaction = 'admit'
            wft = 'wf_transition_admit'
            password = None
        else:
            wfaction = 'return'
            wft = 'wf_transition_return'
            password = self.generatePassword()
            self.makeStudentMember(sid,password)

        for pt in f2t.keys():
            student_obj.invokeFactory(pt,f2t[pt]['id'])
            sub_obj = getattr(student_obj,f2t[pt]['id'])
            sub_doc = sub_obj.getContent()
            #self.portal_workflow.doActionFor(sub_obj,'open',dest_container=sub_obj)
            #d['Title'] = f2t[pt]['title']
            for field in f2t[pt]['fields']:
                d[field] = dict.get(field,'')
            sub_doc.edit(mapping = d)
            new_state = f2t[pt][wft]
            if new_state != "remain":
                self.portal_workflow.doActionFor(sub_obj,new_state,dest_container=sub_obj)
        self.portal_workflow.doActionFor(student_obj,wfaction)
        student_obj.manage_setLocalRoles(sid, ['Owner',])
        return sid,password
    ###)

    security.declarePublic('getCertificateBrain') ###(
    def getCertificateBrain(self,cert_id):
        "do it"
        res = ZCatalog.searchResults(self.portal_catalog_real,
                                {'portal_type':"Certificate",
                                      'id': cert_id})
        if res:
            return res[0]
        return None
    ###)

    security.declareProtected(ModifyPortalContent,'get_csv_filenames') ###(
    def get_csv_filenames(self):
        "do it"
        files = [file for file in os.listdir("%s/import/" % (i_home))
                 if file.endswith('.csv') and (file.find('imported') == -1 and
                                               file.find('pending') == -1)]
        return files
    ###)

    security.declarePublic('findStudentByMatricelNo') ###(
    def findStudentByMatricelNo(self,matric_no):
        "do it"
        res = ZCatalog.searchResults(self.portal_catalog_real,
                                {'portal_type':"StudentClearance",
                                 'SearchableText': matric_no})
        if res:
            return res[0]
        return None
    ###)


    security.declarePublic('getOfficerName') ###(
    def getOfficerName(self,mid):
        """get the real name of a member"""
        membership = self.portal_membership
        member_record = membership.getMemberById(mid)
        return member_record.getProperty('fullname',None)
    ###)


    security.declarePublic('makeStudentMember') ###(
    def makeStudentMember(self,sid,password='uNsEt'):
        """make the student a member"""
        membership = self.portal_membership
        membership.addMember(sid,
                             password ,
                             roles=('Member',
                                     'Student',
                                     ),
                             domains='',
                             properties = {'memberareaCreationFlag': False,
                                           'homeless': True},)
        member = membership.getMemberById(sid)
        self.portal_registration.afterAdd(member, sid, password, None)
        #self.manage_setLocalRoles(sid, ['Owner',])
    ###)

    security.declareProtected(View,'makeStudentData') ###(
    def makeStudentData(self,student_id,email=None,phone_nr=None):
        "create Datastructure for a returning Student"
        #import pdb;pdb.set_trace()
        logger = logging.getLogger('WAeUPTool.makeStudentData')
        students_folder = self.portal_url.getPortalObject().campus.students
        #res = self.students_catalog(id=student_id)
        #if res:
        #    st = res[0]
        #res = self.returning_import(matric_no = st.matric_no)
        res = self.returning_import(id = student_id)
        if res:
            student = res[0]
        else:
            logger.info('Id %s not found in returning_import' % student_id)
            return
        logger.info('%s creates data structure' % student_id)
        s_results = self.results_import(matric_no = student.matric_no)
        if s_results:
            lnr = self.getLevelFromResultsCosCode(s_results)
            level = "%d00" % lnr
            verdict,eligible = self.getVerdict(s_results[0].Verdict)
            #if eligible:
            #    level = "%d00" % (lnr + 1)
        else:
            logger.info('matric_no %s not found in results_import' % student.matric_no)
            level = ''
            verdict = ''
        #student should not be allowed to perform this transition
        #wftool = self.portal_workflow
        #wftool.doActionFor(student,'return')
        certcode_org = student.Coursemajorcode
        certcode = makeCertificateCode(certcode_org)
        certificate_brain = self.getCertificateBrain(certcode)
        if not certificate_brain:
            em = 'Certificate %s org-code %s not found\n' % (certcode, certcode_org)
            logger.info(em)
        matric_no = student.matric_no
        sid = student_id
        student_obj = getattr(students_folder,sid)
        if not getattr(student_obj,'application'):
            student_obj.invokeFactory('StudentApplication','application')
        application = student_obj.application
        self.portal_workflow.doActionFor(application,'open',dest_container=application)
        da = {'Title': 'Application Data'}
        student_obj.invokeFactory('StudentPersonal','personal')
        da['jamb_reg_no'] = student.Entryregno
        em = self.getEntryMode(student.Entryregno)
        da['entry_mode'] = em
        personal = student_obj.personal
        self.portal_workflow.doActionFor(personal,'open',dest_container=personal)
        dp = {'Title': 'Personal Data'}
        student_obj.invokeFactory('StudentClearance','clearance')
        clearance = student_obj.clearance
        self.portal_workflow.doActionFor(clearance,'open',dest_container=clearance)
        dc = {'Title': 'Clearance/Eligibility Record'}
        dc['matric_no'] = matric_no
        state = student.State
        lga = student.LGA
        if state and lga:
            lga =  state + ' / ' + lga
        else:
            lga = "None"
        da['jamb_lga'] = dc['lga'] = lga
        da['app_email'] = dp['email'] = email
        da['app_mobile'] = dp['phone'] = phone_nr
        dp['firstname'] = student.Firstname
        dp['middlename'] = student.Middlename
        dp['lastname'] = student.Lastname
        da['jamb_lastname'] = "%s %s %s" % (student.Firstname,student.Middlename,student.Lastname)
        da['jamb_sex'] = student.Sex
        dp['sex'] = student.Sex == 'F'
        dp['perm_address'] = student.Permanent_Address
        application.getContent().edit(mapping=da)
        self.portal_workflow.doActionFor(application,'close',dest_container=application)
        personal.getContent().edit(mapping=dp)
        clearance.getContent().edit(mapping=dc)
        self.portal_workflow.doActionFor(clearance,'close',dest_container=clearance)
        #
        # Study Course
        #
        student_obj.invokeFactory('StudentStudyCourse','study_course')
        studycourse = student_obj.study_course
        self.portal_workflow.doActionFor(studycourse,'open',dest_container=studycourse)
        dsc = {}
        dsc['study_course'] = certcode
        dsc['current_level'] = level
        dsc['current_verdict'] = verdict
        dsc['current_mode'] = em   #no longer used
        dsc['current_session'] = '05'
        studycourse.getContent().edit(mapping=dsc)
        #
        # Level
        #
        # l = getattr(studycourse,level,None)
        # if l is None:
        #     studycourse.invokeFactory('StudentStudyLevel', level)
        #     l = getattr(studycourse, level)
        #     self.portal_workflow.doActionFor(l,'open',dest_container=l)
        #     l.getContent().edit(mapping={'Title': "Level %s" % level})
        ###)

    def init_timing(self): ###(
        if self.with_timing:
            if not hasattr(self,'_v_step_times'):
                self._v_timer_count = 0
                self._v_total = 0
                self._v_step_times = {}
                current = DateTime.DateTime().strftime("%d-%m-%y_%H_%M_%S")
                self._v_timer_file = "%s/export/timing_%s.csv" % (i_home,current,)
            self.timer_step = 0
            self.total_time = 0
            self.elapse = time.time()
            self.i_elapse = time.time()
            self.c_elapse = time.clock()
    ###)

    def do_timing(self): ###(
        if self.with_timing:
            try:
                raise 'dummy'
            except:
                frame = sys.exc_traceback.tb_frame.f_back
                locals = frame.f_locals
                globals = frame.f_globals
                functionname = frame.f_code.co_name
                filename = os.path.basename(frame.f_code.co_filename)
                lineno = frame.f_lineno
                mod_line = "%(functionname)s:%(lineno)s" % vars()
            i_time = time.time() - self.i_elapse
            td = {}
            if self._v_step_times.has_key(mod_line):
                a_time = self._v_step_times[mod_line]['a_time'] + i_time
                td['a_time'] = a_time
            else:
                td['a_time'] = i_time
            td['i_time'] = i_time
            self._v_step_times[mod_line] = td
            self.i_time = i_time
            self.total_time += i_time
            self.timer_step +=1
            self.i_elapse = time.time()
    ###)

    security.declareProtected(ModifyPortalContent,'print_timing') ###( ###(
    def print_timing(self):
        if self.with_timing:
            l = []
            timer_count = self._v_timer_count + 1
            mod_lines = self._v_step_times.keys()
            mod_lines.sort(cmp,reverse=0)
            for mod_line in mod_lines:
                td = self._v_step_times[mod_line]
                i_time = td['i_time']
                a_time = td['a_time']/(self._v_timer_count + 1)
                l += ("%(mod_line)s,%(i_time)6.2f,%(a_time)6.2f,%(timer_count)d" % vars()),
            total_time = self.total_time
            total_avarage = self._v_total / timer_count
            l += ("total,%(total_time)6.4f,%(total_avarage)6.4f,%(timer_count)d" % vars()),
            print "\r\n".join(l)
            out = open(self._v_timer_file,'a')
            out.write("\r\n".join(l))
            out.close()
    ###)

    security.declareProtected(ModifyPortalContent,'get_timing_data') ###( ###(
    def get_timing_data(self):
        if self.with_timing:
            timer_count = self._v_timer_count + 1
            results = {}
            for k,d in self._v_step_times.items():
                dd = {}
                dd['a_time'] = d['a_time']/timer_count
                dd['i_time'] = d['i_time']
                dd['count'] = timer_count
                results[k] = dd
            dd = {}
            dd['a_time'] = self._v_total / timer_count
            dd['i_time'] = self.total_time
            dd['count'] = timer_count
            results["total"] = dd
            return results
    ###)

    security.declareProtected(ModifyPortalContent,'admitOneStudent') ###(
    def admitOneStudent(self,brain,entry_session,pin_password,with_timing=False):
        "create Datastructure for an admitted Student"
        #import pdb;pdb.set_trace()
        logger = logging.getLogger('WAeUPTool.admitOneStudent')
        self.with_timing = with_timing

        try:
            if brain.screening_type in ('cest','sandwich',) and brain.serial and brain.entry_session:
                if brain.course1:
                    reg_no = "%s%s/%s" % (brain.course1[:3],brain.serial,brain.entry_session)
                else:
                    reg_no = "%s%s/%s" % (brain.course_admitted[:3],brain.serial,brain.entry_session)
            else:
                reg_no = brain.reg_no
        except:
            logger.info('applicant record %s has errors' % (brain.reg_no))
            return

        #ignore argument entry_session
        if not brain.entry_session:
            logger.info('no entry_session for %s provided' % (reg_no))
            return
        else:
            es = brain.entry_session
        if len(es) == 1:
            es = '0%c' % es

        if not hasattr(self,"_v_certificates"):
            self._v_certificates = self.getCertificatesDict()
        students_folder = self.portal_url.getPortalObject().campus.students

        res = self.students_catalog(jamb_reg_no = reg_no)
        if res:
            logger.info('student with reg_no %s exists (%s)' % (reg_no,res[0].id))
            return
        if brain.status != "admitted":
            logger.info('status of %s is %s' % (reg_no,brain.status))
            return
        pin_parts = brain.pin.split('-')
        if pin_parts and len(pin_parts) != 3:
            logger.info('invalid pin %s for %s' % (brain.pin,reg_no))
            return
        if not brain.course_admitted:
            logger.info('no course_admitted provided for %s' % (reg_no))
            return            
        if brain.course_admitted not in self._v_certificates:
            logger.info('certificate %s not found for %s' % (brain.course_admitted,reg_no))
            return
        if brain.sex not in (True,False):
            logger.info('sex of %s not available' % (reg_no))
            return
        self.init_timing()
        student_id = self.generateStudentId('?')
        students_folder.invokeFactory('Student', student_id)
        student_object = getattr(students_folder,student_id)
        self.do_timing()
        if pin_password:
            password = pin_parts[2]
            self.makeStudentMember(student_id,password = password)
        student_object.manage_setLocalRoles(student_id, ['Owner',])
        self.do_timing()
        #logger.info("creating %s reg_no %s" % (student_id,reg_no))
        #
        # application
        #
        student_object.invokeFactory('StudentApplication','application')
        application = student_object.application
        #self.portal_workflow.doActionFor(application,'open',dest_container=application)
        #self.do_timing()
        da = {'Title': 'Application Data'}
        da['jamb_reg_no'] = reg_no

        sex = 'M'
        if brain.sex:
            sex = 'F'
        da['jamb_sex'] = sex
        da['jamb_age'] = brain.jamb_age
        da['app_reg_pin'] = brain.pin
        da['jamb_lga'] = brain.jamb_lga
        da['jamb_state'] = brain.jamb_state
        da['jamb_score'] = brain.aggregate
        da['app_email'] = brain.email
        da['app_mobile'] = brain.phone

        # entry_mode cannot be retrieved from the certtificate 
        # because ug_ft has two different entry modes
                        
        if brain.entry_mode:                      # does not happen because import_application 
            da['entry_mode'] = brain.entry_mode   # schema has no field entry_mode
        elif brain.screening_type == 'pume':  
            da['entry_mode'] = 'ume_ft'  
        elif brain.screening_type == 'pde':  
            da['entry_mode'] = 'de_ft'  
        else:
            da['entry_mode'] = self._v_certificates[brain.course_admitted]['study_mode']
        
        #elif brain.screening_type == 'pce':  
        #   da['entry_mode'] = 'pce'  
        #elif brain.screening_type == 'prence':  
        #   da['entry_mode'] = 'prence'  
        #else:  
        #   da['entry_mode'] = ''  
     

        #da['entry_session'] = entry_session
        da['entry_session'] = es
        da['jamb_lastname'] = brain.lastname
        da['jamb_middlename'] = brain.middlenames   # different field names!
        da['jamb_firstname'] = brain.firstname
        da['screening_application_date'] = brain.application_date
        da['date_of_birth'] = brain.date_of_birth
        da['jamb_first_cos'] = brain.course1
        da['jamb_second_cos'] = brain.course2
        da['course3'] = brain.course3
        da['screening_type'] = brain.screening_type
        da['screening_score'] = brain.screening_score
        da['screening_date'] = brain.screening_date
        da['hq_type'] = brain.hq_type
        da['hq_grade'] = brain.hq_grade
        da['aos'] = brain.aos

        application.getContent().edit(mapping=da)
        self.do_timing()
        #self.portal_workflow.doActionFor(application,'close',dest_container=application)
        #
        # personal
        #
        student_object.invokeFactory('StudentPersonal','personal')
        personal = student_object.personal
        #self.portal_workflow.doActionFor(personal,'open',dest_container=personal)
        #self.do_timing()
        dp = {'Title': 'Personal Data'}
        dp['sex'] = brain.sex
        dp['email'] = brain.email
        dp['phone'] = brain.phone
        dp['lastname'] = brain.lastname
        dp['middlename'] = brain.middlenames   # different field names!
        dp['firstname'] = brain.firstname
        personal.getContent().edit(mapping=dp)
        self.do_timing()
        #
        # clearance
        #
        student_object.invokeFactory('StudentClearance','clearance')
        clearance = student_object.clearance
        #self.portal_workflow.doActionFor(clearance,'open',dest_container=clearance)
        dc = {'Title': 'Clearance/Eligibility Record'}
        dc['lga'] = brain.lga
        dc['birthday'] = brain.date_of_birth
        clearance.getContent().edit(mapping=dc)
        self.do_timing()
        #self.portal_workflow.doActionFor(clearance,'close',dest_container=clearance)
        #
        # study Course
        #
        student_object.invokeFactory('StudentStudyCourse','study_course')
        studycourse = student_object.study_course
        #self.portal_workflow.doActionFor(studycourse,'open',dest_container=studycourse)
        #self.do_timing()
        dsc = {}
        dsc['study_course'] = brain.course_admitted
        dsc['current_verdict'] = ''
        dsc['current_mode'] = da['entry_mode'] # no longer used
        if da['entry_mode'].startswith('de'):
            dsc['current_level'] = '200'
        elif da['entry_mode'].startswith('pre'):
            dsc['current_level'] = '000'
        else:
            dsc['current_level'] = '100'
        dsc['current_session'] = es
        studycourse.getContent().edit(mapping=dsc)
        self.do_timing()
        #
        # payments folder
        student_object.invokeFactory('PaymentsFolder','payments')
        payments = getattr(student_object,'payments')
        #self.do_timing()
        dpay = {}
        dpay['Title'] = 'Payments'
        payments.getContent().edit(mapping=dpay)
        self.portal_workflow.doActionFor(payments,'open')
        self.do_timing()
        #
        # passport foto
        app_picture ="%s/import/images/%s/%s_passport.jpg" % (i_home,
                                                              brain.screening_type,
                                                              brain.reg_no)
        images_dir = getImagesDir(student_id)
        #images_dir = os.path.join("%s" % images_base,student_id)
        letter_dir,student_dir = os.path.split(images_dir)
        if not os.path.exists(letter_dir):
            os.mkdir(letter_dir)
        if not os.path.exists(images_dir):
            os.mkdir(images_dir)
        image_name = os.path.join(images_dir,"passport_%(student_id)s.jpg" % vars())
        if os.path.exists(app_picture):
            copy2(app_picture,image_name)
        else:
            logger.info('passport of %s/%s not found: %s' % (student_id,
                                                             brain.reg_no,
                                                             app_picture))

        self.do_timing()
        self.print_timing()
        if with_timing:
            self.timer_step = 0
            self._v_timer_count += 1
            self._v_total += self.total_time
        return student_id
    ###)

    security.declareProtected(ModifyPortalContent,'makeStudentLevel') ###(
    def makeStudentLevel(self,student_id):
        "create the StudyLevel for a returning Student"
        #import pdb;pdb.set_trace()
        logger = logging.getLogger('WAeUPTool.makeStudentLevel')
        students_folder = self.portal_url.getPortalObject().campus.students
        res = self.students_catalog(id=student_id)
        if res:
            st = res[0]
        course = st.course
        matric_no = st.matric_no
        level = st.level
        res = self.results_import(matric_no = matric_no)
        if res:
            results = res
        logger.info('%s creating Level %s' % (student_id,level))
        #
        # Level
        #
        student_obj = getattr(self.portal_url.getPortalObject().campus.students,student_id)
        studycourse = getattr(student_obj,"study_course",None)
        self.portal_workflow.doActionFor(studycourse,'close_for_edit',dest_container=studycourse)
        l = getattr(studycourse,level,None)
        if l is None:
            studycourse.invokeFactory('StudentStudyLevel', level)
            l = getattr(studycourse, level)
            self.portal_workflow.doActionFor(l,'open',dest_container=l)
            l.getContent().edit(mapping={'Title': "Level %s" % level})
        ###)
        
        
    security.declareProtected(ModifyPortalContent,'makeStudentLevel') ###(
    def exportAllStudyLevels(self,student_id):
        "export the StudyLevels for a student"
        #import pdb;pdb.set_trace()
        logger = logging.getLogger('WAeUPTool.exportAllStudyLevels')
        students_folder = self.portal_url.getPortalObject().campus.students
        res = self.students_catalog(id=student_id)
        if res:
            st = res[0]
        course = st.course
        matric_no = st.matric_no
        level = st.level
        student_obj = getattr(self.portal_url.getPortalObject().campus.students,student_id)
        studycourse = getattr(student_obj,"study_course",None)
        levels = studycourse.objectIds()
        
        stool = getToolByName(self, 'portal_schemas')
        schema = stool._getOb('student_study_level')
        fields = ['student_id','level']
        fields.extend(schema.keys())
        format = '"%(' + ')s","%('.join(fields) + ')s"'
        export_file = "%s/export/study_levels_removed.csv" % (i_home)
        
        if not os.path.exists(export_file):  
            file_handler = open(export_file,"a")
            headline = ','.join(fields)
            file_handler.write(headline +'\n')
        else:
            file_handler = open(export_file,"a")
        
        for level in levels:
            level_object = getattr(studycourse,level)
            level_content = level_object.getContent()
            d = {'student_id':student_id,'level':level}
            for field in schema.keys():
                d[field] = getattr(level_content,field,'')    
            line = format % d
            file_handler.write(line +'\n')                
            
        ###)        

    security.declarePublic('getHallInfo') ###(
    def getHallInfo(self,bed):
        """return Hall Info"""
        info = {}
        bedsplit = bed.split('_')
        if len(bedsplit) == 4:
            hall,block,room,letter = bed.split('_')
        else:
            info['maintenance_code'] = 'None'
            return info
        res = ZCatalog.searchResults(self.portal_catalog_real,portal_type="AccoHall",id=hall)
        if res and len(res) == 1:
            hall_brain = res[0]
            hall_doc = hall_brain.getObject().getContent()
        else:
            return info
        info['hall_title'] = hall_brain.Title
        info['maintenance_code'] = hall_doc.maintenance_code
        res = ZCatalog.searchResults(self.portal_catalog_real,portal_type="ScratchCardBatch")
        batch_doc = None
        for brain in res:
            if brain.id.startswith(info['maintenance_code']):
                batch_doc = brain.getObject().getContent()
                break
        if batch_doc is None:
            info['maintenance_fee'] = ''
        else:
            info['maintenance_fee'] = batch_doc.cost
        return info
    ###)

    security.declareProtected(ModifyPortalContent,'removePictureFolder') ###(
    def removePictureFolder(self,student_id):
        """remove picture_folder by renaming it"""
        picture_path = getImagesDir(student_id)
        dest_path = os.path.join("%s" % images_base,'removed',student_id)
        dest_path = dest_path + "_removed"
        if os.path.exists(dest_path) or not os.path.exists(picture_path):
            return False
        os.rename(picture_path,dest_path)
        return True
    ###)

    security.declareProtected(ModifyPortalContent,'restorePictureFolder') ###(
    def restorePictureFolder(self,student_id):
        """restore picture_folder by renaming it"""
        picture_path = getImagesDir(student_id)
        orig_path = os.path.join("%s" % images_base,'removed',student_id)
        orig_path = orig_path + "_removed"
        if os.path.exists(picture_path) or not os.path.exists(orig_path):
            return False        
        os.rename(orig_path,picture_path)
        return True
    ###)

    security.declarePublic('picturesExist') ###(
    def picturesExist(self, ids,student_id=None):
        """check if pictures exist in the filesystem"""
        if student_id is None:
            student_id = self.getStudentId()
        if student_id is None:
            return False
        picture_path = getImagesDir(student_id)
        #picture_path = os.path.join(images_base,student_id)
        if not os.path.exists(picture_path):
            return False
        pictures  = [picture[:picture.rfind('_')] for picture in os.listdir(picture_path)]
        return set(ids).issubset(set(pictures))
    ###)
    
        security.declarePublic('picturePathExists') ###(
    def picturePathExists(self, student_id=None):
        """check if picture path exists in the filesystem"""
        if student_id is None:
            student_id = self.getStudentId()
        if student_id is None:
            return False
        picture_path = getImagesDir(student_id)
        if os.path.exists(picture_path):
            return True
        return False
    ###)
    
    
    security.declareProtected(ModifyPortalContent,'removeUnusedImageFolders') ###(
    def removeUnusedImageFolders(self):
        """check if an unused image folders exists in the filesystem"""
        mtool = self.portal_membership
        member = mtool.getAuthenticatedMember()
        member_id = str(member)
        logger = logging.getLogger('WAeUPTool.removeUnusedImageFolders')
        abc = os.listdir(images_base)
        ifolders = []
        for i in abc:
            picture_path = os.path.join(images_base,i)
            ifolders.extend(os.listdir(picture_path))
        unused_ids = []
        for id in ifolders:
            res = self.students_catalog(id=id)
            if not res:
                unused_ids.append(id)
                #import pdb;pdb.set_trace()    
                if not id.endswith('removed'):
                    removed = self.waeup_tool.removePictureFolder(id)  
                    if removed:
                        logger.info('%s: image folder %s successfully removed' % (member_id,id))
                    else:
                        logger.info('%s: image folder %s could not be removed' % (member_id,id))
        return
        
    ###)    

    security.declarePublic('picturesList') ###(
    def picturesList(self):
        """check if pictures exist in the filesystem"""
        path = 'images'
        student_id = self.getStudentId()
        #picture_path = os.path.join(i_home,path,student_id)
        picture_path = getImagesDir(student_id)
        if not os.path.exists(picture_path):
            return []
        return [picture[:picture.rfind('_')] for picture in os.listdir(picture_path)]
    ###)

    security.declarePublic('showFsPicture') ###(
    def showFsPicture(self,path):
        """return a picture from the filesystem"""
        #picture_path = os.path.join(i_home,path)
        picture_path = os.path.join(images_base,path)
        response = self.REQUEST.RESPONSE
        #import pdb;pdb.set_trace()
        registry = getToolByName(self, 'mimetypes_registry')
        mimetype = str(registry.lookupExtension(path.lower()) or
                    registry.lookupExtension('file.bin'))
        if os.path.exists(picture_path):
            response.setHeader('Content-type',mimetype)
            return open(picture_path).read()
        picture_path = os.path.join(i_home,'import',path)
        if os.path.exists(picture_path):
            return open(picture_path).read()
    ###)

    security.declareProtected(ModifyPortalContent,'deleteAllCourses') ###(
    def deleteAllCourses(self,department="All"):
        ''' delete the courses'''
        pm = self.portal_membership
        member = pm.getAuthenticatedMember()

        if str(member) not in ("henrik","joachim"):
            return "not possible"
        if department == "All":
            res = self.portal_catalog({'meta_type': 'Department'})
        if len(res) < 1:
            return "No Departments found"

        deleted = []
        for dep in res:
            cf = dep.getObject().courses
            if cf:
                cf.manage_delObjects(ids=cf.objectIds())
                deleted.append("deleted Courses in %s" % dep.getId)
        return "\r".join(deleted)
    ###)

    security.declareProtected(ModifyPortalContent,'getLogfileLines') ###(
    def getLogfileLines(self,filename="event.log",numlines=20):
        """Get last NUMLINES lines of logfile FILENAME.

        Return last lines' of a file in the instances logfile directory as
        a list. The number of returned lines equals `numlines' or less. If
        less than `numlines' lines are available, the whole file ist
        returned. If the file can not be opened or some other error
        occurs, empty list is returend.
        """
        result = []
        lines_hit = 0

        # We only handle files in instances' log directory...
        logpath = os.path.join(i_home, "log")
        filename = str(os.path.abspath( os.path.join( logpath, filename )))
        if not filename.startswith( logpath ):
            # Attempt to access file outside log-dir...
            return []

        try:
            fd = file( filename, "rb" )
        except IOError:
            return []
        if not fd:
            return []

        if os.linesep == None:
            linesep = '\n'
        else:
            linesep = os.linesep

        # Try to find 'numlines' times a lineseparator, searching from end
        # and moving to the beginning of file...
        fd.seek( 0, 2) # Move to end of file...
        while lines_hit < numlines:
            if fd.read(1) == linesep[-1]: # This moves filedescriptor
                                          # one step forward...
                lines_hit += 1
            try:
                fd.seek( -2, 1) # Go two bytes back from current pos...
            except IOError:
                # We cannot go back two bytes. Maybe the file is too small...
                break
        fd.seek(2,1)

        # Read all lines from current position...
        result = fd.readlines()
        # Remove line endings...
        result = [x.strip() for x in result]
        fd.close()
        return result
    ###)

    security.declareProtected(ModifyPortalContent,"getCallbacksFromLog")###(
    def getCallbacksFromLog(self,filename):
        """fix Online Payment Transactions from Z2.log entries"""
        import transaction
        import random
        from cgi import parse_qs
        from urlparse import urlparse
        #from pdb import set_trace
        wftool = self.portal_workflow
        current = DateTime.DateTime().strftime("%d-%m-%y_%H_%M_%S")
        students_folder = self.portal_url.getPortalObject().campus.students
        s = r'(?P<client_ip>\S+) - (?P<member_id>\S+) \['
        s += r'(?P<date>.*)\] "(?P<get>.*)" (?P<codes>\d+ \d+) "'
        s += r'(?P<intersw>.*)" "(?P<agent>.*)"'
        data = re.compile(s)
        start = True
        tr_count = 1
        total = 0
        #name = 'pume_results'
        #name = 'epaymentsuccessful_z2log2'
        name = filename
        no_import = []
        imported = []
        logger = logging.getLogger('WAeUPTool.getFailedTransactions')
        try:
            transactions = open("%s/import/%s" % (i_home,name),"rb").readlines()
        except:
            logger.error('Error reading %s' % name)
            return
        tas = []
        for line in transactions:
            dict = {}
            items = data.search(line)
            dict['idict'] = idict = items.groupdict()
            #print idict
            #from pdb import set_trace;set_trace()
            urlparsed = urlparse(idict['get'][4:])
            #print urlparsed
            path = urlparsed[2].split('/')
            dict['student_id'] = student_id = path[8]
            dict['payment_id'] = payment_id = path[10]
            dict['qs_dict'] = qs_dict = parse_qs(urlparsed[4])
            tas.append(dict)
            tr_count += 1
        return tas
    ###)

    security.declareProtected(ModifyPortalContent,"importOnlinePaymentTransactions")###(
    def importOnlinePaymentTransactions(self):
        """load Online Payment Transactions from CSV values"""
        import transaction
        import random
        #from pdb import set_trace
        current = DateTime.DateTime().strftime("%d-%m-%y_%H_%M_%S")
        opt = self.online_payments_import
        students_folder = self.portal_url.getPortalObject().campus.students
        start = True
        tr_count = 1
        total = 0
        #name = 'pume_results'
        name = 'OnlineTransactions'
        no_import = []
        imported = []
        logger = logging.getLogger('WAeUPTool.importOnlinePaymentTransactions')
        try:
            transactions = csv.DictReader(open("%s/import/%s.csv" % (i_home,name),"rb"))
        except:
            logger.error('Error reading %s.csv' % name)
            return
        for pay_transaction in transactions:
            if start:
                start = False
                logger.info('Start loading from %s.csv' % name)
                s = ','.join(['"%s"' % fn for fn in pay_transaction.keys()])
                no_import.append('%s,"Error"' % s)
                format = ','.join(['"%%(%s)s"' % fn for fn in pay_transaction.keys()])
                format_error = format + ',"%(Error)s"'
            data = {}

            # format of the first file sent by Tayo
            #data['datetime'] = date = DateTime.DateTime(pay_transaction['Date'])
            #data['student_id'] = student_id = pay_transaction['Payer ID']
            #data['order_id'] = order_id = pay_transaction['Order ID (Tranx Ref)']
            #data['response_code'] = response_code = pay_transaction['Resp Code']
            #data['amount'] = amount = pay_transaction['Amount']

            # format of the second file sent by Tayo
            #data['datetime'] = date = 0
            #data['student_id'] = student_id = pay_transaction['Payer ID']
            #data['order_id'] = order_id = pay_transaction['Order ID (Tranx Ref)']
            #data['response_code'] = response_code = '00'
            #data['amount'] = amount = pay_transaction['Amount']

            # format of the third file sent by Kehinde
            data['datetime'] = date = 0
            data['student_id'] = student_id = pay_transaction['customer_id']
            data['order_id'] = order_id = pay_transaction['merchant_reference']
            data['response_code'] = response_code = '00'
            data['amount'] = amount = pay_transaction['Amount']

            dup = False
            if response_code == "12":
                continue
            try:
                opt.addRecord(**data)
            except ValueError:
                dup = True
            #from pdb import set_trace;set_trace()
            if dup:
                if response_code == "00":
                    try:
                        opt.modifyRecord(**data)
                    except:
                        logger.info("duplicate uid, order_id %(order_id)s, student_id %(student_id)s, response_code %(response_code)s" % data)
                        continue
                else:
                    pay_transaction['Error'] = "Duplicate order_id"
                    no_import.append( format_error % pay_transaction)
                    logger.info("duplicate order_id %(order_id)s for %(student_id)s %(response_code)s" % data)
                    continue
            tr_count += 1
            if tr_count > 1000:
                if len(no_import) > 0:
                    open("%s/import/%s_not_imported%s.csv" % (i_home,name,current),"a").write(
                             '\n'.join(no_import) + '\n')
                    no_import = []
                em = '%d transactions committed\n' % (tr_count)
                transaction.commit()
                regs = []
                logger.info(em)
                total += tr_count
                tr_count = 0
        open("%s/import/%s_not_imported%s.csv" % (i_home,name,current),"a").write(
                                                '\n'.join(no_import))
        return self.REQUEST.RESPONSE.redirect("%s" % self.REQUEST.get('URL1'))
    ###)

    security.declareProtected(ModifyPortalContent,"importData")###(
    def importData(self,filename,name,edit=False,bypass_queue_catalog=False):
        """load data from CSV values"""
        import transaction
        import random
        students_folder = self.portal_url.getPortalObject().campus.students
        uploads_folder = self.portal_url.getPortalObject().campus.uploads
        pending_only = False
        pend_str = '--'
        elapse = time.time()
        #
        # preparations
        #
        if filename == pend_str:
            pending_only = True
        importer_name = ''.join([part.capitalize() for part in name.split('_')])
        importer = eval("%sImport" % importer_name)(self)
        logger = importer.logger
        if importer.init_errors:
            logger.info(importer.init_errors)
            return importer.init_errors
        member = importer.member
        #current = importer.current
        import_date = importer.import_date
        #
        # not_imported
        #
        info = importer.info
        data_keys = importer.data_keys
        csv_keys = importer.csv_keys
        #csv_keys.extend(info.keys())
        headline_mapping = dict((k,k) for k in csv_keys)
        #
        # pending
        #
        pending_path = importer.pending_path
        pending_tmp = importer.pending_tmp
        pending_backup = importer.pending_backup
        pending_fn = importer.pending_fn
        imported_path = importer.imported_path
        imported_fn = importer.imported_fn
        commit_after = importer.commit_after
        pending = []
        pending_digests = []
        #total_added_to_pending = 0
        if not pending_only:
            pending,pending_digests = importer.makeIdLists()
            pending_at_start = len(pending)
        datafile = open(pending_tmp,"w")
        pending_csv_writer = csv.DictWriter(datafile,
                                                    csv_keys,
                                                    extrasaction='ignore')
        pending_csv_writer.writerow(headline_mapping)
        datafile.close()
        #
        # imported
        #
        if not os.path.exists(imported_path):
            datafile = open(imported_path,"w")
            imported_csv_writer = csv.DictWriter(datafile,
                                                 csv_keys,
                                                 extrasaction='ignore')
            imported_csv_writer.writerow(headline_mapping)
            datafile.close()
        start = True
        tr_count = 0
        total = 0
        total_added_to_imported = 0
        total_pending = 0
        import_source_done = ""
        if pending_only:
            import_source_path = pending_path
        else:
            import_source_path = "%s/import/%s.csv" % (i_home,filename)
            import_source_done = "%s/import/%s.done" % (i_home,filename)
        if not os.path.exists(import_source_path):
            fn = os.path.split(import_source_path)[1]
            em = 'no import file %(fn)s' % vars()
            return em
        import_source_fn = os.path.split(import_source_path)[1]
        if not pending_only:
            info['imported_from'] = import_source_fn
        headline = csv.reader(open(import_source_path,"rb")).next()
        if "import_mode" not in headline:
            msg = 'import_mode must be in heading'
            return msg
        invalid_keys = importer.checkHeadline(headline)
        if invalid_keys:
            return 'not ignorable key(s): "%s" found in heading' % ", ".join(invalid_keys)

        import_keys = [k.strip() for k in headline if not (k.strip().startswith('ignore')
                                                         or k.strip() in info.keys())]
        # diff2schema = set(import_keys).difference(set(importer.schema.keys()))
        # diff2layout = set(import_keys).difference(set(importer.layout.keys()))
        # if diff2schema and diff2schema != set(['id',]):
        #     msg = 'not ignorable key(s): "%s" found in heading' % ", ".join(diff2schema)
        #     return msg
        #
        # start importing
        #
        try:
            reader = csv.DictReader(open(import_source_path,"rb"))
        except:
            msg = 'Error reading %s.csv' % filename
            logger.error(msg)
            return msg
        items = [item for item in reader]
        total_to_import = len(items)
        tti_float = float(total_to_import)
        if pending_only:
            pending_at_start = total_to_import
        count = 0
        imported = []
        old_commit_count = 0
        error_count = imported_count = 0
        already_in = 0
        for record in items:
            item = {}
            empty_value_keys = []
            for k,v in record.items():
                if k is None:
                    continue
                if v:
                    if v == EMPTY:
                        empty_value_keys += k,
                        v = ''
                    item[k.strip()] = v.strip()
            count += 1
            if start:
                start = False
                adapters = [MappingStorageAdapter(importer.schema, item)]
                logger.info('%(member)s starts import from %(import_source_fn)s' % vars())
            dm = DataModel(item, adapters,context=self)
            ds = DataStructure(data=item,datamodel=dm)
            error_string = ""
            total += 1
            import_mode = item.get('import_mode','')
            import_method = getattr(importer, '%(import_mode)s' % vars(),None )
            if import_method is None:
                error_string += "import_mode '%(import_mode)s' is invalid" % vars()
            elif (import_mode in importer.required_modes and
                not set(importer.required_keys[import_mode]).issubset(set(item.keys()))):
                diff2import = set(importer.required_keys[import_mode]).difference(set(item.keys()))
                error_string += 'required key(s): "%s" not found in record' % ", ".join(diff2import)
            else:
                for k in import_keys:
                    if k not in item.keys() or k not in importer.validators.keys():
                        continue
                    if not importer.validators[k](ds,mode=import_mode):
                        error_string += ' ++ '
                        error_string += "%s: %s" % (k,self.translation_service(ds.getError(k),
                                                                           ds.getErrorMapping(k)))
            if error_string:
                error = error_string
                id = ''
                mapping = item
            else:
                temp_item = item.copy()
                temp_item.update(dm)
                results = import_method(temp_item)
                id = results[0]
                error = results[1]
                mapping = results[2]
            #
            # restore some values
            #
            for k in empty_value_keys:
                mapping[k] = EMPTY
            if mapping.has_key('sex'):
                #import pdb;pdb.set_trace()
                if mapping['sex'] == True:
                    mapping['sex'] = 'F'
                elif mapping['sex'] == False:
                    mapping['sex'] = 'M'
            if pending_only:
                info['imported_from'] = item['imported_from']
            data_string = ", ".join("%s: %s" % (k,v) for k,v in mapping.items())
            info['error'] = error
            info['import_record_no'] = count + 1
            mapping.update(info)
            log_list = []
            if error:
                error_count += 1
                digest = makeDigest(mapping,data_keys)
                if digest not in pending_digests:
                    pending_digests += digest,
                    pending.append(mapping)
                    if not pending_only:
                        log_list += "record from %(import_source_fn)s added to %(pending_fn)s, %(data_string)s, %(error)s" % vars(),
                else:
                    already_in += 1
                    pass
            else:
                imported_count += 1
                imported += mapping,
                log_list += "record imported and added to %(imported_fn)s from %(import_source_fn)s, %(data_string)s" % vars(),
            if log_list:
                time_till_now = time.time() - elapse
                percent_finished = (error_count + imported_count)/tti_float*100
                log_list.insert(0,("%(percent_finished)6.3f %% done in %(time_till_now)3.2fs," % vars()),)
                logger.info(' '.join(log_list))
            finished = count > total_to_import - 1
            must_commit = (imported_count != old_commit_count) and (not imported_count % commit_after)
            if must_commit:
                old_commit_count = imported_count

            if must_commit or finished:
                if len(imported):
                    transaction.commit()
                    datafile = open(imported_path,"a")
                    writer = csv.DictWriter(datafile,
                                            csv_keys,
                                            extrasaction='ignore')
                    writer.writerows(imported)
                    datafile.close()
                    total_added_to_imported += len(imported)
                    imported = []
                if len(pending) > 0:
                    datafile = open(pending_tmp,"a")
                    writer = csv.DictWriter(datafile,
                                            csv_keys,
                                            extrasaction='ignore')
                    writer.writerows(pending)
                    datafile.close()
                    total_pending += len(pending)
                    #total_added_to_pending += len(pending)
                    pending = []
                if not finished:
                    msg = '%(commit_after)d records imported and committed of total %(total_added_to_imported)d\n' % vars()
                    logger.info(msg)
        elapse = time.time() - elapse
        if os.path.exists(pending_path):
            copy2(pending_path,pending_backup)
        copy2(pending_tmp,pending_path)
        msg = "finished importing from %(import_source_fn)s in %(elapse).2f seconds, " % vars()
        msg += "%(count)d records totally read, %(total_added_to_imported)d added to %(imported_fn)s, " % vars()
        if pending_only:
            removed_pending = pending_at_start - total_pending
            msg += "%(removed_pending)d removed from %(pending_fn)s" % vars()
        else:
            added_pending = total_pending - pending_at_start
            msg += "%(added_pending)d added to %(pending_fn)s, %(already_in)s already in %(pending_fn)s" % vars()
        #msg += "%(total_pending)d totally written" % vars()    # this line does not make any sense
        logger.info(msg)
        if import_source_done:
            copy(import_source_path,import_source_done)
            os.remove(import_source_path)
            upload = getattr(uploads_folder,os.path.split(import_source_path)[1],None)
            if upload is not None:
                upload_doc = upload.getContent()
                mapping = {}
                #mapping['import_date'] = DateTime.DateTime()
                mapping['import_date'] = import_date
                mapping['imported_by'] = importer.imported_by
                mapping['import_message'] = msg
                upload_doc.edit(mapping = mapping)
        os.remove(pending_tmp)
        return msg
    ###)

    security.declareProtected(ModifyPortalContent,"moveImagesToFS")###(
    def moveImagesToFS(self,student_id="O738726"):
        "move the images to the filesystem"
        images_dir = getImagesDir(student_id)
        #images_dir = os.path.join("%s" % images_base,student_id)
        student_folder = getattr(self.portal_url.getPortalObject().campus.students,student_id)
        stool = getToolByName(self, 'portal_schemas')
        schemas = ['student_application',
                   'student_clearance',
                   ]
        created = False
        for schema_id in schemas:
            schema = stool._getOb(schema_id)
            object = getattr(student_folder,schema_id[len('student_'):],None)
            if object is None:
                continue
            doc = object.getContent()
            for key in schema.keys():
                if schema[key].meta_type != "CPS Image Field":
                    continue
                #import pdb;pdb.set_trace()
                image = getattr(doc,key,None)
                if not image or not hasattr(image,"data"):
                    continue
                if not created:
                    if not os.path.exists(images_dir):
                        os.mkdir(images_dir)
                    created = True
                filename = os.path.join(images_dir,"%(key)s_%(student_id)s.jpg" % vars())
                open(filename,"wb").write(str(image.data))
    ###)

    security.declareProtected(ModifyPortalContent,"movePassportToFS")###(
    def movePassportToFS(self,student_id="O738726"):
        "move the passports to the filesystem"
        images_dir = os.path.join("%s" % i_home,'passports')
        student_folder = getattr(self.portal_url.getPortalObject().campus.students,student_id)
        stool = getToolByName(self, 'portal_schemas')
        schemas = ['student_application',
                   #'student_clearance',
                   ]
        created = False
        for schema_id in schemas:
            schema = stool._getOb(schema_id)
            object = getattr(student_folder,schema_id[len('student_'):],None)
            if object is None:
                continue
            doc = object.getContent()
            for key in schema.keys():
                if schema[key].meta_type != "CPS Image Field":
                    continue
                #import pdb;pdb.set_trace()
                image = getattr(doc,key)
                if not hasattr(image,"data"):
                    continue
                if not created:
                    if not os.path.exists(images_dir):
                        os.mkdir(images_dir)
                    created = True
                filename = os.path.join(images_dir,"%(student_id)s.jpg" % vars())
                open(filename,"wb").write(str(image.data))
    ###)
    
    
    security.declarePublic('getConfigParams')
    def getConfigParams(self,conf_id="configuration"):
        conf = getattr(self.portal_url.getPortalObject().campus,conf_id)
        conf_obj = conf.getContent()
        stool = getToolByName(self, 'portal_schemas')
        schema = stool._getOb('configuration')
        d = {}
        for key in schema.keys():
            d[key] = getattr(conf_obj,key,None)
        return d


InitializeClass(WAeUPTool)
