├── .gitignore ├── djangoproject ├── __init__.py ├── data │ ├── icons │ │ ├── dbshell.png │ │ ├── python.png │ │ ├── server.png │ │ └── terminal.png │ ├── menu.ui │ └── dialogs.ui ├── appselector.py ├── shell.py ├── output.py ├── server.py ├── project.py └── plugin.py ├── djangoproject.plugin └── README.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | -------------------------------------------------------------------------------- /djangoproject/__init__.py: -------------------------------------------------------------------------------- 1 | from plugin import Plugin 2 | 3 | -------------------------------------------------------------------------------- /djangoproject/data/icons/dbshell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quixotix/gedit-django-project/HEAD/djangoproject/data/icons/dbshell.png -------------------------------------------------------------------------------- /djangoproject/data/icons/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quixotix/gedit-django-project/HEAD/djangoproject/data/icons/python.png -------------------------------------------------------------------------------- /djangoproject/data/icons/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quixotix/gedit-django-project/HEAD/djangoproject/data/icons/server.png -------------------------------------------------------------------------------- /djangoproject/data/icons/terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quixotix/gedit-django-project/HEAD/djangoproject/data/icons/terminal.png -------------------------------------------------------------------------------- /djangoproject.plugin: -------------------------------------------------------------------------------- 1 | [Plugin] 2 | Loader=python 3 | Module=djangoproject 4 | IAge=3 5 | Name=Django Project 6 | Description=Manage Django projects from Gedit. 7 | Authors=Micah Carrick 8 | Copyright=Copyright © 2012 Micah Carrick 9 | Website=https://github.com/Quixotix/gedit-django-project 10 | Version=3.1.3 11 | 12 | -------------------------------------------------------------------------------- /djangoproject/data/menu.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Gedit Django Project 2 | ==================== 3 | 4 | This is a plugin for [Gedit][2], the official text editor of the GNOME desktop 5 | environment. This plugin is for Gedit versions 3 and above. **This plugin is NOT 6 | compatible with Gedit 2.x**. 7 | 8 | Gedit Django Project adds GUI interfaces for [django-admin.py and manage.py][1] 9 | commands within Gedit and simplifies working with Django projects. 10 | 11 | 12 | Features 13 | -------- 14 | 15 | * Create new projects (`manage.py startproject`) and apps (`manage.py startapp`). 16 | * Supports *most* of the django-admin.py and manage.py commands. 17 | * Run the Django development server (`manage.py runserver`) in a dedicated bottom panel. 18 | * Run the interactive Python interpreter (`manage.py shell`) in a dedicated 19 | bottom panel. 20 | * Run the interactive database shell (`manage.py dbshell`) in a dedicated bottom 21 | panel. 22 | * Management commands which produce usable output such as `dumpdata`, `sql`, 23 | `inspectdb` can optionally be loaded into a new Gedit document. 24 | * Select appropriate apps from a GUI list of available apps for management 25 | commands which take a list of apps as parameters. 26 | 27 | 28 | Installation 29 | ------------ 30 | 31 | 1. Download the source code form this repository or using the `git clone` command. 32 | 2. Copy the files to the Gedit plugins directory `~/.local/share/gedit/plugins/`. 33 | 3. Restart Gedit. 34 | 35 | #### For Example... 36 | 37 | git clone git://github.com/Quixotix/gedit-django-project.git 38 | cp djangoproject.plugin ~/.local/share/gedit/plugins/ 39 | cp -R djangoproject ~/.local/share/gedit/plugins/ 40 | 41 | 42 | 43 | 44 | Screenshot 45 | ---------- 46 | 47 | ![Screenshot showing Django menu and bottom panels in Gedit][3] 48 | 49 | [1]: http://docs.djangoproject.com/en/dev/ref/django-admin/ 50 | [2]: http://www.gedit.org 51 | [3]: http://www.micahcarrick.com/images/gedit-django-project/gedit-django-project.jpg 52 | 53 | 54 | -------------------------------------------------------------------------------- /djangoproject/appselector.py: -------------------------------------------------------------------------------- 1 | import imp 2 | from gi.repository import GObject, Gtk 3 | 4 | class AppSelector(Gtk.VBox): 5 | __gtype_name__ = "DjangoProjectAppSelector" 6 | def __init__(self, settings_module=None): 7 | Gtk.VBox.__init__(self, homogeneous=False, spacing=0) 8 | self._model = Gtk.ListStore(GObject.TYPE_INT, GObject.TYPE_STRING) 9 | treeview = Gtk.TreeView.new_with_model(self._model) 10 | treeview.set_headers_visible(False) 11 | column = Gtk.TreeViewColumn("Apps") 12 | cell = Gtk.CellRendererToggle() 13 | cell.set_activatable(True) 14 | cell.connect("toggled", self.on_toggled, (self._model, 0)) 15 | column.pack_start(cell, False) 16 | column.add_attribute(cell, "active", 0) 17 | cell = Gtk.CellRendererText() 18 | column.pack_start(cell, True) 19 | column.add_attribute(cell, "text", 1) 20 | treeview.append_column(column) 21 | scrolled = Gtk.ScrolledWindow() 22 | scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) 23 | scrolled.add(treeview) 24 | scrolled.set_shadow_type(Gtk.ShadowType.IN) 25 | scrolled.show_all() 26 | self.pack_start(scrolled, True, True, 0) 27 | if settings_module: 28 | self.load_from_settings(settings_module) 29 | 30 | def load_from_settings(self, settings_module): 31 | [self._model.append((False, app,)) for app in settings_module.INSTALLED_APPS] 32 | 33 | def get_selected(self, short_names=True): 34 | selected = [] 35 | if short_names: 36 | [selected.append(row[1][row[1].rfind(".")+1:]) for row in self._model if row[0]] 37 | else: 38 | [selected.append(row[1]) for row in self._model if row[0]] 39 | return selected 40 | 41 | def on_toggled(self, renderer, path, data=None): 42 | model, column = data 43 | model[path][column] = not model[path][column] 44 | 45 | -------------------------------------------------------------------------------- /djangoproject/shell.py: -------------------------------------------------------------------------------- 1 | import os 2 | import signal 3 | import subprocess 4 | import shlex 5 | import logging 6 | from gi.repository import GObject, Gtk, Vte, GLib 7 | 8 | logging.basicConfig() 9 | LOG_LEVEL = logging.DEBUG 10 | logger = logging.getLogger(__name__) 11 | logger.setLevel(LOG_LEVEL) 12 | 13 | class Shell(Gtk.HBox): 14 | """ 15 | A terminal widget setup to run as shell. The command will automatically 16 | re-start when it is killed. 17 | """ 18 | __gtype_name__ = "DjangoProjectShell" 19 | 20 | def __init__(self): 21 | Gtk.HBox.__init__(self, homogeneous=False, spacing=0) 22 | self.command = None 23 | self.cwd = None 24 | self._pid = None 25 | self._vte = Vte.Terminal() 26 | self._vte.set_size(self._vte.get_column_count(), 5) 27 | self._vte.set_size_request(200, 50) 28 | self._vte.set_font_from_string("monospace 10") 29 | self._vte.connect("child-exited", self.on_child_exited) 30 | self.pack_start(self._vte, True, True, 0) 31 | scrollbar = Gtk.Scrollbar.new(Gtk.Orientation.VERTICAL, self._vte.get_vadjustment()) 32 | self.pack_start(scrollbar, False, False, 0) 33 | self.show_all() 34 | 35 | def on_child_exited(self, vte, data=None): 36 | logger.debug("Child exited: %s" % self._pid); 37 | if self._running: 38 | self.run() 39 | 40 | def run(self): 41 | self._running = True 42 | args = shlex.split(self.command) 43 | self._pid = self._vte.fork_command_full(Vte.PtyFlags.DEFAULT, 44 | self.cwd, 45 | args, 46 | None, 47 | GLib.SpawnFlags.SEARCH_PATH, 48 | None, 49 | None)[1] 50 | logger.debug("Running %s (pid %s)" % (self.command, self._pid)) 51 | 52 | def kill(self): 53 | self._running = False 54 | if self._pid: 55 | os.kill(self._pid, signal.SIGKILL) 56 | self._vte.reset(False, True) 57 | 58 | def set_font(self, font_name): 59 | self._vte.set_font_from_string(font_name) 60 | 61 | -------------------------------------------------------------------------------- /djangoproject/output.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import shlex 4 | import logging 5 | from gi.repository import GObject, Gtk, GLib, Pango 6 | 7 | logging.basicConfig() 8 | LOG_LEVEL = logging.ERROR 9 | logger = logging.getLogger(__name__) 10 | logger.setLevel(LOG_LEVEL) 11 | 12 | class OutputBox(Gtk.HBox): 13 | """ 14 | A widget to display the output of running django commands. 15 | """ 16 | __gtype_name__ = "DjangoProjectOutputBox" 17 | 18 | def __init__(self): 19 | Gtk.HBox.__init__(self, homogeneous=False, spacing=4) 20 | # configurable options 21 | self.cwd = None 22 | self._last_output = None 23 | scrolled = Gtk.ScrolledWindow() 24 | self._view = self._create_view() 25 | scrolled.add(self._view) 26 | self.pack_start(scrolled, True, True, 0) 27 | self.set_font("monospace 10") 28 | self.show_all() 29 | 30 | def _create_view(self): 31 | """ Create the gtk.TextView used for shell output """ 32 | view = Gtk.TextView() 33 | view.set_editable(False) 34 | buff = view.get_buffer() 35 | buff.create_tag('bold', foreground='#7F7F7F', weight=Pango.Weight.BOLD) 36 | buff.create_tag('info', foreground='#7F7F7F', style=Pango.Style.OBLIQUE) 37 | buff.create_tag('error', foreground='red') 38 | return view 39 | 40 | def get_last_output(self): 41 | return self._last_output 42 | 43 | def set_font(self, font_name): 44 | font_desc = Pango.FontDescription(font_name) 45 | self._view.modify_font(font_desc) 46 | 47 | def run(self, command, cwd=None): 48 | """ Run a command inserting output into the gtk.TextView """ 49 | self.insert("Running: ", 'info') 50 | self.insert("%s\n" % command, 'bold') 51 | args = shlex.split(command) 52 | output = None 53 | if cwd is None: 54 | cwd = self.cwd 55 | logger.debug(cwd) 56 | process = subprocess.Popen(args, 0, 57 | shell=False, 58 | stdout=subprocess.PIPE, 59 | stderr=subprocess.PIPE, 60 | cwd=cwd) 61 | output = process.communicate() 62 | if output[0]: 63 | self.insert(output[0]) 64 | self._last_output = output[0] 65 | if output[1]: 66 | self.insert(output[1], 'error') 67 | 68 | self.insert("\nExit: ", 'info') 69 | self.insert("%s\n\n" % process.returncode, 'bold') 70 | 71 | if output[1] and process.returncode <> 0: 72 | raise Exception(output[1]) 73 | 74 | def insert(self, text, tag_name=None): 75 | """ Insert text, apply tag, and scroll to end iter """ 76 | buff = self._view.get_buffer() 77 | end_iter = buff.get_end_iter() 78 | buff.insert(end_iter, "%s" % text) 79 | if tag_name: 80 | offset = buff.get_char_count() - len(text) 81 | start_iter = buff.get_iter_at_offset(offset) 82 | end_iter = buff.get_end_iter() 83 | buff.apply_tag_by_name(tag_name, start_iter, end_iter) 84 | while Gtk.events_pending(): 85 | Gtk.main_iteration() 86 | self._view.scroll_to_iter(buff.get_end_iter(), 0.0, True, 0.0, 0.0) 87 | 88 | -------------------------------------------------------------------------------- /djangoproject/server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import signal 3 | import subprocess 4 | import shlex 5 | import logging 6 | from gi.repository import GObject, Gtk, Vte, GLib 7 | 8 | logging.basicConfig() 9 | LOG_LEVEL = logging.DEBUG 10 | logger = logging.getLogger(__name__) 11 | logger.setLevel(LOG_LEVEL) 12 | 13 | class DjangoServer(Gtk.HBox): 14 | """ 15 | A terminal widget setup to run the Django development server management 16 | command providing Start/Stop button. 17 | 18 | Start and stop the server by calling start() and stop() methods. 19 | 20 | Connect to the "server-started" and "server-stopped" signals to update UI as 21 | the server may stop for any number of reasons, including errors in Django 22 | code, pressing , or the stop button on the widget. 23 | """ 24 | __gtype_name__ = "DjangoProjectServer" 25 | __gsignals__ = { 26 | "server-started": 27 | (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, 28 | (GObject.TYPE_PYOBJECT,)), 29 | "server-stopped": 30 | (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, 31 | (GObject.TYPE_PYOBJECT,)), 32 | } 33 | 34 | def __init__(self): 35 | Gtk.HBox.__init__(self, homogeneous=False, spacing=0) 36 | self.command = "python manage.py runserver" 37 | self.cwd = None 38 | self._pid = None 39 | self._vte = Vte.Terminal() 40 | self._vte.set_size(self._vte.get_column_count(), 5) 41 | self._vte.set_size_request(200, 50) 42 | self._vte.set_font_from_string("monospace 10") 43 | self._vte.connect("child-exited", self.on_child_exited) 44 | self.pack_start(self._vte, True, True, 0) 45 | scrollbar = Gtk.Scrollbar.new(Gtk.Orientation.VERTICAL, self._vte.get_vadjustment()) 46 | self.pack_start(scrollbar, False, False, 0) 47 | self._button = Gtk.Button() 48 | self._button.connect("clicked", self.on_button_clicked) 49 | box = Gtk.VButtonBox() 50 | box.set_border_width(5) 51 | box.set_layout(Gtk.ButtonBoxStyle.START) 52 | box.add(self._button) 53 | self.pack_start(box, False, False, 0) 54 | self._start_icon = Gtk.Image.new_from_stock(Gtk.STOCK_EXECUTE, Gtk.IconSize.BUTTON) 55 | self._stop_icon = Gtk.Image.new_from_stock(Gtk.STOCK_STOP, Gtk.IconSize.BUTTON) 56 | self.refresh_ui() 57 | self.show_all() 58 | 59 | def is_running(self): 60 | if self._pid is not None: 61 | return True 62 | else: 63 | return False 64 | 65 | def on_button_clicked(self, widget=None, data=None): 66 | if self.is_running(): 67 | self.stop() 68 | else: 69 | self.start() 70 | 71 | def on_child_exited(self, vte, data=None): 72 | pid = self._pid 73 | self._pid = None 74 | self.refresh_ui() 75 | self.emit("server-stopped", pid) 76 | logger.debug("Development server stopped (pid %s)" % pid) 77 | 78 | def set_font(self, font_name): 79 | self._vte.set_font_from_string(font_name) 80 | 81 | def start(self): 82 | if self.is_running(): 83 | return 84 | args = shlex.split(self.command) 85 | self._pid = self._vte.fork_command_full(Vte.PtyFlags.DEFAULT, 86 | self.cwd, 87 | args, 88 | None, 89 | GLib.SpawnFlags.SEARCH_PATH, 90 | None, 91 | None)[1] 92 | self.refresh_ui() 93 | self.emit("server-started", self._pid) 94 | logger.debug("Development server started (pid %s)" % self._pid) 95 | 96 | def stop(self): 97 | if self.is_running(): 98 | os.kill(self._pid, signal.SIGKILL) 99 | 100 | def refresh_ui(self): 101 | if self.is_running(): 102 | self._button.set_image(self._stop_icon) 103 | self._button.set_label("Stop") 104 | else: 105 | self._button.set_image(self._start_icon) 106 | self._button.set_label("Start") 107 | 108 | self._button.set_sensitive(bool(self.cwd)) 109 | 110 | -------------------------------------------------------------------------------- /djangoproject/project.py: -------------------------------------------------------------------------------- 1 | import os 2 | import imp 3 | import sys 4 | import logging 5 | 6 | logging.basicConfig() 7 | LOG_LEVEL = logging.DEBUG 8 | logger = logging.getLogger(__name__) 9 | logger.setLevel(LOG_LEVEL) 10 | 11 | class DjangoProject(object): 12 | 13 | def __init__(self, path): 14 | self.set_path(path) 15 | 16 | def close_project(self): 17 | sys.path.remove(self._path) 18 | try: 19 | del os.environ['DJANGO_SETTINGS_MODULE'] 20 | except: 21 | pass 22 | 23 | def activate_virtualenv(self, path): 24 | """ 25 | Activates the virtualenv if necessary. Traverses the projects path up 26 | to the file system root and checks if 'bin/activate_this.py' exists 27 | in the directories. If this file is found, it gets loaded. 28 | """ 29 | parent_path = os.path.dirname(path) 30 | 31 | if parent_path == path: 32 | logger.debug("Virtual environment not found.") 33 | return 34 | 35 | for dirname in os.listdir(path): 36 | venv = os.path.join(path, dirname) 37 | activate_this = os.path.join(venv, 'bin', 'activate_this.py') 38 | if os.path.isfile(activate_this): 39 | imp.load_source('activate_this', activate_this) 40 | logger.debug("Activated virtual environment: %s" % venv) 41 | return 42 | 43 | self.activate_virtualenv(parent_path) 44 | 45 | def get_path(self): 46 | """ 47 | Return the path to the django project (where settings.py and manage.py 48 | are found). 49 | """ 50 | return self._path 51 | 52 | def set_path(self, path): 53 | """ 54 | Set Path 55 | 56 | Set the full filesystem path to where the Django project files are stored 57 | or raise IOError if the path does not exist or if settings.py or manage.py 58 | cannot be found in the path. 59 | """ 60 | self.activate_virtualenv(path) 61 | 62 | if not os.path.exists(path): 63 | raise IOError("Django project directory does not exist: %s" % path) 64 | 65 | # find manage.py 66 | manage = os.path.join(path, 'manage.py') 67 | if not os.path.isfile(manage): 68 | raise IOError("Django manage file does not exist: %s" % manage) 69 | 70 | orig_cwd = os.getcwd() 71 | os.chdir(path) 72 | sys.path.append(path) 73 | 74 | # Load the manage module, so the DJANGO_SETTINGS_MODULE environment 75 | # variable gets set. Loading may fail, but setting DJANGO_SETTINGS_MODULE 76 | # hopefully don't. 77 | try: 78 | orig_sys_argv = sys.argv 79 | sys.argv = ['manage.py', '--version'] 80 | 81 | imp.load_source('__main__', manage) 82 | logger.debug("Loaded manage module: %s" % manage) 83 | 84 | sys.argv = orig_sys_argv 85 | except: 86 | raise IOError("Django manage module could not get loaded") 87 | 88 | # set DJANGO_SETTINGS_MODULE environment variable to 'settings' if it 89 | # was not already set by the user or manage.py. 90 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") 91 | 92 | # Now we try to load the settings module and get the file path. 93 | try: 94 | __import__(os.environ['DJANGO_SETTINGS_MODULE'], globals(), locals(), [], -1) 95 | mod_settings = sys.modules[os.environ['DJANGO_SETTINGS_MODULE']] 96 | settings = mod_settings.__file__ 97 | logger.debug("Loaded settings module: %s" % settings) 98 | except: 99 | raise IOError("Django settings could not get loaded") 100 | 101 | self._path = path 102 | self._settings = settings 103 | self._mod_settings = mod_settings 104 | self._manage = manage 105 | 106 | os.chdir(orig_cwd) 107 | 108 | """ 109 | # find settings.py in Django >= 1.4 110 | settings = os.path.join(path, os.path.basename(path), 'settings.py') 111 | if not os.path.isfile(settings): 112 | # find settings.py in Django < 1.4 113 | settings = os.path.join(path, 'settings.py') 114 | if not os.path.isfile(settings): 115 | raise IOError("Django settings file does not exist: %s" % settings) 116 | 117 | self._path = path 118 | self._settings = settings 119 | self._manage = manage 120 | """ 121 | 122 | def get_settings_module(self): 123 | return self._mod_settings 124 | 125 | def get_settings_filename(self): 126 | return self._settings 127 | 128 | def get_manage_filename(self): 129 | return self._manage 130 | 131 | -------------------------------------------------------------------------------- /djangoproject/data/dialogs.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | False 6 | 5 7 | True 8 | center 9 | dialog 10 | True 11 | True 12 | 13 | 14 | False 15 | vertical 16 | 12 17 | 18 | 19 | False 20 | end 21 | 22 | 23 | gtk-cancel 24 | True 25 | True 26 | True 27 | False 28 | True 29 | 30 | 31 | False 32 | True 33 | 0 34 | 35 | 36 | 37 | 38 | gtk-ok 39 | True 40 | True 41 | True 42 | True 43 | True 44 | False 45 | True 46 | 47 | 48 | False 49 | True 50 | 1 51 | 52 | 53 | 54 | 55 | False 56 | True 57 | end 58 | 0 59 | 60 | 61 | 62 | 63 | True 64 | False 65 | 12 66 | 12 67 | 69 | 70 | 71 | True 72 | False 73 | 0 74 | Name 75 | 76 | 77 | 0 78 | 0 79 | 1 80 | 1 81 | 82 | 83 | 84 | 85 | True 86 | False 87 | 0 88 | Directory 89 | 90 | 91 | 0 92 | 1 93 | 1 94 | 1 95 | 96 | 97 | 98 | 99 | True 100 | True 101 | True 102 | 103 | 104 | 105 | 1 106 | 0 107 | 1 108 | 1 109 | 110 | 111 | 112 | 113 | True 114 | False 115 | select-folder 116 | Select a Folder 117 | 118 | 119 | 1 120 | 1 121 | 1 122 | 1 123 | 124 | 125 | 126 | 127 | False 128 | True 129 | 1 130 | 131 | 132 | 133 | 134 | 135 | button1 136 | button2 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /djangoproject/plugin.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from gi.repository import GObject, Gtk, Gedit, Gio, GdkPixbuf 4 | from project import DjangoProject 5 | from server import DjangoServer 6 | from output import OutputBox 7 | from shell import Shell 8 | from appselector import AppSelector 9 | 10 | logging.basicConfig() 11 | LOG_LEVEL = logging.DEBUG 12 | logger = logging.getLogger(__name__) 13 | logger.setLevel(LOG_LEVEL) 14 | 15 | DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') 16 | STOCK_DBSHELL = "dbshell" 17 | STOCK_SERVER = "server" 18 | STOCK_PYTHON = "python" 19 | 20 | class Plugin(GObject.Object, Gedit.WindowActivatable): 21 | __gtype_name__ = "GeditDjangoProjectPlugin" 22 | window = GObject.property(type=Gedit.Window) 23 | 24 | def __init__(self): 25 | GObject.Object.__init__(self) 26 | self._project = None 27 | self._server = None 28 | self._output = None 29 | self._shell = None 30 | self._dbshell = None 31 | self._install_stock_icons() 32 | self._admin_cmd = "django-admin.py" 33 | self._manage_cmd = "python manage.py" 34 | self._font = "monospace 10" 35 | 36 | def _add_dbshell_panel(self): 37 | """ Adds a database shell to the bottom pane. """ 38 | logger.debug("Adding database shell panel.") 39 | self._dbshell = Shell() 40 | self._dbshell.set_font(self._font) 41 | panel = self.window.get_bottom_panel() 42 | panel.add_item_with_stock_icon(self._dbshell, "DjangoDbShell", 43 | "Database Shell", STOCK_DBSHELL) 44 | self._setup_dbshell_panel() 45 | panel.activate_item(self._dbshell) 46 | 47 | def _add_output_panel(self): 48 | """ Adds a widget to the bottom pane for django command output. """ 49 | self._output = OutputBox() 50 | self._output.set_font(self._font) 51 | panel = self.window.get_bottom_panel() 52 | panel.add_item_with_stock_icon(self._output, "DjangoOutput", 53 | "Django Output", Gtk.STOCK_EXECUTE) 54 | 55 | def _add_server_panel(self, cwd=None): 56 | """ Adds a VTE widget to the bottom pane for development server. """ 57 | logger.debug("Adding server panel.") 58 | self._server = DjangoServer() 59 | self._server.set_font(self._font) 60 | self._server.command = "%s runserver" % (self._manage_cmd) 61 | if cwd: 62 | self._server.cwd = cwd 63 | self._server.connect("server-started", self.on_server_started) 64 | self._server.connect("server-stopped", self.on_server_stopped) 65 | panel = self.window.get_bottom_panel() 66 | panel.add_item_with_stock_icon(self._server, "DjangoServer", 67 | "Django Server", STOCK_SERVER) 68 | self._setup_server_panel() 69 | 70 | def _add_shell_panel(self): 71 | """ Adds a python shell to the bottom pane. """ 72 | logger.debug("Adding shell.") 73 | self._shell = Shell() 74 | self._shell.set_font(self._font) 75 | panel = self.window.get_bottom_panel() 76 | panel.add_item_with_stock_icon(self._shell, "DjangoShell", 77 | "Python Shell", STOCK_PYTHON) 78 | self._setup_shell_panel() 79 | panel.activate_item(self._shell) 80 | 81 | def _add_ui(self): 82 | """ Merge the 'Django' menu into the Gedit menubar. """ 83 | ui_file = os.path.join(DATA_DIR, 'menu.ui') 84 | manager = self.window.get_ui_manager() 85 | 86 | # global actions are always sensitive 87 | self._global_actions = Gtk.ActionGroup("DjangoGlobal") 88 | self._global_actions.add_actions([ 89 | ('Django', None, "_Django", None, None, None), 90 | ('NewProject', Gtk.STOCK_NEW, "_New Project...", 91 | "N", "Start a new Django project.", 92 | self.on_new_project_activate), 93 | ('OpenProject', Gtk.STOCK_OPEN, "_Open Project", 94 | "O", "Open an existing Django project.", 95 | self.on_open_project_activate), 96 | ('NewApp', Gtk.STOCK_NEW, "New _App...", 97 | "A", "Start a new Django application.", 98 | self.on_new_app_activate), 99 | ]) 100 | self._global_actions.add_toggle_actions([ 101 | ('ViewServerPanel', None, "Django _Server", 102 | None, "Add the Django development server to the bottom panel.", 103 | self.on_view_server_panel_activate, True), 104 | ('ViewPythonShell', None, "_Python Shell", 105 | None, "Add a Python shell to the bottom panel.", 106 | self.on_view_python_shell_panel_activate, False), 107 | ('ViewDbShell', None, "_Database Shell", 108 | None, "Add a Database shell to the bottom panel.", 109 | self.on_view_db_shell_panel_activate, False), 110 | ]) 111 | manager.insert_action_group(self._global_actions) 112 | 113 | # project actions are sensitive when a project is open 114 | self._project_actions = Gtk.ActionGroup("DjangoProject") 115 | self._project_actions.add_actions([ 116 | ('CloseProject', Gtk.STOCK_CLOSE, "_Close Project...", 117 | "", "Close the current Django project.", 118 | self.on_close_project_activate), 119 | ('Manage', None, "_Manage", None, None, None), 120 | ('SyncDb', Gtk.STOCK_REFRESH, "_Synchronize Database", None, 121 | "Creates the database tables for all apps whose tables have not already been created.", 122 | self.on_manage_command_activate), 123 | ('Cleanup', None, "_Cleanup", None, 124 | "Clean out old data from the database.", 125 | self.on_manage_command_activate), 126 | ('DiffSettings', None, "Di_ff Settings", None, 127 | "Displays differences between the current settings and Django's default settings.", 128 | self.on_manage_command_activate), 129 | ('InspectDb', None, "_Inspect Database", None, 130 | "Introspects the database and outputs a Django model module.", 131 | self.on_manage_command_activate), 132 | ('Flush', None, "_Flush", None, 133 | "Returns the database to the state it was in immediately after syncdb was executed.", 134 | self.on_manage_command_activate), 135 | # all clear custom flush 136 | ('Sql', None, "S_QL...", None, 137 | "Prints the CREATE TABLE SQL statements for the given app name(s).", 138 | self.on_manage_app_select_command_activate), 139 | ('SqlAll', None, "SQL _All...", None, 140 | "Prints the CREATE TABLE and initial-data SQL statements for the given app name(s).", 141 | self.on_manage_app_select_command_activate), 142 | ('SqlClear', None, "SQL C_lear...", None, 143 | "Prints the DROP TABLE SQL statements for the given app name(s).", 144 | self.on_manage_app_select_command_activate), 145 | ('SqlCustom', None, "SQL C_ustom...", None, 146 | "Prints the custom SQL statements for the given app name(s).", 147 | self.on_manage_app_select_command_activate), 148 | ('SqlFlush', None, "S_QL Flush", None, 149 | "Prints the SQL statements that would be executed for the flush command.", 150 | self.on_manage_command_activate), 151 | ('SqlIndexes', None, "SQL _Indexes...", None, 152 | "Prints the CREATE INDEX SQL statements for the given app name(s).", 153 | self.on_manage_app_select_command_activate), 154 | ('SqlSequenceReset', None, "SQL Sequence Rese_t...", None, 155 | "Prints the SQL statements for resetting sequences for the given app name(s).", 156 | self.on_manage_app_select_command_activate), 157 | ('Validate', None, "_Validate", None, 158 | "Validates all installed models.", 159 | self.on_manage_command_activate), 160 | ('LoadData', None, "_Load Data...", None, 161 | "Loads the contents of fixtures into the database.", 162 | self.on_manage_load_data_activate), 163 | ('DumpData', None, "_Dump Data...", None, 164 | "Outputs all data in the database associated with the named application(s).", 165 | self.on_manage_app_select_command_activate), 166 | ]) 167 | self._project_actions.add_toggle_actions([ 168 | ('RunServer', None, "_Run Development Server", 169 | "F5", "Start/Stop the Django development server.", 170 | self.on_manage_runserver_activate, False), 171 | ]) 172 | self._project_actions.set_sensitive(False) 173 | manager.insert_action_group(self._project_actions) 174 | 175 | self._ui_merge_id = manager.add_ui_from_file(ui_file) 176 | manager.ensure_update() 177 | 178 | def close_project(self): 179 | self._project = None 180 | self._server.stop() 181 | self._server.cwd = None 182 | self._server.refresh_ui() 183 | if self._shell: 184 | self._shell.kill() 185 | if self._dbshell: 186 | self._dbshell.kill() 187 | self._project_actions.set_sensitive(False) 188 | self._update_run_server_action() 189 | 190 | def confirmation_dialog(self, message): 191 | """ Display a very basic informative Yes/No dialog. """ 192 | dialog = Gtk.MessageDialog(self.window, 193 | Gtk.DialogFlags.MODAL | 194 | Gtk.DialogFlags.DESTROY_WITH_PARENT, 195 | Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, 196 | message) 197 | dialog.set_title("Confirm") 198 | response = dialog.run() 199 | dialog.destroy() 200 | 201 | if response == Gtk.ResponseType.YES: 202 | return True 203 | else: 204 | return False 205 | 206 | def do_activate(self): 207 | logger.debug("Activating plugin.") 208 | self._add_ui() 209 | self._add_output_panel() 210 | self._add_server_panel() 211 | 212 | def do_deactivate(self): 213 | logger.debug("Deactivating plugin.") 214 | self._remove_ui() 215 | self._remove_output_panel() 216 | self._remove_server_panel() 217 | self._remove_shell_panel() 218 | self._remove_dbshell_panel() 219 | 220 | def do_update_state(self): 221 | pass 222 | 223 | def error_dialog(self, message): 224 | """ Display a very basic error dialog. """ 225 | logger.warn(message) 226 | dialog = Gtk.MessageDialog(self.window, 227 | Gtk.DialogFlags.MODAL | 228 | Gtk.DialogFlags.DESTROY_WITH_PARENT, 229 | Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, 230 | message) 231 | dialog.set_title("Error") 232 | dialog.run() 233 | dialog.destroy() 234 | 235 | def _install_stock_icons(self): 236 | """ Register custom stock icons used on the tabs. """ 237 | logger.debug("Installing stock icons.") 238 | icons = (STOCK_PYTHON, STOCK_DBSHELL, STOCK_SERVER) 239 | factory = Gtk.IconFactory() 240 | for name in icons: 241 | filename = name + ".png" 242 | pixbuf = GdkPixbuf.Pixbuf.new_from_file(os.path.join(DATA_DIR, "icons", filename)) 243 | iconset = Gtk.IconSet.new_from_pixbuf(pixbuf) 244 | factory.add(name, iconset) 245 | factory.add_default() 246 | 247 | def new_app(self, path, name): 248 | """ Runs the 'startapp' Django command. """ 249 | try: 250 | self.run_admin_command("startapp %s" % name, path) 251 | except Exception as e: 252 | self.error_dialog(str(e)) 253 | 254 | def new_dialog(self, title): 255 | filename = os.path.join(DATA_DIR, 'dialogs.ui') 256 | path = name = None 257 | builder = Gtk.Builder() 258 | try: 259 | builder.add_from_file(filename) 260 | except Exception as e: 261 | logger.error("Failed to load %s: %s." % (filename, str(e))) 262 | return 263 | dialog = builder.get_object('new_dialog') 264 | if not dialog: 265 | logger.error("Could not find 'new_dialog' widget in %s." % filename) 266 | return 267 | dialog.set_transient_for(self.window) 268 | dialog.set_default_response(Gtk.ResponseType.OK) 269 | dialog.set_title(title) 270 | response = dialog.run() 271 | if response == Gtk.ResponseType.OK: 272 | name_widget = builder.get_object('name') 273 | project_widget = builder.get_object('directory') 274 | name = name_widget.get_text() 275 | path = project_widget.get_filename() 276 | dialog.destroy() 277 | 278 | return (name, path) 279 | 280 | def new_project(self, path, name): 281 | """ Runs the 'startproject' Django command and opens the project. """ 282 | try: 283 | self.run_admin_command("startproject %s" % name, path) 284 | except Exception as e: 285 | self.error_dialog(str(e)) 286 | return 287 | 288 | self.open_project(os.path.join(path, name)) 289 | 290 | def new_tab_from_output(self): 291 | message = "Do you want to create a new document with the output?" 292 | if not self.confirmation_dialog(message): 293 | return 294 | tab = self.window.create_tab(False) 295 | buff = tab.get_view().get_buffer() 296 | end_iter = buff.get_end_iter() 297 | buff.insert(end_iter, self._output.get_last_output()) 298 | self.window.set_active_tab(tab) 299 | 300 | def on_close_project_activate(self, action, data=None): 301 | self.close_project() 302 | 303 | def on_manage_command_activate(self, action, data=None): 304 | """ Handles simple manage.py actions. """ 305 | command = action.get_name().lower() 306 | if command in ('syncdb', 'flush'): 307 | command += ' --noinput' 308 | try: 309 | self.run_management_command(command) 310 | except: 311 | pass # errors show up in output 312 | 313 | if command in ('inspectdb', 'sqlflush', 'diffsettings'): 314 | self.new_tab_from_output() 315 | 316 | def on_manage_app_select_command_activate(self, action, data=None): 317 | dialog = Gtk.Dialog("Select apps...", 318 | self.window, 319 | Gtk.DialogFlags.MODAL | 320 | Gtk.DialogFlags.DESTROY_WITH_PARENT, 321 | (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 322 | Gtk.STOCK_OK, Gtk.ResponseType.OK)) 323 | dialog.set_default_size(300, 200) 324 | selector = AppSelector() 325 | selector.show_all() 326 | try: 327 | selector.load_from_settings(self._project.get_settings_module()) 328 | except Exception as e: 329 | self.error_dialog("Error getting app list: %s" % str(e)) 330 | box = dialog.get_content_area() 331 | box.set_border_width(10) 332 | box.pack_start(selector, True, True, 0) 333 | response = dialog.run() 334 | if response == Gtk.ResponseType.OK: 335 | files = selector.get_selected() 336 | command = action.get_name().lower() 337 | full_command = "%s %s" % (command, " ".join([f for f in files]) ) 338 | try: 339 | self.run_management_command(full_command) 340 | except Exception as e: 341 | self.error_dialog(str(e)) 342 | dialog.destroy() 343 | 344 | # only after the dialog is destroyed do we prompt them for a new tab 345 | if response == Gtk.ResponseType.OK: 346 | if command[:3] == "sql" or command in ('dumpdata'): 347 | self.new_tab_from_output() 348 | 349 | def on_manage_load_data_activate(self, action, data=None): 350 | """ Prompt user for fixtures to load into database. """ 351 | dialog = Gtk.FileChooserDialog("Select fixtures...", 352 | self.window, 353 | Gtk.FileChooserAction.OPEN, 354 | (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 355 | Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) 356 | dialog.set_select_multiple(True) 357 | if self._project: 358 | dialog.set_filename(self._project.get_path()) 359 | response = dialog.run() 360 | if response == Gtk.ResponseType.OK: 361 | files = dialog.get_files() 362 | command = "loaddata "+" ".join([f.get_path() for f in files]) 363 | try: 364 | self.run_management_command(command) 365 | except Exception as e: 366 | self.error_dialog(str(e)) 367 | 368 | dialog.destroy() 369 | 370 | def on_manage_runserver_activate(self, action, data=None): 371 | """ Run Django development server. """ 372 | if not self._server: 373 | return 374 | try: 375 | if not action.get_active() and self._server.is_running(): 376 | self._server.stop() 377 | elif action.get_active() and not self._server.is_running(): 378 | self._server.start() 379 | except Exception as e: 380 | self.error_dialog(str(e)) 381 | return 382 | 383 | def on_new_app_activate(self, action, data=None): 384 | """ Prompt user for new app name and directory """ 385 | name, path = self.new_dialog("New Django App") 386 | if name and path: 387 | self.new_app(path, name) 388 | 389 | def on_new_project_activate(self, action, data=None): 390 | """ Prompt user for new project name and directory """ 391 | name, path = self.new_dialog("New Django Project") 392 | if name and path: 393 | self.new_project(path, name) 394 | 395 | def on_open_project_activate(self, action, data=None): 396 | """ Prompt the user for the Django project directory. """ 397 | path = None 398 | dialog = Gtk.FileChooserDialog("Select project folder...", self.window, 399 | Gtk.FileChooserAction.SELECT_FOLDER, 400 | (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 401 | Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) 402 | response = dialog.run() 403 | if response == Gtk.ResponseType.OK: 404 | path = dialog.get_filename() 405 | dialog.destroy() 406 | if path: 407 | self.open_project(path) 408 | 409 | def on_server_started(self, server, pid, data=None): 410 | self._project_actions.get_action("RunServer").set_active(True) 411 | panel = self.window.get_bottom_panel() 412 | panel.activate_item(self._server) 413 | 414 | def on_server_stopped(self, server, pid, data=None): 415 | self._project_actions.get_action("RunServer").set_active(False) 416 | panel = self.window.get_bottom_panel() 417 | panel.activate_item(self._server) 418 | 419 | def on_view_db_shell_panel_activate(self, action, data=None): 420 | """ Show/Hide database shell from main menu. """ 421 | if action.get_active(): 422 | self._add_dbshell_panel() 423 | else: 424 | self._remove_dbshell_panel() 425 | 426 | def on_view_python_shell_panel_activate(self, action, data=None): 427 | """ Show/Hide python shell from main menu. """ 428 | if action.get_active(): 429 | self._add_shell_panel() 430 | else: 431 | self._remove_shell_panel() 432 | 433 | def on_view_server_panel_activate(self, action, data=None): 434 | """ Show/Hide development server from main menu. """ 435 | if action.get_active(): 436 | self._add_server_panel() 437 | else: 438 | self._remove_server_panel() 439 | self._update_run_server_action() 440 | 441 | def open_project(self, path): 442 | logger.debug("Opening Django project: %s" % path) 443 | if self._project: 444 | self.close_project() 445 | try: 446 | self._project = DjangoProject(path) 447 | except IOError as e: 448 | self.error_dialog("Could not open project: %s" % str(e)) 449 | return 450 | 451 | self._output.cwd = self._project.get_path() 452 | self._setup_server_panel() 453 | self._setup_shell_panel() 454 | self._setup_dbshell_panel() 455 | self._project_actions.set_sensitive(True) 456 | self._update_run_server_action() 457 | 458 | # print version as it may have changed due to virtualenv 459 | try: 460 | #command = "%s --version" % (self._admin_cmd) 461 | #self._output.run(command) 462 | self.run_admin_command("--version" , path) 463 | except: 464 | pass 465 | 466 | def _setup_dbshell_panel(self): 467 | if self._dbshell and self._project: 468 | self._dbshell.cwd = self._project.get_path() 469 | self._dbshell.command = "%s dbshell" % self._manage_cmd 470 | self._dbshell.run() 471 | 472 | def _setup_server_panel(self): 473 | if self._server and self._project: 474 | self._server.cwd = self._project.get_path() 475 | self._server.refresh_ui() 476 | 477 | def _setup_shell_panel(self): 478 | if self._shell and self._project: 479 | self._shell.cwd = self._project.get_path() 480 | self._shell.command = "%s shell" % self._manage_cmd 481 | self._shell.run() 482 | 483 | def _remove_output_panel(self): 484 | """ Remove the output box from the bottom panel. """ 485 | logger.debug("Removing output panel.") 486 | if self._output: 487 | self._remove_panel(self._output) 488 | self._output = None 489 | 490 | def _remove_panel(self, item): 491 | panel = self.window.get_bottom_panel() 492 | panel.remove_item(item) 493 | 494 | def _remove_server_panel(self): 495 | """ Stop and remove development server panel from the bottom panel. """ 496 | if self._server: 497 | logger.debug("Removing server panel.") 498 | self._server.stop() 499 | self._remove_panel(self._server) 500 | self._server = None 501 | 502 | 503 | def _remove_shell_panel(self): 504 | """ Remove python shell from bottom panel. """ 505 | if self._shell: 506 | logger.debug("Removing shell panel.") 507 | self._remove_panel(self._shell) 508 | self._shell = None 509 | 510 | def _remove_dbshell_panel(self): 511 | """ Remove database shell from bottom panel. """ 512 | if self._dbshell: 513 | logger.debug("Removing database shell panel.") 514 | self._remove_panel(self._dbshell) 515 | self._dbshell = None 516 | 517 | def _remove_ui(self): 518 | """ Remove the 'Django' menu from the the Gedit menubar. """ 519 | manager = self.window.get_ui_manager() 520 | manager.remove_ui(self._ui_merge_id) 521 | manager.remove_action_group(self._global_actions) 522 | manager.remove_action_group(self._project_actions) 523 | manager.ensure_update() 524 | 525 | def run_admin_command(self, command, path=None): 526 | """ Run a django-admin.py command in the output panel. """ 527 | self.window.get_bottom_panel().activate_item(self._output) 528 | full_command = "%s %s" % (self._admin_cmd, command) 529 | deb_command = "%s %s" % (self._admin_cmd[0:-3], command) 530 | original_cwd = self._output.cwd 531 | self._output.cwd = path 532 | try: 533 | self._output.run(full_command) 534 | except OSError: 535 | try: 536 | # try without ".py" for debian/ubuntu system installs 537 | print(deb_command) 538 | self._output.run(deb_command) 539 | except: 540 | raise Exception("Could not execute django-admin.py command.\nIs Django installed?") 541 | finally: 542 | self._output.cwd = original_cwd 543 | 544 | def run_management_command(self, command): 545 | """ Run a manage.py command in the output panel. """ 546 | self.window.get_bottom_panel().activate_item(self._output) 547 | full_command = "%s %s" % (self._manage_cmd, command) 548 | self._output.run(full_command) 549 | 550 | def _update_run_server_action(self): 551 | if not self._server or not self._project: 552 | self._project_actions.get_action("RunServer").set_sensitive(False) 553 | else: 554 | self._project_actions.get_action("RunServer").set_sensitive(True) 555 | 556 | 557 | --------------------------------------------------------------------------------