Beispiel #1
0
 def _add_output_panel(self):
     """ Adds a widget to the bottom pane for django command output. """
     self._output = OutputBox()
     self._output.set_font(self._font)
     panel = self.window.get_bottom_panel()
     panel.add_item_with_stock_icon(self._output, "DjangoOutput",
                                    "Django Output", Gtk.STOCK_EXECUTE)
Beispiel #2
0
 def _add_output_panel(self):
     """ Adds a widget to the bottom pane for django command output. """
     self._output = OutputBox()
     self._output.set_font(self._font)
     panel = self.window.get_bottom_panel()
     panel.add_item_with_stock_icon(self._output, "DjangoOutput", 
                                    "Django Output", Gtk.STOCK_EXECUTE)
Beispiel #3
0
 def _add_output_panel(self):
     """ Adds a widget to the bottom pane for django command output. """
     self._output = OutputBox()
     self._output.set_font(self._font)
     panel = self.window.get_bottom_panel()
     panel.add_item_with_stock_icon(self._output, "DjangoOutput", "Django Output", Gtk.STOCK_EXECUTE)
     try:
         command = "%s --version" % (self._admin_cmd)
         self._output.run(command)
     except:
         pass
Beispiel #4
0
class Plugin(GObject.Object, Gedit.WindowActivatable):
    __gtype_name__ = "GeditDjangoProjectPlugin"
    window = GObject.property(type=Gedit.Window)
    
    def __init__(self):
        GObject.Object.__init__(self)
        self._project = None
        self._server = None
        self._output = None
        self._shell = None
        self._dbshell = None
        self._install_stock_icons()
        self._admin_cmd = "django-admin.py" 
        self._manage_cmd = "python manage.py"
        self._font = "monospace 10"
    
    def _add_dbshell_panel(self):
        """ Adds a database shell to the bottom pane. """
        logger.debug("Adding database shell panel.")
        self._dbshell = Shell()
        self._dbshell.set_font(self._font)
        panel = self.window.get_bottom_panel()
        panel.add_item_with_stock_icon(self._dbshell, "DjangoDbShell", 
                                       "Database Shell", STOCK_DBSHELL)
        self._setup_dbshell_panel()
        panel.activate_item(self._dbshell)
                                       
    def _add_output_panel(self):
        """ Adds a widget to the bottom pane for django command output. """
        self._output = OutputBox()
        self._output.set_font(self._font)
        panel = self.window.get_bottom_panel()
        panel.add_item_with_stock_icon(self._output, "DjangoOutput", 
                                       "Django Output", Gtk.STOCK_EXECUTE)
        
    def _add_server_panel(self, cwd=None):
        """ Adds a VTE widget to the bottom pane for development server. """
        logger.debug("Adding server panel.")
        self._server = DjangoServer()
        self._server.set_font(self._font)
        self._server.command = "%s runserver" % (self._manage_cmd)
        if cwd:
            self._server.cwd = cwd
        self._server.connect("server-started", self.on_server_started)
        self._server.connect("server-stopped", self.on_server_stopped)
        panel = self.window.get_bottom_panel()
        panel.add_item_with_stock_icon(self._server, "DjangoServer", 
                                       "Django Server", STOCK_SERVER)
        self._setup_server_panel()
    
    def _add_shell_panel(self):
        """ Adds a python shell to the bottom pane. """
        logger.debug("Adding shell.")
        self._shell = Shell()
        self._shell.set_font(self._font)
        panel = self.window.get_bottom_panel()
        panel.add_item_with_stock_icon(self._shell, "DjangoShell", 
                                       "Python Shell", STOCK_PYTHON)
        self._setup_shell_panel()
        panel.activate_item(self._shell)
                                       
    def _add_ui(self):
        """ Merge the 'Django' menu into the Gedit menubar. """
        ui_file = os.path.join(DATA_DIR, 'menu.ui')
        manager = self.window.get_ui_manager()
        
        # global actions are always sensitive
        self._global_actions = Gtk.ActionGroup("DjangoGlobal")
        self._global_actions.add_actions([
            ('Django', None, "_Django", None, None, None),
            ('NewProject', Gtk.STOCK_NEW, "_New Project...", 
                "<Shift><Control>N", "Start a new Django project.", 
                self.on_new_project_activate),
            ('OpenProject', Gtk.STOCK_OPEN, "_Open Project", 
                "<Shift><Control>O", "Open an existing Django project.", 
                self.on_open_project_activate),
            ('NewApp', Gtk.STOCK_NEW, "New _App...", 
                "<Shift><Control>A", "Start a new Django application.", 
                self.on_new_app_activate),
        ])
        self._global_actions.add_toggle_actions([
            ('ViewServerPanel', None, "Django _Server", 
                None, "Add the Django development server to the bottom panel.", 
                self.on_view_server_panel_activate, True),
            ('ViewPythonShell', None, "_Python Shell", 
                None, "Add a Python shell to the bottom panel.", 
                self.on_view_python_shell_panel_activate, False),
            ('ViewDbShell', None, "_Database Shell", 
                None, "Add a Database shell to the bottom panel.", 
                self.on_view_db_shell_panel_activate, False),
        ])
        manager.insert_action_group(self._global_actions)       
        
        # project actions are sensitive when a project is open
        self._project_actions = Gtk.ActionGroup("DjangoProject")
        self._project_actions.add_actions([
            ('CloseProject', Gtk.STOCK_CLOSE, "_Close Project...", 
                "", "Close the current Django project.", 
                self.on_close_project_activate),
            ('Manage', None, "_Manage", None, None, None),
            ('SyncDb', Gtk.STOCK_REFRESH, "_Synchronize Database", None, 
                "Creates the database tables for all apps whose tables have not already been created.", 
                self.on_manage_command_activate),
            ('Cleanup', None, "_Cleanup", None, 
                "Clean out old data from the database.", 
                self.on_manage_command_activate),
            ('DiffSettings', None, "Di_ff Settings", None, 
                "Displays differences between the current settings and Django's default settings.", 
                self.on_manage_command_activate),
            ('InspectDb', None, "_Inspect Database", None, 
                "Introspects the database and outputs a Django model module.", 
                self.on_manage_command_activate),
            ('Flush', None, "_Flush", None, 
                "Returns the database to the state it was in immediately after syncdb was executed.", 
                self.on_manage_command_activate),
                # all clear custom flush
            ('Sql', None, "S_QL...", None, 
                "Prints the CREATE TABLE SQL statements for the given app name(s).", 
                self.on_manage_app_select_command_activate),
            ('SqlAll', None, "SQL _All...", None, 
                "Prints the CREATE TABLE and initial-data SQL statements for the given app name(s).", 
                self.on_manage_app_select_command_activate),
            ('SqlClear', None, "SQL C_lear...", None, 
                "Prints the DROP TABLE SQL statements for the given app name(s).", 
                self.on_manage_app_select_command_activate),
            ('SqlCustom', None, "SQL C_ustom...", None, 
                "Prints the custom SQL statements for the given app name(s).", 
                self.on_manage_app_select_command_activate),
            ('SqlFlush', None, "S_QL Flush", None, 
                "Prints the SQL statements that would be executed for the flush command.", 
                self.on_manage_command_activate),
            ('SqlIndexes', None, "SQL _Indexes...", None, 
                "Prints the CREATE INDEX SQL statements for the given app name(s).", 
                self.on_manage_app_select_command_activate),
            ('SqlSequenceReset', None, "SQL Sequence Rese_t...", None, 
                "Prints the SQL statements for resetting sequences for the given app name(s).", 
                self.on_manage_app_select_command_activate),
            ('Validate', None, "_Validate", None, 
                "Validates all installed models.", 
                self.on_manage_command_activate),
            ('LoadData', None, "_Load Data...", None, 
                "Loads the contents of fixtures into the database.", 
                self.on_manage_load_data_activate),
            ('DumpData', None, "_Dump Data...", None, 
                "Outputs all data in the database associated with the named application(s).", 
                self.on_manage_app_select_command_activate),
        ])
        self._project_actions.add_toggle_actions([
            ('RunServer', None, "_Run Development Server", 
                "<Shift>F5", "Start/Stop the Django development server.", 
                self.on_manage_runserver_activate, False),
        ])
        self._project_actions.set_sensitive(False)
        manager.insert_action_group(self._project_actions)   
        
        self._ui_merge_id = manager.add_ui_from_file(ui_file)
        manager.ensure_update()
    
    def close_project(self):
        self._project = None
        self._server.stop()
        self._server.cwd = None
        self._server.refresh_ui()
        if self._shell:
            self._shell.kill()
        if self._dbshell:
            self._dbshell.kill()
        self._project_actions.set_sensitive(False)
        self._update_run_server_action()
    
    def confirmation_dialog(self, message):
        """ Display a very basic informative Yes/No dialog. """
        dialog = Gtk.MessageDialog(self.window,
                                   Gtk.DialogFlags.MODAL | 
                                   Gtk.DialogFlags.DESTROY_WITH_PARENT,
                                   Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO,  
                                   message)
        dialog.set_title("Confirm")
        response = dialog.run()
        dialog.destroy()
        
        if response == Gtk.ResponseType.YES: 
            return True
        else:
            return False
            
    def do_activate(self):
        logger.debug("Activating plugin.")
        self._add_ui()
        self._add_output_panel()
        self._add_server_panel()

    def do_deactivate(self):
        logger.debug("Deactivating plugin.")
        self._remove_ui()
        self._remove_output_panel()
        self._remove_server_panel()
        self._remove_shell_panel()
        self._remove_dbshell_panel()

    def do_update_state(self):
        pass
    
    def error_dialog(self, message):
        """ Display a very basic error dialog. """
        logger.warn(message)
        dialog = Gtk.MessageDialog(self.window,
                                   Gtk.DialogFlags.MODAL | 
                                   Gtk.DialogFlags.DESTROY_WITH_PARENT,
                                   Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, 
                                   message)
        dialog.set_title("Error")
        dialog.run()
        dialog.destroy()
    
    def _install_stock_icons(self):
        """ Register custom stock icons used on the tabs. """
        logger.debug("Installing stock icons.")
        icons = (STOCK_PYTHON, STOCK_DBSHELL, STOCK_SERVER)
        factory = Gtk.IconFactory()
        for name in icons:
            filename = name + ".png"
            pixbuf = GdkPixbuf.Pixbuf.new_from_file(os.path.join(DATA_DIR, "icons", filename))
            iconset = Gtk.IconSet.new_from_pixbuf(pixbuf)
            factory.add(name, iconset)
        factory.add_default()
    
    def new_app(self, path, name):
        """ Runs the 'startapp' Django command. """ 
        try:
            self.run_admin_command("startapp %s" % name, path)
        except Exception as e:
            self.error_dialog(str(e))
            
    def new_dialog(self, title):
        filename = os.path.join(DATA_DIR, 'dialogs.ui')
        path = name = None
        builder = Gtk.Builder()
        try:
            builder.add_from_file(filename)
        except Exception as e:
            logger.error("Failed to load %s: %s." % (filename, str(e)))
            return 
        dialog = builder.get_object('new_dialog')
        if not dialog:
            logger.error("Could not find 'new_dialog' widget in %s." % filename)
            return
        dialog.set_transient_for(self.window)
        dialog.set_default_response(Gtk.ResponseType.OK)
        dialog.set_title(title)
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            name_widget = builder.get_object('name')
            project_widget = builder.get_object('directory')
            name = name_widget.get_text()
            path = project_widget.get_filename()
        dialog.destroy()
        
        return (name, path)
    
    def new_project(self, path, name):
        """ Runs the 'startproject' Django command and opens the project. """ 
        try:
            self.run_admin_command("startproject %s" % name, path)
        except Exception as e:
            self.error_dialog(str(e))
            return
        
        self.open_project(os.path.join(path, name))
    
    def new_tab_from_output(self):
        message = "Do you want to create a new document with the output?"
        if not self.confirmation_dialog(message):
            return
        tab = self.window.create_tab(False)
        buff = tab.get_view().get_buffer()
        end_iter = buff.get_end_iter()
        buff.insert(end_iter, self._output.get_last_output())
        self.window.set_active_tab(tab)
            
    def on_close_project_activate(self, action, data=None):
        self.close_project()
   
    def on_manage_command_activate(self, action, data=None):
        """ Handles simple manage.py actions. """
        command = action.get_name().lower()
        if command in ('syncdb', 'flush'):
            command += ' --noinput'
        try:
            self.run_management_command(command)
        except:
            pass # errors show up in output
        
        if command in ('inspectdb', 'sqlflush', 'diffsettings'):
            self.new_tab_from_output()
    
    def on_manage_app_select_command_activate(self, action, data=None):
        dialog = Gtk.Dialog("Select apps...",
                            self.window,
                            Gtk.DialogFlags.MODAL | 
                            Gtk.DialogFlags.DESTROY_WITH_PARENT,
                            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 
                            Gtk.STOCK_OK, Gtk.ResponseType.OK))
        dialog.set_default_size(300, 200)
        selector = AppSelector()
        selector.show_all()
        try:
            selector.load_from_settings(self._project.get_settings_module())
        except Exception as e:
            self.error_dialog("Error getting app list: %s" % str(e))
        box = dialog.get_content_area()
        box.set_border_width(10)
        box.pack_start(selector, True, True, 0)
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            files = selector.get_selected()
            command = action.get_name().lower()
            full_command = "%s %s" % (command, " ".join([f for f in files]) )
            try:
                self.run_management_command(full_command)
            except Exception as e:
                self.error_dialog(str(e))
        dialog.destroy()
        
        # only after the dialog is destroyed do we prompt them for a new tab
        if response == Gtk.ResponseType.OK:
            if command[:3] == "sql" or command in ('dumpdata'):
                self.new_tab_from_output()
        
    def on_manage_load_data_activate(self, action, data=None):
        """ Prompt user for fixtures to load into database. """
        dialog = Gtk.FileChooserDialog("Select fixtures...",
                                       self.window,
                                       Gtk.FileChooserAction.OPEN,
                                       (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 
                                       Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
        dialog.set_select_multiple(True)
        if self._project:
            dialog.set_filename(self._project.get_path())
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            files = dialog.get_files()
            command = "loaddata "+" ".join([f.get_path() for f in files]) 
            try:
                self.run_management_command(command)
            except Exception as e:
                self.error_dialog(str(e))
            
        dialog.destroy()
         
    def on_manage_runserver_activate(self, action, data=None):
        """ Run Django development server. """
        if not self._server:
            return
        try:
            if not action.get_active() and self._server.is_running():
                self._server.stop()
            elif action.get_active() and not self._server.is_running():
                self._server.start()
        except Exception as e:
            self.error_dialog(str(e))
            return
        
    def on_new_app_activate(self, action, data=None):
        """ Prompt user for new app name and directory """
        name, path = self.new_dialog("New Django App")
        if name and path:
            self.new_app(path, name)
            
    def on_new_project_activate(self, action, data=None):
        """ Prompt user for new project name and directory """
        name, path = self.new_dialog("New Django Project")
        if name and path:
            self.new_project(path, name)
    
    def on_open_project_activate(self, action, data=None):
        """ Prompt the user for the Django project directory. """
        path = None
        dialog = Gtk.FileChooserDialog("Select project folder...", self.window,
                                       Gtk.FileChooserAction.SELECT_FOLDER,
                                       (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 
                                        Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
        response = dialog.run()
        if response == Gtk.ResponseType.OK: 
            path = dialog.get_filename()
        dialog.destroy()
        if path:
            self.open_project(path)
    
    def on_server_started(self, server, pid, data=None):
        self._project_actions.get_action("RunServer").set_active(True)
        panel = self.window.get_bottom_panel()
        panel.activate_item(self._server)
                
    def on_server_stopped(self, server, pid, data=None):
        self._project_actions.get_action("RunServer").set_active(False)
        panel = self.window.get_bottom_panel()
        panel.activate_item(self._server)
    
    def on_view_db_shell_panel_activate(self, action, data=None):
        """ Show/Hide database shell from main menu. """
        if action.get_active():
            self._add_dbshell_panel()
        else:
            self._remove_dbshell_panel()
        
    def on_view_python_shell_panel_activate(self, action, data=None):
        """ Show/Hide python shell from main menu. """
        if action.get_active():
            self._add_shell_panel()
        else:
            self._remove_shell_panel()
        
    def on_view_server_panel_activate(self, action, data=None):
        """ Show/Hide development server from main menu. """
        if action.get_active():
            self._add_server_panel()
        else:
            self._remove_server_panel()
        self._update_run_server_action()
        
    def open_project(self, path):
        logger.debug("Opening Django project: %s" % path)
        if self._project:
            self.close_project()
        try:
            self._project = DjangoProject(path)
        except IOError as e:
            self.error_dialog("Could not open project: %s" % str(e))
            return

        self._output.cwd = self._project.get_path()
        self._setup_server_panel()
        self._setup_shell_panel()
        self._setup_dbshell_panel()
        self._project_actions.set_sensitive(True)
        self._update_run_server_action()
        
        # print version as it may have changed due to virtualenv
        try:
            #command = "%s --version" % (self._admin_cmd)
            #self._output.run(command)
            self.run_admin_command("--version" , path)
        except:
            pass

    def _setup_dbshell_panel(self):
        if self._dbshell and self._project:
            self._dbshell.cwd = self._project.get_path()
            self._dbshell.command = "%s dbshell" % self._manage_cmd
            self._dbshell.run()
    
    def _setup_server_panel(self):
        if self._server and self._project:
            self._server.cwd = self._project.get_path()
            self._server.refresh_ui()
        
    def _setup_shell_panel(self):
        if self._shell and self._project:
            self._shell.cwd = self._project.get_path()
            self._shell.command = "%s shell" % self._manage_cmd
            self._shell.run()
        
    def _remove_output_panel(self):
        """ Remove the output box from the bottom panel. """
        logger.debug("Removing output panel.")
        if self._output:
            self._remove_panel(self._output)
            self._output = None
    
    def _remove_panel(self, item):
        panel = self.window.get_bottom_panel()
        panel.remove_item(item)
        
    def _remove_server_panel(self):
        """ Stop and remove development server panel from the bottom panel. """
        if self._server:
            logger.debug("Removing server panel.")
            self._server.stop()
            self._remove_panel(self._server)
            self._server = None
            
    
    def _remove_shell_panel(self):
        """ Remove python shell from bottom panel. """
        if self._shell:
            logger.debug("Removing shell panel.")
            self._remove_panel(self._shell)
            self._shell = None
    
    def _remove_dbshell_panel(self):
        """ Remove database shell from bottom panel. """
        if self._dbshell:
            logger.debug("Removing database shell panel.")
            self._remove_panel(self._dbshell)
            self._dbshell = None
            
    def _remove_ui(self):
        """ Remove the 'Django' menu from the the Gedit menubar. """
        manager = self.window.get_ui_manager()
        manager.remove_ui(self._ui_merge_id)
        manager.remove_action_group(self._global_actions)
        manager.remove_action_group(self._project_actions)
        manager.ensure_update()
    
    def run_admin_command(self, command, path=None):
        """ Run a django-admin.py command in the output panel. """
        self.window.get_bottom_panel().activate_item(self._output)
        full_command = "%s %s" % (self._admin_cmd, command)
        deb_command =  "%s %s" % (self._admin_cmd[0:-3], command)
        original_cwd = self._output.cwd
        self._output.cwd = path
        try:
            self._output.run(full_command)
        except OSError:
            try: 
                # try without ".py" for debian/ubuntu system installs
                print(deb_command)
                self._output.run(deb_command)
            except:
                raise Exception("Could not execute django-admin.py command.\nIs Django installed?")
        finally:
            self._output.cwd = original_cwd
            
    def run_management_command(self, command):
        """ Run a manage.py command in the output panel. """
        self.window.get_bottom_panel().activate_item(self._output)
        full_command = "%s %s" % (self._manage_cmd, command)
        self._output.run(full_command)
    
    def _update_run_server_action(self):
        if not self._server or not self._project:
            self._project_actions.get_action("RunServer").set_sensitive(False)
        else:
            self._project_actions.get_action("RunServer").set_sensitive(True)
Beispiel #5
0
class Plugin(GObject.Object, Gedit.WindowActivatable):
    __gtype_name__ = "GeditDjangoProjectPlugin"
    window = GObject.property(type=Gedit.Window)

    def __init__(self):
        GObject.Object.__init__(self)
        self._project = None
        self._server = None
        self._output = None
        self._shell = None
        self._dbshell = None
        self._install_stock_icons()
        self._admin_cmd = "django-admin.py"
        self._manage_cmd = "python manage.py"
        self._font = "monospace 10"

    def _add_dbshell_panel(self):
        """ Adds a database shell to the bottom pane. """
        logger.debug("Adding database shell panel.")
        self._dbshell = Shell()
        self._dbshell.set_font(self._font)
        panel = self.window.get_bottom_panel()
        panel.add_item_with_stock_icon(self._dbshell, "DjangoDbShell",
                                       "Database Shell", STOCK_DBSHELL)
        self._setup_dbshell_panel()
        panel.activate_item(self._dbshell)

    def _add_output_panel(self):
        """ Adds a widget to the bottom pane for django command output. """
        self._output = OutputBox()
        self._output.set_font(self._font)
        panel = self.window.get_bottom_panel()
        panel.add_item_with_stock_icon(self._output, "DjangoOutput",
                                       "Django Output", Gtk.STOCK_EXECUTE)

    def _add_server_panel(self, cwd=None):
        """ Adds a VTE widget to the bottom pane for development server. """
        logger.debug("Adding server panel.")
        self._server = DjangoServer()
        self._server.set_font(self._font)
        self._server.command = "%s runserver" % (self._manage_cmd)
        if cwd:
            self._server.cwd = cwd
        self._server.connect("server-started", self.on_server_started)
        self._server.connect("server-stopped", self.on_server_stopped)
        panel = self.window.get_bottom_panel()
        panel.add_item_with_stock_icon(self._server, "DjangoServer",
                                       "Django Server", STOCK_SERVER)
        self._setup_server_panel()

    def _add_shell_panel(self):
        """ Adds a python shell to the bottom pane. """
        logger.debug("Adding shell.")
        self._shell = Shell()
        self._shell.set_font(self._font)
        panel = self.window.get_bottom_panel()
        panel.add_item_with_stock_icon(self._shell, "DjangoShell",
                                       "Python Shell", STOCK_PYTHON)
        self._setup_shell_panel()
        panel.activate_item(self._shell)

    def _add_ui(self):
        """ Merge the 'Django' menu into the Gedit menubar. """
        ui_file = os.path.join(DATA_DIR, 'menu.ui')
        manager = self.window.get_ui_manager()

        # global actions are always sensitive
        self._global_actions = Gtk.ActionGroup("DjangoGlobal")
        self._global_actions.add_actions([
            ('Django', None, "_Django", None, None, None),
            ('NewProject', Gtk.STOCK_NEW, "_New Project...",
             "<Shift><Control>N", "Start a new Django project.",
             self.on_new_project_activate),
            ('OpenProject', Gtk.STOCK_OPEN, "_Open Project",
             "<Shift><Control>O", "Open an existing Django project.",
             self.on_open_project_activate),
            ('NewApp', Gtk.STOCK_NEW, "New _App...", "<Shift><Control>A",
             "Start a new Django application.", self.on_new_app_activate),
        ])
        self._global_actions.add_toggle_actions([
            ('ViewServerPanel', None, "Django _Server", None,
             "Add the Django development server to the bottom panel.",
             self.on_view_server_panel_activate, True),
            ('ViewPythonShell', None, "_Python Shell", None,
             "Add a Python shell to the bottom panel.",
             self.on_view_python_shell_panel_activate, False),
            ('ViewDbShell', None, "_Database Shell", None,
             "Add a Database shell to the bottom panel.",
             self.on_view_db_shell_panel_activate, False),
        ])
        manager.insert_action_group(self._global_actions)

        # project actions are sensitive when a project is open
        self._project_actions = Gtk.ActionGroup("DjangoProject")
        self._project_actions.add_actions([
            ('CloseProject', Gtk.STOCK_CLOSE, "_Close Project...", "",
             "Close the current Django project.",
             self.on_close_project_activate),
            ('Manage', None, "_Manage", None, None, None),
            ('SyncDb', Gtk.STOCK_REFRESH, "_Synchronize Database", None,
             "Creates the database tables for all apps whose tables have not already been created.",
             self.on_manage_command_activate),
            ('Cleanup', None, "_Cleanup", None,
             "Clean out old data from the database.",
             self.on_manage_command_activate),
            ('DiffSettings', None, "Di_ff Settings", None,
             "Displays differences between the current settings and Django's default settings.",
             self.on_manage_command_activate),
            ('InspectDb', None, "_Inspect Database", None,
             "Introspects the database and outputs a Django model module.",
             self.on_manage_command_activate),
            ('Flush', None, "_Flush", None,
             "Returns the database to the state it was in immediately after syncdb was executed.",
             self.on_manage_command_activate),
            # all clear custom flush
            ('Sql', None, "S_QL...", None,
             "Prints the CREATE TABLE SQL statements for the given app name(s).",
             self.on_manage_app_select_command_activate),
            ('SqlAll', None, "SQL _All...", None,
             "Prints the CREATE TABLE and initial-data SQL statements for the given app name(s).",
             self.on_manage_app_select_command_activate),
            ('SqlClear', None, "SQL C_lear...", None,
             "Prints the DROP TABLE SQL statements for the given app name(s).",
             self.on_manage_app_select_command_activate),
            ('SqlCustom', None, "SQL C_ustom...", None,
             "Prints the custom SQL statements for the given app name(s).",
             self.on_manage_app_select_command_activate),
            ('SqlFlush', None, "S_QL Flush", None,
             "Prints the SQL statements that would be executed for the flush command.",
             self.on_manage_command_activate),
            ('SqlIndexes', None, "SQL _Indexes...", None,
             "Prints the CREATE INDEX SQL statements for the given app name(s).",
             self.on_manage_app_select_command_activate),
            ('SqlSequenceReset', None, "SQL Sequence Rese_t...", None,
             "Prints the SQL statements for resetting sequences for the given app name(s).",
             self.on_manage_app_select_command_activate),
            ('Validate', None, "_Validate", None,
             "Validates all installed models.",
             self.on_manage_command_activate),
            ('LoadData', None, "_Load Data...", None,
             "Loads the contents of fixtures into the database.",
             self.on_manage_load_data_activate),
            ('DumpData', None, "_Dump Data...", None,
             "Outputs all data in the database associated with the named application(s).",
             self.on_manage_app_select_command_activate),
        ])
        self._project_actions.add_toggle_actions([
            ('RunServer', None, "_Run Development Server", "<Shift>F5",
             "Start/Stop the Django development server.",
             self.on_manage_runserver_activate, False),
        ])
        self._project_actions.set_sensitive(False)
        manager.insert_action_group(self._project_actions)

        self._ui_merge_id = manager.add_ui_from_file(ui_file)
        manager.ensure_update()

    def close_project(self):
        self._project = None
        self._server.stop()
        self._server.cwd = None
        self._server.refresh_ui()
        if self._shell:
            self._shell.kill()
        if self._dbshell:
            self._dbshell.kill()
        self._project_actions.set_sensitive(False)
        self._update_run_server_action()

    def confirmation_dialog(self, message):
        """ Display a very basic informative Yes/No dialog. """
        dialog = Gtk.MessageDialog(
            self.window,
            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
            Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, message)
        dialog.set_title("Confirm")
        response = dialog.run()
        dialog.destroy()

        if response == Gtk.ResponseType.YES:
            return True
        else:
            return False

    def do_activate(self):
        logger.debug("Activating plugin.")
        self._add_ui()
        self._add_output_panel()
        self._add_server_panel()

    def do_deactivate(self):
        logger.debug("Deactivating plugin.")
        self._remove_ui()
        self._remove_output_panel()
        self._remove_server_panel()
        self._remove_shell_panel()
        self._remove_dbshell_panel()

    def do_update_state(self):
        pass

    def error_dialog(self, message):
        """ Display a very basic error dialog. """
        logger.warn(message)
        dialog = Gtk.MessageDialog(
            self.window,
            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
            Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, message)
        dialog.set_title("Error")
        dialog.run()
        dialog.destroy()

    def _install_stock_icons(self):
        """ Register custom stock icons used on the tabs. """
        logger.debug("Installing stock icons.")
        icons = (STOCK_PYTHON, STOCK_DBSHELL, STOCK_SERVER)
        factory = Gtk.IconFactory()
        for name in icons:
            filename = name + ".png"
            pixbuf = GdkPixbuf.Pixbuf.new_from_file(
                os.path.join(DATA_DIR, "icons", filename))
            iconset = Gtk.IconSet.new_from_pixbuf(pixbuf)
            factory.add(name, iconset)
        factory.add_default()

    def new_app(self, path, name):
        """ Runs the 'startapp' Django command. """
        try:
            self.run_admin_command("startapp %s" % name, path)
        except Exception as e:
            self.error_dialog(str(e))

    def new_dialog(self, title):
        filename = os.path.join(DATA_DIR, 'dialogs.ui')
        path = name = None
        builder = Gtk.Builder()
        try:
            builder.add_from_file(filename)
        except Exception as e:
            logger.error("Failed to load %s: %s." % (filename, str(e)))
            return
        dialog = builder.get_object('new_dialog')
        if not dialog:
            logger.error("Could not find 'new_dialog' widget in %s." %
                         filename)
            return
        dialog.set_transient_for(self.window)
        dialog.set_default_response(Gtk.ResponseType.OK)
        dialog.set_title(title)
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            name_widget = builder.get_object('name')
            project_widget = builder.get_object('directory')
            name = name_widget.get_text()
            path = project_widget.get_filename()
        dialog.destroy()

        return (name, path)

    def new_project(self, path, name):
        """ Runs the 'startproject' Django command and opens the project. """
        try:
            self.run_admin_command("startproject %s" % name, path)
        except Exception as e:
            self.error_dialog(str(e))
            return

        self.open_project(os.path.join(path, name))

    def new_tab_from_output(self):
        message = "Do you want to create a new document with the output?"
        if not self.confirmation_dialog(message):
            return
        tab = self.window.create_tab(False)
        buff = tab.get_view().get_buffer()
        end_iter = buff.get_end_iter()
        buff.insert(end_iter, self._output.get_last_output())
        self.window.set_active_tab(tab)

    def on_close_project_activate(self, action, data=None):
        self.close_project()

    def on_manage_command_activate(self, action, data=None):
        """ Handles simple manage.py actions. """
        command = action.get_name().lower()
        if command in ('syncdb', 'flush'):
            command += ' --noinput'
        try:
            self.run_management_command(command)
        except:
            pass  # errors show up in output

        if command in ('inspectdb', 'sqlflush', 'diffsettings'):
            self.new_tab_from_output()

    def on_manage_app_select_command_activate(self, action, data=None):
        dialog = Gtk.Dialog(
            "Select apps...", self.window,
            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK,
             Gtk.ResponseType.OK))
        dialog.set_default_size(300, 200)
        selector = AppSelector()
        selector.show_all()
        try:
            selector.load_from_settings(self._project.get_settings_module())
        except Exception as e:
            self.error_dialog("Error getting app list: %s" % str(e))
        box = dialog.get_content_area()
        box.set_border_width(10)
        box.pack_start(selector, True, True, 0)
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            files = selector.get_selected()
            command = action.get_name().lower()
            full_command = "%s %s" % (command, " ".join([f for f in files]))
            try:
                self.run_management_command(full_command)
            except Exception as e:
                self.error_dialog(str(e))
        dialog.destroy()

        # only after the dialog is destroyed do we prompt them for a new tab
        if response == Gtk.ResponseType.OK:
            if command[:3] == "sql" or command in ('dumpdata'):
                self.new_tab_from_output()

    def on_manage_load_data_activate(self, action, data=None):
        """ Prompt user for fixtures to load into database. """
        dialog = Gtk.FileChooserDialog(
            "Select fixtures...", self.window, Gtk.FileChooserAction.OPEN,
            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN,
             Gtk.ResponseType.OK))
        dialog.set_select_multiple(True)
        if self._project:
            dialog.set_filename(self._project.get_path())
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            files = dialog.get_files()
            command = "loaddata " + " ".join([f.get_path() for f in files])
            try:
                self.run_management_command(command)
            except Exception as e:
                self.error_dialog(str(e))

        dialog.destroy()

    def on_manage_runserver_activate(self, action, data=None):
        """ Run Django development server. """
        if not self._server:
            return
        try:
            if not action.get_active() and self._server.is_running():
                self._server.stop()
            elif action.get_active() and not self._server.is_running():
                self._server.start()
        except Exception as e:
            self.error_dialog(str(e))
            return

    def on_new_app_activate(self, action, data=None):
        """ Prompt user for new app name and directory """
        name, path = self.new_dialog("New Django App")
        if name and path:
            self.new_app(path, name)

    def on_new_project_activate(self, action, data=None):
        """ Prompt user for new project name and directory """
        name, path = self.new_dialog("New Django Project")
        if name and path:
            self.new_project(path, name)

    def on_open_project_activate(self, action, data=None):
        """ Prompt the user for the Django project directory. """
        path = None
        dialog = Gtk.FileChooserDialog(
            "Select project folder...", self.window,
            Gtk.FileChooserAction.SELECT_FOLDER,
            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN,
             Gtk.ResponseType.OK))
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            path = dialog.get_filename()
        dialog.destroy()
        if path:
            self.open_project(path)

    def on_server_started(self, server, pid, data=None):
        self._project_actions.get_action("RunServer").set_active(True)
        panel = self.window.get_bottom_panel()
        panel.activate_item(self._server)

    def on_server_stopped(self, server, pid, data=None):
        self._project_actions.get_action("RunServer").set_active(False)
        panel = self.window.get_bottom_panel()
        panel.activate_item(self._server)

    def on_view_db_shell_panel_activate(self, action, data=None):
        """ Show/Hide database shell from main menu. """
        if action.get_active():
            self._add_dbshell_panel()
        else:
            self._remove_dbshell_panel()

    def on_view_python_shell_panel_activate(self, action, data=None):
        """ Show/Hide python shell from main menu. """
        if action.get_active():
            self._add_shell_panel()
        else:
            self._remove_shell_panel()

    def on_view_server_panel_activate(self, action, data=None):
        """ Show/Hide development server from main menu. """
        if action.get_active():
            self._add_server_panel()
        else:
            self._remove_server_panel()
        self._update_run_server_action()

    def open_project(self, path):
        logger.debug("Opening Django project: %s" % path)
        if self._project:
            self.close_project()
        try:
            self._project = DjangoProject(path)
        except IOError as e:
            self.error_dialog("Could not open project: %s" % str(e))
            return

        self._output.cwd = self._project.get_path()
        self._setup_server_panel()
        self._setup_shell_panel()
        self._setup_dbshell_panel()
        self._project_actions.set_sensitive(True)
        self._update_run_server_action()

        # print version as it may have changed due to virtualenv
        try:
            #command = "%s --version" % (self._admin_cmd)
            #self._output.run(command)
            self.run_admin_command("--version", path)
        except:
            pass

    def _setup_dbshell_panel(self):
        if self._dbshell and self._project:
            self._dbshell.cwd = self._project.get_path()
            self._dbshell.command = "%s dbshell" % self._manage_cmd
            self._dbshell.run()

    def _setup_server_panel(self):
        if self._server and self._project:
            self._server.cwd = self._project.get_path()
            self._server.refresh_ui()

    def _setup_shell_panel(self):
        if self._shell and self._project:
            self._shell.cwd = self._project.get_path()
            self._shell.command = "%s shell" % self._manage_cmd
            self._shell.run()

    def _remove_output_panel(self):
        """ Remove the output box from the bottom panel. """
        logger.debug("Removing output panel.")
        if self._output:
            self._remove_panel(self._output)
            self._output = None

    def _remove_panel(self, item):
        panel = self.window.get_bottom_panel()
        panel.remove_item(item)

    def _remove_server_panel(self):
        """ Stop and remove development server panel from the bottom panel. """
        if self._server:
            logger.debug("Removing server panel.")
            self._server.stop()
            self._remove_panel(self._server)
            self._server = None

    def _remove_shell_panel(self):
        """ Remove python shell from bottom panel. """
        if self._shell:
            logger.debug("Removing shell panel.")
            self._remove_panel(self._shell)
            self._shell = None

    def _remove_dbshell_panel(self):
        """ Remove database shell from bottom panel. """
        if self._dbshell:
            logger.debug("Removing database shell panel.")
            self._remove_panel(self._dbshell)
            self._dbshell = None

    def _remove_ui(self):
        """ Remove the 'Django' menu from the the Gedit menubar. """
        manager = self.window.get_ui_manager()
        manager.remove_ui(self._ui_merge_id)
        manager.remove_action_group(self._global_actions)
        manager.remove_action_group(self._project_actions)
        manager.ensure_update()

    def run_admin_command(self, command, path=None):
        """ Run a django-admin.py command in the output panel. """
        self.window.get_bottom_panel().activate_item(self._output)
        full_command = "%s %s" % (self._admin_cmd, command)
        deb_command = "%s %s" % (self._admin_cmd[0:-3], command)
        original_cwd = self._output.cwd
        self._output.cwd = path
        try:
            self._output.run(full_command)
        except OSError:
            try:
                # try without ".py" for debian/ubuntu system installs
                print(deb_command)
                self._output.run(deb_command)
            except:
                raise Exception(
                    "Could not execute django-admin.py command.\nIs Django installed?"
                )
        finally:
            self._output.cwd = original_cwd

    def run_management_command(self, command):
        """ Run a manage.py command in the output panel. """
        self.window.get_bottom_panel().activate_item(self._output)
        full_command = "%s %s" % (self._manage_cmd, command)
        self._output.run(full_command)

    def _update_run_server_action(self):
        if not self._server or not self._project:
            self._project_actions.get_action("RunServer").set_sensitive(False)
        else:
            self._project_actions.get_action("RunServer").set_sensitive(True)