rapid gui programming with python and qt databasesby raed s. rasheed databasesby 1

34
Rapid GUI Programming with Python and Qt Databases By Raed S. Rasheed 1

Upload: melina-tindell

Post on 15-Dec-2015

291 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

1

Rapid GUI Programmingwith Python and Qt

DatabasesBy

Raed S. Rasheed

Page 2: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

2

Databases

PyQt provides a consistent cross-platform API for database access using the QtSql module and PyQt’s model/view architecture. Python also has its own completely different database API, called DB-API, but it isn’t needed with PyQt and is not covered here. The commercial edition of Qt comes with many database drivers, whereas the GPL edition has fewer due to licensing restrictions. The drivers that are available include ones for IBM’s DB2, Borland’s Interbase, MySQL, Oracle, ODBC (for Microsoft SQL Server), PostgreSQL, SQLite, and Sybase. However, like any aspect of PyQt, it is possible to create additional database drivers if one we need is not available.

Page 3: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

3

Connecting to the Database

To use PyQt’s SQL classes we must import the QtSql module:from PyQt4.QtSql import *

A database connection is established by calling the static QSqlDatabase.addDatabase()

method, with the name of the driver we want to use. Then we must set various attributes, such as the database’s name, the username, and the password. And finally, we must call open() to make the connection.

Page 4: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

4

Connecting to the Database

db = QSqlDatabase.addDatabase("QSQLITE")db.setDatabaseName(filename)if not db.open():

QMessageBox.warning(None, "Phone Log",QString("Database Error: %1").arg(db.lastError().text()))

sys.exit(1)

Page 5: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

5

Executing SQL Queries

query = QSqlQuery()query.exec_("""CREATE TABLE outcomes (

id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,name VARCHAR(40) NOT NULL)""")

Page 6: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

6

Executing SQL Queries

query.exec_("""CREATE TABLE calls (id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,caller VARCHAR(40) NOT NULL,starttime DATETIME NOT NULL,endtime DATETIME NOT NULL,topic VARCHAR(80) NOT NULL,outcomeid INTEGER NOT NULL,FOREIGN KEY (outcomeid) REFERENCES outcomes)""")

Page 7: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

7

Executing SQL Queries

The Phone Log database design

Page 8: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

8

Executing SQL Queries

Now that we have created the tables, we can populate them with data.

for name in ("Resolved", "Unresolved", "Calling back", "Escalate","Wrong number"):

query.exec_("INSERT INTO outcomes (name) VALUES ('%s')" % name)

Page 9: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

9

Executing SQL Queries

query.prepare("INSERT INTO calls (caller, starttime, endtime, ""topic, outcomeid) VALUES (?, ?, ?, ?, ?)")

for name, start, end, topic, outcomeid in data:query.addBindValue(QVariant(QString(name)))query.addBindValue(QVariant(start)) # QDateTimequery.addBindValue(QVariant(end)) # QDateTimequery.addBindValue(QVariant(QString(topic)))query.addBindValue(QVariant(outcomeid)) # intquery.exec_()

Page 10: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

10

Executing SQL Queries

We can use QSqlQuery to execute any arbitrary SQL statement. For example:

query.exec_("DELETE FROM calls WHERE id = 12")

Page 11: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

11

Executing SQL Queries

We will conclude our coverage of QSqlQuery by looking at how to use it to execute SELECT statements, and how to iterate over the resultant records.

DATETIME_FORMAT = "yyyy-MM-dd hh:mm"ID, CALLER, STARTTIME, ENDTIME, TOPIC, OUTCOMEID = range(6)query.exec_("SELECT id, caller, starttime, endtime, topic, "

"outcomeid FROM calls ORDER by starttime")

Page 12: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

12

Executing SQL Queries

while query.next():id = query.value(ID).toInt()[0]caller = unicode(query.value(CALLER).toString())starttime = unicode(query.value(STARTTIME).toDateTime() \

.toString(DATETIME_FORMAT))endtime = unicode(query.value(ENDTIME).toDateTime() \

.toString(DATETIME_FORMAT))topic = unicode(query.value(TOPIC).toString())

Page 13: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

13

Executing SQL Queries

