Vytváření aplikací, které využívají SQL databázi, je poměrně běžný programátorský úkol. SQL databáze jsou všude a mají skvělou podporu v Pythonu. V programování GUI poskytuje PyQt robustní a multiplatformní podporu databází SQL, která vám umožňuje konzistentně vytvářet, připojovat se a spravovat vaše databáze.
Podpora SQL PyQt se plně integruje s architekturou Model-View, aby vám pomohla v procesu vytváření databázových aplikací.
V tomto kurzu se dozvíte, jak:
- Používejte podporu SQL PyQt pro spolehlivé připojení k databázi
- Provádět SQL dotazy na databázi využívající PyQt
- Použijte architekturu zobrazení modelu PyQt v databázových aplikací
- Zobrazujte a upravujte data pomocí různých widgetů PyQt
Příklady v tomto tutoriálu vyžadují základní znalost jazyka SQL, zejména systému správy databází SQLite. Některé předchozí znalosti programování GUI s Pythonem a PyQt budou také užitečné.
Box zdarma: 5 Thoughts On Python Mastery, bezplatný kurz pro vývojáře Pythonu, který vám ukáže plán a způsob myšlení, které budete potřebovat, abyste své dovednosti v Pythonu posunuli na další úroveň.
Připojení PyQt k databázi SQL
Připojení aplikace k relační databázi a přimět aplikaci, aby vytvářela, četla, aktualizovala a odstraňovala data uložená v této databázi, je běžným úkolem programování. Relační databáze jsou obecně organizovány do sady tabulek nebo vztahy . Daný řádek v tabulce se nazývá záznam nebo nice a sloupec se označuje jako atribut .
Poznámka: Termín pole se běžně používá k identifikaci jednoho kusu dat uložených v buňce daného záznamu v tabulce. Na druhé straně výraz název pole se používá k identifikaci názvu sloupce.
Každý sloupec obsahuje určitý druh informací, jako jsou jména, data nebo čísla. Každý řádek představuje sadu úzce souvisejících dat a každý řádek má stejnou obecnou strukturu. Například v databázi, která uchovává data o zaměstnancích ve společnosti, představuje konkrétní řádek jednotlivého zaměstnance.
Většina relačních databázových systémů používá SQL (strukturovaný dotazovací jazyk) pro dotazování, manipulaci a údržbu dat uložených v databázi. SQL je deklarativní a doménově specifický programovací jazyk speciálně navržený pro komunikaci s databázemi.
V dnešní době jsou široce používány relační databázové systémy a SQL. Najdete zde několik různých systémů pro správu databází, jako je SQLite, PostgreSQL, MySQL, MariaDB a mnoho dalších. Python můžete připojit ke kterémukoli z těchto databázových systémů pomocí vyhrazené Python SQL knihovny.
Poznámka: I když je vestavěná podpora SQL v PyQt preferovanou možností pro správu databází SQL v PyQt, můžete také použít jakoukoli jinou knihovnu pro zpracování databázového připojení. Některé z těchto knihoven zahrnují SQLAlchemy, pandy, SQLite a tak dále.
Použití jiné knihovny pro správu databází má však určité nevýhody. Nebudete moci využívat výhod integrace mezi třídami SQL PyQt a architekturou Model-View. Kromě toho do své aplikace přidáte další závislosti.
Pokud jde o programování GUI pomocí Pythonu a PyQt, PyQt poskytuje robustní sadu tříd pro práci s databázemi SQL. Tato sada tříd bude vaším nejlepším spojencem, když potřebujete připojit vaši aplikaci k databázi SQL.
Poznámka: Oficiální dokumentace PyQt5 má bohužel některé neúplné sekce. Chcete-li to vyřešit, můžete se podívat na dokumentaci PyQt4, dokumentaci Qt For Python nebo původní dokumentaci Qt. V tomto tutoriálu vás některé odkazy zavedou na původní dokumentaci Qt, která je ve většině případů lepším zdrojem informací.
V tomto tutoriálu se naučíte základy toho, jak používat podporu SQL PyQt k vytváření aplikací GUI, které spolehlivě spolupracují s relačními databázemi při čtení, zápisu, mazání a zobrazování dat.
Vytvoření databázového připojení
Připojení vašich aplikací k fyzické SQL databázi je důležitým krokem v procesu vývoje databázových aplikací s PyQt. K úspěšnému provedení tohoto kroku potřebujete nějaké obecné informace o tom, jak je vaše databáze nastavena.
Potřebujete například vědět, na jakém systému správy databází je vaše databáze postavena, a možná budete také potřebovat uživatelské jméno, heslo, název hostitele atd.
V tomto tutoriálu použijete SQLite 3, což je dobře otestovaný databázový systém s podporou na všech platformách a minimálními požadavky na konfiguraci. SQLite vám umožňuje číst a zapisovat přímo do databází na vašem lokálním disku, aniž byste potřebovali samostatný serverový proces. To z něj dělá uživatelsky přívětivou možnost pro výuku vývoje databázových aplikací.
Další výhodou použití SQLite je, že knihovna je dodávána s Pythonem a také s PyQt, takže nemusíte instalovat nic dalšího, abyste s ní mohli začít pracovat.
V PyQt můžete vytvořit připojení k databázi pomocí QSqlDatabase
třída. Tato třída představuje připojení a poskytuje rozhraní pro přístup k databázi. Chcete-li vytvořit připojení, stačí zavolat .addDatabase()
na QSqlDatabase
. Tato statická metoda bere jako argumenty ovladač SQL a volitelný název připojení a vrací připojení k databázi:
QSqlDatabase.addDatabase(
driver, connectionName=QSqlDatabase.defaultConnection
)
První argument, driver
, je povinný argument, který obsahuje řetězec obsahující název ovladače SQL podporovaného PyQt. Druhý argument, connectionName
, je volitelný argument, který obsahuje řetězec s názvem připojení. connectionName
výchozí je QSqlDatabase.defaultConnection
, který normálně obsahuje řetězec "qt_sql_default_connection"
.
Pokud již máte připojení s názvem connectionName
, pak je toto připojení odstraněno a nahrazeno novým připojením a .addDatabase()
vrátí nově přidané připojení k databázi zpět volajícímu.
Volání .addDatabase()
přidá připojení k databázi do seznamu dostupných připojení. Tento seznam je globálním registrem které PyQt udržuje v zákulisí, aby měl přehled o dostupných připojeních v aplikaci. Registrace vašich připojení pomocí smysluplného connectionName
vám umožní spravovat několik připojení v databázové aplikaci.
Jakmile vytvoříte připojení, možná pro něj budete muset nastavit několik atributů. Konkrétní sada atributů bude záviset na ovladači, který používáte. Obecně platí, že pro přístup k databázi budete muset nastavit atributy, jako je název databáze, uživatelské jméno a heslo.
Zde je souhrn metod nastavení, které můžete použít k nastavení běžně používaných atributů nebo vlastností databázového připojení:
Metoda | Popis |
---|---|
.setDatabaseName(name) | Nastaví název databáze na name , což je řetězec představující platný název databáze |
.setHostName(host) | Nastaví název hostitele na host , což je řetězec představující platný název hostitele |
.setUserName(username) | Nastaví uživatelské jméno na username , což je řetězec představující platné uživatelské jméno |
.setPassword(password) | Nastaví heslo na password , což je řetězec představující platné heslo |
Všimněte si, že heslo, které předáte jako argument do .setPassword()
je uložen v prostém textu a lze jej později získat voláním .password()
. Jedná se o vážné bezpečnostní riziko které byste se neměli zavádět do databázových aplikací. Bezpečnější přístup se dozvíte v části Otevření databázového připojení dále v tomto kurzu.
Chcete-li vytvořit připojení k databázi SQLite pomocí QSqlDatabase
, otevřete interaktivní relaci Pythonu a zadejte následující kód:
>>> from PyQt5.QtSql import QSqlDatabase
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> con
<PyQt5.QtSql.QSqlDatabase object at 0x7f0facec0c10>
>>> con.databaseName()
'contacts.sqlite'
>>> con.connectionName()
'qt_sql_default_connection'
Tento kód vytvoří objekt připojení k databázi pomocí "QSQLITE"
jako ovladač připojení a "contacts.sqlite"
jako název databáze připojení. Protože do .addDatabase()
nepředáváte název připojení , nově vytvořené připojení se stane vaším výchozím připojením, jehož název je "qt_sql_default_connection"
.
V případě databází SQLite je názvem databáze obvykle název souboru nebo cesta, která obsahuje název souboru databáze. Můžete také použít speciální název ":memory:"
pro databázi v paměti.
Obsluha více připojení
Mohou nastat situace, kdy budete muset použít více připojení k jedné databázi. Můžete například chtít protokolovat interakce uživatelů s databází pomocí specifického připojení pro každého uživatele.
V jiných situacích může být nutné připojit aplikaci k několika databázím. Můžete se například chtít připojit k několika vzdáleným databázím za účelem shromažďování dat pro naplnění nebo aktualizaci místní databáze.
Chcete-li tyto situace zvládnout, můžete zadat konkrétní názvy pro různá připojení a odkazovat na každé připojení jeho názvem. Pokud chcete dát svému připojení k databázi název, předejte toto jméno jako druhý argument do .addDatabase()
:
>>> from PyQt5.QtSql import QSqlDatabase
>>> # First connection
>>> con1 = QSqlDatabase.addDatabase("QSQLITE", "con1")
>>> con1.setDatabaseName("contacts.sqlite")
>>> # Second connection
>>> con2 = QSqlDatabase.addDatabase("QSQLITE", "con2")
>>> con2.setDatabaseName("contacts.sqlite")
>>> con1
<PyQt5.QtSql.QSqlDatabase object at 0x7f367f5fbf90>
>>> con2
<PyQt5.QtSql.QSqlDatabase object at 0x7f3686dd7510>
>>> con1.databaseName()
'contacts.sqlite'
>>> con2.databaseName()
'contacts.sqlite'
>>> con1.connectionName()
'con1'
>>> con2.connectionName()
'con2'
Zde vytvoříte dvě různá připojení ke stejné databázi contacts.sqlite
. Každé připojení má svůj vlastní název připojení. Název připojení můžete použít k získání odkazu na konkrétní připojení kdykoli později v kódu podle vašich potřeb. Chcete-li to provést, můžete zavolat .database()
s názvem připojení:
>>> from PyQt5.QtSql import QSqlDatabase
>>> db = QSqlDatabase.database("con1", open=False)
>>> db.databaseName()
'contacts.sqlite'
>>> db.connectionName()
'con1'
V tomto příkladu vidíte, že .database()
má dva argumenty:
connectionName
obsahuje název připojení, který potřebujete použít. Pokud nezadáte název připojení, použije se výchozí připojení.open
obsahuje booleovskou hodnotu, která říká.database()
jestli chcete automaticky otevřít připojení nebo ne. Pokudopen
jeTrue
(výchozí nastavení) a připojení není otevřené, pak se připojení otevře automaticky.
Vrácená hodnota .database()
je odkaz na objekt připojení s názvem connectionName
. Můžete použít různé názvy připojení k získání odkazů na konkrétní objekty připojení a poté je použít ke správě databáze.
Používání různých potápěčů SQL
Zatím jste se naučili, jak vytvořit připojení k databázi pomocí ovladače SQLite . Toto není jediný ovladač dostupný v PyQt. Knihovna poskytuje bohatou sadu ovladačů SQL, které vám umožňují používat různé typy systémů správy databází podle vašich konkrétních potřeb:
Jméno ovladače | Systém správy databáze |
---|---|
QDB2 | IBM Db2 (verze 7.1 a vyšší) |
QIBASE | Borland InterBase |
QMYSQL/MARIADB | MySQL nebo MariaDB (verze 5.0 a vyšší) |
QOCI | Rozhraní volání Oracle |
QODBC | Otevřená databázová konektivita (ODBC) |
QPSQL | PostgreSQL (verze 7.3 a vyšší) |
QSQLITE2 | SQLite 2 (zastaralé od Qt 5.14) |
QSQLITE | SQLite 3 |
QTDS | Sybase Adaptive Server (zastaralý od Qt 4.7) |
Sloupec Název ovladače obsahuje řetězce identifikátorů které musíte předat .addDatabase()
jako první argument pro použití přidruženého ovladače. Na rozdíl od ovladače SQLite, když používáte jiný ovladač, možná budete muset nastavit několik atributů, jako je databaseName
, hostName
, userName
a password
, aby připojení fungovalo správně.
Ovladače databáze jsou odvozeny z QSqlDriver
. Můžete si vytvořit své vlastní databázové ovladače podtřídou QSqlDriver
, ale toto téma přesahuje rámec tohoto tutoriálu. Pokud máte zájem o vytvoření vlastních databázových ovladačů, podívejte se na Jak napsat svůj vlastní databázový ovladač, kde najdete další podrobnosti.
Otevření připojení k databázi
Jakmile máte připojení k databázi, musíte toto připojení otevřít, abyste mohli s databází pracovat. Chcete-li to provést, zavoláte .open()
na objektu připojení. .open()
má následující dvě varianty:
.open()
otevře připojení k databázi pomocí aktuálních hodnot připojení..open(username, password)
otevře připojení k databázi pomocí zadanéhousername
apassword
.
Obě varianty vrátí True
pokud je připojení úspěšné. V opačném případě vrátí False
. Pokud připojení nelze navázat, můžete zavolat .lastError()
získat informace o tom, co se stalo. Tato funkce vrací informaci o poslední chybě hlášené databází.
Poznámka: Jak jste se dozvěděli dříve, .setPassword(password)
ukládá hesla jako prostý text, což představuje bezpečnostní riziko. Na druhé straně .open()
vůbec neukládá hesla. Při otevírání spojení předává heslo přímo ovladači. Poté heslo zahodí. Takže pomocí .open()
Chcete-li předejít problémům se zabezpečením, je správou vašich hesel cesta.
Zde je příklad, jak otevřít připojení k databázi SQLite pomocí první varianty .open()
:
>>> from PyQt5.QtSql import QSqlDatabase
>>> # Create the connection
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> # Open the connection
>>> con.open()
True
>>> con.isOpen()
True
Ve výše uvedeném příkladu nejprve vytvoříte připojení k databázi SQLite a otevřete toto připojení pomocí .open()
. Od .open()
vrátí True
, připojení je úspěšné. V tomto okamžiku můžete zkontrolovat připojení pomocí .isOpen()
, což vrátí True
pokud je připojení otevřené a False
jinak.
Poznámka: Pokud zavoláte .open()
na připojení, které používá ovladač SQLite a databázový soubor neexistuje, pak se automaticky vytvoří nový a prázdný databázový soubor.
V aplikacích reálného světa se musíte ujistit, že máte platné připojení k databázi, než se pokusíte provést jakékoli operace s daty. V opačném případě může vaše aplikace selhat a selhat. Co když například nemáte oprávnění k zápisu pro adresář, ve kterém se pokoušíte vytvořit tento databázový soubor? Musíte se ujistit, že řešíte jakoukoli chybu, která může nastat při otevírání připojení.
Běžný způsob volání .open()
je zabalit to do podmíněného příkazu. To vám umožní ošetřit chyby, které mohou nastat při otevírání připojení:
>>> import sys
>>> from PyQt5.QtSql import QSqlDatabase
>>> # Create the connection
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> # Open the connection and handle errors
>>> if not con.open():
... print("Unable to connect to the database")
... sys.exit(1)
Zabalení volání do .open()
v podmíněném příkazu umožňuje zpracovat jakoukoli chybu, ke které dojde při otevření připojení. Tímto způsobem můžete své uživatele informovat o případných problémech ještě před spuštěním aplikace. Všimněte si, že aplikace se ukončí se stavem ukončení 1
, který se obvykle používá k označení selhání programu.
Ve výše uvedeném příkladu používáte .open()
v interaktivní relaci, takže použijete print()
k zobrazení chybových zpráv uživatelům. V aplikacích GUI však místo použití print()
, normálně používáte QMessageBox
objekt. Pomocí QMessageBox
, můžete vytvořit malá dialogová okna pro prezentaci informací svým uživatelům.
Zde je ukázková aplikace GUI, která ilustruje způsob řešení chyb připojení:
1import sys
2
3from PyQt5.QtSql import QSqlDatabase
4from PyQt5.QtWidgets import QApplication, QMessageBox, QLabel
5
6# Create the connection
7con = QSqlDatabase.addDatabase("QSQLITE")
8con.setDatabaseName("/home/contacts.sqlite")
9
10# Create the application
11app = QApplication(sys.argv)
12
13# Try to open the connection and handle possible errors
14if not con.open():
15 QMessageBox.critical(
16 None,
17 "App Name - Error!",
18 "Database Error: %s" % con.lastError().databaseText(),
19 )
20 sys.exit(1)
21
22# Create the application's window
23win = QLabel("Connection Successfully Opened!")
24win.setWindowTitle("App Name")
25win.resize(200, 100)
26win.show()
27sys.exit(app.exec_())
if
příkaz v řádku 14 zkontroluje, zda bylo připojení neúspěšné. Pokud /home/
adresář neexistuje nebo pokud nemáte oprávnění do něj zapisovat, pak volání .open()
selže, protože soubor databáze nelze vytvořit. V této situaci tok provádění zadá if
blok výpisu kódu a zobrazí zprávu na obrazovce.
Pokud změníte cestu k jakémukoli jinému adresáři, do kterého můžete zapisovat, pak volání .open()
bude úspěšná a uvidíte okno se zprávou Connection Successfully Opened!
Budete mít také nový databázový soubor s názvem contacts.sqlite
ve vybraném adresáři.
Upozorňujeme, že předáte None
jako rodič zprávy protože v době zobrazení zprávy jste ještě nevytvořili okno, takže pro schránku se zprávou nemáte životaschopného rodiče.
Spouštění dotazů SQL pomocí PyQt
S plně funkčním databázovým připojením jste připraveni začít s databází pracovat. Chcete-li to provést, můžete použít dotazy SQL založené na řetězcích a QSqlQuery
objektů. QSqlQuery
umožňuje spouštět jakýkoli druh SQL dotazu ve vaší databázi. Pomocí QSqlQuery
, můžete provádět příkazy jazyka DML (data manipulation language), jako je SELECT
, INSERT
, UPDATE
a DELETE
a také příkazy jazyka definice dat (DDL), jako je CREATE TABLE
a tak dále.
Konstruktor QSqlQuery
má několik variant, ale v tomto tutoriálu se dozvíte o dvou z nich:
-
QSqlQuery(query, connection)
vytvoří objekt dotazu pomocíquery
SQL založeného na řetězcích aconnection
databáze . Pokud nezadáte připojení nebo je zadané připojení neplatné, použije se výchozí připojení k databázi. Pokudquery
není prázdný řetězec, pak bude okamžitě spuštěn. -
QSqlQuery(connection)
vytvoří objekt dotazu pomocíconnection
. Pokudconnection
je neplatné, použije se výchozí připojení.
Můžete také vytvořit QSqlQuery
objektů bez předání jakýchkoli argumentů konstruktoru. V takovém případě dotaz použije výchozí připojení k databázi, pokud existuje.
Chcete-li provést dotaz, musíte zavolat .exec()
na objekt dotazu. Můžete použít .exec()
dvěma různými způsoby:
-
.exec(query)
provede řetězec SQL dotaz obsažený vquery
. VracíTrue
pokud byl dotaz úspěšný a jinak vracíFalse
. -
.exec()
provede dříve připravený SQL dotaz. VracíTrue
pokud byl dotaz úspěšný a jinak vracíFalse
.
Poznámka: PyQt také implementuje varianty QSqlQuery.exec()
s názvem .exec_()
. Ty poskytují zpětnou kompatibilitu se staršími verzemi Pythonu, ve kterých exec
bylo klíčové slovo jazyka.
Nyní, když znáte základy používání QSqlQuery
Chcete-li vytvářet a spouštět dotazy SQL, jste připraveni naučit se, jak uplatnit své znalosti v praxi.
Provádění statických SQL dotazů
Chcete-li začít vytvářet a spouštět dotazy pomocí PyQt, spustíte svůj oblíbený editor kódu nebo IDE a vytvoříte skript Python s názvem queries.py
. Uložte skript a přidejte do něj následující kód:
1import sys
2
3from PyQt5.QtSql import QSqlDatabase, QSqlQuery
4
5# Create the connection
6con = QSqlDatabase.addDatabase("QSQLITE")
7con.setDatabaseName("contacts.sqlite")
8
9# Open the connection
10if not con.open():
11 print("Database Error: %s" % con.lastError().databaseText())
12 sys.exit(1)
13
14# Create a query and execute it right away using .exec()
15createTableQuery = QSqlQuery()
16createTableQuery.exec(
17 """
18 CREATE TABLE contacts (
19 id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
20 name VARCHAR(40) NOT NULL,
21 job VARCHAR(50),
22 email VARCHAR(40) NOT NULL
23 )
24 """
25)
26
27print(con.tables())
V tomto skriptu začnete importováním modulů a tříd, se kterými budete pracovat. Poté vytvoříte připojení k databázi pomocí .addDatabase()
s ovladačem SQLite. Nastavíte název databáze na "contacts.sqlite"
a otevřete připojení.
Chcete-li vytvořit svůj první dotaz, vytvořte instanci QSqlQuery
bez jakýchkoliv argumentů. Když je objekt dotazu na místě, zavoláte .exec()
, předávání dotazu SQL založeného na řetězcích jako argument. Tento druh dotazu se nazývá statický dotaz protože nezískává žádné parametry mimo dotaz.
Výše uvedený SQL dotaz vytvoří novou tabulku nazvanou contacts
ve vaší databázi. Tato tabulka bude mít následující čtyři sloupce:
Sloupec | Obsah |
---|---|
id | Celé číslo s primárním klíčem tabulky |
name | Řetězec se jménem kontaktu |
job | Řetězec s pracovním názvem kontaktu |
email | Řetězec s e-mailem kontaktu |
Poslední řádek výše uvedeného skriptu vytiskne seznam tabulek obsažených ve vaší databázi. Pokud skript spustíte, všimnete si, že nový databázový soubor s názvem contacts.sqlite
se vytvoří ve vašem aktuálním adresáři. Získáte také něco jako ['contacts', 'sqlite_sequence']
vytištěné na vaší obrazovce. Tento seznam obsahuje názvy tabulek ve vaší databázi.
Poznámka: Dotaz SQL založený na řetězcích musí používat vhodnou syntaxi podle konkrétní databáze SQL, kterou dotazujete. Pokud je syntaxe chybná, pak .exec()
ignoruje dotaz a vrátí False
.
V případě SQLite může dotaz obsahovat vždy pouze jeden příkaz.
Volání .exec()
na QSqlQuery
objekt je běžný způsob okamžitého provádění řetězcových SQL dotazů ve vašich databázích, ale co když chcete své dotazy předem připravit na pozdější spuštění? To je téma další sekce.
Provádění dynamických dotazů:Formátování řetězce
Doposud jste se naučili spouštět statické dotazy na databázi. Statické dotazy jsou ty, které nepřijímají parametry , takže dotaz běží tak, jak je. I když jsou tyto dotazy poměrně užitečné, někdy je potřeba vytvořit dotazy, které získávají data v reakci na určité vstupní parametry.
Dotazy, které přijímají parametry v době provádění, jsou známé jako dynamické dotazy . Použití parametrů umožňuje doladit dotaz a načíst data v reakci na konkrétní hodnoty parametrů. Různé hodnoty vedou k různým výsledkům. Vstupní parametry v dotazu můžete převzít jedním z následujících dvou přístupů:
- Vytvářejte dotaz dynamicky pomocí formátování řetězců k interpolaci hodnot parametrů.
- Připravte dotaz pomocí zástupných parametrů a poté navažte konkrétní hodnoty na parametry.
První přístup umožňuje rychle vytvářet dynamické dotazy. Chcete-li však tento přístup bezpečně používat, musíte si být jisti, že hodnoty vašich parametrů pocházejí z důvěryhodného zdroje. Jinak byste mohli čelit útokům SQL injection.
Zde je příklad, jak používat formátování řetězců k vytváření dynamických dotazů v PyQt:
>>>>>> from PyQt5.QtSql import QSqlQuery, QSqlDatabase
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> con.open()
True
>>> name = "Linda"
>>> job = "Technical Lead"
>>> email = "[email protected]"
>>> query = QSqlQuery()
>>> query.exec(
... f"""INSERT INTO contacts (name, job, email)
... VALUES ('{name}', '{job}', '{email}')"""
... )
True
V tomto příkladu použijete f-string k vytvoření dynamického dotazu interpolací konkrétních hodnot do dotazu SQL založeného na řetězcích. Poslední dotaz vloží data do vašich contacts
tabulka, která nyní obsahuje údaje o Linda
.
Poznámka: Později v tomto tutoriálu uvidíte, jak načíst a procházet data uložená v databázi.
Všimněte si, že aby tento druh dynamického dotazu fungoval, musíte se ujistit, že vkládané hodnoty mají správný datový typ. Takže používáte jednoduché uvozovky kolem zástupného symbolu v f-řetězci, protože tyto hodnoty musí být řetězce.
Provádění dynamických dotazů:zástupné parametry
Druhý přístup k provádění dynamických dotazů vyžaduje, abyste si dotazy předem připravili pomocí šablony s zástupnými symboly pro parametry. PyQt podporuje dva styly zástupných symbolů:
- Styl Oracle používá pojmenované zástupné symboly, jako je
:name
nebo:email
. - Styl ODBC používá otazník (
?
) jako poziční zástupný symbol.
Všimněte si, že tyto styly nelze kombinovat ve stejném dotazu. Další příklady použití zástupných symbolů najdete v části Přístupy k hodnotám vazeb.
Poznámka: ODBC znamená Open Database Connectivity.
Chcete-li vytvořit tento druh dynamického dotazu v PyQt, musíte nejprve vytvořit šablonu se zástupným symbolem pro každý parametr dotazu a poté tuto šablonu předat jako argument do .prepare()
, který analyzuje, zkompiluje a připraví šablonu dotazu k provedení. Pokud má šablona nějaké problémy, jako je chyba syntaxe SQL, použijte .prepare()
nezdaří kompilaci šablony a vrátí False
.
Pokud je proces přípravy úspěšný, pak prepare()
vrátí True
. Poté můžete každému parametru předat konkrétní hodnotu pomocí .bindValue()
s pojmenovanými nebo pozičními parametry nebo pomocí .addBindValue()
s polohovými parametry. .bindValue()
má následující dvě varianty:
.bindValue(placeholder, val)
.bindValue(pos, val)
V první variantě placeholder
představuje zástupný symbol ve stylu Oracle. Ve druhé variantě pos
představuje celé číslo založené na nule s pozicí parametru v dotazu. V obou variantách val
obsahuje hodnotu, která má být vázána na konkrétní parametr.
.addBindValue()
přidá hodnotu do seznamu zástupných symbolů pomocí poziční vazby. To znamená, že pořadí volání .addBindValue()
určuje, která hodnota bude vázána na každý zástupný parametr v připraveném dotazu.
Chcete-li začít používat připravené dotazy, můžete si připravit INSERT INTO
Příkaz SQL k naplnění databáze ukázkovými daty. Vraťte se ke skriptu, který jste vytvořili v části Provádění statických SQL dotazů a přidejte následující kód hned za volání print()
:
28# Creating a query for later execution using .prepare()
29insertDataQuery = QSqlQuery()
30insertDataQuery.prepare(
31 """
32 INSERT INTO contacts (
33 name,
34 job,
35 email
36 )
37 VALUES (?, ?, ?)
38 """
39)
40
41# Sample data
42data = [
43 ("Joe", "Senior Web Developer", "[email protected]"),
44 ("Lara", "Project Manager", "[email protected]"),
45 ("David", "Data Analyst", "[email protected]"),
46 ("Jane", "Senior Python Developer", "[email protected]"),
47]
48
49# Use .addBindValue() to insert data
50for name, job, email in data:
51 insertDataQuery.addBindValue(name)
52 insertDataQuery.addBindValue(job)
53 insertDataQuery.addBindValue(email)
54 insertDataQuery.exec()
Prvním krokem je vytvoření QSqlQuery
objekt. Potom zavoláte .prepare()
na objekt dotazu. V tomto případě použijete pro zástupné symboly styl ODBC. Váš dotaz bude mít hodnoty pro name
vašeho kontaktu , job
a email
, takže potřebujete tři zástupné symboly. Protože id
sloupec je automaticky inkrementované celé číslo, nemusíte pro něj zadávat hodnoty.
Poté vytvoříte nějaká ukázková data k naplnění databáze. data
obsahuje seznam n-tic a každá n-tice obsahuje tři položky:jméno, úlohu a e-mail každého kontaktu.
Posledním krokem je svázat hodnoty, které chcete předat každému zástupnému symbolu, a poté zavolat .exec()
k provedení dotazu. Chcete-li to provést, použijte for
smyčka. Záhlaví smyčky rozbalí každou n-tici do data
do tří samostatných proměnných s vhodnými názvy. Then you call .addBindValue()
on the query object to bind the values to the placeholders.
Note that you’re using positional placeholders , so the order in which you call .addBindValue()
will define the order in which each value is passed to the corresponding placeholder.
This approach for creating dynamic queries is handy when you want to customize your queries using values that come from your user’s input. Anytime you take the user’s input to complete a query on a database, you face the security risk of SQL injection.
In PyQt, combining .prepare()
, .bindValue()
, and .addBindValue()
fully protects you against SQL injection attacks, so this is the way to go when you’re taking untrusted input to complete your queries.
Navigating the Records in a Query
If you execute a SELECT
statement, then your QSqlQuery
object will retrieve zero or more records from one or more tables in your database. The query will hold records containing data that matches the query’s criteria. If no data matches the criteria, then your query will be empty.
QSqlQuery
provides a set of navigation methods that you can use to move throughout the records in a query result:
Metoda | Retrieves |
---|---|
.next() | The next record |
.previous() | The previous record |
.first() | The first record |
.last() | The last record |
.seek(index, relative=False) | The record at position index |
All these methods position the query object on the retrieved record if that record is available. Most of these methods have specific rules that apply when using them. With these methods, you can move forward, backward, or arbitrarily through the records in a query result. Since they all return either True
or False
, you can use them in a while
loop to navigate all the records in one go.
These methods work with active queries . A query is active when you’ve successfully run .exec()
on it, but the query isn’t finished yet. Once an active query is on a valid record, you can retrieve data from that record using .value(index)
. This method takes a zero-based integer number, index
, and returns the value at that index (column) in the current record.
Poznámka: If you execute a SELECT *
type of query, then the columns in the result won’t follow a known order. This might cause problems when you use .value()
to retrieve the value at a given column because there’s no way of knowing if you’re using the right column index.
You’ll look at a few examples of how to use some of the navigation methods to move throughout a query below. But first, you need to create a connection to your database:
>>>>>> from PyQt5.QtSql import QSqlDatabase, QSqlQuery
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> con.open()
True
Here, you create and open a new connection to contacts.sqlite
. If you’ve been following along with this tutorial so far, then this database already contains some sample data. Now you can create a QSqlQuery
object and execute it on that data:
>>> # Create and execute a query
>>> query = QSqlQuery()
>>> query.exec("SELECT name, job, email FROM contacts")
True
This query retrieves data about the name
, job
, and email
of all the contacts stored in the contacts
stůl. Since .exec()
returned True
, the query was successful and is now an active query. You can navigate the records in this query using any of the navigation methods you saw before. You can also retrieve the data at any column in a record using .value()
:
>>> # First record
>>> query.first()
True
>>> # Named indices for readability
>>> name, job, email = range(3)
>>> # Retrieve data from the first record
>>> query.value(name)
'Linda'
>>> # Next record
>>> query.next()
True
>>> query.value(job)
'Senior Web Developer'
>>> # Last record
>>> query.last()
True
>>> query.value(email)
'[email protected]'
With the navigation methods, you can move around the query result. With .value()
, you can retrieve the data at any column in a given record.
You can also iterate through all the records in your query using a while
loop along with .next()
:
>>> query.exec()
True
>>> while query.next():
... print(query.value(name), query.value(job), query.value(email))
...
Linda Technical Lead [email protected]
Joe Senior Web Developer [email protected]
...
With .next()
, you navigate all the records in a query result. .next()
works similar to the iterator protocol in Python. Once you’ve iterated over the records in a query result, .next()
starts returning False
until you run .exec()
again. A call to .exec()
retrieves data from a database and places the query object’s internal pointer one position before the first record, so when you call .next()
, you get the first record again.
You can also loop in reverse order using .previous()
:
>>> while query.previous():
... print(query.value(name), query.value(job), query.value(email))
...
Jane Senior Python Developer [email protected]
David Data Analyst [email protected]
...
.previous()
works similar to .next()
, but the iteration is done in reverse order. In other words, the loop goes from the query pointer’s position back to the first record.
Sometimes you might want to get the index that identifies a given column in a table by using the name of that column. To do that, you can call .indexOf()
on the return value of .record()
:
>>> query.first()
True
>>> # Get the index of name
>>> name = query.record().indexOf("name")
>>> query.value(name)
'Linda'
>>> # Finish the query object if unneeded
>>> query.finish()
>>> query.isActive()
False
The call to .indexOf()
on the result of .record()
returns the index of the "name"
column. If "name"
doesn’t exist, then .indexOf()
vrátí -1
. This is handy when you use a SELECT *
statement in which the order of columns is unknown. Finally, if you’re done with a query object, then you can turn it inactive by calling .finish()
. This will free the system memory associated with the query object at hand.
Closing and Removing Database Connections
In practice, some of your PyQt applications will depend on a database, and others won’t. An application that depends on a database often creates and opens a database connection just before creating any window or graphical component and keeps the connection open until the application is closed.
On the other hand, applications that don’t depend on a database but use a database to provide some of their functionalities typically connect to that database only when needed, if at all. In these cases, you can close the connection after use and free the resources associated with that connection, such as system memory.
To close a connection in PyQt, you call .close()
on the connection. This method closes the connection and frees any acquired resources. It also invalidates any associated QSqlQuery
objects because they can’t work properly without an active connection.
Here’s an example of how to close an active database connection using .close()
:
>>> from PyQt5.QtSql import QSqlDatabase
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> con.open()
True
>>> con.isOpen()
True
>>> con.close()
>>> con.isOpen()
False
You can call .close()
on a connection to close it and free all its associated resources. To make sure that a connection is closed, you call .isOpen()
.
Note that QSqlQuery
objects remain in memory after closing their associated connection, so you must make your queries inactive by calling .finish()
or .clear()
, or by deleting the QSqlQuery
object before closing the connection. Otherwise, residual memory is left out in your query object.
You can reopen and reuse any previously closed connection. That’s because .close()
doesn’t remove connections from the list of available connections, so they remain usable.
You can also completely remove your database connections using .removeDatabase()
. To do this safely, first finish your queries using .finish()
, then close the database using .close()
, and finally remove the connection. You can use .removeDatabase(connectionName)
to remove the database connection called connectionName
from the list of available connections. Removed connections are no longer available for use in the application at hand.
To remove the default database connection, you can call .connectionName()
on the object returned by .database()
and pass the result to .removeDatabase()
:
>>> # The connection is closed but still in the list of connections
>>> QSqlDatabase.connectionNames()
['qt_sql_default_connection']
>>> # Remove the default connection
>>> QSqlDatabase.removeDatabase(QSqlDatabase.database().connectionName())
>>> # The connection is no longer in the list of connections
>>> QSqlDatabase.connectionNames()
[]
>>> # Try to open a removed connection
>>> con.open()
False
Here, the call to .connectionNames()
returns the list of available connections. In this case, you have only one connection, the default. Then you remove the connection using .removeDatabase()
.
Poznámka: Before closing and removing a database connection, you need to make sure that everything that uses the connection is deleted or set to use a different data source. Otherwise, you can have a resource leak .
Since you need a connection name to use .removeDatabase()
, you call .connectionName()
on the result of .database()
to get the name of the default connection. Finally, you call .connectionNames()
again to make sure that the connection is no longer in the list of available connections. Trying to open a removed connection will return False
because the connection no longer exists.
Displaying and Editing Data With PyQt
A common requirement in GUI applications that use databases is the ability to load, display, and edit data from the database using different widgets. Table, list, and tree widgets are commonly used in GUIs to manage data.
PyQt provides two different kind of widgets for managing data:
- Standard widgets include internal containers for storing data.
- View widgets don’t maintain internal data containers but use models to access data.
For small GUI applications that manage small databases, you can use the first approach. The second approach is handy when you’re building complex GUI applications that manage large databases.
The second approach takes advantage of PyQt’s Model-View programming. With this approach, you have widgets that represent views such as tables, lists, and trees on one hand and model classes that communicate with your data on the other hand.
Understanding PyQt’s Model-View Architecture
The Model-View-Controller (MVC) design pattern is a general software pattern intended to divide an application’s code into three general layers, each with a different role.
The model takes care of the business logic of the application, the view provides on-screen representations, and the controller connects the model and the view to make the application work.
Qt provides a custom variation of MVC. They call it the Model-View architecture, and it’s available for PyQt as well. The pattern also separates the logic into three components:
-
Models communicate with and access the data. They also define an interface that’s used by views and delegates to access the data. All models are based on
QAbstractItemModel
. Some commonly used models includeQStandardItemModel
,QFileSystemModel
, and SQL-related models. -
Views are responsible for displaying the data to the user. They also have similar functionality to the controller in the MVC pattern. All views are based on
QAbstractItemView
. Some commonly used views areQListView
,QTableView
, andQTreeView
. -
Delegates paint view items and provide editor widgets for modifying items. They also communicate back with the model if an item has been modified. The base class is
QAbstractItemDelegate
.
Separating classes into these three components implies that changes on models will be reflected on associated views or widgets automatically, and changes on views or widgets through delegates will update the underlying model automatically.
In addition, you can display the same data in different views without the need for multiple models.
Using Standard Widget Classes
PyQt provides a bunch of standard widgets for displaying and editing data in your GUI applications. These standard widgets provide views such as tables, trees, and lists. They also provide an internal container for storing data and convenient delegates for editing the data. All these features are grouped into a single class.
Here are three of these standard classes:
Standard Class | Displays |
---|---|
QListWidget | A list of items |
QTreeWidget | A hierarchical tree of items |
QTableWidget | A table of items |
QTableWidget
is arguably the most popular widget when it comes to displaying and editing data. It creates a 2D array of QTableWidgetItem
objects. Each item holds an individual value as a string. All these values are displayed and organized in a table of rows and columns.
You can perform at least the following operations on a QTableWidget
object:
- Editing the content of its items using delegate objects
- Adding new items using
.setItem()
- Setting the number of rows and columns using
.setRowCount()
and.setColumnCount()
- Adding vertical and horizontal header labels using
setHorizontalHeaderLabels()
and.setVerticalHeaderLabels
Here’s a sample application that shows how to use a QTableWidget
object to display data in a GUI. The application uses the database you created and populated in previous sections, so if you want to run it, then you need to save the code into the same directory in which you have the contacts.sqlite
database:
If you double-click any cell of the table, then you’ll be able to edit the content of the cell. However, your changes won’t be saved to your database.
Here’s the code for your application:
1import sys
2
3from PyQt5.QtSql import QSqlDatabase, QSqlQuery
4from PyQt5.QtWidgets import (
5 QApplication,
6 QMainWindow,
7 QMessageBox,
8 QTableWidget,
9 QTableWidgetItem,
10)
11
12class Contacts(QMainWindow):
13 def __init__(self, parent=None):
14 super().__init__(parent)
15 self.setWindowTitle("QTableView Example")
16 self.resize(450, 250)
17 # Set up the view and load the data
18 self.view = QTableWidget()
19 self.view.setColumnCount(4)
20 self.view.setHorizontalHeaderLabels(["ID", "Name", "Job", "Email"])
21 query = QSqlQuery("SELECT id, name, job, email FROM contacts")
22 while query.next():
23 rows = self.view.rowCount()
24 self.view.setRowCount(rows + 1)
25 self.view.setItem(rows, 0, QTableWidgetItem(str(query.value(0))))
26 self.view.setItem(rows, 1, QTableWidgetItem(query.value(1)))
27 self.view.setItem(rows, 2, QTableWidgetItem(query.value(2)))
28 self.view.setItem(rows, 3, QTableWidgetItem(query.value(3)))
29 self.view.resizeColumnsToContents()
30 self.setCentralWidget(self.view)
31
32def createConnection():
33 con = QSqlDatabase.addDatabase("QSQLITE")
34 con.setDatabaseName("contacts.sqlite")
35 if not con.open():
36 QMessageBox.critical(
37 None,
38 "QTableView Example - Error!",
39 "Database Error: %s" % con.lastError().databaseText(),
40 )
41 return False
42 return True
43
44app = QApplication(sys.argv)
45if not createConnection():
46 sys.exit(1)
47win = Contacts()
48win.show()
49sys.exit(app.exec_())
Here’s what’s happening in this example:
- Lines 18 to 20 create a
QTableWidget
object, set the number of columns to4
, and set user-friendly labels for each column’s header. - Line 21 creates and executes a
SELECT
SQL query on your database to get all the data in thecontacts
table. - Line 22 starts a
while
loop to navigate the records in the query result using.next()
. - Line 24 increments the number of rows in the table by
1
using.setRowCount()
. - Lines 25 to 28 add items of data to your table using
.setItem()
. Note that since the values in theid
columns are integer numbers, you need to convert them into strings to be able to store them in aQTableWidgetItem
object.
.setItem()
takes three arguments:
row
holds a zero-based integer that represents the index of a given row in the table.column
holds a zero-based integer that represents the index of a given column in the table.item
holds theQTableWidgetItem
object that you need to place at a given cell in the table.
Finally, you call .resizeColumnsToContents()
on your view to adjust the size of the columns to their content and provide a better rendering of the data.
Displaying and editing database tables using standard widgets can become a challenging task. That’s because you’ll have two copies of the same data. In other words you’ll have a copy of the data in two locations:
- Outside the widget, in your database
- Inside the widget, in the widget’s internal containers
You’re responsible for synchronizing both copies of your data manually, which can be an annoying and error-prone operation. Luckily, you can use PyQt’s Model-View architecture to avoid most of these problems, as you’ll see in the following section.
Using View and Model Classes
PyQt’s Model-View classes eliminate the problems of data duplication and synchronization that may occur when you use standard widget classes to build database applications. The Model-View architecture allows you to use several views to display the same data because you can pass one model to many views.
Model classes provide an application programming interface (API) that you can use to manipulate data. View classes provide convenient delegate objects that you can use to edit data in the view directly. To connect a view with a given module, you need to call .setModel()
on the view object.
PyQt offers a set of view classes that support the Model-View architecture:
View Class | Displays |
---|---|
QListView | A list of items that take values directly from a model class |
QTreeView | A hierarchical tree of items that take values directly from a model class |
QTableView | A table of items that take values directly from a model class |
You can use these view classes along with model classes to create your database applications. This will make your applications more robust, faster to code, and less error-prone.
Here are some of the model classes that PyQt provides for working with SQL databases:
Model Class | Popis |
---|---|
QSqlQueryModel | A read-only data model for SQL queries |
QSqlTableModel | An editable data model for reading and writing records in a single table |
QSqlRelationalTableModel | An editable data model for reading and writing records in a relational table |
Once you’ve connected one of these models to a physical database table or query, you can use them to populate your views. Views provide delegate objects that allow you to modify the data directly in the view. The model connected to the view will update the data in your database to reflect any change in the view. Note that you don’t have to update the data in the database manually. The model will do that for you.
Here’s an example that shows the basics of how to use a QTableView
object and a QSqlTableModel
object together to build a database application using PyQt’s Model-View architecture:
To edit the data in a cell of the table, you can double-click the cell. A convenient delegate widget will show in the cell, allowing you to edit its content. Then you can hit Enter to save the changes.
The ability to automatically handle and save changes in the data is one of the more important advantages of using PyQt’s Model-View classes. The Model-View architecture will improve your productivity and reduce the errors that can appear when you have to write data manipulation code by yourself.
Here’s the code to create the application:
1import sys
2
3from PyQt5.QtCore import Qt
4from PyQt5.QtSql import QSqlDatabase, QSqlTableModel
5from PyQt5.QtWidgets import (
6 QApplication,
7 QMainWindow,
8 QMessageBox,
9 QTableView,
10)
11
12class Contacts(QMainWindow):
13 def __init__(self, parent=None):
14 super().__init__(parent)
15 self.setWindowTitle("QTableView Example")
16 self.resize(415, 200)
17 # Set up the model
18 self.model = QSqlTableModel(self)
19 self.model.setTable("contacts")
20 self.model.setEditStrategy(QSqlTableModel.OnFieldChange)
21 self.model.setHeaderData(0, Qt.Horizontal, "ID")
22 self.model.setHeaderData(1, Qt.Horizontal, "Name")
23 self.model.setHeaderData(2, Qt.Horizontal, "Job")
24 self.model.setHeaderData(3, Qt.Horizontal, "Email")
25 self.model.select()
26 # Set up the view
27 self.view = QTableView()
28 self.view.setModel(self.model)
29 self.view.resizeColumnsToContents()
30 self.setCentralWidget(self.view)
31
32def createConnection():
33 con = QSqlDatabase.addDatabase("QSQLITE")
34 con.setDatabaseName("contacts.sqlite")
35 if not con.open():
36 QMessageBox.critical(
37 None,
38 "QTableView Example - Error!",
39 "Database Error: %s" % con.lastError().databaseText(),
40 )
41 return False
42 return True
43
44app = QApplication(sys.argv)
45if not createConnection():
46 sys.exit(1)
47win = Contacts()
48win.show()
49sys.exit(app.exec_())
Here’s what’s happening in this code:
- Line 18 creates an editable
QSqlTableModel
object. - Line 19 connects your model with the
contacts
table in your database using.setTable()
. - Line 20 sets the edit strategy of the model to
OnFieldChange
. This strategy allows the model to automatically update the data in your database if the user modifies any of the data directly in the view. - Lines 21 to 24 set some user-friendly labels to the horizontal headers of the model using
.setHeaderData()
. - Line 25 loads the data from your database and populates the model by calling
.select()
. - Line 27 creates the table view object to display the data contained in the model.
- Line 28 connects the view with the model by calling
.setModel()
on the view with your data model as an argument. - Line 29 calls
.resizeColumnsToContents()
on the view object to adjust the table to its content.
That’s it! You now have a fully-functional database application.
Using SQL Databases in PyQt:Best Practices
When it comes to using PyQt’s SQL support effectively, there are some best practices that you might want to use in your applications:
-
Favor PyQt’s SQL support over Python standard library or third-party libraries to take advantage of the natural integration of these classes with the rest of PyQt’s classes and infrastructure, mostly with the Model-View architecture.
-
Use previously prepared dynamic queries with placeholders for parameters and bind values to those parameters using
.addBindValue()
and.bindValue()
. This will help prevent SQL injection attacks. -
Handle errors that can occur when opening a database connection to avoid unexpected behaviors and application crashes.
-
Close and remove unneeded database connections and queries to free any acquired system resources.
-
Minimize the use of
SELECT *
queries to avoid problems when retrieving data with.value()
. -
Pass your passwords to
.open()
instead of to.setPassword()
to avoid the risk of compromising your security. -
Take advantage of PyQt’s Model-View architecture and its integration with PyQt’s SQL support to make your applications more robust.
This list isn’t complete, but it’ll help you make better use of PyQt’s SQL support when developing your database applications.
Conclusion
Using PyQt’s built-in support to work with SQL databases is an important skill for any Python developer who’s creating PyQt GUI applications and needs to connect them to a database. PyQt provides a consistent set of classes for managing SQL databases.
These classes fully integrate with PyQt’s Model-View architecture, allowing you to develop GUI applications that can manage databases in a user-friendly way.
In this tutorial, you’ve learned how to:
- Use PyQt’s SQL support to connect to a database
- Execute SQL queries on a database with PyQt
- Build database applications using PyQt’s Model-View architecture
- Display and edit data from a database using PyQt widgets
With this knowledge, you can improve your productivity when creating nontrivial database applications and make your GUI applications more robust.