© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
J. M. WillmanBeginning PyQthttps://doi.org/10.1007/978-1-4842-7999-1_14

14. Introduction to Handling Databases

Joshua M Willman1  
(1)
Sunnyvale, CA, USA
 

Data is fundamental to the ways that modern business, communications, science, and even our personal lives are changing. The information we create from our online shopping, social media posts, search engine queries, and location data is collected, managed, and analyzed and can be used for a number of reasons, including to track consumer patterns, to train artificial intelligence algorithms, or even to study the geographic distribution of particular events such as diseases.

In this chapter, we are going to
  • Learn about Qt’s Model/View architecture for creating GUIs that work with data

  • Use the QTableView class to build data-handling applications

  • See how to work with CSV files in PyQt

  • Introduce the QtSql module for creating and managing SQL relational databases

Before we begin, let’s think a little more about data’s usefulness.

Thinking About Data

Data analysis , or the process of organizing, modifying, and modeling data, is an important process, and this chapter will have a look at working with structured data for GUI development. Data can be stored in many different formats, including textual, visual, and multimedia.

In order to analyze data, we need to organize it into structures that we can store and then access electronically through a computer system. Sometimes, you may only be working with a small dataset consisting of one or two files. Other times, you may need to access certain portions of an entire database filled with private information. A database is an organized collection of multiple datasets.

We generally view the data from files and databases in tables. The rows and columns of a table typically work best for handling the style of data in data files. If we had a dataset of employees in a company, each row might represent an individual employee in the company, while each column depicts the different types of attributes for each employee, such as their age, salary, and employee ID number.

This chapter will focus on using PyQt’s table classes for displaying and manipulating data. We will see how to use tables to work with CSV files and to build and interact with the SQL database management language. Of course, there are also other formats that you can use for viewing data, namely, lists and trees, should they better fit your application’s requirements.

Introduction to Model/View Programming

Qt, and therefore PyQt, needs a system to access, display, and manage data that can be presented to the user. An older technique used for managing the relationship between data and its visual representation for user interfaces is the Model-View-Controller (MVC) software design pattern. MVC divides a program’s logic into three interlinked components: a model, a view, and a controller.

Qt utilizes a similar design pattern that is based on MVC: the Model/View paradigm.

The Components of the Model/View Architecture

Model/View programming, similar to MVC, also separates the logic between three components but combines the view and the controller objects and introduces a new element – a delegate. A diagram of the architecture can be seen in Figure 14-1.
Figure 14-1

The Model/View architecture

  • Model The class that communicates with the data source, accessing the data, and provides a point of connection between the data and the view and delegate.

  • View The class that is responsible for displaying the data to the user, either in list, table, or tree formats, and for retrieving items of data from the model using model indexes. The view also has similar functionality to the controller in the MVC pattern, which handles the input from a user’s interaction with items displayed in the view.

  • Delegate The class that is in charge of painting items and providing editors in the view. The delegate also communicates back to the model if an item has been edited.

Using the model/view structure has quite a few benefits, specifically being ideal for developing large-scale applications, giving more flexibility and control over the appearance and editing of data items, simplifying the framework for displaying data, and offering the ability to display multiple views of a model at the same time.

PyQt’s Model/View Classes

As we saw in Chapter 10, Qt provides a few convenience classes for working with data. These classes greatly streamline a developer’s work and provide all of the functionality needed for basic data applications. The following is a quick recap:
  • QTreeWidget – Creates a table of items

  • QListWidget – Displays a list of items

  • QTreeWidget – Provides a hierarchical tree-like structure

What these widgets provide are all the tools necessary to work with data and already include the view, model, and delegate classes grouped together into single classes. However, these classes are more focused on item-based interfaces and are less flexible than working with the Model/View structure. It is also worth noting that each of these widgets inherits behavior from the abstract item view class, QAbstractItemView, creating the behavior for selecting items and managing headers.

An abstract class provides the points of connection, referred to as an interface, between other components, providing the class methods, functionality, and default implementation of features. They are the basis used for creating other classes. Qt’s abstract data classes can also be used to create custom models, views, or delegates.

Let’s take a moment to try and understand a little more about the model, view, and delegate classes that Qt provides:
  • Models All models are based on the QAbstractItemModel class, defining the interface used by both views and delegates to access data. They can be used to handle lists, tables, or trees. Data can take on a number of forms, including Python data structures, separate classes, files, or databases. Some other model classes are QStandardItemModel, QFileSystemModel, and SQL-related models.

  • Views All views are based on QAbstractItemView and are used to display data items from a data source, including QListView, QTableView, and QTreeView.

  • Delegates The base class is QAbstractItemDelegate, responsible for drawing items from the model and providing an editor widget for modifying items. For example, while editing a cell in a table, an editor widget, such as QLineEdit, is placed directly on top of the item.

In this section, we are going to create a GUI that demonstrates how to use the Model/View classes for displaying data in tables. For the GUI in Figure 14-2, data that is contained in a CSV file will be loaded and displayed in the table. In this example, we will also take a look at using the QStandardItemModel class , which provides a general model for storing data. This example will also demonstrate how we can connect a model for managing data to a view that will display the data.
Figure 14-2

Table created using the Model/View architecture

Communication between the models, views, and delegates is handled by signals and slots. The model uses signals to notify the view about changes to the data. The view generates signals that provide information about how a user interacts with items. For a simple GUI, you may not need to interact with a delegate, but it is important to know that signals from the delegate are emitted while editing an item in the view. This, in turn, informs the model and view about the state of the editor widget.

Explanation for Introduction to Model/View

Be sure to download the files folder from the GitHub repository before beginning this program. Listings 14-1 to 14-3 illustrate how to use Model/View programming to display the contents of a small CSV file in a table view. In Listing 14-1, we’ll use the basic_window.py script from Chapter 1 to begin setting up the MainWindow class.
# model_view_ex.py
# Import necessary modules
import sys, csv
from PyQt6.QtWidgets import (QApplication, QWidget,
    QTableView, QAbstractItemView, QVBoxLayout)
from PyQt6.QtGui import (QStandardItemModel, QStandardItem)
class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.initializeUI()
    def initializeUI(self):
        """Set up the application's GUI."""
        self.setGeometry(100, 100, 450, 300)
        self.setWindowTitle("Model and View Example")
        self.setupMainWindow()
        self.loadCSVFile()
        self.show()
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec())
Listing 14-1

