Module tkintertable.TableModels

Module implementing the TableModel class that manages data for it's associated TableCanvas.

Created Oct 2008 Copyright (C) Damien Farrell

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

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

Source code
#!/usr/bin/env python
"""
    Module implementing the TableModel class that manages data for
    it's associated TableCanvas.

    Created Oct 2008
    Copyright (C) Damien Farrell

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    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
"""

from __future__ import absolute_import, division, print_function
from .TableFormula import Formula
from . import Filtering
from types import *
from collections import OrderedDict
import operator
import string, types, copy
import pickle, os, sys, csv

class TableModel(object):
    """A base model for managing the data in a TableCanvas class"""

    keywords = {'columnnames':'columnNames', 'columntypes':'columntypes',
               'columnlabels':'columnlabels', 'columnorder':'columnOrder',
               'colors':'colors'}

    def __init__(self, newdict=None, rows=None, columns=None):
        """Constructor"""
        self.initialiseFields()
        self.setupModel(newdict, rows, columns)
        return

    def setupModel(self, newdict, rows=None, columns=None):
        """Create table model"""

        if newdict != None:
            self.data = copy.deepcopy(newdict)
            for k in self.keywords:
                if k in self.data:
                    self.__dict__[self.keywords[k]] = self.data[k]
                    del self.data[k]
            #read in the record list order
            if 'reclist' in self.data:
                temp = self.data['reclist']
                del self.data['reclist']
                self.reclist = temp
            else:
                self.reclist = self.data.keys()
        else:
            #just make a new empty model
            self.createEmptyModel()

        if not set(self.reclist) == set(self.data.keys()):
            print ('reclist does not match data keys')
        #restore last column order
        if hasattr(self, 'columnOrder') and self.columnOrder != None:
            self.columnNames=[]
            for i in self.columnOrder.keys():
                self.columnNames.append(self.columnOrder[i])
                i=i+1
        self.defaulttypes = ['text', 'number']
        #setup default display for column types
        self.default_display = {'text' : 'showstring',
                                'number' : 'numtostring'}
        #set default sort order as first col
        if len(self.columnNames)>0:
            self.sortkey = self.columnNames[0]
        else:
            self.sortkey = None
        #add rows and cols if they are given in the constructor
        if newdict == None:
            if rows != None:
                self.autoAddRows(rows)
            if columns != None:
                self.autoAddColumns(columns)
        self.filteredrecs = None
        return

    def initialiseFields(self):
        """Create base fields, some of which are not saved"""
        self.data = None    # holds the table dict
        self.colors = {}    # holds cell colors
        self.colors['fg']={}
        self.colors['bg']={}
        #default types
        self.defaulttypes = ['text', 'number']
        #list of editable column types
        self.editable={}
        self.nodisplay = []
        self.columnwidths={}  #used to store col widths, not held in saved data
        return

    def createEmptyModel(self):
        """Create the basic empty model dict"""

        self.data = {}
        # Define the starting column names and locations in the table.
        self.columnNames = []
        self.columntypes = {}
        self.columnOrder = None
        #record column labels for use in a table header
        self.columnlabels={}
        for colname in self.columnNames:
            self.columnlabels[colname]=colname
        self.reclist = list(self.data.keys())
        return

    def importCSV(self, filename, sep=','):
        """Import table data from a comma separated file."""

        if not os.path.isfile(filename) or not os.path.exists(filename):
            print ('no such file')
            return None

        #takes first row as field names
        dictreader = csv.DictReader(open(filename, "r"), delimiter=sep)
        dictdata = {}
        count=0
        for rec in dictreader:
            dictdata[count]=rec
            count=count+1
        self.importDict(dictdata)
        return

    def importDict(self, newdata):
        """Try to create a table model from a dict of the form
           {{'rec1': {'col1': 3, 'col2': 2}, ..}"""

        #get cols from sub data keys
        colnames = []
        for k in newdata:
            fields = newdata[k].keys()
            for f in fields:
                if not f in colnames:
                    colnames.append(f)
        for c in colnames:
            self.addColumn(c)
        #add the data
        self.data.update(newdata)
        self.reclist = list(self.data.keys())
        return

    def getDefaultTypes(self):
        """Get possible field types for this table model"""
        return self.defaulttypes

    def getData(self):
        """Return the current data for saving"""

        data = copy.deepcopy(self.data)
        data['colors'] = self.colors
        data['columnnames'] = self.columnNames
        #we keep original record order
        data['reclist'] = self.reclist
        #record current col order
        data['columnorder']={}
        i=0
        for name in self.columnNames:
            data['columnorder'][i] = name
            i=i+1
        data['columntypes'] = self.columntypes
        data['columnlabels'] = self.columnlabels
        return data

    def getAllCells(self):
        """Return a dict of the form rowname: list of cell contents
          Useful for a simple table export for example"""

        records={}
        for row in range(len(self.reclist)):
            recdata=[]
            for col in range(len(self.columnNames)):
                recdata.append(self.getValueAt(row,col))
            records[row]=recdata
        return records

    def getColCells(self, colIndex):
        """Get the viewable contents of a col into a list"""

        collist = []
        if self.getColumnType(colIndex) == 'Link':
            return ['xxxxxx']
        else:
            for row in range(len(self.reclist)):
                v = self.getValueAt(row, colIndex)
                collist.append(v)
        return collist

    def getlongestEntry(self, columnIndex):
        """Get the longest cell entry in the col"""

        collist = self.getColCells(columnIndex)
        maxw=5
        for c in collist:
            try:
                w = len(str(c))
            except UnicodeEncodeError:
                pass
            if w > maxw:
                maxw = w
        #print 'longest width', maxw
        return maxw

    def getRecordAtRow(self, rowIndex):
        """Get the entire record at the specifed row."""

        name = self.getRecName(rowIndex)
        record = self.data[name]
        return record

    def getCellRecord(self, rowIndex, columnIndex):
        """Get the data held in this row and column"""

        value = None
        colname = self.getColumnName(columnIndex)
        coltype = self.columntypes[colname]
        name = self.getRecName(rowIndex)
        #print self.data[name]
        if colname in self.data[name]:
            celldata=self.data[name][colname]
        else:
            celldata=None
        return celldata

    def deleteCellRecord(self, rowIndex, columnIndex):
        """Remove the cell data at this row/column"""

        colname = self.getColumnName(columnIndex)
        coltype = self.columntypes[colname]
        name = self.getRecName(rowIndex)
        if colname in self.data[name]:
            del self.data[name][colname]
        return

    def getRecName(self, rowIndex):
        """Get record name from row number"""

        if len(self.reclist)==0:
            return None
        if self.filteredrecs != None:
            name = self.filteredrecs[rowIndex]
        else:
            name = self.reclist[rowIndex]
        return name

    def setRecName(self, newname, rowIndex):
        """Set the record name to another value - requires re-setting in all
           dicts that this rec is referenced"""

        if len(self.reclist)==0:
            return None
        currname = self.getRecName(rowIndex)
        self.reclist[rowIndex] = newname
        temp = copy.deepcopy(self.data[currname])
        self.data[newname] = temp
        #self.data[newname]['Name'] = newname
        del self.data[currname]
        for key in ['bg', 'fg']:
            if currname in self.colors[key]:
                temp = copy.deepcopy(self.colors[key][currname])
                self.colors[key][newname] = temp
                del self.colors[key][currname]
        print ('renamed')
        #would also need to resolve all refs to this rec in formulas here!

        return

    def getRecordAttributeAtColumn(self, rowIndex=None, columnIndex=None,
                                        recName=None, columnName=None):
         """Get the attribute of the record at the specified column index.
            This determines what will be displayed in the cell"""

         value = None
         if columnName != None and recName != None:
             if columnName not in self.data[recName]:
                 return ''
             cell = self.data[recName][columnName]
         else:
             cell = self.getCellRecord(rowIndex, columnIndex)
             columnName = self.getColumnName(columnIndex)
         if cell == None:
             cell=''
         # Set the value based on the data record field
         coltype = self.columntypes[columnName]
         if Formula.isFormula(cell) == True:
             value = self.doFormula(cell)
             return value

         if not type(cell) is dict:
             if coltype == 'text' or coltype == 'Text':
                 value = cell
             elif coltype == 'number':
                 value = str(cell)
             else:
                 value = 'other'
         if value==None:
             value=''
         return value

    def getRecordIndex(self, recname):
        rowIndex = int(self.reclist.index(recname))
        return rowIndex

    def setSortOrder(self, columnIndex=None, columnName=None, reverse=0):
        """Changes the order that records are sorted in, which will
           be reflected in the table upon redrawing"""

        if columnName != None and columnName in self.columnNames:
            self.sortkey = columnName
        elif columnIndex != None:
            self.sortkey = self.getColumnName(columnIndex)
        else:
            return
        self.reclist = list(self.createSortMap(self.reclist, self.sortkey, reverse))
        if self.filteredrecs != None:
            self.filteredrecs = self.createSortMap(self.filteredrecs, self.sortkey, reverse)
        return

    def createSortMap(self, names, sortkey, reverse=0):
        """Create a sort mapping for given list"""

        recdata = []
        for rec in names:
            recdata.append(self.getRecordAttributeAtColumn(recName=rec, columnName=sortkey))
        #try create list of floats if col has numbers only
        try:
            recdata = self.toFloats(recdata)
        except:
            pass
        smap = zip(names, recdata)
        #sort the mapping by the second key
        smap = sorted(smap, key=operator.itemgetter(1), reverse=reverse)
        #now sort the main reclist by the mapping order
        sortmap = map(operator.itemgetter(0), smap)
        return sortmap

    def toFloats(self, l):
        x=[]
        for i in l:
            if i == '':
                x.append(0.0)
            else:
                x.append(float(i))
        return x

    '''def getSortIndex(self):
        """Return the current sort order index"""
        if self.sortcolumnIndex:
            return self.sortcolumnIndex
        else:
            return 0'''

    def moveColumn(self, oldcolumnIndex, newcolumnIndex):
        """Changes the order of columns"""
        self.oldnames = self.columnNames
        self.columnNames=[]

        #write out a new column names list - tedious
        moved = self.oldnames[oldcolumnIndex]
        del self.oldnames[oldcolumnIndex]
        #print self.oldnames
        i=0
        for c in self.oldnames:
            if i==newcolumnIndex:
                self.columnNames.append(moved)
            self.columnNames.append(c)
            i=i+1
        #if new col is at end just append
        if moved not in self.columnNames:
            self.columnNames.append(moved)
        return

    def getNextKey(self):
        """Return the next numeric key in the dict"""
        num = len(self.reclist)+1
        return num

    def addRow(self, key=None, **kwargs):
        """Add a row"""
        if key == '':
            return
        if key==None:
            key = self.getNextKey()
        if key in self.data or key in self.reclist:
            print ('name already present!!')
            return
        self.data[key]={}
        for k in kwargs:
            if not k in self.columnNames:
                self.addColumn(k)
            self.data[key][k] = str(kwargs[k])
        self.reclist.append(key)
        return key

    def deleteRow(self, rowIndex=None, key=None, update=True):
        """Delete a row"""
        if key == None or not key in self.reclist:
            key = self.getRecName(rowIndex)
        del self.data[key]
        if update==True:
            self.reclist.remove(key)
        return

    def deleteRows(self, rowlist=None):
        """Delete multiple or all rows"""
        if rowlist == None:
            rowlist = range(len(self.reclist))
        names = [self.getRecName(i) for i in rowlist]
        for name in names:
            self.deleteRow(key=name, update=True)
        return

    def addColumn(self, colname=None, coltype=None):
        """Add a column"""
        index = self.getColumnCount()+ 1
        if colname == None:
            colname=str(index)
        if colname in self.columnNames:
            #print 'name is present!'
            return
        self.columnNames.append(colname)
        self.columnlabels[colname] = colname
        if coltype == None:
            self.columntypes[colname]='text'
        else:
            self.columntypes[colname]=coltype
        return

    def deleteColumn(self, columnIndex):
        """delete a column"""
        colname = self.getColumnName(columnIndex)
        self.columnNames.remove(colname)
        del self.columnlabels[colname]
        del self.columntypes[colname]
        #remove this field from every record
        for recname in self.reclist:
            if colname in self.data[recname]:
                del self.data[recname][colname]
        if self.sortkey != None:
            currIndex = self.getColumnIndex(self.sortkey)
            if columnIndex == currIndex:
                self.setSortOrder(0)
        #print 'column deleted'
        #print 'new cols:', self.columnNames
        return

    def deleteColumns(self, cols=None):
        """Remove all cols or list provided"""
        if cols == None:
            cols = self.columnNames
        if self.getColumnCount() == 0:
            return
        for col in cols:
            self.deleteColumn(col)
        return

    def autoAddRows(self, numrows=None):
        """Automatically add x number of records"""
        rows = self.getRowCount()
        ints = [i for i in self.reclist if isinstance(i, int)]
        if len(ints)>0:
            start = max(ints)+1
        else:
            start = 0
        #we don't use addRow as it's too slow
        keys = range(start,start+numrows)
        #make sure no keys are present already
        keys = list(set(keys)-set(self.reclist))
        newdata = {}
        for k in keys:
            newdata[k] = {}
        self.data.update(newdata)
        self.reclist.extend(newdata.keys())
        return keys

    def autoAddColumns(self, numcols=None):
        """Automatically add x number of cols"""

        #alphabet = string.lowercase[:26]
        alphabet = string.ascii_lowercase
        currcols=self.getColumnCount()
        #find where to start
        start = currcols + 1
        end = currcols + numcols + 1
        new = []
        for n in range(start, end):
            new.append(str(n))
        #check if any of these colnames present
        common = set(new) & set(self.columnNames)
        extra = len(common)
        end = end + extra
        for x in range(start, end):
            self.addColumn(str(x))
        return

    def relabel_Column(self, columnIndex, newname):
        """Change the column label - can be used in a table header"""
        colname = self.getColumnName(columnIndex)
        self.columnlabels[colname]=newname
        return

    def getColumnType(self, columnIndex):
        """Get the column type"""
        colname = self.getColumnName(columnIndex)
        coltype = self.columntypes[colname]
        return coltype

    def getColumnCount(self):
         """Returns the number of columns in the data model."""
         return len(self.columnNames)

    def getColumnName(self, columnIndex):
         """Returns the name of the given column by columnIndex."""
         return self.columnNames[columnIndex]

    def getColumnLabel(self, columnIndex):
        """Returns the label for this column"""
        colname = self.getColumnName(columnIndex)
        return self.columnlabels[colname]

    def getColumnIndex(self, columnName):
        """Returns the column index for this column"""
        colindex = self.columnNames.index(columnName)
        return colindex

    def getColumnData(self, columnIndex=None, columnName=None,
                        filters=None):
        """Return the data in a list for this col,
            filters is a tuple of the form (key,value,operator,bool)"""
        if columnIndex != None and columnIndex < len(self.columnNames):
            columnName = self.getColumnName(columnIndex)
        names = Filtering.doFiltering(searchfunc=self.filterBy,
                                         filters=filters)
        coldata = [self.data[n][columnName] for n in names]
        return coldata

    def getColumns(self, colnames, filters=None, allowempty=True):
        """Get column data for multiple cols, with given filter options,
            filterby: list of tuples of the form (key,value,operator,bool)
            allowempty: boolean if false means rows with empty vals for any
            required fields are not returned
            returns: lists of column data"""

        def evaluate(l):
            for i in l:
                if i == '' or i == None:
                    return False
            return True
        coldata=[]
        for c in colnames:
            vals = self.getColumnData(columnName=c, filters=filters)
            coldata.append(vals)
        if allowempty == False:
            result = [i for i in zip(*coldata) if evaluate(i) == True]
            coldata = zip(*result)
        return coldata

    def getDict(self, colnames, filters=None):
        """Get the model data as a dict for given columns with filter options"""
        data={}
        names = self.reclist
        cols = self.getColumns(colnames, filters)
        coldata = zip(*cols)
        for name,cdata in zip(names, coldata):
            data[name] = dict(zip(colnames,cdata))
        return data

    def filterBy(self, filtercol, value, op='contains', userecnames=False,
                     progresscallback=None):
        """The searching function that we apply to the model data.
           This is used in Filtering.doFiltering to find the required recs
           according to column, value and an operator"""

        funcs = Filtering.operatornames
        floatops = ['=','>','<']
        func = funcs[op]
        data = self.data
        #coltype = self.columntypes[filtercol]
        names=[]
        for rec in self.reclist:
            if filtercol in data[rec]:
                #try to do float comparisons if required
                if op in floatops:
                    try:
                        #print float(data[rec][filtercol])
                        item = float(data[rec][filtercol])
                        v = float(value)
                        if func(v, item) == True:
                            names.append(rec)
                        continue
                    except:
                        pass
                if filtercol == 'name' and userecnames == True:
                    item = rec
                else:
                    item = str(data[rec][filtercol])
                if func(value, item):
                    names.append(rec)
        return names

    def getRowCount(self):
         """Returns the number of rows in the table model."""
         return len(self.reclist)

    def getValueAt(self, rowIndex, columnIndex):
         """Returns the cell value at location specified
             by columnIndex and rowIndex."""
         value = self.getRecordAttributeAtColumn(rowIndex, columnIndex)
         return value

    def setValueAt(self, value, rowIndex, columnIndex):
        """Changed the dictionary when cell is updated by user"""

        name = self.getRecName(rowIndex)
        colname = self.getColumnName(columnIndex)
        coltype = self.columntypes[colname]
        if coltype == 'number':
            try:
                if value == '': #need this to allow deletion of values
                    self.data[name][colname] = ''
                else:
                    self.data[name][colname] = float(value)
            except:
                pass
        else:
            self.data[name][colname] = value
        return

    def setFormulaAt(self, f, rowIndex, columnIndex):
        """Set a formula at cell given"""
        name = self.getRecName(rowIndex)
        colname = self.getColumnName(columnIndex)
        coltype = self.columntypes[colname]
        rec = {}
        rec['formula'] = f
        self.data[name][colname] = rec
        return

    def getColorAt(self, rowIndex, columnIndex, key='bg'):
        """Return color of that record field for the table"""
        name = self.getRecName(rowIndex)
        colname = self.getColumnName(columnIndex)
        if name in self.colors[key] and colname in self.colors[key][name]:
            return self.colors[key][name][colname]
        else:
            return None

    def setColorAt(self, rowIndex, columnIndex, color, key='bg'):
        """Set color"""
        name = self.getRecName(rowIndex)
        colname = self.getColumnName(columnIndex)
        if not name in self.colors[key]:
            self.colors[key][name] = {}
        self.colors[key][name][colname] = str(color)
        return

    def resetcolors(self):
        """Remove all color formatting"""
        self.colors={}
        self.colors['fg']={}
        self.colors['bg']={}
        return

    def getRecColNames(self, rowIndex, ColIndex):
        """Returns the rec and col name as a tuple"""
        recname = self.getRecName(rowIndex)
        colname = self.getColumnName(ColIndex)
        return (recname, colname)

    def getRecAtRow(self, recname, colname, offset=1, dim='y'):
        """Get the record name at a specified offset in the current
           table from the record given, by using the current sort order"""
        thisrow = self.getRecordIndex(recname)
        thiscol = self.getColumnIndex(colname)
        #table goto next row
        if dim == 'y':
            nrow = thisrow + offset
            ncol = thiscol
        else:
            nrow = thisrow
            ncol = thiscol + offset

        newrecname, newcolname = self.getRecColNames(nrow, ncol)
        print ('recname, colname', recname, colname)
        print ('thisrow, col', thisrow, thiscol)
        return newrecname, newcolname

    def appendtoFormula(self, formula, rowIndex, colIndex):
        """Add the input cell to the formula"""
        cellRec = getRecColNames(rowIndex, colIndex)
        formula.append(cellRec)
        return

    def doFormula(self, cellformula):
        """Evaluate the formula for a cell and return the result"""
        value = Formula.doFormula(cellformula, self.data)
        return value

    def copyFormula(self, cellval, row, col, offset=1, dim='y'):
        """Copy a formula down or across, using the provided offset"""
        import re
        frmla = Formula.getFormula(cellval)
        #print 'formula', frmla

        newcells=[]
        cells, ops = Formula.readExpression(frmla)

        for c in cells:
            print (c)
            if type(c) is not ListType:
                nc = c
            else:
                recname = c[0]
                colname = c[1]
                nc = list(self.getRecAtRow(recname, colname, offset, dim=dim))
            newcells.append(nc)
        newformula = Formula.doExpression(newcells, ops, getvalues=False)
        return newformula

    def merge(self, model, key='name', fields=None):
        """Merge another table model with this one based on a key field,
           we only add records from the new model where the key is present
           in both models"""
        if fields == None: fields = model.columnNames
        for rec in self.reclist:
            if not key in self.data[rec]:
                continue
            for new in model.reclist:
                if not key in model.data[new]:
                    continue
                if self.data[rec][key] == model.data[new][key]:
                #if new == rec:
                    for f in fields:
                        if not f in model.data[rec]:
                            continue
                        if not f in self.columnNames:
                            self.addColumn(f)
                        self.data[rec][f] = model.data[rec][f]
        return

    def save(self, filename=None):
        """Save model to file"""
        if filename == None:
            return
        data = self.getData()
        fd = open(filename,'wb')
        pickle.dump(data,fd)
        fd.close()
        return

    def load(self, filename):
        """Load model from pickle file"""
        fd=open(filename,'rb')
        data = pickle.load(fd)
        self.setupModel(data)
        return

    def copy(self):
        """Return a copy of this model"""
        M = TableModel()
        data = self.getData()
        M.setupModel(data)
        return M

    def __repr__(self):
        return 'Table Model with %s rows' %len(self.reclist)

