]> git.zarvox.org Git - wp3.git/blob - editperson.py
Added Python docstrings for all classes.
[wp3.git] / editperson.py
1 from PyQt4.QtCore import *
2 from PyQt4.QtGui import *
3 from PyQt4.QtSql import *
4
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)
9                 self.db = db
10
11                 self.setupModel()
12
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)
18
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()
24
25                 self.setWindowTitle("Edit records")
26                 self.toplayout = QHBoxLayout() # Master layout
27                 
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)
37                 
38                 self.rightpanel = QVBoxLayout() # Contains fields and buttons
39                 self.setupForm()
40
41
42                 self.rightpanel.addLayout(self.formlayout)
43                 self.rightpanel.addLayout(self.buttonlayout)
44
45                 self.toplayout.addLayout(self.leftpanel)
46                 self.toplayout.addSpacing(10)
47                 self.toplayout.addLayout(self.rightpanel)
48
49                 self.setLayout(self.toplayout)
50                 self.setupActions()
51                 self.resize(800,600)
52                 self.show()
53                 
54         def setupModel(self):
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")
59                 self.model.select()
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"))
62         def setupForm(self):
63                 """Initialize data breakout widgets on right"""
64                 self.current_record = QSqlRecord()
65                 self.current_row = -1
66                 # Text labels
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)
88                 # Personal info
89                 self.surname = QLineEdit()
90                 self.firstname = QLineEdit()
91                 self.phone = QLineEdit()
92                 self.email = QLineEdit()
93                 self.major = QLineEdit()
94                 self.netid = QLineEdit()
95                 # Birthday
96                 self.year = QComboBox()
97                 self.month = QComboBox()
98                 self.day = QComboBox()
99                 # Residence locations
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 )
176
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()
187                 if len(items) > 0:
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())
190                         self.fillForm()
191         def fillForm(self):
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()
210                 if ba.size() > 0:
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)
216                 else:
217                         self.photo.setText("No photo available")
218                 self.pb_changephoto.setEnabled(True)
219
220         def reselect(self):
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."""
225                 if text.isEmpty():
226                         self.model.setFilter("")
227                         self.model.select()
228                         return
229                 t = str(text)
230                 strs = t.split()
231                 patterns = [] # can't use list comprehensions for this one because setValue returns void
232                 for i in strs:
233                         f = QSqlField("input",QVariant.String)
234                         f.setValue(QVariant(QString("%" + i + "%")))
235                         patterns.append(f)
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))
240                 self.model.select()
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)
246                 self.fillForm()
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."""
253                 print "Closing"
254                 self.model.revertAll()
255                 # Wipe contents of all lineedits, destroy current_record and current_index
256                 self.wipeFields()
257                 self.model.select()
258                 self.emit(SIGNAL("done()"))
259         def savePressed(self):
260                 """Commit changes to currently-selected record to the database."""
261                 print "Saving"
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]
273                 bday = QDate(y,m,d)
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
276
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."""
285                 self.savePressed()
286                 self.closePressed()
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)
297                         ba = QByteArray()
298                         buffer = QBuffer(ba)
299                         buffer.open(QIODevice.WriteOnly)
300                         self.currentimage.save(buffer, "JPG")
301                         buffer.close()
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
311                 self.surname.clear()
312                 self.firstname.clear()
313                 self.phone.clear()
314                 self.email.clear()
315                 self.major.clear()
316                 self.netid.clear()
317                 self.year.setCurrentIndex(0)
318                 self.month.setCurrentIndex(0)
319                 self.day.setCurrentIndex(0)
320                 self.dorm.setCurrentIndex(0)
321                 self.room.clear()
322                 self.photo.setText("No record selected")
323
324         def updateDB(self, newdbname):
325                 """Force database reload with new database name."""
326                 print "updateDB called",newdbname
327                 self.db.close()
328                 self.db.setDatabaseName(newdbname)
329                 self.db.open()
330                 self.setupModel()
331                 self.tableview.setModel(self.model)
332                 QObject.connect(self.tableview.selectionModel(), SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.selectionChanged )
333                 self.wipeFields()
334                 self.updateTable(QString())
335
336
337
338 if __name__ == "__main__":
339         a = QApplication([""])
340         w = EditPerson()
341         a.exec_()