Извлеките дату (ЦЕЛОЕ число) из базы данных SQLite и вставьте ее в поле QDateEdit

#python #pyqt #pyqt5

#python #pyqt #pyqt5

Вопрос:

У меня есть простая тестовая форма с 2 полями: имя, дата рождения. Они оба получают значения из базовой базы данных SQLite. Поле даты рождения в базе данных имеет тип INTEGER (время UNIX). Я могу получить имя в форме нормально, но я не могу получить дату рождения (вместо этого я получаю 1/1/2000). Я подозреваю, что моя проблема связана с преобразованием типа из INTEGER в QDate, но я не могу понять, как это исправить.

Ниже приведен код для этой упрощенной версии формы. Я включил минимум, чтобы воссоздать проблему. Если вы сохраните этот код в каталоге в виде файла .py и запустите его оттуда, он создаст базу данных в этом каталоге и отобразит форму. Я использую подход View / Model, и любые ответы были бы особенно оценены, если бы они могли работать в рамках существующего кода.

Заранее большое спасибо

 import sys
import os.path
import sqlite3
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from PyQt5 import QtCore as qtc
from PyQt5 import QtSql as qts


DIR_PATH = os.path.dirname(os.path.abspath(__file__))
DB_NAME = 'test.db'
target_db = os.path.join(DIR_PATH, DB_NAME)


 # create database 'test.db', add schema, then close it
cnn = sqlite3.connect(target_db)
c = cnn.cursor()
c.executescript("""
                            
                    DROP TABLE IF EXISTS clients;
                    CREATE TABLE clients
                    (
                        id INTEGER PRIMARY KEY,
                        name STRING,
                        birthdate INTEGER
                    );

                    INSERT INTO clients VALUES (1, 'Error Flynn', '12/30/1980');
                            
                """)
cnn.commit()
cnn.close()


class Model(qtc.QObject):

    connection_error = qtc.pyqtSignal(str)
    
    def start_db(self):

        connection_error = ''
        # create a db connection:
        self.db = qts.QSqlDatabase.addDatabase('QSQLITE')
        self.db.setDatabaseName(target_db)
        # test the connection for errors:    
        if not self.db.open():
            connection_error = f'{self.db.lastError().text()}'
            sys.exit(1)

        if connection_error:
            self.error.emit(connection_error)


    def create_model(self):

        self.clients_model = qts.QSqlRelationalTableModel()
        self.clients_model.setTable('clients')

        # query once the model to populate it
        self.clients_model.select()

        return self.clients_model


# create the Clients Form
class View(qtw.QWidget):

    error = qtc.pyqtSignal(str)
    
    def __init__(self, a_clients_model):

        super().__init__()
        self.setLayout(qtw.QFormLayout())
        
        # The 2 Client Fields
        
        self.client_name = qtw.QLineEdit()
        self.layout().addRow('Name: ', self.client_name)

        self.client_bdate = qtw.QDateEdit()
        self.layout().addRow('Birthdate: ', self.client_bdate)
        self.client_bdate.setCalendarPopup(True)
    

        # create a mapper and point it to the clients_model 
        self.this_clients_model = a_clients_model
        self.mapper = qtw.QDataWidgetMapper(self)
        self.mapper.setModel(self.this_clients_model)
        self.mapper.setItemDelegate(
            qts.QSqlRelationalDelegate(self))

        # map the Client Name field  
        self.mapper.addMapping(
            self.client_name,
            self.this_clients_model.fieldIndex('name')
        )

        # map the Client Birthdate field  
        self.mapper.addMapping(
            self.client_bdate,
            self.this_clients_model.fieldIndex('birthdate')
            # client_birthdate is declared INTEGER in the SQLite database,
            # to be converted to a QDateEdit object before it can be used!
        )

        # this will show the first record in the tbl_clients
        self.mapper.setCurrentIndex(0)
       
        self.show()


    # display error message originating in Model class
    def show_connection_error(self, error):
        qtw.QMessageBox.critical(
            None,
            'DB Connection Error',
            'Could not open database file: ' 
            f'{error}')
        sys.exit(1)


class MainWindow(qtw.QMainWindow):

    def __init__(self):
        """MainWindow constructor.
        """
        super().__init__()
        # Main UI code goes here

        self.stack = qtw.QStackedWidget()
        self.setCentralWidget(self.stack)

        self.model = Model()
        self.model.start_db()
               
        self.view = View(self.model.create_model())
        self.stack.addWidget(self.view)
        
        self.model.connection_error.connect(self.view.show_connection_error)

        # End main UI code
        self.show()


if __name__ == '__main__':
    app = qtw.QApplication(sys.argv)
    # it's required to save a reference to MainWindow.
    # if it goes out of scope, it will be destroyed.
    mw = MainWindow()
    sys.exit(app.exec())
  

Ответ №1:

Проблема в том, что нет преобразования между строкой «30.12.1980» и QDate по умолчанию. Решением в этом случае является использование делегата для выполнения этого преобразования. С другой стороны, я считаю ненужным использовать QSqlRelationalDelegate в качестве делегата.

 class Delegate(qtw.QItemDelegate):
    def setEditorData(self, editor, index):
        if isinstance(editor, qtw.QDateEdit):
            dt = qtc.QDate.fromString(index.data(), "MM/dd/yyyy")
            editor.setDate(dt)
        else:
            super().setEditorData(editor, index)

    def setModelData(self, editor, model, index):
        if isinstance(editor, qtw.QDateEdit):
            data = editor.date().toString("MM/dd/yyyy")
            model.setData(index, data)
        else:
            super().setModelData(editor, model, index)
  
 # create a mapper and point it to the clients_model
self.this_clients_model = a_clients_model
self.mapper = qtw.QDataWidgetMapper(self)
self.mapper.setModel(self.this_clients_model)
# self.mapper.setItemDelegate(qts.QSqlRelationalDelegate(self))
self.mapper.setItemDelegate(Delegate(self))  

Комментарии:

1. спасибо eyllanesc, это сработало как шарм! Мне все еще нужно выяснить, почему это не работает в моем реальном приложении, хотя, как вы думаете, имеет ли значение, что моя таблица «клиенты» ссылается на многие другие таблицы поиска? Потребуется ли в этом случае QSqlRelationalDelegate?

2. @ErrorFlynn попробуйте изменить class Delegate(qtw.QItemDelegate): на class Delegate(qts.QSqlRelationalDelegate):

3. еще раз спасибо за обновление. Поскольку я очень новичок в этом, я не понимаю логики. Делегат имеет 2 метода: setEditorData форматирует дату, полученную из базы данных, для использования QDateEdit. setModelData форматирует пользовательский ввод в QDateEdit (календарь), который будет использоваться базой данных. Но разве нам не нужно явно вызывать эти методы для работы с событием onDateChanged или чем-то подобным? Кроме того, в примере с обновленным кодом, когда я пытаюсь изменить дату с помощью календаря, изменения не записываются в базу данных. Чего мне не хватает?

4. @ErrorFlynn Проблема в вашем случае вызвана моделью, поскольку по умолчанию она не сохраняет информацию в базе данных, для этого вам нужно добавить self.clients_model.setEditStrategy(qts.QSqlTableModel.OnFieldChange)