def __init__(self, g, mappedClass, query, columns, defaultSortClause=None): super(BaseQueryModel, self).__init__() self.g = g self.mappedClass = mappedClass self.g.registerRetranslate(self.allDataChanged) self.baseQuery = query self.columns = [] for column in columns: try: self.columns.append(ModelColumn.load(column, model=self)) except Exception: traceback.print_exc() print 'Failed to load column:', column if defaultSortClause is None: self.defaultSortClause = self.columns[0].getSortClause() else: self.defaultSortClause = defaultSortClause self.sortClauses = SortModel(self, []) self.sortClauses.rowsInserted.connect(self.sortChanged) self.sortClauses.rowsRemoved.connect(self.sortChanged) self.sortClauses.dataChanged.connect(self.sortChanged) self.filters = [] self._setQuery()
class BaseQueryModel(QtCore.QAbstractItemModel): """A model that displays an ORM query, with a set of custom columns. Can be queried the Python way, (with []). """ collapsingPossible = False _pagesize = 1000 __metaclass__ = ModelMetaclass def __init__(self, g, mappedClass, query, columns, defaultSortClause=None): super(BaseQueryModel, self).__init__() self.g = g self.mappedClass = mappedClass self.g.registerRetranslate(self.allDataChanged) self.baseQuery = query self.columns = [] for column in columns: try: self.columns.append(ModelColumn.load(column, model=self)) except Exception: traceback.print_exc() print 'Failed to load column:', column if defaultSortClause is None: self.defaultSortClause = self.columns[0].getSortClause() else: self.defaultSortClause = defaultSortClause self.sortClauses = SortModel(self, []) self.sortClauses.rowsInserted.connect(self.sortChanged) self.sortClauses.rowsRemoved.connect(self.sortChanged) self.sortClauses.dataChanged.connect(self.sortChanged) self.filters = [] self._setQuery() @property def allSortClauses(self): return (self.defaultSortClause, ) + tuple(self.sortClauses) def _setQuery(self): """Called every time the query changes""" builder = self.baseBuilder() for clause in reversed(self.allSortClauses): clause.sort(builder) self._query = builder.query self._rows = int(self._query.count()) self.pages = [None] * (self._rows // self._pagesize + 1) def baseBuilder(self): """Return a QueryBuilder corresponding to the base query """ return QueryBuilder(self.baseQuery, self.mappedClass) def dump(self): """Dump a simple representation of the data to stdout """ for item in self: for column in self.columns: print column.data(item, None), print def allDataChanged(self): """Called when all of the data is changed, e.g. retranslated""" self._setQuery() self.dataChanged.emit( self.index(0, 0), self.index(self.rowCount() - 1, self.columnCount() - 1), ) self.headerDataChanged.emit(Qt.Horizontal, 0, self.columnCount() - 1) def __getitem__(self, i): pageno, offset = divmod(i, self._pagesize) page = self.pages[pageno] if not page: start = pageno * self._pagesize end = (pageno + 1) * self._pagesize page = self.pages[pageno] = self._query[start:end] return page[offset] def columnCount(self, parent=QtCore.QModelIndex()): return len(self.columns) def data(self, index, role=Qt.DisplayRole): item = self.itemForIndex(index) if item: return self.columns[index.column()].data(item, role) def itemForIndex(self, index): """Returns the item that corresponds to the given index""" if index.isValid() and not index.parent().isValid(): return self[index.row()] def headerData(self, section, orientation, role): if orientation == Qt.Horizontal: return self.columns[section].headerData(role, self) def index(self, row, column, parent=QtCore.QModelIndex()): if not parent.isValid(): if 0 <= row < self.rowCount() and 0 <= column < self.columnCount(): return self.createIndex(row, column) def rowCount(self, parent=QtCore.QModelIndex()): if not parent.isValid(): return self._rows else: return 0 def parent(self, index): return QtCore.QModelIndex() def save(self): """Can't save a query directly""" raise AssertionError("Can't save a BaseQueryModel") def removeColumns(self, column, count, parent=QtCore.QModelIndex()): if not parent.isValid(): last = column + count - 1 if column == 0: # Can't remove the first column column += 1 last -= 1 if column == last: return False self.beginRemoveColumns(QtCore.QModelIndex(), column, last) del self.columns[column:last + 1] self.endRemoveColumns() return True def insertQueryColumn(self, position, column): """Insert a ModelColumn at the specified position Qt's normal column-inserting API doesn't work: it doesn't specify the column to be inserted. """ self.beginInsertColumns(QtCore.QModelIndex(), position, position) self.columns.insert(position, column) self.endInsertColumns() def replaceQueryColumn(self, position, new_column): old_column = self.columns[position] self.columns[position] = new_column self.dataChanged.emit(self.index(0, position), self.index(self.rowCount, position)) self.headerDataChanged.emit(Qt.Horizontal, position, position) def sort(self, columnIndex, order=Qt.AscendingOrder): newClauses = [self.defaultSortClause] if columnIndex == -1: pass else: column = self.columns[columnIndex] descending = (order == Qt.DescendingOrder) sortClause = column.getSortClause(descending=descending) self.sortClauses.append(sortClause) def sortChanged(self): # Sorting's an expensive operation; if there are more resorts in a # single event loop iteration, only actually sort once self._sortChanged = True def resort(): if not self._sortChanged: return self._sortChanged = False QtGui.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) try: self.layoutAboutToBeChanged.emit() self._setQuery() self.layoutChanged.emit() finally: QtGui.QApplication.restoreOverrideCursor() QtCore.QTimer.singleShot(0, resort)