Classes

class TableModel (newdict=None, rows=None, columns=None)

A base model for managing the data in a TableCanvas class

Constructor

Source code
class TableModel(object):
    """A base model for managing the data in a TableCanvas class"""

    keywords = {'columnnames':'columnNames', 'columntypes':'columntypes',
               'columnlabels':'columnlabels', 'columnorder':'columnOrder',
               'colors':'colors'}

    def __init__(self, newdict=None, rows=None, columns=None):
        """Constructor"""
        self.initialiseFields()
        self.setupModel(newdict, rows, columns)
        return

    def setupModel(self, newdict, rows=None, columns=None):
        """Create table model"""

        if newdict != None:
            self.data = copy.deepcopy(newdict)
            for k in self.keywords:
                if k in self.data:
                    self.__dict__[self.keywords[k]] = self.data[k]
                    del self.data[k]
            #read in the record list order
            if 'reclist' in self.data:
                temp = self.data['reclist']
                del self.data['reclist']
                self.reclist = temp
            else:
                self.reclist = self.data.keys()
        else:
            #just make a new empty model
            self.createEmptyModel()

        if not set(self.reclist) == set(self.data.keys()):
            print ('reclist does not match data keys')
        #restore last column order
        if hasattr(self, 'columnOrder') and self.columnOrder != None:
            self.columnNames=[]
            for i in self.columnOrder.keys():
                self.columnNames.append(self.columnOrder[i])
                i=i+1
        self.defaulttypes = ['text', 'number']
        #setup default display for column types
        self.default_display = {'text' : 'showstring',
                                'number' : 'numtostring'}
        #set default sort order as first col
        if len(self.columnNames)>0:
            self.sortkey = self.columnNames[0]
        else:
            self.sortkey = None
        #add rows and cols if they are given in the constructor
        if newdict == None:
            if rows != None:
                self.autoAddRows(rows)
            if columns != None:
                self.autoAddColumns(columns)
        self.filteredrecs = None
        return

    def initialiseFields(self):
        """Create base fields, some of which are not saved"""
        self.data = None    # holds the table dict
        self.colors = {}    # holds cell colors
        self.colors['fg']={}
        self.colors['bg']={}
        #default types
        self.defaulttypes = ['text', 'number']
        #list of editable column types
        self.editable={}
        self.nodisplay = []
        self.columnwidths={}  #used to store col widths, not held in saved data
        return

    def createEmptyModel(self):
        """Create the basic empty model dict"""

        self.data = {}
        # Define the starting column names and locations in the table.
        self.columnNames = []
        self.columntypes = {}
        self.columnOrder = None
        #record column labels for use in a table header
        self.columnlabels={}
        for colname in self.columnNames:
            self.columnlabels[colname]=colname
        self.reclist = list(self.data.keys())
        return

    def importCSV(self, filename, sep=','):
        """Import table data from a comma separated file."""

        if not os.path.isfile(filename) or not os.path.exists(filename):
            print ('no such file')
            return None

        #takes first row as field names
        dictreader = csv.DictReader(open(filename, "r"), delimiter=sep)
        dictdata = {}
        count=0
        for rec in dictreader:
            dictdata[count]=rec
            count=count+1
        self.importDict(dictdata)
        return

    def importDict(self, newdata):
        """Try to create a table model from a dict of the form
           {{'rec1': {'col1': 3, 'col2': 2}, ..}"""

        #get cols from sub data keys
        colnames = []
        for k in newdata:
            fields = newdata[k].keys()
            for f in fields:
                if not f in colnames:
                    colnames.append(f)
        for c in colnames:
            self.addColumn(c)
        #add the data
        self.data.update(newdata)
        self.reclist = list(self.data.keys())
        return

    def getDefaultTypes(self):
        """Get possible field types for this table model"""
        return self.defaulttypes

    def getData(self):
        """Return the current data for saving"""

        data = copy.deepcopy(self.data)
        data['colors'] = self.colors
        data['columnnames'] = self.columnNames
        #we keep original record order
        data['reclist'] = self.reclist
        #record current col order
        data['columnorder']={}
        i=0
        for name in self.columnNames:
            data['columnorder'][i] = name
            i=i+1
        data['columntypes'] = self.columntypes
        data['columnlabels'] = self.columnlabels
        return data

    def getAllCells(self):
        """Return a dict of the form rowname: list of cell contents
          Useful for a simple table export for example"""

        records={}
        for row in range(len(self.reclist)):
            recdata=[]
            for col in range(len(self.columnNames)):
                recdata.append(self.getValueAt(row,col))
            records[row]=recdata
        return records

    def getColCells(self, colIndex):
        """Get the viewable contents of a col into a list"""

        collist = []
        if self.getColumnType(colIndex) == 'Link':
            return ['xxxxxx']
        else:
            for row in range(len(self.reclist)):
                v = self.getValueAt(row, colIndex)
                collist.append(v)
        return collist

    def getlongestEntry(self, columnIndex):
        """Get the longest cell entry in the col"""

        collist = self.getColCells(columnIndex)
        maxw=5
        for c in collist:
            try:
                w = len(str(c))
            except UnicodeEncodeError:
                pass
            if w > maxw:
                maxw = w
        #print 'longest width', maxw
        return maxw

    def getRecordAtRow(self, rowIndex):
        """Get the entire record at the specifed row."""

        name = self.getRecName(rowIndex)
        record = self.data[name]
        return record

    def getCellRecord(self, rowIndex, columnIndex):
        """Get the data held in this row and column"""

        value = None
        colname = self.getColumnName(columnIndex)
        coltype = self.columntypes[colname]
        name = self.getRecName(rowIndex)
        #print self.data[name]
        if colname in self.data[name]:
            celldata=self.data[name][colname]
        else:
            celldata=None
        return celldata

    def deleteCellRecord(self, rowIndex, columnIndex):
        """Remove the cell data at this row/column"""

        colname = self.getColumnName(columnIndex)
        coltype = self.columntypes[colname]
        name = self.getRecName(rowIndex)
        if colname in self.data[name]:
            del self.data[name][colname]
        return

    def getRecName(self, rowIndex):
        """Get record name from row number"""

        if len(self.reclist)==0:
            return None
        if self.filteredrecs != None:
            name = self.filteredrecs[rowIndex]
        else:
            name = self.reclist[rowIndex]
        return name

    def setRecName(self, newname, rowIndex):
        """Set the record name to another value - requires re-setting in all
           dicts that this rec is referenced"""

        if len(self.reclist)==0:
            return None
        currname = self.getRecName(rowIndex)
        self.reclist[rowIndex] = newname
        temp = copy.deepcopy(self.data[currname])
        self.data[newname] = temp
        #self.data[newname]['Name'] = newname
        del self.data[currname]
        for key in ['bg', 'fg']:
            if currname in self.colors[key]:
                temp = copy.deepcopy(self.colors[key][currname])
                self.colors[key][newname] = temp
                del self.colors[key][currname]
        print ('renamed')
        #would also need to resolve all refs to this rec in formulas here!

        return

    def getRecordAttributeAtColumn(self, rowIndex=None, columnIndex=None,
                                        recName=None, columnName=None):
         """Get the attribute of the record at the specified column index.
            This determines what will be displayed in the cell"""

         value = None
         if columnName != None and recName != None:
             if columnName not in self.data[recName]:
                 return ''
             cell = self.data[recName][columnName]
         else:
             cell = self.getCellRecord(rowIndex, columnIndex)
             columnName = self.getColumnName(columnIndex)
         if cell == None:
             cell=''
         # Set the value based on the data record field
         coltype = self.columntypes[columnName]
         if Formula.isFormula(cell) == True:
             value = self.doFormula(cell)
             return value

         if not type(cell) is dict:
             if coltype == 'text' or coltype == 'Text':
                 value = cell
             elif coltype == 'number':
                 value = str(cell)
             else:
                 value = 'other'
         if value==None:
             value=''
         return value

    def getRecordIndex(self, recname):
        rowIndex = int(self.reclist.index(recname))
        return rowIndex

    def setSortOrder(self, columnIndex=None, columnName=None, reverse=0):
        """Changes the order that records are sorted in, which will
           be reflected in the table upon redrawing"""

        if columnName != None and columnName in self.columnNames:
            self.sortkey = columnName
        elif columnIndex != None:
            self.sortkey = self.getColumnName(columnIndex)
        else:
            return
        self.reclist = list(self.createSortMap(self.reclist, self.sortkey, reverse))
        if self.filteredrecs != None:
            self.filteredrecs = self.createSortMap(self.filteredrecs, self.sortkey, reverse)
        return

    def createSortMap(self, names, sortkey, reverse=0):
        """Create a sort mapping for given list"""

        recdata = []
        for rec in names:
            recdata.append(self.getRecordAttributeAtColumn(recName=rec, columnName=sortkey))
        #try create list of floats if col has numbers only
        try:
            recdata = self.toFloats(recdata)
        except:
            pass
        smap = zip(names, recdata)
        #sort the mapping by the second key
        smap = sorted(smap, key=operator.itemgetter(1), reverse=reverse)
        #now sort the main reclist by the mapping order
        sortmap = map(operator.itemgetter(0), smap)
        return sortmap

    def toFloats(self, l):
        x=[]
        for i in l:
            if i == '':
                x.append(0.0)
            else:
                x.append(float(i))
        return x

    '''def getSortIndex(self):
        """Return the current sort order index"""
        if self.sortcolumnIndex:
            return self.sortcolumnIndex
        else:
            return 0'''

    def moveColumn(self, oldcolumnIndex, newcolumnIndex):
        """Changes the order of columns"""
        self.oldnames = self.columnNames
        self.columnNames=[]

        #write out a new column names list - tedious
        moved = self.oldnames[oldcolumnIndex]
        del self.oldnames[oldcolumnIndex]
        #print self.oldnames
        i=0
        for c in self.oldnames:
            if i==newcolumnIndex:
                self.columnNames.append(moved)
            self.columnNames.append(c)
            i=i+1
        #if new col is at end just append
        if moved not in self.columnNames:
            self.columnNames.append(moved)
        return

    def getNextKey(self):
        """Return the next numeric key in the dict"""
        num = len(self.reclist)+1
        return num

    def addRow(self, key=None, **kwargs):
        """Add a row"""
        if key == '':
            return
        if key==None:
            key = self.getNextKey()
        if key in self.data or key in self.reclist:
            print ('name already present!!')
            return
        self.data[key]={}
        for k in kwargs:
            if not k in self.columnNames:
                self.addColumn(k)
            self.data[key][k] = str(kwargs[k])
        self.reclist.append(key)
        return key

    def deleteRow(self, rowIndex=None, key=None, update=True):
        """Delete a row"""
        if key == None or not key in self.reclist:
            key = self.getRecName(rowIndex)
        del self.data[key]
        if update==True:
            self.reclist.remove(key)
        return

    def deleteRows(self, rowlist=None):
        """Delete multiple or all rows"""
        if rowlist == None:
            rowlist = range(len(self.reclist))
        names = [self.getRecName(i) for i in rowlist]
        for name in names:
            self.deleteRow(key=name, update=True)
        return

    def addColumn(self, colname=None, coltype=None):
        """Add a column"""
        index = self.getColumnCount()+ 1
        if colname == None:
            colname=str(index)
        if colname in self.columnNames:
            #print 'name is present!'
            return
        self.columnNames.append(colname)
        self.columnlabels[colname] = colname
        if coltype == None:
            self.columntypes[colname]='text'
        else:
            self.columntypes[colname]=coltype
        return

    def deleteColumn(self, columnIndex):
        """delete a column"""
        colname = self.getColumnName(columnIndex)
        self.columnNames.remove(colname)
        del self.columnlabels[colname]
        del self.columntypes[colname]
        #remove this field from every record
        for recname in self.reclist:
            if colname in self.data[recname]:
                del self.data[recname][colname]
        if self.sortkey != None:
            currIndex = self.getColumnIndex(self.sortkey)
            if columnIndex == currIndex:
                self.setSortOrder(0)
        #print 'column deleted'
        #print 'new cols:', self.columnNames
        return

    def deleteColumns(self, cols=None):
        """Remove all cols or list provided"""
        if cols == None:
            cols = self.columnNames
        if self.getColumnCount() == 0:
            return
        for col in cols:
            self.deleteColumn(col)
        return

    def autoAddRows(self, numrows=None):
        """Automatically add x number of records"""
        rows = self.getRowCount()
        ints = [i for i in self.reclist if isinstance(i, int)]
        if len(ints)>0:
            start = max(ints)+1
        else:
            start = 0
        #we don't use addRow as it's too slow
        keys = range(start,start+numrows)
        #make sure no keys are present already
        keys = list(set(keys)-set(self.reclist))
        newdata = {}
        for k in keys:
            newdata[k] = {}
        self.data.update(newdata)
        self.reclist.extend(newdata.keys())
        return keys

    def autoAddColumns(self, numcols=None):
        """Automatically add x number of cols"""

        #alphabet = string.lowercase[:26]
        alphabet = string.ascii_lowercase
        currcols=self.getColumnCount()
        #find where to start
        start = currcols + 1
        end = currcols + numcols + 1
        new = []
        for n in range(start, end):
            new.append(str(n))
        #check if any of these colnames present
        common = set(new) & set(self.columnNames)
        extra = len(common)
        end = end + extra
        for x in range(start, end):
            self.addColumn(str(x))
        return

    def relabel_Column(self, columnIndex, newname):
        """Change the column label - can be used in a table header"""
        colname = self.getColumnName(columnIndex)
        self.columnlabels[colname]=newname
        return

    def getColumnType(self, columnIndex):
        """Get the column type"""
        colname = self.getColumnName(columnIndex)
        coltype = self.columntypes[colname]
        return coltype

    def getColumnCount(self):
         """Returns the number of columns in the data model."""
         return len(self.columnNames)

    def getColumnName(self, columnIndex):
         """Returns the name of the given column by columnIndex."""
         return self.columnNames[columnIndex]

    def getColumnLabel(self, columnIndex):
        """Returns the label for this column"""
        colname = self.getColumnName(columnIndex)
        return self.columnlabels[colname]

    def getColumnIndex(self, columnName):
        """Returns the column index for this column"""
        colindex = self.columnNames.index(columnName)
        return colindex

    def getColumnData(self, columnIndex=None, columnName=None,
                        filters=None):
        """Return the data in a list for this col,
            filters is a tuple of the form (key,value,operator,bool)"""
        if columnIndex != None and columnIndex < len(self.columnNames):
            columnName = self.getColumnName(columnIndex)
        names = Filtering.doFiltering(searchfunc=self.filterBy,
                                         filters=filters)
        coldata = [self.data[n][columnName] for n in names]
        return coldata

    def getColumns(self, colnames, filters=None, allowempty=True):
        """Get column data for multiple cols, with given filter options,
            filterby: list of tuples of the form (key,value,operator,bool)
            allowempty: boolean if false means rows with empty vals for any
            required fields are not returned
            returns: lists of column data"""

        def evaluate(l):
            for i in l:
                if i == '' or i == None:
                    return False
            return True
        coldata=[]
        for c in colnames:
            vals = self.getColumnData(columnName=c, filters=filters)
            coldata.append(vals)
        if allowempty == False:
            result = [i for i in zip(*coldata) if evaluate(i) == True]
            coldata = zip(*result)
        return coldata

    def getDict(self, colnames, filters=None):
        """Get the model data as a dict for given columns with filter options"""
        data={}
        names = self.reclist
        cols = self.getColumns(colnames, filters)
        coldata = zip(*cols)
        for name,cdata in zip(names, coldata):
            data[name] = dict(zip(colnames,cdata))
        return data

    def filterBy(self, filtercol, value, op='contains', userecnames=False,
                     progresscallback=None):
        """The searching function that we apply to the model data.
           This is used in Filtering.doFiltering to find the required recs
           according to column, value and an operator"""

        funcs = Filtering.operatornames
        floatops = ['=','>','<']
        func = funcs[op]
        data = self.data
        #coltype = self.columntypes[filtercol]
        names=[]
        for rec in self.reclist:
            if filtercol in data[rec]:
                #try to do float comparisons if required
                if op in floatops:
                    try:
                        #print float(data[rec][filtercol])
                        item = float(data[rec][filtercol])
                        v = float(value)
                        if func(v, item) == True:
                            names.append(rec)
                        continue
                    except:
                        pass
                if filtercol == 'name' and userecnames == True:
                    item = rec
                else:
                    item = str(data[rec][filtercol])
                if func(value, item):
                    names.append(rec)
        return names

    def getRowCount(self):
         """Returns the number of rows in the table model."""
         return len(self.reclist)

    def getValueAt(self, rowIndex, columnIndex):
         """Returns the cell value at location specified
             by columnIndex and rowIndex."""
         value = self.getRecordAttributeAtColumn(rowIndex, columnIndex)
         return value

    def setValueAt(self, value, rowIndex, columnIndex):
        """Changed the dictionary when cell is updated by user"""

        name = self.getRecName(rowIndex)
        colname = self.getColumnName(columnIndex)
        coltype = self.columntypes[colname]
        if coltype == 'number':
            try:
                if value == '': #need this to allow deletion of values
                    self.data[name][colname] = ''
                else:
                    self.data[name][colname] = float(value)
            except:
                pass
        else:
            self.data[name][colname] = value
        return

    def setFormulaAt(self, f, rowIndex, columnIndex):
        """Set a formula at cell given"""
        name = self.getRecName(rowIndex)
        colname = self.getColumnName(columnIndex)
        coltype = self.columntypes[colname]
        rec = {}
        rec['formula'] = f
        self.data[name][colname] = rec
        return

    def getColorAt(self, rowIndex, columnIndex, key='bg'):
        """Return color of that record field for the table"""
        name = self.getRecName(rowIndex)
        colname = self.getColumnName(columnIndex)
        if name in self.colors[key] and colname in self.colors[key][name]:
            return self.colors[key][name][colname]
        else:
            return None

    def setColorAt(self, rowIndex, columnIndex, color, key='bg'):
        """Set color"""
        name = self.getRecName(rowIndex)
        colname = self.getColumnName(columnIndex)
        if not name in self.colors[key]:
            self.colors[key][name] = {}
        self.colors[key][name][colname] = str(color)
        return

    def resetcolors(self):
        """Remove all color formatting"""
        self.colors={}
        self.colors['fg']={}
        self.colors['bg']={}
        return

    def getRecColNames(self, rowIndex, ColIndex):
        """Returns the rec and col name as a tuple"""
        recname = self.getRecName(rowIndex)
        colname = self.getColumnName(ColIndex)
        return (recname, colname)

    def getRecAtRow(self, recname, colname, offset=1, dim='y'):
        """Get the record name at a specified offset in the current
           table from the record given, by using the current sort order"""
        thisrow = self.getRecordIndex(recname)
        thiscol = self.getColumnIndex(colname)
        #table goto next row
        if dim == 'y':
            nrow = thisrow + offset
            ncol = thiscol
        else:
            nrow = thisrow
            ncol = thiscol + offset

        newrecname, newcolname = self.getRecColNames(nrow, ncol)
        print ('recname, colname', recname, colname)
        print ('thisrow, col', thisrow, thiscol)
        return newrecname, newcolname

    def appendtoFormula(self, formula, rowIndex, colIndex):
        """Add the input cell to the formula"""
        cellRec = getRecColNames(rowIndex, colIndex)
        formula.append(cellRec)
        return

    def doFormula(self, cellformula):
        """Evaluate the formula for a cell and return the result"""
        value = Formula.doFormula(cellformula, self.data)
        return value

    def copyFormula(self, cellval, row, col, offset=1, dim='y'):
        """Copy a formula down or across, using the provided offset"""
        import re
        frmla = Formula.getFormula(cellval)
        #print 'formula', frmla

        newcells=[]
        cells, ops = Formula.readExpression(frmla)

        for c in cells:
            print (c)
            if type(c) is not ListType:
                nc = c
            else:
                recname = c[0]
                colname = c[1]
                nc = list(self.getRecAtRow(recname, colname, offset, dim=dim))
            newcells.append(nc)
        newformula = Formula.doExpression(newcells, ops, getvalues=False)
        return newformula

    def merge(self, model, key='name', fields=None):
        """Merge another table model with this one based on a key field,
           we only add records from the new model where the key is present
           in both models"""
        if fields == None: fields = model.columnNames
        for rec in self.reclist:
            if not key in self.data[rec]:
                continue
            for new in model.reclist:
                if not key in model.data[new]:
                    continue
                if self.data[rec][key] == model.data[new][key]:
                #if new == rec:
                    for f in fields:
                        if not f in model.data[rec]:
                            continue
                        if not f in self.columnNames:
                            self.addColumn(f)
                        self.data[rec][f] = model.data[rec][f]
        return

    def save(self, filename=None):
        """Save model to file"""
        if filename == None:
            return
        data = self.getData()
        fd = open(filename,'wb')
        pickle.dump(data,fd)
        fd.close()
        return

    def load(self, filename):
        """Load model from pickle file"""
        fd=open(filename,'rb')
        data = pickle.load(fd)
        self.setupModel(data)
        return

    def copy(self):
        """Return a copy of this model"""
        M = TableModel()
        data = self.getData()
        M.setupModel(data)
        return M

    def __repr__(self):
        return 'Table Model with %s rows' %len(self.reclist)

