camelot.view.database_selection — Camelot documentation

### Navigation

- index
- modules |
- Camelot documentation »
- Module code »

# Source code for camelot.view.database_selection

    #  ============================================================================
    #
    #  Copyright (C) 2007-2011 Conceptive Engineering bvba. All rights reserved.
    #  www.conceptive.be / project-camelot@conceptive.be
    #
    #  This file is part of the Camelot Library.
    #
    #  This file may be used under the terms of the GNU General Public
    #  License version 2.0 as published by the Free Software Foundation
    #  and appearing in the file license.txt included in the packaging of
    #  this file.  Please review this information to ensure GNU
    #  General Public Licensing requirements will be met.
    #
    #  If you are unsure which license is appropriate for your use, please
    #  visit www.python-camelot.com or contact project-camelot@conceptive.be
    #
    #  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
    #  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
    #
    #  For use of this library in commercial applications, please contact
    #  project-camelot@conceptive.be
    #
    #  ============================================================================

    import os
    import sys
    import logging
    import pkgutil

    # from collections import defaultdict

    from sqlalchemy import create_engine

    from PyQt4 import QtCore
    from PyQt4 import QtNetwork
    from PyQt4.QtCore import Qt
    from PyQt4.QtCore import QFileInfo
    from PyQt4.QtGui import QBoxLayout, QDialog, QFont, QGridLayout, QHBoxLayout, \
        QLabel, QLineEdit, QPushButton, QFileDialog, QComboBox, QWidget, QVBoxLayout
    from camelot.view import art
    from camelot.view.controls.progress_dialog import ProgressDialog
    from camelot.view.controls.editors import ChoicesEditor, TextLineEditor, LanguageEditor
    from camelot.view.controls.standalone_wizard_page import HSeparator, StandaloneWizardPage
    from camelot.view.controls.combobox_input_dialog import ComboBoxInputDialog

    from camelot.core.exception import UserException
    from camelot.core.utils import ugettext as _
    from camelot.view.model_thread.signal_slot_model_thread import SignalSlotModelThread
    from camelot.view.model_thread import gui_function
    from camelot.core.dbprofiles import fetch_profiles, use_chosen_profile, \
        store_profiles, get_network_proxy, last_used_profile

    logger = logging.getLogger('camelot.view.database_selection')

    NEW_PROFILE_LABEL = _('new/edit profile')

    def select_database(app_admin):
        profiles_dict = fetch_profiles()
        if not profiles_dict:
            create_new_profile(app_admin, profiles_dict)

        selected = select_profile(profiles_dict)
        if selected in profiles_dict:
            use_chosen_profile(selected)
        elif selected == NEW_PROFILE_LABEL:
            create_new_profile(app_admin, profiles_dict)
        else:
            sys.exit(0)

    def select_profile(profiles_dict):
        title = _('Profile Selection')
        input_label = _('Select a stored profile:')
        ok_label = _('OK')
        cancel_label = _('Quit')

        input_dialog = ComboBoxInputDialog(autoaccept=True)
        input_dialog.set_window_title(title)
        input_dialog.set_label_text(input_label)
        input_dialog.set_ok_button_text(ok_label)
        input_dialog.set_cancel_button_text(cancel_label)
        input_dialog.set_items(sorted(profiles_dict.keys()) + [NEW_PROFILE_LABEL])
        _last_used_profile = last_used_profile()
        if _last_used_profile:
            input_dialog.set_choice_by_text(_last_used_profile)
        input_dialog.set_ok_button_default()

        last_index = input_dialog.count()-1
        custom_font = QFont()
        custom_font.setItalic(True)
        icon = art.Icon('tango/16x16/actions/document-new.png').getQIcon()
        input_dialog.set_data(last_index, custom_font, Qt.FontRole)
        input_dialog.set_data(last_index, icon, Qt.DecorationRole)

        dialog_code = input_dialog.exec_()
        if dialog_code == QDialog.Accepted:
            return unicode(input_dialog.get_text())

        return None

    def new_profile_item_selected(input_dialog):
        input_dialog.accept()

    def create_new_profile(app_admin, profiles):
        wizard = app_admin.database_profile_wizard(profiles)
        dialog_code = wizard.exec_()
        if dialog_code == QDialog.Rejected:
            # no profiles? exit
            if not profiles:
                sys.exit(0)
            # one more time
            select_database(app_admin)

    class ProfileWizard(StandaloneWizardPage):
        """Wizard for the creation of a new database
    profile.

    .. attribute:: languages
    .. attribute:: dialects

    A list of languages allowed in the profile selection, an empty list will
    allow all languages
        """

        languages = []
        dialects = []

        def __init__(self, profiles, parent=None):
            super(ProfileWizard, self).__init__(parent)

            self._connection_valid = False
            self.network_reply = None
            self.profiles = profiles

            self.setWindowTitle(_('Profile Wizard'))
            self.set_banner_logo_pixmap(art.Icon('tango/22x22/categories/preferences-system.png').getQPixmap())
            self.set_banner_title(_('Create New/Edit Profile'))
            self.set_banner_subtitle(_('Please enter the database settings'))
            self.banner_widget().setStyleSheet('background-color: white;')

            self.manager = QtNetwork.QNetworkAccessManager( self )
            self.manager.finished.connect( self.update_network_status )
            #self.manager.networkAccessibleChanged.connect( self.network_accessible_changed )
            self.manager.proxyAuthenticationRequired.connect( self.proxy_authentication_required )

            self.create_labels_and_widgets()
            self.create_buttons()
            self.set_tab_order()
            self.set_widgets_values()
            # note: connections come after labels and widgets are created
            # and have default values
            self.connect_widgets()
            self.connect_buttons()

            timer = QtCore.QTimer(self)
            timer.timeout.connect( self.new_network_request )
            timer.setInterval( 3000 )
            timer.start()
            self.new_network_request()

        @gui_function
        def create_labels_and_widgets(self):
            self.profile_label = QLabel(_('Profile Name:'))
            self.dialect_label = QLabel(_('Driver:'))
            self.host_label = QLabel(_('Server Host:'))
            self.port_label = QLabel(_('Port:'))
            self.database_name_label = QLabel(_('Database Name:'))
            self.username_label = QLabel(_('Username:'))
            self.password_label = QLabel(_('Password:'))
            self.media_location_label = QLabel(_('Media Location:'))
            self.language_label = QLabel(_('Language:'))
            self.proxy_host_label = QLabel(_('Proxy Host:'))
            self.proxy_port_label = QLabel(_('Port:'))
            self.proxy_username_label = QLabel(_('Proxy Username:'))
            self.proxy_password_label = QLabel(_('Proxy Password:'))
            self.network_status_label = QLabel()

            self.not_accessible_media_path_label = QLabel(_('Media location path '\
                'is not accessible.'))
            self.not_accessible_media_path_label.setStyleSheet('color: red')
            self.not_writable_media_path_label = QLabel(_('Media location path '\
                'is not writable.'))
            self.not_writable_media_path_label.setStyleSheet('color: red')

            layout = QGridLayout()

            layout.addWidget(self.profile_label, 0, 0, Qt.AlignRight)
            layout.addWidget(self.dialect_label, 1, 0, Qt.AlignRight)
            layout.addWidget(self.host_label, 2, 0, Qt.AlignRight)
            layout.addWidget(self.port_label, 2, 3, Qt.AlignRight)
            layout.addWidget(self.database_name_label, 3, 0, Qt.AlignRight)
            layout.addWidget(self.username_label, 4, 0, Qt.AlignRight)
            layout.addWidget(self.password_label, 5, 0, Qt.AlignRight)
            layout.addWidget(self.media_location_label, 7, 0, Qt.AlignRight)
            layout.addWidget(self.language_label, 8, 0, Qt.AlignRight)
            layout.addWidget(self.proxy_host_label,  10, 0, Qt.AlignRight)
            layout.addWidget(self.proxy_port_label,  10, 3, Qt.AlignRight)
            layout.addWidget(self.proxy_username_label, 11, 0, Qt.AlignRight)
            layout.addWidget(self.proxy_password_label, 12, 0, Qt.AlignRight)

            self.profile_editor = QComboBox(self)
            self.profile_editor.setEditable(True)

            # 32767 is Qt max length for string
            # should be more than enough for folders
            # http://doc.qt.nokia.com/latest/qlineedit.html#maxLength-prop
            self.dialect_editor = ChoicesEditor(parent=self)
            self.host_editor = TextLineEditor(self, length=32767)
            self.port_editor = TextLineEditor(self)
            self.port_editor.setFixedWidth(60)
            self.database_name_editor = TextLineEditor(self, length=32767)
            self.username_editor = TextLineEditor(self)
            self.password_editor = TextLineEditor(self)
            self.password_editor.setEchoMode(QLineEdit.Password)
            self.media_location_editor = TextLineEditor(self, length=32767)
            self.language_editor = LanguageEditor(languages=self.languages,
                                                  parent=self)
            #
            # try to find a default language
            #
            system_language = QtCore.QLocale.system().name()
            if self.languages:
                if system_language in self.languages:
                    self.language_editor.set_value( system_language )
                else:
                    self.language_editor.set_value( self.languages[0] )
            else:
                self.language_editor.set_value( system_language )

            self.proxy_host_editor = TextLineEditor(self, length=32767)
            self.proxy_port_editor = TextLineEditor(self)
            self.proxy_port_editor.setFixedWidth(60)
            self.proxy_username_editor = TextLineEditor(self)
            self.proxy_password_editor = TextLineEditor(self)
            self.proxy_password_editor.setEchoMode(QLineEdit.Password)

            layout.addWidget(self.profile_editor, 0, 1, 1, 1)
            layout.addWidget(self.dialect_editor, 1, 1, 1, 1)
            layout.addWidget(self.host_editor, 2, 1, 1, 1)
            layout.addWidget(self.port_editor, 2, 4, 1, 1)
            layout.addWidget(self.database_name_editor, 3, 1, 1, 1)
            layout.addWidget(self.username_editor, 4, 1, 1, 1)
            layout.addWidget(self.password_editor, 5, 1, 1, 1)
            layout.addWidget(HSeparator(), 6, 0, 1, 5)
            layout.addWidget(self.media_location_editor, 7, 1, 1, 1)
            layout.addWidget(self.language_editor, 8, 1, 1, 1)
            layout.addWidget(HSeparator(), 9, 0, 1, 5)
            layout.addWidget(self.proxy_host_editor, 10, 1, 1, 1)
            layout.addWidget(self.proxy_port_editor, 10, 4, 1, 1)
            layout.addWidget(self.proxy_username_editor, 11, 1, 1, 1)
            layout.addWidget(self.proxy_password_editor, 12, 1, 1, 1)

            layout.addWidget(self.network_status_label, 13, 1, 1, 4)

            self.main_widget().setLayout(layout)

        def set_widgets_values(self):
            self.dialect_editor.clear()
            self.profile_editor.clear()

            if self.dialects:
                dialects = self.dialects
            else:
                import sqlalchemy.dialects
                dialects = [name for _importer, name, is_package in \
                            pkgutil.iter_modules(sqlalchemy.dialects.__path__) \
                            if is_package]
            self.dialect_editor.set_choices([(dialect, dialect.capitalize()) \
                for dialect in dialects])

            self.profile_editor.insertItems(1, [''] + \
                [item for item in fetch_profiles()])
            self.profile_editor.setFocus()
            self.update_wizard_values()

        def connect_widgets(self):
            self.profile_editor.editTextChanged.connect(self.update_wizard_values)
            # self.dialect_editor.currentIndexChanged.connect(self.update_wizard_values)

        def create_buttons(self):
            self.more_button = QPushButton(_('More'))
            self.more_button.setCheckable(True)
            self.more_button.setAutoDefault(False)
            self.cancel_button = QPushButton(_('Cancel'))
            self.ok_button = QPushButton(_('OK'))

            layout = QHBoxLayout()
            layout.setDirection(QBoxLayout.RightToLeft)

            layout.addWidget(self.cancel_button)
            layout.addWidget(self.ok_button)
            layout.addStretch()
            layout.addWidget(self.more_button)

            self.buttons_widget().setLayout(layout)

            self.browse_button = QPushButton(_('Browse'))
            self.main_widget().layout().addWidget(self.browse_button, 7, 2, 1, 3)

            self.setup_extension()

        def setup_extension(self):
            self.extension = QWidget()

            self.load_button = QPushButton(_('Load profiles'))
            self.save_button = QPushButton(_('Save profiles'))

            extension_buttons_layout = QHBoxLayout()
            extension_buttons_layout.setContentsMargins(0, 0, 0, 0)
            extension_buttons_layout.addWidget(self.load_button)
            extension_buttons_layout.addWidget(self.save_button)
            extension_buttons_layout.addStretch()

            extension_layout = QVBoxLayout()
            extension_layout.setContentsMargins(0, 0, 0, 0)
            extension_layout.addWidget(HSeparator())
            extension_layout.addLayout(extension_buttons_layout)

            self.extension.setLayout(extension_layout)
            self.main_widget().layout().addWidget(self.extension, 15, 0, 1, 5)
            self.extension.hide()

        def set_tab_order(self):
            all_widgets = [self.profile_editor, self.dialect_editor,
                self.host_editor, self.port_editor,  self.database_name_editor,
                self.username_editor, self.password_editor,
                self.media_location_editor, self.browse_button,
                self.language_editor,
                self.proxy_host_editor, self.proxy_port_editor,
                self.proxy_username_editor, self.proxy_password_editor,
                self.ok_button, self.cancel_button]

            i = 1
            while i != len(all_widgets):
                self.setTabOrder(all_widgets[i-1], all_widgets[i])
                i += 1

        def connect_buttons(self):
            self.cancel_button.pressed.connect(self.reject)
            self.ok_button.pressed.connect(self.proceed)
            self.browse_button.pressed.connect(self.fill_media_location)
            self.more_button.toggled.connect(self.extension.setVisible)
            self.save_button.pressed.connect(self.save_profiles_to_file)
            self.load_button.pressed.connect(self.load_profiles_from_file)

        def proceed(self):
            if self.is_connection_valid():
                profilename, info = self.collect_info()
                if profilename in self.profiles:
                    self.profiles[profilename].update(info)
                else:
                    self.profiles[profilename] = info
                store_profiles(self.profiles)
                use_chosen_profile(profilename)
                self.accept()

        def is_connection_valid(self):
            profilename, info = self.collect_info()
            mt = SignalSlotModelThread(lambda:None)
            mt.start()
            progress = ProgressDialog(_('Verifying database settings'))
            mt.post(lambda:self.test_connection(info['dialect'], info['host'],
                info['port'], info['user'], info['pass'], info['database']),
                progress.finished, progress.exception)
            progress.exec_()
            return self._connection_valid

        def test_connection(self, dialect, host, port, user, passwd, db):
            self._connection_valid = False
            connection_string = '%s://%s:%s@%s:%s/%s' % (dialect, user, passwd, host, port, db)
            engine = create_engine(connection_string, pool_recycle=True)
            try:
                connection = engine.raw_connection()
                cursor = connection.cursor()
                cursor.close()
                connection.close()
                self._connection_valid = True
            except Exception, e:
                self._connection_valid = False
                raise UserException( _('Could not connect to database, please check host and port'),
                                     resolution = _('Verify driver, host and port or contact your system administrator'),
                                     detail = unicode(e) )

        def toggle_ok_button(self, enabled):
            self.ok_button.setEnabled(enabled)

        def current_profile(self):
            text = unicode(self.profile_editor.currentText())
            self.toggle_ok_button(bool(text))
            return text

        # FIXME can't get this to work properly (call in update_wizard_values(): port_editor )
        # def _related_default_port(self, dialect_editor):
        #     class Missing(defaultdict):
        #         def __missing__(self, key):
        #             return ''
        #     ports = Missing(mysql='3306', postgresql='5432')
        #     return ports[dialect_editor.get_value()]

        def update_wizard_values(self):
            network_proxy = get_network_proxy()
            # self.dialect_editor.set_value(self.get_profile_value('dialect') or 'mysql')
            # self.host_editor.setText(self.get_profile_value('host') or '127.0.0.1')
            # self.port_editor.setText(self.get_profile_value('port') or '3306')
            self.dialect_editor.set_value(self.get_profile_value('dialect') or 'postgresql')
            self.host_editor.setText(self.get_profile_value('host') or self.host_editor.text())
            self.port_editor.setText(self.get_profile_value('port') or self.port_editor.text())
            # self.port_editor.setText(self.get_profile_value('port') or self._related_default_port(self.dialect_editor))
            self.database_name_editor.setText(self.get_profile_value('database') or self.database_name_editor.text())
            self.username_editor.setText(self.get_profile_value('user') or self.username_editor.text())
            self.password_editor.setText(self.get_profile_value('pass') or self.password_editor.text())
            self.media_location_editor.setText(self.get_profile_value('media_location') or self.media_location_editor.text())
            self.language_editor.set_value(self.get_profile_value('locale_language') or self.language_editor.get_value())
            self.proxy_host_editor.setText(self.get_profile_value('proxy_host') or str(network_proxy.hostName()))
            self.proxy_port_editor.setText(self.get_profile_value('proxy_port') or str(network_proxy.port()))
            self.proxy_username_editor.setText(self.get_profile_value('proxy_username') or str(network_proxy.user()))
            self.proxy_password_editor.setText(self.get_profile_value('proxy_password') or str(network_proxy.password()))
            self.network_status_label.setText('')
            self.network_status_label.setStyleSheet('')

        @QtCore.pyqtSlot(QtNetwork.QNetworkProxy, QtNetwork.QAuthenticator)
        def proxy_authentication_required(self, proxy, authenticator):
            pass

        @QtCore.pyqtSlot(QtNetwork.QNetworkReply)
        def update_network_status(self, reply):
            if reply.isFinished():
                error = reply.error()
                if error == QtNetwork.QNetworkReply.NoError:
                    self.network_status_label.setText(_('Internet available.'))
                    self.network_status_label.setStyleSheet('color: green')
                    return
            self.network_status_label.setText(_('Internet not available.\n%s.'%reply.errorString()))
            self.network_status_label.setStyleSheet('color: red')

        @QtCore.pyqtSlot()
        def new_network_request(self):
            if self.network_reply and not self.network_reply.isFinished():
                self.network_reply.abort()
            if self.proxy_host_editor.text():
                proxy = QtNetwork.QNetworkProxy( QtNetwork.QNetworkProxy.HttpProxy,
                                                 self.proxy_host_editor.text(),
                                                 int( str( self.proxy_port_editor.text() ) ) )#,
                                                 #self.proxy_username_editor.text(),
                                                 #self.proxy_password_editor.text() )
                self.manager.setProxy( proxy )
            else:
                self.manager.setProxy( QtNetwork.QNetworkProxy() )
            self.network_reply = self.manager.get( QtNetwork.QNetworkRequest( QtCore.QUrl('http://aws.amazon.com') ) )

        def get_profile_value(self, key):
            current = self.current_profile()
            if current in self.profiles:
                return self.profiles[current][key]
            return ''

        def collect_info(self):
            logger.info('collecting new database profile info')
            info = {}
            profilename = self.current_profile()
            info['dialect'] = self.dialect_editor.get_value()
            info['host'] = self.host_editor.text()
            info['port'] = self.port_editor.text()
            info['database'] = self.database_name_editor.text()
            info['user'] = self.username_editor.text()
            info['pass'] = self.password_editor.text()
            info['media_location'] = self.media_location_editor.text()
            info['locale_language'] = self.language_editor.get_value()
            info['proxy_host'] = self.proxy_host_editor.text()
            info['proxy_port'] = self.proxy_port_editor.text()
            info['proxy_username'] = self.proxy_username_editor.text()
            info['proxy_password'] = self.proxy_password_editor.text()
            return profilename, info

        def fill_media_location(self):
            caption = _('Select media location')
            selected = unicode(QFileDialog.getExistingDirectory(self, caption))

            if not selected:
                return

            info = QFileInfo(selected)
            if not info.isReadable():
                self.main_widget().layout().addWidget(
                    self.not_accessible_media_path_label, 13, 1, 1, 4)
                return
            if not info.isWritable():
                self.main_widget().layout().addWidget(
                    self.not_writable_media_path_label, 13, 1, 1, 4)
                return

            self.media_location_editor.setText(selected)

        def save_profiles_to_file(self):
            caption = _('Save Profiles To a File')
            filters = _('Profiles file (*.ini)')
            path = QFileDialog.getSaveFileName(self, caption, 'profiles', filters)
            if not path:
                logger.debug('Could not save profiles to file; no path.')
                return
            store_profiles(self.profiles, to_file=path)

        def load_profiles_from_file(self):
            caption = _('Load Profiles From a File')
            filters = _('Profiles file (*.ini)')
            path = QFileDialog.getOpenFileName(self, caption, 'profiles', filters)
            if not path:
                logger.debug('Could not load profiles from file; no path.')
                return
            self.profiles = fetch_profiles(from_file=path)
            if self.profiles:
                store_profiles(self.profiles)
                os.execv(sys.executable, [sys.executable] + sys.argv)

### Navigation

- index
- modules |
- Camelot documentation »
- Module code »

Comments
blog comments powered by Disqus