QSortFilterProxyModel с датами и sqlite - PullRequest
0 голосов
/ 20 февраля 2019

Я переписал этот пример на PyQt5 почти дословно.Проблема в этом примере: предполагается, что вы вводите QDate в модель, но в моем случае модель берется из базы данных sqlite, поэтому даты являются только текстовыми.

from PyQt5.QtCore import (QDate, QDateTime, QRegExp, QSortFilterProxyModel, Qt,
                          QTime)
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QDateEdit,
                             QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QTreeView,
                             QVBoxLayout, QWidget,QTableView)


class MySortFilterProxyModel(QSortFilterProxyModel):
    def __init__(self, parent=None):
        super(MySortFilterProxyModel, self).__init__(parent)

        self.minDate = QDate()
        self.maxDate = QDate()

    def setFilterMinimumDate(self, date):
        self.minDate = date
        self.invalidateFilter()

    def filterMinimumDate(self):
        return self.minDate

    def setFilterMaximumDate(self, date):
        self.maxDate = date
        self.invalidateFilter()

    def filterMaximumDate(self):
        return self.maxDate

    def filterAcceptsRow(self, sourceRow, sourceParent):
        index0 = self.sourceModel().index(sourceRow, 0, sourceParent)
        index1 = self.sourceModel().index(sourceRow, 1, sourceParent)
        index2 = self.sourceModel().index(sourceRow, 2, sourceParent)


        return ((self.filterRegExp().indexIn(self.sourceModel().data(index0)) >= 0
                 or self.filterRegExp().indexIn(self.sourceModel().data(index1)) >= 0)
                and self.dateInRange(self.sourceModel().data(index2)))

    def dateInRange(self, date):
        if isinstance(date, QDateTime):
            date = date.date()

        return ((not self.minDate.isValid() or date >= self.minDate)
                and (not self.maxDate.isValid() or date <= self.maxDate))