Class variables

var keywords

Methods

def addColumn(self, colname=None, coltype=None)

Add a column

Source code
def addColumn(self, colname=None, coltype=None):
    """Add a column"""
    index = self.getColumnCount()+ 1
    if colname == None:
        colname=str(index)
    if colname in self.columnNames:
        #print 'name is present!'
        return
    self.columnNames.append(colname)
    self.columnlabels[colname] = colname
    if coltype == None:
        self.columntypes[colname]='text'
    else:
        self.columntypes[colname]=coltype
    return
def addRow(self, key=None, **kwargs)

Add a row

Source code
def addRow(self, key=None, **kwargs):
    """Add a row"""
    if key == '':
        return
    if key==None:
        key = self.getNextKey()
    if key in self.data or key in self.reclist:
        print ('name already present!!')
        return
    self.data[key]={}
    for k in kwargs:
        if not k in self.columnNames:
            self.addColumn(k)
        self.data[key][k] = str(kwargs[k])
    self.reclist.append(key)
    return key
def appendtoFormula(self, formula, rowIndex, colIndex)

Add the input cell to the formula

Source code
def appendtoFormula(self, formula, rowIndex, colIndex):
    """Add the input cell to the formula"""
    cellRec = getRecColNames(rowIndex, colIndex)
    formula.append(cellRec)
    return
