Beispiel #1
0
import datetime
from pprint import pprint

import hosts
from log import MyLog

LENGTH_SAMPLE = 3

HEADERS = {
    'User-agent':
    'Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0',
    'Connection': 'close'
}

l = MyLog("output_tests_hosts.log", mode='w')
l.debug("---------------------- START NEW RUN OF TESTS ----------------------")


def logAssert(test, msg):
    """Function to log the result of an assert
    http://stackoverflow.com/questions/24892396/py-test-logging-messages-and-test-results-assertions-into-a-single-file
    """

    if not test:
        l.error(msg)
        assert test, msg


def test_reject():
    """Test each entry in a sample of rejectable articles"""
Beispiel #2
0
class WizardDelJournal(QtWidgets.QDialog):
    """Simple wizard to help the user to delete the journals he added"""
    def __init__(self, parent=None):

        super(WizardDelJournal, self).__init__(parent)

        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

        # Store the checkboxes of the window
        self.check_journals = []

        self.initUI()
        self.defineSlots()

    def defineSlots(self):
        """Establish the slots"""

        # Checkbox to select/unselect all the journals
        self.box_select_all.stateChanged.connect(self.selectUnselectAll)

        # Confirm deleting journals
        self.button_del.clicked.connect(self.confirmDelete)

    def selectUnselectAll(self, state):
        """Select or unselect all the journals"""

        for box in self.check_journals:
            box.setCheckState(state)

    def initUI(self):
        """Handle the display"""

        self.setWindowTitle('Deleting journals')

        self.vbox_global = QtWidgets.QVBoxLayout()

        # Open a dialog box to explain how to add a journal
        mes = "Confirmation will be asked before\nanything permanent is done: no worries"

        self.label_help = QtWidgets.QLabel(mes)
        self.vbox_global.addWidget(self.label_help)

        # Scroll area for the journals to check
        self.scroll_check_journals = QtWidgets.QScrollArea()
        self.scrolling_check_journals = QtWidgets.QWidget()
        self.vbox_check_journals = QtWidgets.QVBoxLayout()
        self.scrolling_check_journals.setLayout(self.vbox_check_journals)

        labels_checkboxes = []

        # Get labels of the future check boxes of the journals to be parsed
        # Only journals on user's side
        for company in hosts.getCompanies(user=True):
            labels_checkboxes += hosts.getJournals(company, user=True)[1]

        labels_checkboxes.sort()

        self.box_select_all = QtWidgets.QCheckBox("Select all")
        self.box_select_all.setCheckState(0)
        self.vbox_check_journals.addWidget(self.box_select_all)

        # Build the checkboxes, and put them in a layout
        for label in labels_checkboxes:
            check_box = QtWidgets.QCheckBox(label)
            check_box.setCheckState(0)
            self.check_journals.append(check_box)
            self.vbox_check_journals.addWidget(check_box)

        self.scroll_check_journals.setWidget(self.scrolling_check_journals)

        self.vbox_global.addWidget(self.scroll_check_journals)

        # Validate. Triggers verification process
        self.button_del = QtWidgets.QPushButton("Delete journal(s)")
        self.vbox_global.addWidget(self.button_del)

        self.setLayout(self.vbox_global)
        self.show()

    def confirmDelete(self):
        """Delete the journals selected by the user"""

        # Build a list of abb for the journals to delete
        j_to_del = []
        for box in self.check_journals:
            if box.checkState() == 2:
                j_to_del.append(box.text())

        if not j_to_del:
            return

        mes = """The selected journals will be deleted from the catalog. The 
        corresponding data will not be removed from your database until you 
        clean it (Settings > Database > Clean database"""

        # Clean the tabs in the message (tabs are 4 spaces)
        mes = mes.replace("    ", "")
        mes = mes.replace("\n", "")

        # Confirmation dialog box
        choice = QtWidgets.QMessageBox.information(
            self,
            "Confirm",
            mes,
            QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Ok,
            defaultButton=QtWidgets.QMessageBox.Cancel)

        if choice == QtWidgets.QMessageBox.Cancel:
            return
        else:
            self.l.debug(
                "User confirmed deleting journals {}".format(j_to_del))

        # For each company, open the ini file and check that each journal
        # IS NOT a journal to delete, then rewrite the file
        for company in os.listdir(os.path.join(self.DATA_PATH, 'journals')):

            with open(os.path.join(self.DATA_PATH, 'journals', company),
                      'r',
                      encoding='utf-8') as config:

                lines = config.readlines()

            lines = [l for l in lines if not any(j in l for j in j_to_del)]

            with open(os.path.join(self.DATA_PATH, 'journals', company),
                      'w',
                      encoding='utf-8') as config:

                for line in lines:
                    config.write(line)

        # Refresh parent check boxes and close
        if self.parent is not None:
            self.parent.displayJournals()
            self.parent.saveSettings()

        self.close()
