class WizardAddJournal(QtWidgets.QDialog): def __init__(self, parent=None): super(WizardAddJournal, self).__init__(parent) self.TIMEOUT = 60 self.setModal(True) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.parent = parent self.resource_dir, self.DATA_PATH = functions.getRightDirs() if parent is None: self.l = MyLog("activity.log") # Dummy file for saving if testing self.options = QtCore.QSettings("debug/options.ini", QtCore.QSettings.IniFormat) self.test = True else: self.l = self.parent.l self.options = self.parent.options self.test = False self.initUI() self.defineSlots() def defineSlots(self): """Establish the slots""" # When clicking OK, verify the user's input self.ok_button.clicked.connect(self.verifyInput) # Display help self.help_button.clicked.connect(self.showHelp) def _checkIsFeed(self, url: str, company: str, feed: feedparser.util.FeedParserDict) -> bool: self.l.debug("Entering _checkIsFeed") # Check if the feed has a title try: journal = feed['feed']['title'] except Exception as e: self.l.critical("verifyInput, can't access title {}".format(url), exc_info=True) return False nbr_ok = 0 for entry in feed.entries: try: doi = hosts.getDoi(company, journal, entry) url = hosts.refineUrl(company, journal, entry) self.l.debug("{}, {}".format(doi, url)) except Exception as e: self.l.error( "verifyInput, entry has no doi or no url".format(url), exc_info=True) continue # Check if DOI and URL can be obtained if (doi.startswith('10.') and validators.url(url) or validators.url(doi) and validators.url(url)): nbr_ok += 1 # If 3 entries are OK, the feed is considered valid if nbr_ok == 3: self.l.debug("3 entries ok, valid feed") return True # If still here, the feed is NOT considered valid return False def _getFeed(self, url: str, timeout: int) -> feedparser.util.FeedParserDict: self.l.debug("Entering _getFeed") # Try to dl the RSS feed page try: # Get the RSS page of the url provided feed = feedparser.parse(url, timeout=timeout) self.l.debug("Add journal, RSS page successfully dled") return feed except Exception as e: self.l.error( "Add journal feed {} could not be downloaded: {}".format( url, e), exc_info=True) return None def verifyInput(self): """Verify the input. Dl the RSS page and check it belongs to a journal. Then, call the method to save the journal""" abb = self.line_abbreviation.text().strip() url = self.line_url_journal.text().strip() company = self.combo_publishers.currentText() self.l.debug("Starting verifyInput: {}, {}, {}".format( abb, url, company)) feed = self._getFeed(url, timeout=self.TIMEOUT) if feed is None: self.l.critical("verifyInput, feed is None") # Create error message if RSS page can't be downloaded error_mes = "An error occured while downloading the RSS page.\ Are you sure you have the right URL ?\ Try again later, maybe ?" error_mes = error_mes.replace(" ", "") QtWidgets.QMessageBox.critical( self, "Error while adding new journal", error_mes, QtWidgets.QMessageBox.Ok, defaultButton=QtWidgets.QMessageBox.Ok) is_feed = self._checkIsFeed(url, company, feed) if not is_feed: self.l.critical("verifyInput, not a valid feed") # Create error message if RSS page can't be downloaded error_mes = "The URL you provided does not match a valid RSS feed.\ Are you sure you have the right URL ?" error_mes = error_mes.replace(" ", "") QtWidgets.QMessageBox.critical( self, "Error while adding new journal", error_mes, QtWidgets.QMessageBox.Ok, defaultButton=QtWidgets.QMessageBox.Ok) return title = feed['feed']['title'] mes = "The following journal will be added to your selection:\n{}" mes = mes.format(title) self.l.debug("New journal {} about to be added".format(title)) # Confirmation dialog box choice = QtWidgets.QMessageBox.information( self, "Verification", mes, QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Ok, defaultButton=QtWidgets.QMessageBox.Cancel) if choice == QtWidgets.QMessageBox.Cancel: return else: self.l.debug("Try to save the new journal") self.saveJournal(title, abb, url, company) def showHelp(self): """Help displayed in a dialog box to help the user when adding a new journal""" mes = """Define the abbreviation of the journal you want to add.\n\n\ Find the URL of the RSS page of the journal you want to add.\n\n\ Publisher: to which publisher does the new journal belong ? This\ choice will help ChemBrows to format the articles.\n\ NOTE: Nature2 is for journals with a RSS feed formatted like Sci. Rep. """ # Clean the tabs in the message (tabs are 4 spaces) mes = mes.replace(" ", "") QtWidgets.QMessageBox.information(self, "Information", mes, QtWidgets.QMessageBox.Ok) def initUI(self): """Handles the display""" self.setWindowTitle('Adding new journal') # Open a dialog box to explain how to add a journal self.help_button = QtWidgets.QPushButton("Help") # Validate. Triggers verification process self.ok_button = QtWidgets.QPushButton("Add journal") self.form_layout = QtWidgets.QFormLayout() self.line_abbreviation = QtWidgets.QLineEdit() self.line_abbreviation.setPlaceholderText("Ex: Chem. Commun.") self.line_url_journal = QtWidgets.QLineEdit() self.line_url_journal.setPlaceholderText("http://feeds.rsc.org/rss/cc") list_publishers = sorted(hosts.getCompanies()) self.combo_publishers = QtWidgets.QComboBox() self.combo_publishers.addItems(list_publishers) self.form_layout.addRow(self.help_button) self.form_layout.addRow("Journal abbreviation:", self.line_abbreviation) self.form_layout.addRow("URL RSS page:", self.line_url_journal) self.form_layout.addRow("Publisher:", self.combo_publishers) # ------------------------ ASSEMBLING ----------------------------------------- self.vbox_global = QtWidgets.QVBoxLayout() self.vbox_global.addLayout(self.form_layout) self.vbox_global.addWidget(self.ok_button) self.setLayout(self.vbox_global) self.show() def saveJournal(self, title, abb, url, company): """Will save the new journal, in file company.ini located in the user directory""" mes = "Journal already in the catalog" # Check if the RSS page's URL is not present in any company file for company in hosts.getCompanies(): data_company = hosts.getJournals(company) # If URL already present, display error dialog box if url in data_company[2]: QtWidgets.QMessageBox.critical(self, "Error", mes, QtWidgets.QMessageBox.Ok) self.l.debug("URL {} already in catalog".format(url)) return try: # If still here, write the new journal with open(os.path.join(self.DATA_PATH, "journals/{}.ini".format(company)), 'a', encoding='utf-8') as out: out.write("{} : {} : {}".format(title, abb, url)) self.l.debug("New journal written user side") self.l.debug("{} : {} : {}".format(title, abb, url)) self.l.info("{} added to the catalog".format(title)) except Exception as e: self.l.error("saveJournal, error writing journal: {}".format(e), exc_info=True) return # Refresh parent check boxes and close if self.parent is not None: self.parent.displayJournals() self.parent.saveSettings() self.close()
class Signing(QtWidgets.QDialog): """Module to log the user and assign to him a user_id the first time he starts the programm""" def __init__(self, parent=None): super(Signing, self).__init__(parent) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.parent = parent self.resource_dir, self.DATA_PATH = functions.getRightDirs() if parent is None: self.logger = MyLog("activity.log") self.test = True else: self.logger = self.parent.l self.test = False # Attribute to check if the login was valid self.validated = False self.setModal(True) self.initUI() self.defineSlots() self.getCaptcha() def closeEvent(self, event): """Actions to perform when closing the window. Exit ChemBrows if the user closes this window""" self.parent.close() # Close the app if the user does not signin if not self.validated and not self.test: self.logger.critical("The user did not sign in") self.parent.closeEvent(event) def showInfo(self): """Open a dialog info box to tell the user what we are using his email for""" mes = """ Your email address will ONLY be used to provide you with \ important news about ChemBrows (ex: updates) """.replace(' ', '') # Clean the tabs in the message (tabs are 4 spaces) mes = mes.replace(" ", "") QtWidgets.QMessageBox.information(self, "Information", mes, QtWidgets.QMessageBox.Ok) def getCaptcha(self): # r = requests.get("http://127.0.0.1:8000/cgi-bin/cap.py") # remove b'' of the str representation of the bytes # only for local server # self.captcha_id = r.text.split('\n')[0][2:][:-1] # text = r.text.split('\n')[1][2:][:-1] r = requests.get("http://chembrows.com/cgi-bin/cap.py") self.captcha_id = r.text.split('\n')[0] text = r.text.split('\n')[1] io = BytesIO(base64.b64decode(text)) Image.open(io).save(os.path.join(self.DATA_PATH, 'captcha.png'), format='PNG') image = QtGui.QPixmap(os.path.join(self.DATA_PATH, 'captcha.png')) self.label_image.setPixmap(image) def defineSlots(self): """Establish the slots""" # If OK pressed, send a request to the server self.ok_button.clicked.connect(self.validateForm) # If Cancel is pressed, terminate the program. The user can't # use it if he's not logged self.cancel_button.clicked.connect(self.closeEvent) def validateForm(self): """Slot to validate the infos. First, check them locally and then send them""" # http://sametmax.com/valider-une-adresse-email-avec-une-regex-en-python/ validate = True email_re = re.compile( r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE) if email_re.search(self.line_email.text()) is None: self.line_email.setStyleSheet('QLineEdit \ {background-color: #FFA07A}') validate = False else: self.line_email.setStyleSheet(None) if self.combo_status.currentIndex() == 0: self.combo_status.setStyleSheet('QComboBox \ {background-color: #FFA07A}') validate = False else: self.combo_status.setStyleSheet(None) if validate: payload = { 'status': self.combo_status.currentIndex(), 'email': self.line_email.text(), 'user_input': self.line_captcha.text(), 'captcha_id': self.captcha_id, 'platform': platform.platform(), } try: r = requests.post("http://chembrows.com/cgi-bin/sign.py", data=payload, timeout=20) # r = requests.post("http://127.0.0.1:8000/cgi-bin/sign.py", # data=payload) except (requests.exceptions.ReadTimeout, requests.exceptions.Timeout): mes = """ A time out error occured while contacting the server. \ Please check you are connected to the internet, or contact us. """.replace(' ', '') QtWidgets.QMessageBox.critical( self, "Signing up error", mes, QtWidgets.QMessageBox.Ok, defaultButton=QtWidgets.QMessageBox.Ok) self.logger.critical("ReadTimeout while signing up") return except requests.exceptions.ConnectionError: mes = """ An error occured while contacting the server. Please check \ you are connected to the internet, or contact us. """.replace(' ', '') QtWidgets.QMessageBox.critical( self, "Signing up error", mes, QtWidgets.QMessageBox.Ok, defaultButton=QtWidgets.QMessageBox.Ok) self.logger.critical("ConnectionError while signing up") return except Exception as e: mes = """ An unknown error occured while contacting the server. Please \ check you are connected to the internet, or contact us. {} """.replace(' ', '').format(e) QtWidgets.QMessageBox.critical( self, "Signing up error", mes, QtWidgets.QMessageBox.Ok, defaultButton=QtWidgets.QMessageBox.Ok) self.logger.critical("validateForm: {}".format(e), exc_info=True) return # Get the response from the server and log it self.logger.debug("Response from the server: {}".format(r.text)) response = [part for part in r.text.split("\n") if part != ''] # The server responded an user_id if 'user_id' in response[-1]: if not self.test: self.parent.options.setValue('user_id', response[-1].split(':')[-1]) self.accept() self.validated = True # Delete the captcha file os.remove(os.path.join(self.DATA_PATH, "captcha.png")) # user_id already in db on the server elif response[-1] == 'A user with this email already exists': mes = """ A user with the same email already exists. Please use another email.""".replace(' ', '') QtWidgets.QMessageBox.critical( self, "Signing up error", mes, QtWidgets.QMessageBox.Ok, defaultButton=QtWidgets.QMessageBox.Ok) self.line_email.setStyleSheet('QLineEdit \ {background-color: #FFA07A}') # The server says the captcha is incorrect elif response[-1] == 'Wrong captcha': mes = """ The input for the captcha is incorrect. Please try again. """.replace(' ', '') QtWidgets.QMessageBox.critical( self, "Signing up error", mes, QtWidgets.QMessageBox.Ok, defaultButton=QtWidgets.QMessageBox.Ok) self.line_captcha.setStyleSheet('QLineEdit \ {background-color: #FFA07A}') # Unhandled response from the server else: mes = """ Unknown error. Please retry and/or contact us. """.replace(' ', '') QtWidgets.QMessageBox.critical( self, "Signing up error", mes, QtWidgets.QMessageBox.Ok, defaultButton=QtWidgets.QMessageBox.Ok) def initUI(self): """Handles the display""" self.combo_status = QtWidgets.QComboBox() self.combo_status.addItem(None) self.combo_status.addItem("Student") self.combo_status.addItem("PhD student") self.combo_status.addItem("Post doc") self.combo_status.addItem("Researcher") self.combo_status.addItem("Professor") self.combo_status.addItem("Obi Wan Kenobi") self.form_sign = QtWidgets.QFormLayout() self.form_sign.addRow("Who are you? :", self.combo_status) # LineEdit for the email, with an icon opening an info box # info box about data privacy self.line_email = ButtonLineIcon( os.path.join(self.resource_dir, 'images/info')) self.line_email.buttonClicked.connect(self.showInfo) self.form_sign.addRow("Email :", self.line_email) # Label image for the captcha self.label_image = QtWidgets.QLabel() self.label_image.setAlignment(QtCore.Qt.AlignHCenter) self.form_sign.addRow(None, self.label_image) self.line_captcha = QtWidgets.QLineEdit() self.line_captcha.setPlaceholderText("I'm not a robot !") self.form_sign.addRow("Enter the captcha :", self.line_captcha) self.ok_button = QtWidgets.QPushButton("OK", self) self.cancel_button = QtWidgets.QPushButton("Cancel", self) self.hbox_buttons = QtWidgets.QHBoxLayout() self.vbox_global = QtWidgets.QVBoxLayout() self.hbox_buttons.addWidget(self.cancel_button) self.hbox_buttons.addWidget(self.ok_button) self.vbox_global.addLayout(self.form_sign) self.vbox_global.addLayout(self.hbox_buttons) self.setLayout(self.vbox_global) self.show()