def autoAddColumns(self, numcols=None)

Automatically add x number of cols

Source code
def autoAddColumns(self, numcols=None):
    """Automatically add x number of cols"""

    #alphabet = string.lowercase[:26]
    alphabet = string.ascii_lowercase
    currcols=self.getColumnCount()
    #find where to start
    start = currcols + 1
    end = currcols + numcols + 1
    new = []
    for n in range(start, end):
        new.append(str(n))
    #check if any of these colnames present
    common = set(new) & set(self.columnNames)
    extra = len(common)
    end = end + extra
    for x in range(start, end):
        self.addColumn(str(x))
    return
def autoAddRows(self, numrows=None)

Automatically add x number of records

Source code
def autoAddRows(self, numrows=None):
    """Automatically add x number of records"""
    rows = self.getRowCount()
    ints = [i for i in self.reclist if isinstance(i, int)]
    if len(ints)>0:
        start = max(ints)+1
    else:
        start = 0
    #we don't use addRow as it's too slow
    keys = range(start,start+numrows)
    #make sure no keys are present already
    keys = list(set(keys)-set(self.reclist))
    newdata = {}
    for k in keys:
        newdata[k] = {}
    self.data.update(newdata)
    self.reclist.extend(newdata.keys())
    return keys
def copy(self)

