|
- ;;; python-django.el --- A Jazzy package for managing Django projects
-
- ;; Copyright (C) 2011 Free Software Foundation, Inc.
-
- ;; Author: Fabián E. Gallina <fabian@anue.biz>
- ;; URL: https://github.com/fgallina/python-django.el
- ;; Version: 0.1
- ;; Maintainer: FSF
- ;; Created: Jul 2011
- ;; Keywords: languages
-
- ;; This file is NOT part of GNU Emacs.
-
- ;; python-django.el is free software: you can redistribute it and/or
- ;; modify it under the terms of the GNU General Public License as
- ;; published by the Free Software Foundation, either version 3 of the
- ;; License, or (at your option) any later version.
-
- ;; python-django.el is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- ;; General Public License for more details.
-
- ;; You should have received a copy of the GNU General Public License
- ;; along with python-django.el. If not, see
- ;; <http://www.gnu.org/licenses/>.
-
- ;;; Commentary:
-
- ;; Django project management package with the goodies you would expect
- ;; and then some. The project buffer workings is pretty much inspired
- ;; by the good ol' `magit-status' buffer.
-
- ;; This package relies heavily in fgallina's `python.el' available in
- ;; stock Emacs>=24.3 (or https://github.com/fgallina/python.el).
-
- ;; Implements File navigation (per app, STATIC_ROOT, MEDIA_ROOT and
- ;; TEMPLATE_DIRS), Etag building, Grep in project, Quick jump (to
- ;; settings, project root, virtualenv and docs), Management commands
- ;; and Quick management commands.
-
- ;; File navigation: After opening a project, a directory tree for each
- ;; installed app, the STATIC_ROOT, the MEDIA_ROOT and each
- ;; TEMPLATE_DIRS is created. Several commands are provided to work
- ;; with the current directory at point.
-
- ;; Etags building: Provides a simple wrapper to create etags for
- ;; current opened project.
-
- ;; Grep in project: Provides a simple way to grep relevant project
- ;; directories using `rgrep'. You can override the use of `rgrep' by
- ;; tweaking the `python-django-cmd-grep-function'.
-
- ;; Quick jump: fast key bindings to jump to the settings module, the
- ;; project root, the current virtualenv and Django official web docs
- ;; are provided.
-
- ;; Management commands: You can run any management command from the
- ;; project buffer via `python-django-mgmt-run-command' or via the
- ;; quick management commands accesible from the Django menu.
- ;; Completion is provided for all arguments and you can cycle through
- ;; opened management command process buffers very easily. Another
- ;; cool feature is that comint processes are spiced up with special
- ;; processing, for instance if are using runserver and get a
- ;; breakpoint via pdb or ipdb the pdb-tracking provided by
- ;; `python-mode' will trigger or if you enter dbshell the proper
- ;; `sql-mode' will be used.
-
- ;; Quick management commands: This mode provides quick management
- ;; commands (management commands with sane defaults, smart prompt
- ;; completion and process extra processing) defined to work with the
- ;; most used Django built-in management commands like syncdb, shell,
- ;; runserver, test; several good ones from `django-extensions' like
- ;; shell_plus, clean_pyc; and `south' ones like convert_to_south,
- ;; migrate, schemamigration. You can define new quick commands via
- ;; the `python-django-qmgmt-define' and define ways to handle when
- ;; it's finished by defining a callback function.
-
- ;;; Usage:
-
- ;; The main entry point is the `python-django-open-project'
- ;; interactive function, see its documentation for more info on its
- ;; behavior. Mainly this function requires two things, a project path
- ;; and a settings module. How you chose them really depends on your
- ;; project's directory layout. The recommended way to chose your
- ;; project root, is to use the directory containing your settings
- ;; module; for instance if your settings module is in
- ;; /path/django/settings.py, use /path/django/ as your project path
- ;; and django.settings as your settings module. Remember to always
- ;; set `python-shell-interpreter' to either python or python2 and
- ;; never use iPython directly as Django enables it automatically when
- ;; the shell is started.
-
- ;;; Installation:
-
- ;; Add this to your .emacs:
-
- ;; (add-to-list 'load-path "/folder/containing/file")
- ;; (require 'python-django)
-
- ;;; Code:
-
- (require 'hippie-exp)
- (require 'json)
- (require 'python)
- (require 'sql)
- (require 'tree-widget)
- (require 'wid-edit)
- (require 'widget)
-
- ;; Avoid compiler warnings
- (defvar view-return-to-alist)
-
- (defgroup python-django nil
- "Python Django project goodies."
- :group 'convenience
- :version "24.2")
-
-
- ;;; keymaps
-
- (defvar python-django-mode-map
- (let ((map (make-keymap)))
- (suppress-keymap map t)
- (define-key map [remap next-line] 'python-django-ui-widget-forward)
- (define-key map [remap previous-line] 'python-django-ui-widget-backward)
- (define-key map [remap forward-char] 'widget-forward)
- (define-key map [remap backward-char] 'widget-backward)
- (define-key map [remap beginning-of-buffer]
- 'python-django-ui-beginning-of-widgets)
- (define-key map [remap newline] 'python-django-ui-safe-button-press)
- (define-key map (kbd "^") 'python-django-ui-move-up-tree)
- (define-key map (kbd "p") 'python-django-ui-widget-backward)
- (define-key map (kbd "n") 'python-django-ui-widget-forward)
- (define-key map (kbd "b") 'widget-backward)
- (define-key map (kbd "f") 'widget-forward)
- (define-key map (kbd "d") 'python-django-cmd-dired-at-point)
- (define-key map (kbd "w") 'python-django-cmd-directory-at-point)
- (define-key map (kbd "ja") 'python-django-cmd-jump-to-app)
- (define-key map (kbd "jm") 'python-django-cmd-jump-to-media)
- (define-key map (kbd "jt") 'python-django-cmd-jump-to-template-dir)
- (define-key map (kbd "vs") 'python-django-cmd-visit-settings)
- (define-key map (kbd "vr") 'python-django-cmd-visit-project-root)
- (define-key map (kbd "vv") 'python-django-cmd-visit-virtualenv)
- (define-key map (kbd "t") 'python-django-cmd-build-etags)
- (define-key map (kbd "s") 'python-django-cmd-grep)
- (define-key map (kbd "o") 'python-django-cmd-open-docs)
- (define-key map (kbd "h") 'python-django-help)
- (define-key map (kbd "m") 'python-django-mgmt-run-command)
- (define-key map (kbd "g") 'python-django-refresh-project)
- (define-key map (kbd "q") 'python-django-close-project)
- (define-key map (kbd "k") 'python-django-mgmt-kill)
- (define-key map (kbd "K") 'python-django-mgmt-kill-all)
- (define-key map (kbd "u") 'universal-argument)
- (define-key map (kbd "$") 'python-django-mgmt-cycle-buffers-forward)
- (define-key map (kbd "#") 'python-django-mgmt-cycle-buffers-backward)
- (easy-menu-define python-django-menu map "Python Django Mode menu"
- `("Django"
- :help "Django project tools"
- ["Run management command"
- python-django-mgmt-run-command
- :help "Run management command in current project"]
- ["Kill all running commands"
- python-django-mgmt-kill-all
- :help "Kill all running commands for current project"]
- ["Get command help" python-django-help
- :help "Get help for any project's management commands"]
- ["Cycle to next running management command"
- python-django-mgmt-cycle-buffers-forward
- :help "Cycle to next running management command"]
- ["Cycle to previous running management command"
- python-django-mgmt-cycle-buffers-backward
- :help "Cycle to previous running management command"]
- "--"
- ;; Reserved for quick management commands
- "---"
- ["Browse Django documentation"
- python-django-cmd-open-docs
- :help "Open a Browser with Django's documentation"]
- ["Build Tags"
- python-django-cmd-build-etags
- :help "Build TAGS file for python source in project"]
- ["Dired at point"
- python-django-cmd-dired-at-point
- :help "Open dired at current tree node"]
- ["Grep in project directories"
- python-django-cmd-grep
- :help "Grep in project directories"]
- ["Refresh project"
- python-django-refresh-project
- :help "Refresh project"]
- "--"
- ["Visit settings file"
- python-django-cmd-visit-settings
- :help "Visit settings file"]
- ["Visit virtualenv directory"
- python-django-cmd-visit-virtualenv
- :help "Visit virtualenv directory"]
- ["Visit project root directory"
- python-django-cmd-visit-project-root
- :help "Visit project root directory"]
- "--"
- ["Jump to app's directory"
- python-django-cmd-jump-to-app
- :help "Jump to app's directory"]
- ["Jump to a media directory"
- python-django-cmd-jump-to-media
- :help "Jump to a media directory"]
- ["Jump to a template directory"
- python-django-cmd-jump-to-template-dir
- :help "Jump to a template directory"]))
- map)
- "Keymap for `python-django-mode'.")
-
-
- ;;; Main vars
-
- (defvar python-django-project-root nil
- "Django project root directory.")
-
- (defvar python-django-project-manage.py nil
- "Django project manage.py path.")
-
- (defvar python-django-project-settings nil
- "Django project settings module.")
-
- (defvar python-django-project-name nil
- "Django project name.")
-
- (define-obsolete-variable-alias
- 'python-django-settings-module
- 'python-django-project-settings
- "24.2")
-
- (define-obsolete-variable-alias
- 'python-django-info-project-name
- 'python-django-project-name
- "24.2")
-
-
- ;;; Faces
-
- (defgroup python-django-faces nil
- "Customize the appearance of Django buffers."
- :prefix "python-django-"
- :group 'faces
- :group 'python-django)
-
- (defface python-django-face-header
- '((t :inherit font-lock-function-name-face))
- "Face for generic header lines.
-
- Many Django faces inherit from this one by default."
- :group 'python-django-faces)
-
- (defface python-django-face-path
- '((t :inherit font-lock-type-face))
- "Face for paths."
- :group 'python-django-faces)
-
- (defface python-django-face-title
- '((t :inherit font-lock-keyword-face))
- "Face for titles."
- :group 'python-django-faces)
-
- (defface python-django-face-django-version
- '((t :inherit python-django-face-header))
- "Face for project's Django version."
- :group 'python-django-faces)
-
- (defface python-django-face-project-root
- '((t :inherit python-django-face-path))
- "Face for project path."
- :group 'python-django-faces)
-
- (defface python-django-face-settings-module
- '((t :inherit python-django-face-header))
- "Face for project settings module."
- :group 'python-django-faces)
-
- (defface python-django-face-virtualenv-path
- '((t :inherit python-django-face-header))
- "Face for project settings module."
- :group 'python-django-faces)
-
-
- ;;; Dev tools
-
- (font-lock-add-keywords
- 'emacs-lisp-mode
- `(("(\\(python-django-qmgmt-define\\)\\>[ \t]\\([^ \t]+\\)"
- (1 'font-lock-keyword-face)
- (2 'font-lock-function-name-face))))
-
-
- ;;; Error logging
-
- (defvar python-django-error-log-formatter
- #'python-django-error-default-formatter)
-
- (defun python-django-error-default-formatter (error-string)
- "Formats ERROR-STRING to be placed in the error log."
- (format
- (concat
- "An error occurred retrieving project information.\n"
- "Check your project settings and try again:\n\n"
- "Current values:\n"
- " + python-django-project-root: %s\n"
- " + python-django-project-settings: %s\n"
- " + python-shell-interpreter: %s\n"
- " - found in %s\n\n"
- "Details: \n\n%s\n")
- python-django-project-root
- python-django-project-settings
- python-shell-interpreter
- (let* ((process-environment
- (python-django-info-calculate-process-environment))
- (exec-path (python-shell-calculate-exec-path)))
- (executable-find python-shell-interpreter))
- error-string))
-
- (defun python-django-error-log (error-string)
- "Log ERROR-STRING by calling `user-error'."
- (user-error "%s" (funcall python-django-error-log-formatter error-string)))
-
-
- ;;; Utility functions
-
- (defun python-django-util-clone-local-variables ()
- "Clone local variables from manage.py file.
- This function is intended to be used so the project buffer gets
- the same variables of python files."
- (let* ((file-name
- (expand-file-name
- python-django-project-manage.py))
- (manage.py-exists (get-file-buffer file-name))
- (flymake-start-syntax-check-on-find-file nil)
- (manage.py-buffer
- (or manage.py-exists
- (prog1
- (find-file-noselect file-name t)
- (message nil)))))
- ;; TODO: Add a predicate parameter to
- ;; `python-util-clone-local-variables' itself to handle vars not
- ;; intended to be changed by the variable cloning and replace the
- ;; following code with that.
- (mapc
- (lambda (pair)
- (and (symbolp (car pair))
- (string-match "^python-" (symbol-name (car pair)))
- (not (memq (car pair)
- '(python-django-project-root
- python-django-project-settings
- python-django-project-name
- python-django-project-manage.py)))
- (set (make-local-variable (car pair))
- (cdr pair))))
- (buffer-local-variables manage.py-buffer))
- (when (not manage.py-exists)
- (kill-buffer manage.py-buffer))))
-
- (defmacro python-django-util-alist-add (key value alist)
- "Update for KEY the VALUE in ALIST."
- `(let* ((k (if (bufferp ,key)
- (buffer-name ,key)
- ,key))
- (v (if (bufferp ,value)
- (buffer-name ,value)
- ,value))
- (elt (assoc k ,alist)))
- (if (not elt)
- (setq ,alist (cons (list k v) ,alist))
- (and (not (member v (cdr elt)))
- (setf (cdr elt)
- (cons v (cdr elt)))))))
-
- (defmacro python-django-util-alist-del (key value alist)
- "Remove for KEY the VALUE in ALIST."
- `(let* ((k (if (bufferp ,key)
- (buffer-name ,key)
- ,key))
- (v (if (bufferp ,value)
- (buffer-name ,value)
- ,value))
- (elt (assoc k ,alist)))
- (and elt (setf (cdr elt) (remove v (cdr elt))))))
-
- (defmacro python-django-util-alist-del-key (key alist)
- "Empty KEY in ALIST."
- `(let* ((k (if (bufferp ,key)
- (buffer-name ,key)
- ,key))
- (elt (assoc k ,alist)))
- (and elt (setf (cdr elt) nil))))
-
- (defun python-django-util-alist-get (key alist)
- "Get values for KEY in ALIST."
- (and (bufferp key) (setq key (buffer-name key)))
- (cdr (assoc key alist)))
-
- ;; Based on `file-name-extension'
- (defun python-django-util-file-name-extension (filename)
- "Return FILENAME's final \"extension\" sans dot."
- (save-match-data
- (let ((file (file-name-nondirectory filename)))
- (if (and (string-match "\\.[^.]*\\'" file)
- (not (eq 0 (match-beginning 0))))
- (substring file (+ (match-beginning 0) 1))))))
-
- (defun python-django-util-shell-command-to-string (command)
- "Execute shell COMMAND and return its output as a string.
- Returns a cons cell where the car is the exit status and the cdr
- is the captured output."
- (with-temp-buffer
- (cons
- (apply 'call-process shell-file-name
- nil t nil (list shell-command-switch command))
- (buffer-string))))
-
- (defun python-django-util-shell-command-or-error (command)
- "Execute shell COMMAND and return its output as a string.
- If the exit status is an error `python-django-error-log' is used
- to display command output."
- (let* ((result (python-django-util-shell-command-to-string command))
- (status (car result))
- (output (cdr result)))
- (if (zerop status)
- output
- (python-django-error-log
- (concat "Error executing: " command "\n\n" output)))))
-
- (defun python-django-util-shorten-settings (&optional settings)
- "Return a shorter SETTINGS module string.
- Optional Argument SETTINGS defaults to the value of
- `python-django-project-settings'."
- (or settings (setq settings python-django-project-settings))
- (let ((beg (string-match "settings\\." settings)))
- (if beg
- (substring
- settings
- (+ beg (length (match-string-no-properties 0 settings))))
- settings)))
-
-
- ;;; Help
-
- (defun python-django--help-get (&optional command)
- "Get help for COMMAND."
- (let* ((process-environment
- (python-django-info-calculate-process-environment))
- (exec-path (python-shell-calculate-exec-path)))
- (python-django-util-shell-command-or-error
- (format "%s %s help%s"
- (executable-find python-shell-interpreter)
- python-django-project-manage.py
- (or (and command (concat " " command)) "")))))
-
- (defun python-django-help (&optional command show-help)
- "Get help for given COMMAND.
- Optional argument SHOW-HELP when non-nil causes the help buffer to pop."
- (interactive
- (list
- (python-django-minibuffer-read-command)))
- (if (or show-help (called-interactively-p 'interactive))
- (with-help-window (help-buffer)
- (princ (python-django--help-get command)))
- (python-django--help-get command)))
-
- (defun python-django-help-close ()
- "Close help window if visible."
- (let ((win (get-buffer-window (help-buffer))))
- (and win
- (delete-window win))))
-
-
- ;;; Project info
-
- (defun python-django-info-calculate-process-environment ()
- "Calculate process environment given current Django project."
- (let* ((process-environment (python-shell-calculate-process-environment))
- (pythonpath (getenv "PYTHONPATH"))
- (project-pythonpath
- (mapconcat
- 'identity
- (list (expand-file-name python-django-project-root)
- (expand-file-name "../" python-django-project-root))
- path-separator)))
- (setenv "PYTHONPATH" (if (not pythonpath)
- project-pythonpath
- (format "%s%s%s"
- pythonpath
- path-separator
- project-pythonpath)))
- (setenv "DJANGO_SETTINGS_MODULE"
- python-django-project-settings)
- process-environment))
-
- (defun python-django-info-find-manage.py (&optional dir)
- "Find manage.py script starting from DIR."
- (let ((dir (expand-file-name (or dir default-directory))))
- (if (not (directory-files dir nil "^manage\\.py$"))
- (and
- ;; Check dir is not directory root.
- (not (string-equal "/" dir))
- (not
- (and (memq system-type '(windows-nt ms-dos))
- (string-match "\\`[a-zA-Z]:[/\\]\\'" dir)))
- (python-django-info-find-manage.py
- (expand-file-name
- (file-name-as-directory "..") dir)))
- (expand-file-name "manage.py" dir))))
-
- (defvar python-django-info-prefetched-settings
- '("INSTALLED_APPS" "DATABASES" "MEDIA_ROOT" "STATIC_ROOT" "TEMPLATE_DIRS"
- "STATICFILES_DIRS"))
-
- (defvar python-django-info--get-setting-cache nil
- "Alist with cached list of settings.")
-
- (defvar python-django-info--get-version-cache nil
- "Alist with cached list of settings.")
-
- (defun python-django-info-get-version (&optional force)
- "Get current Django version path.
- Values retrieved by this function are cached so when FORCE is
- non-nil the cached value is invalidated."
- (or
- (and (not force) python-django-info--get-version-cache))
- (setq
- python-django-info--get-version-cache
- (let* ((process-environment
- (python-django-info-calculate-process-environment))
- (exec-path (python-shell-calculate-exec-path)))
- (python-django-util-shell-command-or-error
- (format
- "%s -c \"%s\""
- (executable-find python-shell-interpreter)
- (concat
- "from __future__ import print_function\n"
- "import django\n"
- "print(django.get_version(), end='')"))))))
-
- (defvar python-django-info-imports-code
- (concat "\n"
- "from __future__ import print_function\n"
- "import os\n"
- "import sys\n"
- "from os.path import dirname, abspath\n"
- "stdout = sys.stdout; stderr = sys.stderr\n"
- "sys.stdout = sys.stderr = open(os.devnull, 'w')\n"
- "from django.conf import settings\n"
- "# Try to import json really hard\n"
- "try:\n"
- " import json\n"
- "except ImportError:\n"
- " from django.utils import simplejson as json\n"
- "# Force settings loading so all output is sent to devnull.\n"
- "settings.DEBUG\n"
- "sys.stdout = stdout; sys.stderr = stderr\n\n")
- "All imports code used to get info.
- It contains output redirecting features so settings import
- doesn't break the JSON output.")
-
- (defun python-django-info-get-settings (&optional force)
- "Prefretch most common used settings for project.
- Values retrieved by this function are cached so when FORCE is
- non-nil the cached value is invalidated."
- (let ((cached
- (mapcar
- #'(lambda (setting)
- (assq (intern setting)
- python-django-info--get-setting-cache))
- python-django-info-prefetched-settings)))
- (if (and (not force)
- (catch 'exit
- (dolist (elt cached)
- (when (null elt)
- (throw 'exit nil)))
- t))
- cached
- (let* ((process-environment
- (python-django-info-calculate-process-environment))
- (exec-path (python-shell-calculate-exec-path))
- (settings-list-string
- (concat "["
- (mapconcat
- #'(lambda (str) (concat "'" str "'"))
- python-django-info-prefetched-settings
- ", ")
- "]"))
- (value
- (json-read-from-string
- (python-django-util-shell-command-or-error
- (format "%s -c \"%s%s\""
- (executable-find python-shell-interpreter)
- python-django-info-imports-code
- (concat
- "acc = {}\n"
- "for name in " settings-list-string ":\n"
- " acc[name] = getattr(settings, name, None)\n"
- "print(json.dumps(acc), end='')"))))))
- (mapc
- (lambda (elt)
- (let ((cached-val
- (assq (car elt) python-django-info--get-setting-cache)))
- (if cached-val
- (setcdr cached-val (cdr elt))
- (setq python-django-info--get-setting-cache
- (cons elt python-django-info--get-setting-cache)))))
- value)))))
-
- (defun python-django-info-get-setting (setting &optional force)
- "Get SETTING value from django.conf.settings in JSON format.
- Values retrieved by this function are cached so when FORCE is
- non-nil the cached value is invalidated."
- (let ((cached
- (or (and
- (member setting python-django-info-prefetched-settings)
- (assq (intern setting) (python-django-info-get-settings force)))
- (assq (intern setting)
- python-django-info--get-setting-cache))))
- (if (and (not force) cached)
- (cdr cached)
- (let* ((process-environment
- (python-django-info-calculate-process-environment))
- (exec-path (python-shell-calculate-exec-path))
- (value
- (json-read-from-string
- (python-django-util-shell-command-or-error
- (format
- "%s -c \"%s%s\""
- (executable-find python-shell-interpreter)
- python-django-info-imports-code
- (format
- (concat
- "print(json.dumps("
- "getattr(settings, '%s', None)), end='')")
- setting)))))
- (already-cached (assq (intern setting)
- python-django-info--get-setting-cache)))
- (if already-cached
- (setcdr already-cached value)
- (setq python-django-info--get-setting-cache
- (cons (cons (intern setting) value)
- python-django-info--get-setting-cache)))
- value))))
-
- (defvar python-django-info--get-app-paths-cache nil
- "Cached list of apps and paths.")
-
- (defun python-django-info-get-app-paths (&optional force)
- "Get project paths path.
- Values retrieved by this function are cached so when FORCE is
- non-nil the cached value is invalidated."
- (if (or force (not python-django-info--get-app-paths-cache))
- (setq
- python-django-info--get-app-paths-cache
- (let* ((process-environment
- (python-django-info-calculate-process-environment))
- (exec-path (python-shell-calculate-exec-path)))
- (json-read-from-string
- (python-django-util-shell-command-or-error
- (format "%s -c \"%s%s\""
- (executable-find python-shell-interpreter)
- python-django-info-imports-code
- (concat
- "app_paths = {}\n"
- "for app in settings.INSTALLED_APPS:\n"
- " mod = __import__(app)\n"
- " if '.' in app:\n"
- " for sub in app.split('.')[1:]:\n"
- " mod = getattr(mod, sub)\n"
- " app_paths[app] = dirname(abspath(mod.__file__))\n"
- "print(json.dumps(app_paths), end='')"))))))
- python-django-info--get-app-paths-cache))
-
- (defun python-django-info-get-app-path (app &optional force)
- "Get APP's path.
- Values retrieved by this function are cached so when FORCE is
- non-nil the cached value is invalidated."
- (cdr (assq (intern app) (python-django-info-get-app-paths force))))
-
- (defun python-django-info-get-app-migrations (app)
- "Get APP's list of migrations."
- (mapcar (lambda (file)
- file)
- (ignore-errors
- (directory-files
- (expand-file-name
- "migrations"
- (python-django-info-get-app-path app))
- nil "^[0-9]\\{4\\}_.*\\.py$"))))
-
- (defun python-django-info-module-path (module)
- "Get MODULE's path."
- (let* ((process-environment
- (python-django-info-calculate-process-environment))
- (exec-path (python-shell-calculate-exec-path)))
- (python-django-util-shell-command-or-error
- (format "%s -c \"%s%s%s\""
- (executable-find python-shell-interpreter)
- python-django-info-imports-code
- (format "import %s\n" module)
- (format
- "print(%s.__file__.replace('.pyc', '.py'), end='')" module)))))
-
- (defun python-django-info-directory-basename (&optional dir)
- "Get innermost directory name for given DIR."
- (car (last (split-string dir "/" t))))
-
-
- ;;; Hippie expand completion
-
- (defun python-django-minibuffer-try-complete-args (old)
- "Try to complete word as a management command argument.
- The argument OLD has to be nil the first call of this function, and t
- for subsequent calls (for further possible completions of the same
- string). It returns t if a new completion is found, nil otherwise."
- (save-excursion
- (unless old
- (he-init-string (he-dabbrev-beg) (point))
- (when (not (equal he-search-string ""))
- (setq he-expand-list
- (sort (all-completions
- he-search-string
- minibuffer-completion-table)
- 'string<))))
- (while (and he-expand-list
- (he-string-member (car he-expand-list) he-tried-table))
- (setq he-expand-list (cdr he-expand-list)))
- (if (null he-expand-list)
- (progn (if old (he-reset-string)) ())
- (progn
- (he-substitute-string (car he-expand-list))
- (setq he-tried-table (cons (car he-expand-list)
- (cdr he-tried-table)))
- t))))
-
- (defun python-django-minibuffer-try-complete-filenames (old)
- "Try to complete filenames in command arguments.
- The argument OLD has to be nil the first call of this function, and t
- for subsequent calls (for further possible completions of the same
- string). It returns t if a new completion is found, nil otherwise."
- (if (not old)
- (progn
- (he-init-string (let ((max-point (point)))
- (save-excursion
- (goto-char (he-file-name-beg))
- (re-search-forward "--?[a-z0-9_-]+=?" max-point t)
- (point)))
- (point))
- (let ((name-part (file-name-nondirectory he-search-string))
- (dir-part (expand-file-name (or (file-name-directory
- he-search-string) ""))))
- (if (not (he-string-member name-part he-tried-table))
- (setq he-tried-table (cons name-part he-tried-table)))
- (if (and (not (equal he-search-string ""))
- (file-directory-p dir-part))
- (setq he-expand-list (sort (file-name-all-completions
- name-part
- dir-part)
- 'string-lessp))
- (setq he-expand-list ())))))
- (while (and he-expand-list
- (he-string-member (car he-expand-list) he-tried-table))
- (setq he-expand-list (cdr he-expand-list)))
- (if (null he-expand-list)
- (progn
- (if old (he-reset-string))
- ())
- (let ((filename (he-concat-directory-file-name
- (file-name-directory he-search-string)
- (car he-expand-list))))
- (he-substitute-string filename)
- (setq he-tried-table (cons (car he-expand-list) (cdr he-tried-table)))
- (setq he-expand-list (cdr he-expand-list))
- t)))
-
-
- ;;; Minibuffer
-
- (defvar python-django-minibuffer-complete-command-map
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map minibuffer-local-must-match-map)
- map)
- "Keymap used for completing commands in minibuffer.")
-
- (defvar python-django-minibuffer-complete-command-args-map
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map minibuffer-local-map)
- (define-key map "\t" 'hippie-expand)
- (define-key map [remap scroll-other-window]
- 'python-django-minibuffer-scroll-help-window)
- (define-key map [remap scroll-other-window-down]
- 'python-django-minibuffer-scroll-help-window-down)
- map)
- "Keymap used for completing command args in minibuffer.")
-
- (defun python-django-minibuffer-read-command (&optional trigger-help)
- "Read django management command from minibuffer.
- Optional argument TRIGGER-HELP sets if help buffer with commmand
- details should be displayed."
- (let* ((current-buffer (current-buffer))
- (command
- (minibuffer-with-setup-hook
- (lambda ()
- (python-util-clone-local-variables current-buffer)
- (setq minibuffer-completion-table
- (python-django-mgmt-list-commands)))
- (read-from-minibuffer
- "./manage.py: " nil
- python-django-minibuffer-complete-command-map))))
- (when trigger-help
- (python-django-help command t))
- command))
-
- (defun python-django-minibuffer-read-command-args (command)
- "Read django management arguments for command from minibuffer.
- Arguments are parsed for especific COMMAND."
- (let* ((current-buffer (current-buffer)))
- (minibuffer-with-setup-hook
- (lambda ()
- (python-util-clone-local-variables current-buffer)
- (setq minibuffer-completion-table
- (python-django-mgmt-list-command-args command))
- (set (make-local-variable 'hippie-expand-try-functions-list)
- '(python-django-minibuffer-try-complete-args
- python-django-minibuffer-try-complete-filenames)))
- (read-from-minibuffer
- (format "./manage.py %s (args): " command)
- nil python-django-minibuffer-complete-command-args-map))))
-
- (defun python-django-minibuffer-read-list (thing &rest args)
- "Helper function to read list of THING from minibuffer.
- Optional argument ARGS are the args passed to the THING."
- (let ((objs))
- (catch 'exit
- (while t
- (add-to-list
- 'objs
- (apply thing args) t)
- (when (not (y-or-n-p "Add another? "))
- (throw 'exit (mapconcat 'identity objs " ")))))))
-
- (defun python-django-minibuffer-read-file-name (prompt)
- "Read a single file name from minibuffer.
- PROMPT is a string to prompt user for filenames."
- (let ((use-dialog-box nil))
- ;; Lets make shell expansion work.
- (replace-regexp-in-string
- "[\\]\\*" "*"
- (shell-quote-argument
- (let ((func
- (if ido-mode
- 'ido-read-file-name
- 'read-file-name)))
- (funcall func prompt python-django-project-root
- python-django-project-root nil))))))
-
- (defun python-django-minibuffer-read-file-names (prompt)
- "Read a list of file names from minibuffer.
- PROMPT is a string to prompt user for filenames."
- (python-django-minibuffer-read-list
- 'python-django-minibuffer-read-file-name prompt))
-
- (defun python-django-minibuffer-read-app (prompt &optional initial-input full)
- "Read django app from minibuffer.
- PROMPT is a string to prompt user for app. Optional argument
- INITIAL-INPUT is the initial prompted value. When FULL is
- non-nill the full module name for the installed app is prompted."
- (let ((apps (mapcar (lambda (app)
- (if (not full)
- (car (last (split-string app "\\.")))
- app))
- (python-django-info-get-setting "INSTALLED_APPS")))
- (current-buffer (current-buffer)))
- (minibuffer-with-setup-hook
- (lambda ()
- (python-util-clone-local-variables current-buffer)
- (setq minibuffer-completion-table apps))
- (catch 'app
- (while t
- (let ((app (read-from-minibuffer
- prompt initial-input minibuffer-local-must-match-map)))
- (when (> (length app) 0)
- (throw 'app app))))))))
-
- (defun python-django-minibuffer-read-apps (prompt &optional initial-input full)
- "Read django apps from minibuffer.
- PROMPT is a string to prompt user for app. Optional argument
- INITIAL-INPUT is the initial prompted value. When FULL is
- non-nill the full module name for the installed app is prompted."
- (python-django-minibuffer-read-list
- 'python-django-minibuffer-read-app prompt full))
-
- (defun python-django-minibuffer-read-database (prompt &optional initial-input)
- "Read django database router name from minibuffer.
- PROMPT is a string to prompt user for database.
- Optional argument INITIAL-INPUT is the initial prompted value."
- (let ((databases (mapcar (lambda (router)
- (format "%s" (car router)))
- (python-django-info-get-setting "DATABASES")))
- (current-buffer (current-buffer)))
- (minibuffer-with-setup-hook
- (lambda ()
- (python-util-clone-local-variables current-buffer)
- (setq minibuffer-completion-table databases))
- (catch 'db
- (while t
- (let ((db (read-from-minibuffer
- prompt initial-input minibuffer-local-must-match-map)))
- (when (> (length db) 0)
- (throw 'db db))))))))
-
- (defun python-django-minibuffer-read-migration (prompt app)
- "Read south migration number for given app from minibuffer.
- PROMPT is a string to prompt user for database. APP is the app
- to read migrations from."
- (let* ((migrations (python-django-info-get-app-migrations app)))
- (minibuffer-with-setup-hook
- (lambda ()
- (setq minibuffer-completion-table migrations))
- (let ((migration (read-from-minibuffer
- prompt nil minibuffer-local-must-match-map)))
- (when (not (string= migration ""))
- (substring migration 0 4))))))
-
- (defun python-django-minibuffer-read-from-list (prompt lst &optional default)
- "Read a value from a list from minibuffer.
- PROMPT is a string to prompt user. LST is the list containing
- the values to choose from. Optional argument DEFAULT is the
- default value."
- (minibuffer-with-setup-hook
- (lambda ()
- (setq minibuffer-completion-table lst))
- (read-from-minibuffer prompt default minibuffer-local-must-match-map)))
-
-
- ;;; Management commands
-
- (defvar python-django-mgmt--available-commands nil
- "Alist with cached list of management commands for each project.")
-
- (defun python-django-mgmt-list-commands (&optional force)
- "List available management commands.
- Optional argument FORCE makes the function to recalculate the
- list of command for current project instead of getting it from
- the `python-django-mgmt--available-commands' cache."
- (and force
- (set (make-local-variable 'python-django-mgmt--available-commands) nil))
- (cdr
- (or python-django-mgmt--available-commands
- (let ((help-string (python-django-help)))
- (set (make-local-variable 'python-django-mgmt--available-commands)
- (let ((help-string (python-django-help))
- (commands))
- (with-temp-buffer
- (insert help-string)
- (goto-char (point-min))
- (delete-region
- (and (re-search-forward "Usage: manage.py")
- (line-beginning-position))
- (or (and
- (re-search-forward "Available subcommands:\n" nil t)
- (forward-line -1)
- (line-beginning-position))
- (point-max)))
- (goto-char (point-min))
- (re-search-forward "Available subcommands:\n")
- (while (re-search-forward " +\\([a-z0-9_]+\\)\n" nil t)
- (setq commands
- (cons (match-string-no-properties 1) commands)))
- (reverse commands))))))))
-
- (defun python-django-mgmt-list-command-args (command)
- "List available arguments for COMMAND."
- (let ((help-string (python-django-help command))
- (args))
- (with-temp-buffer
- (insert help-string)
- (goto-char (point-min))
- (when (re-search-forward "^Options:\n" nil t)
- (while (re-search-forward "--[a-z0-9_-]+=?" nil t)
- (setq args (cons (match-string 0) args))
- (append args (match-string 0)))
- (sort args 'string<)))))
-
- (defun python-django-mgmt-make-comint (command process-name)
- "Run COMMAND with PROCESS-NAME in generic Comint buffer."
- (apply 'make-comint process-name
- (executable-find python-shell-interpreter) nil
- (split-string-and-unquote command)))
-
- (defun python-django-mgmt-make-comint-for-shell (command process-name)
- "Run COMMAND with PROCESS-NAME in generic Comint buffer."
- (let ((python-shell-interpreter-args command))
- (python-shell-make-comint (python-shell-parse-command) process-name)))
-
- (defun python-django-mgmt-make-comint-for-shell_plus (command process-name)
- "Run COMMAND with PROCESS-NAME in generic Comint buffer."
- (python-django-mgmt-make-comint-for-shell command process-name))
-
- (defun python-django-mgmt-make-comint-for-runserver (command process-name)
- "Run COMMAND with PROCESS-NAME in generic Comint buffer."
- (let ((python-shell-enable-font-lock nil))
- (python-django-mgmt-make-comint-for-shell command process-name)))
-
- (defun python-django-mgmt-make-comint-for-runserver_plus (command process-name)
- "Run COMMAND with PROCESS-NAME in generic Comint buffer."
- (python-django-mgmt-make-comint-for-runserver command process-name))
-
- (defun python-django-mgmt-make-comint-for-dbshell (command process-name)
- "Run COMMAND with PROCESS-NAME in generic Comint buffer."
- (let* ((dbsetting (python-django-info-get-setting "DATABASES"))
- (dbengine (cdr (assoc 'ENGINE (assoc 'default dbsetting))))
- (sql-interactive-product-1
- (cond ((string= dbengine "django.db.backends.mysql")
- 'mysql)
- ((string= dbengine "django.db.backends.oracle")
- 'oracle)
- ((string= dbengine "django.db.backends.postgresql")
- 'postgres)
- ((string= dbengine "django.db.backends.sqlite3")
- 'sqlite)
- (t nil)))
- (buffer
- (python-django-mgmt-make-comint command process-name)))
- (with-current-buffer buffer
- (setq sql-buffer (current-buffer)
- sql-interactive-product sql-interactive-product-1)
- (sql-interactive-mode))
- buffer))
-
- (defcustom python-django-mgmt-buffer-switch-function 'display-buffer
- "Function for switching to the process buffer.
- The function receives one argument, the management command
- process buffer."
- :group 'python-django
- :type '(radio (function-item switch-to-buffer)
- (function-item pop-to-buffer)
- (function-item display-buffer)
- (function :tag "Other")))
-
- (defvar python-django-mgmt--previous-window-configuration nil
- "Snapshot of previous window configuration before executing command.
- This variable is for internal purposes, don't use it directly.")
-
- (defun python-django-mgmt-restore-window-configuration ()
- "Restore window configuration after running a management command."
- (and python-django-mgmt--previous-window-configuration
- (set-window-configuration
- python-django-mgmt--previous-window-configuration)))
-
- (defvar python-django-mgmt-parent-buffer nil
- "Parent project buffer for current process.")
-
- (defvar python-django-mgmt--opened-buffers nil
- "Alist of currently opened process buffers.")
-
- (defun python-django-mgmt-buffer-list (&optional parent-buffer)
- "Return all opened buffer names for PARENT-BUFFER.
- Optional Argument PARENT-BUFFER defaults to the current buffer."
- (python-django-util-alist-get
- (or parent-buffer (current-buffer))
- python-django-mgmt--opened-buffers))
-
- (defvar python-django-mgmt--buffer-index 0)
-
- (defun python-django-mgmt-buffer-get (&optional index)
- "Get management buffer by INDEX.
- Optional Argument INDEX defaults to the value of
- `python-django-mgmt--buffer-index'."
- (let ((buffer-list (python-django-mgmt-buffer-list)))
- (and buffer-list
- (nth (mod (or index python-django-mgmt--buffer-index)
- (length buffer-list)) buffer-list))))
-
- (defun python-django-mgmt-cycle-buffers-forward (&optional arg)
- "Cycle opened process buffers forward.
- With Optional Argument ARG cycle that many buffers."
- (interactive "p")
- (setq arg (or arg 1))
- (let ((buffers (python-django-mgmt-buffer-list)))
- (and buffers
- (let ((newindex
- (mod (+ python-django-mgmt--buffer-index arg)
- (length buffers))))
- (set (make-local-variable
- 'python-django-mgmt--buffer-index) newindex)
- (display-buffer (nth newindex buffers))))))
-
- (defun python-django-mgmt-cycle-buffers-backward (&optional arg)
- "Cycle opened process buffers backward.
- With Optional Argument ARG cycle that many buffers."
- (interactive "p")
- (python-django-mgmt-cycle-buffers-forward (- (or arg 1))))
-
- (defun python-django-mgmt-run-command (command
- &optional args capture-ouput no-pop)
- "Run management COMMAND with given ARGS.
- When optional argument CAPTURE-OUPUT is non-nil process output is
- not truncated by the `comint-truncate-buffer' output filter. If
- optional argument NO-POP is provided the process buffer is not
- displayed automatically."
- (interactive
- (list
- (setq command
- (python-django-minibuffer-read-command t))
- (python-django-minibuffer-read-command-args command)))
- (python-django-help-close)
- (when (not (member command (python-django-mgmt-list-commands)))
- (error
- "Management command %s is not available in current project" command))
- (let* ((args (or args ""))
- (process-environment
- (python-django-info-calculate-process-environment))
- (exec-path (python-shell-calculate-exec-path))
- (process-name
- (replace-regexp-in-string
- "[\t ]+$" ""
- (format "[Django: %s (%s)] ./manage.py %s %s"
- python-django-project-name
- (python-django-util-shorten-settings)
- command args)))
- (buffer-name (format "*%s*" process-name))
- (current-buffer (current-buffer))
- (make-comint-special-func-name
- (intern
- (format "python-django-mgmt-make-comint-for-%s" command)))
- (full-command
- (format "%s %s %s"
- python-django-project-manage.py
- command args)))
- (if (not (fboundp make-comint-special-func-name))
- (python-django-mgmt-make-comint full-command process-name)
- (funcall make-comint-special-func-name full-command process-name))
- (with-current-buffer buffer-name
- (python-util-clone-local-variables current-buffer)
- (and (not capture-ouput)
- (add-hook 'comint-output-filter-functions
- 'comint-truncate-buffer nil t))
- (set (make-local-variable
- 'python-django-mgmt-parent-buffer) current-buffer)
- (python-django-util-alist-add
- current-buffer (current-buffer)
- python-django-mgmt--opened-buffers)
- (add-hook
- 'kill-buffer-hook
- (lambda ()
- (python-django-util-alist-del
- python-django-mgmt-parent-buffer (current-buffer)
- python-django-mgmt--opened-buffers))
- nil t))
- (unless no-pop
- (funcall python-django-mgmt-buffer-switch-function buffer-name)
- (with-current-buffer buffer-name
- (and (get-buffer-process (current-buffer))
- (comint-goto-process-mark))))
- buffer-name))
-
- (add-to-list 'debug-ignored-errors
- "^Management command .* is not available in current project.")
-
- (defun python-django-mgmt-kill (&optional buffer)
- "Kill current command's BUFFER."
- (interactive)
- (setq buffer (or buffer (python-django-mgmt-buffer-get)))
- (when (and buffer (or (not (called-interactively-p 'any))
- (y-or-n-p
- (format "Kill %s? " buffer))))
- (let ((win (get-buffer-window buffer 0))
- (proc (get-buffer-process buffer)))
- (and win (delete-window win))
- (and proc (set-process-query-on-exit-flag proc nil))
- (kill-buffer buffer)
- (python-django-mgmt-cycle-buffers-forward))))
-
- (defun python-django-mgmt-kill-all (&optional command)
- "Kill all running commands for current project after CONFIRM.
- When called with universal argument you can filter the COMMAND to kill."
- (interactive
- (list
- (and current-prefix-arg
- (python-django-minibuffer-read-command nil))))
- (when (or (not (called-interactively-p 'any))
- (y-or-n-p
- (format "Do you want to kill all running commands for %s? "
- python-django-project-name)))
- (dolist (buffer
- (python-django-mgmt-buffer-list (current-buffer)))
- (when (or (not command)
- (string-match
- (format "\\./manage.py %s" (or command "")) buffer))
- (let ((win (get-buffer-window buffer 0))
- (proc (get-buffer-process buffer)))
- (and win (delete-window win))
- (and proc (set-process-query-on-exit-flag proc nil)))
- (kill-buffer buffer)))))
-
-
- ;;; Management shortcuts
-
- (defvar python-django-qmgmt-process-status-ok nil
- "Non-nil if management command process ended successfully.
- This variable is set automatically and locally to the management
- command process buffer after the process finishes, making it
- available for user defined callbacks. See
- `python-django-qmgmt-kill-and-msg-callback' as an example for how
- to use this variable to execute callback code only if process
- ended successfully.")
-
- (defmacro python-django-qmgmt-define (name doc-or-args
- &optional args &rest iswitches)
- "Define a quick management command.
- Argument NAME is a symbol and it is used to calculate the
- management command this command will execute, so it should have
- the form cmdname[-rest]. Argument DOC-OR-ARGS might be the
- docstring for the defined command or the list of arguments, when
- a docstring is supplied ARGS is used as the list of arguments
- instead. The rest ISWITCHES is a list of interactive switches
- the user will be prompted for.
-
- This is a full example that will define how to execute Django's
- dumpdata for the current application quickly:
-
- (python-django-qmgmt-define dumpdata-app
- \"Run dumpdata for current application.\"
- (:submenu \"Database\" :switches \"--format=json\" :binding \"dda\")
- (database \"Database\" \"default\" \"--database=\")
- (app \"App\"))
-
- When that's is evaled a command called
- `python-django-qmgmt-dumpdata-app' is created and will react
- depending on the arguments passed to this macro.
-
- All commands defined by this macro, when called with `prefix-arg'
- will ask the user for values instead of using defaults.
-
- ARGS is a property list. Valid keys are (all optional):
-
- + :binding, when defined, the new command is bound to the
- default prefix for quick management commands plus this value.
-
- + :capture-output, when non-nil, the command output is not
- truncated by the `comint-truncate-buffer' output filter.
-
- + :msg, when defined, commands that use the
- `python-django-qmgmt-kill-and-msg-callback' show this instead
- of the buffer contents.
-
- + :no-pop, when non-nil, causes the process buffer to not be
- displayed.
-
- + :submenu, when defined, the quick management command is
- added within that submenu tree. If omitted the menu is added
- to the root.
-
- + :switches, when defined, the new command is executed with
- these fixed switches.
-
- If you define any extra keys they will not be taken into account
- by this macro but you may well use them in your command's
- callback.
-
- ISWITCHES have the form (VARNAME PROMPT DEFAULT SWITCH
- FORCE-ASK), you can add 0 or more ISWITCHES depending on the
- number of parameters you need to pass to the management command.
- The description for each element of the list are:
-
- + VARNAME must be a unique symbol not used in other switch.
-
- + PROMPT must be a string for the prompt that will be shown
- when user is asked for a value using `read-string' or it can
- be a expresion that will be used to read the value for
- VARNAME. When you need to use the calculated value of
- DEFAULT in the provided expression you can just use that
- variable like this:
-
- (read-file-name \"Fixture: \" nil default)
-
- + DEFAULT is an expression to be executed in order to
- calculate the default value for VARNAME. This is optional
- and in the case is not provided or returns nil after executed
- the user will be prompted to insert a value for VARNAME.
-
- + SWITCH is a string that represents the switch used to pass
- the VARNAME's value to Django's management command.
-
- + FORCE-ASK might be nil or non-nil, when is non-nil the user
- will be asked to insert a value for VARNAME even if a default
- value is available.
-
- Each command defined via this macro may have a callback to be
- executed when the process finishes correctly. The way to define
- callbacks is to append -callback to the defined name, for
- instance if you defined a quick management command called syncdb,
- then you need to create a function named
- `python-django-qmgmt-syncdb-callback' and it will be called with
- an alist containing all ISWITCHES and ARGS with the
- additional :command key holding the executed command. See the
- `python-django-qmgmt-kill-and-msg-callback' function for a nice
- example of a callback."
- (declare
- (indent defun))
- (let* ((docstring (and (stringp doc-or-args) doc-or-args))
- (args (if docstring args doc-or-args))
- (args (if (eq ?: (string-to-char (symbol-name (car args))))
- args
- ;; args is not a plist, append it to iswitches and
- ;; set args to nil.
- (setq iswitches (cons args iswitches))
- nil))
- (defun-name (intern (format "qmgmt-%s" name)))
- (full-name (intern (format "python-django-%s" defun-name)))
- (callback (intern (format "%s-callback" full-name)))
- (command (car (split-string (format "%s" name) "-")))
- (binding (plist-get args :binding))
- (capture-output (plist-get args :capture-output))
- (no-pop (plist-get args :no-pop))
- (quick-submenu (plist-get args :submenu))
- (switches (plist-get args :switches))
- (keys (and binding (format "c%s" binding)))
- (defargs (mapcar 'car iswitches))
- (cmd-spec
- ;; The spec is the shell command with placeholders in it.
- ;; Example: ./manage.py dumpdata --database=<database>
- ;; --indent=<indent> --format=<format> <app>
- (concat
- (format "./manage.py %s " command)
- (and switches (format "%s " switches))
- (and defargs
- (mapconcat
- (lambda (arg)
- (let* ((switch (nth 3 arg))
- (switch
- (cond
- ((eq (length switch) 0)
- "")
- ((eq ?= (car (last (append switch nil))))
- switch)
- (t (format "%s " switch))))
- (varname (symbol-name (nth 0 arg))))
- (format "%s<%s>" switch varname)))
- iswitches
- " "))))
- (interactive-code
- (mapcar
- (lambda (arg)
- (let* ((default (nth 2 arg))
- (switch (nth 3 arg))
- (switch
- (cond ((eq (length switch) 0)
- "")
- ((eq ?= (car (last (append switch nil))))
- switch)
- (t (format "%s " switch))))
- (force-ask (nth 4 arg))
- (read-func
- (if (listp (nth 1 arg))
- (nth 1 arg)
- `(read-string ,(nth 1 arg) default))))
- `(concat
- ,switch
- (setq ,(car arg)
- (let ((default ,default))
- (if (or ,force-ask
- current-prefix-arg
- (not default))
- ,read-func
- default))))))
- iswitches))
- (item-docstring
- (if docstring
- (car (split-string docstring "\n"))
- (format "Run ./manage.py %s" cmd-spec)))
- (full-docstring
- (format "%s\n\n%s\n\n%s"
- cmd-spec
- (or docstring item-docstring)
- (concat
- "This is an interactive command defined by "
- "`python-django-qmgmt-define' macro.\n"
- "Users can override any parameter with defaults by "
- "calling this command with `prefix-arg' .\n\n"
- (and switches
- (format "Default switches: \n\n * %s\n\n" switches))
- (and
- iswitches
- (format
- "Arguments: \n\n%s"
- (mapconcat
- (lambda (arg)
- (let* ((default (nth 2 arg))
- (switch (nth 3 arg))
- (switch
- (cond
- ((eq (length switch) 0)
- nil)
- ((eq ?= (car (last (append switch nil))))
- switch)
- (t (format "%s " switch))))
- (force-ask (nth 4 arg)))
- (concat
- (format " * %s:\n"
- (upcase (symbol-name (car arg))))
- (format " + Switch: %s\n" switch)
- (format " + Defaults: %s\n"
- (prin1-to-string default))
- (format " + Read SPEC: %s\n"
- (prin1-to-string (nth 1 arg)))
- (format " + Force prompt: %s\n"
- force-ask)
- (format " + Requires user interaction?: %s"
- (if (or force-ask (not default))
- "yes" "no")))))
- iswitches "\n\n")))))))
- `(progn
- (defun ,full-name ,defargs
- ,full-docstring
- (interactive
- (let ,defargs
- (list ,@interactive-code)))
- (setq python-django-mgmt--previous-window-configuration
- (current-window-configuration))
- (let* ((cmd-args (concat ,switches (and ,switches " ")
- (mapconcat 'symbol-value ',defargs " ")))
- (process (get-buffer-process
- (python-django-mgmt-run-command
- ,command cmd-args
- ,capture-output ,no-pop))))
- (message "Running: ./manage.py %s %s" ,command cmd-args)
- (when (fboundp ',callback)
- ;; There's a callback defined, we create another function
- ;; by applying partially the existing callback and giving
- ;; it an alist with all the user selected values as an
- ;; argument. Then the callback is executed when the
- ;; process finishes correctly.
- (set-process-sentinel
- process
- (apply-partially
- #'(lambda (cb process status)
- (set-buffer (process-buffer process))
- (set (make-local-variable 'python-django-qmgmt-process-status-ok)
- (string= status "finished\n"))
- (funcall cb))
- (apply-partially
- ',callback
- (append
- (cons
- ;; Add the executed command.
- (cons :command ,command)
- ;; Convert the args plist to an alist
- (mapcar (lambda (key)
- (cons key (plist-get ',args key)))
- (let ((lst)
- (i 0))
- ;; Collect all keys from args plist
- (while (< i (length ',args))
- (setq lst (cons (nth i ',args) lst))
- (setq i (+ i 2)))
- lst)))
- ;; Retrieve all switches.
- (mapcar
- #'(lambda (sym)
- (let ((val (symbol-value sym)))
- (cons
- sym
- (cond
- ((string-match "^--" val)
- (substring val (1+ (string-match "=" val))))
- ((string-match "^-" val)
- (substring val 3))
- (t val)))))
- ',defargs) nil)))))))
- ;; Add the specified binding for this quick command.
- (and ,binding
- (ignore-errors
- (define-key python-django-mode-map ,keys ',full-name)))
- ;; Add menu stuff.
- (easy-menu-add-item
- 'python-django-menu nil (list ,quick-submenu) "---")
- (easy-menu-add-item
- 'python-django-menu (list ,quick-submenu)
- [,cmd-spec
- ,full-name
- :help ,item-docstring
- :active (member ,command (python-django-mgmt-list-commands))
- ] "---")
- ;; Just like defun, return the defined function.
- #',full-name)))
-
- (defun python-django-qmgmt-kill-and-msg-callback (args)
- "Kill the process buffer and show message or output.
- Argument ARGS is an alist with the arguments passed to the
- management command."
- (when python-django-qmgmt-process-status-ok
- (let ((msg (or (cdr (assq :msg args))
- (buffer-substring-no-properties
- (point-min) (point-max))))
- (buffer-name (buffer-name)))
- (kill-buffer)
- (python-django-mgmt-restore-window-configuration)
- (display-message-or-buffer msg buffer-name))))
-
- (python-django-qmgmt-define collectstatic
- "Collect static files."
- (:submenu "Tools" :binding "ocs"))
-
- (defalias 'python-django-qmgmt-collectstatic-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define clean_pyc
- "Remove all python compiled files from the project."
- (:submenu "Tools" :binding "ocp" :no-pop t
- :msg "All *.pyc and *.pyo cleaned."))
-
- (defalias 'python-django-qmgmt-clean_pyc-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define create_command
- "Create management commands directory structure for app."
- (:submenu "Tools" :binding "occ" :no-pop t)
- (app (python-django-minibuffer-read-app "App name: ")))
-
- (defun python-django-qmgmt-create_command-callback (args)
- "Callback for create_command quick management command.
- Optional argument ARGS args for it."
- (when python-django-qmgmt-process-status-ok
- (let* ((appname (cdr (assoc 'app args)))
- (manage-directory
- (file-name-directory
- (with-current-buffer
- python-django-mgmt-parent-buffer
- python-django-project-manage.py)))
- (default-app-dir
- (expand-file-name appname manage-directory))
- (default-create-dir
- (expand-file-name "management" default-app-dir))
- (delete-safe
- (and (file-exists-p default-app-dir)
- (equal (directory-files default-app-dir)
- '("." ".." "management")))))
- (when (y-or-n-p
- (format "Created in app %s. Move it? " default-app-dir))
- (let ((newdir
- (read-directory-name
- "Move app to: " manage-directory nil t)))
- (if (not (file-exists-p
- (expand-file-name "management" newdir)))
- (rename-file default-create-dir newdir)
- (message
- "Directory structure already exists in %s" appname))
- (and delete-safe (delete-directory default-app-dir t)))))
- (kill-buffer)
- (python-django-mgmt-restore-window-configuration)))
-
- (python-django-qmgmt-define startapp
- "Create new Django app for current project."
- (:submenu "Tools" :binding "osa" :no-pop t)
- (app "App name: "))
-
- (defun python-django-qmgmt-startapp-callback (args)
- "Callback for clean_pyc quick management command.
- Optional argument ARGS args for it."
- (when python-django-qmgmt-process-status-ok
- (let ((appname (cdr (assoc 'app args)))
- (manage-directory
- (file-name-directory
- (with-current-buffer
- python-django-mgmt-parent-buffer
- python-django-project-manage.py))))
- (when (y-or-n-p
- (format
- "App created in %s. Do you want to move it? "
- manage-directory))
- (rename-file
- (expand-file-name appname manage-directory)
- (read-directory-name
- "Move app to: " manage-directory nil t))))
- (kill-buffer)
- (python-django-mgmt-restore-window-configuration)))
-
- ;; Shell
-
- (python-django-qmgmt-define shell
- "Run a Python interpreter for this project."
- (:submenu "Shell" :binding "ss"))
-
- (python-django-qmgmt-define shell_plus
- "Like the 'shell' but autoloads all models."
- (:submenu "Shell" :binding "sp"))
-
- ;; Database
-
- (python-django-qmgmt-define syncdb
- "Sync database tables for all INSTALLED_APPS."
- (:submenu "Database" :binding "dsy" :no-pop t)
- (database (python-django-minibuffer-read-database "Database: " default)
- "default" "--database="))
-
- (defalias 'python-django-qmgmt-syncdb-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define dbshell
- "Run the command-line client for specified database."
- (:submenu "Database" :binding "dsh")
- (database (python-django-minibuffer-read-database "Database: " default)
- "default" "--database="))
-
- (defvar python-django-qmgmt-dumpdata-formats '("json" "xml" "yaml")
- "Valid formats for dumpdata management command.")
-
- (defcustom python-django-qmgmt-dumpdata-default-format "json"
- "Default format for quick dumpdata."
- :group 'python-django
- :type `(choice
- ,@(mapcar (lambda (fmt)
- `(string :tag ,fmt ,fmt))
- python-django-qmgmt-dumpdata-formats))
- :safe 'stringp)
-
- (defcustom python-django-qmgmt-dumpdata-default-indent 4
- "Default indent value quick dumpdata."
- :group 'python-django
- :type 'integer
- :safe 'integerp)
-
- (python-django-qmgmt-define dumpdata-all
- "Save the contents of the database as a fixture for all apps."
- (:submenu "Database" :binding "ddp" :no-pop t :capture-output t)
- (database (python-django-minibuffer-read-database "Database: " default)
- "default" "--database=")
- (indent (number-to-string
- (read-number "Indent Level: "
- (string-to-number default)))
- (number-to-string python-django-qmgmt-dumpdata-default-indent)
- "--indent=")
- (format (python-django-minibuffer-read-from-list
- "Dump to format: " python-django-qmgmt-dumpdata-formats default)
- "json" "--format="))
-
- (python-django-qmgmt-define dumpdata-app
- "Save the contents of the database as a fixture for the specified app."
- (:submenu "Database" :binding "dda" :no-pop t :capture-output t)
- (database (python-django-minibuffer-read-database "Database: " default)
- "default" "--database=")
- (indent (number-to-string
- (read-number "Indent Level: "
- (string-to-number default))) "4" "--indent=")
- (format (python-django-minibuffer-read-from-list
- "Dump to format: " python-django-qmgmt-dumpdata-formats default)
- "json" "--format=")
- (app (python-django-minibuffer-read-app "Dumpdata for App: ")))
-
- (defun python-django-qmgmt-dumpdata-callback (args)
- "Callback executed after dumpdata finishes.
- ARGS is an alist containing arguments passed to the quick
- management command."
- (when python-django-qmgmt-process-status-ok
- (let ((file-name
- (catch 'file-name
- (while t
- (let ((file-name
- (read-file-name
- "Save fixture to file: "
- (expand-file-name
- (with-current-buffer
- python-django-mgmt-parent-buffer
- python-django-project-root)) nil nil nil)))
- (if (not (file-exists-p file-name))
- (throw 'file-name file-name)
- (when (y-or-n-p
- (format "File `%s' exists; overwrite? " file-name))
- (throw 'file-name file-name)))))))
- (output-buffer (buffer-substring-no-properties
- (point-min) (point-max))))
- (with-temp-buffer
- (set (make-local-variable 'require-final-newline) t)
- (insert output-buffer)
- ;; Ensure there's a final newline
- (and (> (point-max) (point-min))
- (not (= (char-after (1- (point-max))) ?\n))
- (insert "\n"))
- (write-region
- (progn
- ;; Remove possible logs from output.
- (goto-char (point-min))
- (re-search-forward
- "^\\[\\|^<\\?xml +version=\"\\|^- +fields: " nil t)
- (beginning-of-line 1)
- (point))
- (point-max)
- file-name))
- (kill-buffer)
- (python-django-mgmt-restore-window-configuration)
- (message "Fixture saved to file `%s'." file-name))))
-
- (defalias 'python-django-qmgmt-dumpdata-app-callback
- 'python-django-qmgmt-dumpdata-callback)
- (defalias 'python-django-qmgmt-dumpdata-all-callback
- 'python-django-qmgmt-dumpdata-callback)
-
- (python-django-qmgmt-define flush
- "Execute 'sqlflush' on the given database."
- (:submenu "Database" :binding "df" :msg "Flushed database")
- (database (python-django-minibuffer-read-database "Database: " default)
- "default" "--database="))
-
- (defalias 'python-django-qmgmt-flush-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define loaddata
- "Install the named fixture(s) in the database."
- (:submenu "Database" :binding "dl")
- (database (python-django-minibuffer-read-database "Database: " default)
- "default" "--database=")
- (fixtures (python-django-minibuffer-read-file-names "Fixtures: ")))
-
- (defalias 'python-django-qmgmt-loaddata-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define validate
- "Validate all installed models."
- (:submenu "Database" :binding "dv"))
-
- (defalias 'python-django-qmgmt-validate-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define graph_models-all
- "Creates a Graph of models for all project apps."
- (:submenu "Database" :switches "-ag" :binding "dgg")
- (filename
- (expand-file-name
- (read-file-name "Filename for generated Graph: "
- default default))
- (expand-file-name
- "graph_all.png" python-django-project-root)
- "--output=" t))
-
- (python-django-qmgmt-define graph_models-apps
- "Creates a Graph of models for given apps."
- (:submenu "Database" :binding "dga")
- (apps (python-django-minibuffer-read-apps "Graph for App: "))
- (filename
- (expand-file-name
- (read-file-name "Filename for generated Graph: " default default))
- (expand-file-name
- (format "graph_%s.png" (replace-regexp-in-string " " "_" apps))
- python-django-project-root)
- "--output=" t))
-
- (defun python-django-qmgmt-graph_models-callback (args)
- "Callback for graph_model quick management command.
- Optional argument ARGS args for it."
- (when python-django-qmgmt-process-status-ok
- (let ((open (y-or-n-p "Open generated graph? ")))
- (kill-buffer)
- (python-django-mgmt-restore-window-configuration)
- (and open
- (find-file (cdr (assoc 'filename args)))))))
-
- (defalias 'python-django-qmgmt-graph_models-all-callback
- 'python-django-qmgmt-graph_models-callback)
- (defalias 'python-django-qmgmt-graph_models-apps-callback
- 'python-django-qmgmt-graph_models-callback)
-
- ;; i18n
-
- (python-django-qmgmt-define makemessages-all
- "Create/Update translation string files."
- (:submenu "i18n" :switches "--all" :binding "im"))
-
- (defalias 'python-django-qmgmt-makemessages-all-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define compilemessages-all
- "Compile project .po files to .mo."
- (:submenu "i18n" :binding "ic"))
-
- (defalias 'python-django-qmgmt-compilemessages-all-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- ;; Dev Server
-
- (defcustom python-django-qmgmt-runserver-default-bindaddr "localhost:8000"
- "Default binding address for quick runserver."
- :group 'python-django
- :type 'string
- :safe 'stringp)
-
- (defcustom python-django-qmgmt-testserver-default-bindaddr "localhost:8000"
- "Default binding address for quick testserver."
- :group 'python-django
- :type 'string
- :safe 'stringp)
-
- (defcustom python-django-qmgmt-mail_debug-default-bindaddr "localhost:1025"
- "Default binding address for quick mail_debug."
- :group 'python-django
- :type 'string
- :safe 'stringp)
-
- (python-django-qmgmt-define runserver
- "Start development Web server."
- (:submenu "Server" :binding "rr")
- (bindaddr "Serve on [ip]:[port]: "
- python-django-qmgmt-runserver-default-bindaddr))
-
- (python-django-qmgmt-define runserver_plus
- "Start extended development Web server."
- (:submenu "Server" :binding "rp")
- (bindaddr "Serve on [ip]:[port]: "
- python-django-qmgmt-runserver-default-bindaddr))
-
- (python-django-qmgmt-define testserver
- "Start development server with data from the given fixture(s)."
- (:submenu "Server" :binding "rt")
- (bindaddr "Serve on [ip]:[port]: "
- python-django-qmgmt-testserver-default-bindaddr)
- (fixtures (python-django-minibuffer-read-file-names "Fixtures: ")))
-
- (python-django-qmgmt-define mail_debug
- "Start a test mail server for development."
- (:submenu "Server" :binding "rm")
- (bindaddr "Serve on [ip]:[port]: "
- python-django-qmgmt-mail_debug-default-bindaddr))
-
- ;; Testing
-
- (python-django-qmgmt-define test-all
- "Run the test suite for the entire project."
- (:submenu "Test" :binding "tp"))
-
- (defalias 'python-django-qmgmt-test-all-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define test-app
- "Run the test suite for the specified app."
- (:submenu "Test" :binding "ta")
- (app (python-django-minibuffer-read-app "Test App: ")))
-
- (defalias 'python-django-qmgmt-test-app-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- ;; South integration
-
- (defun python-django-qmgmt-open-migration-callback (args)
- "Callback for commands that create migrations.
- Argument ARGS is an alist with the arguments passed to the
- management command."
- (when python-django-qmgmt-process-status-ok
- (let ((app (cdr (assq 'app args))))
- (python-django-qmgmt-kill-and-msg-callback args)
- (and (y-or-n-p "Open the created migration? ")
- (find-file
- (expand-file-name
- (car (last (python-django-info-get-app-migrations app)))
- (expand-file-name
- "migrations"
- (python-django-info-get-app-path app))))))))
-
- (python-django-qmgmt-define convert_to_south
- "Convert given app to South."
- (:submenu "South" :binding "soc")
- (app (python-django-minibuffer-read-app "Convert App: ")))
-
- (defalias 'python-django-qmgmt-convert_to_south-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define datamigration
- "Create a new datamigration for the given app."
- (:submenu "South" :binding "sod")
- (app (python-django-minibuffer-read-app "Datamigration for App: "))
- (name "Datamigration name: "))
-
- (defalias 'python-django-qmgmt-datamigration-callback
- 'python-django-qmgmt-open-migration-callback)
-
- (python-django-qmgmt-define migrate-all
- "Run all migrations for all apps."
- (:submenu "South" :switches "--all" :binding "somp")
- (database (python-django-minibuffer-read-database "Database: " default)
- "default" "--database="))
-
- (defalias 'python-django-qmgmt-migrate-all-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define migrate-app
- "Run all migrations for given app."
- (:submenu "South" :binding "soma")
- (database (python-django-minibuffer-read-database "Database: " default)
- "default" "--database=")
- (app (python-django-minibuffer-read-app "Migrate App: ")))
-
- (defalias 'python-django-qmgmt-migrate-app-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define migrate-list
- "Run all migrations for all apps."
- (:submenu "South" :switches "--list" :binding "soml")
- (database (python-django-minibuffer-read-database "Database: " default)
- "default" "--database="))
-
- (defalias 'python-django-qmgmt-migrate-list-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define migrate-app-to
- "Run migrations for given app [up|down]-to given number."
- (:submenu "South" :binding "somt")
- (database (python-django-minibuffer-read-database "Database: " default)
- "default" "--database=")
- (app (python-django-minibuffer-read-app "Migrate App: " nil t))
- (migration (python-django-minibuffer-read-migration "To migration: " app)))
-
- (defalias 'python-django-qmgmt-migrate-app-to-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define schemamigration-initial
- "Create the initial schemamigration for the given app."
- (:submenu "South" :switches "--initial" :binding "sosi")
- (app (python-django-minibuffer-read-app
- "Initial schemamigration for App: ")))
-
- (defalias 'python-django-qmgmt-schemamigration-initial-callback
- 'python-django-qmgmt-kill-and-msg-callback)
-
- (python-django-qmgmt-define schemamigration
- "Create new empty schemamigration for the given app."
- (:submenu "South" :switches "--empty" :binding "soss")
- (app (python-django-minibuffer-read-app
- "Initial schemamigration for App: "))
- (name "Schemamigration name: "))
-
- (defalias 'python-django-qmgmt-schemamigration-callback
- 'python-django-qmgmt-open-migration-callback)
-
- (python-django-qmgmt-define schemamigration-auto
- "Create an automatic schemamigration for the given app."
- (:submenu "South" :switches "--auto" :binding "sosa")
- (app (python-django-minibuffer-read-app
- "Auto schemamigration for App: ")))
-
- (defalias 'python-django-qmgmt-schemamigration-auto-callback
- 'python-django-qmgmt-open-migration-callback)
-
-
- ;;; Fast commands
-
- (defcustom python-django-cmd-etags-command
- "etags `find -name \"*.py\"`"
- "Command used to build tags tables."
- :group 'python-django
- :type 'string)
-
- (defcustom python-django-cmd-grep-function nil
- "Function to grep on a directory.
- The function receives no args, however `default-directory' will
- default to a sane value."
- :group 'python-django
- :type 'function)
-
- (defun python-django-cmd-build-etags ()
- "Build tags for current project."
- (interactive)
- (let ((current-dir default-directory))
- (cd
- (file-name-directory
- python-django-project-manage.py))
- (if (eq 0
- (shell-command
- python-django-cmd-etags-command))
- (message "Tags created sucessfully")
- (message "Tags creation failed"))
- (cd current-dir)))
-
- (defun python-django-cmd-grep ()
- "Grep in project directories."
- (interactive)
- (let ((default-directory
- (or (python-django-ui-directory-at-point)
- (file-name-directory
- python-django-project-manage.py))))
- (if (not python-django-cmd-grep-function)
- (call-interactively #'rgrep)
- (funcall
- python-django-cmd-grep-function default-directory))))
-
- (defun python-django-cmd-open-docs ()
- "Open Django documentation in a browser."
- (interactive)
- (browse-url
- (format
- "https://docs.djangoproject.com/en/%s/"
- (substring (python-django-info-get-version) 0 3))))
-
- (defun python-django-cmd-visit-settings ()
- "Visit settings file."
- (interactive)
- (find-file (python-django-info-module-path
- python-django-project-settings)))
-
- (defun python-django-cmd-visit-virtualenv ()
- "Visit virtualenv directory."
- (interactive)
- (and python-shell-virtualenv-path
- (dired python-shell-virtualenv-path)))
-
- (defun python-django-cmd-visit-project-root ()
- "Visit project root directory."
- (interactive)
- (dired python-django-project-root))
-
- (defun python-django-cmd-dired-at-point ()
- "Open dired at current tree node."
- (interactive)
- (let ((dir (python-django-ui-directory-at-point)))
- (and dir (dired dir))))
-
- (defun python-django-cmd-directory-at-point ()
- "Message the current directory at point."
- (interactive)
- (message (or (python-django-ui-directory-at-point) "")))
-
- (defun python-django-cmd-jump-to-app (app)
- "Jump to APP's directory."
- (interactive
- (list
- (python-django-minibuffer-read-app "Jump to app: " nil t)))
- (let ((app (assq (intern app)
- (python-django-info-get-app-paths))))
- (when app
- (goto-char (point-min))
- (re-search-forward (format " %s" (car app)))
- (python-django-ui-move-to-closest-icon))))
-
- (defun python-django-cmd-jump-to-media (which)
- "Jump to a WHICH media directory."
- (interactive
- (list
- (python-django-minibuffer-read-from-list
- "Jump to: " '("MEDIA_ROOT" "STATIC_ROOT"))))
- (goto-char (point-min))
- (re-search-forward (format " %s" which))
- (python-django-ui-move-to-closest-icon))
-
- (defun python-django-cmd-jump-to-template-dir (which)
- "Jump to a WHICH template directory."
- (interactive
- (list
- (python-django-minibuffer-read-from-list
- "Jump to: "
- (mapcar 'identity (python-django-info-get-setting "TEMPLATE_DIRS")))))
- (goto-char (point-min))
- (re-search-forward (format " %s" which))
- (python-django-ui-move-to-closest-icon))
-
-
-
- ;;; UI stuff
-
- (defvar python-django-ui-ignored-dirs
- '("." ".." ".bzr" ".cdv" "~.dep" "~.dot" "~.nib" "~.plst" ".git" ".hg" ".pc"
- ".svn" "_MTN" "blib" "CVS" "RCS" "SCCS" "_darcs" "_sgbak" "autom4te.cache"
- "cover_db" "_build" ".ropeproject" "__pycache__")
- "Directories ignored when scanning project files.")
-
- (defvar python-django-ui-allowed-extensions
- '("css" "gif" "htm" "html" "jpg" "js" "json" "mo" "png" "po" "py" "txt" "xml"
- "yaml" "scss" "less")
- "Allowed extensions when scanning project files.")
-
- (defcustom python-django-ui-image-enable t
- "Enable images for widgets?"
- :group 'python-django
- :type 'boolean
- :safe 'booleanp)
-
- (defcustom python-django-ui-theme "folder"
- "Default theme for widgets."
- :group 'python-django
- :type 'boolean
- :safe 'stringp)
-
- (defcustom python-django-ui-buffer-switch-function 'switch-to-buffer
- "Function for switching to the project buffer.
- The function receives one argument, the status buffer."
- :group 'python-django
- :type '(radio (function-item switch-to-buffer)
- (function-item pop-to-buffer)
- (function-item display-buffer)
- (function :tag "Other")))
-
- (defun python-django-ui-show-buffer (buffer)
- "Show the Project BUFFER."
- (funcall python-django-ui-buffer-switch-function buffer))
-
- (defun python-django-ui-clean ()
- "Empty current UI buffer."
- (let ((inhibit-read-only t))
- (erase-buffer)))
-
- (defun python-django-ui-insert-header ()
- "Draw header information."
- (insert
- (format "%s\t\t%s\n"
- (propertize
- "Django Version:"
- 'face 'python-django-face-title)
- (propertize
- (python-django-info-get-version)
- 'face 'python-django-face-django-version))
- (format "%s\t\t%s\n"
- (propertize
- "Project:"
- 'face 'python-django-face-title)
- (propertize
- python-django-project-root
- 'face 'python-django-face-project-root))
- (format "%s\t\t%s\n"
- (propertize
- "Settings:"
- 'face 'python-django-face-title)
- (propertize
- python-django-project-settings
- 'face 'python-django-face-settings-module))
- (format "%s\t\t%s"
- (propertize
- "Virtualenv:"
- 'face 'python-django-face-title)
- (propertize
- (or python-shell-virtualenv-path "None")
- 'face 'python-django-face-virtualenv-path))
- "\n\n\n"))
-
- (defun python-django-ui-build-section-alist ()
- "Create section Alist for current project."
- (list
- (cons
- "Apps" (mapcar
- (lambda (app)
- (cons app (python-django-info-get-app-path app)))
- (python-django-info-get-setting "INSTALLED_APPS")))
- (cons
- "Media"
- (list
- (cons "MEDIA_ROOT" (python-django-info-get-setting "MEDIA_ROOT"))
- (cons "STATIC_ROOT" (python-django-info-get-setting "STATIC_ROOT"))))
- (cons
- "Static Content" (mapcar
- (lambda (dir)
- ;; STATICFILES_DIRS elements can be either a
- ;; string or a size-two tuple with the first
- ;; element being the prefix and the latter
- ;; being the path: http://bit.ly/16Fw9xW
- (if (stringp dir)
- (cons dir dir)
- (cons (aref dir 0) (aref dir 1))))
- (python-django-info-get-setting "STATICFILES_DIRS")))
- (cons
- "Templates" (mapcar
- (lambda (dir)
- (cons dir dir))
- (python-django-info-get-setting "TEMPLATE_DIRS")))))
-
- ;; Many kudos to Ye Wenbin since dirtree.el was of great help when
- ;; looking for examples of `tree-widget':
- ;; https://github.com/zkim/emacs-dirtree/blob/master/
- (define-widget 'python-django-ui-tree-section-widget 'tree-widget
- "Tree widget for sections of Django Project buffer."
- :expander 'python-django-ui-tree-section-widget-expand
- :help-echo 'ignore
- :has-children t)
-
- (define-widget 'python-django-ui-tree-section-node-widget 'push-button
- "Widget for a nodes of `python-django-ui-tree-section-widget'."
- :format "%[%t%]\n"
- :button-face 'default
- :notify 'python-django-ui-tree-section-widget-expand)
-
- (define-widget 'python-django-ui-tree-dir-widget 'tree-widget
- "Tree widget for directories of Django Project."
- :expander 'python-django-ui-tree-dir-widget-expand
- :help-echo 'ignore
- :has-children t)
-
- (define-widget 'python-django-ui-tree-file-widget 'push-button
- "Widget for a files inside the `python-django-ui-tree-dir-widget'."
- :format "%[%t%]\n"
- :button-face 'default
- :notify 'python-django-ui-tree-file-widget-select)
-
- (defun python-django-ui-tree-section-widget-expand (tree &rest ignore)
- "Expand directory for given section TREE widget.
- Optional argument IGNORE is there for compatibility."
- (or (widget-get tree :args)
- (let ((section-alist (widget-get tree :section-alist)))
- (mapcar (lambda (section)
- (let ((name (car section))
- (dir (cdr section)))
- `(python-django-ui-tree-dir-widget
- :node (python-django-ui-tree-file-widget
- :tag ,name
- :file ,dir)
- :file ,dir
- :open nil
- :indent 0)))
- section-alist))))
-
- (defun python-django-ui-tree-dir-widget-expand (tree)
- "Expand directory for given TREE widget."
- (or (widget-get tree :args)
- (let* ((dir (widget-get tree :file))
- dir-list file-list)
- (when (and dir (file-exists-p dir))
- (dolist (file (directory-files dir t))
- (let ((basename (file-name-nondirectory file)))
- (if (file-directory-p file)
- (when (not (member basename python-django-ui-ignored-dirs))
- (setq dir-list (cons basename dir-list)))
- (when (member (python-django-util-file-name-extension file)
- python-django-ui-allowed-extensions)
- (setq file-list (cons basename file-list))))))
- (setq dir-list (sort dir-list 'string<))
- (setq file-list (sort file-list 'string<))
- (append
- (mapcar (lambda (file)
- `(python-django-ui-tree-dir-widget
- :file ,(expand-file-name file dir)
- :node (python-django-ui-tree-file-widget
- :tag ,file
- :file ,(expand-file-name file dir))))
- dir-list)
- (mapcar (lambda (file)
- `(python-django-ui-tree-file-widget
- :file ,(and file (not (string= file ""))
- (expand-file-name file dir))
- :tag ,file))
- file-list))))))
-
- (defun python-django-ui-tree-file-widget-select (node &rest ignore)
- "Open file in other window.
- Argument NODE and IGNORE are just for compatibility."
- (let ((file (widget-get node :file)))
- (and file (find-file-other-window file))))
-
- (defun python-django-ui-tree-section-insert (name section-alist)
- "Create tree widget for NAME and SECTION-ALIST."
- (apply 'widget-create
- `(python-django-ui-tree-section-widget
- :node (python-django-ui-tree-section-node-widget
- :tag ,name)
- :section-alist ,section-alist
- :open t)))
-
- (defun python-django-ui-widget-move (arg)
- "Move between widgets sensibly in the project buffer.
- Movement between widgets of the tree happen line by line, leaving
- point next to the closest icon available. With positive ARG move
- forward that many times, else backwards."
- (let* ((success-moves 0)
- (forward (> arg 0))
- (func (if forward
- 'widget-forward
- 'widget-backward))
- (abs-arg (abs arg)))
- (catch 'nowidget
- (while (> abs-arg success-moves)
- (if (memq (widget-type (widget-at (point)))
- '(tree-widget-close-icon
- tree-widget-empty-icon
- tree-widget-leaf-icon
- tree-widget-open-icon))
- (ignore-errors (funcall func 2))
- (ignore-errors (funcall func 1)))
- (when (not (widget-at (point)))
- (throw 'nowidget t))
- (setq success-moves (1+ success-moves))))
- (python-django-ui-move-to-closest-icon)
- (setq default-directory
- (or (python-django-ui-directory-at-point)
- (file-name-directory python-django-project-manage.py)))))
-
- (defun python-django-ui-widget-forward (arg)
- "Move point to the next line's main widget.
- With optional ARG, move across that many fields."
- (interactive "p")
- (python-django-ui-widget-move arg))
-
- (defun python-django-ui-widget-backward (arg)
- "Move point to the previous line's main widget.
- With optional ARG, move across that many fields."
- (interactive "p")
- (python-django-ui-widget-move (- arg)))
-
- (defun python-django-ui-move-up-tree (arg)
- "Move point to the parent widget of the tree.
- With optional ARG, move across that many fields."
- (interactive "p")
- (and (< arg 0) (setq arg (- arg)))
- (python-django-ui-move-to-closest-icon)
- (let ((start-depth (- (point) (line-beginning-position))))
- (when (not (= 0 start-depth))
- (while (<= start-depth (- (point) (line-beginning-position)))
- (python-django-ui-widget-backward 1)))))
-
- (defun python-django-ui-beginning-of-widgets ()
- "Move to the first widget.
- With optional ARG, move across that many fields."
- (interactive)
- (goto-char (point-min))
- (python-django-ui-widget-forward 1))
-
- (defun python-django-ui-end-of-widgets ()
- "Move point to last widget.
- With optional ARG, move across that many fields."
- (interactive)
- (goto-char (point-max))
- (python-django-ui-widget-backward 1))
-
- (defun python-django-ui-move-to-closest-icon ()
- "Move to closest button from point."
- (interactive)
- (if (and
- (not (widget-at (point)))
- (not (widget-at (1- (point)))))
- (progn
- (widget-backward 1)
- (beginning-of-line 1)
- (widget-forward 1))
- (beginning-of-line 1)
- (and (not (widget-at (point)))
- (widget-forward 1))))
-
- (defun python-django-ui-safe-button-press ()
- "Move to closest button from point and press it."
- (interactive)
- (and (not (widget-at (point)))
- (python-django-ui-move-to-closest-icon))
- (widget-button-press (point)))
-
- (defun python-django-ui-widget-type-at-point ()
- "Return the node type for current position."
- (let* ((widget (widget-at (point)))
- (file-p (widget-get
- (tree-widget-node widget)
- :tree-widget--guide-flags)))
- (and widget (if file-p 'file 'dir))))
-
- (defun python-django-ui-directory-at-point ()
- "Return the node type for current position."
- (widget-get
- (widget-get (tree-widget-node (widget-at (point))) :parent) :file))
-
-
- ;;;Main functions
-
- (defcustom python-django-known-projects nil
- "Alist of known projects."
- :group 'python-django
- :type '(repeat (list string string string))
- :safe (lambda (val)
- (and
- (stringp (car val))
- (stringp (nth 1 val))
- (stringp (nth 2 val)))))
-
- (defun python-django-mode-find-next-buffer ()
- "Find the next Django project buffer available."
- (let ((current-buffer (current-buffer)))
- (catch 'buffer
- (dolist (buf (buffer-list))
- (and (with-current-buffer buf
- (and (eq major-mode 'python-django-mode)
- (not (equal buf current-buffer))))
- (throw 'buffer buf))))))
-
- (defun python-django-mode-on-kill-buffer ()
- "Hook run on `buffer-kill-hook'."
- (and (python-django-mgmt-buffer-list (current-buffer))
- (call-interactively 'python-django-mgmt-kill-all)))
-
- (define-derived-mode python-django-mode special-mode "Django"
- "Major mode to manage Django projects.
-
- \\{python-django-mode-map}")
-
- ;;;###autoload
- (defun python-django-open-project (directory settings &optional existing)
- "Open a Django project at given DIRECTORY using SETTINGS.
- Optional argument EXISTING is internal and should not be used.
-
- The recommended way to chose your project root, is to use the
- directory containing your settings module; for instance if your
- settings module is in /path/django/settings.py, use /path/django/
- as your project path and django.settings as your settings module.
-
- When called with no `prefix-arg', this function will try to find
- an opened project-buffer, if current buffer is already a project
- buffer it will cycle to next opened project. If no project
- buffers are found, then the user prompted for the project path
- and settings module unless `python-django-project-root' and
- `python-django-project-settings' are somehow set, normally via
- directory local variables. If none of the above matched or the
- function is called with one `prefix-arg' and there are projects
- defined in the `python-django-known-projects' variable the user
- is prompted for any of those known projects, if the variable
- turns to be nil the user will be prompted for project-path and
- settings module (the same happens when called with two or more
- `prefix-arg')."
- (interactive
- (let ((buf
- ;; Get an existing project buffer that's not the current.
- (python-django-mode-find-next-buffer)))
- (cond
- ((and (not current-prefix-arg)
- (not buf)
- python-django-project-root
- python-django-project-settings)
- ;; There's no existing buffer but project variables are
- ;; set, so use them to open the project.
- (list python-django-project-root
- python-django-project-settings
- ;; if the user happens to be in the project buffer
- ;; itself, do nothing.
- (and (eq major-mode 'python-django-mode)
- (current-buffer))))
- ((and (not current-prefix-arg) buf)
- ;; there's an existing buffer move/cycle to it.
- (with-current-buffer buf
- (list
- python-django-project-root
- python-django-project-settings
- buf)))
- ((or (and python-django-known-projects
- (<= (prefix-numeric-value current-prefix-arg) 4)))
- ;; When there are known projects and called at most with one
- ;; prefix arg try opening a known project.
- (cdr
- (assoc
- (python-django-minibuffer-read-from-list
- "Project: " python-django-known-projects)
- python-django-known-projects)))
- (t
- (let ((root))
- ;; When called with two or more prefix arguments or all
- ;; input methods failed.
- (list
- (setq root (read-directory-name
- "Project Root: " python-django-project-root nil t))
- (read-string
- "Settings module: "
- (or python-django-project-settings
- (format "%s.settings"
- (python-django-info-directory-basename root))))))))))
- (if (not existing)
- (let* ((project-name (python-django-info-directory-basename directory))
- (buffer-name
- (format "*Django: %s (%s)*"
- project-name
- (python-django-util-shorten-settings settings)))
- (success t))
- (with-current-buffer (get-buffer-create buffer-name)
- (let ((inhibit-read-only t))
- (python-django-mode)
- (python-django-ui-clean)
- (set (make-local-variable
- 'python-django-info--get-setting-cache) nil)
- (set (make-local-variable
- 'python-django-info--get-version-cache) nil)
- (set (make-local-variable
- 'python-django-info--get-app-paths-cache) nil)
- (set (make-local-variable
- 'python-django-project-root) directory)
- (set (make-local-variable
- 'python-django-project-settings) settings)
- (set (make-local-variable
- 'python-django-project-name) project-name)
- (set (make-local-variable
- 'python-django-project-manage.py)
- (python-django-info-find-manage.py directory))
- (set (make-local-variable 'default-directory)
- (file-name-directory
- python-django-project-manage.py))
- (python-django-util-clone-local-variables)
- (set (make-local-variable 'tree-widget-image-enable)
- python-django-ui-image-enable)
- (tree-widget-set-theme python-django-ui-theme)
- (condition-case err
- (progn
- (python-django-ui-insert-header)
- (mapc (lambda (section)
- (python-django-ui-tree-section-insert
- (car section) (cdr section))
- (insert "\n"))
- (python-django-ui-build-section-alist)))
- (user-error
- (setq success nil)
- (insert (error-message-string err))
- (goto-char (point-min)))))
- (when success
- (add-hook 'kill-buffer-hook
- #'python-django-mode-on-kill-buffer nil t)
- (python-django-ui-beginning-of-widgets))
- (python-django-ui-show-buffer (current-buffer))))
- (python-django-ui-show-buffer existing)))
-
- ;; Stolen from magit.
- (defun python-django-close-project (&optional kill-buffer)
- "Bury the buffer and delete its window.
- With a prefix argument, KILL-BUFFER instead."
- (interactive "P")
- (quit-window kill-buffer (selected-window)))
-
- (defun python-django-refresh-project ()
- "Refresh Django project."
- (interactive)
- (python-django-open-project
- python-django-project-root
- python-django-project-settings))
-
- (provide 'python-django)
-
- ;; Local Variables:
- ;; coding: utf-8
- ;; indent-tabs-mode: nil
- ;; End:
-
- ;;; python-django.el ends here
|