Code for setting up the MainWindow class in the introductory Model/View example

Tables are great for organizing and displaying various types of textual (and sometimes graphical) data, such as employee or inventory information.

We begin by importing classes, including QTableView from the QtWidgets module and the QStandardItemModel and QStandardItem classes from QtGui. QStandardItemModel will supply the item-based model we need to work with the data; QStandardItem creates the items that are used in the model.

Setting Up the Model, View, and Selection Modes

For the setUpMainWindow() method in Listing 14-2, instances of both the model using QStandardItemModel and the QTableView class are created. The loadCSVFile() method for loading the data into the table is handled in Listing 14-3.
# model_view_ex.py
    def setupMainWindow(self):
        """Create and arrange widgets in the main window."""
        self.model = QStandardItemModel()
        table_view = QTableView()
        table_view.setSelectionMode(
            QAbstractItemView.SelectionMode.ExtendedSelection)
        table_view.setModel(self.model)
        # Set initial row and column values
        self.model.setRowCount(3)
        self.model.setColumnCount(4)
        main_v_box = QVBoxLayout()
        main_v_box.addWidget(table_view)
        self.setLayout(main_v_box)
Listing 14-2

Code for the setUpMainWindow() method in the introductory Model/View example

There are different ways that users can select items in the table view. The setSelectionMode() setter, along with the QAbstractItemView.SelectionMode enum, specifies how the view responds to user selections. The following list describes the different flags:
  • SingleSelection – A user can select only a single item at any given time. The item that was previously selected will become unselected.

  • ExtendedSelection – Allows for normal selection and also for a user to select multiple items by pressing the Ctrl key (Cmd on MacOS) while clicking an item in the view or to select several items using the Shift key.

  • ContiguousSelection – Allows for normal selection and also for a user to select multiple items by pressing the Shift key.

  • MultiSelection – The user can select and deselect multiple items by clicking and dragging the mouse in the table.

  • NoSelection – Selection of items is disabled.

To set up the view to display data from the model, you’ll need to call the setModel() method and pass the model you instantiated. The model used for this example is QStandardItemModel.

In Chapter 10 where we looked at QTableWidget, the setRowCount() and setColumnCount() methods were called on the table widget. When using QTableView, these methods are not built-in and instead are called on the model like in the following line of code from Listing 14-2:
        self.model.setRowCount(3)

The table_view widget is added to the QVBoxLayout.

Working with CSV Files

In initializeUI() from Listing 14-1, the next step is to call loadCSVFile() and read the contents of the data file. The items are then added to the model to be displayed in the view. The contents of the file are displayed in Figure 14-3.
Figure 14-3

Example of the data stored in a CSV file

In the loadCSVFile() method in Listing 14-3, we can see how to read headers and data from a CSV file. Comma-Separated Values (CSV) is a very common format used for storing the data of spreadsheets and datasets.
# model_view_ex.py
    def loadCSVFile(self):
        """Load header and rows from CSV file."""
        file_name = "files/parts.csv"
        with open(file_name, "r") as csv_f:
            reader = csv.reader(csv_f)
            header_labels = next(reader)
            self.model.setHorizontalHeaderLabels(
                header_labels)
            for i, row in enumerate(csv.reader(csv_f)):
                items = [QStandardItem(item) for item in row]
                self.model.insertRow(i, items)
Listing 14-3

Code for the loadCSVFile() method in the introductory Model/View example

We’ll open the file, set up the reader object to read the sequences in the file, get the header labels, and skip to the next line using next(). For this example, we'll assume that the CSV file will have header labels. The horizontal labels for model are set using the list of items from the first row in the file, which are stored in header_labels.

For the remaining rows, we use a list comprehension to read the items for each row into a list. Items created for QStandardItemModel need to be instances of QStandardItem. The insertRow() method is used to insert the list of items into the ith row.

With a fundamental understanding of how to create a model and a view, we can move onto creating GUIs that handle larger datasets. While the rest of this chapter will focus on SQL-based classes and models, you do not have to use SQL. All you need is some form of structured data in order to use the Model/View classes.

Working with SQL Databases in PyQt

Now that we have looked at PyQt’s Model/View architecture and the QTableView class, let’s see how to use SQL for handling structured data.

What Is SQL?

Structured Query Language (SQL) is a programming language designed for communication with databases. The data stored in databases is organized into a set of tables. The rows of the tables are referred to as records, and the columns are referred to as fields. Each column can only store a specific kind of information, such as names, dates, or numbers.

With SQL, we can query the data stored in relational databases – a collection of data items that have predefined relationships across multiple tables, marked by a unique identifier known as a foreign key. In a relational database, multiple tables comprise a schema, more than one schema makes up a database, and those databases are stored on a server. Relational databases allow for multiple users to handle the data at the same time. For this reason, accessing a database often requires a user to log in with a username and password in order to connect to the database.

This section will focus solely on using SQL along with classes from PyQt’s QtSql module for creating a very basic database management system interface.

Working with Database Management Systems

The QtSql module provides drivers for a number of Relational Database Management Systems (RDBMS) , including MySQL, Oracle, Microsoft SQL Server, PostgreSQL, and SQLite versions 2 and 3. An RDBMS is the software that allows users to interact with relational databases using SQL. More information about Qt SQL drivers can be found at https://doc-snapshots.qt.io/qt6-dev/sql-driver.html.

For the following examples, we will be using SQLite 3 since the library already comes shipped with Python and is included with Qt. SQLite is not a client-server database engine, so we do not need a database server. In addition, SQLite operates on a single file and is mainly used for small desktop applications.

Getting Familiar with SQL Commands

The SQL language already has its own commands for generating queries from databases. Using these commands, a user can perform a number of different actions for interacting with database tables. For example, the SQL SELECT statement can be used to retrieve records from a table. If you had database for a dog identification registry that contained a table called dog_registry, you could select all of the records in the table with the following statement:
    SELECT * FROM dog_registry
The asterisk, *, means all columns in the table. When you are creating a query, you should consider where you are getting your data from, including which database or table. You should keep in mind what fields you will use. Also be mindful of any conditions in the selection. For example, do you need to display all of the pets in the database or only a specific breed of dog? An example of this using the dog_registry is shown in the following line:
    SELECT name FROM dog_registry WHERE breed = 'shiba inu'