Return a copy of this model

Source code
def copy(self):
    """Return a copy of this model"""
    M = TableModel()
    data = self.getData()
    M.setupModel(data)
    return M
def copyFormula(self, cellval, row, col, offset=1, dim='y')

Copy a formula down or across, using the provided offset

Source code
def copyFormula(self, cellval, row, col, offset=1, dim='y'):
    """Copy a formula down or across, using the provided offset"""
    import re
    frmla = Formula.getFormula(cellval)
    #print 'formula', frmla

    newcells=[]
    cells, ops = Formula.readExpression(frmla)

    for c in cells:
        print (c)
        if type(c) is not ListType:
            nc = c
        else:
            recname = c[0]
            colname = c[1]
            nc = list(self.getRecAtRow(recname, colname, offset, dim=dim))
        newcells.append(nc)
    newformula = Formula.doExpression(newcells, ops, getvalues=False)
    return newformula
def createEmptyModel(self)

Create the basic empty model dict

Source code
def createEmptyModel(self):
    """Create the basic empty model dict"""

    self.data = {}
    # Define the starting column names and locations in the table.
    self.columnNames = []
    self.columntypes = {}
    self.columnOrder = None
    #record column labels for use in a table header
    self.columnlabels={}
    for colname in self.columnNames:
        self.columnlabels[colname]=colname
    self.reclist = list(self.data.keys())
    return