Beispiel #3
0
class AdvancedSearch(QtWidgets.QDialog):
    """Class to perform advanced searches"""
    def __init__(self, parent=None):

        super(AdvancedSearch, self).__init__(parent)

        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        self.parent = parent

        self.resource_dir, DATA_PATH = functions.getRightDirs()

        # Condition to use a specific logger if
        # module started in standalone
        if parent is None:
            self.logger = MyLog("activity.log")
            self.test = True
        else:
            self.logger = self.parent.l
            self.test = False

        self.options = QtCore.QSettings(DATA_PATH + "/config/searches.ini",
                                        QtCore.QSettings.IniFormat)

        # List to store the lineEdit, with the value of
        # the search fields
        self.fields_list = []

        self.initUI()
        self.defineSlots()
        self.restoreSettings()

    def restoreSettings(self):
        """Restore the right number of tabs"""

        for query in self.options.childGroups():
            # Don't create a search tab for the to read list
            if query == "ToRead":
                continue
            self.tabs.addTab(self.createForm(), query)

        # Try to restore the geometry of the AdvancedSearch window
        try:
            self.restoreGeometry(self.options.value("window_geometry"))
        except TypeError:
            self.logger.debug(
                "Can't restore window geometry for AdvancedSearch")

    def closeEvent(self, event):
        """Actions to perform when closing the window.
        Mainly saves the window geometry"""

        self.logger.debug("Saving windows state for AdvancedSearch")
        self.options.setValue("window_geometry", self.saveGeometry())

        super(AdvancedSearch, self).closeEvent(event)

    def defineSlots(self):
        """Establish the slots"""

        self.button_search_and_save.clicked.connect(self.saveSearch)

        self.tabs.currentChanged.connect(self.tabChanged)

        self.button_delete_search.clicked.connect(self.deleteSearch)

        self.destroyed.connect(self.closeEvent)

    def deleteSearch(self):
        """Slot to delete a query"""

        # Get the title of the search, get the group with
        # the same name in searches.ini, and clear the group
        tab_title = self.tabs.tabText(self.tabs.currentIndex())
        self.options.beginGroup(tab_title)
        # Re-initialize the keys
        self.options.remove("")
        self.options.endGroup()

        self.tabs.removeTab(self.tabs.currentIndex())

        if not self.test:
            for index in range(self.parent.onglets.count()):
                if self.parent.onglets.tabText(index) == tab_title:
                    self.parent.list_tables_in_tabs.remove(
                        self.parent.onglets.widget(index))
                    self.parent.onglets.removeTab(index)
                    self.parent.onglets.setCurrentIndex(0)
                    break

    def buildSearch(self):
        """Build the query"""

        # Get all the lineEdit from the current tab
        lines = self.tabs.currentWidget().findChildren(QtWidgets.QLineEdit)
        radios = self.tabs.currentWidget().findChildren(QtWidgets.QRadioButton)

        # Clean the fields of tailing comma
        topic_entries = [
            line.text()[:-1]
            if line.text() and line.text()[-1] == ',' else line.text()
            for line in lines[0:2]
        ]
        author_entries = [
            line.text()[:-1]
            if line.text() and line.text()[-1] == ',' else line.text()
            for line in lines[2:4]
        ]
        radio_states = [radio.isChecked() for radio in radios]

        base = functions.buildSearch(topic_entries, author_entries,
                                     radio_states)

        return base

    def tabChanged(self):
        """Method called when tab is changed.
        Fill the fields with the good data"""

        # Get the current tab number
        index = self.tabs.currentIndex()
        tab_title = self.tabs.tabText(index)

        # Get the lineEdit objects of the current search tab displayed
        lines = self.tabs.currentWidget().findChildren(QtWidgets.QLineEdit)
        topic_entries = [line for line in lines[0:2]]
        author_entries = [line for line in lines[2:4]]

        radios = self.tabs.currentWidget().findChildren(QtWidgets.QRadioButton)

        if index != 0:

            # Change the buttons at the button if the tab is
            # a tab dedicated to search edition
            self.button_delete_search.show()

            topic_entries_options = self.options.value(
                "{0}/topic_entries".format(tab_title), None)
            if topic_entries_options is not None:
                topic_entries = [
                    line.setText(value) for line, value in zip(
                        topic_entries, topic_entries_options)
                ]
            author_entries_options = self.options.value(
                "{0}/author_entries".format(tab_title), None)
            if author_entries_options is not None:
                author_entries = [
                    line.setText(value) for line, value in zip(
                        author_entries, author_entries_options)
                ]

            radio_states = self.options.value(
                "{0}/radio_states".format(tab_title), None)
            radio_states = [
                True if element == 'true' else False
                for element in radio_states
            ]
            if radio_states is not None:
                [
                    radio.setChecked(value)
                    for radio, value in zip(radios, radio_states)
                ]

        else:
            self.button_delete_search.hide()

    def saveSearch(self):
        """Slot to save a query"""

        lines = self.tabs.currentWidget().findChildren(QtWidgets.QLineEdit)
        radios = self.tabs.currentWidget().findChildren(QtWidgets.QRadioButton)

        # Get the name of the current tab. Used to determine if the current
        # tab is the "new query" tab
        tab_title = self.tabs.tabText(self.tabs.currentIndex())

        topic_entries = [line.text() for line in lines[0:2]]
        author_entries = [line.text() for line in lines[2:4]]
        radio_states = [radio.isChecked() for radio in radios]

        # Build the query string
        base = self.buildSearch()

        if not base:
            return

        # Creating a new search
        if tab_title == "New query":
            # Get the search name with a dialogBox, if the user pushed the
            # save button
            name_search = QtWidgets.QInputDialog.getText(
                self, "Search name", "Save your search as:")

            if "/" in name_search:
                name_search = name_search.replace("/", "-")

            if not name_search[1] or name_search[0] == "":
                return
            else:
                name_search = name_search[0]
            if name_search in self.options.childGroups():
                # Display an error message if the search name is already used
                QtWidgets.QMessageBox.critical(
                    self,
                    "Saving search",
                    "You already have a search called like this",
                    QtWidgets.QMessageBox.Ok,
                    defaultButton=QtWidgets.QMessageBox.Ok)

                self.logger.debug("This search name is already used")
                return
            else:
                self.tabs.addTab(self.createForm(), name_search)
                if not self.test:
                    self.parent.createSearchTab(name_search, base,
                                                topic_entries, author_entries,
                                                radio_states)
                    self.parent.loadNotifications()

                # Clear the fields when perform search
                for line in lines:
                    line.clear()

        # Modifying and saving an existing search
        else:
            name_search = tab_title

            if not self.test:
                self.parent.createSearchTab(name_search,
                                            base,
                                            topic_entries,
                                            author_entries,
                                            radio_states,
                                            update=True)

        self.logger.debug("Saving the search")

        self.options.beginGroup(name_search)

        # Re-initialize the keys
        self.options.remove("")
        if topic_entries != [''] * 2:
            self.options.setValue("topic_entries", topic_entries)
        if author_entries != [''] * 2:
            self.options.setValue("author_entries", author_entries)
        if base:
            self.options.setValue("sql_query", base)
        self.options.setValue("radio_states", radio_states)
        self.options.endGroup()

        self.options.sync()

    def showInfo(self, field_type):

        if field_type == 1:
            # Generic message fot the field tooltips
            mes = """
            Insert comma(s) between keywords. Ex: heparin sulfate, \
            heparinase. If 'Any' is checked, will match any keyword. If 'All' \
            is checked, will match all the keywords.\nWildcards (*) are \
            accepted. Ex: heparin*.\nFilters are case insensitive.
            """
        if field_type == 2:
            # Generic message fot the field tooltips
            mes = """
            Insert comma(s) between keywords. Ex: heparin sulfate, \
            heparinase.\nWildcards (*) are accepted. Ex: heparin*.\nFilters \
            are case insensitive.
            """
        elif field_type == 3:
            # Generic message fot the authors tooltips
            mes = """Insert comma(s) between keywords. Ex: Jean-Patrick \
            Francoia, Laurent Vial. If 'Any' is checked, will match any \
            author. If 'All' is checked, will match all the authors. \
            Wildcards (*) are accepted. Ex: J* Francoia. \nFilters are case \
            insensitive.\nFirst name comes before last name. Ex: Linus \
            Pauling or L* Pauling.
            """
        elif field_type == 4:
            # Generic message fot the authors tooltips
            mes = """Insert comma(s) between keywords. Ex: Jean-Patrick \
            Francoia, Laurent Vial. Wildcards (*) are accepted. \
            Ex: J* Francoia. \nFilters are case insensitive.\nFirst name \
            comes before last name. Ex: Linus Pauling or L* Pauling.
            """

        # Clean the tabs in the message (tabs are 4 spaces)
        mes = mes.replace("    ", "")

        QtWidgets.QMessageBox.information(self, "Information", mes,
                                          QtWidgets.QMessageBox.Ok)

    def createForm(self):

        # ------------------------ NEW SEARCH TAB -----------------------------

        # Main widget of the tab, with a grid layout
        widget_query = QtWidgets.QWidget()

        vbox_query = QtWidgets.QVBoxLayout()
        widget_query.setLayout(vbox_query)

        vbox_query.addStretch(1)

        # ------------- TOPIC ----------------------------------
        # Create a groupbox for the topic
        group_topic = QtWidgets.QGroupBox("Topic")
        grid_topic = QtWidgets.QGridLayout()
        group_topic.setLayout(grid_topic)

        # Add the topic groupbox to the global vbox
        vbox_query.addWidget(group_topic)

        # Create 3 lines, with their label: AND, OR, NOT
        label_topic_include = QtWidgets.QLabel("Include:")
        line_topic_include = ButtonLineIcon(
            os.path.join(self.resource_dir, 'images/info'))
        line_topic_include.buttonClicked.connect(lambda: self.showInfo(1))

        group_radio_topic = QtWidgets.QButtonGroup()
        radio_topic_any = QtWidgets.QRadioButton("Any")
        radio_topic_any.setChecked(True)
        radio_topic_all = QtWidgets.QRadioButton("All")
        group_radio_topic.addButton(radio_topic_any)
        group_radio_topic.addButton(radio_topic_all)

        label_topic_exclude = QtWidgets.QLabel("Exclude:")
        line_topic_exclude = QtWidgets.QLineEdit()
        line_topic_exclude = ButtonLineIcon(
            os.path.join(self.resource_dir, 'images/info'))
        line_topic_exclude.buttonClicked.connect(lambda: self.showInfo(2))

        # Organize the lines and the lab within the grid
        # addWidget (self, QWidget, int row, int column, int rowSpan, int columnSpan, Qt.Alignment alignment = 0)
        grid_topic.addWidget(label_topic_include, 0, 0)
        grid_topic.addWidget(line_topic_include, 0, 1)
        grid_topic.addWidget(radio_topic_any, 0, 2)
        grid_topic.addWidget(radio_topic_all, 0, 3)
        grid_topic.addWidget(label_topic_exclude, 1, 0)
        grid_topic.addWidget(line_topic_exclude, 1, 1)

        vbox_query.addStretch(1)

        # ------------- AUTHORS ----------------------------------
        # Create a groupbox for the authors
        group_author = QtWidgets.QGroupBox("Author(s)")
        grid_author = QtWidgets.QGridLayout()
        group_author.setLayout(grid_author)

        # Add the author groupbox to the global vbox
        vbox_query.addWidget(group_author)

        label_author_include = QtWidgets.QLabel("Include:")
        line_author_include = QtWidgets.QLineEdit()
        line_author_include = ButtonLineIcon(
            os.path.join(self.resource_dir, 'images/info'))
        line_author_include.buttonClicked.connect(lambda: self.showInfo(3))

        group_radio_author = QtWidgets.QButtonGroup()
        radio_author_any = QtWidgets.QRadioButton("Any")
        radio_author_any.setChecked(True)
        radio_author_all = QtWidgets.QRadioButton("All")
        group_radio_author.addButton(radio_author_any)
        group_radio_author.addButton(radio_author_all)

        label_author_not = QtWidgets.QLabel("Exclude:")
        line_author_exclude = QtWidgets.QLineEdit()
        line_author_exclude = ButtonLineIcon(
            os.path.join(self.resource_dir, 'images/info'))
        line_author_exclude.buttonClicked.connect(lambda: self.showInfo(4))

        grid_author.addWidget(label_author_include, 0, 0)
        grid_author.addWidget(line_author_include, 0, 1)
        grid_author.addWidget(radio_author_any, 0, 2)
        grid_author.addWidget(radio_author_all, 0, 3)
        grid_author.addWidget(label_author_not, 1, 0)
        grid_author.addWidget(line_author_exclude, 1, 1)

        vbox_query.addStretch(1)

        line_topic_include.returnPressed.connect(self.saveSearch)
        line_topic_exclude.returnPressed.connect(self.saveSearch)
        line_author_include.returnPressed.connect(self.saveSearch)
        line_author_exclude.returnPressed.connect(self.saveSearch)

        return widget_query

    def initUI(self):
        """Handles the display"""

        self.setWindowTitle('Advanced Search')

        self.tabs = QtWidgets.QTabWidget()

        query = self.createForm()

        self.tabs.addTab(query, "New query")

        # ----------------- BUTTONS -----------------------------------------

        self.button_delete_search = QtWidgets.QPushButton(
            "Delete search", self)
        self.button_search_and_save = QtWidgets.QPushButton(
            "Save search", self)

        # ------------------------ ASSEMBLING ---------------------------------

        # Create a global vbox, and stack the main widget + the search button
        self.vbox_global = QtWidgets.QVBoxLayout()
        self.vbox_global.addWidget(self.tabs)

        self.vbox_global.addWidget(self.button_delete_search)
        self.button_delete_search.hide()
        self.vbox_global.addWidget(self.button_search_and_save)

        self.setLayout(self.vbox_global)
        self.show()
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 #5
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()
Beispiel #6
0
class AdvancedSearch(QtGui.QDialog):

    """Class to perform advanced searches"""

    def __init__(self, parent):

        super(AdvancedSearch, self).__init__(parent)

        self.parent = parent

        # Condition to use a specific logger if
        # module started in standalone
        if type(parent) is QtGui.QWidget:
            self.logger = MyLog("activity.log")
            self.test = True
            DATA_PATH = '.'
            self.parent.resource_dir = DATA_PATH
        else:
            DATA_PATH = self.parent.DATA_PATH
            self.logger = self.parent.l
            self.test = False

        self.options = QtCore.QSettings(DATA_PATH + "/config/searches.ini", QtCore.QSettings.IniFormat)

        # List to store the lineEdit, with the value of
        # the search fields
        self.fields_list = []

        self.initUI()
        self.defineSlots()
        self.restoreSettings()


    def restoreSettings(self):

        """Restore the right number of tabs"""

        for query in self.options.childGroups():
            self.tabs.addTab(self.createForm(), query)

        # Try to restore the geometry of the AdvancedSearch window
        try:
            self.restoreGeometry(self.options.value("window_geometry"))
        except TypeError:
            self.logger.debug("Can't restore window geometry for AdvancedSearch")


    def closeEvent(self, event):

        """Actions to perform when closing the window.
        Mainly saves the window geometry"""

        self.logger.debug("Saving windows state for AdvancedSearch")
        self.options.setValue("window_geometry", self.saveGeometry())

        super(AdvancedSearch, self).closeEvent(event)


    def defineSlots(self):

        """Establish the slots"""

        self.button_search_and_save.clicked.connect(self.search)

        self.tabs.currentChanged.connect(self.tabChanged)

        self.button_delete_search.clicked.connect(self.deleteSearch)

        self.destroyed.connect(self.closeEvent)


    def deleteSearch(self):

        """Slot to delete a query"""

        # Get the title of the search, get the group with
        # the same name in searches.ini, and clear the group
        tab_title = self.tabs.tabText(self.tabs.currentIndex())
        self.options.beginGroup(tab_title)
        # Re-initialize the keys
        self.options.remove("")
        self.options.endGroup()

        self.tabs.removeTab(self.tabs.currentIndex())

        if not self.test:
            for index in range(self.parent.onglets.count()):
                if self.parent.onglets.tabText(index) == tab_title:
                    self.parent.list_tables_in_tabs.remove(self.parent.onglets.widget(index))
                    self.parent.onglets.removeTab(index)
                    self.parent.onglets.setCurrentIndex(0)
                    break


    def buildSearch(self):

        """Build the query"""

        # Get all the lineEdit from the current tab
        lines = self.tabs.currentWidget().findChildren(QtGui.QLineEdit)
        radios = self.tabs.currentWidget().findChildren(QtGui.QRadioButton)

        # Clean the fields of tailing comma
        topic_entries = [line.text()[:-1] if line.text() and line.text()[-1] == ',' else line.text() for line in lines[0:2]]
        author_entries = [line.text()[:-1] if line.text() and line.text()[-1] == ',' else line.text() for line in lines[2:4]]
        radio_states = [radio.isChecked() for radio in radios]

        base = functions.buildSearch(topic_entries, author_entries,
                                     radio_states)

        return base


    def tabChanged(self):

        """Method called when tab is changed.
        Fill the fields with the good data"""

        # Get the current tab number
        index = self.tabs.currentIndex()
        tab_title = self.tabs.tabText(index)

        # Get the lineEdit objects of the current search tab displayed
        lines = self.tabs.currentWidget().findChildren(QtGui.QLineEdit)
        topic_entries = [line for line in lines[0:2]]
        author_entries = [line for line in lines[2:4]]

        radios = self.tabs.currentWidget().findChildren(QtGui.QRadioButton)

        if index != 0:

            # Change the buttons at the button if the tab is
            # a tab dedicated to search edition
            self.button_delete_search.show()

            topic_entries_options = self.options.value("{0}/topic_entries".format(tab_title), None)
            if topic_entries_options is not None:
                topic_entries = [line.setText(value) for line, value in zip(topic_entries, topic_entries_options)]
            author_entries_options = self.options.value("{0}/author_entries".format(tab_title), None)
            if author_entries_options is not None:
                author_entries = [line.setText(value) for line, value in zip(author_entries, author_entries_options)]

            radio_states = self.options.value("{0}/radio_states".format(tab_title), None)
            radio_states = [True if element == 'true' else False for element in radio_states]
            if radio_states is not None:
                [radio.setChecked(value) for radio, value in zip(radios, radio_states)]

        else:
            self.button_delete_search.hide()


    def search(self):

        """Slot to save a query"""

        lines = self.tabs.currentWidget().findChildren(QtGui.QLineEdit)
        radios = self.tabs.currentWidget().findChildren(QtGui.QRadioButton)

        # Get the name of the current tab. Used to determine if the current
        # tab is the "new query" tab
        tab_title = self.tabs.tabText(self.tabs.currentIndex())

        topic_entries = [line.text() for line in lines[0:2]]
        author_entries = [line.text() for line in lines[2:4]]
        radio_states = [radio.isChecked() for radio in radios]

        # Build the query string
        base = self.buildSearch()

        if not base:
            return

        # Creating a new search
        if tab_title == "New query":
            # Get the search name with a dialogBox, if the user pushed the save button
            name_search = QtGui.QInputDialog.getText(self, "Search name", "Save your search as:")

            if "/" in name_search:
                name_search = name_search.replace("/", "-")

            if not name_search[1] or name_search[0] == "":
                return
            else:
                name_search = name_search[0]
            if name_search in self.options.childGroups():
                # Display an error message if the search name is already used
                QtGui.QMessageBox.critical(self, "Saving search", "You already have a search called like this",
                                           QtGui.QMessageBox.Ok, defaultButton=QtGui.QMessageBox.Ok)

                self.logger.debug("This search name is already used")
                return
            else:
                self.tabs.addTab(self.createForm(), name_search)
                if not self.test:
                    self.parent.createSearchTab(name_search, base,
                                                topic_entries,
                                                author_entries,
                                                radio_states)
                    self.parent.loadNotifications()

                # Clear the fields when perform search
                for line in lines:
                    line.clear()

        # Modifying and saving an existing search
        else:
            name_search = tab_title

            if not self.test:
                self.parent.createSearchTab(name_search, base, topic_entries,
                                            author_entries, radio_states,
                                            update=True)

        self.logger.debug("Saving the search")

        self.options.beginGroup(name_search)

        # Re-initialize the keys
        self.options.remove("")
        if topic_entries != [''] * 2:
            self.options.setValue("topic_entries", topic_entries)
        if author_entries != [''] * 2:
            self.options.setValue("author_entries", author_entries)
        if base:
            self.options.setValue("sql_query", base)
        self.options.setValue("radio_states", radio_states)
        self.options.endGroup()

        self.options.sync()


    def showInfo(self, field_type):

        if field_type == 1:
            # Generic message fot the field tooltips
            mes = """
            Insert comma(s) bewtween keywords. Ex: heparin sulfate, \
            heparinase. If 'Any' is checked, will match any keyword. If 'All' \
            is checked, will match all the keywords.\nWildcards (*) are \
            accepted. Ex: heparin*.\nFilters are case insensitive.
            """
        if field_type == 2:
            # Generic message fot the field tooltips
            mes = """
            Insert comma(s) bewtween keywords. Ex: heparin sulfate, \
            heparinase.\nWildcards (*) are accepted. Ex: heparin*.\nFilters \
            are case insensitive.
            """
        elif field_type == 3:
            # Generic message fot the authors tooltips
            mes = """Insert comma(s) bewtween keywords. Ex: Jean-Patrick \
            Francoia, Laurent Vial. If 'Any' is checked, will match any \
            author. If 'All' is checked, will match all the authors. \
            Wildcards (*) are accepted. Ex: J* Francoia. \nFilters are case \
            insensitive.\nFirst name comes before last name. Ex: Linus \
            Pauling or L* Pauling.
            """
        elif field_type == 4:
            # Generic message fot the authors tooltips
            mes = """Insert comma(s) bewtween keywords. Ex: Jean-Patrick \
            Francoia, Laurent Vial. Wildcards (*) are accepted. \
            Ex: J* Francoia. \nFilters are case insensitive.\nFirst name \
            comes before last name. Ex: Linus Pauling or L* Pauling.
            """

        # Clean the tabs in the message (tabs are 4 spaces)
        mes = mes.replace("    ", "")

        QtGui.QMessageBox.information(self, "Information", mes,
                                      QtGui.QMessageBox.Ok)


    def createForm(self):

        # ------------------------ NEW SEARCH TAB -----------------------------

        # Main widget of the tab, with a grid layout
        widget_query = QtGui.QWidget()

        vbox_query = QtGui.QVBoxLayout()
        widget_query.setLayout(vbox_query)

        vbox_query.addStretch(1)

        # ------------- TOPIC ----------------------------------
        # Create a groupbox for the topic
        group_topic = QtGui.QGroupBox("Topic")
        grid_topic = QtGui.QGridLayout()
        group_topic.setLayout(grid_topic)

        # Add the topic groupbox to the global vbox
        vbox_query.addWidget(group_topic)

        # Create 3 lines, with their label: AND, OR, NOT
        label_topic_include = QtGui.QLabel("Include:")
        line_topic_include = ButtonLineIcon(os.path.join(self.parent.resource_dir,
                                                     'images/info'))
        line_topic_include.buttonClicked.connect(lambda: self.showInfo(1))

        group_radio_topic = QtGui.QButtonGroup()
        radio_topic_any = QtGui.QRadioButton("Any")
        radio_topic_any.setChecked(True)
        radio_topic_all = QtGui.QRadioButton("All")
        group_radio_topic.addButton(radio_topic_any)
        group_radio_topic.addButton(radio_topic_all)

        label_topic_exclude = QtGui.QLabel("Exclude:")
        line_topic_exclude = QtGui.QLineEdit()
        line_topic_exclude = ButtonLineIcon(os.path.join(self.parent.resource_dir,
                                                     'images/info'))
        line_topic_exclude.buttonClicked.connect(lambda: self.showInfo(2))

        # Organize the lines and the lab within the grid
        # addWidget (self, QWidget, int row, int column, int rowSpan, int columnSpan, Qt.Alignment alignment = 0)
        grid_topic.addWidget(label_topic_include, 0, 0)
        grid_topic.addWidget(line_topic_include, 0, 1)
        grid_topic.addWidget(radio_topic_any, 0, 2)
        grid_topic.addWidget(radio_topic_all, 0, 3)
        grid_topic.addWidget(label_topic_exclude, 1, 0)
        grid_topic.addWidget(line_topic_exclude, 1, 1)

        vbox_query.addStretch(1)

        # ------------- AUTHORS ----------------------------------
        # Create a groupbox for the authors
        group_author = QtGui.QGroupBox("Author(s)")
        grid_author = QtGui.QGridLayout()
        group_author.setLayout(grid_author)

        # Add the author groupbox to the global vbox
        vbox_query.addWidget(group_author)

        label_author_include = QtGui.QLabel("Include:")
        line_author_include = QtGui.QLineEdit()
        line_author_include = ButtonLineIcon(os.path.join(self.parent.resource_dir,
                                                      'images/info'))
        line_author_include.buttonClicked.connect(lambda: self.showInfo(3))

        group_radio_author = QtGui.QButtonGroup()
        radio_author_any = QtGui.QRadioButton("Any")
        radio_author_any.setChecked(True)
        radio_author_all = QtGui.QRadioButton("All")
        group_radio_author.addButton(radio_author_any)
        group_radio_author.addButton(radio_author_all)

        label_author_not = QtGui.QLabel("Exclude:")
        line_author_exclude = QtGui.QLineEdit()
        line_author_exclude = ButtonLineIcon(os.path.join(self.parent.resource_dir,
                                                      'images/info'))
        line_author_exclude.buttonClicked.connect(lambda: self.showInfo(4))

        grid_author.addWidget(label_author_include, 0, 0)
        grid_author.addWidget(line_author_include, 0, 1)
        grid_author.addWidget(radio_author_any, 0, 2)
        grid_author.addWidget(radio_author_all, 0, 3)
        grid_author.addWidget(label_author_not, 1, 0)
        grid_author.addWidget(line_author_exclude, 1, 1)

        vbox_query.addStretch(1)

        line_topic_include.returnPressed.connect(self.search)
        line_topic_exclude.returnPressed.connect(self.search)
        line_author_include.returnPressed.connect(self.search)
        line_author_exclude.returnPressed.connect(self.search)

        return widget_query


    def initUI(self):

        """Handles the display"""

        self.setWindowTitle('Advanced Search')

        self.tabs = QtGui.QTabWidget()

        query = self.createForm()

        self.tabs.addTab(query, "New query")

        # ----------------- BUTTONS -----------------------------------------

        self.button_delete_search = QtGui.QPushButton("Delete search", self)
        self.button_search_and_save = QtGui.QPushButton("Save search", self)

        # ------------------------ ASSEMBLING ---------------------------------

        # Create a global vbox, and stack the main widget + the search button
        self.vbox_global = QtGui.QVBoxLayout()
        self.vbox_global.addWidget(self.tabs)

        self.vbox_global.addWidget(self.button_delete_search)
        self.button_delete_search.hide()
        self.vbox_global.addWidget(self.button_search_and_save)

        self.setLayout(self.vbox_global)
        self.show()
