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()
Beispiel #2
0
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()