def createSortMap(self, names, sortkey, reverse=0)

Create a sort mapping for given list

Source code
def createSortMap(self, names, sortkey, reverse=0):
    """Create a sort mapping for given list"""

    recdata = []
    for rec in names:
        recdata.append(self.getRecordAttributeAtColumn(recName=rec, columnName=sortkey))
    #try create list of floats if col has numbers only
    try:
        recdata = self.toFloats(recdata)
    except:
        pass
    smap = zip(names, recdata)
    #sort the mapping by the second key
    smap = sorted(smap, key=operator.itemgetter(1), reverse=reverse)
    #now sort the main reclist by the mapping order
    sortmap = map(operator.itemgetter(0), smap)
    return sortmap
def deleteCellRecord(self, rowIndex, columnIndex)

Remove the cell data at this row/column

Source code
def deleteCellRecord(self, rowIndex, columnIndex):
    """Remove the cell data at this row/column"""

    colname = self.getColumnName(columnIndex)
    coltype = self.columntypes[colname]
    name = self.getRecName(rowIndex)
    if colname in self.data[name]:
        del self.data[name][colname]
    return
def deleteColumn(self, columnIndex)

delete a column

Source code
def deleteColumn(self, columnIndex):
    """delete a column"""
    colname = self.getColumnName(columnIndex)
    self.columnNames.remove(colname)
    del self.columnlabels[colname]
    del self.columntypes[colname]
    #remove this field from every record
    for recname in self.reclist:
        if colname in self.data[recname]:
            del self.data[recname][colname]
    if self.sortkey != None:
        currIndex = self.getColumnIndex(self.sortkey)
        if columnIndex == currIndex:
            self.setSortOrder(0)
    #print 'column deleted'
    #print 'new cols:', self.columnNames
    return
def deleteColumns(self, cols=None)

Remove all cols or list provided

Source code
def deleteColumns(self, cols=None):
    """Remove all cols or list provided"""
    if cols == None:
        cols = self.columnNames
    if self.getColumnCount() == 0:
        return
    for col in cols:
        self.deleteColumn(col)
    return
def deleteRow(self, rowIndex=None, key=None, update=True)

Delete a row

Source code
def deleteRow(self, rowIndex=None, key=None, update=True):
    """Delete a row"""
    if key == None or not key in self.reclist:
        key = self.getRecName(rowIndex)
    del self.data[key]
    if update==True:
        self.reclist.remove(key)
    return
def deleteRows(self, rowlist=None)

Delete multiple or all rows

Source code
def deleteRows(self, rowlist=None):
    """Delete multiple or all rows"""
    if rowlist == None:
        rowlist = range(len(self.reclist))
    names = [self.getRecName(i) for i in rowlist]
    for name in names:
        self.deleteRow(key=name, update=True)
    return
def doFormula(self, cellformula)

Evaluate the formula for a cell and return the result

Source code
def doFormula(self, cellformula):
    """Evaluate the formula for a cell and return the result"""
    value = Formula.doFormula(cellformula, self.data)
    return value
def filterBy(self, filtercol, value, op='contains', userecnames=False, progresscallback=None)

The searching function that we apply to the model data. This is used in Filtering.doFiltering to find the required recs according to column, value and an operator

Source code
def filterBy(self, filtercol, value, op='contains', userecnames=False,
                 progresscallback=None):
    """The searching function that we apply to the model data.
       This is used in Filtering.doFiltering to find the required recs
       according to column, value and an operator"""

    funcs = Filtering.operatornames
    floatops = ['=','>','<']
    func = funcs[op]
    data = self.data
    #coltype = self.columntypes[filtercol]
    names=[]
    for rec in self.reclist:
        if filtercol in data[rec]:
            #try to do float comparisons if required
            if op in floatops:
                try:
                    #print float(data[rec][filtercol])
                    item = float(data[rec][filtercol])
                    v = float(value)
                    if func(v, item) == True:
                        names.append(rec)
                    continue
                except:
                    pass
            if filtercol == 'name' and userecnames == True:
                item = rec
            else:
                item = str(data[rec][filtercol])
            if func(value, item):
                names.append(rec)
    return names
def getAllCells(self)

Return a dict of the form rowname: list of cell contents Useful for a simple table export for example

Source code
def getAllCells(self):
    """Return a dict of the form rowname: list of cell contents
      Useful for a simple table export for example"""

    records={}
    for row in range(len(self.reclist)):
        recdata=[]
        for col in range(len(self.columnNames)):
            recdata.append(self.getValueAt(row,col))
        records[row]=recdata
    return records
def getCellRecord(self, rowIndex, columnIndex)

Get the data held in this row and column