Using different drivers will more than likely entail using different SQL syntax, but PyQt can handle the differences. Table 14-1 lists a few common SQLite 3 commands that will be used in this chapter’s examples.
Table 14-1

A list of common SQLite keywords and functions that can be found in this chapter1

SQLite Keywords

Description

AUTOINCREMENT

Generates a unique number automatically when a new record is inserted into the table

CREATE TABLE

Creates a new table in the database

DELETE

Deletes a row from the table

DROP TABLE

Deletes a table that already exists in the database

FOREIGN KEY

Constraint that links two tables together

FROM

Specifies the table to interact with when selecting or deleting data

INTEGER

Signed integer datatype

INSERT INTO

Inserts new rows into the table

MAX()

Function that finds the maximum value of a specified column

NOT NULL

Constraint that ensures a column will not accept NULL values

PRIMARY KEY

Constraint that uniquely identifies a record in the table

REFERENCES

Used with FOREIGN KEY to specify another table that has relation with the first table

SELECT

Selects data from a database

SET

Identifies which columns and values should be updated

UNIQUE

Constraint that ensures all values in a column are unique

UPDATE

Updates existing values in a row

VALUES

Defines the values of an INSERT INTO statement

VARCHAR

Variable character datatype for strings

WHERE

Filters the results of a query to include only records that satisfy specific conditions

In the following sections, we will work toward creating a user interface that can be used to view and manage a database’s information in a table view.

Project 14.1 – Account Management GUI

For this project, we are going to take a different approach to designing the account management GUI. This section builds up to the final project by working through a number of smaller example programs. There is a good deal of information to unpack, and if this is your first time working with SQL, especially to build an interface in PyQt, then the process for working with databases can become a little unclear.

Imagine you have a business and you want to create a database to keep track of your employees’ information. You want to include information such as their first and last names, employee IDs, email addresses, departments, and the countries where they work. (This could be extended to include more information such as salaries, phone numbers, and dates of hire.) In the beginning, a small database is okay. However, as your workforce builds, so will the information. Some employees may have the same first or last name or even work in the same country. You need a way to manage all of those employees so that fields in the database are populated with the correct information and data types.

Using a relational database, we can avoid issues with the data’s integrity. We could set up multiple tables, one for the different employees’ accounts and one for the countries. For this example, we only use repeating country names to demonstrate how to use PyQt’s classes for working with relational databases. Figure 14-4 displays the account management GUI.
Figure 14-4

The account management GUI. The last row of the table displays a new record being added to the database

This project is broken down into the following parts:
  1. 1.

    Introduce how to use QSqlDatabase to connect to databases and QSqlQuery for creating queries.

     
  2. 2.

    A few examples of how to use QSqlQuery for editing database items.

     
  3. 3.

    Introduce QSqlTableModel for creating editable data models that work with tables that do not contain foreign keys.

     
  4. 4.

    Show how use to QSqlRelationalTableModel for creating editable data models that work with tables that do have foreign key support.

     
  5. 5.

    Create the account management GUI.

     

Let’s get started!

Explanation for Working with the QtSql Module

In this first example, we are going to see how to use QSqlQuery to create a small database that we will be able to view in the account management GUI. The database has two tables, accounts and countries. The two tables are linked together through the country_id field in accounts and the id field in countries.

Note

This program does not create a GUI. Rather, it demonstrates how to get started with QSqlDatabase for connecting to a database and how to use QSqlQuery to create entries and in a database. It also creates the database, accounts.db, that is used throughout the remainder of the chapter.

Creating a Connection to a Database

Since this program does not create a GUI, we’ll only need to import the QSqlDatabase and QSqlQuery classes from QtSql. We will use QSqlDatabase to create the connection that allows access to a database; QSqlQuery will be used to perform SQL statements in PyQt.

The connection to the database is made in Listing 14-4.
# create_database.py
# Import necessary modules
import sys, random
from PyQt6.QtSql import QSqlDatabase, QSqlQuery
class CreateEmployeeData:
    """Create a sample database for the project.
    Class demonstrates how to connect to a database, create
    queries, and create tables and records in those tables."""
    # Create connection to database. If db file does not
    # exist, a new db file will be created
    # Use the SQLite version 3 driver
    database = QSqlDatabase.addDatabase("QSQLITE")
    database.setDatabaseName("files/accounts.db")
    if not database.open():
        print("Unable to open data source file.")
        sys.exit(1) # Error code 1 - signifies error
Listing 14-4

Connecting to a database with QSqlDatabase

We begin by creating a connection to the database in the CreateEmployeeData class. The addDatabase() function allows you to specify the SQL driver that you want to use. The examples in this chapter use SQLite 3 so we pass QSQLITE. Once the database object is created, we can set the other connection parameters, including which database we are going to use, the username, password, host name, and the connection port. For SQLite 3, we only need to specify the name of the database with setDatabaseName(). You can also create multiple connections to a database by passing an additional argument, a connection name, to addDatabase() after the driver argument.

Note

A connection is referenced by its name, not by the name of the database. If you want to give your database a name, pass it as an argument after the driver in the addDatabase() method. If no name is specified, then a default connection will be used.

If accounts.db does not already exist, then it will be created. Once the parameters are set, you must call open() to activate the connection to the database. A connection cannot be used until it is opened.

Building a Dataset with QSqlQuery

Now that the connections are established in CreateEmployeeData, we can begin querying our database. You typically might start with databases that already have data in them, but in this example, we are going to see how we can create a database using SQL commands. To query a database using PyQt, we first need to create an instance of QSqlQuery. This is handled in Listing 14-5.
# create_database.py
    query = QSqlQuery()
    # Erase database contents
    query.exec("DROP TABLE accounts")
    query.exec("DROP TABLE countries")
    query.exec("""CREATE TABLE accounts (
        id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
        employee_id INTEGER NOT NULL,
        first_name VARCHAR(30) NOT NULL,
        last_name VARCHAR(30) NOT NULL,
        email VARCHAR(40) NOT NULL,
        department VARCHAR(20) NOT NULL,
        country_id VARCHAR(20) REFERENCES countries(id))""")
    # Positional binding to insert records into the database
    query.prepare("""INSERT INTO accounts (
        employee_id, first_name, last_name,
        email, department, country_id)
        VALUES (?, ?, ?, ?, ?, ?)""")
Listing 14-5

Building a dataset with QSqlQuery in the CreateEmployeeData class