class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()

        self.proxyModel = MySortFilterProxyModel(self)
        self.proxyModel.setDynamicSortFilter(True)

        self.sourceView = QTreeView()
        self.sourceView.setRootIsDecorated(False)
        self.sourceView.setAlternatingRowColors(True)

        sourceLayout = QHBoxLayout()
        sourceLayout.addWidget(self.sourceView)
        sourceGroupBox = QGroupBox("Original Model")
        sourceGroupBox.setLayout(sourceLayout)

        self.filterCaseSensitivityCheckBox = QCheckBox("Case sensitive filter")
        self.filterCaseSensitivityCheckBox.setChecked(True)
        self.filterPatternLineEdit = QLineEdit()
        self.filterPatternLineEdit.setText("Grace|Sports")
        filterPatternLabel = QLabel("&Filter pattern:")
        filterPatternLabel.setBuddy(self.filterPatternLineEdit)
        self.filterSyntaxComboBox = QComboBox()
        self.filterSyntaxComboBox.addItem("Regular expression", QRegExp.RegExp)
        self.filterSyntaxComboBox.addItem("Wildcard", QRegExp.Wildcard)
        self.filterSyntaxComboBox.addItem("Fixed string", QRegExp.FixedString)
        self.fromDateEdit = QDateEdit()
        self.fromDateEdit.setDate(QDate(2006, 12, 22))
        self.fromDateEdit.setCalendarPopup(True)
        fromLabel = QLabel("F&rom:")
        fromLabel.setBuddy(self.fromDateEdit)
        self.toDateEdit = QDateEdit()
        self.toDateEdit.setDate(QDate(2007, 1, 5))
        self.toDateEdit.setCalendarPopup(True)
        toLabel = QLabel("&To:")
        toLabel.setBuddy(self.toDateEdit)

        self.filterPatternLineEdit.textChanged.connect(self.textFilterChanged)
        self.filterSyntaxComboBox.currentIndexChanged.connect(self.textFilterChanged)
        self.filterCaseSensitivityCheckBox.toggled.connect(self.textFilterChanged)
        self.fromDateEdit.dateChanged.connect(self.dateFilterChanged)
        self.toDateEdit.dateChanged.connect(self.dateFilterChanged)

        self.proxyView = QTableView()
        # self.proxyView.setRootIsDecorated(False)
        self.proxyView.setAlternatingRowColors(True)
        self.proxyView.setModel(self.proxyModel)
        self.proxyView.setSortingEnabled(True)
        self.proxyView.sortByColumn(1, Qt.AscendingOrder)

        self.textFilterChanged()
        self.dateFilterChanged()

        proxyLayout = QGridLayout()
        proxyLayout.addWidget(self.proxyView, 0, 0, 1, 3)
        proxyLayout.addWidget(filterPatternLabel, 1, 0)
        proxyLayout.addWidget(self.filterPatternLineEdit, 1, 1)
        proxyLayout.addWidget(self.filterSyntaxComboBox, 1, 2)
        proxyLayout.addWidget(self.filterCaseSensitivityCheckBox, 2, 0, 1, 3)
        proxyLayout.addWidget(fromLabel, 3, 0)
        proxyLayout.addWidget(self.fromDateEdit, 3, 1, 1, 2)
        proxyLayout.addWidget(toLabel, 4, 0)
        proxyLayout.addWidget(self.toDateEdit, 4, 1, 1, 2)
        proxyGroupBox = QGroupBox("Sorted/Filtered Model")
        proxyGroupBox.setLayout(proxyLayout)

        mainLayout = QVBoxLayout()
        mainLayout.addWidget(sourceGroupBox)
        mainLayout.addWidget(proxyGroupBox)
        self.setLayout(mainLayout)

        self.setWindowTitle("Custom Sort/Filter Model")
        self.resize(500, 450)

    def setSourceModel(self, model):
        self.proxyModel.setSourceModel(model)
        self.sourceView.setModel(model)

    def textFilterChanged(self):
        syntax = QRegExp.PatternSyntax(
            self.filterSyntaxComboBox.itemData(
                self.filterSyntaxComboBox.currentIndex()))
        caseSensitivity = (
                self.filterCaseSensitivityCheckBox.isChecked()
                and Qt.CaseSensitive or Qt.CaseInsensitive)
        regExp = QRegExp(self.filterPatternLineEdit.text(), caseSensitivity, syntax)
        self.proxyModel.setFilterRegExp(regExp)

    def dateFilterChanged(self):
        self.proxyModel.setFilterMinimumDate(self.fromDateEdit.date())
        self.proxyModel.setFilterMaximumDate(self.toDateEdit.date())


    def addMail(model, subject, sender, date):
        model.insertRow(0)
        model.setData(model.index(0, 0), subject)
        model.setData(model.index(0, 1), sender)
        model.setData(model.index(0, 2), date)


def createMailModel(parent):
    model = QStandardItemModel(0, 3, parent)

    model.setHeaderData(0, Qt.Horizontal, "Subject")
    model.setHeaderData(1, Qt.Horizontal, "Sender")
    model.setHeaderData(2, Qt.Horizontal, "Date")

    addMail(model, "Happy New Year!", "Grace K. <grace@software-inc.com>",
            QDateTime(QDate(2006, 12, 31), QTime(17, 3)))
    addMail(model, "Radically new concept", "Grace K. <grace@software-inc.com>",
            QDateTime(QDate(2006, 12, 22), QTime(9, 44)))
    addMail(model, "Accounts", "pascale@nospam.com",
            QDateTime(QDate(2006, 12, 31), QTime(12, 50)))
    addMail(model, "Expenses", "Joe Bloggs <joe@bloggs.com>",
            QDateTime(QDate(2006, 12, 25), QTime(11, 39)))
    addMail(model, "Re: Expenses", "Andy <andy@nospam.com>",
            QDateTime(QDate(2007, 1, 2), QTime(16, 5)))
    addMail(model, "Re: Accounts", "Joe Bloggs <joe@bloggs.com>",
            QDateTime(QDate(2007, 1, 3), QTime(14, 18)))
    addMail(model, "Re: Accounts", "Andy <andy@nospam.com>",
            QDateTime(QDate(2007, 1, 3), QTime(14, 26)))
    addMail(model, "Sports", "Linda Smith <linda.smith@nospam.com>",
            QDateTime(QDate(2007, 1, 5), QTime(11, 33)))
    addMail(model, "AW: Sports", "Rolf Newschweinstein <rolfn@nospam.com>",
            QDateTime(QDate(2007, 1, 5), QTime(12, 0)))
    addMail(model, "RE: Sports", "Petra Schmidt <petras@nospam.com>",
            QDateTime(QDate(2007, 1, 5), QTime(12, 1)))

    return model


