Bài 30: Kiến trúc Model View-SQL Databases–PyQt6–Part 5

Bài này Tui sẽ trình bày về ứng dụng kiến trúc Model View trong tương tác và hiển thị cơ sở dữ liệu dạng SQL, cụ thể là SQLite Database, cung cấp tính năng lọc dữ liệu theo dòng, lọc dữ liệu theo cột, cũng như cập nhật dữ liệu ngay trong QTableView khi có sự thay đổi, bạn có thể áp dụng để làm các loại cơ sở dữ liệu khác như PostGreSQL, MySQL…

Khi sử dụng PyQt để kết nối tới các loại cơ sở dữ liệu khác nhau ta cần kiểm tra xem PyQt đã có các loại Drivers nào rồi, nếu chưa có ta cần cài đặt.

Để kiểm tra các loại Drivers này ta dùng lệnh dưới đây:

from PyQt6.QtSql import QSqlDatabase

drivers=QSqlDatabase.drivers()

print(drivers)

Giả sử khi chạy lệnh trên, bạn có mảng các Driver sau trong máy tính:

['QSQLITE','QMYSQL', 'QMYSQL3', 'QODBC', 'QODBC3', 'QPSQL', 'QPSQL7']

Máy tính có Driver nào thì ta mới sử dụng được Driver đó. Như vậy tùy vào nhu cầu sử dụng mà ta cần cài Driver phù hợp nếu PyQt chưa support trong máy của bạn.

Để kết nối tới các Remote Server ví dụ như MYSQL, Microsoft SQL Server… thì bạn dùng tập các mã lệnh đưới đây:

db = QSqlDatabase('<driver>')
db.setHostName('<localhost>')
db.setPort('<port number>')
db.setDatabaseName('<databasename>')
db.setUserName('<username>')
db.setPassword('<password>')
db.open()

Trong bài học này chúng ta kết nối với Local server, cụ thể là SQLite Database. Ví dụ ta muốn kết nối tới cơ sở dữ liệu “Chinook_Sqlite.sqlite” đặt trong cùng thư mục dự án, thì chúng ta sẽ sử dụng các lệnh sau:

baseDir=os.path.dirname(__file__)
databasePath=os.path.join(baseDir,"Chinook_Sqlite.sqlite")
self.db=QSqlDatabase("QSQLITE")
self.db.setDatabaseName(databasePath)
self.db.open()

Sau khi kết nối tới SQLite Database thành công, ta có thể tạo đối tượng QSqlTableModel để truy suất tới bảng dữ liệu trong SQLite Database, ví dụ bảng “Employee“:

self.model = QSqlTableModel(db=self.db)
self.model.setTable("Employee")
self.model.select()
self.model.setEditStrategy(QSqlTableModel.EditStrategy.OnFieldChange)

Hàm setEditStrategy() nhận vào enum để xác định cách thức cập nhật dữ liệu trên model:

EnumÝ nghĩa chức năng
QSqlTableModel.EditStrategy.OnFieldChangeMô hình sẽ tự động cập nhật dữ liệu khi người dùng di chuyển ra khỏi ô nhập liệu
QSqlTableModel.EditStrategy.OnRowChangeMô hình sẽ tự động cập nhật dữ liệu khi người dùng di chuyển ra khỏi dòng nhập liệu
QSqlTableModel.EditStrategy.OnManualSubmitChương trình không tự động cập nhật dữ liệu ngay, mà nó lưu những dòng dữ liệu được chỉnh sửa vào caches. Và khi muốn cập nhật thì ta gọi hàm submitAll() hoặc nếu hủy sự thay đổi thì ta gọi hàm revertAll()

Để hiển thị dữ liệu lên QTableView ta gọi lệnh sau:

self.tableView.setModel(self.model)

Để filter dữ liệu theo các thuộc tính ta viết lệnh:

def processFilterName(self,s):
    filter_str = 'LastName LIKE "%{}%" or FirstName LIKE "%{}%"'.format(s,s)
    self.model.setFilter(filter_str)

Ví dụ mã lệnh ở trên là filter dữ liệu theo LastName và FirstName. Chẳng hạn như lọc ra các Employee mà FirstName và LastName có chứa từ “an”.