The exec() method is used to execute the SQL queries in PyQt. In the following lines, we want to create a query object and delete the table accounts:
    query = QSqlQuery()
    query.exec("DROP TABLE accounts")
Let’s next create a new accounts table using exec() and the SQL command CREATE TABLE accounts. Each table entry will have its own unique id by using AUTOINCREMENT. The accounts table will include information for an employee’s id, first name, last name, email, department, and the country where they are located. We also create a countries table that holds the names of the employee’s countries and is linked to the accounts table using the following line:
    country_id VARCHAR(20) REFERENCES countries(id))
The country_id references the countries table’s id. Figure 14-5 shows the connection between the two tables.
Figure 14-5

The relations between the accounts and countries tables

The next task is to insert records into the tables. We could continue to use exec() to execute queries, but this would become tedious if we have a large database. To insert multiple records at the same time, we separate the query from the actual values being inserted using placeholders and the prepare() method. The placeholder will act as a temporary variable, allowing users to supply different data using the same SQL query. In the following code, the positional placeholders are the ?. PyQt supports two placeholder syntaxes: the ODBC style, which uses ?, and the Oracle style, which uses :field_name.
    query.prepare("""INSERT INTO accounts (
                  employee_id, first_name, last_name,
                  email, department, country_id)
                  VALUES (?, ?, ?, ?, ?, ?)""")

Each field, such as employee_id or first_name, is associated with one of the placeholders. Since we used AUTOINCREMENT for id, we do not have to include the field or a placeholder in the query.

The prepare() method gets the query ready for execution. If the query is prepared successfully, then values can be bound to the fields using the addBindValue() method. For information about executing SQL statements in Qt, have a look at https://doc.qt.io/qt-6/sql-sqlstatements.html. Different approaches for binding values are found at https://doc.qt.io/qt-6/sql-sqlstatements.html.

Next, we’ll create the values for the first_name, last_name, and other fields in the SQL tables using Python lists and dictionaries in Listings 14-6 and 14-7.
# create_database.py
    first_names = ["Emma", "Olivia", "Ava", "Isabella",
        "Sophia", "Mia", "Charlotte", "Amelia", "Evelyn",
        "Abigail", "Valorie", "Teesha", "Jazzmin", "Liam",
        "Noah", "William", "James", "Logan", "Benjamin",
        "Mason", "Elijah", "Oliver", "Jason", "Lucas",
        "Michael"]
    last_names = ["Smith", "Johnson", "Williams", "Brown",
        "Jones", "Garcia", "Miller", "Davis", "Rodriguez",
        "Martinez", "Hernandez", "Lopez", "Gonzalez",
        "Wilson", “Anderson", "Thomas", "Taylor", "Moore",
        "Jackson", "Martin", "Lee", "Perez", "Thompson",
        "White", "Harris"]
    # Create data for the first table, account
    employee_ids = random.sample(
        range(1000, 2500), len(first_names))
    countries = {"USA": 1, "India": 2, "China": 3,
        "France": 4, "Germany": 5}
    country_names = list(countries.keys())
    country_codes = list(countries.values())
    departments = ["Production", "R&D", "Marketing", "HR",
                   "Finance", "Engineering", "Managerial"]
    for f_name in first_names:
        l_name = last_names.pop()
        email = (l_name + f_name[0]).lower() + "@job.com"
        country_id = random.choice(country_codes)
        dept = random.choice(departments)
        employee_id = employee_ids.pop()
        query.addBindValue(employee_id)
        query.addBindValue(f_name)
        query.addBindValue(l_name)
        query.addBindValue(email)
        query.addBindValue(dept)
        query.addBindValue(country_id)
        query.exec()
Listing 14-6

Creating the values for the example dataset in the CreateEmployeeData class, part 1

A for loop is then used where we bind the values to the placeholders. The exec() method is called at the end of each iteration to insert the values into the accounts table. The countries table is prepared in a similar manner in Listing 14-7.
# create_database.py
    # Create data for the second table, countries
    country_query = QSqlQuery()
    country_query.exec("""CREATE TABLE countries (
        id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
        country VARCHAR(20) NOT NULL)""")
    country_query.prepare(
        "INSERT INTO countries (country) VALUES (?)")
    for name in country_names:
        country_query.addBindValue(name)
        country_query.exec()
    print("[INFO] Database successfully created.")
if __name__ == "__main__":
    CreateEmployeeData()
    sys.exit(0)
Listing 14-7

Creating the values for the example dataset in the CreateEmployeeData class, part 2

Once the tables are populated, we call sys.exit(0) to exit the program. There is no QApplication instance since there is no GUI.

Visualizing SQL Data Using SQLite

If you want to create a readable SQLite file from accounts.db in order to visualize the data, there are a few options. The first is to use available tools, such as SQLiteStudio at https://sqlitestudio.pl/, for browsing databases.

Another option is to use the SQLite library. If you are using macOS or Linux, this should already be installed on your system. For Windows, you may have to follow along with the following additional steps (macOS and Linux users should be able to skip this list):
  1. 1.

    On the SQLite download page, www.sqlite.org/download.html, download the Precompiled Binaries for Windows. Look for the option that includes command-line tools. Locate where the files have downloaded on your computer. Inside the folder, you’ll notice three files, one of which is sqlite3.exe.

     
  2. 2.

    Open a shell window and navigate to C:>. Next, mkdir sqlite.

     
  3. 3.

    Move the three files from step 1 to your new sqlite folder. One way to do this is by opening the folder with start .sqlite and dragging and dropping the files.

     
  4. 4.

    So that you’ll be able to use SQLite no matter what folder you are in, you’ll need to add the sqlite folder you made to your PATH environment variable. On the command line, enter $env:Path += ";C:sqlite".

     
  5. 5.

    Finally, run sqlite3 in the command line, and you should enter the SQLite shell environment. To exit, type .quit.

     
Once you are sure that SQLite is installed, navigate to the files folder in your application’s directory where accounts.db is located, and use the following sqlite3 command in your shell:
$ sqlite3 accounts.db .dump >> accounts.sql

You’ll see a new file, accounts.sql, created in the files folder. Also, worth a mention is that SQLite 3 is included as part of the standard Python library, so you can import sqlite in your applications if necessary.

In the following section, you’ll see how to use QSqlQuery to do more than just create tables.

Explanation for Querying a Database with QSqlQuery