if __name__ == "__main__":
    import sys

    app = QApplication(sys.argv)

    window = Window()
    window.setSourceModel(createMailModel(window))
    window.show()        
    sys.exit(app.exec_())

Я пытался изменить эту строку: self.dateInRange(self.sourceModel().data(index2))) на эту: self.dateInRange(datetime.strptime(self.sourceModel().data(index2),"%Y/%m/%d %H:%M"))), чтобы преобразовать данные TXT в дату формата, и это хорошо работает для фильтрации, но вылетает, если я добавляюновая строка в БД с использованием:

@pyqtSlot()
def on_pushButton_clicked(self):
    self.add_record()

def add_record(self):
    row = self.db_model.rowCount()
    self.db_model.insertRow(row)

Куда я иду не так?

Соответствующая часть БД:

class essaiFindDb():
    def __init__(self):
        self.db = QSqlDatabase.addDatabase("QSQLITE")
        self.db.setDatabaseName("essai_find_database.db")

        self.db.open()

        query = QSqlQuery()
        query.exec_('''CREATE TABLE Pilots_exp(id INTEGER PRIMARY KEY UNIQUE , pilot_1 TEXT,aircraft TEXT, date_time1 TEXT, date_time2 TEXT, total TEXT)''')

1 Ответ

0 голосов
/ 21 февраля 2019

Анализ проблемы невозможен, поскольку вы не предоставили MCVE.

Таким образом, мое решение будет основано на том, на что вы указали:

  • Столбцы 3 (date_time1) и 4(date_time2) имеет формат даты.
  • Формат даты %Y/%m/%d %H:%M (формат даты и времени в python) - yyyy/MM/dd hh:mm (формат Qt)
  • Фильтр даты применяется к столбцу 3.

Как вы заметили, самая простая вещь была бы, если бы столбцы 3 и 4 были QDateTime, поэтому я создам прокси для преобразования.

С другой стороны, яразделил фильтр на временные фильтры и текстовые фильтры, чтобы иметь более четкие коды.

В этом случае я буду применять каскадные прокси:

┌-----------------┐    ┌---------------------------┐    ┌------------------------┐    ┌------------------------┐
|    db_model     | -> |     text_to_QDateTime     | -> |       filter_date      | -> |       filter_text      |
|(QSqlTableModel) |    | (ConvertToDateProxyModel) |    | (FilterDateProxyModel) |    | (FilterTextProxyModel) |
└-----------------┘    └---------------------------┘    └------------------------┘    └------------------------┘

Учитывая вышеизложенное, решение:

from PyQt5 import QtCore, QtGui, QtWidgets, QtSql 

def createConnection():
    db = QtSql.QSqlDatabase.addDatabase('QSQLITE')
    db.setDatabaseName('essai_find_database.db')
    if not db.open():
        QtWidgets.QMessageBox.critical(None, QtWidgets.qApp.tr("Cannot open database"),
                             QtWidgets.qApp.tr("Unable to establish a database connection.\n"
                                     "This example needs SQLite support. Please read "
                                     "the Qt SQL driver documentation for information "
                                     "how to build it.\n\n"
                                     "Click Cancel to exit."),
                            QtWidgets.QMessageBox.Cancel)
        return False

    query = QtSql.QSqlQuery()
    return query.exec_('''
        CREATE TABLE IF NOT EXISTS Pilots_exp ( 
            id INTEGER PRIMARY KEY UNIQUE ,
            pilot_1 TEXT,aircraft TEXT, 
            date_time1 TEXT, date_time2 TEXT, 
            total TEXT)
        ''')

class ConvertToDateProxyModel(QtCore.QIdentityProxyModel):
    def __init__(self, parent=None):
        super(ConvertToDateProxyModel, self).__init__(parent)
        self._columns = []
        self._fmt = ""

    def set_format(self, fmt):
        self._fmt = fmt

    def set_columns(self, columns):
        self._columns = columns

    def data(self, index, role=QtCore.Qt.DisplayRole):
        v = super(ConvertToDateProxyModel, self).data(index, role)
        if not index.isValid():
            return
        if index.column() in self._columns and self._fmt:
            return QtCore.QDateTime.fromString(v, self._fmt)
        return v

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        if index.column() in self._columns and self._fmt:
            sm = self.sourceModel()
            ix = self.mapToSource(index)
            return sm.setData(ix, value.toString(self._fmt), role)
        return super(ConvertToDateProxyModel, self).setData(index, value, role)

