# -*- coding: utf-8 -*-from PyQt4.QtCore import *from PyQt4.QtSql import *import threading as trdclass DbThread(QThread): """ DbThread(QSqlDatabase db) Экземпляр класса в отдельном потоке загружает данные из БД. """ def __init__(self, db): QThread.__init__(self) self.db = db self.data = {} self.begin = 0 self.end = 0 # Эта переменная указывает на последний индекс + 1 self.cacheSize = 0 self.queryInfo = {} # В словаре хранятся списки таблиц, полей и сортировки self.queryPrepared = {} # В словаре хранятся таблицы, поля и сортировки, перечисленные через запятую def setBounds(self, begin, end): """ setBound(begin, end) Установка первой и "последней + 1" строки необходимого блока данных. """ if begin > end or begin < 0 or end < 0: return self.begin = begin self.end = end def run(self): """ Не использовать, автоматически запускается функцией start(). Здесь находится основной код получения данных от БД. """ blockBegin = -1 qryPrepare = self.queryInfo qryText = 'SELECT %(fields)s FROM %(tables)s ORDER BY %(orders)s' % self.queryPrepared for i in xrange(self.begin, self.end): if blockBegin == -1 and (not self.data.has_key(i)): blockBegin = i if blockBegin != -1 and (self.data.has_key(i) or i == self.end - 1): if i == self.end - 1: blockEnd = self.end else: blockEnd = i query = QSqlQuery(self.db) #print '%s OFFSET %d LIMIT %d' % (qryText, blockBegin, blockEnd - blockBegin) query.exec_('%s OFFSET %d LIMIT %d' % (qryText, blockBegin, blockEnd - blockBegin)) #print query.executedQuery() currentRow = blockBegin while query.next(): fld = [] for i in xrange(query.record().count()): fld.append(query.record().value(i).toString()) self.data[currentRow] = fld currentRow += 1 blockBegin = -1 for i in self.data.copy(): # сборка мусора if i < self.begin - self.cacheSize or i >= self.end + self.cacheSize: del(self.data[i])class DbData(QObject): """ DbData(QSqlDatabase db) С классом можно обращаться как с обычным списком, любой индекс будет корректен. Если строка не загружена, то возвращается None. Элементы списка - словари {field:data}""" def __init__(self, db): QObject.__init__(self) self.thread = DbThread(db) self.connect(self.thread, SIGNAL('finished()'), self.threadFinished) self.lastBegin = -1 self.lastEnd = -1 self.fin = True def __getitem__(self, index): """ Получение строки данных по индексу. dbData.__getitem__(i) <==> dbData[i] """ if self.thread.data.has_key(index): return dict(zip(self.thread.queryInfo['fields'], self.thread.data[index])) else: return None def __setitem__(self, index, item): """ Присваивание строки данных по индексу. dbData.__setitem__(i, data) <==> dbData[i] = data """ pass # Не реализовано и не надо def start(self, newBegin, newEnd): """ start(begin, end) Запуск процесса получения данных. begin = первая строка end = последняя строка + 1 """ if not self.fin: # Здесь мы ожидаем окончания потока, хотя лучше бы его убивать, self.lastBegin = newBegin # но это почему-то глючит. :( А сейчас мы немного теряем в производительности, self.lastEnd = newEnd # но есть мнение, что выигрываем в нагрузке на сервер. else: self.thread.setBounds(newBegin, newEnd) # Устанавливаем границы, которые были запрошены последними self.fin = False # и запускаем поток self.thread.start() self.lastBegin = -1 def threadFinished(self): """ Слот окончания работы потока. Если других запросов во время работы не было, генерирует сигнал finished() """ if self.lastBegin > -1: self.thread.setBounds(self.lastBegin, self.lastEnd) self.fin = False self.thread.start() self.lastBegin = -1 else: self.emit(SIGNAL('finished()')) self.fin = True def getCacheSize(self): return self.thread.cacheSize def setCacheSize(self, cacheSize): self.thread.cacheSize = cacheSize cacheSize = property(getCacheSize, setCacheSize)
# -*- coding: utf-8 -*-from PyQt4.QtCore import *from PyQt4.QtSql import *from db_data import DbDataclass DbAbstractModel(QAbstractTableModel): def __init__(self, database, parent=None): self.rCnt = None self.database = database self.dbData = DbData(database) self.connect(self.dbData, SIGNAL('finished()'), self.dbFinished) self.queryInfo = {} self.queryPrepared = {} self.colList = [] QAbstractTableModel.__init__(self, parent) def getRowCount(self): qry = QSqlQuery(self.database) qry.exec_('SELECT COUNT(*) FROM %(tables)s' % (self.queryPrepared)) qry.first() self.rCnt = qry.record().value('count').toInt()[0] def viewportChanged(self, begin, end): self.dbData.start(begin, end) def dbFinished(self): self.emit(SIGNAL('layoutChanged()')) def rowCount(self, parent=QModelIndex()): if self.rCnt == None: self.getRowCount() return self.rCnt def columnCount(self, parent=QModelIndex()): return len(self.colList) def data(self, index, role = Qt.DisplayRole): if role == Qt.DisplayRole: if self.dbData[index.row()]: return self.dbData[index.row()][self.colList[index.column()]] return QVariant() def setQueryInfo(self, queryInfo): """ setQueryInfo(dict('tables':[], 'fields':[], 'orders':[])) Параметр передает в словаре списки таблиц, полей и сортировки """ self.queryInfo = queryInfo for key in queryInfo: self.queryPrepared[key] = ','.join(queryInfo[key]) if len(queryInfo['pkey']): self.queryPrepared['orders'] += ',' + self.queryPrepared['pkey'] self.dbData.thread.queryInfo = queryInfo self.dbData.thread.queryPrepared = self.queryPrepared self.dataReset() def setCacheSize(self, cacheSize): self.dbData.cacheSize = cacheSize def headerData(self, section, orientation, role = Qt.DisplayRole): if role == Qt.DisplayRole and orientation == Qt.Horizontal: return self.colList[section] return QAbstractTableModel.headerData(self, section, orientation, role) def dataReset(self): self.dbData.thread.terminate() self.dbData.thread.data.clear() self.getRowCount()