The program created from Listings 14-8 to 14-10 is not necessary for the accounting manager GUI, but it does give a few more examples for understanding how to input, update, and delete records with SQL in a PyQt application. The purpose of the section is to demonstrate how to open an existing database and modify its contents. We’ll do this for the database created in the “Explanation for Working with the QtSql Module” section.

For Listing 14-8, let’s import the QSqlDatabase and QSqlQuery classes again. We’ll also create a new class called QueryExamples and create two class methods:
  • createConnection() – Establishes the connection to the database

  • exampleQueries() – Queries the database to acquire and modify existing entries

# query_examples.py
# Import necessary modules
import sys
from PyQt6.QtSql import QSqlDatabase, QSqlQuery
class QueryExamples:
    def __init__(self):
        super().__init__()
        self.createConnection()
        self.exampleQueries()
    def createConnection(self):
        """Create connection to the database."""
        database = QSqlDatabase.addDatabase("QSQLITE")
        database.setDatabaseName("files/accounts.db")
        if not database.open():
            print("Unable to open data source file.")
            sys.exit(1) # Error code 1 - signifies error
Listing 14-8

Creating the connection for the QueryExamples class

We start by adding a database using the SQLite 3 driver and a default connection since no connection name is passed to addDatabase(). Next, set the database created in the previous program, accounts.db. Next, we’ll complete the connection using open().

In exampleQueries() in Listing 14-9, let’s take a look at how to use the QSqlQuery class and the SQL command SELECT to query the database.
# query_examples.py
    def exampleQueries(self):
        """Examples of working with the database.”""
        # The QSqlQuery constructor accepts an optional
        # QSqlDatabase object that specifies which database
        # connection to use. In this example, we don't specify
        # any connection, so the default connection is used.
        # If an error occurs, exec() returns false. The error
        # is then available as QSqlQuery::lastError()
        # Executing a simple query
        query = QSqlQuery()
        query.exec("SELECT first_name, last_name FROM
            accounts WHERE employee_id > 2000”)
        # Navigating the result set
        while (query.next()):
            f_name = str(query.value(0))
            l_name = str(query.value(1))
            print(f_name, l_name)
Listing 14-9

Demonstrating how to access SQL databases in PyQt

We create a new QSqlQuery instance to search for the first and last names of the employees whose employee ids are greater than 2000.

With that query, we could use the values from first_name and last_name to update or delete records. To cycle through the results of the query, we use the QSqlQuery method next(). Other methods that could be used to navigate the results include next(), previous(), first(), and last().

Additional queries are shown in Listing 14-10.
# query_examples.py
        # Inserting a single new record into the database
        query.exec("""INSERT INTO accounts (
                  employee_id, first_name, last_name,
                  email, department, country_id)
                  VALUES (2134, 'Robert', 'Downey',
                    '[email protected]', 'Managerial', 1)""")
        # Update a record in the database
        query.exec("UPDATE accounts SET department = 'R&D'
            WHERE employee_id = 2134")
        # Delete a record from the database
        query.exec("DELETE FROM accounts WHERE
            employee_id <= 1500")
if __name__ == "__main__":
    QueryExamples()
    sys.exit(0)
Listing 14-10

Demonstrating how to insert, update, and delete records using SQL and PyQt

To insert a single record, we can use the INSERT SQL command. In this query, we insert specific values for each field. You could also add multiple records into the database. Refer back to the “Explanation for Working with the QtSql Module” section to see how.

To update records, use UPDATE. We update the department value for the employee that was just inserted. Finally, to delete a record, use DELETE.

This example also has no GUI window. To see the changes, you could run this program after running the program in the “Explanation for Working with the QtSql Module” section and then use the GUI in the next section to visualize the results in a table.

Working with the QSqlTableModel Class

We are finally going to create a GUI for visualizing the database’s contents. In the table in Figure 14-6, we are only going to visualize the accounts table to demonstrate the QSqlTableModel class , an interface that is useful for reading and writing database records when you only need to use a single table with no links to other tables. The following program will demonstrate how to use Model/View programming to view the contents of a SQL database.
Figure 14-6

The table created using QSqlTableModel

We could use QSqlQuery to do all of the database work, but combining the class with the Model/View paradigm allows us to design GUIs that make the data management process simpler.

Explanation for Working with QSqlTableModel

Get started by using the basic_window.py script from Chapter 1 and then import the PyQt classes we need, including QSqlTableModel, in Listing 14-11. QHeaderView is the class that provides both horizontal and vertical headers for item view classes.

Next, create the MainWindow class for displaying the contents of the database.
# table_model.py
# Import necessary modules
import sys
from PyQt6.QtWidgets import (QApplication, QWidget,
    QTableView, QHeaderView, QMessageBox, QVBoxLayout)
from PyQt6.QtSql import QSqlDatabase, QSqlTableModel
class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.initializeUI()
    def initializeUI(self):
        """Set up the application's GUI."""
        self.setMinimumSize(1000, 500)
        self.setWindowTitle("SQL Table Model")
        self.createConnection()
        self.setUpMainWindow()
        self.show()
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec())
Listing 14-11

Code for the MainWindow class using QSqlTableModel

We’ll call createConnection() before setUpMainWindow() since the model and view objects in the main window rely on the data from the database. In the createConnection() method in Listing 14-12, we connect to the database and activate the connection with open(). This time, let’s check to make sure that the tables we want to use are in the database. If they cannot be found, then a dialog box like the one in Figure 14-7 will be displayed to inform the user and the program will close.
Figure 14-7

QMessageBox letting users know that the table they want to view is missing

# table_model.py
    def createConnection(self):
        """Set up the connection to the database.
        Check for the tables needed."""
        database = QSqlDatabase.addDatabase("QSQLITE")
        database.setDatabaseName("files/accounts.db")
        if not database.open():
            print("Unable to open data source file.")
            sys.exit(1) # Error code 1 - signifies error
        # Check if the tables we need exist in the database
        tables_needed = {"accounts"}
        tables_not_found = tables_needed -
            set(database.tables())
        if tables_not_found:
            QMessageBox.critical(None, "Error",
                f"""<p>The following tables are missing
                from the database: {tables_not_found}</p>""")
            sys.exit(1) # Error code 1 - signifies error
Listing 14-12

Code for the createConnection() method in the QSqlTableModel example

