1 from PyQt4.QtCore import *
2 from PyQt4.QtGui import *
3 from PyQt4.QtSql import *
5 class EditPerson(QWidget):
6 def __init__(self, parent=None, db=None):
7 """Set up EditPerson with parent widget and database from which to pull data"""
8 QWidget.__init__(self,parent)
13 self.tableview = QTableView(self)
14 self.tableview.setSelectionBehavior(QAbstractItemView.SelectRows)
15 self.tableview.setSelectionMode(QAbstractItemView.SingleSelection)
16 self.tableview.setEditTriggers(QAbstractItemView.NoEditTriggers)
17 self.tableview.setModel(self.model)
19 for i in xrange(self.model.columnCount()):
20 self.tableview.hideColumn(i)
21 self.tableview.showColumn(self.model.fieldIndex("forename"))
22 self.tableview.showColumn(self.model.fieldIndex("surname"))
23 self.tableview.resizeColumnsToContents()
25 self.setWindowTitle("Edit records")
26 self.toplayout = QHBoxLayout() # Master layout
28 self.leftpanel = QVBoxLayout() # Contains user search and select
29 self.search_label = QLabel("F&ilter by name:")
30 self.search_bar = QLineEdit()
31 self.search_label.setBuddy(self.search_bar)
32 self.searchpair = QHBoxLayout()
33 self.searchpair.addWidget(self.search_label)
34 self.searchpair.addWidget(self.search_bar)
35 self.leftpanel.addLayout(self.searchpair)
36 self.leftpanel.addWidget(self.tableview)
38 self.rightpanel = QVBoxLayout() # Contains fields and buttons
42 self.rightpanel.addLayout(self.formlayout)
43 self.rightpanel.addLayout(self.buttonlayout)
45 self.toplayout.addLayout(self.leftpanel)
46 self.toplayout.addSpacing(10)
47 self.toplayout.addLayout(self.rightpanel)
49 self.setLayout(self.toplayout)
55 """Prepare data model for the table on the left and add header fields"""
56 self.model = QSqlTableModel(self, self.db)
57 self.model.setEditStrategy(QSqlTableModel.OnManualSubmit)
58 self.model.setTable("people")
60 self.model.setHeaderData(self.model.fieldIndex("forename"), Qt.Horizontal, QVariant("First Name"))
61 self.model.setHeaderData(self.model.fieldIndex("surname"), Qt.Horizontal, QVariant("Last Name"))
63 """Initialize data breakout widgets on right"""
64 self.current_record = QSqlRecord()
67 self.netid_lab = QLabel("NetID:")
68 self.year_lab = QLabel("Year:")
69 self.month_lab = QLabel("Month:")
70 self.day_lab = QLabel("Day:")
71 self.firstname_lab = QLabel("Preferred name:")
72 self.surname_lab = QLabel("Last name:")
73 self.email_lab = QLabel("Email:")
74 self.phone_lab = QLabel("Phone:")
75 self.major_lab = QLabel("Major:")
76 self.hometown_lab = QLabel("Hometown:")
77 self.room_lab = QLabel("Room number:")
78 self.bday_lab = QLabel("Birthday:")
79 self.dorm_lab = QLabel("Dorm:")
80 self.photo = QLabel("No record selected")
81 self.photo.setScaledContents(False)
82 self.currentimage = QImage()
83 self.displayimage = QImage()
84 self.displaypixmap = QPixmap()
85 self.photo.setAlignment(Qt.AlignCenter)
86 self.pb_changephoto = QPushButton("Select &photo")
87 self.pb_changephoto.setEnabled(False)
89 self.surname = QLineEdit()
90 self.firstname = QLineEdit()
91 self.phone = QLineEdit()
92 self.email = QLineEdit()
93 self.major = QLineEdit()
94 self.netid = QLineEdit()
96 self.year = QComboBox()
97 self.month = QComboBox()
98 self.day = QComboBox()
100 self.dorm = QComboBox()
101 self.room = QLineEdit()
102 # Fill the comboboxes with values, set valid field types
103 years = QStringList(["1983","1984","1985","1986","1987","1988","1989","1990","1991","1992","1993","1994"])
104 months = QStringList(["January","February","March","April","May","June","July","August","September","October","November","December"])
105 days = QStringList(["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31"])
106 dorms = QStringList(["Clements","Lechner","McFadden"])
107 self.year.addItems(years)
108 self.month.addItems(months)
109 self.day.addItems(days)
110 self.dorm.addItems(dorms)
111 self.phone.setInputMask("(999)-999-9999")
112 self.major.setMaxLength(4)
113 self.room.setMaxLength(3)
114 # Create and fill the UI
115 self.formlayout = QGridLayout()
116 self.bday_layout = QHBoxLayout()
117 self.bday_layout.addWidget(self.year)
118 self.bday_layout.addWidget(self.month)
119 self.bday_layout.addWidget(self.day)
120 self.formlayout.addWidget(self.netid_lab, 0, 0)
121 self.formlayout.addWidget(self.netid, 0, 1)
122 self.formlayout.addWidget(self.firstname_lab, 1, 0)
123 self.formlayout.addWidget(self.firstname, 1, 1)
124 self.formlayout.addWidget(self.surname_lab, 2, 0)
125 self.formlayout.addWidget(self.surname, 2, 1)
126 self.formlayout.addWidget(self.email_lab, 3, 0)
127 self.formlayout.addWidget(self.email, 3, 1)
128 self.formlayout.addWidget(self.phone_lab, 4, 0)
129 self.formlayout.addWidget(self.phone, 4, 1)
130 self.formlayout.addWidget(self.bday_lab,5,0)
131 self.formlayout.addLayout(self.bday_layout,5,1)
132 self.formlayout.addWidget(self.major_lab, 6, 0)
133 self.formlayout.addWidget(self.major, 6, 1)
134 self.formlayout.addWidget(self.dorm_lab, 7, 0)
135 self.formlayout.addWidget(self.dorm, 7, 1)
136 self.formlayout.addWidget(self.room_lab, 8, 0)
137 self.formlayout.addWidget(self.room, 8, 1)
138 self.formlayout.addWidget(self.photo,9,0,1,2)
139 self.formlayout.addWidget(self.pb_changephoto,10,0,1,2)
140 self.buttonlayout = QHBoxLayout()
141 self.pb_cancel = QPushButton("&Cancel")
142 self.pb_save = QPushButton("&Save")
143 self.pb_saveclose = QPushButton("Save && Clos&e")
144 self.pb_reset = QPushButton("&Reset")
145 self.pb_save.setEnabled(False)
146 self.pb_saveclose.setEnabled(False)
147 self.pb_reset.setEnabled(False)
148 self.buttonlayout.addWidget(self.pb_reset)
149 self.buttonlayout.addWidget(self.pb_cancel)
150 self.buttonlayout.addWidget(self.pb_save)
151 self.buttonlayout.addWidget(self.pb_saveclose)
152 def setupActions(self):
153 """Set up signal/slot connections"""
154 # Update filtered table
155 QObject.connect(self.search_bar, SIGNAL("textChanged(QString)"), self.updateTable )
156 # Selecting a record loads it for editing
157 QObject.connect(self.tableview.selectionModel(), SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.selectionChanged )
158 # Map buttons to actions
159 QObject.connect(self.pb_reset, SIGNAL("clicked()"), self.resetPressed )
160 QObject.connect(self.pb_cancel, SIGNAL("clicked()"), self.closePressed )
161 QObject.connect(self.pb_saveclose, SIGNAL("clicked()"), self.saveClosePressed )
162 QObject.connect(self.pb_save, SIGNAL("clicked()"), self.savePressed )
163 QObject.connect(self.pb_changephoto, SIGNAL("clicked()"), self.changePhoto )
164 # Changes to the record enable buttons
165 QObject.connect(self.netid, SIGNAL("textEdited(QString)"), self.formChanged )
166 QObject.connect(self.firstname, SIGNAL("textEdited(QString)"), self.formChanged )
167 QObject.connect(self.surname, SIGNAL("textEdited(QString)"), self.formChanged )
168 QObject.connect(self.email, SIGNAL("textEdited(QString)"), self.formChanged )
169 QObject.connect(self.phone, SIGNAL("textEdited(QString)"), self.formChanged )
170 QObject.connect(self.major, SIGNAL("textEdited(QString)"), self.formChanged )
171 QObject.connect(self.room, SIGNAL("textEdited(QString)"), self.formChanged )
172 QObject.connect(self.year, SIGNAL("currentIndexChanged(int)"), self.formChanged )
173 QObject.connect(self.month, SIGNAL("currentIndexChanged(int)"), self.formChanged )
174 QObject.connect(self.day, SIGNAL("currentIndexChanged(int)"), self.formChanged )
175 QObject.connect(self.dorm, SIGNAL("currentIndexChanged(int)"), self.formChanged )
177 def formChanged(self):
178 """Enable Save/Reset buttons when a field has been changed in the editor"""
179 # Do this only if a record has been selected
180 if not self.current_record.isEmpty():
181 self.pb_reset.setEnabled(True)
182 self.pb_save.setEnabled(True)
183 self.pb_saveclose.setEnabled(True)
184 def selectionChanged(self, selected, deselected):
185 """Handle changes in table row selection"""
186 items = selected.indexes()
188 self.current_row = items[0].row() # this is the row of the current selected item
189 self.current_record = self.model.record(items[0].row())
192 """Fill form with fields from the selected row of the database"""
193 # Fill fields with data from selected record
194 self.netid.setText(self.current_record.field("netid").value().toString())
195 self.firstname.setText(self.current_record.field("forename").value().toString())
196 self.surname.setText(self.current_record.field("surname").value().toString())
197 self.email.setText(self.current_record.field("email").value().toString())
198 self.phone.setText(self.current_record.field("phone").value().toString())
199 self.major.setText(self.current_record.field("major").value().toString())
200 self.room.setText(self.current_record.field("room").value().toString())
201 bday = str(self.current_record.field("birthday").value().toString()).split("-")
202 self.year.setCurrentIndex(self.year.findText(bday[0]))
203 self.month.setCurrentIndex(int(bday[1]) - 1)
204 self.day.setCurrentIndex(int(bday[2]) - 1)
205 self.dorm.setCurrentIndex(self.dorm.findText(self.current_record.field("dorm").value().toString()))
206 self.pb_reset.setEnabled(False)
207 self.pb_save.setEnabled(False)
208 self.pb_saveclose.setEnabled(False)
209 ba = self.current_record.field("photo").value().toByteArray()
211 self.currentimage = QImage()
212 self.currentimage.loadFromData(ba)
213 self.displayimage = self.currentimage.scaled(self.photo.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
214 self.displaypixmap = QPixmap.fromImage(self.displayimage)
215 self.photo.setPixmap(self.displaypixmap)
217 self.photo.setText("No photo available")
218 self.pb_changephoto.setEnabled(True)
221 """Refreshes the table view from the data source. Called when databases merged - need to reload rows from database."""
222 self.updateTable(self.search_bar.text())
223 def updateTable(self, text):
224 """Filter table view with text in the search bar. Table view shows only entries that contain each of the space-separated strings in the bar in either the entries forename or surname."""
226 self.model.setFilter("")
231 patterns = [] # can't use list comprehensions for this one because setValue returns void
233 f = QSqlField("input",QVariant.String)
234 f.setValue(QVariant(QString("%" + i + "%")))
236 escapedpatterns = [ self.model.database().driver().formatValue(wc, False) for wc in patterns]
237 filters = [ str(QString("(surname LIKE %1 OR forename LIKE %2)").arg(wc).arg(wc)) for wc in escapedpatterns]
238 filterString = " AND ".join(filters)
239 self.model.setFilter(QString(filterString))
241 if self.model.rowCount() == 1:
242 self.tableview.selectRow(0)
243 def resetPressed(self):
244 """Revert fields in editor to their source from the database. Disable Save/Reset buttons."""
245 self.current_record = self.model.record(self.current_row)
247 self.pb_reset.setEnabled(False)
248 self.pb_save.setEnabled(False)
249 self.pb_saveclose.setEnabled(False)
250 print "Resetting form"
251 def closePressed(self):
252 """Revert all unsaved changes, clear all entries, and emit done() for parent to swap widgets shown."""
254 self.model.revertAll()
255 # Wipe contents of all lineedits, destroy current_record and current_index
258 self.emit(SIGNAL("done()"))
259 def savePressed(self):
260 """Commit changes to currently-selected record to the database."""
262 self.current_record.setValue("netid",QVariant(self.netid.text()))
263 self.current_record.setValue("forename",QVariant(self.firstname.text()))
264 self.current_record.setValue("surname",QVariant(self.surname.text()))
265 self.current_record.setValue("email",QVariant(self.email.text()))
266 self.current_record.setValue("phone",QVariant(self.phone.text()))
267 self.current_record.setValue("major",QVariant(self.major.text()))
268 self.current_record.setValue("room",QVariant(self.room.text()))
269 self.current_record.setValue("dorm",QVariant(self.dorm.currentText()))
270 y = self.year.currentText().toInt()[0]
271 m = self.month.currentIndex() + 1
272 d = self.day.currentText().toInt()[0]
274 self.current_record.setValue("birthday", QVariant(bday.toString(Qt.ISODate)) )
275 self.current_record.setValue("mtime", QVariant(QDateTime.currentDateTime().toString(Qt.ISODate)) ) # update modification time
277 self.model.setRecord(self.current_row, self.current_record)
278 self.model.submitAll()
279 self.updateTable(self.search_bar.text()) # Reselect from the DB
280 self.pb_reset.setEnabled(False)
281 self.pb_save.setEnabled(False)
282 self.pb_saveclose.setEnabled(False)
283 def saveClosePressed(self):
284 """Act as if Save were pressed, then Close."""
287 def changePhoto(self):
288 """Allow user to pick a JPEG or PNG file to set as the current record's photo."""
289 fileName = QFileDialog.getOpenFileName(self, "Select Photo", ".", "Images (*.jpg *.JPG *.png)" )
290 if not fileName.isEmpty():
291 print "opening", fileName
292 self.currentimage = QImage()
293 self.currentimage.load(fileName)
294 self.displayimage = self.currentimage.scaled(self.photo.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
295 self.displaypixmap = QPixmap.fromImage(self.displayimage)
296 self.photo.setPixmap(self.displaypixmap)
299 buffer.open(QIODevice.WriteOnly)
300 self.currentimage.save(buffer, "JPG")
302 self.current_record.setValue("photo",QVariant(ba))
303 self.pb_reset.setEnabled(True)
304 self.pb_save.setEnabled(True)
305 self.pb_saveclose.setEnabled(True)
306 def wipeFields(self):
307 """Clear all editable fields, including the search bar."""
308 self.search_bar.clear()
309 self.current_record = QSqlRecord()
310 self.current_index = -1
312 self.firstname.clear()
317 self.year.setCurrentIndex(0)
318 self.month.setCurrentIndex(0)
319 self.day.setCurrentIndex(0)
320 self.dorm.setCurrentIndex(0)
322 self.photo.setText("No record selected")
324 def updateDB(self, newdbname):
325 """Force database reload with new database name."""
326 print "updateDB called",newdbname
328 self.db.setDatabaseName(newdbname)
331 self.tableview.setModel(self.model)
332 QObject.connect(self.tableview.selectionModel(), SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.selectionChanged )
334 self.updateTable(QString())
338 if __name__ == "__main__":
339 a = QApplication([""])