class Analytics(QWidget):
def __init__(self,parent=None, database=None):
QWidget.__init__(self, parent)
+ # These are the room numbers in the respective dorms that people can live in
self.roomlists = { "Lechner" : [111, 112, 113, 114, 115, 116, 117, 121, 122, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 205, 206, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 221, 222, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 305, 306, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 321, 322, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 405, 406, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 421, 422, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440] ,
"McFadden" : [151, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 170, 171, 172, 173, 175, 176, 177, 178, 179, 182, 183, 184, 185, 186, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 274, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 374, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 474] ,
"Clements" : [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 332, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 432]
self.setLayout(self.layout)
def updateDB(self, newdbname):
+ """Give the widget a handle to the changed database, so analytics will be run on the right DB"""
self.db.close()
self.db.setDatabaseName(newdbname)
self.db.open()
def vlog(self, line): # append to the verbose log only
+ """Append line to the verbose log only"""
self.verbosereport = self.verbosereport + line + "\n"
def log(self, line): # append to the verbose and report logs
+ """Append line to both the report and verbose logs"""
self.verbosereport = self.verbosereport + line + "\n"
self.report = self.report + line + "\n"
def generateReport(self):
+ """Runs analytics on the current database and displays them in the report and verbose detail boxes."""
self.report = ""
self.verbosereport = ""
q = QSqlQuery(self.db)
self.verbosereportbox.setPlainText(self.verbosereport)
def finished(self):
+ """Slot that indicates this widget is done being used (Cancel has been pressed)"""
self.emit(SIGNAL("done()"))
class ChooseAction (QWidget):
def __init__(self, parent=None, db=None):
+ """Sets up the list of buttons and associated actions"""
QWidget.__init__(self,parent)
self.db = db
self.l1 = QLabel("<h1>Hall Directory</h1><h1>Select a task:</h1>")
QtCore.QObject.connect(self.pb5, QtCore.SIGNAL("clicked()"), self.runAnalytics)
self.setLayout(self.layout)
def addPerson(self):
+ """Slot triggered to add a person. Runs the NewPersonWizard."""
wiz = NewPersonWizard(self,self.db)
wiz.exec_()
wiz = None
def editPerson(self):
+ """Emits the signal editPerson(), so parent can swap to the EditPerson widget"""
self.emit(QtCore.SIGNAL("editPerson()"))
def importPhotos(self):
+ """Emits the signal mergePhotos(), so parent can swap to the MergePhotos widget"""
self.emit(QtCore.SIGNAL("mergePhotos()"))
def exportDocument(self):
+ """Gets a filename to export the current database to, then emits the exportDocument(QString) signal so parent can pass it to the document exporter"""
fileName = QFileDialog.getSaveFileName(self, "Save new file as:", ".", "OpenDocument Text Documents (*.odt)")
if not fileName.isEmpty():
if not fileName.endsWith(".odt"): # if they leave off the extension, add it
fileName = fileName.append(".odt")
self.emit(QtCore.SIGNAL("exportDocument(QString)"),fileName)
def runAnalytics(self):
+ """Emits the signal runAnalytics(), so parent can swap to Analytics widget"""
self.emit(QtCore.SIGNAL("runAnalytics()"))
class EditPerson(QWidget):
def __init__(self, parent=None, db=None):
+ """Set up EditPerson with parent widget and database from which to pull data"""
QWidget.__init__(self,parent)
self.db = db
self.show()
def setupModel(self):
+ """Prepare data model for the table on the left and add header fields"""
self.model = QSqlTableModel(self, self.db)
self.model.setEditStrategy(QSqlTableModel.OnManualSubmit)
self.model.setTable("people")
self.model.setHeaderData(self.model.fieldIndex("forename"), Qt.Horizontal, QVariant("First Name"))
self.model.setHeaderData(self.model.fieldIndex("surname"), Qt.Horizontal, QVariant("Last Name"))
def setupForm(self):
+ """Initialize data breakout widgets on right"""
self.current_record = QSqlRecord()
self.current_row = -1
# Text labels
self.buttonlayout.addWidget(self.pb_save)
self.buttonlayout.addWidget(self.pb_saveclose)
def setupActions(self):
+ """Set up signal/slot connections"""
# Update filtered table
QObject.connect(self.search_bar, SIGNAL("textChanged(QString)"), self.updateTable )
# Selecting a record loads it for editing
QObject.connect(self.dorm, SIGNAL("currentIndexChanged(int)"), self.formChanged )
def formChanged(self):
+ """Enable Save/Reset buttons when a field has been changed in the editor"""
# Do this only if a record has been selected
if not self.current_record.isEmpty():
self.pb_reset.setEnabled(True)
self.pb_save.setEnabled(True)
self.pb_saveclose.setEnabled(True)
def selectionChanged(self, selected, deselected):
+ """Handle changes in table row selection"""
items = selected.indexes()
if len(items) > 0:
self.current_row = items[0].row() # this is the row of the current selected item
self.current_record = self.model.record(items[0].row())
self.fillForm()
def fillForm(self):
+ """Fill form with fields from the selected row of the database"""
# Fill fields with data from selected record
self.netid.setText(self.current_record.field("netid").value().toString())
self.firstname.setText(self.current_record.field("forename").value().toString())
self.pb_changephoto.setEnabled(True)
def reselect(self):
+ """Refreshes the table view from the data source. Called when databases merged - need to reload rows from database."""
self.updateTable(self.search_bar.text())
def updateTable(self, text):
+ """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."""
if text.isEmpty():
self.model.setFilter("")
self.model.select()
if self.model.rowCount() == 1:
self.tableview.selectRow(0)
def resetPressed(self):
+ """Revert fields in editor to their source from the database. Disable Save/Reset buttons."""
self.current_record = self.model.record(self.current_row)
self.fillForm()
self.pb_reset.setEnabled(False)
self.pb_saveclose.setEnabled(False)
print "Resetting form"
def closePressed(self):
+ """Revert all unsaved changes, clear all entries, and emit done() for parent to swap widgets shown."""
print "Closing"
self.model.revertAll()
# Wipe contents of all lineedits, destroy current_record and current_index
self.model.select()
self.emit(SIGNAL("done()"))
def savePressed(self):
+ """Commit changes to currently-selected record to the database."""
print "Saving"
self.current_record.setValue("netid",QVariant(self.netid.text()))
self.current_record.setValue("forename",QVariant(self.firstname.text()))
self.pb_save.setEnabled(False)
self.pb_saveclose.setEnabled(False)
def saveClosePressed(self):
+ """Act as if Save were pressed, then Close."""
self.savePressed()
self.closePressed()
def changePhoto(self):
+ """Allow user to pick a JPEG or PNG file to set as the current record's photo."""
fileName = QFileDialog.getOpenFileName(self, "Select Photo", ".", "Images (*.jpg *.JPG *.png)" )
if not fileName.isEmpty():
print "opening", fileName
self.pb_save.setEnabled(True)
self.pb_saveclose.setEnabled(True)
def wipeFields(self):
+ """Clear all editable fields, including the search bar."""
self.search_bar.clear()
self.current_record = QSqlRecord()
self.current_index = -1
self.photo.setText("No record selected")
def updateDB(self, newdbname):
+ """Force database reload with new database name."""
print "updateDB called",newdbname
self.db.close()
self.db.setDatabaseName(newdbname)
class LDAPSearcher():
def __init__(self):
+ """Bind to the Texas A&M LDAP server."""
server = "operator.tamu.edu"
who = ""
cred = ""
#print "bound successfully"
def lookup(self,username=""):
+ """Look up person with netid <username> and return a dictionary of relevant attributes."""
base = ""
scope = ldap.SCOPE_SUBTREE
filter = ""
class MainApp (QMainWindow):
schema = "CREATE TABLE people ( id INTEGER PRIMARY KEY AUTOINCREMENT, forename TEXT NOT NULL, surname TEXT NOT NULL, netid TEXT NOT NULL, email TEXT, birthday TEXT, phone TEXT, major TEXT, dorm TEXT, room INTEGER, photo BLOB, createtime TEXT NOT NULL, mtime TEXT NOT NULL);"
def __init__(self, Parent=None):
+ """Set up the main view, load the default database and schema, and set the widgets visible."""
QMainWindow.__init__(self, Parent)
self.setWindowTitle("Whitepages V3")
self.show()
def createActions(self):
+ """Create actions and connect signals and slots."""
self.fileNewAction = QAction("&New Database",self)
QObject.connect( self.fileNewAction, SIGNAL("triggered()"), self.newFile)
self.fileOpenAction = QAction("&Open Database",self)
def createMenus(self):
+ """Create the drop-down File menu."""
self.fileMenu = self.menuBar().addMenu("&File")
self.fileMenu.addAction(self.fileNewAction)
self.fileMenu.addAction(self.fileOpenAction)
def newFile(self):
+ """Allow user to specify a new SQLite database to work with."""
fileName = QFileDialog.getSaveFileName(self, "Save new file as:", ".", "Databases (*.db)")
if not fileName.isEmpty(): # If they cancelled, do nothing
if not fileName.endsWith(".db"): # if they leave off the extension, add it
else:
self.statusBar().showMessage("Cancelled creating new database")
def openFile(self):
+ """Load user-specified SQLite database for editing."""
fileName = QFileDialog.getOpenFileName(self, "Open dataset", ".", "Databases (*.db)")
if not fileName.isEmpty():
self.db.close()
else:
self.statusBar().showMessage("Canceled loading database")
def mergeWizard(self):
+ """Import user-specified SQLite database into this database, with intelligent merge heuristics. NetID conflicts are resolved by ignoring the to-be-imported record and keeping the one in the currently-open database."""
fileName = QFileDialog.getOpenFileName(self, "Import which dataset?", ".", "Databases (*.db)" )
if not fileName.isEmpty():
tempdb = QSqlDatabase.addDatabase("QSQLITE","tempdb")
else:
self.statusBar().showMessage("Canceled merging databases")
def quit(self):
+ """Exit program."""
# You could do cleanup, like closing/flushing the database, an "Are you sure you want to quit?"
# modal dialog, or saving the window layout/state.
qApp.quit()
def returnToMainMenu(self):
+ """Switch to ChooseAction widget."""
self.center.setCurrentWidget(self.chooseaction)
def editPersonSlot(self):
+ """Switch to EditPerson widget."""
self.editperson.model.select() # update the table
self.center.setCurrentWidget(self.editperson)
def mergePhotosSlot(self):
+ """Switch to MergePhotos widget."""
print "beginning photo merge"
self.center.setCurrentWidget(self.mergephotos)
#self.mergephotos.beginMerge()
def exportDocumentSlot(self, filename):
+ """Export current database to ODT file <filename>."""
writer = ODTWriter(self.db)
success = writer.write(filename)
if success:
else:
self.statusBar().showMessage(QString("Document export cancelled or failed"))
def runAnalyticsSlot(self):
+ """Switch to Analytics widget."""
print "Running analytics"
self.analytics.generateReport()
self.center.setCurrentWidget(self.analytics)
self.setLayout(self.layout)
def beginMergeNetID(self):
+ """Import photos from a user-specified folder by filename; namely <netid>.JPG."""
foldertext = QFileDialog.getExistingDirectory( None, "Pick folder of photos", ".")
if not foldertext.isEmpty():
print foldertext
self.done()
def beginMergeTimestamp(self):
+ """Import photos from a user-specified folder by timestamp. This relies on the folder containing the same number of pictures as records in the database missing photos, as well as the photos and database records being created in the same order."""
foldertext = QFileDialog.getExistingDirectory( None, "Pick folder of photos", ".")
if not foldertext.isEmpty():
print foldertext
print "incorrect number of photos, can't merge"
def done(self):
+ """Indicate to parent widget to switch to main menu."""
self.emit(SIGNAL("done()") )
class NewPersonWizard(QWizard):
def __init__(self, parent=None,db=None):
+ """Run the new person wizard."""
QWizard.__init__(self, parent)
self.db = db
self.addPage(PageNetID(self,self.db))
self.db = db
def write(self, filename):
+ """Write out data from current database to <filename> in ODT format."""
doc = QTextDocument()
cur = QTextCursor(doc)
cur.movePosition(QTextCursor.Start)
class PageCommit(QWizardPage):
def __init__(self, parent=None, db=None):
+ """Set up widgets and message to have picture taken."""
QWizardPage.__init__(self,parent)
self.db = db
self.instructions = QLabel("You're done with the computer. Go have your picture taken.\n\nThe SAs/PAs will add the photo to your data.")
self.setSubTitle("Photo")
def initializePage(self):
+ """Add data collected from previous pages to database."""
q = QSqlQuery(self.db)
q.prepare("INSERT INTO people (netid, forename, surname, email, birthday, phone, major, dorm, room, createtime, mtime )"
"VALUES (:netid, :forename, :surname, :email, :birthday, :phone, :major, :dorm, :room, :createtime, :mtime )" );
class PageNetID(QWizardPage):
def __init__(self, parent=None, db=None ):
+ """Display disclaimer and get NetID from user."""
QWizardPage.__init__(self,parent)
self.db = db
self.instructions = QLabel("&Enter your NetID:")
self.setTitle("Move-in Wizard")
self.setSubTitle("Enter your NetID:")
def validatePage(self):
+ """Make sure the NetID doesn't already exist in this database - if it does, we already have this person's data."""
netid = self.field("netid")
q = QSqlQuery()
q.prepare("SELECT * FROM people where netid = :netid")
class PageNewUserData(QWizardPage):
def __init__(self, parent=None):
+ """Set up lots of input fields."""
QWizardPage.__init__(self,parent)
self.setCommitPage(True)
self.setButtonText(QWizard.CommitButton, "Next")
def initializePage(self):
+ """Load known information from LDAP, like email, major, name, and NetID."""
a = LDAPSearcher()
ldapinfo = a.lookup(str(self.field("netid").toString()))
for key,value in ldapinfo.iteritems():