Source code
def getCellRecord(self, rowIndex, columnIndex):
    """Get the data held in this row and column"""

    value = None
    colname = self.getColumnName(columnIndex)
    coltype = self.columntypes[colname]
    name = self.getRecName(rowIndex)
    #print self.data[name]
    if colname in self.data[name]:
        celldata=self.data[name][colname]
    else:
        celldata=None
    return celldata
def getColCells(self, colIndex)

Get the viewable contents of a col into a list

Source code
def getColCells(self, colIndex):
    """Get the viewable contents of a col into a list"""

    collist = []
    if self.getColumnType(colIndex) == 'Link':
        return ['xxxxxx']
    else:
        for row in range(len(self.reclist)):
            v = self.getValueAt(row, colIndex)
            collist.append(v)
    return collist
def getColorAt(self, rowIndex, columnIndex, key='bg')

Return color of that record field for the table

Source code
def getColorAt(self, rowIndex, columnIndex, key='bg'):
    """Return color of that record field for the table"""
    name = self.getRecName(rowIndex)
    colname = self.getColumnName(columnIndex)
    if name in self.colors[key] and colname in self.colors[key][name]:
        return self.colors[key][name][colname]
    else:
        return None
def getColumnCount(self)

Returns the number of columns in the data model.

Source code
def getColumnCount(self):
     """Returns the number of columns in the data model."""
     return len(self.columnNames)
def getColumnData(self, columnIndex=None, columnName=None, filters=None)

Return the data in a list for this col, filters is a tuple of the form (key,value,operator,bool)

Source code
def getColumnData(self, columnIndex=None, columnName=None,
                    filters=None):
    """Return the data in a list for this col,
        filters is a tuple of the form (key,value,operator,bool)"""
    if columnIndex != None and columnIndex < len(self.columnNames):
        columnName = self.getColumnName(columnIndex)
    names = Filtering.doFiltering(searchfunc=self.filterBy,
                                     filters=filters)
    coldata = [self.data[n][columnName] for n in names]
    return coldata
def getColumnIndex(self, columnName)

Returns the column index for this column

Source code
def getColumnIndex(self, columnName):
    """Returns the column index for this column"""
    colindex = self.columnNames.index(columnName)
    return colindex
def getColumnLabel(self, columnIndex)

Returns the label for this column

Source code
def getColumnLabel(self, columnIndex):
    """Returns the label for this column"""
    colname = self.getColumnName(columnIndex)
    return self.columnlabels[colname]
def getColumnName(self, columnIndex)

Returns the name of the given column by columnIndex.

Source code
def getColumnName(self, columnIndex):
     """Returns the name of the given column by columnIndex."""
     return self.columnNames[columnIndex]
def getColumnType(self, columnIndex)

Get the column type

Source code
def getColumnType(self, columnIndex):
    """Get the column type"""
    colname = self.getColumnName(columnIndex)
    coltype = self.columntypes[colname]
    return coltype
def getColumns(self, colnames, filters=None, allowempty=True)
Get column data for multiple cols, with given filter options,
filterby : list of tuples of the form (key,value,operator,bool)
 
allowempty : boolean if false means rows with empty vals for any
 
required fields are not returned
returns : lists of column data
 
Source code
def getColumns(self, colnames, filters=None, allowempty=True):
    """Get column data for multiple cols, with given filter options,
        filterby: list of tuples of the form (key,value,operator,bool)
        allowempty: boolean if false means rows with empty vals for any
        required fields are not returned
        returns: lists of column data"""

    def evaluate(l):
        for i in l:
            if i == '' or i == None:
                return False
        return True
    coldata=[]
    for c in colnames:
        vals = self.getColumnData(columnName=c, filters=filters)
        coldata.append(vals)
    if allowempty == False:
        result = [i for i in zip(*coldata) if evaluate(i) == True]
        coldata = zip(*result)
    return coldata
def getData(self)

Return the current data for saving

Source code
def getData(self):
    """Return the current data for saving"""

    data = copy.deepcopy(self.data)
    data['colors'] = self.colors
    data['columnnames'] = self.columnNames
    #we keep original record order
    data['reclist'] = self.reclist
    #record current col order
    data['columnorder']={}
    i=0
    for name in self.columnNames:
        data['columnorder'][i] = name
        i=i+1
    data['columntypes'] = self.columntypes
    data['columnlabels'] = self.columnlabels
    return data
def getDefaultTypes(self)

Get possible field types for this table model

Source code
def getDefaultTypes(self):
    """Get possible field types for this table model"""
    return self.defaulttypes
def getDict(self, colnames, filters=None)

Get the model data as a dict for given columns with filter options

Source code
def getDict(self, colnames, filters=None):
    """Get the model data as a dict for given columns with filter options"""
    data={}
    names = self.reclist
    cols = self.getColumns(colnames, filters)
    coldata = zip(*cols)
    for name,cdata in zip(names, coldata):
        data[name] = dict(zip(colnames,cdata))
    return data
def getNextKey(self)

Return the next numeric key in the dict

Source code
def getNextKey(self):
    """Return the next numeric key in the dict"""
    num = len(self.reclist)+1
    return num
def getRecAtRow(self, recname, colname, offset=1, dim='y')

Get the record name at a specified offset in the current table from the record given, by using the current sort order

Source code
def getRecAtRow(self, recname, colname, offset=1, dim='y'):
    """Get the record name at a specified offset in the current
       table from the record given, by using the current sort order"""
    thisrow = self.getRecordIndex(recname)
    thiscol = self.getColumnIndex(colname)
    #table goto next row
    if dim == 'y':
        nrow = thisrow + offset
        ncol = thiscol
    else:
        nrow = thisrow
        ncol = thiscol + offset

    newrecname, newcolname = self.getRecColNames(nrow, ncol)
    print ('recname, colname', recname, colname)
    print ('thisrow, col', thisrow, thiscol)
    return newrecname, newcolname
def getRecColNames(self, rowIndex, ColIndex)

Returns the rec and col name as a tuple

Source code
def getRecColNames(self, rowIndex, ColIndex):
    """Returns the rec and col name as a tuple"""
    recname = self.getRecName(rowIndex)
    colname = self.getColumnName(ColIndex)
    return (recname, colname)
def getRecName(self, rowIndex)

Get record name from row number

Source code
def getRecName(self, rowIndex):
    """Get record name from row number"""

    if len(self.reclist)==0:
        return None
    if self.filteredrecs != None:
        name = self.filteredrecs[rowIndex]
    else:
        name = self.reclist[rowIndex]
    return name
def getRecordAtRow(self, rowIndex)

Get the entire record at the specifed row.

Source code
def getRecordAtRow(self, rowIndex):
    """Get the entire record at the specifed row."""

    name = self.getRecName(rowIndex)
    record = self.data[name]
    return record
def getRecordAttributeAtColumn(self, rowIndex=None, columnIndex=None, recName=None, columnName=None)

Get the attribute of the record at the specified column index. This determines what will be displayed in the cell

Source code
def getRecordAttributeAtColumn(self, rowIndex=None, columnIndex=None,
                                    recName=None, columnName=None):
     """Get the attribute of the record at the specified column index.
        This determines what will be displayed in the cell"""

     value = None
     if columnName != None and recName != None:
         if columnName not in self.data[recName]:
             return ''
         cell = self.data[recName][columnName]
     else:
         cell = self.getCellRecord(rowIndex, columnIndex)
         columnName = self.getColumnName(columnIndex)
     if cell == None:
         cell=''
     # Set the value based on the data record field
     coltype = self.columntypes[columnName]
     if Formula.isFormula(cell) == True:
         value = self.doFormula(cell)
         return value

     if not type(cell) is dict:
         if coltype == 'text' or coltype == 'Text':
             value = cell
         elif coltype == 'number':
             value = str(cell)
         else:
             value = 'other'
     if value==None:
         value=''
     return value
def getRecordIndex(self, recname)
Source code
def getRecordIndex(self, recname):
    rowIndex = int(self.reclist.index(recname))
    return rowIndex
def getRowCount(self)

Returns the number of rows in the table model.

Source code
def getRowCount(self):
     """Returns the number of rows in the table model."""
     return len(self.reclist)
def getValueAt(self, rowIndex, columnIndex)

Returns the cell value at location specified by columnIndex and rowIndex.

Source code
def getValueAt(self, rowIndex, columnIndex):
     """Returns the cell value at location specified
         by columnIndex and rowIndex."""
     value = self.getRecordAttributeAtColumn(rowIndex, columnIndex)
     return value
def getlongestEntry(self, columnIndex)

Get the longest cell entry in the col

Source code
def getlongestEntry(self, columnIndex):
    """Get the longest cell entry in the col"""

    collist = self.getColCells(columnIndex)
    maxw=5
    for c in collist:
        try:
            w = len(str(c))
        except UnicodeEncodeError:
            pass
        if w > maxw:
            maxw = w
    #print 'longest width', maxw
    return maxw