class FilterDateProxyModel(QtCore.QSortFilterProxyModel):
    def __init__(self, parent=None):
        super(FilterDateProxyModel, self).__init__(parent)
        self._from_date, self._to_date = QtCore.QDate(), QtCore.QDate()

    def setRange(self, from_date, to_date):
        self._from_date = from_date
        self._to_date = to_date
        self.invalidateFilter()

    def filterAcceptsRow(self, sourceRow, sourceParent):
        if any([not date.isValid() for date in (self._from_date, self._to_date,)]):
            return True
        ix = self.sourceModel().index(sourceRow, self.filterKeyColumn(), sourceParent)
        dt = ix.data().date()
        return self._from_date <= dt <= self._to_date

class FilterTextProxyModel(QtCore.QSortFilterProxyModel):
    def __init__(self, parent=None):
        super(FilterTextProxyModel, self).__init__(parent)
        self._columns = []

    def set_columns(self, columns):
        self._columns = columns
        self.invalidateFilter()

    def filterAcceptsRow(self, sourceRow, sourceParent):
        if not self._columns:
            return True
        values = []
        for c in range(self.sourceModel().columnCount()):
            if c in self._columns:
                ix = self.sourceModel().index(sourceRow, c, sourceParent)
                values.append(self.filterRegExp().indexIn(ix.data()) >= 0)
        return any(values)


class AddDialog(QtWidgets.QDialog):
    def __init__(self, formats, parent=None):
        super(AddDialog, self).__init__(parent)
        self._editors = dict()
        flay = QtWidgets.QFormLayout(self)
        for key, value in formats.items():
            editor = self.create_editor_by_type(value)
            flay.addRow(key, editor)
            self._editors[key] = editor

        buttonBox = QtWidgets.QDialogButtonBox()
        buttonBox.setOrientation(QtCore.Qt.Horizontal)
        buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)

        buttonBox.accepted.connect(self.accept)
        buttonBox.rejected.connect(self.reject)
        flay.addRow(buttonBox)

    def create_editor_by_type(self, t):
        editor = QtWidgets.QLineEdit()
        if t == QtCore.QDateTime:
            editor = QtWidgets.QDateTimeEdit(
                dateTime= QtCore.QDateTime.currentDateTime(),
                displayFormat="yyyy/MM/dd hh:mm",
                calendarPopup=True
            )
        return editor

    def get_value_from_editor(self, editor):
        if isinstance(editor, QtWidgets.QLineEdit):
            return editor.text()
        if isinstance(editor, QtWidgets.QDateTimeEdit):
            return editor.dateTime()

    def get_values(self):
        result = dict()
        for key, editor in self._editors.items():
            result[key] = self.get_value_from_editor(editor)
        return result

