;;; django-manage.el --- Django minor mode for commanding manage.py ;; Copyright (C) 2015 Daniel Gopar ;; Author: Daniel Gopar ;; Package-Requires: ((hydra "0.13.2")) ;; Package-Version: 20160818.1912 ;; Version: 0.1 ;; Keywords: languages ;; This file is NOT part of GNU Emacs. ;; GNU Emacs 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. ;; GNU Emacs 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 GNU Emacs. If not, see . ;;; Commentary: ;; ;; Simple package to be to be able to control `manage.py', the standard ;; file that every Django project comes with. You are able to call any ;; command with `django-manage-command' plus it comes with code ;; completion so third party plugins will also be completed. ;;; Code: (condition-case nil (require 'python) (error (require 'python-mode))) (require 'hydra) (defcustom django-manage-shell-preference 'pyshell "What shell to use." :type 'symbol :options '(eshell term pyshell) :group 'shell) (defcustom django-manage-server-ipaddr "127.0.0.1" "What address Django will use when running the dev server." :type 'string :group 'server) (defcustom django-manage-server-port "8000" "What port Django will use when running the dev server." :type 'string :group 'server) (defcustom django-manage-prompt-for-command nil "When non-nil will ask for confirmation on command. Will affect every function other than `django-manage-command'." :type 'boolean :group 'django-manage) (defcustom django-manage-root "" "The directory where 'manage.py' lives." :type 'string :group 'django-manage) (make-local-variable 'django-manage-root) (defun django-manage-root () "Return the root directory of Django project." ;; Check if projectile is in use, and if it is. Return root directory (if (not (string= django-manage-root "")) django-manage-root (if (fboundp 'projectile-project-root) (projectile-project-root) ;; Try looking for the directory holding 'manage.py' (locate-dominating-file default-directory "manage.py")))) (defun django-manage-python-command () "Return Python version to use with args." (if (boundp 'python-shell-interpreter) (concat python-shell-interpreter " " python-shell-interpreter-args) ;; For old python.el (mapconcat 'identity (cons python-python-command python-python-command-args) " "))) (defun django-manage-get-commands () "Return list of django commands." (let ((help-output (shell-command-to-string (concat python-shell-interpreter " " (shell-quote-argument (django-manage-root)) "manage.py -h")))) (setq dj-commands-str (with-temp-buffer (progn (insert help-output) (beginning-of-buffer) (delete-region (point) (search-forward "Available subcommands:" nil nil nil)) ;; cleanup [auth] and stuff (beginning-of-buffer) (save-excursion (replace-regexp "\\[.*\\]" "")) (buffer-string)))) ;; get a list of commands from the output of manage.py -h ;; What would be the pattern to optimize this ? (setq dj-commands-str (s-split "\n" dj-commands-str)) (setq dj-commands-str (-remove (lambda (x) (string= x "")) dj-commands-str)) (setq dj-commands-str (mapcar (lambda (x) (s-trim x)) dj-commands-str)) (sort dj-commands-str 'string-lessp))) (defun django-manage-command (command &optional no-prompt) "Allow to run any `manage.py' command. Argument COMMAND command for django to run. Optional Argument NO-PROMPT if non-nil will *not* ask if you wish to pass extra arguments." ;; nil nil: enable user to exit with any command. Still, he can not edit a completed choice. (interactive (list (completing-read "Command: " (django-manage-get-commands) nil nil))) (if (not no-prompt) ;; Now ask to edit the command. How to do the two actions at once ? (setq command (read-shell-command "Run command like this: " command))) (compile (concat (django-manage-python-command) " " (shell-quote-argument (django-manage-root)) "manage.py " command))) (defun django-manage-makemigrations (&optional app-name) "Run \"makemigrations app-name\", will prompt for \"app-name\". You can leave blank to simply run \"makemigrations\". To choose arguments call `django-manage-command'. Optional argument APP-NAME name of django app create migrations." (interactive "sName: ") (django-manage-command (concat "makemigrations " app-name) (not django-manage-prompt-for-command))) (defun django-manage-flush () "Run \"flush --noinput\". To choose arguments call `django-manage-command'." (interactive) (django-manage-command "flush --noinput" (not django-manage-prompt-for-command))) (defun django-manage-runserver () "Start the development server. To change what address and port to use, customize `django-manage-server-ipaddr' and `django-manage-server-port' If you want to pass arguments, then call `django-manage-command'" (interactive) (let ((parent-dir (file-name-base (substring (django-manage-root) 0 -1)))) (compile (concat (django-manage-python-command) " " (shell-quote-argument (django-manage-root)) "manage.py runserver " django-manage-server-ipaddr ":" django-manage-server-port)) (with-current-buffer "*compilation*" (rename-buffer (format "*runserver[%s]*" parent-dir))))) (defun django-manage-migrate () "Run \"migrate\". To choose arguments call `django-manage-command'." (interactive) (django-manage-command "migrate" (not django-manage-prompt-for-command))) (defun django-manage-assets-rebuild () "Run \"assets rebuild\". To choose arguments call `django-manage-command'." (interactive) (django-manage-command "assets rebuild" (not django-manage-prompt-for-command))) (defun django-manage-startapp (name) "Run \"startapp name\". Will prompt for name of app. To choose arguments call `django-manage-command'. Argument NAME name of app to create." (interactive "sName:") (django-manage-command (concat "startapp " name) (not django-manage-prompt-for-command))) (defun django-manage-makemessages () "Run \"makemessages --all --symlinks\". To pass arguments call `django-manage-command'." (interactive) (django-manage-command "makemessages --all --symlinks" (not django-manage-prompt-for-command))) (defun django-manage-compilemessages () "Run \"compilemessages\". To pass arguments call `django-manage-command'." (interactive) (django-manage-command "compilemessages" (not django-manage-prompt-for-command))) (defun django-manage-test (name) "Run \"test name\". Will prompt for Django app name to test. To pass arguments call `django-manage-command'. Argument NAME name of django app to test." (interactive "sTest app:") (django-manage-command (concat "test " name) (not django-manage-prompt-for-command))) (defun django-manage--prep-shell (pref-shell) "Prepare the shell with users preference. Argument PREF-SHELL users shell of choice" ;; If a preexisting shell buffer exists return that one. If not create it (let* ((parent-dir (file-name-base (substring (django-manage-root) 0 -1))) (default-directory (django-manage-root)) (buffer-shell-name (format (if (string= pref-shell "shell") "*Django Shell[%s]*" "*Django DBshell[%s]*") parent-dir))) ;; If it exists return it (if (get-buffer buffer-shell-name) (switch-to-buffer buffer-shell-name) ;; Shell didn't exist, so let's create it (if (eq 'term django-manage-shell-preference) (term (concat (django-manage-python-command) " " (shell-quote-argument (django-manage-root)) "manage.py " pref-shell))) (if (eq 'eshell django-manage-shell-preference) (progn (unless (get-buffer eshell-buffer-name) (eshell)) (insert (concat (django-manage-python-command) " " (shell-quote-argument (django-manage-root)) "manage.py " pref-shell)) (eshell-send-input))) (if (eq 'pyshell django-manage-shell-preference) (let ((setup-code "os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"%s.settings\")") (cmd ";from django.core.management import execute_from_command_line") (exe (if (string= pref-shell "shell") ";import django;django.setup()" (format ";execute_from_command_line(['manage.py', '%s'])" pref-shell)))) (run-python (python-shell-parse-command)) (python-shell-send-string (concat (format setup-code parent-dir) cmd exe)) (switch-to-buffer (python-shell-get-buffer)))) (rename-buffer buffer-shell-name)))) (defun django-manage-shell () "Start Python shell with Django already configured." (interactive) (django-manage--prep-shell "shell")) (defun django-manage-dbshell () "Start Database shell." (interactive) (django-manage--prep-shell "dbshell")) (defun django-manage-insert-transpy (from to &optional buffer) "Wraps highlighted region in _(...) for i18n. Argument FROM start point TO wrap. Optional argument BUFFER end point to wrap." ;; From http://garage.pimentech.net/libcommonDjango_django_emacs/ ;; Modified a little (interactive "*r") (save-excursion (save-restriction (narrow-to-region from to) (goto-char from) (iso-iso2sgml from to) (insert "_(") (goto-char (point-max)) (insert ")") (point-max)))) (defhydra django-manage-hydra (:color blue :hint nil) " Manage.py -------------------------------------------------- _mm_: Enter manage.py commnand _r_: runserver _f_: Flush _t_: Run rest _ma_: Makemigrations _sa_: Start new app _i_: Insert transpy _mg_: Migrate _ss_: Run shell _a_: Rebuild Assets _me_: Make messages _sd_: Run DB Shell _c_: Compile messages _q_: Cancel " ("mm" django-manage-command) ("ma" django-manage-makemigrations) ("mg" django-manage-migrate) ("me" django-manage-makemessages) ("r" django-manage-runserver "Start server") ("sa" django-manage-startapp) ("ss" django-manage-shell) ("sd" django-manage-dbshell) ("f" django-manage-flush) ("a" django-manage-assets-rebuild) ("c" django-manage-compilemessages) ("t" django-manage-test) ("i" django-manage-insert-transpy) ("q" nil "cancel")) (defvar django-manage-map (let ((map (make-keymap))) (define-key map (kbd "C-c C-x") 'django-manage-hydra/body) map)) (defun django-manage-setup () "Determine whether to start minor mode or not." (when (and (stringp buffer-file-name) (locate-dominating-file default-directory "manage.py")) (django-manage))) ;;;###autoload (define-minor-mode django-manage "Minor mode for handling Django's manage.py" :lighter " Manage" :keymap django-manage-map) (easy-menu-define django-manage-menu django-manage-map "Django menu" '("Django" ["Start an app" django-manage-startapp t] ["Run tests" django-manage-test t] ["Make migrations" django-manage-makemigrations t] ["Flush database" django-manage-flush t] ["Runserver" django-manage-runserver t] ["Run database migrations" django-manage-migrate t] ["Rebuild assets" django-manage-assets-rebuild t] ["Make translations" django-manage-makemessages t] ["Compile translations" django-manage-compilemessages t] ["Open Python shell" django-manage-shell t] ["Open database shell" django-manage-dbshell t] ["Run other command" django-manage-command t] "-" ["Insert translation mark" django-manage-insert-transpy t])) (easy-menu-add django-manage-menu django-manage-map) (provide 'django-manage) ;;; django-manage.el ends here