The instances of the QSqlTableModel and the QTableView are created in the setUpMainWindow() method in Listing 14-13. For QSqlTableModel, we need to set the database table we want to use with setTable(). Here, we’ll use the accounts table.
# table_model.py
    def setUpMainWindow(self):
        """Create and arrange widgets in the main window."""
        # Create the model
        model = QSqlTableModel()
        model.setTable("accounts")
        table_view = QTableView()
        table_view.setModel(model)
        table_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.ResizeMode.Stretch)
        # Populate the model with data
        model.select()
        main_v_box = QVBoxLayout()
        main_v_box.addWidget(table_view)
        self.setLayout(main_v_box)
Listing 14-13

Code for the setUpMainWindow() method in the QSqlTableModel example

Next, create a QTableView object and set its model using setModel(). To make the table stretch to fit into the view horizontally, we use the following line:
        table_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)

This line also handles stretching the table when the window resizes.

Finally, the model is populated with data using select(). If you have made changes to the table but have not submitted them, then select() will cause the edited items to return back to their previous states.

Figure 14-6 displays the contents of the database in a table view. Notice how the header labels display the field names used when the database was created. We will see how to set header labels when we actually create the account management GUI. Also, the country_id column currently only displays numbers associated with the different names in the countries table. If you only want to display specific columns, the following code lets you select which ones you want to display:
        model.setQuery(QSqlQuery("SELECT id, employee_id,
            first_name, last_name FROM accounts"))

In the next section, you’ll find out how to create and display relations set by foreign keys in the table view.

Working with the QSqlRelationalTableModel Class

Next, we are going to see how to use the QSqlRelationalTableModel class for working with relational databases. The QSqlRelationalTableModel class provides a model for managing and editing data in a SQL table, with additional support for using foreign keys. A foreign key is a SQL constraint used to link tables together.

The application in Figure 14-8 builds upon the GUI in the “Working with the QSqlTableModel Class” section.
Figure 14-8

The table created using QSqlRelationalTableModel

Explanation for Working with QSqlRelationalTableModel

Start with basic_window.py script from Chapter 1. This time we need to import QSqlRelationalTableModel since we are working with relational databases and foreign keys. Also, QSqlRelation is included because we’ll need to use the class to store the information about SQL foreign keys. The QSqlRelationalDelegate is also needed because we’ll need to display editor widgets in the columns that pertain to foreign keys.

Listing 14-14 handles all of this as well as sets up the MainWindow class.
# relational_model.py
# Import necessary modules
import sys
from PyQt6.QtWidgets import (QApplication, QWidget,
    QTableView, QMessageBox, QHeaderView, QVBoxLayout)
from PyQt6.QtSql import (QSqlDatabase, QSqlRelation,
    QSqlRelationalTableModel, QSqlRelationalDelegate)
class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.initializeUI()
    def initializeUI(self):
        """Set up the application's GUI."""
        self.setMinimumSize(1000, 500)
        self.setWindowTitle("Relational Table Model")
        self.createConnection()
        self.setUpMainWindow()
        self.show()
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec())
Listing 14-14

Code for the MainWindow class using QSqlRelationalTableModel

We connect to the database in Listing 14-15 just like we did in the QSqlTableModel example, except this time we are checking for both tables, accounts and countries.
# relational_model.py
    def createConnection(self):
        """Set up the connection to the database.
        Check for the tables needed."""
        database = QSqlDatabase.addDatabase("QSQLITE")
        database.setDatabaseName("files/accounts.db")
        if not database.open():
            print("Unable to open data source file.")
            sys.exit(1) # Error code 1 - signifies error
        # Check if the tables we need exist in the database
        tables_needed = {"accounts", "countries"}
        tables_not_found = tables_needed -
            set(database.tables())
        if tables_not_found:
            QMessageBox.critical(None, "Error",
                f"""<p>The following tables are missing
                from the database: {tables_not_found}</p>""")
            sys.exit(1) # Error code 1 - signifies error
Listing 14-15

Code for the createConnection() method in the QSqlRelationalTableModel example

For setUpMainWindow() in Listing 14-16, create instances of the QSqlRelationalTableModel and QTableView classes. The setTable() method causes model to fetch the accounts table’s information.
# relational_model.py
   def setUpMainWindow(self):
        """Create and arrange widgets in the main window."""
        # Create the model
        model = QSqlRelationalTableModel()
        model.setTable("accounts")
        # Set up relationship for foreign keys
        model.setRelation(model.fieldIndex("country_id"),
            QSqlRelation("countries", "id", "country"))
        table_view = QTableView()
        table_view.setModel(model)
        table_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.ResizeMode.Stretch)
        # Populate the model with data
        model.select()
Listing 14-16

Code for the setUpMainWindow() method in the QSqlRelationalTableModel example

The country_id field in accounts is mapped to countries table’s field, id. For the QSqlRelationalTableModel method setRelation(), we’ll need to pass the index of the column that contains a foreign key (done with fieldIndex()) and a QSqlRelation object that defines the relationship. For QSqlRelation, the field id of table countries maps to country_id in the accounts table. The final argument, country, specifies which field should be displayed in the accounts table.

If you compare Figure 14-8 to Figure 14-6, you’ll notice that data in the last column, country, has been updated to display the names of the countries and that the header has also been changed to country.

Adding Delegates to Edit Relational Data

The purpose of delegates when using Model/View classes becomes more obvious when you either start creating your own custom classes or when you need to use relational classes to select values for fields with foreign keys. With a delegate, an editor widget such as QLineEdit or QComboBox will appear when a user is editing data. You may not have even realized that every time you edit values in the cells of QTableView, you have been using a delegate all along. That’s because of how Qt seamlessly blends the view and delegates.

For SQL relational databases, QSqlRelationalDelegate data from a QSqlRelationalTableModel can be viewed and edited.

For the final portion of setUpMainWindow(), let’s create a QSqlRelationalDelegate instance and add it to table_view using setItemDelegate() in Listing 14-17.
# relational_model.py
        # Instantiate the delegate
        delegate = QSqlRelationalDelegate()
        table_view.setItemDelegate(delegate)
        main_v_box = QVBoxLayout()
        main_v_box.addWidget(table_view)
        self.setLayout(main_v_box)
Listing 14-17

Adding delegates in the QSqlRelationalTableModel example

Now if you double-click in the country column, you will see a QComboBox appear containing the list of countries. An example of this is shown in Figure 14-9.
Figure 14-9

An editor widget (QComboBox) displayed in a column with foreign keys

The last step is to create and set the layout for the main window.