Dưới đây là bảng ý nghĩa cú pháp của Filter:

Cú pháp(Pattern)Ý nghĩa chức năng
columnName=“{}”Lọc chính xác dữ liệu mà thuộc tính
columnName LIKE “{}%”Lọc các dòng dữ liệu mà nó chứa chuỗi so khớp đằng trước nó
columnName LIKE %{}”Lọc các dòng dữ liệu mà nó chứa chuỗi so khớp đằng sau nó
columnName LIKE %{}%”Lọc ra các dòng dữ liệu mà nó có chứa chuỗi so khớp

Trong mỗi pattern ở trên, {} là chuỗi tìm kiếm mà ta phải viết:

“{}”.format(search_str)

Ngoài ra chúng ta có thể dùng QSqlQuery để lọc dữ liệu theo cột:

def processFilterColumns(self):
    query = QSqlQuery("SELECT EmployeeId, FirstName, LastName FROM Employee ", db=self.db)
    self.model.setQuery(query)

Mã lệnh ở trên sẽ giúp truy vấn dữ liệu nhưng chỉ lọc ra các cột EmployeeId, FirstName, LastName

Bây giờ ta đi vào chi tiết từng bước để ứng dụng kiến trúc Model View, sử dụng QSqlTableModel để truy vấn/mapping bảng dữ liệu và hiển thị Lên QTableView.

Bước 1: Tạo dự án “LearnModelViewSQLitePart5” có cấu trúc như dưới đây:

  • “Chinook_Sqlite.sqlite” là cơ sở dữ liệu SQLite mẫu để giới thiệu ở bài học trước
  • “MainWindow.ui” là file thiết kế giao diện bằng Qt Designer
  • “MainWindow.py” là file Generate Python code của MainWindow.ui
  • “MainWindowEx.py” là mã lệnh kế thừa từ Generate Python code để xử lý sự kiện người dùng, nạp giao diện, gán model
  • “MyApp.py” là file mã lệnh để thực thi chương trình

Bước 2: Thiết kế giao diện “MainWindow.ui” như hình dưới đây:

Ta kéo thả và đặt tên cho các Widget như trên

Bước 3: Generate Python code cho MainWindow.ui, đặt tên MainWindow.py:

# Form implementation generated from reading ui file 'MainWindow.ui'
#
# Created by: PyQt6 UI code generator 6.4.2
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt6 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(463, 327)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.label = QtWidgets.QLabel(parent=self.centralwidget)
        self.label.setObjectName("label")
        self.verticalLayout.addWidget(self.label)
        self.lineEditFilter = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditFilter.setObjectName("lineEditFilter")
        self.verticalLayout.addWidget(self.lineEditFilter)
        self.tableView = QtWidgets.QTableView(parent=self.centralwidget)
        self.tableView.setObjectName("tableView")
        self.verticalLayout.addWidget(self.tableView)
        self.pushButtonFilter = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonFilter.setObjectName("pushButtonFilter")
        self.verticalLayout.addWidget(self.pushButtonFilter, 0, QtCore.Qt.AlignmentFlag.AlignLeft)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 463, 26))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Trần Duy Thanh - SQLite"))
        self.label.setText(_translate("MainWindow", "Filter name:"))
        self.pushButtonFilter.setText(_translate("MainWindow", "Filter Columns"))

Bước 5: Tạo “MainWindowEx.py“, kế thừa từ Generate Python code để nạp giao diện, xử lý nghiệp vụ, gán mô hình:

import os.path

from PyQt6.QtSql import QSqlDatabase, QSqlTableModel, QSqlQuery

from MainWindow import Ui_MainWindow


class MainWindowEx(Ui_MainWindow):
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.loadDatabase()
        self.lineEditFilter.textChanged.connect(self.processFilterName)
        self.pushButtonFilter.clicked.connect(self.processFilterColumns)

Ta overrice setupUi() để nạp giao diện, gọi hiển thị dữ liệu cũng như xử lý các signal cho widget.

Tiếp theo ta viết hàm loadDatabase(), hàm này sẽ đọc SQLite Database và truy vấn bảng “Empoyee” rồi hiển thị lên QTableView, sử dụng kiến trúc Model View:

