class TextUI: def __init__(self): self.db = None self.tools = Utils() def start(self): #=========================================================================== # Main function - start connection to db then open main menu #=========================================================================== try: while 1: # Get DB info self.getDbInfo() # Create the DB object self.db = DB(self.dbHost, self.dbName, self.dbUsersTable, self.dbVisitsTable, self.dbUser, self.dbPass) # Connect to the database connectStatus = self.connectToDatabase() # If we failed to connect to the database offer to re-enter db info if connectStatus != c.SUCCESS: reenter = input( "Failed to connect to database. Re-enter database info? (Y,n) " ) if reenter.lower() == "n": print("Bye.") sys.exit(0) else: break # Start the main menu loop self.displayMenu() except KeyboardInterrupt: pass finally: print("Cleaning up and exiting...") if self.db is not None: self.db.close() def displayMenu(self): #=========================================================================== # Display main cli and take appropriate actions #=========================================================================== #print("\nType \"back\" at any time to go up a menu level.") while 1: # Display main menu print("\n\t1.) Check-in\n\t2.) Show Visits\n\t3.) Exit") try: option = input("\n>> ") if option == "1": self.checkIn() elif option == "2": self.showVisits() elif option == "3": sys.exit(0) #elif option == "back" or option == "exit": # exit = input("Exit? (y,N) ") # if exit.lower() == "y": # sys.exit(0) else: self.invalidInput() except ValueError: self.invalidInput() def connectToDatabase(self): #=========================================================================== # Open connection to database and return status #=========================================================================== # Use stdout.write to prevent newline sys.stdout.write("Connecting to database...") # Connect to the DB! status = self.db.connect() if status == c.SUCCESS: print("done.") return status elif status == c.BAD_PASSWD: print("\nError connecting to database: Bad username or password.") return status else: print("\nUnknown Error connecting to database.") return c.FAILURE def checkIn(self): #=========================================================================== # Log card data to db #=========================================================================== # Get and validate the visit value for this check-in # Limited to 500 visits to prevent bad typos """while 1: visitValue = self.tools.sanitizeInput(input("\nVisit Value (" + str(c.DEFAULT_VISITS) + "): ")) # Validate visit input if visitValue == "": visitValue = str(c.DEFAULT_VISITS) break elif (visitValue.isdigit() and int(visitValue) <= 500) or visitValue == "back": break else: print("Invalid input. Try again.")""" while 1: CUID = self.tools.getCardSwipe() # If the user requested to exit the loop, break if CUID == c.BACK: break elif CUID == c.ERROR_READING_CARD: print("Error reading card. Swipe card again.") continue # Sanitize CUID CUID = self.tools.sanitizeInput(CUID) # CUID will be empty if it failed sanitization. Skip checkIn if that is the case if CUID == "": continue # Do the checkIn checkInResult = self.db.checkIn(CUID) if checkInResult["checkInStatus"] == c.SQL_ERROR: self.showDatabaseError(checkInResult["sqlError"]) elif checkInResult["checkInStatus"] == c.BAD_CHECKIN_TIME: print("Error: You may only check-in once per hour.") elif checkInResult["checkInStatus"] == c.FUTURE_CHECKIN_TIME: print( "Error: Previous check-in time was in the future. Check your local system time." ) elif checkInResult["checkInStatus"] == c.CUID_NOT_IN_DB: # Ask if user wants to add the card addCard = input( "Error: Card not found in database. Add it now? (Y,n) ") if addCard == "n": continue # Get the userID for the new card firstName = self.tools.sanitizeInput(input("First Name: ")) lastName = self.tools.sanitizeInput(input("Last Name: ")) email = self.tools.sanitizeInput(input("Clemson Username: "******"addCardStatus"] == c.SUCCESS: self.showCheckinConfirmation(email) elif addCardResult["addCardStatus"] == c.SQL_ERROR: self.showDatabaseError(addCardResult["sqlError"]) elif checkInResult["checkInStatus"] == c.SUCCESS: self.showCheckinConfirmation(checkInResult["userID"]) else: print("Unknown error checking in.") def showVisits(self): #=========================================================================== # Poll db for visit data based on userID #=========================================================================== userID = self.tools.sanitizeInput(input("\nUser ID (blank for all): ")) showVisitsResult = self.db.showVisits(userID) if showVisitsResult["showVisitsStatus"] == c.SQL_ERROR: self.showDatabaseError(showVisitsResult["sqlError"]) elif showVisitsResult["showVisitsStatus"] == c.NO_RESULTS: print("\nThere were no results to that query.") elif showVisitsResult["showVisitsStatus"] == c.SUCCESS: # If showing all users, display a pretty table if userID == "": print( "\n+--------------------+\n| User ID | Visits |\n+--------------------+" ) for i in range(len(showVisitsResult["visitsTuple"])): print("|%10s | %6s |" % (showVisitsResult["visitsTuple"][i][0], showVisitsResult["visitsTuple"][i][1])) print("+--------------------+") # Show a single user's visits else: print("\n%s has %s visits." % (userID, str(showVisitsResult["visitsTuple"][0][0]))) def getDbInfo(self): #=========================================================================== # Request dbInfo from user - suggest default info from constants #=========================================================================== self.dbName = input("Database name: (" + c.DEFAULT_DATABASE + ") ") if self.dbName == "": self.dbName = c.DEFAULT_DATABASE self.dbHost = input("Database host: (" + c.DEFAULT_HOST + ") ") if self.dbHost == "": self.dbHost = c.DEFAULT_HOST self.dbUsersTable = input("Database User table: (" + c.TABLE_USERS + ") ") if self.dbUsersTable == "": self.dbUsersTable = c.TABLE_USERS self.dbVisitsTable = input("Database Visits table: (" + c.TABLE_VISITS + ") ") if self.dbVisitsTable == "": self.dbVisitsTable = c.TABLE_VISITS self.dbUser = input("Database Username: (" + c.DEFAULT_USER + ") ") if self.dbUser == "": self.dbUser = c.DEFAULT_USER while 1: self.dbPass = getpass.getpass("Database Password: "******"": print("Database password cannot be blank.") else: break def showCheckinConfirmation(self, userID): #=========================================================================== # show confirmation of successful check in #=========================================================================== print("\n%s is checked in" % (userID)) def showDatabaseError(self, error): #=========================================================================== # Warn of database error - and provide information #=========================================================================== print("\nWARNING! Database error:\n%s" % error.pgerror) def invalidInput(self): #=========================================================================== # Complain about invalid input #=========================================================================== print("Invalid option. Try again.")
class MainWnd(QMainWindow): def __init__(self, db): super(MainWnd, self).__init__() self.db = db self.tools = Utils() # Init card input so it can be appended to later self.cardInput = "" # Compile the regex for pulling the card ID from all the data on a card self.regex = re.compile("%(.+)..\?;") # Declare sleepThread self.sleepThread = SleepThread(c.TIME_BETWEEN_CHECKINS, self.resetCheckinWidget) self.initUI() def initUI(self): # =========================================================================== # init main ui - allow for attendance logging and checking # =========================================================================== # Center the window # setGeometry args are x, y, width, height self.setGeometry(0, 0, 550, 100) geo = self.frameGeometry() centerPt = QDesktopWidget().availableGeometry().center() geo.moveCenter(centerPt) self.move(geo.topLeft()) # Title, icon, and statusbar self.setWindowTitle(c.GROUP_INITIALS + " Attendance") self.setWindowIcon(QIcon(os.path.abspath("images/login_logo.png"))) self.statusBar().showMessage( "Connected to server | " + c.GROUP_NAME + " Attendance Tracker Version " + str(c.VERSION) ) # Init all the central widgets self.initMainMenuWidget() self.initCheckinWidget() self.initShowVisitsWidget() # Init the central stacked widget and set it as the central widget # This allows us to change the central widget easily self.centralWidget = QStackedWidget() self.setCentralWidget(self.centralWidget) # Add the widgets to the main central stacked widget self.centralWidget.addWidget(self.mainMenuWidget) self.centralWidget.addWidget(self.checkinWidget) self.centralWidget.addWidget(self.visitsWidget) # Show the main menu first self.showMainMenuWidget() def keyPressEvent(self, event): # =========================================================================== # Collect key data until regex matches - then reset and collect again # =========================================================================== # Only look for card swipes if the checkin widget is currently shown if self.centralWidget.currentWidget() == self.checkinWidget: try: # Try to match the input to the card ID regex r = self.regex.search(self.cardInput) CUID = r.groups()[0] # A match was made so reset cardInput for the next card self.cardInput = "" # Set the card ID and start the checkin thread # CUID is going into an SQL query; don't forget to sanitize the input if not (self.checkinThread.isRunning() and self.sleepThread.isRunning()): # self.checkinThread.setCUID(Utils.sanitizeInput(str(CUID))) self.checkinThread.setCUID(CUID) self.checkinThread.start() except AttributeError: # If a match was not made append the current text to card input self.cardInput += event.text() def closeEvent(self, closeEvent): # =========================================================================== # Close database connection prior to closing application # =========================================================================== print("Cleaning up and exiting...") if self.db is not None: self.db.close() closeEvent.accept() def initMainMenuWidget(self): # =========================================================================== # Initialize Main menu Widget # =========================================================================== self.mainMenuWidget = QWidget() checkinButton = QImageButton( "Check-in", os.path.abspath("images/magnetic_card.png"), self.showCheckinWidget, 100, self ) showVisitsButton = QImageButton( "Show Visits", os.path.abspath("images/trophy.png"), self.showVisitsWidget, 100, self ) hbox = QHBoxLayout() hbox.addStretch(1) hbox.addWidget(checkinButton) hbox.addSpacing(45) hbox.addWidget(showVisitsButton) hbox.addStretch(1) self.mainMenuWidget.setLayout(hbox) def initCheckinWidget(self): # =========================================================================== # Initialize Check In widget # =========================================================================== self.checkinWidget = QWidget() # Init widgets self.cardPix = QPixmap(os.path.abspath("images/magnetic_card.png")) self.greenPix = QPixmap(os.path.abspath("images/green_check_mark.png")) self.redPix = QPixmap(os.path.abspath("images/red_x_mark.png")) self.checkinImg = QLabel(self) self.checkinLabel = QLabel("Waiting for card swipe...") self.checkinBackBtn = QPushButton("Back", self) # Size the images properly self.cardPix = self.cardPix.scaledToHeight(175, Qt.SmoothTransformation) self.greenPix = self.greenPix.scaledToHeight(175, Qt.SmoothTransformation) self.redPix = self.redPix.scaledToHeight(175, Qt.SmoothTransformation) # Add the card image to image widget self.checkinImg.setPixmap(self.cardPix) # Set the font for the checkin label font = QFont("Sans Serif", 16, QFont.Bold) self.checkinLabel.setFont(font) # Add signals to buttons self.checkinBackBtn.clicked.connect(self.closeCheckinScreen) # Center the image imgHbox = QHBoxLayout() imgHbox.addStretch(1) imgHbox.addWidget(self.checkinImg) imgHbox.addStretch(1) # Add widgets to vbox layout for vertical centering vbox = QVBoxLayout() vbox.addStretch(1) vbox.addLayout(imgHbox) vbox.addWidget(self.checkinLabel) vbox.addWidget(self.checkinBackBtn) vbox.addStretch(1) # Add grid to the hbox layout for horizontal centering hbox = QHBoxLayout() hbox.addStretch(1) hbox.addLayout(vbox) hbox.addStretch(1) # Add the completeted layout to the overall check-in widget self.checkinWidget.setLayout(hbox) def initShowVisitsWidget(self): # =========================================================================== # Initialize visits widget # =========================================================================== self.visitsWidget = QWidget() self.checkinThread = None # Init widgets self.visitsTitle = QLabel("Current visits Standings") self.visitsTextArea = QTextEdit() self.visitsBackBtn = QPushButton("Back", self) # Set the font for the checkin label self.visitsTitle.setFont(QFont("Sans Serif", 12, QFont.Bold)) # Add signals to buttons self.visitsBackBtn.clicked.connect(self.closeShowVisitsScreen) # Create the layout for the visits scroll area self.visitsTextArea.setFont(QFont("Monospace", 8, QFont.Normal)) # Add widgets to vbox layout for vertical centering vbox = QVBoxLayout() vbox.addStretch(1) vbox.addWidget(self.visitsTitle, alignment=Qt.AlignCenter) vbox.addWidget(self.visitsTextArea) vbox.addWidget(self.visitsBackBtn) vbox.addStretch(1) self.visitsWidget.setLayout(vbox) def showMainMenuWidget(self): # =========================================================================== # Show main menu widget # =========================================================================== self.centralWidget.setCurrentWidget(self.mainMenuWidget) def showCheckinWidget(self): # =========================================================================== # Show Checkin Widget - used to request point value that is now depreciated # =========================================================================== self.centralWidget.setCurrentWidget(self.checkinWidget) """# Get the visit value while 1: visitValue, ok = QInputDialog.getText(self, "Visit Value", "Visit Value:", text=str(c.DEFAULT_VISITS)) if ok: if str(visitValue).isdigit(): break else: QMessageBox.critical(self, "Input Error", "Invalid input", QMessageBox.Ok, QMessageBox.Ok) else: self.closeCheckinScreen() return""" # Init the checkin thread # visitValue will be used in SQL queries. Sanitize it. self.checkinThread = CheckinThread(self.db, self.postCardSwipe) def showVisitsWidget(self): # =========================================================================== # Show visits for certain CUID # =========================================================================== self.visitsTextArea.clear() self.centralWidget.setCurrentWidget(self.visitsWidget) # Get the user ID to show visits for or an empty string for all user ID's CUID, ok = QInputDialog.getText(self, "CUID", "CUID (blank for all CUID's):") if not ok: # The show visits thread was not declared yet so just skip the closeShowvisitsScreen function self.showMainMenuWidget() # Init the show visits thread # userID will be used in SQL queries. Sanitize it. self.showVisitsThread = ShowVisitsThread(self.db, self.tools.sanitizeInput(str(CUID)), self.setVisits) self.showVisitsThread.start() def closeCheckinScreen(self): # =========================================================================== # Close checkinThread and return to Main Menu # =========================================================================== if self.checkinThread is not None: self.checkinThread.terminate() self.showMainMenuWidget() def closeShowVisitsScreen(self): # ======================================================================= # Close visits thread and return to main menu # ======================================================================= if self.checkinThread is not None: self.showVisitsThread.terminate() self.showMainMenuWidget() def postCardSwipe(self, checkinStatus, userID, CUID, sqlError): # =========================================================================== # Display results after a card is read - If new card request name # =========================================================================== if checkinStatus == c.SUCCESS: self.checkinImg.setPixmap(self.greenPix) self.checkinLabel.setText(str(userID)) elif checkinStatus == c.SQL_ERROR: QMessageBox.critical( self, "Database Error", "WARNING! Database error: " + sqlError.pgerror, QMessageBox.Ok, QMessageBox.Ok ) # Don't bother to change UI elements or start the sleep thread, just wait for the next card return else: self.checkinImg.setPixmap(self.redPix) if checkinStatus == c.ERROR_READING_CARD: self.checkinLabel.setText("Error reading card. Swipe again.") elif checkinStatus == c.BAD_CHECKIN_TIME: self.checkinLabel.setText("Yoau may only check-in once per hour.") elif checkinStatus == c.FUTURE_CHECKIN_TIME: self.checkinLabel.setText("Previous check-in time was in the future. Check your local system time.") elif checkinStatus == c.CUID_NOT_IN_DB: # If the card is not in the DB ask to add it reply = QMessageBox.question( self, "CUID Not in Database", "This CUID was not found in the database. Add it now?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No, ) if reply == QMessageBox.Yes: # If adding new card, get the userID associated with the card userID, ok = QInputDialog.getText(self, "Add New Card", "User ID:") # Sanitize the userID input and call the add card thread if ok and userID != "": self.addCardThread = AddCardThread( self.db, CUID, self.tools.sanitizeInput(userID), self.postCardSwipe ) self.addCardThread.start() # Don't bother to change UI elements or start the sleep thread, just wait for the next card return else: self.checkinLabel.setText("An unknown error occurred.") QMessageBox.critical(self, "Unknown Error", "An unknown error occurred", QMessageBox.Ok, QMessageBox.Ok) # Force a repaint of the UI self.checkinImg.update() self.checkinLabel.update() # Sleep for a few seconds before resetting the UI for the next card # The number of seconds is defined in the constants file # This must be on a separate thread since blocking the UI thread is a big no-no self.sleepThread.start() def resetCheckinWidget(self): # =========================================================================== # Reset the UI for a new card swipe # =========================================================================== self.checkinImg.setPixmap(self.cardPix) self.checkinLabel.setText("Waiting for card swipe...") self.checkinImg.update() self.checkinLabel.update() def setVisits(self, showVisitsStatus, visitsTuple, sqlError): # =========================================================================== # Depreciated - removing visits setting # =========================================================================== if showVisitsStatus == c.NO_RESULTS: QMessageBox.critical( self, "Empty Query", "The specified user ID was not found in the database", QMessageBox.Ok, QMessageBox.Ok, ) return for i in range(len(visitsTuple)): userID = str(visitsTuple[i][0]) visits = str(visitsTuple[i][1]) self.visitsTextArea.append(userID + "\t" + visits) # Move the scrollbar to the top scrollbar = self.visitsTextArea.verticalScrollBar() scrollbar.setValue(scrollbar.minimum())
class DB: def __init__(self, dbHost, dbDatabase, dbUsersTable, dbVisitsTable, dbUser, dbPass): self.dbConn = None self.dbHost = dbHost self.dbDatabase = dbDatabase self.dbUsersTable = dbUsersTable self.dbVisitsTable = dbVisitsTable self.dbUser = dbUser self.dbPass = dbPass self.tools = Utils() def connect(self): #=========================================================================== # Connect to db with given info - need to fix error system #=========================================================================== # If a password was not given, ask for it if self.dbPass == "": self.dbPass = getDbPass() try: # Connect to the database server self.dbConn = psycopg2.connect(database=self.dbDatabase, user=self.dbUser, password=self.dbPass, host=self.dbHost) return c.SUCCESS except psycopg2.Error as e: #if "user denied" in e.args[1]: # Bad password error print("\n", e) #if "password authentication" in e: # return c.BAD_PASSWD #else: # Other error #return c.FAILURE def close(self): #=========================================================================== # Close out db connection #=========================================================================== if self.dbConn is not None: self.dbConn.close() def addCard(self, cuid, firstName, lastName, email): #=========================================================================== # add a CUID and userID to the database #=========================================================================== # Init some stuff that could cause problems if not initialized sqlError = None # Get a cursor to the DB cursor = self.dbConn.cursor() cuid = self.tools.sanitizeInput(cuid) firstName = self.tools.sanitizeInput(firstName) lastName = self.tools.sanitizeInput(lastName) email = self.tools.sanitizeInput(email) try: cursor.execute("""BEGIN TRANSACTION;""") # Add the new record into the DB cursor.execute( """INSERT INTO %s (%s, %s, %s, %s, %s) values (\'%s\', \'%s\', \'%s\', \'%s\', \'%s\');""" % (self.dbUsersTable, c.CUID_COLUMN_USER, c.FIRST_NAME_COLUMN_USER, c.LAST_NAME_COLUMN_USER, c.EMAIL_COLUMN_USER, c.VISIT_NUM_COLUMN_USER, cuid, firstName, lastName, email, c.DEFAULT_VISITS)) cursor.execute("""END TRANSACTION;""") finally: cursor.close() checkInResult = self.checkIn(cuid) return { "addCardStatus": checkInResult["checkInStatus"], "Name": firstName, "CUID": cuid, "sqlError": sqlError } def checkIn(self, CUID): #=========================================================================== # Check in to db with CUID already in db #=========================================================================== # Init some stuff that could cause problems if not initialized status = c.FAILURE userID = None sqlError = None visitNum = None # Get a cursor to the DB if self.dbConn is not None: cursor = self.dbConn.cursor() else: print("dbConn is None") try: cursor.execute("""BEGIN TRANSACTION;""") # Get the last check-in time cursor.execute( """SELECT last_checkIn FROM %s WHERE CUID=\'%s\';""" % (self.dbUsersTable, CUID)) # Ensure that the card is in the database if cursor.rowcount == 0: status = c.CUID_NOT_IN_DB # Raise a generic exception to break out of the try block raise Exception else: result = cursor.fetchone() # Verify the check-in times if c.ALLOW_CHECKIN_WITHIN_HOUR: status = c.SUCCESS else: status = self.checkCheckInTime(result[0]) if status == c.SUCCESS: # Update the database with the new visits cursor.execute( """UPDATE %s SET %s = \'%s\' WHERE %s = \'%s\';""" % (self.dbUsersTable, c.LAST_CHECKIN_COLUMN_USER, datetime.now(), c.CUID_COLUMN_USER, CUID)) cursor.execute( """UPDATE %s SET %s = %s + 1 WHERE %s = \'%s\';""" % (self.dbUsersTable, c.VISIT_NUM_COLUMN_USER, c.VISIT_NUM_COLUMN_USER, c.CUID_COLUMN_USER, CUID)) cursor.execute( """SELECT %s FROM %s WHERE CUID=\'%s\';""" % (c.VISIT_NUM_COLUMN_USER, self.dbUsersTable, CUID)) visitNum = cursor.fetchone()[0] cursor.execute( """INSERT INTO %s (%s, %s, %s) values (\'%s\', \'%s\', \'%s\');""" % (self.dbVisitsTable, c.CUID_COLUMN_VISIT, c.TIMEIN_COLUMN_VISIT, c.VISIT_NUM_COLUMN_VISIT, CUID, datetime.now(), visitNum)) # Grab the user ID that just checked-in to print confirmation cursor.execute("""SELECT %s FROM %s WHERE CUID=\'%s\';""" % (c.EMAIL_COLUMN_USER, self.dbUsersTable, CUID)) userID = cursor.fetchone()[0] cursor.execute("""END TRANSACTION;""") except psycopg2.Error as e: status = c.SQL_ERROR sqlError = e except Exception as e: print(e) pass finally: cursor.close() return { "checkInStatus": status, "userID": userID, "CUID": CUID, "sqlError": sqlError } def checkCheckInTime(self, lastCheckIn): #=========================================================================== # Verifies that we are not checking into the past or the future #=========================================================================== # Get the current date/time curDate = datetime.now() # The last_checkIn column was added after the DB was initially populated meaning it could be a NoneType # Only check the dates if this is not the case if lastCheckIn and datetime.date(curDate) == datetime.date( lastCheckIn): tmzAdjust = 0 # Check that the current system time is at least one hour greater than the last check-in time if (datetime.time(curDate).hour + tmzAdjust == datetime.time(lastCheckIn).hour or (datetime.time(curDate).hour + tmzAdjust == datetime.time(lastCheckIn).hour + 1 and datetime.time(curDate).minute < datetime.time(lastCheckIn).minute)): return c.BAD_CHECKIN_TIME # If the current system time is before the check-in time, do not allow check-in elif datetime.time(curDate).hour + tmzAdjust < datetime.time( lastCheckIn).hour: return c.FUTURE_CHECKIN_TIME # If the current system date is before the check-in date, do not allow check-in elif lastCheckIn and datetime.date(curDate) < datetime.date( lastCheckIn): return c.FUTURE_CHECKIN_TIME else: return c.SUCCESS def showVisits(self, userID=""): #=========================================================================== # Check visits associated with userID and return value #=========================================================================== # Init result and sqlError result = None sqlError = None # Get a cursor to the DB cursor = self.dbConn.cursor() try: # Either get all user ID's and visits from DB or just one user ID if userID == "": cursor.execute( """SELECT userID, visits FROM %s ORDER BY visits DESC;""" % (self.dbUsersTable)) else: cursor.execute( """SELECT userID, visits FROM %s WHERE userID=\'%s\';""" % (self.dbUsersTable, userID)) # Show error if no results (user ID is not in database) if cursor.rowcount == 0: status = c.NO_RESULTS else: result = cursor.fetchall() status = c.SUCCESS except psycopg2.Error as e: status = c.SQL_ERROR sqlError = e finally: cursor.close() return { "showVisitsStatus": status, "visitsTuple": result, "sqlError": sqlError }
class TextUI: def __init__(self): self.db = None self.tools = Utils() def start(self): #=========================================================================== # Main function - start connection to db then open main menu #=========================================================================== try: while 1: # Get DB info self.getDbInfo() # Create the DB object self.db = DB(self.dbHost, self.dbName, self.dbUsersTable, self.dbVisitsTable, self.dbUser, self.dbPass) # Connect to the database connectStatus = self.connectToDatabase() # If we failed to connect to the database offer to re-enter db info if connectStatus != c.SUCCESS: reenter = input("Failed to connect to database. Re-enter database info? (Y,n) ") if reenter.lower() == "n": print("Bye.") sys.exit(0) else: break # Start the main menu loop self.displayMenu() except KeyboardInterrupt: pass finally: print("Cleaning up and exiting...") if self.db is not None: self.db.close() def displayMenu(self): #=========================================================================== # Display main cli and take appropriate actions #=========================================================================== #print("\nType \"back\" at any time to go up a menu level.") while 1: # Display main menu print("\n\t1.) Check-in\n\t2.) Show Visits\n\t3.) Exit") try: option = input("\n>> ") if option == "1": self.checkIn() elif option == "2": self.showVisits() elif option == "3": sys.exit(0) #elif option == "back" or option == "exit": # exit = input("Exit? (y,N) ") # if exit.lower() == "y": # sys.exit(0) else: self.invalidInput() except ValueError: self.invalidInput() def connectToDatabase(self): #=========================================================================== # Open connection to database and return status #=========================================================================== # Use stdout.write to prevent newline sys.stdout.write("Connecting to database...") # Connect to the DB! status = self.db.connect() if status == c.SUCCESS: print("done.") return status elif status == c.BAD_PASSWD: print("\nError connecting to database: Bad username or password.") return status else: print("\nUnknown Error connecting to database.") return c.FAILURE def checkIn(self): #=========================================================================== # Log card data to db #=========================================================================== # Get and validate the visit value for this check-in # Limited to 500 visits to prevent bad typos """while 1: visitValue = self.tools.sanitizeInput(input("\nVisit Value (" + str(c.DEFAULT_VISITS) + "): ")) # Validate visit input if visitValue == "": visitValue = str(c.DEFAULT_VISITS) break elif (visitValue.isdigit() and int(visitValue) <= 500) or visitValue == "back": break else: print("Invalid input. Try again.")""" while 1: CUID = self.tools.getCardSwipe() # If the user requested to exit the loop, break if CUID == c.BACK: break elif CUID == c.ERROR_READING_CARD: print("Error reading card. Swipe card again.") continue # Sanitize CUID CUID = self.tools.sanitizeInput(CUID) # CUID will be empty if it failed sanitization. Skip checkIn if that is the case if CUID == "": continue # Do the checkIn checkInResult = self.db.checkIn(CUID) if checkInResult["checkInStatus"] == c.SQL_ERROR: self.showDatabaseError(checkInResult["sqlError"]) elif checkInResult["checkInStatus"] == c.BAD_CHECKIN_TIME: print("Error: You may only check-in once per hour.") elif checkInResult["checkInStatus"] == c.FUTURE_CHECKIN_TIME: print("Error: Previous check-in time was in the future. Check your local system time.") elif checkInResult["checkInStatus"] == c.CUID_NOT_IN_DB: # Ask if user wants to add the card addCard = input("Error: Card not found in database. Add it now? (Y,n) ") if addCard == "n": continue # Get the userID for the new card firstName = self.tools.sanitizeInput(input("First Name: ")) lastName = self.tools.sanitizeInput(input("Last Name: ")) email = self.tools.sanitizeInput(input("Clemson Username: "******"addCardStatus"] == c.SUCCESS: self.showCheckinConfirmation(email) elif addCardResult["addCardStatus"] == c.SQL_ERROR: self.showDatabaseError(addCardResult["sqlError"]) elif checkInResult["checkInStatus"] == c.SUCCESS: self.showCheckinConfirmation(checkInResult["userID"]) else: print("Unknown error checking in.") def showVisits(self): #=========================================================================== # Poll db for visit data based on userID #=========================================================================== userID = self.tools.sanitizeInput(input("\nUser ID (blank for all): ")) showVisitsResult = self.db.showVisits(userID) if showVisitsResult["showVisitsStatus"] == c.SQL_ERROR: self.showDatabaseError(showVisitsResult["sqlError"]) elif showVisitsResult["showVisitsStatus"] == c.NO_RESULTS: print("\nThere were no results to that query.") elif showVisitsResult["showVisitsStatus"] == c.SUCCESS: # If showing all users, display a pretty table if userID == "": print("\n+--------------------+\n| User ID | Visits |\n+--------------------+") for i in range(len(showVisitsResult["visitsTuple"])): print("|%10s | %6s |" % (showVisitsResult["visitsTuple"][i][0], showVisitsResult["visitsTuple"][i][1])) print("+--------------------+") # Show a single user's visits else: print("\n%s has %s visits." % (userID, str(showVisitsResult["visitsTuple"][0][0]))) def getDbInfo(self): #=========================================================================== # Request dbInfo from user - suggest default info from constants #=========================================================================== self.dbName = input("Database name: (" + c.DEFAULT_DATABASE + ") ") if self.dbName == "": self.dbName = c.DEFAULT_DATABASE self.dbHost = input("Database host: (" + c.DEFAULT_HOST + ") ") if self.dbHost == "": self.dbHost = c.DEFAULT_HOST self.dbUsersTable = input("Database User table: (" + c.TABLE_USERS + ") ") if self.dbUsersTable == "": self.dbUsersTable = c.TABLE_USERS self.dbVisitsTable = input("Database Visits table: (" + c.TABLE_VISITS + ") ") if self.dbVisitsTable == "": self.dbVisitsTable = c.TABLE_VISITS self.dbUser = input("Database Username: (" + c.DEFAULT_USER + ") ") if self.dbUser == "": self.dbUser = c.DEFAULT_USER while 1: self.dbPass = getpass.getpass("Database Password: "******"": print("Database password cannot be blank.") else: break def showCheckinConfirmation(self, userID): #=========================================================================== # show confirmation of successful check in #=========================================================================== print("\n%s is checked in" % (userID)) def showDatabaseError(self, error): #=========================================================================== # Warn of database error - and provide information #=========================================================================== print("\nWARNING! Database error:\n%s" % error.pgerror) def invalidInput(self): #=========================================================================== # Complain about invalid input #=========================================================================== print("Invalid option. Try again.")
class MainWnd(QMainWindow): def __init__(self, db): super(MainWnd, self).__init__() self.db = db self.tools = Utils() # Init card input so it can be appended to later self.cardInput = "" # Compile the regex for pulling the card ID from all the data on a card self.regex = re.compile("%(.+)..\?;") # Declare sleepThread self.sleepThread = SleepThread(c.TIME_BETWEEN_CHECKINS, self.resetCheckinWidget) self.initUI() def initUI(self): #=========================================================================== # init main ui - allow for attendance logging and checking #=========================================================================== # Center the window # setGeometry args are x, y, width, height self.setGeometry(0, 0, 550, 100) geo = self.frameGeometry() centerPt = QDesktopWidget().availableGeometry().center() geo.moveCenter(centerPt) self.move(geo.topLeft()) # Title, icon, and statusbar self.setWindowTitle(c.GROUP_INITIALS + " Attendance") self.setWindowIcon(QIcon(os.path.abspath("images/login_logo.png"))) self.statusBar().showMessage("Connected to server | " + c.GROUP_NAME + " Attendance Tracker Version " + str(c.VERSION)) # Init all the central widgets self.initMainMenuWidget() self.initCheckinWidget() self.initShowVisitsWidget() # Init the central stacked widget and set it as the central widget # This allows us to change the central widget easily self.centralWidget = QStackedWidget() self.setCentralWidget(self.centralWidget) # Add the widgets to the main central stacked widget self.centralWidget.addWidget(self.mainMenuWidget) self.centralWidget.addWidget(self.checkinWidget) self.centralWidget.addWidget(self.visitsWidget) # Show the main menu first self.showMainMenuWidget() def keyPressEvent(self, event): #=========================================================================== # Collect key data until regex matches - then reset and collect again #=========================================================================== # Only look for card swipes if the checkin widget is currently shown if self.centralWidget.currentWidget() == self.checkinWidget: try: # Try to match the input to the card ID regex r = self.regex.search(self.cardInput) CUID = r.groups()[0] # A match was made so reset cardInput for the next card self.cardInput = "" # Set the card ID and start the checkin thread # CUID is going into an SQL query; don't forget to sanitize the input if not (self.checkinThread.isRunning() and self.sleepThread.isRunning()): #self.checkinThread.setCUID(Utils.sanitizeInput(str(CUID))) self.checkinThread.setCUID(CUID) self.checkinThread.start() except AttributeError: # If a match was not made append the current text to card input self.cardInput += event.text() def closeEvent(self, closeEvent): #=========================================================================== # Close database connection prior to closing application #=========================================================================== print("Cleaning up and exiting...") if self.db is not None: self.db.close() closeEvent.accept() def initMainMenuWidget(self): #=========================================================================== # Initialize Main menu Widget #=========================================================================== self.mainMenuWidget = QWidget() checkinButton = QImageButton( "Check-in", os.path.abspath('images/magnetic_card.png'), self.showCheckinWidget, 100, self) showVisitsButton = QImageButton("Show Visits", os.path.abspath('images/trophy.png'), self.showVisitsWidget, 100, self) hbox = QHBoxLayout() hbox.addStretch(1) hbox.addWidget(checkinButton) hbox.addSpacing(45) hbox.addWidget(showVisitsButton) hbox.addStretch(1) self.mainMenuWidget.setLayout(hbox) def initCheckinWidget(self): #=========================================================================== # Initialize Check In widget #=========================================================================== self.checkinWidget = QWidget() # Init widgets self.cardPix = QPixmap(os.path.abspath("images/magnetic_card.png")) self.greenPix = QPixmap(os.path.abspath("images/green_check_mark.png")) self.redPix = QPixmap(os.path.abspath("images/red_x_mark.png")) self.checkinImg = QLabel(self) self.checkinLabel = QLabel("Waiting for card swipe...") self.checkinBackBtn = QPushButton("Back", self) # Size the images properly self.cardPix = self.cardPix.scaledToHeight(175, Qt.SmoothTransformation) self.greenPix = self.greenPix.scaledToHeight(175, Qt.SmoothTransformation) self.redPix = self.redPix.scaledToHeight(175, Qt.SmoothTransformation) # Add the card image to image widget self.checkinImg.setPixmap(self.cardPix) # Set the font for the checkin label font = QFont("Sans Serif", 16, QFont.Bold) self.checkinLabel.setFont(font) # Add signals to buttons self.checkinBackBtn.clicked.connect(self.closeCheckinScreen) # Center the image imgHbox = QHBoxLayout() imgHbox.addStretch(1) imgHbox.addWidget(self.checkinImg) imgHbox.addStretch(1) # Add widgets to vbox layout for vertical centering vbox = QVBoxLayout() vbox.addStretch(1) vbox.addLayout(imgHbox) vbox.addWidget(self.checkinLabel) vbox.addWidget(self.checkinBackBtn) vbox.addStretch(1) # Add grid to the hbox layout for horizontal centering hbox = QHBoxLayout() hbox.addStretch(1) hbox.addLayout(vbox) hbox.addStretch(1) # Add the completeted layout to the overall check-in widget self.checkinWidget.setLayout(hbox) def initShowVisitsWidget(self): #=========================================================================== # Initialize visits widget #=========================================================================== self.visitsWidget = QWidget() self.checkinThread = None # Init widgets self.visitsTitle = QLabel("Current visits Standings") self.visitsTextArea = QTextEdit() self.visitsBackBtn = QPushButton("Back", self) # Set the font for the checkin label self.visitsTitle.setFont(QFont("Sans Serif", 12, QFont.Bold)) # Add signals to buttons self.visitsBackBtn.clicked.connect(self.closeShowVisitsScreen) # Create the layout for the visits scroll area self.visitsTextArea.setFont(QFont("Monospace", 8, QFont.Normal)) # Add widgets to vbox layout for vertical centering vbox = QVBoxLayout() vbox.addStretch(1) vbox.addWidget(self.visitsTitle, alignment=Qt.AlignCenter) vbox.addWidget(self.visitsTextArea) vbox.addWidget(self.visitsBackBtn) vbox.addStretch(1) self.visitsWidget.setLayout(vbox) def showMainMenuWidget(self): #=========================================================================== # Show main menu widget #=========================================================================== self.centralWidget.setCurrentWidget(self.mainMenuWidget) def showCheckinWidget(self): #=========================================================================== # Show Checkin Widget - used to request point value that is now depreciated #=========================================================================== self.centralWidget.setCurrentWidget(self.checkinWidget) """# Get the visit value while 1: visitValue, ok = QInputDialog.getText(self, "Visit Value", "Visit Value:", text=str(c.DEFAULT_VISITS)) if ok: if str(visitValue).isdigit(): break else: QMessageBox.critical(self, "Input Error", "Invalid input", QMessageBox.Ok, QMessageBox.Ok) else: self.closeCheckinScreen() return""" # Init the checkin thread # visitValue will be used in SQL queries. Sanitize it. self.checkinThread = CheckinThread(self.db, self.postCardSwipe) def showVisitsWidget(self): #=========================================================================== # Show visits for certain CUID #=========================================================================== self.visitsTextArea.clear() self.centralWidget.setCurrentWidget(self.visitsWidget) # Get the user ID to show visits for or an empty string for all user ID's CUID, ok = QInputDialog.getText(self, "CUID", "CUID (blank for all CUID\'s):") if not ok: # The show visits thread was not declared yet so just skip the closeShowvisitsScreen function self.showMainMenuWidget() # Init the show visits thread # userID will be used in SQL queries. Sanitize it. self.showVisitsThread = ShowVisitsThread( self.db, self.tools.sanitizeInput(str(CUID)), self.setVisits) self.showVisitsThread.start() def closeCheckinScreen(self): #=========================================================================== # Close checkinThread and return to Main Menu #=========================================================================== if self.checkinThread is not None: self.checkinThread.terminate() self.showMainMenuWidget() def closeShowVisitsScreen(self): #======================================================================= # Close visits thread and return to main menu #======================================================================= if self.checkinThread is not None: self.showVisitsThread.terminate() self.showMainMenuWidget() def postCardSwipe(self, checkinStatus, userID, CUID, sqlError): #=========================================================================== # Display results after a card is read - If new card request name #=========================================================================== if checkinStatus == c.SUCCESS: self.checkinImg.setPixmap(self.greenPix) self.checkinLabel.setText(str(userID)) elif checkinStatus == c.SQL_ERROR: QMessageBox.critical( self, "Database Error", "WARNING! Database error: " + sqlError.pgerror, QMessageBox.Ok, QMessageBox.Ok) # Don't bother to change UI elements or start the sleep thread, just wait for the next card return else: self.checkinImg.setPixmap(self.redPix) if checkinStatus == c.ERROR_READING_CARD: self.checkinLabel.setText("Error reading card. Swipe again.") elif checkinStatus == c.BAD_CHECKIN_TIME: self.checkinLabel.setText( "Yoau may only check-in once per hour.") elif checkinStatus == c.FUTURE_CHECKIN_TIME: self.checkinLabel.setText( "Previous check-in time was in the future. Check your local system time." ) elif checkinStatus == c.CUID_NOT_IN_DB: # If the card is not in the DB ask to add it reply = QMessageBox.question( self, "CUID Not in Database", "This CUID was not found in the database. Add it now?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: # If adding new card, get the userID associated with the card userID, ok = QInputDialog.getText(self, "Add New Card", "User ID:") # Sanitize the userID input and call the add card thread if ok and userID != "": self.addCardThread = AddCardThread( self.db, CUID, self.tools.sanitizeInput(userID), self.postCardSwipe) self.addCardThread.start() # Don't bother to change UI elements or start the sleep thread, just wait for the next card return else: self.checkinLabel.setText("An unknown error occurred.") QMessageBox.critical(self, "Unknown Error", "An unknown error occurred", QMessageBox.Ok, QMessageBox.Ok) # Force a repaint of the UI self.checkinImg.update() self.checkinLabel.update() # Sleep for a few seconds before resetting the UI for the next card # The number of seconds is defined in the constants file # This must be on a separate thread since blocking the UI thread is a big no-no self.sleepThread.start() def resetCheckinWidget(self): #=========================================================================== # Reset the UI for a new card swipe #=========================================================================== self.checkinImg.setPixmap(self.cardPix) self.checkinLabel.setText("Waiting for card swipe...") self.checkinImg.update() self.checkinLabel.update() def setVisits(self, showVisitsStatus, visitsTuple, sqlError): #=========================================================================== # Depreciated - removing visits setting #=========================================================================== if showVisitsStatus == c.NO_RESULTS: QMessageBox.critical( self, "Empty Query", "The specified user ID was not found in the database", QMessageBox.Ok, QMessageBox.Ok) return for i in range(len(visitsTuple)): userID = str(visitsTuple[i][0]) visits = str(visitsTuple[i][1]) self.visitsTextArea.append(userID + "\t" + visits) # Move the scrollbar to the top scrollbar = self.visitsTextArea.verticalScrollBar() scrollbar.setValue(scrollbar.minimum())