By this point, you should have a fundamental understanding of how to use model and view classes, use QtSql classes to perform queries, and display foreign key relationships in a table using relational classes. We are now ready to build the account management GUI.

Explanation for the Account Management GUI

The account management GUI uses the QSqlRelationalTableModel for managing the accounts and countries tables. We'll use the concepts we learned in the previous sections to design a GUI with features for managing the database directly rather than programmatically. Refer back to Figure 14-4 to see the interface.

The application lets a user add, delete, and sort the contents of the table. Rows added or deleted will also update the database.

Also, be sure to download the icons folder from GitHub for this project.

For Listing 14-18, let’s start with the basic_window.py script from Chapter 1 and import a variety of classes.
# account_manager.py
# Import necessary modules
import sys, os
from PyQt6.QtWidgets import (QApplication, QWidget, QLabel,
    QPushButton, QComboBox, QTableView, QHeaderView,
    QAbstractItemView, QMessageBox, QHBoxLayout, QVBoxLayout,
    QSizePolicy)
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QIcon
from PyQt6.QtSql import (QSqlDatabase, QSqlQuery,
    QSqlRelation, QSqlRelationalTableModel,
    QSqlRelationalDelegate)
class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.initializeUI()
    def initializeUI(self):
        """Set up the application's GUI."""
        self.setMinimumSize(1000, 600)
        self.setWindowTitle("14.1 – Account Management GUI")
        self.createConnection()
        self.createModel()
        self.setUpMainWindow()
        self.show()
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec())
Listing 14-18

Code for the MainWindow class in the account management GUI

For this GUI, a separate method, createModel(), will create the QSqlRelationalTableModel that is used by the QTableView object in setUpMainWindow(). This is done to help organize the code.

The next task is to connect to accounts.db just like we have previously done in createConnections(). This is handled in Listing 14-19.
# account_manager.py
    def createConnection(self):
        """Set up the connection to the database.
        Check for the tables needed."""
        database = QSqlDatabase.addDatabase("QSQLITE")
        database.setDatabaseName("files/accounts.db")
        if not database.open():
            print("Unable to open data source file.")
            sys.exit(1) # Error code 1 - signifies error
        # Check if the tables we need exist in the database
        tables_needed = {"accounts", "countries"}
        tables_not_found = tables_needed -
            set(database.tables())
        if tables_not_found:
            QMessageBox.critical(None, "Error",
                f"""<p>The following tables are missing
                from the database: {tables_not_found}</p>""")
            sys.exit(1) # Error code 1 - signifies error
Listing 14-19

Code for the createConnection() method in the account management GUI

The createModel() method in Listing 14-20 instantiates and sets up model, establishing the foreign key relationship between the two tables with setRelation(). The setHeaderData() method applies labels to each of the columns. We can use the QSqlTableModel method that QSqlRelationalTableModel inherits, fieldIndex(), to specify the index of a field name and modify its value.
# account_manager.py
    def createModel(self):
        """Set up the model and headers, and populate the
        model."""
        self.model = QSqlRelationalTableModel()
        self.model.setTable("accounts")
        self.model.setRelation(
            self.model.fieldIndex("country_id"),
                QSqlRelation("countries", "id", "country"))
        self.model.setHeaderData(
            self.model.fieldIndex("id"),
                Qt.Orientation.Horizontal, "ID")
        self.model.setHeaderData(
            self.model.fieldIndex("employee_id"),
                Qt.Orientation.Horizontal, "Employee ID")
        self.model.setHeaderData(
            self.model.fieldIndex("first_name"),
                Qt.Orientation.Horizontal, "First")
        self.model.setHeaderData(
            self.model.fieldIndex("last_name"),
                Qt.Orientation.Horizontal, "Last")
        self.model.setHeaderData(
            self.model.fieldIndex("email"),
                Qt.Orientation.Horizontal, "E-mail")
        self.model.setHeaderData(
            self.model.fieldIndex("department"),
                Qt.Orientation.Horizontal, "Dept.")
        self.model.setHeaderData(
            self.model.fieldIndex("country_id"),
                Qt.Orientation.Horizontal, "Country")
        # Populate the model with data
        self.model.select()
Listing 14-20

Code for the createModel() method for the account management GUI

The QTableView object, table_view, is created in Listing 14-21 in the setUpMainWindow() method. The GUI’s labels, push buttons, and combo box are also instantiated.
# account_manager.py
    def setUpMainWindow(self):
        """Create and arrange widgets in the main window."""
        icons_path = "icons"
        title = QLabel("Account Management System")
        title.setSizePolicy(QSizePolicy.Policy.Fixed,
            QSizePolicy.Policy.Fixed)
        title.setStyleSheet("font: bold 24px")
        add_product_button = QPushButton("Add Employee")
        add_product_button.setIcon(QIcon(os.path.join(
            icons_path, "add_user.png")))
        add_product_button.setStyleSheet("padding: 10px")
        add_product_button.clicked.connect(self.addItem)
        del_product_button = QPushButton("Delete")
        del_product_button.setIcon(QIcon(os.path.join(
            icons_path, "trash_can.png")))
        del_product_button.setStyleSheet("padding: 10px")
        del_product_button.clicked.connect(self.deleteItem)
        # Set up sorting combobox
        sorting_options = [
            "Sort by ID", "Sort by Employee ID",
            "Sort by First Name", "Sort by Last Name",
            "Sort by Department", "Sort by Country"]
        sort_combo = QComboBox()
        sort_combo.addItems(sorting_options)
        sort_combo.currentTextChanged.connect(
            self.setSortingOrder)
        buttons_h_box = QHBoxLayout()
        buttons_h_box.addWidget(add_product_button)
        buttons_h_box.addWidget(del_product_button)
        buttons_h_box.addStretch()
        buttons_h_box.addWidget(sort_combo)
        # Widget to contain editing buttons
        edit_container = QWidget()
        edit_container.setLayout(buttons_h_box)
Listing 14-21

Code for the setUpMainWindow() method in the account management GUI, part 1

The add_product_button and del_product_button objects are used to add and delete items from the table and the model. Each button is connected to a slot using the clicked signal. The slots are created in Listing 14-23.

Items in a view can also be sorted. The sort_combo provides various ways to organize the table’s data based on the column names. For the QComboBox, when the selection has changed, the widget can emit a currentTextChanged signal. The signal also passes text that we can use to determine how to set the view’s order for displaying records in the setSortingOrder() slot. This is handled in Listing 14-24. The widgets are then arranged in buttons_h_box and added to edit_container.

