"""Data (keyword-based) table definition and implementation. Variant for tables without a key column: RecordsTable using a list of records (key-value pairs). """ from BTrees.OOBTree import OOBTree from zope.cachedescriptors.property import Lazy from zope import component, schema from zope.component import adapts from zope.interface import implementer, implements, Interface, Attribute from zope.schema.interfaces import IContextSourceBinder, IIterableSource from cybertools.composer.schema.factory import SchemaFactory from cybertools.composer.schema.grid.interfaces import KeyTable, RecordsTable from cybertools.composer.interfaces import IInstance from cybertools.meta.interfaces import IOptions from loops.common import AdapterBase, adapted, baseObject from loops.external.element import Element from loops.interfaces import IConcept, IConceptSchema, ILoopsAdapter from loops.type import TypeInterfaceSourceList from loops import util from loops.util import _ class IDataTable(IConceptSchema, ILoopsAdapter): """ The schema for the data table type. """ viewName = schema.TextLine( title=_(u'Adapter/View name'), description=_(u'Optional name of the view ' u'to be used for presenting ' u'the data of the table.'), default=u'', required=False) columns = schema.List( title=_(u'Columns'), description=_(u'The names of the columns of the data table. ' u'The first name is the name of the key column.'), value_type=schema.BytesLine(), default=['key', 'value'], required=True) data = KeyTable(title=_(u'Table Data'), description=_(u'Table Data'), required=False) IDataTable['columns'].hidden = True class IRecordsTable(IDataTable): data = RecordsTable(title=_(u'Table Data'), description=_(u'Table Data'), required=False) class DataTable(AdapterBase): implements(IDataTable) _contextAttributes = list(IDataTable) _adapterAttributes = AdapterBase._adapterAttributes + ('columns', 'data') def getColumns(self): cols = getattr(self.context, '_columns', None) if not cols: cols = getattr(baseObject(self.type), '_columns', None) return cols or ['key', 'value'] def setColumns(self, value): self.context._columns = value columns = property(getColumns, setColumns) columnNames = columns def getData(self): data = getattr(self.context, '_data', None) if data is None: data = OOBTree() self.context._data = data reclen = len(self.columns) - 1 for k, v in data.items(): #v = v[:reclen] missing = reclen - len(v) if missing > 0: v += (missing * [u'']) data[k] = v return data def setData(self, data): self.context._data = OOBTree(data) data = property(getData, setData) def dataAsRecords(self): return self.asRecords(self.data) def asRecords(self, data): result = [] for k, v in sorted(data.items()): item = {} for idx, c in enumerate(self.columns): if idx == 0: item[c] = k else: item[c] = v[idx-1] #item[c] = len(v) > idx and v[idx-1] or u'' result.append(item) return result def getRowsByValue(self, column, value): return [r for r in self.dataAsRecords() if r[column] == value] TypeInterfaceSourceList.typeInterfaces += (IDataTable, IRecordsTable) class DataTableSchemaFactory(SchemaFactory): adapts(IDataTable) def __call__(self, interface, **kw): schema = super(DataTableSchemaFactory, self).__call__(interface, **kw) if not isinstance(kw.get('manager'), Element): schema.fields.remove('columns') schema.fields.remove('viewName') return schema def getRowValue(k, v): return v[0] def getRowValueWithKey(k, v): return u' '.join((unicode(k), v[0])) class DataTableSourceList(object): implements(IIterableSource) def __init__(self, context, valueProvider=getRowValue): self.context = context self.valueProvider = valueProvider def __iter__(self): items = [(k, self.valueProvider(k, v)) for k, v in self.context.data.items()] return iter(sorted(items, key=lambda x: x[1])) def __contains__(self, value): return value in self.context.data def __len__(self): return len(self.context.data) class DataTableSourceListByValue(DataTableSourceList): def __iter__(self): items = [(k, v[0], v[1]) for k, v in self.context.data.items()] items.sort() return iter([(i[1], i[2]) for i in items]) class DataTableSourceBinder(object): implements(IContextSourceBinder) def __init__(self, tableName, valueProvider=getRowValue, sourceList=None): self.tableName = tableName self.valueProvider = valueProvider self.sourceList = sourceList or DataTableSourceList def __call__(self, instance): if IInstance.providedBy(instance): context = instance.view.nodeView.context elif IConcept.providedBy(instance): context = baseObject(instance) else: context = baseObject(instance.context) dt = context.getLoopsRoot().getConceptManager()[self.tableName] return self.sourceList(adapted(dt), self.valueProvider) @implementer(IRecordsTable) class RecordsTable(DataTable): def getData(self): data = self.context._data if not isinstance(data, list): data = self.asRecords(data) sortcrit = IOptions(self.type)('table.sort') if sortcrit: data.sort(key=lambda x: [x.get(f) for f in sortcrit]) return data def setData(self, data): self.context._data = data data = property(getData, setData) def dataAsRecords(self): return self.data