class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)

        self.db_model = QtSql.QSqlTableModel(self)
        self.db_model.setTable("Pilots_exp")
        self.db_model.select()

        proxy_convert_to_date = ConvertToDateProxyModel(self)
        proxy_convert_to_date.setSourceModel(self.db_model)
        proxy_convert_to_date.set_columns([3, 4])
        proxy_convert_to_date.set_format("yyyy/MM/dd hh:mm")

        sourceGroupBox = QtWidgets.QGroupBox("Original Model")
        sourceView = QtWidgets.QTableView(alternatingRowColors=True)
        sourceView.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
        sourceView.verticalHeader().hide()
        sourceView.setModel(proxy_convert_to_date)
        sourceLayout = QtWidgets.QHBoxLayout(sourceGroupBox)
        sourceLayout.addWidget(sourceView)

        self._proxy_date = FilterDateProxyModel(self)
        self._proxy_date.setFilterKeyColumn(3)
        self._proxy_date.setSourceModel(proxy_convert_to_date)

        self._proxy_filter = FilterTextProxyModel(self)
        self._proxy_filter.setSourceModel(self._proxy_date)
        self._proxy_filter.set_columns([1, 2])

        proxyGroupBox = QtWidgets.QGroupBox("Sorted/Filtered Model")
        proxyView = QtWidgets.QTableView()
        proxyView.verticalHeader().hide()
        proxyView.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
        proxyView.setSortingEnabled(True)
        proxyView.setModel(self._proxy_filter)
        proxyView.sortByColumn(1, QtCore.Qt.AscendingOrder)
        proxyLayout = QtWidgets.QVBoxLayout(proxyGroupBox)
        proxyLayout.addWidget(proxyView)

        filterPatternLabel = QtWidgets.QLabel("&Filter pattern:")
        self.filterPatternLineEdit = QtWidgets.QLineEdit(
            "Grace|Sports",
            textChanged=self.update_filter_text
        )
        filterPatternLabel.setBuddy(self.filterPatternLineEdit)
        self.filterSyntaxComboBox = QtWidgets.QComboBox(
            currentIndexChanged=self.update_filter_text
        )

        self.filterCaseSensitivityCheckBox = QtWidgets.QCheckBox(
            "Case sensitive filter", 
            checked=True,
            stateChanged=self.update_filter_text
        )
        self.fromDateEdit = QtWidgets.QDateEdit(
            calendarPopup=True, 
            date=QtCore.QDate(2006, 12, 22),
            dateChanged=self.update_filter_date
        )
        self.toDateEdit = QtWidgets.QDateEdit(
            calendarPopup=True, 
            date=QtCore.QDate(2007, 1, 5),
            dateChanged=self.update_filter_date
        )

        self.filterSyntaxComboBox.addItem("Regular expression", QtCore.QRegExp.RegExp)
        self.filterSyntaxComboBox.addItem("Wildcard", QtCore.QRegExp.Wildcard)
        self.filterSyntaxComboBox.addItem("Fixed string", QtCore.QRegExp.FixedString)
        self.update_filter_text()
        self.update_filter_date()

        flay = QtWidgets.QFormLayout()
        flay.addRow("F&rom:", self.fromDateEdit)
        flay.addRow("&To:", self.toDateEdit)

        hlay = QtWidgets.QHBoxLayout()
        hlay.addWidget(filterPatternLabel)
        hlay.addWidget(self.filterPatternLineEdit)
        hlay.addWidget(self.filterSyntaxComboBox)
        proxyLayout.addWidget(self.filterCaseSensitivityCheckBox)
        proxyLayout.addLayout(hlay)
        proxyLayout.addLayout(flay)

        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(sourceGroupBox)
        lay.addWidget(proxyGroupBox)

        proxyView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        proxyView.customContextMenuRequested.connect(self.on_customContextMenuRequested)

    @QtCore.pyqtSlot(QtCore.QPoint)
    def on_customContextMenuRequested(self, p):
        view = self.sender()
        menu = QtWidgets.QMenu()
        add_row_action = menu.addAction("Add Row")
        action = menu.exec_(view.viewport().mapToGlobal(p))
        if action == add_row_action:
            self.add_record()

    @QtCore.pyqtSlot()
    def update_filter_text(self):
        syntax = self.filterSyntaxComboBox.currentData()
        caseSensitivity = QtCore.Qt.CaseSensitive \
            if self.filterCaseSensitivityCheckBox.isChecked() \
            else QtCore.Qt.CaseInsensitive
        regExp = QtCore.QRegExp(self.filterPatternLineEdit.text(), caseSensitivity, syntax)
        self._proxy_filter.setFilterRegExp(regExp)

    @QtCore.pyqtSlot()
    def update_filter_date(self):
        self._proxy_date.setRange(self.fromDateEdit.date(), self.toDateEdit.date())

    def add_record(self):
        d = {}
        rec = self.db_model.record()
        for i in range(rec.count()):
            d[rec.fieldName(i)] = type(rec.value(i))

        for i in (3, 4): d[rec.fieldName(i)] = QtCore.QDateTime

        del d[rec.fieldName(0)]

        dialog = AddDialog(d, self)
        if dialog.exec_() == QtWidgets.QDialog.Accepted:
            results = dialog.get_values()
            for i in (3, 4):
                k = rec.fieldName(i)
                results[k] = results[k].toString("yyyy/MM/dd hh:mm")
            for k, value in results.items():
                rec.setValue(k, value)
            self.db_model.insertRecord(-1, rec)
            self.db_model.select()

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    if not createConnection():
        sys.exit(-1)
    w = Widget()
    w.resize(960, 480)
    w.show()
    sys.exit(app.exec_())
...