outcomeid = query.value(OUTCOMEID).toInt()[0]subquery = QSqlQuery("SELECT name FROM outcomes "

"WHERE id = %d" % outcomeid)outcome = "invalid foreign key"if subquery.next():

outcome = unicode(subquery.value(0).toString())print "%02d: %s %s - %s %s [%s]" % (id, caller, starttime,

endtime, topic, outcome)

Page 14: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

14

Using Database Form Views

The simplified Phone Log application

Page 15: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

15

Using Database Form Views

With the widgets in place, we create a QSqlTableModel. Since we did not specify a particular database connection, it uses the default one. We tell the model which able it is to work on and call select() to make it populate itself with data. We also choose to apply a sort order to the table.

self.model = QSqlTableModel(self)self.model.setTable("calls")self.model.setSort(STARTTIME, Qt.AscendingOrder)self.model.select()

Page 16: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

16

Using Database Form Views

Now that we have suitable widgets and a model, we must somehow link them together. This is achieved by using a QDataWidgetMapper..

self.mapper = QDataWidgetMapper(self)self.mapper.setSubmitPolicy(QDataWidgetMapper.ManualSubmit)self.mapper.setModel(self.model)self.mapper.addMapping(self.callerEdit, CALLER)self.mapper.addMapping(self.startDateTime, STARTTIME)self.mapper.addMapping(self.endDateTime, ENDTIME)self.mapper.addMapping(topicEdit, TOPIC)self.mapper.toFirst()

Page 17: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

17

Using Database Form Views

If the user navigates, we must remember the current row, since it is forgotten after calling submit(). Then, after saving the current record, we set the row to be the one appropriate for the navigation the user requested (but kept within bounds), and then use setCurrentIndex() to move to the appropriate record.

Page 18: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

18

Using Database Form Views

def saveRecord(self, where):row = self.mapper.currentIndex()self.mapper.submit()if where == PhoneLogDlg.FIRST:

row = 0elif where == PhoneLogDlg.PREV:

row = 0 if row <= 1 else row - 1elif where == PhoneLogDlg.NEXT:

Page 19: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

19

Using Database Form Views

row += 1if row >= self.model.rowCount():

row = self.model.rowCount() - 1elif where == PhoneLogDlg.LAST:

row = self.model.rowCount() - 1self.mapper.setCurrentIndex(row)

Page 20: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

20

Using Database Form Views

row += 1if row >= self.model.rowCount():

row = self.model.rowCount() - 1elif where == PhoneLogDlg.LAST:

row = self.model.rowCount() - 1self.mapper.setCurrentIndex(row)

Page 21: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

21

Using Database Form Views

We have chosen to always add new records at the end. To do this we find the row after the last one, save the current record, and then insert a new record at the last row in the model. Then we set the mapper’s current index to the new row, initialize a couple of fields, and give the caller field the focus, ready for the user to start typing.

Page 22: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

22

Using Database Form Views

def addRecord(self):row = self.model.rowCount()self.mapper.submit()self.model.insertRow(row)self.mapper.setCurrentIndex(row)now = QDateTime.currentDateTime()self.startDateTime.setDateTime(now)self.endDateTime.setDateTime(now)self.callerEdit.setFocus()

Page 23: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

23

Using Database Form Views

If the user clicks Delete we pick out some information from the current record and use it when we ask the user to confirm the deletion. If they confirm, we retrieve the current row, remove the row from the model, and call submitAll() to force the model to write back the change to the underlying data source (in this case the database). Then we finish up by navigating to the next record.

Page 24: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

24

Using Database Form Views

def deleteRecord(self):caller = self.callerEdit.text()starttime = self.startDateTime.dateTime().toString(DATETIME_FORMAT)if QMessageBox.question(self,QString("Delete"),QString("Delete call made by<br>%1 on %2?").arg(caller).arg(starttime),QMessageBox.Yes|QMessageBox.No) == QMessageBox.No:

returnrow = self.mapper.currentIndex()self.model.removeRow(row)self.model.submitAll()if row + 1 >= self.model.rowCount():

row = self.model.rowCount() - 1self.mapper.setCurrentIndex(row)

Page 25: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

25

Using Database Table Views

Probably the most natural and convenient way to present database data is to show database tables and views in GUI tables. This allows users to see many records at once, and it is particularly convenient for showing master–detail relationships.

The Asset Manager database design