Beispiel #7
0
class MyTwit(QtWidgets.QDialog):

    """Module to authenticate the user on Twitter. Allows to tweet"""
    # http://www.adrianjock.com/twitter-myth-url-shorteners/
    # https://dev.twitter.com/faq#3
    # https://dev.twitter.com/rest/reference/get/help/configuration


    def __init__(self, title, link, graphical=None, parent=None):

        super(MyTwit, self).__init__(parent)

        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        self.parent = parent

        # Remove html tags from the title
        self.title = removeHtml(title)

        self.link = link
        self.graphical = graphical

        self.resource_dir, self.DATA_PATH = functions.getRightDirs()

        if parent is None:
            self.l = MyLog("activity.log")
        else:
            self.l = self.parent.l

        self.CONSUMER_KEY = 'IaTVXKtZ7uBjzcVWzsVmMYKtP'
        self.CONSUMER_SECRET = '8hsz0Zj3CupFfvJMAhpG3UjMLs7HZjGywRsjRJI8IcjIA4NrEk'

        self.MY_TWITTER_CREDS = self.DATA_PATH + '/config/twitter_credentials'

        self.initUI()
        self.defineSlots()

        # If no credentials, try to get them
        if not os.path.exists(self.MY_TWITTER_CREDS):
            authentified = self.openAuthPage()

            # No credentials obtained, exit
            if not authentified:
                return

        self.setTweetText()
        self.show()


    def openAuthPage(self):

        """Method to open the web page which gives the PIN code for
        authentication. When done, verify the pin code and write the
        keys into a local file. The user won't have to do the dance each
        time he wants to tweet"""

        twitter = Twitter(auth=OAuth('', '', self.CONSUMER_KEY,
                                     self.CONSUMER_SECRET),
                          format='', api_version=None)

        token, token_secret = self.parseOauthTokens(twitter.oauth.request_token(oauth_callback="oob"))

        self.l.debug("Opening authentication URL")

        oauth_url = ('https://api.twitter.com/oauth/authorize?oauth_token=' + token)

        try:
            r = webbrowser.open(oauth_url)

            # Sometimes the last command can print some
            # crap. Wait a bit so it doesn't mess up the next
            # prompt.
            time.sleep(2)

            if not r:
                raise Exception()
        except Exception as e:
            QtWidgets.QMessageBox.critical(self, "Authentication", "ChemBrows could not open a web page.\nVisit {} to get the PIN code".format(oauth_url),
                                           QtWidgets.QMessageBox.Ok,defaultButton=QtWidgets.QMessageBox.Ok)
            self.l.error("Authentication URL not opened")
            self.l.error("openAuthPage: {}".format(e), exc_info=True)
            return False


        pin = QtWidgets.QInputDialog.getText(self, "PIN verification","Enter the PIN to authenticate yourself")

        # If OK wasn't pressed, exit
        if not pin[1]:
            return False

        oauth_verifier = pin[0]
        twitter = Twitter(auth=OAuth(token, token_secret, self.CONSUMER_KEY,
                                     self.CONSUMER_SECRET),
                          format='', api_version=None)

        try:
            oauth_token, oauth_secret = self.parseOauthTokens(twitter.oauth.access_token(oauth_verifier=oauth_verifier))
        except Exception as e:
            QtWidgets.QMessageBox.critical(self, "Authentication", "Impossible to obtain tokens",
                                           QtWidgets.QMessageBox.Ok, defaultButton=QtWidgets.QMessageBox.Ok)
            self.l.error("openAuthPage, no tokens : {}".format(e),
                         exc_info=True)
            return False

        self.l.debug("Writing authentication file")
        write_token_file(self.MY_TWITTER_CREDS, oauth_token, oauth_secret)

        self.twitter = Twitter(auth=OAuth(oauth_token, oauth_secret,
                               self.CONSUMER_KEY, self.CONSUMER_SECRET))

        return True


    def parseOauthTokens(self, result):

        """Original function from the twitter.oauth_dance module.
        Don't really know what it does (probably parsing)..."""

        for r in result.split('&'):
            k, v = r.split('=')
            if k == 'oauth_token':
                oauth_token = v
            elif k == 'oauth_token_secret':
                oauth_token_secret = v
        return oauth_token, oauth_token_secret


    def setTweetText(self):

        """Method to shorten the title if it is too long"""

        # All links are 23 characters long, even the shorter ones
        len_data = 23

        # If graphical abstract included, shorten the title of 24 characters
        # https://dev.twitter.com/rest/reference/get/help/configuration
        try:
            if self.check_graphical.checkState() == 2:
                len_data += 24
        except AttributeError:
            pass

        LEN_TWEET = constants.LEN_TWEET

        # -1 for 1 space, -10 for #ChemBrows
        if len(self.title) >= LEN_TWEET - len_data - 1 - 10:
            title = self.title[:LEN_TWEET - len_data - 1 - 10 - 3].rstrip()
            title += "..."
        else:
            title = self.title + " "

        self.text_tweet.setText(title + self.link)


    def postTweet(self):

        """Simple method to post a tweet"""

        oauth_token, oauth_secret = read_token_file(self.MY_TWITTER_CREDS)

        try:
            if self.check_graphical.checkState() == 2:
                t_up = Twitter(domain='upload.twitter.com',
                               auth=OAuth(oauth_token, oauth_secret,
                                          self.CONSUMER_KEY,
                                          self.CONSUMER_SECRET))

                with open(self.DATA_PATH + "/graphical_abstracts/{}".format(self.graphical), "rb") as image:
                    imagedata = image.read()

                id_img = t_up.media.upload(media=imagedata)["media_id_string"]
            else:
                self.l.debug("No image, check box not checked")
                id_img = None

        except AttributeError:
            self.l.debug("No image, no check box at all")
            id_img = None

        twitter = Twitter(auth=OAuth(oauth_token, oauth_secret,
                                     self.CONSUMER_KEY, self.CONSUMER_SECRET))

        text = self.text_tweet.toPlainText() + " #ChemBrows"

        if id_img is None:
            try:
                twitter.statuses.update(status=text)
            except Exception as e:
                QtWidgets.QMessageBox.critical(self, "Twitter error", "ChemBrows could not tweet that.\nYour tweet is probably too long: {} chara.".format(len(text)),
                                           QtWidgets.QMessageBox.Ok, defaultButton=QtWidgets.QMessageBox.Ok)
                self.l.error('postTweet: {}'.format(e), exc_info=True)
        else:
            try:
                twitter.statuses.update(status=text, media_ids=id_img)
            except Exception as e:
                QtWidgets.QMessageBox.critical(self, "Twitter error", "ChemBrows could not tweet that.\nYour tweet is probably too long: {} chara.".format(len(text)),
                                           QtWidgets.QMessageBox.Ok, defaultButton=QtWidgets.QMessageBox.Ok)
                self.l.error("postTweet: {}".format(e), exc_info=True)

        self.close()


    def defineSlots(self):

        """Establish the slots"""

        # If next pressed, go to next slide
        self.ok_button.clicked.connect(self.postTweet)

        # Quit the tuto at any moment
        self.cancel_button.clicked.connect(self.done)


    def initUI(self):

        """Handles the display"""

        self.text_tweet = QtWidgets.QTextEdit()

        self.cancel_button = QtWidgets.QPushButton("Cancel", self)
        self.ok_button = QtWidgets.QPushButton("Tweet me !", self)

        self.hbox_buttons = QtWidgets.QHBoxLayout()
        self.vbox_global = QtWidgets.QVBoxLayout()

        # Display a check box to let the user choose
        # if he wants the graphical abstract to be displayed
        if self.graphical is not None:
            self.check_graphical = QtWidgets.QCheckBox("Include graphical abstract")
            self.check_graphical.setCheckState(2)
            self.check_graphical.stateChanged.connect(self.setTweetText)
            self.hbox_buttons.addWidget(self.check_graphical)

        self.hbox_buttons.addWidget(self.cancel_button)
        self.hbox_buttons.addWidget(self.ok_button)

        self.vbox_global.addWidget(self.text_tweet)
        self.vbox_global.addLayout(self.hbox_buttons)

        self.setLayout(self.vbox_global)