def loadDatabase(self):
    baseDir=os.path.dirname(__file__)
    databasePath=os.path.join(baseDir,"Chinook_Sqlite.sqlite")
    self.db=QSqlDatabase("QSQLITE")
    self.db.setDatabaseName(databasePath)
    self.db.open()
    self.model = QSqlTableModel(db=self.db)
    self.model.setTable("Employee")
    self.model.select()
    self.model.setEditStrategy(QSqlTableModel.EditStrategy.OnFieldChange)
    self.tableView.setModel(self.model)

Tiếp đến là ta xử lý lọc dữ liệu theo dòng khi người dùng nhập liệu vào QLineEdit:

def processFilterName(self,s):
    filter_str = 'LastName LIKE "%{}%" or FirstName LIKE "%{}%"'.format(s,s)
    self.model.setFilter(filter_str)

Tiếp sau đó ta viết hàm filter dữ liệu theo cột bằng QSqlQuery, đây là đối tượng rất tiện lợi cho ta tùy biến truy vấn dữ liệu:

def processFilterColumns(self):
    query = QSqlQuery("SELECT EmployeeId, FirstName, LastName FROM Employee ", db=self.db)
    self.model.setQuery(query)

Dưới đây là mã lệnh đầy đủ của MainWindowEx.py:

import os.path

from PyQt6.QtSql import QSqlDatabase, QSqlTableModel, QSqlQuery

from MainWindow import Ui_MainWindow


class MainWindowEx(Ui_MainWindow):
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.loadDatabase()
        self.lineEditFilter.textChanged.connect(self.processFilterName)
        self.pushButtonFilter.clicked.connect(self.processFilterColumns)
    def loadDatabase(self):
        baseDir=os.path.dirname(__file__)
        databasePath=os.path.join(baseDir,"Chinook_Sqlite.sqlite")
        self.db=QSqlDatabase("QSQLITE")
        self.db.setDatabaseName(databasePath)
        self.db.open()
        self.model = QSqlTableModel(db=self.db)
        self.model.setTable("Employee")
        self.model.select()
        self.model.setEditStrategy(QSqlTableModel.EditStrategy.OnFieldChange)
        self.tableView.setModel(self.model)
    def processFilterName(self,s):
        filter_str = 'LastName LIKE "%{}%" or FirstName LIKE "%{}%"'.format(s,s)
        self.model.setFilter(filter_str)
    def processFilterColumns(self):
        query = QSqlQuery("SELECT EmployeeId, FirstName, LastName FROM Employee ", db=self.db)
        self.model.setQuery(query)
    def show(self):
        self.MainWindow.show()

Bước 6: Cuối cùng ta viết mã lệnh “MyApp.py”

from PyQt6.QtWidgets import QApplication, QMainWindow

from MainWindowEx import MainWindowEx

app=QApplication([])
myWindow=MainWindowEx()
myWindow.setupUi(QMainWindow())
myWindow.show()
app.exec()

Chạy MyApp.Py ta có kết quả như mong muốn:

Như vậy là Tui đã hướng dẫn xong cách ứng dụng kiến trúc Model View trong tương tác và hiển thị cơ sở dữ liệu dạng SQL, cụ thể là SQLite Database, cung cấp tính năng lọc dữ liệu theo dòng, lọc dữ liệu theo cột, cũng như cập nhật dữ liệu ngay trong QTableView khi có sự thay đổi, bạn có thể áp dụng để làm các loại cơ sở dữ liệu khác.

Các bạn tải source code đầy đủ ở đây:

https://www.mediafire.com/file/d568w6uiz3qc8jh/LearnModelViewSQLitePart5.rar/file

Bài học sau Tui hướng dẫn các bạn cách trực quan hóa dữ liệu bằng các Chart, đây là một trong các thư viện phổ biến và quan trọng trong trình bày trực quan dữ liệu để đưa ra các đồ thị, biểu đồ. Các bạn chú ý theo dõi

Chúc các bạn thành công

One thought on “Bài 30: Kiến trúc Model View-SQL Databases–PyQt6–Part 5”

Leave a Reply