Page 26: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

26

Using Database Table Views

Probably the most natural and convenient way to present database data is to show database tables and views in GUI tables. This allows users to see many records at once, and it is particularly convenient for showing master–detail relationships.

Page 27: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

27

Using Database Table Views

The Asset Manager database design

Page 28: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

28

Using Database Table Views

class MainForm(QDialog):def __init__(self):

super(MainForm, self).__init__()self.assetModel = QSqlRelationalTableModel(self)self.assetModel.setTable("assets")self.assetModel.setRelation(CATEGORYID,QSqlRelation("categories", "id", "name"))self.assetModel.setSort(ROOM, Qt.AscendingOrder)self.assetModel.setHeaderData(ID, Qt.Horizontal,QVariant("ID"))self.assetModel.setHeaderData(NAME, Qt.Horizontal,QVariant("Name"))self.assetModel.setHeaderData(CATEGORYID, Qt.Horizontal,QVariant("Category"))self.assetModel.setHeaderData(ROOM, Qt.Horizontal,QVariant("Room"))self.assetModel.select()

Page 29: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

29

Using Database Table Views

The view is a standard QTableView, but instead of setting a QSqlRelationalDelegate, we have set a custom delegate. We will detour to look at this in a moment. The selection mode is set so that users can navigate to individual fields; the selection behavior is that the row that has the focus is highlighted. We don’t want to show the ID field since it isn’t meaningful to the user, so we hide it.

Page 30: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

30

Using Database Table Views

self.assetView = QTableView()self.assetView.setModel(self.assetModel)self.assetView.setItemDelegate(AssetDelegate(self))self.assetView.setSelectionMode(QTableView.SingleSelection)self.assetView.setSelectionBehavior(QTableView.SelectRows)self.assetView.setColumnHidden(ID, True)self.assetView.resizeColumnsToContents()

Page 31: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

31

Using Database Table Views

The heart of the createEditor() method is the code that sets up the QLineEdit for entering room numbers. Room numbers are four digits long, made up of a floor number, in the range 01–27 (but excluding 13), and a room number on the floor in the range 01–62. For example, 0231 is floor 2, room 31, but 0364 is invalid. The regular expression is sufficient for specifying valid room numbers, but it cannot set a minimum number of digits, since one, two, or three digits may be a valid prefix for a valid four digit room number. We have solved this by using an input mask that requires exactly four digits to be entered. For the other fields, we pass the work on to the base class.

Page 32: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

32

Using Database Table Views

def createEditor(self, parent, option, index):if index.column() == ROOM:

editor = QLineEdit(parent)regex = QRegExp(r"(?:0[1-9]|1[0124-9]|2[0-7])“

r"(?:0[1-9]|[1-5][0-9]|6[012])")validator = QRegExpValidator(regex, parent)editor.setValidator(validator)editor.setInputMask("9999")editor.setAlignment(Qt.AlignRight|Qt.AlignVCenter)return editor

else:return QSqlRelationalDelegate.createEditor(self, parent,option, index)

Page 33: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

33

Using Database Table Views

The code for creating the log model is almost the same as the code we used for the asset model. We use a QSqlRelationalTableModel because we have a foreign key field, and we provide our own column titles.

self.logModel = QSqlRelationalTableModel(self)self.logModel.setTable("logs")self.logModel.setRelation(ACTIONID,QSqlRelation("actions", "id", "name"))self.logModel.setSort(DATE, Qt.AscendingOrder)self.logModel.setHeaderData(DATE, Qt.Horizontal,QVariant("Date"))self.logModel.setHeaderData(ACTIONID, Qt.Horizontal,QVariant("Action"))self.logModel.select()

Page 34: Rapid GUI Programming with Python and Qt DatabasesBy Raed S. Rasheed DatabasesBy 1

34

Using Database Table Views

self.logView = QTableView()self.logView.setModel(self.logModel)self.logView.setItemDelegate(LogDelegate(self))self.logView.setSelectionMode(QTableView.SingleSelection)self.logView.setSelectionBehavior(QTableView.SelectRows)self.logView.setColumnHidden(ID, True)self.logView.setColumnHidden(ASSETID, True)self.logView.resizeColumnsToContents()self.logView.horizontalHeader().setStretchLastSection(True)