def importCSV(self, filename, sep=',')

Import table data from a comma separated file.

Source code
def importCSV(self, filename, sep=','):
    """Import table data from a comma separated file."""

    if not os.path.isfile(filename) or not os.path.exists(filename):
        print ('no such file')
        return None

    #takes first row as field names
    dictreader = csv.DictReader(open(filename, "r"), delimiter=sep)
    dictdata = {}
    count=0
    for rec in dictreader:
        dictdata[count]=rec
        count=count+1
    self.importDict(dictdata)
    return
def importDict(self, newdata)

Try to create a table model from a dict of the form {{'rec1': {'col1': 3, 'col2': 2}, ..}

Source code
def importDict(self, newdata):
    """Try to create a table model from a dict of the form
       {{'rec1': {'col1': 3, 'col2': 2}, ..}"""

    #get cols from sub data keys
    colnames = []
    for k in newdata:
        fields = newdata[k].keys()
        for f in fields:
            if not f in colnames:
                colnames.append(f)
    for c in colnames:
        self.addColumn(c)
    #add the data
    self.data.update(newdata)
    self.reclist = list(self.data.keys())
    return
def initialiseFields(self)

Create base fields, some of which are not saved

Source code
def initialiseFields(self):
    """Create base fields, some of which are not saved"""
    self.data = None    # holds the table dict
    self.colors = {}    # holds cell colors
    self.colors['fg']={}
    self.colors['bg']={}
    #default types
    self.defaulttypes = ['text', 'number']
    #list of editable column types
    self.editable={}
    self.nodisplay = []
    self.columnwidths={}  #used to store col widths, not held in saved data
    return
def load(self, filename)

Load model from pickle file

Source code
def load(self, filename):
    """Load model from pickle file"""
    fd=open(filename,'rb')
    data = pickle.load(fd)
    self.setupModel(data)
    return
def merge(self, model, key='name', fields=None)

Merge another table model with this one based on a key field, we only add records from the new model where the key is present in both models

Source code
def merge(self, model, key='name', fields=None):
    """Merge another table model with this one based on a key field,
       we only add records from the new model where the key is present
       in both models"""
    if fields == None: fields = model.columnNames
    for rec in self.reclist:
        if not key in self.data[rec]:
            continue
        for new in model.reclist:
            if not key in model.data[new]:
                continue
            if self.data[rec][key] == model.data[new][key]:
            #if new == rec:
                for f in fields:
                    if not f in model.data[rec]:
                        continue
                    if not f in self.columnNames:
                        self.addColumn(f)
                    self.data[rec][f] = model.data[rec][f]
    return
def moveColumn(self, oldcolumnIndex, newcolumnIndex)

Changes the order of columns

Source code
def moveColumn(self, oldcolumnIndex, newcolumnIndex):
    """Changes the order of columns"""
    self.oldnames = self.columnNames
    self.columnNames=[]

    #write out a new column names list - tedious
    moved = self.oldnames[oldcolumnIndex]
    del self.oldnames[oldcolumnIndex]
    #print self.oldnames
    i=0
    for c in self.oldnames:
        if i==newcolumnIndex:
            self.columnNames.append(moved)
        self.columnNames.append(c)
        i=i+1
    #if new col is at end just append
    if moved not in self.columnNames:
        self.columnNames.append(moved)
    return
def relabel_Column(self, columnIndex, newname)

Change the column label - can be used in a table header

Source code
def relabel_Column(self, columnIndex, newname):
    """Change the column label - can be used in a table header"""
    colname = self.getColumnName(columnIndex)
    self.columnlabels[colname]=newname
    return
def resetcolors(self)

Remove all color formatting

Source code
def resetcolors(self):
    """Remove all color formatting"""
    self.colors={}
    self.colors['fg']={}
    self.colors['bg']={}
    return
def save(self, filename=None)

Save model to file

Source code
def save(self, filename=None):
    """Save model to file"""
    if filename == None:
        return
    data = self.getData()
    fd = open(filename,'wb')
    pickle.dump(data,fd)
    fd.close()
    return
def setColorAt(self, rowIndex, columnIndex, color, key='bg')

Set color

Source code
def setColorAt(self, rowIndex, columnIndex, color, key='bg'):
    """Set color"""
    name = self.getRecName(rowIndex)
    colname = self.getColumnName(columnIndex)
    if not name in self.colors[key]:
        self.colors[key][name] = {}
    self.colors[key][name][colname] = str(color)
    return
def setFormulaAt(self, f, rowIndex, columnIndex)

Set a formula at cell given

Source code
def setFormulaAt(self, f, rowIndex, columnIndex):
    """Set a formula at cell given"""
    name = self.getRecName(rowIndex)
    colname = self.getColumnName(columnIndex)
    coltype = self.columntypes[colname]
    rec = {}
    rec['formula'] = f
    self.data[name][colname] = rec
    return
def setRecName(self, newname, rowIndex)

Set the record name to another value - requires re-setting in all dicts that this rec is referenced

Source code
def setRecName(self, newname, rowIndex):
    """Set the record name to another value - requires re-setting in all
       dicts that this rec is referenced"""

    if len(self.reclist)==0:
        return None
    currname = self.getRecName(rowIndex)
    self.reclist[rowIndex] = newname
    temp = copy.deepcopy(self.data[currname])
    self.data[newname] = temp
    #self.data[newname]['Name'] = newname
    del self.data[currname]
    for key in ['bg', 'fg']:
        if currname in self.colors[key]:
            temp = copy.deepcopy(self.colors[key][currname])
            self.colors[key][newname] = temp
            del self.colors[key][currname]
    print ('renamed')
    #would also need to resolve all refs to this rec in formulas here!

    return
def setSortOrder(self, columnIndex=None, columnName=None, reverse=0)

Changes the order that records are sorted in, which will be reflected in the table upon redrawing

Source code
def setSortOrder(self, columnIndex=None, columnName=None, reverse=0):
    """Changes the order that records are sorted in, which will
       be reflected in the table upon redrawing"""

    if columnName != None and columnName in self.columnNames:
        self.sortkey = columnName
    elif columnIndex != None:
        self.sortkey = self.getColumnName(columnIndex)
    else:
        return
    self.reclist = list(self.createSortMap(self.reclist, self.sortkey, reverse))
    if self.filteredrecs != None:
        self.filteredrecs = self.createSortMap(self.filteredrecs, self.sortkey, reverse)
    return
def setValueAt(self, value, rowIndex, columnIndex)

Changed the dictionary when cell is updated by user

Source code
def setValueAt(self, value, rowIndex, columnIndex):
    """Changed the dictionary when cell is updated by user"""

    name = self.getRecName(rowIndex)
    colname = self.getColumnName(columnIndex)
    coltype = self.columntypes[colname]
    if coltype == 'number':
        try:
            if value == '': #need this to allow deletion of values
                self.data[name][colname] = ''
            else:
                self.data[name][colname] = float(value)
        except:
            pass
    else:
        self.data[name][colname] = value
    return
def setupModel(self, newdict, rows=None, columns=None)

Create table model

Source code
def setupModel(self, newdict, rows=None, columns=None):
    """Create table model"""

    if newdict != None:
        self.data = copy.deepcopy(newdict)
        for k in self.keywords:
            if k in self.data:
                self.__dict__[self.keywords[k]] = self.data[k]
                del self.data[k]
        #read in the record list order
        if 'reclist' in self.data:
            temp = self.data['reclist']
            del self.data['reclist']
            self.reclist = temp
        else:
            self.reclist = self.data.keys()
    else:
        #just make a new empty model
        self.createEmptyModel()

    if not set(self.reclist) == set(self.data.keys()):
        print ('reclist does not match data keys')
    #restore last column order
    if hasattr(self, 'columnOrder') and self.columnOrder != None:
        self.columnNames=[]
        for i in self.columnOrder.keys():
            self.columnNames.append(self.columnOrder[i])
            i=i+1
    self.defaulttypes = ['text', 'number']
    #setup default display for column types
    self.default_display = {'text' : 'showstring',
                            'number' : 'numtostring'}
    #set default sort order as first col
    if len(self.columnNames)>0:
        self.sortkey = self.columnNames[0]
    else:
        self.sortkey = None
    #add rows and cols if they are given in the constructor
    if newdict == None:
        if rows != None:
            self.autoAddRows(rows)
        if columns != None:
            self.autoAddColumns(columns)
    self.filteredrecs = None
    return
def toFloats(self, l)
Source code
def toFloats(self, l):
    x=[]
    for i in l:
        if i == '':
            x.append(0.0)
        else:
            x.append(float(i))
    return x