For table_view, in Listing 14-22, we set its model and a few parameters. These include setting the table’s vertical and horizontal headers to stretch and fill the space in the window. For this example, users can only select single items in the table using the flag SingleSelection.
# account_manager.py
        # Create table view and set model
        self.table_view = QTableView()
        self.table_view.setModel(self.model)
        horizontal = self.table_view.horizontalHeader()
        horizontal.setSectionResizeMode(
            QHeaderView.ResizeMode.Stretch)
        vertical = self.table_view.verticalHeader()
        vertical.setSectionResizeMode(
            QHeaderView.ResizeMode.Stretch)
        self.table_view.setSelectionMode(
            QAbstractItemView.SelectionMode.SingleSelection)
        self.table_view.setSelectionBehavior(
            QAbstractItemView.SelectionBehavior.SelectRows)
        # Instantiate the delegate
        delegate = QSqlRelationalDelegate()
        self.table_view.setItemDelegate(delegate)
        # Main layout
        main_v_box = QVBoxLayout()
        main_v_box.addWidget(
            title, Qt.AlignmentFlag.AlignLeft)
        main_v_box.addWidget(edit_container)
        main_v_box.addWidget(self.table_view)
        self.setLayout(main_v_box)
Listing 14-22

Code for the setUpMainWindow() method in the account management GUI, part 2

For view classes, the behavior for selecting rows, columns, or single items can be defined using QAbstractItemView.SelectionBehavior. The following list describes the different flags:
  • SelectRows – Only rows can be selected

  • SelectColumns – Only columns can be selected

  • SelectItems – Only individual items can be selected

In Model/View programming, the delegate provides the default tools for painting item data in the view and for providing editor widgets for item models. The appearance and editor widgets of the item delegate can be customized. For the account management GUI, the delegate used is the QSqlRelationalDelegate. This class provides a combo box for editing data in fields that are foreign keys for other tables.

An example of the combo box used by the delegate can be seen in the bottom-right corner of Figure 14-4. The widget appears whenever the user needs to select a country from the countries table that will be displayed in the view.

For addItem() in Listing 14-23, we check how many rows are in the table with rowCount() and use insertRow() to insert an empty row at the end of table view. We query the database to find out the largest id value in the table. If a user does not enter a value for id into the row, then the new record’s id is equal to the highest id value plus one. It is also worth noting that if every item in the new row is not filled in, the new record will not be saved to the model upon closing the application.
# account_manager.py
    def addItem(self):
        """Add a new record to the last row of the table."""
        last_row = self.model.rowCount()
        self.model.insertRow(last_row)
        query = QSqlQuery()
        query.exec("SELECT MAX (id) FROM accounts")
        if query.next():
           int(query.value(0))
    def deleteItem(self):
        """Delete an entire row from the table."""
        current_item = self.table_view.selectedIndexes()
        for index in current_item:
            self.model.removeRow(index.row())
        self.model.select()
Listing 14-23

Code for addItem() and deleteItem() slots in the account management GUI

For deleteItem(), we get the currently selected row’s index and delete the row with removeRow(). Then we update the model using select().

The last slot to create in the MainWindow class is setSortingOrder(). We’ll use the text that is passed from the currentTextChanged signal to determine how to sort the data. For example, if the user wants to order the items using employee id numbers, they’ll first select Sort by Employee ID in the QComboBox. The signal is then emitted, and the value of text is compared in various conditions in setSortingOrder(). The setSort() method is then used to organize the employee_id field in ascending order.
# account_manager.py
    def setSortingOrder(self, text):
        """Sort the rows in the table."""
        if text == "Sort by ID":
            self.model.setSort(self.model.fieldIndex("id"),
                Qt.SortOrder.AscendingOrder)
        elif text == "Sort by Employee ID":
            self.model.setSort(
                self.model.fieldIndex("employee_id"),
                    Qt.SortOrder.AscendingOrder)
        elif text == "Sort by First Name":
            self.model.setSort(
                self.model.fieldIndex("first_name"),
                    Qt.SortOrder.AscendingOrder)
        elif text == "Sort by Last Name":
            self.model.setSort(
                self.model.fieldIndex("last_name"),
                    Qt.SortOrder.AscendingOrder)
        elif text == "Sort by Department":
            self.model.setSort(
                self.model.fieldIndex("department"),
                    Qt.SortOrder.AscendingOrder)
        elif text == "Sort by Country":
            self.model.setSort(
                self.model.fieldIndex("country"),
                    Qt.SortOrder.AscendingOrder)
        self.model.select()
Listing 14-24

Code for setSortingOrder() slot in the account management GUI

Finally, select() is called to update the model and view with the data.

At this point, you should run the application and test it out. If you want to tinker around with the code, first have a look at the different selection modes and selection behaviors. From there, you could go back to the SQL database and try implementing additional fields or create new foreign keys to test out the relational classes.

Summary

PyQt provides convenience classes for lists, tables, and trees. QListWidget, QTableWidget, and QTreeWidget are useful when you need to view data for general situations. While they are practical for creating quick interfaces for editing data, if you need to have more than one widget for displaying a dataset in an application, you must also create a process for keeping the datasets and the widgets in agreement. A better option is to use PyQt’s Model/View architecture.

There are different formats available for storing and managing data. One example is the CSV format that is convenient for reading, parsing, and storing smaller datasets. However, for large databases that contain multiple tables with relational characteristics, a Relational Database Management System that uses the SQL language is a more preferable option for managing the data. SQL allows users to select desired information that might be shared between tables as well as insert, update, and delete existing records easily.

Model/View is very useful for working with SQL databases, providing the tools necessary for connecting to a database and viewing its content. Qt provides three models for working with SQL databases. For an editable data model without foreign key support, use QSqlTableModel. If you have tables with relational properties, use QSqlRelationalTableModel. Finally, the QSqlQueryModel is beneficial when you only need to read the results of a query without editing them.

Over the course of this book, we took a look at a few applications that could have benefited greatly from data management. The login GUI in Chapter 3 could connect to a database to retrieve usernames and passwords. There is also the pizza ordering GUI from Chapter 6. You could implement a database for storing customer’s information, using a relational database for adding new customers, updating existing ones, and preventing data from being duplicated.

In Chapter 15, we will take a brief look at multithreading in PyQt.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset