Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2559 lines
99 KiB

  1. ;;; python-django.el --- A Jazzy package for managing Django projects
  2. ;; Copyright (C) 2011 Free Software Foundation, Inc.
  3. ;; Author: Fabián E. Gallina <fabian@anue.biz>
  4. ;; URL: https://github.com/fgallina/python-django.el
  5. ;; Package-Version: 20150822.404
  6. ;; Package-Commit: fc54ad74f0309670359b939f64d0f1fff68aeac4
  7. ;; Version: 0.1
  8. ;; Maintainer: FSF
  9. ;; Created: Jul 2011
  10. ;; Keywords: languages
  11. ;; This file is NOT part of GNU Emacs.
  12. ;; python-django.el is free software: you can redistribute it and/or
  13. ;; modify it under the terms of the GNU General Public License as
  14. ;; published by the Free Software Foundation, either version 3 of the
  15. ;; License, or (at your option) any later version.
  16. ;; python-django.el is distributed in the hope that it will be useful,
  17. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  19. ;; General Public License for more details.
  20. ;; You should have received a copy of the GNU General Public License
  21. ;; along with python-django.el. If not, see
  22. ;; <http://www.gnu.org/licenses/>.
  23. ;;; Commentary:
  24. ;; Django project management package with the goodies you would expect
  25. ;; and then some. The project buffer workings is pretty much inspired
  26. ;; by the good ol' `magit-status' buffer.
  27. ;; This package relies heavily in fgallina's `python.el' available in
  28. ;; stock Emacs>=24.3 (or https://github.com/fgallina/python.el).
  29. ;; Implements File navigation (per app, STATIC_ROOT, MEDIA_ROOT and
  30. ;; TEMPLATE_DIRS), Etag building, Grep in project, Quick jump (to
  31. ;; settings, project root, virtualenv and docs), Management commands
  32. ;; and Quick management commands.
  33. ;; File navigation: After opening a project, a directory tree for each
  34. ;; installed app, the STATIC_ROOT, the MEDIA_ROOT and each
  35. ;; TEMPLATE_DIRS is created. Several commands are provided to work
  36. ;; with the current directory at point.
  37. ;; Etags building: Provides a simple wrapper to create etags for
  38. ;; current opened project.
  39. ;; Grep in project: Provides a simple way to grep relevant project
  40. ;; directories using `rgrep'. You can override the use of `rgrep' by
  41. ;; tweaking the `python-django-cmd-grep-function'.
  42. ;; Quick jump: fast key bindings to jump to the settings module, the
  43. ;; project root, the current virtualenv and Django official web docs
  44. ;; are provided.
  45. ;; Management commands: You can run any management command from the
  46. ;; project buffer via `python-django-mgmt-run-command' or via the
  47. ;; quick management commands accesible from the Django menu.
  48. ;; Completion is provided for all arguments and you can cycle through
  49. ;; opened management command process buffers very easily. Another
  50. ;; cool feature is that comint processes are spiced up with special
  51. ;; processing, for instance if are using runserver and get a
  52. ;; breakpoint via pdb or ipdb the pdb-tracking provided by
  53. ;; `python-mode' will trigger or if you enter dbshell the proper
  54. ;; `sql-mode' will be used.
  55. ;; Quick management commands: This mode provides quick management
  56. ;; commands (management commands with sane defaults, smart prompt
  57. ;; completion and process extra processing) defined to work with the
  58. ;; most used Django built-in management commands like syncdb, shell,
  59. ;; runserver, test; several good ones from `django-extensions' like
  60. ;; shell_plus, clean_pyc; and `south' ones like convert_to_south,
  61. ;; migrate, schemamigration. You can define new quick commands via
  62. ;; the `python-django-qmgmt-define' and define ways to handle when
  63. ;; it's finished by defining a callback function.
  64. ;;; Usage:
  65. ;; The main entry point is the `python-django-open-project'
  66. ;; interactive function, see its documentation for more info on its
  67. ;; behavior. Mainly this function requires two things, a project path
  68. ;; and a settings module. How you chose them really depends on your
  69. ;; project's directory layout. The recommended way to chose your
  70. ;; project root, is to use the directory containing your settings
  71. ;; module; for instance if your settings module is in
  72. ;; /path/django/settings.py, use /path/django/ as your project path
  73. ;; and django.settings as your settings module. Remember to always
  74. ;; set `python-shell-interpreter' to either python or python2 and
  75. ;; never use iPython directly as Django enables it automatically when
  76. ;; the shell is started.
  77. ;;; Installation:
  78. ;; Add this to your .emacs:
  79. ;; (add-to-list 'load-path "/folder/containing/file")
  80. ;; (require 'python-django)
  81. ;;; Code:
  82. (require 'hippie-exp)
  83. (require 'json)
  84. (require 'python)
  85. (require 'sql)
  86. (require 'tree-widget)
  87. (require 'wid-edit)
  88. (require 'widget)
  89. ;; Avoid compiler warnings
  90. (defvar view-return-to-alist)
  91. (defgroup python-django nil
  92. "Python Django project goodies."
  93. :group 'convenience
  94. :version "24.2")
  95. ;;; keymaps
  96. (defvar python-django-mode-map
  97. (let ((map (make-keymap)))
  98. (suppress-keymap map t)
  99. (define-key map [remap next-line] 'python-django-ui-widget-forward)
  100. (define-key map [remap previous-line] 'python-django-ui-widget-backward)
  101. (define-key map [remap forward-char] 'widget-forward)
  102. (define-key map [remap backward-char] 'widget-backward)
  103. (define-key map [remap beginning-of-buffer]
  104. 'python-django-ui-beginning-of-widgets)
  105. (define-key map [remap newline] 'python-django-ui-safe-button-press)
  106. (define-key map (kbd "^") 'python-django-ui-move-up-tree)
  107. (define-key map (kbd "p") 'python-django-ui-widget-backward)
  108. (define-key map (kbd "n") 'python-django-ui-widget-forward)
  109. (define-key map (kbd "b") 'widget-backward)
  110. (define-key map (kbd "f") 'widget-forward)
  111. (define-key map (kbd "d") 'python-django-cmd-dired-at-point)
  112. (define-key map (kbd "w") 'python-django-cmd-directory-at-point)
  113. (define-key map (kbd "ja") 'python-django-cmd-jump-to-app)
  114. (define-key map (kbd "jm") 'python-django-cmd-jump-to-media)
  115. (define-key map (kbd "jt") 'python-django-cmd-jump-to-template-dir)
  116. (define-key map (kbd "vs") 'python-django-cmd-visit-settings)
  117. (define-key map (kbd "vr") 'python-django-cmd-visit-project-root)
  118. (define-key map (kbd "vv") 'python-django-cmd-visit-virtualenv)
  119. (define-key map (kbd "t") 'python-django-cmd-build-etags)
  120. (define-key map (kbd "s") 'python-django-cmd-grep)
  121. (define-key map (kbd "o") 'python-django-cmd-open-docs)
  122. (define-key map (kbd "h") 'python-django-help)
  123. (define-key map (kbd "m") 'python-django-mgmt-run-command)
  124. (define-key map (kbd "g") 'python-django-refresh-project)
  125. (define-key map (kbd "q") 'python-django-close-project)
  126. (define-key map (kbd "k") 'python-django-mgmt-kill)
  127. (define-key map (kbd "K") 'python-django-mgmt-kill-all)
  128. (define-key map (kbd "u") 'universal-argument)
  129. (define-key map (kbd "$") 'python-django-mgmt-cycle-buffers-forward)
  130. (define-key map (kbd "#") 'python-django-mgmt-cycle-buffers-backward)
  131. (easy-menu-define python-django-menu map "Python Django Mode menu"
  132. `("Django"
  133. :help "Django project tools"
  134. ["Run management command"
  135. python-django-mgmt-run-command
  136. :help "Run management command in current project"]
  137. ["Kill all running commands"
  138. python-django-mgmt-kill-all
  139. :help "Kill all running commands for current project"]
  140. ["Get command help" python-django-help
  141. :help "Get help for any project's management commands"]
  142. ["Cycle to next running management command"
  143. python-django-mgmt-cycle-buffers-forward
  144. :help "Cycle to next running management command"]
  145. ["Cycle to previous running management command"
  146. python-django-mgmt-cycle-buffers-backward
  147. :help "Cycle to previous running management command"]
  148. "--"
  149. ;; Reserved for quick management commands
  150. "---"
  151. ["Browse Django documentation"
  152. python-django-cmd-open-docs
  153. :help "Open a Browser with Django's documentation"]
  154. ["Build Tags"
  155. python-django-cmd-build-etags
  156. :help "Build TAGS file for python source in project"]
  157. ["Dired at point"
  158. python-django-cmd-dired-at-point
  159. :help "Open dired at current tree node"]
  160. ["Grep in project directories"
  161. python-django-cmd-grep
  162. :help "Grep in project directories"]
  163. ["Refresh project"
  164. python-django-refresh-project
  165. :help "Refresh project"]
  166. "--"
  167. ["Visit settings file"
  168. python-django-cmd-visit-settings
  169. :help "Visit settings file"]
  170. ["Visit virtualenv directory"
  171. python-django-cmd-visit-virtualenv
  172. :help "Visit virtualenv directory"]
  173. ["Visit project root directory"
  174. python-django-cmd-visit-project-root
  175. :help "Visit project root directory"]
  176. "--"
  177. ["Jump to app's directory"
  178. python-django-cmd-jump-to-app
  179. :help "Jump to app's directory"]
  180. ["Jump to a media directory"
  181. python-django-cmd-jump-to-media
  182. :help "Jump to a media directory"]
  183. ["Jump to a template directory"
  184. python-django-cmd-jump-to-template-dir
  185. :help "Jump to a template directory"]))
  186. map)
  187. "Keymap for `python-django-mode'.")
  188. ;;; Main vars
  189. (defvar python-django-project-root nil
  190. "Django project root directory.")
  191. (defvar python-django-project-manage.py nil
  192. "Django project manage.py path.")
  193. (defvar python-django-project-settings nil
  194. "Django project settings module.")
  195. (defvar python-django-project-name nil
  196. "Django project name.")
  197. (define-obsolete-variable-alias
  198. 'python-django-settings-module
  199. 'python-django-project-settings
  200. "24.2")
  201. (define-obsolete-variable-alias
  202. 'python-django-info-project-name
  203. 'python-django-project-name
  204. "24.2")
  205. ;;; Faces
  206. (defgroup python-django-faces nil
  207. "Customize the appearance of Django buffers."
  208. :prefix "python-django-"
  209. :group 'faces
  210. :group 'python-django)
  211. (defface python-django-face-header
  212. '((t :inherit font-lock-function-name-face))
  213. "Face for generic header lines.
  214. Many Django faces inherit from this one by default."
  215. :group 'python-django-faces)
  216. (defface python-django-face-path
  217. '((t :inherit font-lock-type-face))
  218. "Face for paths."
  219. :group 'python-django-faces)
  220. (defface python-django-face-title
  221. '((t :inherit font-lock-keyword-face))
  222. "Face for titles."
  223. :group 'python-django-faces)
  224. (defface python-django-face-django-version
  225. '((t :inherit python-django-face-header))
  226. "Face for project's Django version."
  227. :group 'python-django-faces)
  228. (defface python-django-face-project-root
  229. '((t :inherit python-django-face-path))
  230. "Face for project path."
  231. :group 'python-django-faces)
  232. (defface python-django-face-settings-module
  233. '((t :inherit python-django-face-header))
  234. "Face for project settings module."
  235. :group 'python-django-faces)
  236. (defface python-django-face-virtualenv-path
  237. '((t :inherit python-django-face-header))
  238. "Face for project settings module."
  239. :group 'python-django-faces)
  240. ;;; Dev tools
  241. (font-lock-add-keywords
  242. 'emacs-lisp-mode
  243. `(("(\\(python-django-qmgmt-define\\)\\>[ \t]\\([^ \t]+\\)"
  244. (1 'font-lock-keyword-face)
  245. (2 'font-lock-function-name-face))))
  246. ;;; Error logging
  247. (defvar python-django-error-log-formatter
  248. #'python-django-error-default-formatter)
  249. (defun python-django-error-default-formatter (error-string)
  250. "Formats ERROR-STRING to be placed in the error log."
  251. (format
  252. (concat
  253. "An error occurred retrieving project information.\n"
  254. "Check your project settings and try again:\n\n"
  255. "Current values:\n"
  256. " + python-django-project-root: %s\n"
  257. " + python-django-project-settings: %s\n"
  258. " + python-shell-interpreter: %s\n"
  259. " - found in %s\n\n"
  260. "Details: \n\n%s\n")
  261. python-django-project-root
  262. python-django-project-settings
  263. python-shell-interpreter
  264. (let* ((process-environment
  265. (python-django-info-calculate-process-environment))
  266. (exec-path (python-shell-calculate-exec-path)))
  267. (executable-find python-shell-interpreter))
  268. error-string))
  269. (defun python-django-error-log (error-string)
  270. "Log ERROR-STRING by calling `user-error'."
  271. (user-error "%s" (funcall python-django-error-log-formatter error-string)))
  272. ;;; Utility functions
  273. (defun python-django-util-clone-local-variables ()
  274. "Clone local variables from manage.py file.
  275. This function is intended to be used so the project buffer gets
  276. the same variables of python files."
  277. (let* ((file-name
  278. (expand-file-name
  279. python-django-project-manage.py))
  280. (manage.py-exists (get-file-buffer file-name))
  281. (flymake-start-syntax-check-on-find-file nil)
  282. (manage.py-buffer
  283. (or manage.py-exists
  284. (prog1
  285. (find-file-noselect file-name t)
  286. (message nil)))))
  287. ;; TODO: Add a predicate parameter to
  288. ;; `python-util-clone-local-variables' itself to handle vars not
  289. ;; intended to be changed by the variable cloning and replace the
  290. ;; following code with that.
  291. (mapc
  292. (lambda (pair)
  293. (and (symbolp (car pair))
  294. (string-match "^python-" (symbol-name (car pair)))
  295. (not (memq (car pair)
  296. '(python-django-project-root
  297. python-django-project-settings
  298. python-django-project-name
  299. python-django-project-manage.py)))
  300. (set (make-local-variable (car pair))
  301. (cdr pair))))
  302. (buffer-local-variables manage.py-buffer))
  303. (when (not manage.py-exists)
  304. (kill-buffer manage.py-buffer))))
  305. (defmacro python-django-util-alist-add (key value alist)
  306. "Update for KEY the VALUE in ALIST."
  307. `(let* ((k (if (bufferp ,key)
  308. (buffer-name ,key)
  309. ,key))
  310. (v (if (bufferp ,value)
  311. (buffer-name ,value)
  312. ,value))
  313. (elt (assoc k ,alist)))
  314. (if (not elt)
  315. (setq ,alist (cons (list k v) ,alist))
  316. (and (not (member v (cdr elt)))
  317. (setf (cdr elt)
  318. (cons v (cdr elt)))))))
  319. (defmacro python-django-util-alist-del (key value alist)
  320. "Remove for KEY the VALUE in ALIST."
  321. `(let* ((k (if (bufferp ,key)
  322. (buffer-name ,key)
  323. ,key))
  324. (v (if (bufferp ,value)
  325. (buffer-name ,value)
  326. ,value))
  327. (elt (assoc k ,alist)))
  328. (and elt (setf (cdr elt) (remove v (cdr elt))))))
  329. (defmacro python-django-util-alist-del-key (key alist)
  330. "Empty KEY in ALIST."
  331. `(let* ((k (if (bufferp ,key)
  332. (buffer-name ,key)
  333. ,key))
  334. (elt (assoc k ,alist)))
  335. (and elt (setf (cdr elt) nil))))
  336. (defun python-django-util-alist-get (key alist)
  337. "Get values for KEY in ALIST."
  338. (and (bufferp key) (setq key (buffer-name key)))
  339. (cdr (assoc key alist)))
  340. ;; Based on `file-name-extension'
  341. (defun python-django-util-file-name-extension (filename)
  342. "Return FILENAME's final \"extension\" sans dot."
  343. (save-match-data
  344. (let ((file (file-name-nondirectory filename)))
  345. (if (and (string-match "\\.[^.]*\\'" file)
  346. (not (eq 0 (match-beginning 0))))
  347. (substring file (+ (match-beginning 0) 1))))))
  348. (defun python-django-util-shell-command-to-string (command)
  349. "Execute shell COMMAND and return its output as a string.
  350. Returns a cons cell where the car is the exit status and the cdr
  351. is the captured output."
  352. (with-temp-buffer
  353. (cons
  354. (apply 'call-process shell-file-name
  355. nil t nil (list shell-command-switch command))
  356. (buffer-string))))
  357. (defun python-django-util-shell-command-or-error (command)
  358. "Execute shell COMMAND and return its output as a string.
  359. If the exit status is an error `python-django-error-log' is used
  360. to display command output."
  361. (let* ((result (python-django-util-shell-command-to-string command))
  362. (status (car result))
  363. (output (cdr result)))
  364. (if (zerop status)
  365. output
  366. (python-django-error-log
  367. (concat "Error executing: " command "\n\n" output)))))
  368. (defun python-django-util-shorten-settings (&optional settings)
  369. "Return a shorter SETTINGS module string.
  370. Optional Argument SETTINGS defaults to the value of
  371. `python-django-project-settings'."
  372. (or settings (setq settings python-django-project-settings))
  373. (let ((beg (string-match "settings\\." settings)))
  374. (if beg
  375. (substring
  376. settings
  377. (+ beg (length (match-string-no-properties 0 settings))))
  378. settings)))
  379. ;;; Help
  380. (defun python-django--help-get (&optional command)
  381. "Get help for COMMAND."
  382. (let* ((process-environment
  383. (python-django-info-calculate-process-environment))
  384. (exec-path (python-shell-calculate-exec-path)))
  385. (python-django-util-shell-command-or-error
  386. ;; "--help" is better than "help" because it won't make the command end
  387. ;; with failure on Django<1.4.
  388. (format "%s %s %s--help"
  389. (executable-find python-shell-interpreter)
  390. python-django-project-manage.py
  391. (if command (format "%s " command) "")))))
  392. (defun python-django-help (&optional command show-help)
  393. "Get help for given COMMAND.
  394. Optional argument SHOW-HELP when non-nil causes the help buffer to pop."
  395. (interactive
  396. (list
  397. (python-django-minibuffer-read-command)))
  398. (if (or show-help (called-interactively-p 'interactive))
  399. (with-help-window (help-buffer)
  400. (princ (python-django--help-get command)))
  401. (python-django--help-get command)))
  402. (defun python-django-help-close ()
  403. "Close help window if visible."
  404. (let ((win (get-buffer-window (help-buffer))))
  405. (and win
  406. (delete-window win))))
  407. ;;; Project info
  408. (defun python-django-info-calculate-process-environment ()
  409. "Calculate process environment given current Django project."
  410. (let* ((process-environment (python-shell-calculate-process-environment))
  411. (pythonpath (getenv "PYTHONPATH"))
  412. (project-pythonpath
  413. (mapconcat
  414. 'identity
  415. (list (expand-file-name python-django-project-root)
  416. (expand-file-name "../" python-django-project-root))
  417. path-separator)))
  418. (setenv "PYTHONPATH" (if (not pythonpath)
  419. project-pythonpath
  420. (format "%s%s%s"
  421. pythonpath
  422. path-separator
  423. project-pythonpath)))
  424. (setenv "DJANGO_SETTINGS_MODULE"
  425. python-django-project-settings)
  426. process-environment))
  427. (defun python-django-info-find-manage.py (&optional dir)
  428. "Find manage.py script starting from DIR."
  429. (let ((dir (expand-file-name (or dir default-directory))))
  430. (if (not (directory-files dir nil "^manage\\.py$"))
  431. (and
  432. ;; Check dir is not directory root.
  433. (not (string-equal "/" dir))
  434. (not
  435. (and (memq system-type '(windows-nt ms-dos))
  436. (string-match "\\`[a-zA-Z]:[/\\]\\'" dir)))
  437. (python-django-info-find-manage.py
  438. (expand-file-name
  439. (file-name-as-directory "..") dir)))
  440. (expand-file-name "manage.py" dir))))
  441. (defvar python-django-info-prefetched-settings
  442. '("INSTALLED_APPS" "DATABASES" "MEDIA_ROOT" "STATIC_ROOT" "TEMPLATE_DIRS"
  443. "STATICFILES_DIRS"))
  444. (defvar python-django-info--get-setting-cache nil
  445. "Alist with cached list of settings.")
  446. (defvar python-django-info--get-version-cache nil
  447. "Alist with cached list of settings.")
  448. (defun python-django-info-get-version (&optional force)
  449. "Get current Django version path.
  450. Values retrieved by this function are cached so when FORCE is
  451. non-nil the cached value is invalidated."
  452. (or
  453. (and (not force) python-django-info--get-version-cache))
  454. (setq
  455. python-django-info--get-version-cache
  456. (let* ((process-environment
  457. (python-django-info-calculate-process-environment))
  458. (exec-path (python-shell-calculate-exec-path)))
  459. (python-django-util-shell-command-or-error
  460. (format
  461. "%s -c \"%s\""
  462. (executable-find python-shell-interpreter)
  463. (concat
  464. "from __future__ import print_function\n"
  465. "import django\n"
  466. "print(django.get_version(), end='')"))))))
  467. (defvar python-django-info-imports-code
  468. (concat "\n"
  469. "from __future__ import print_function\n"
  470. "import os\n"
  471. "import sys\n"
  472. "from os.path import dirname, abspath\n"
  473. "stdout = sys.stdout; stderr = sys.stderr\n"
  474. "sys.stdout = sys.stderr = open(os.devnull, 'w')\n"
  475. "from django.conf import settings\n"
  476. "# Try to import json really hard\n"
  477. "try:\n"
  478. " import json\n"
  479. "except ImportError:\n"
  480. " from django.utils import simplejson as json\n"
  481. "# Force settings loading so all output is sent to devnull.\n"
  482. "settings.DEBUG\n"
  483. "sys.stdout = stdout; sys.stderr = stderr\n\n")
  484. "All imports code used to get info.
  485. It contains output redirecting features so settings import
  486. doesn't break the JSON output.")
  487. (defun python-django-info-get-settings (&optional force)
  488. "Prefretch most common used settings for project.
  489. Values retrieved by this function are cached so when FORCE is
  490. non-nil the cached value is invalidated."
  491. (let ((cached
  492. (mapcar
  493. #'(lambda (setting)
  494. (assq (intern setting)
  495. python-django-info--get-setting-cache))
  496. python-django-info-prefetched-settings)))
  497. (if (and (not force)
  498. (catch 'exit
  499. (dolist (elt cached)
  500. (when (null elt)
  501. (throw 'exit nil)))
  502. t))
  503. cached
  504. (let* ((process-environment
  505. (python-django-info-calculate-process-environment))
  506. (exec-path (python-shell-calculate-exec-path))
  507. (settings-list-string
  508. (concat "["
  509. (mapconcat
  510. #'(lambda (str) (concat "'" str "'"))
  511. python-django-info-prefetched-settings
  512. ", ")
  513. "]"))
  514. (value
  515. (json-read-from-string
  516. (python-django-util-shell-command-or-error
  517. (format "%s -c \"%s%s\""
  518. (executable-find python-shell-interpreter)
  519. python-django-info-imports-code
  520. (concat
  521. "acc = {}\n"
  522. "for name in " settings-list-string ":\n"
  523. " acc[name] = getattr(settings, name, None)\n"
  524. "print(json.dumps(acc), end='')"))))))
  525. (mapc
  526. (lambda (elt)
  527. (let ((cached-val
  528. (assq (car elt) python-django-info--get-setting-cache)))
  529. (if cached-val
  530. (setcdr cached-val (cdr elt))
  531. (setq python-django-info--get-setting-cache
  532. (cons elt python-django-info--get-setting-cache)))))
  533. value)))))
  534. (defun python-django-info-get-setting (setting &optional force)
  535. "Get SETTING value from django.conf.settings in JSON format.
  536. Values retrieved by this function are cached so when FORCE is
  537. non-nil the cached value is invalidated."
  538. (let ((cached
  539. (or (and
  540. (member setting python-django-info-prefetched-settings)
  541. (assq (intern setting) (python-django-info-get-settings force)))
  542. (assq (intern setting)
  543. python-django-info--get-setting-cache))))
  544. (if (and (not force) cached)
  545. (cdr cached)
  546. (let* ((process-environment
  547. (python-django-info-calculate-process-environment))
  548. (exec-path (python-shell-calculate-exec-path))
  549. (value
  550. (json-read-from-string
  551. (python-django-util-shell-command-or-error
  552. (format
  553. "%s -c \"%s%s\""
  554. (executable-find python-shell-interpreter)
  555. python-django-info-imports-code
  556. (format
  557. (concat
  558. "print(json.dumps("
  559. "getattr(settings, '%s', None)), end='')")
  560. setting)))))
  561. (already-cached (assq (intern setting)
  562. python-django-info--get-setting-cache)))
  563. (if already-cached
  564. (setcdr already-cached value)
  565. (setq python-django-info--get-setting-cache
  566. (cons (cons (intern setting) value)
  567. python-django-info--get-setting-cache)))
  568. value))))
  569. (defvar python-django-info--get-app-paths-cache nil
  570. "Cached list of apps and paths.")
  571. (defun python-django-info-get-app-paths (&optional force)
  572. "Get project paths path.
  573. Values retrieved by this function are cached so when FORCE is
  574. non-nil the cached value is invalidated."
  575. (if (or force (not python-django-info--get-app-paths-cache))
  576. (setq
  577. python-django-info--get-app-paths-cache
  578. (let* ((process-environment
  579. (python-django-info-calculate-process-environment))
  580. (exec-path (python-shell-calculate-exec-path)))
  581. (json-read-from-string
  582. (python-django-util-shell-command-or-error
  583. (format "%s -c \"%s%s\""
  584. (executable-find python-shell-interpreter)
  585. python-django-info-imports-code
  586. "
  587. try:
  588. from django.apps import apps
  589. except ImportError:
  590. # Django<1.7 app loading.
  591. import os.path
  592. from django.utils.importlib import import_module
  593. app_paths = {}
  594. for app_string in settings.INSTALLED_APPS:
  595. app_module = import_module(app_string)
  596. app_path = os.path.dirname(app_module.__file__)
  597. # Keep the last part (e.g: 'django.contrib.admin' -> 'admin')
  598. app_label = app_string.rpartition('.')[2]
  599. app_paths[app_label] = app_path
  600. else:
  601. # Django>=1.7 app loading.
  602. apps.populate(installed_apps=settings.INSTALLED_APPS)
  603. app_paths = {app.label: app.path for app in apps.get_app_configs()}
  604. print(json.dumps(app_paths), end='')")))))
  605. python-django-info--get-app-paths-cache))
  606. (defun python-django-info-get-app-path (app &optional force)
  607. "Get APP's path.
  608. Values retrieved by this function are cached so when FORCE is
  609. non-nil the cached value is invalidated."
  610. (cdr (assq (intern app) (python-django-info-get-app-paths force))))
  611. (defun python-django-info-get-installed-apps (&optional force)
  612. "Get list of strings of installed app labels.
  613. Values retrieved by this function are cached so when FORCE is
  614. non-nil the cached value is invalidated."
  615. (mapcar
  616. (lambda (elt)
  617. (symbol-name (car elt)))
  618. (python-django-info-get-app-paths force)))
  619. (defun python-django-info-get-app-migrations (app)
  620. "Get APP's list of migrations."
  621. (mapcar (lambda (file)
  622. file)
  623. (ignore-errors
  624. (directory-files
  625. (expand-file-name
  626. "migrations"
  627. (python-django-info-get-app-path app))
  628. nil "^[0-9]\\{4\\}_.*\\.py$"))))
  629. (defun python-django-info-module-path (module)
  630. "Get MODULE's path."
  631. (let* ((process-environment
  632. (python-django-info-calculate-process-environment))
  633. (exec-path (python-shell-calculate-exec-path)))
  634. (python-django-util-shell-command-or-error
  635. (format "%s -c \"%s%s%s\""
  636. (executable-find python-shell-interpreter)
  637. python-django-info-imports-code
  638. (format "import %s\n" module)
  639. (format
  640. "print(%s.__file__.replace('.pyc', '.py'), end='')" module)))))
  641. (defun python-django-info-directory-basename (&optional dir)
  642. "Get innermost directory name for given DIR."
  643. (car (last (split-string dir "/" t))))
  644. ;;; Hippie expand completion
  645. (defun python-django-minibuffer-try-complete-args (old)
  646. "Try to complete word as a management command argument.
  647. The argument OLD has to be nil the first call of this function, and t
  648. for subsequent calls (for further possible completions of the same
  649. string). It returns t if a new completion is found, nil otherwise."
  650. (save-excursion
  651. (unless old
  652. (he-init-string (he-dabbrev-beg) (point))
  653. (when (not (equal he-search-string ""))
  654. (setq he-expand-list
  655. (sort (all-completions
  656. he-search-string
  657. minibuffer-completion-table)
  658. 'string<))))
  659. (while (and he-expand-list
  660. (he-string-member (car he-expand-list) he-tried-table))
  661. (setq he-expand-list (cdr he-expand-list)))
  662. (if (null he-expand-list)
  663. (progn (if old (he-reset-string)) ())
  664. (progn
  665. (he-substitute-string (car he-expand-list))
  666. (setq he-tried-table (cons (car he-expand-list)
  667. (cdr he-tried-table)))
  668. t))))
  669. (defun python-django-minibuffer-try-complete-filenames (old)
  670. "Try to complete filenames in command arguments.
  671. The argument OLD has to be nil the first call of this function, and t
  672. for subsequent calls (for further possible completions of the same
  673. string). It returns t if a new completion is found, nil otherwise."
  674. (if (not old)
  675. (progn
  676. (he-init-string (let ((max-point (point)))
  677. (save-excursion
  678. (goto-char (he-file-name-beg))
  679. (re-search-forward "--?[a-z0-9_-]+=?" max-point t)
  680. (point)))
  681. (point))
  682. (let ((name-part (file-name-nondirectory he-search-string))
  683. (dir-part (expand-file-name (or (file-name-directory
  684. he-search-string) ""))))
  685. (if (not (he-string-member name-part he-tried-table))
  686. (setq he-tried-table (cons name-part he-tried-table)))
  687. (if (and (not (equal he-search-string ""))
  688. (file-directory-p dir-part))
  689. (setq he-expand-list (sort (file-name-all-completions
  690. name-part
  691. dir-part)
  692. 'string-lessp))
  693. (setq he-expand-list ())))))
  694. (while (and he-expand-list
  695. (he-string-member (car he-expand-list) he-tried-table))
  696. (setq he-expand-list (cdr he-expand-list)))
  697. (if (null he-expand-list)
  698. (progn
  699. (if old (he-reset-string))
  700. ())
  701. (let ((filename (he-concat-directory-file-name
  702. (file-name-directory he-search-string)
  703. (car he-expand-list))))
  704. (he-substitute-string filename)
  705. (setq he-tried-table (cons (car he-expand-list) (cdr he-tried-table)))
  706. (setq he-expand-list (cdr he-expand-list))
  707. t)))
  708. ;;; Minibuffer
  709. (defvar python-django-minibuffer-complete-command-map
  710. (let ((map (make-sparse-keymap)))
  711. (set-keymap-parent map minibuffer-local-must-match-map)
  712. map)
  713. "Keymap used for completing commands in minibuffer.")
  714. (defvar python-django-minibuffer-complete-command-args-map
  715. (let ((map (make-sparse-keymap)))
  716. (set-keymap-parent map minibuffer-local-map)
  717. (define-key map "\t" 'hippie-expand)
  718. (define-key map [remap scroll-other-window]
  719. 'python-django-minibuffer-scroll-help-window)
  720. (define-key map [remap scroll-other-window-down]
  721. 'python-django-minibuffer-scroll-help-window-down)
  722. map)
  723. "Keymap used for completing command args in minibuffer.")
  724. (defun python-django-minibuffer-read-command (&optional trigger-help)
  725. "Read django management command from minibuffer.
  726. Optional argument TRIGGER-HELP sets if help buffer with commmand
  727. details should be displayed."
  728. (let* ((current-buffer (current-buffer))
  729. (command
  730. (minibuffer-with-setup-hook
  731. (lambda ()
  732. (python-util-clone-local-variables current-buffer)
  733. (setq minibuffer-completion-table
  734. (python-django-mgmt-list-commands)))
  735. (read-from-minibuffer
  736. "./manage.py: " nil
  737. python-django-minibuffer-complete-command-map))))
  738. (when trigger-help
  739. (python-django-help command t))
  740. command))
  741. (defun python-django-minibuffer-read-command-args (command)
  742. "Read django management arguments for command from minibuffer.
  743. Arguments are parsed for especific COMMAND."
  744. (let* ((current-buffer (current-buffer)))
  745. (minibuffer-with-setup-hook
  746. (lambda ()
  747. (python-util-clone-local-variables current-buffer)
  748. (setq minibuffer-completion-table
  749. (python-django-mgmt-list-command-args command))
  750. (set (make-local-variable 'hippie-expand-try-functions-list)
  751. '(python-django-minibuffer-try-complete-args
  752. python-django-minibuffer-try-complete-filenames)))
  753. (read-from-minibuffer
  754. (format "./manage.py %s (args): " command)
  755. nil python-django-minibuffer-complete-command-args-map))))
  756. (defun python-django-minibuffer-read-list (thing &rest args)
  757. "Helper function to read list of THING from minibuffer.
  758. Optional argument ARGS are the args passed to the THING."
  759. (let ((objs))
  760. (catch 'exit
  761. (while t
  762. (add-to-list
  763. 'objs
  764. (apply thing args) t)
  765. (when (not (y-or-n-p "Add another? "))
  766. (throw 'exit (mapconcat 'identity objs " ")))))))
  767. (defun python-django-minibuffer-read-file-name (prompt)
  768. "Read a single file name from minibuffer.
  769. PROMPT is a string to prompt user for filenames."
  770. (let ((use-dialog-box nil))
  771. ;; Lets make shell expansion work.
  772. (replace-regexp-in-string
  773. "[\\]\\*" "*"
  774. (shell-quote-argument
  775. (let ((func
  776. (if ido-mode
  777. 'ido-read-file-name
  778. 'read-file-name)))
  779. (funcall func prompt python-django-project-root
  780. python-django-project-root nil))))))
  781. (defun python-django-minibuffer-read-file-names (prompt)
  782. "Read a list of file names from minibuffer.
  783. PROMPT is a string to prompt user for filenames."
  784. (python-django-minibuffer-read-list
  785. 'python-django-minibuffer-read-file-name prompt))
  786. (defun python-django-minibuffer-read-app (prompt &optional initial-input)
  787. "Read django app from minibuffer.
  788. PROMPT is a string to prompt user for app. Optional argument
  789. INITIAL-INPUT is the initial prompted value."
  790. (let ((apps (python-django-info-get-installed-apps))
  791. (current-buffer (current-buffer)))
  792. (minibuffer-with-setup-hook
  793. (lambda ()
  794. (python-util-clone-local-variables current-buffer)
  795. (setq minibuffer-completion-table apps))
  796. (catch 'app
  797. (while t
  798. (let ((app (read-from-minibuffer
  799. prompt initial-input minibuffer-local-must-match-map)))
  800. (when (> (length app) 0)
  801. (throw 'app app))))))))
  802. (defun python-django-minibuffer-read-apps (prompt &optional initial-input)
  803. "Read django apps from minibuffer.
  804. PROMPT is a string to prompt user for app. Optional argument
  805. INITIAL-INPUT is the initial prompted value."
  806. (python-django-minibuffer-read-list
  807. 'python-django-minibuffer-read-app prompt))
  808. (defun python-django-minibuffer-read-database (prompt &optional initial-input)
  809. "Read django database router name from minibuffer.
  810. PROMPT is a string to prompt user for database.
  811. Optional argument INITIAL-INPUT is the initial prompted value."
  812. (let ((databases (mapcar (lambda (router)
  813. (format "%s" (car router)))
  814. (python-django-info-get-setting "DATABASES")))
  815. (current-buffer (current-buffer)))
  816. (minibuffer-with-setup-hook
  817. (lambda ()
  818. (python-util-clone-local-variables current-buffer)
  819. (setq minibuffer-completion-table databases))
  820. (catch 'db
  821. (while t
  822. (let ((db (read-from-minibuffer
  823. prompt initial-input minibuffer-local-must-match-map)))
  824. (when (> (length db) 0)
  825. (throw 'db db))))))))
  826. (defun python-django-minibuffer-read-migration (prompt app)
  827. "Read south migration number for given app from minibuffer.
  828. PROMPT is a string to prompt user for database. APP is the app
  829. to read migrations from."
  830. (let* ((migrations (python-django-info-get-app-migrations app)))
  831. (minibuffer-with-setup-hook
  832. (lambda ()
  833. (setq minibuffer-completion-table migrations))
  834. (let ((migration (read-from-minibuffer
  835. prompt nil minibuffer-local-must-match-map)))
  836. (when (not (string= migration ""))
  837. (substring migration 0 4))))))
  838. (defun python-django-minibuffer-read-from-list (prompt lst &optional default)
  839. "Read a value from a list from minibuffer.
  840. PROMPT is a string to prompt user. LST is the list containing
  841. the values to choose from. Optional argument DEFAULT is the
  842. default value."
  843. (minibuffer-with-setup-hook
  844. (lambda ()
  845. (setq minibuffer-completion-table lst))
  846. (read-from-minibuffer prompt default minibuffer-local-must-match-map)))
  847. ;;; Management commands
  848. (defvar python-django-mgmt--available-commands nil
  849. "Alist with cached list of management commands for each project.")
  850. (defun python-django-mgmt-list-commands (&optional force)
  851. "List available management commands.
  852. Optional argument FORCE makes the function to recalculate the
  853. list of command for current project instead of getting it from
  854. the `python-django-mgmt--available-commands' cache."
  855. (and force
  856. (set (make-local-variable 'python-django-mgmt--available-commands) nil))
  857. (cdr
  858. (or python-django-mgmt--available-commands
  859. (let ((help-string (python-django-help))
  860. (commands))
  861. (set (make-local-variable 'python-django-mgmt--available-commands)
  862. (with-temp-buffer
  863. (insert help-string)
  864. (goto-char (point-min))
  865. (re-search-forward "Available subcommands:\n")
  866. (delete-region (point-min) (point))
  867. (while (re-search-forward " +\\([a-z0-9_]+\\)\n" nil t)
  868. (setq commands
  869. (cons (match-string-no-properties 1) commands)))
  870. (reverse commands)))))))
  871. (defun python-django-mgmt-list-command-args (command)
  872. "List available arguments for COMMAND."
  873. (let ((help-string (python-django-help command))
  874. (args))
  875. (with-temp-buffer
  876. (insert help-string)
  877. (goto-char (point-min))
  878. (when (re-search-forward "^Options:\n" nil t)
  879. (while (re-search-forward "--[a-z0-9_-]+=?" nil t)
  880. (setq args (cons (match-string 0) args))
  881. (append args (match-string 0)))
  882. (sort args 'string<)))))
  883. (defun python-django-mgmt-make-comint (command process-name)
  884. "Run COMMAND with PROCESS-NAME in generic Comint buffer."
  885. (apply 'make-comint process-name
  886. (executable-find python-shell-interpreter) nil
  887. (split-string-and-unquote command)))
  888. (defun python-django-mgmt-make-comint-for-shell (command process-name)
  889. "Run COMMAND with PROCESS-NAME in generic Comint buffer."
  890. (let ((python-shell-interpreter-args command))
  891. (python-shell-make-comint (python-shell-parse-command) process-name)))
  892. (defun python-django-mgmt-make-comint-for-runserver (command process-name)
  893. "Run COMMAND with PROCESS-NAME in generic Comint buffer."
  894. (let ((python-shell-enable-font-lock nil))
  895. (python-django-mgmt-make-comint-for-shell command process-name)))
  896. (defun python-django-mgmt-make-comint-for-dbshell (command process-name)
  897. "Run COMMAND with PROCESS-NAME in generic Comint buffer."
  898. (let* ((dbsetting (python-django-info-get-setting "DATABASES"))
  899. (dbengine (cdr (assoc 'ENGINE (assoc 'default dbsetting))))
  900. (sql-interactive-product-1
  901. (cond ((string= dbengine "django.db.backends.mysql")
  902. 'mysql)
  903. ((string= dbengine "django.db.backends.oracle")
  904. 'oracle)
  905. ((string= dbengine "django.db.backends.postgresql")
  906. 'postgres)
  907. ((string= dbengine "django.db.backends.sqlite3")
  908. 'sqlite)
  909. (t nil)))
  910. (buffer
  911. (python-django-mgmt-make-comint command process-name)))
  912. (with-current-buffer buffer
  913. (setq sql-buffer (current-buffer)
  914. sql-interactive-product sql-interactive-product-1)
  915. (sql-interactive-mode))
  916. buffer))
  917. (defcustom python-django-mgmt-buffer-switch-function 'display-buffer
  918. "Function for switching to the process buffer.
  919. The function receives one argument, the management command
  920. process buffer."
  921. :group 'python-django
  922. :type '(radio (function-item switch-to-buffer)
  923. (function-item pop-to-buffer)
  924. (function-item display-buffer)
  925. (function :tag "Other")))
  926. (defvar python-django-mgmt--previous-window-configuration nil
  927. "Snapshot of previous window configuration before executing command.
  928. This variable is for internal purposes, don't use it directly.")
  929. (defun python-django-mgmt-restore-window-configuration ()
  930. "Restore window configuration after running a management command."
  931. (and python-django-mgmt--previous-window-configuration
  932. (set-window-configuration
  933. python-django-mgmt--previous-window-configuration)))
  934. (defvar python-django-mgmt-parent-buffer nil
  935. "Parent project buffer for current process.")
  936. (defvar python-django-mgmt--opened-buffers nil
  937. "Alist of currently opened process buffers.")
  938. (defun python-django-mgmt-buffer-list (&optional parent-buffer)
  939. "Return all opened buffer names for PARENT-BUFFER.
  940. Optional Argument PARENT-BUFFER defaults to the current buffer."
  941. (python-django-util-alist-get
  942. (or parent-buffer (current-buffer))
  943. python-django-mgmt--opened-buffers))
  944. (defvar python-django-mgmt--buffer-index 0)
  945. (defun python-django-mgmt-buffer-get (&optional index)
  946. "Get management buffer by INDEX.
  947. Optional Argument INDEX defaults to the value of
  948. `python-django-mgmt--buffer-index'."
  949. (let ((buffer-list (python-django-mgmt-buffer-list)))
  950. (and buffer-list
  951. (nth (mod (or index python-django-mgmt--buffer-index)
  952. (length buffer-list)) buffer-list))))
  953. (defun python-django-mgmt-cycle-buffers-forward (&optional arg)
  954. "Cycle opened process buffers forward.
  955. With Optional Argument ARG cycle that many buffers."
  956. (interactive "p")
  957. (setq arg (or arg 1))
  958. (let ((buffers (python-django-mgmt-buffer-list)))
  959. (and buffers
  960. (let ((newindex
  961. (mod (+ python-django-mgmt--buffer-index arg)
  962. (length buffers))))
  963. (set (make-local-variable
  964. 'python-django-mgmt--buffer-index) newindex)
  965. (display-buffer (nth newindex buffers))))))
  966. (defun python-django-mgmt-cycle-buffers-backward (&optional arg)
  967. "Cycle opened process buffers backward.
  968. With Optional Argument ARG cycle that many buffers."
  969. (interactive "p")
  970. (python-django-mgmt-cycle-buffers-forward (- (or arg 1))))
  971. (defun python-django-mgmt-run-command (command
  972. &optional args capture-ouput no-pop
  973. make-comint-function)
  974. "Run management COMMAND with given ARGS.
  975. When optional argument CAPTURE-OUPUT is non-nil process output is
  976. not truncated by the `comint-truncate-buffer' output filter. If
  977. optional argument NO-POP is provided the process buffer is not
  978. displayed automatically. When optional argument
  979. MAKE-COMINT-FUNCTION is non-nil use that function to create the
  980. comint process, defaults to `python-django-mgmt-make-comint'."
  981. (interactive
  982. (list
  983. (setq command
  984. (python-django-minibuffer-read-command t))
  985. (python-django-minibuffer-read-command-args command)))
  986. (python-django-help-close)
  987. (when (not (member command (python-django-mgmt-list-commands)))
  988. (error
  989. "Management command %s is not available in current project" command))
  990. (let* ((args (or args ""))
  991. (process-environment
  992. (python-django-info-calculate-process-environment))
  993. (exec-path (python-shell-calculate-exec-path))
  994. (process-name
  995. (replace-regexp-in-string
  996. "[\t ]+$" ""
  997. (format "[Django: %s (%s)] ./manage.py %s %s"
  998. python-django-project-name
  999. (python-django-util-shorten-settings)
  1000. command args)))
  1001. (buffer-name (format "*%s*" process-name))
  1002. (current-buffer (current-buffer))
  1003. (make-comint-function (or make-comint-function
  1004. #'python-django-mgmt-make-comint))
  1005. (full-command
  1006. (format "%s %s %s"
  1007. python-django-project-manage.py
  1008. command args)))
  1009. (funcall make-comint-function full-command process-name)
  1010. (with-current-buffer buffer-name
  1011. (python-util-clone-local-variables current-buffer)
  1012. (and (not capture-ouput)
  1013. (add-hook 'comint-output-filter-functions
  1014. 'comint-truncate-buffer nil t))
  1015. (set (make-local-variable
  1016. 'python-django-mgmt-parent-buffer) current-buffer)
  1017. (python-django-util-alist-add
  1018. current-buffer (current-buffer)
  1019. python-django-mgmt--opened-buffers)
  1020. (add-hook
  1021. 'kill-buffer-hook
  1022. (lambda ()
  1023. (python-django-util-alist-del
  1024. python-django-mgmt-parent-buffer (current-buffer)
  1025. python-django-mgmt--opened-buffers))
  1026. nil t))
  1027. (unless no-pop
  1028. (funcall python-django-mgmt-buffer-switch-function buffer-name)
  1029. (with-current-buffer buffer-name
  1030. (and (get-buffer-process (current-buffer))
  1031. (comint-goto-process-mark))))
  1032. buffer-name))
  1033. (add-to-list 'debug-ignored-errors
  1034. "^Management command .* is not available in current project.")
  1035. (defun python-django-mgmt-kill (&optional buffer)
  1036. "Kill current command's BUFFER."
  1037. (interactive)
  1038. (setq buffer (or buffer (python-django-mgmt-buffer-get)))
  1039. (when (and buffer (or (not (called-interactively-p 'any))
  1040. (y-or-n-p
  1041. (format "Kill %s? " buffer))))
  1042. (let ((win (get-buffer-window buffer 0))
  1043. (proc (get-buffer-process buffer)))
  1044. (and win (delete-window win))
  1045. (and proc (set-process-query-on-exit-flag proc nil))
  1046. (kill-buffer buffer)
  1047. (python-django-mgmt-cycle-buffers-forward))))
  1048. (defun python-django-mgmt-kill-all (&optional command)
  1049. "Kill all running commands for current project after CONFIRM.
  1050. When called with universal argument you can filter the COMMAND to kill."
  1051. (interactive
  1052. (list
  1053. (and current-prefix-arg
  1054. (python-django-minibuffer-read-command nil))))
  1055. (when (or (not (called-interactively-p 'any))
  1056. (y-or-n-p
  1057. (format "Do you want to kill all running commands for %s? "
  1058. python-django-project-name)))
  1059. (dolist (buffer
  1060. (python-django-mgmt-buffer-list (current-buffer)))
  1061. (when (or (not command)
  1062. (string-match
  1063. (format "\\./manage.py %s" (or command "")) buffer))
  1064. (let ((win (get-buffer-window buffer 0))
  1065. (proc (get-buffer-process buffer)))
  1066. (and win (delete-window win))
  1067. (and proc (set-process-query-on-exit-flag proc nil)))
  1068. (kill-buffer buffer)))))
  1069. ;;; Management shortcuts
  1070. (eval-and-compile
  1071. (defun python-django-qmgmt--make-fn-symbol (name)
  1072. "Return a quick management command defun symbol from NAME."
  1073. (intern (format "python-django-qmgmt-%s" name)))
  1074. (defun python-django-qmgmt--make-functions-symbol (name)
  1075. "Return a quick management command functions symbol from NAME."
  1076. (intern (format "python-django-qmgmt-%s-functions" name)))
  1077. (defun python-django-qmgmt--make-spec
  1078. (command &optional switches interactive-switches)
  1079. "Return human readable spec for COMMAND.
  1080. The spec is the shell command with placeholders in it. Example:
  1081. ./manage.py cmd --all --database=<database> --app=<app>
  1082. Where \"--all\" is SWITCHES value, while database and app are
  1083. generated from INTERACTIVE-SWITCHES."
  1084. (concat
  1085. (format "./manage.py %s " command)
  1086. (unless (zerop (length switches))
  1087. (format "%s " switches))
  1088. (when interactive-switches
  1089. (mapconcat
  1090. (lambda (arg)
  1091. (let* ((switch (nth 3 arg))
  1092. (switch
  1093. (cond
  1094. ((eq (length switch) 0)
  1095. "")
  1096. ((eq ?= (car (last (append switch nil))))
  1097. switch)
  1098. (t (format "%s " switch))))
  1099. (varname (symbol-name (nth 0 arg))))
  1100. (format "%s<%s>" switch varname)))
  1101. interactive-switches
  1102. " "))))
  1103. (defun python-django-qmgmt--make-docstring
  1104. (command &optional switches interactive-switches docstring)
  1105. "Return documentation string for auto-generated command.
  1106. Arguments COMMAND, SWITCHES, INTERACTIVE-SWITCHES and DOCSTRING
  1107. are used to generate the string."
  1108. (format
  1109. "%s\n\n%s"
  1110. ;; Show either the spec, or the docstring *and* the spec.
  1111. (concat
  1112. (unless (zerop (length docstring))
  1113. (concat docstring "\n\n"))
  1114. "Run: "
  1115. (python-django-qmgmt--make-spec
  1116. command switches interactive-switches))
  1117. (concat
  1118. "This is an interactive command defined via "
  1119. "`python-django-qmgmt-define' macro.\n"
  1120. "Parameter defaults can be overriden by "
  1121. "calling this command with `prefix-arg' .\n\n"
  1122. (when switches
  1123. (format "Default switches: \n\n * %s\n\n" switches))
  1124. (when interactive-switches
  1125. (format
  1126. "Arguments: \n\n%s"
  1127. (mapconcat
  1128. (lambda (arg)
  1129. (let* ((default (nth 2 arg))
  1130. (switch (nth 3 arg))
  1131. (switch
  1132. (cond
  1133. ((eq (length switch) 0)
  1134. nil)
  1135. ((eq ?= (car (last (append switch nil))))
  1136. switch)
  1137. (t (format "%s " switch))))
  1138. (force-ask (nth 4 arg)))
  1139. (concat
  1140. (format " * %s:\n"
  1141. (upcase (symbol-name (car arg))))
  1142. (format " + Switch: %s\n" switch)
  1143. (format " + Defaults: %s\n"
  1144. (prin1-to-string default))
  1145. (format " + Read SPEC: %s\n"
  1146. (prin1-to-string (nth 1 arg)))
  1147. (format " + Force prompt: %s\n"
  1148. force-ask)
  1149. (format " + Requires user interaction?: %s"
  1150. (if (or force-ask (not default))
  1151. "yes" "no")))))
  1152. interactive-switches "\n\n"))))))
  1153. (defun python-django-qmgmt--make-functions-docstring
  1154. (fn-symbol)
  1155. "Return documentation string for auto-generated functions variable.
  1156. Argument FN-SYMBOL is the symbol of the command."
  1157. (format
  1158. "A function or list of called after `%s's bound process finishes.
  1159. Functions defined here are called in order and must receive two
  1160. arguments: The first must be a STATUS-OK which is non-nil if the
  1161. process exited successfully and ARGS which is a plist with the
  1162. switches and arguments given to it. See
  1163. `python-django-qmgmt-define' docstring for details." fn-symbol))
  1164. (defun python-django-qmgmt--make-interactive-form (interactive-switches)
  1165. "Return interactive form from INTERACTIVE-SWITCHES."
  1166. (mapcar
  1167. (lambda (arg)
  1168. ;; Interactive switch form: (VARNAME PROMPT DEFAULT SWITCH FORCE-ASK)
  1169. (let* ((default (nth 2 arg))
  1170. (switch (nth 3 arg))
  1171. (switch
  1172. (cond ((zerop (length switch))
  1173. ;; This is a positional argument. Skip prefix.
  1174. "")
  1175. ((eq ?= (car (last (append switch nil))))
  1176. ;; Long switch (e.g "--database="), keep it as prefix.
  1177. switch)
  1178. ;; Shor switch (e.g "-d"), add space suffix.
  1179. (t (format "%s " switch))))
  1180. (force-ask (nth 4 arg))
  1181. (read-func
  1182. (if (stringp (nth 1 arg))
  1183. ;; Use string as prompt.
  1184. `(read-string ,(nth 1 arg) default)
  1185. ;; Execute form to read argument value.
  1186. (nth 1 arg))))
  1187. `(concat
  1188. ,switch
  1189. (setq ,(car arg)
  1190. (let ((default ,default))
  1191. (if (or ,force-ask
  1192. current-prefix-arg
  1193. (not default))
  1194. ,read-func
  1195. default))))))
  1196. interactive-switches))
  1197. (defun python-django-qmgmt--add-binding (symbol &optional binding)
  1198. "Add keybinding to SYMBOL using BINDING."
  1199. (when binding
  1200. (ignore-errors
  1201. (define-key python-django-mode-map (concat "c" binding) symbol))))
  1202. (defun python-django-qmgmt--add-menu-item
  1203. (symbol submenu command &optional switches interactive-switches docstring)
  1204. "Add menu item for SYMBOL in SUBMENU to execute mangament COMMAND.
  1205. Arguments SWITCHES, INTERACTIVE-SWITCHES and DOCSTRING are used
  1206. to generate a descriptive item."
  1207. (let* ((spec (python-django-qmgmt--make-spec
  1208. command switches interactive-switches))
  1209. (help (if (zerop (length docstring)) ; also handle empty string
  1210. (format "Run ./manage.py %s" spec)
  1211. (car (split-string docstring "\n")))))
  1212. (easy-menu-add-item
  1213. 'python-django-menu nil (list submenu) "---")
  1214. (easy-menu-add-item
  1215. 'python-django-menu (list submenu)
  1216. `[,spec
  1217. ,symbol
  1218. :help ,help
  1219. :active (member ,command (python-django-mgmt-list-commands))]
  1220. "---"))))
  1221. (defmacro python-django-qmgmt-define (name doc-or-args
  1222. &optional args &rest interactive-switches)
  1223. "Define a quick management command.
  1224. Argument NAME is a symbol and it is used to calculate the
  1225. management command this command will execute, so it should have
  1226. the form cmdname[-rest]. Argument DOC-OR-ARGS might be the
  1227. docstring for the defined command or the list of arguments, when
  1228. a docstring is supplied ARGS is used as the list of arguments
  1229. instead. The rest INTERACTIVE-SWITCHES is a list of interactive
  1230. switches the user will be prompted for.
  1231. This is a full example that will define how to execute Django's
  1232. dumpdata for the current application quickly:
  1233. (python-django-qmgmt-define dumpdata-app
  1234. \"Run dumpdata for current application.\"
  1235. (:submenu \"Database\" :switches \"--format=json\" :binding \"dda\")
  1236. (database \"Database\" \"default\" \"--database=\")
  1237. (app \"App\"))
  1238. When that's is evaled a command called
  1239. `python-django-qmgmt-dumpdata-app' is created and will react
  1240. depending on the arguments passed to this macro.
  1241. All commands defined by this macro, when called with `prefix-arg'
  1242. will ask the user for values instead of using defaults.
  1243. ARGS is a property list. Valid keys are (all optional):
  1244. + :binding, when defined, the new command is bound to the
  1245. default prefix for quick management commands plus this value.
  1246. + :capture-output, when non-nil, the command output is not
  1247. truncated by the `comint-truncate-buffer' output filter.
  1248. + :msg, when defined, commands that use the
  1249. `python-django-qmgmt-kill-and-msg-function' show this instead
  1250. of the buffer contents.
  1251. + :no-pop, when non-nil, causes the process buffer to not be
  1252. displayed.
  1253. + :submenu, when defined, the quick management command is
  1254. added within that submenu tree. If omitted the item is added
  1255. to the root.
  1256. + :switches, when defined, the new command is executed with
  1257. these fixed switches.
  1258. + :make-comint-function, a function to be used to create the
  1259. comint process, defaults to `python-django-mgmt-make-comint'.
  1260. + :functions, If the value is a function, it is called with
  1261. two arguments. If it is a list, the elements are called, in
  1262. order, with same two arguments each. The first argument is
  1263. non-nil if the process ended successfully; the second
  1264. argument is a property-list containing passed ARGS and
  1265. INTERACTIVE-SWITCHES with symbols as keys. E.g: '(app
  1266. \"auth\" :command \"test\").
  1267. If you define any extra keys they will not be taken into account
  1268. by this macro but you may well use them in your command's
  1269. callback.
  1270. INTERACTIVE-SWITCHES have the form (VARNAME PROMPT DEFAULT SWITCH
  1271. FORCE-ASK), you can add 0 or more INTERACTIVE-SWITCHES depending
  1272. on the number of parameters you need to pass to the management
  1273. command. The description for each element of the list are:
  1274. + VARNAME must be a unique symbol not used in other switch.
  1275. + PROMPT must be a string for the prompt that will be shown
  1276. when user is asked for a value using `read-string' or it can
  1277. be a expresion that will be used to read the value for
  1278. VARNAME. When you need to use the calculated value of
  1279. DEFAULT in the provided expression you can just use that
  1280. variable like this:
  1281. (read-file-name \"Fixture: \" nil default)
  1282. + DEFAULT is an expression to be executed in order to
  1283. calculate the default value for VARNAME. This is optional
  1284. and in the case is not provided or returns nil after executed
  1285. the user will be prompted to insert a value for VARNAME.
  1286. + SWITCH is a string that represents the switch used to pass
  1287. the VARNAME's value to Django's management command.
  1288. + FORCE-ASK might be nil or non-nil, when is non-nil the user
  1289. will be asked to insert a value for VARNAME even if a default
  1290. value is available.
  1291. Each command defined via this macro may have a callback to be
  1292. executed when the process finishes correctly. The way to define
  1293. callbacks is to append -callback to the defined name, for
  1294. instance if you defined a quick management command called syncdb,
  1295. then you need to create a function named
  1296. `python-django-qmgmt-syncdb-callback' and it will be called with
  1297. an alist containing all INTERACTIVE-SWITCHES and ARGS with the
  1298. additional :command key holding the executed command. See the
  1299. `python-django-qmgmt-kill-and-msg-function' function for a nice
  1300. example of a callback."
  1301. (declare (indent defun))
  1302. (let* ((docstring (when (stringp doc-or-args) doc-or-args))
  1303. (args (if docstring args doc-or-args))
  1304. (args (if (eq ?: (string-to-char (symbol-name (car args))))
  1305. args
  1306. ;; args is not a plist, append it to interactive-switches and
  1307. ;; set args to nil.
  1308. (setq interactive-switches (cons args interactive-switches))
  1309. nil))
  1310. (fn-symbol (python-django-qmgmt--make-fn-symbol name))
  1311. (callback (intern (format "%s-callback" fn-symbol)))
  1312. (command (car (split-string (format "%s" name) "-")))
  1313. (binding (plist-get args :binding))
  1314. (capture-output (plist-get args :capture-output))
  1315. (functions (plist-get args :functions))
  1316. (no-pop (plist-get args :no-pop))
  1317. (submenu (plist-get args :submenu))
  1318. (switches (plist-get args :switches))
  1319. (make-comint-function (plist-get args :make-comint-function))
  1320. (defargs (mapcar 'car interactive-switches))
  1321. ;; Abnormal hooks AKA functions
  1322. (functions-symbol (python-django-qmgmt--make-functions-symbol name))
  1323. (functions-docstring
  1324. (python-django-qmgmt--make-functions-docstring fn-symbol)))
  1325. `(progn
  1326. (defvar ,functions-symbol nil
  1327. ,functions-docstring)
  1328. (setq ,functions-symbol ,functions)
  1329. (defun ,fn-symbol ,defargs
  1330. ,(python-django-qmgmt--make-docstring
  1331. command switches interactive-switches docstring)
  1332. (interactive
  1333. (list ,@(python-django-qmgmt--make-interactive-form
  1334. interactive-switches)))
  1335. (setq python-django-mgmt--previous-window-configuration
  1336. (current-window-configuration))
  1337. (let* ((cmd-args (concat ,switches (and ,switches " ")
  1338. (mapconcat #'symbol-value ',defargs " ")))
  1339. (process (get-buffer-process
  1340. (python-django-mgmt-run-command
  1341. ,command cmd-args
  1342. ,capture-output ,no-pop
  1343. ,make-comint-function))))
  1344. (message "Running: ./manage.py %s %s" ,command cmd-args)
  1345. (set-process-sentinel
  1346. process
  1347. (lambda (process status)
  1348. (when (and (not (process-live-p process))
  1349. (buffer-live-p (process-buffer process)))
  1350. (with-current-buffer (process-buffer process)
  1351. (run-hook-with-args
  1352. ',functions-symbol
  1353. (string= status "finished\n")
  1354. (let ((plist ',args))
  1355. (plist-put plist :command ,command)
  1356. (mapc
  1357. #'(lambda (sym)
  1358. (message "[%s]" sym)
  1359. (let* ((cmd-switch (symbol-value sym))
  1360. (value
  1361. (cond
  1362. ((string-match "^--" cmd-switch)
  1363. (substring
  1364. cmd-switch
  1365. (1+ (string-match "=" cmd-switch))))
  1366. ((string-match "^-" cmd-switch)
  1367. (substring cmd-switch 3))
  1368. (t cmd-switch))))
  1369. (plist-put plist sym value)))
  1370. ',defargs)
  1371. plist))))))))
  1372. (python-django-qmgmt--add-binding #',fn-symbol ,binding)
  1373. (python-django-qmgmt--add-menu-item
  1374. #',fn-symbol ,submenu ,command ,switches ',interactive-switches ,docstring)
  1375. ;; Just like defun, return the defined function.
  1376. #',fn-symbol)))
  1377. (defun python-django-qmgmt-kill-and-msg-function (status-ok args)
  1378. "Kill the process buffer and show message or output.
  1379. Argument STATUS-OK is non-nil if process exited successfully.
  1380. Argument ARGS is a plist with the switches and arguments passed
  1381. to the command. See `python-django-qmgmt-define' docstring for
  1382. details."
  1383. (when status-ok
  1384. (let ((msg (or (plist-get args :msg)
  1385. (buffer-substring-no-properties
  1386. (point-min) (point-max))))
  1387. (buffer-name (buffer-name)))
  1388. (kill-buffer)
  1389. (python-django-mgmt-restore-window-configuration)
  1390. (display-message-or-buffer msg buffer-name))))
  1391. (python-django-qmgmt-define collectstatic
  1392. "Collect static files."
  1393. (:submenu "Tools" :binding "ocs"
  1394. :functions #'python-django-qmgmt-kill-and-msg-function))
  1395. (python-django-qmgmt-define clean_pyc
  1396. "Remove all python compiled files from the project."
  1397. (:submenu "Tools" :binding "ocp" :no-pop t
  1398. :msg "All *.pyc and *.pyo cleaned."
  1399. :functions #'python-django-qmgmt-kill-and-msg-function))
  1400. (defun python-django-qmgmt-create_command-function (status-ok args)
  1401. "Callback for create_command quick management command.
  1402. Argument STATUS-OK is non-nil if process exited successfully.
  1403. Argument ARGS is a plist with the switches and arguments passed
  1404. to the command. See `python-django-qmgmt-define' docstring for
  1405. details."
  1406. (when status-ok
  1407. (let* ((appname (plist-get args 'app))
  1408. (manage-directory
  1409. (file-name-directory
  1410. (with-current-buffer
  1411. python-django-mgmt-parent-buffer
  1412. python-django-project-manage.py)))
  1413. (default-app-dir
  1414. (expand-file-name appname manage-directory))
  1415. (default-create-dir
  1416. (expand-file-name "management" default-app-dir))
  1417. (delete-safe
  1418. (and (file-exists-p default-app-dir)
  1419. (equal (directory-files default-app-dir)
  1420. '("." ".." "management")))))
  1421. ;; TODO: cleanup. Do not offer moving, the command does the right thing.
  1422. (when (y-or-n-p
  1423. (format "Created in app %s. Move it? " default-app-dir))
  1424. (let ((newdir
  1425. (read-directory-name
  1426. "Move app to: " manage-directory nil t)))
  1427. (if (not (file-exists-p
  1428. (expand-file-name "management" newdir)))
  1429. (rename-file default-create-dir newdir)
  1430. (message
  1431. "Directory structure already exists in %s" appname))
  1432. (and delete-safe (delete-directory default-app-dir t)))))
  1433. (kill-buffer)
  1434. (python-django-mgmt-restore-window-configuration)))
  1435. (python-django-qmgmt-define create_command
  1436. "Create management commands directory structure for app."
  1437. (:submenu "Tools" :binding "occ" :no-pop t
  1438. :functions #'python-django-qmgmt-create_command-function)
  1439. (app (python-django-minibuffer-read-app "App name: ")))
  1440. (defun python-django-qmgmt-startapp-function (status-ok args)
  1441. "Callback for clean_pyc quick management command.
  1442. Argument STATUS-OK is non-nil if process exited successfully.
  1443. Argument ARGS is a plist with the switches and arguments passed
  1444. to the command. See `python-django-qmgmt-define' docstring for
  1445. details."
  1446. (when status-ok
  1447. (let ((appname (plist-get args 'app))
  1448. (manage-directory
  1449. (file-name-directory
  1450. (with-current-buffer
  1451. python-django-mgmt-parent-buffer
  1452. python-django-project-manage.py))))
  1453. (when (y-or-n-p
  1454. (format
  1455. "App created in %s. Do you want to move it? "
  1456. manage-directory))
  1457. (rename-file
  1458. (expand-file-name appname manage-directory)
  1459. (read-directory-name
  1460. "Move app to: " manage-directory nil t))))
  1461. (kill-buffer)
  1462. (python-django-mgmt-restore-window-configuration)))
  1463. (python-django-qmgmt-define startapp
  1464. "Create new Django app for current project."
  1465. (:submenu "Tools" :binding "osa" :no-pop t
  1466. :functions #'python-django-qmgmt-startapp-function)
  1467. (app "App name: "))
  1468. ;; Shell
  1469. (python-django-qmgmt-define shell
  1470. "Run a Python interpreter for this project."
  1471. (:submenu "Shell" :binding "ss"
  1472. :make-comint-function #'python-django-mgmt-make-comint-for-shell))
  1473. (python-django-qmgmt-define shell_plus
  1474. "Like the 'shell' but autoloads all models."
  1475. (:submenu "Shell" :binding "sp"
  1476. :make-comint-function #'python-django-mgmt-make-comint-for-shell))
  1477. ;; Database
  1478. (python-django-qmgmt-define syncdb
  1479. "Sync database tables for all INSTALLED_APPS."
  1480. (:submenu "Database" :binding "dsy" :no-pop t
  1481. :functions #'python-django-qmgmt-kill-and-msg-function)
  1482. (database (python-django-minibuffer-read-database "Database: " default)
  1483. "default" "--database="))
  1484. (python-django-qmgmt-define dbshell
  1485. "Run the command-line client for specified database."
  1486. (:submenu "Database" :binding "dsh"
  1487. :make-comint-function #'python-django-mgmt-make-comint-for-dbshell)
  1488. (database (python-django-minibuffer-read-database "Database: " default)
  1489. "default" "--database="))
  1490. (defvar python-django-qmgmt-dumpdata-formats '("json" "xml" "yaml")
  1491. "Valid formats for dumpdata management command.")
  1492. (defcustom python-django-qmgmt-dumpdata-default-format "json"
  1493. "Default format for quick dumpdata."
  1494. :group 'python-django
  1495. :type `(choice
  1496. ,@(mapcar (lambda (fmt)
  1497. `(string :tag ,fmt ,fmt))
  1498. python-django-qmgmt-dumpdata-formats))
  1499. :safe 'stringp)
  1500. (defcustom python-django-qmgmt-dumpdata-default-indent 4
  1501. "Default indent value quick dumpdata."
  1502. :group 'python-django
  1503. :type 'integer
  1504. :safe 'integerp)
  1505. (defun python-django-qmgmt-dumpdata-function (status-ok args)
  1506. "Callback executed after dumpdata finishes.
  1507. Argument STATUS-OK is non-nil if process exited successfully.
  1508. Argument ARGS is a plist with the switches and arguments passed
  1509. to the command. See `python-django-qmgmt-define' docstring for
  1510. details."
  1511. (when status-ok
  1512. (let ((file-name
  1513. (catch 'file-name
  1514. (while t
  1515. (let ((file-name
  1516. (read-file-name
  1517. "Save fixture to file: "
  1518. (expand-file-name
  1519. (with-current-buffer
  1520. python-django-mgmt-parent-buffer
  1521. python-django-project-root)) nil nil nil)))
  1522. (if (not (file-exists-p file-name))
  1523. (throw 'file-name file-name)
  1524. (when (y-or-n-p
  1525. (format "File `%s' exists; overwrite? " file-name))
  1526. (throw 'file-name file-name)))))))
  1527. (output-buffer (buffer-substring-no-properties
  1528. (point-min) (point-max))))
  1529. (with-temp-buffer
  1530. (set (make-local-variable 'require-final-newline) t)
  1531. (insert output-buffer)
  1532. ;; Ensure there's a final newline
  1533. (and (> (point-max) (point-min))
  1534. (not (= (char-after (1- (point-max))) ?\n))
  1535. (insert "\n"))
  1536. (write-region
  1537. (progn
  1538. ;; Remove possible logs from output.
  1539. (goto-char (point-min))
  1540. (re-search-forward
  1541. "^\\[\\|^<\\?xml +version=\"\\|^- +fields: " nil t)
  1542. (beginning-of-line 1)
  1543. (point))
  1544. (point-max)
  1545. file-name))
  1546. (kill-buffer)
  1547. (python-django-mgmt-restore-window-configuration)
  1548. (message "Fixture saved to file `%s'." file-name))))
  1549. (python-django-qmgmt-define dumpdata-all
  1550. "Save the contents of the database as a fixture for all apps."
  1551. (:submenu "Database" :binding "ddp" :no-pop t :capture-output t
  1552. :functions #'python-django-qmgmt-dumpdata-function)
  1553. (database (python-django-minibuffer-read-database "Database: " default)
  1554. "default" "--database=")
  1555. (indent (number-to-string
  1556. (read-number "Indent Level: "
  1557. (string-to-number default)))
  1558. (number-to-string python-django-qmgmt-dumpdata-default-indent)
  1559. "--indent=")
  1560. (format (python-django-minibuffer-read-from-list
  1561. "Dump to format: " python-django-qmgmt-dumpdata-formats default)
  1562. "json" "--format="))
  1563. (python-django-qmgmt-define dumpdata-app
  1564. "Save the contents of the database as a fixture for the specified app."
  1565. (:submenu "Database" :binding "dda" :no-pop t :capture-output t
  1566. :functions #'python-django-qmgmt-dumpdata-function)
  1567. (database (python-django-minibuffer-read-database "Database: " default)
  1568. "default" "--database=")
  1569. (indent (number-to-string
  1570. (read-number "Indent Level: "
  1571. (string-to-number default))) "4" "--indent=")
  1572. (format (python-django-minibuffer-read-from-list
  1573. "Dump to format: " python-django-qmgmt-dumpdata-formats default)
  1574. "json" "--format=")
  1575. (app (python-django-minibuffer-read-app "Dumpdata for App: ")))
  1576. (python-django-qmgmt-define flush
  1577. "Execute 'sqlflush' on the given database."
  1578. (:submenu "Database" :binding "df" :msg "Flushed database"
  1579. :functions #'python-django-qmgmt-kill-and-msg-function)
  1580. (database (python-django-minibuffer-read-database "Database: " default)
  1581. "default" "--database="))
  1582. (python-django-qmgmt-define loaddata
  1583. "Install the named fixture(s) in the database."
  1584. (:submenu "Database" :binding "dl"
  1585. :functions #'python-django-qmgmt-kill-and-msg-function)
  1586. (database (python-django-minibuffer-read-database "Database: " default)
  1587. "default" "--database=")
  1588. (fixtures (python-django-minibuffer-read-file-names "Fixtures: ")))
  1589. (python-django-qmgmt-define validate
  1590. "Validate all installed models."
  1591. (:submenu "Database" :binding "dv"
  1592. :functions #'python-django-qmgmt-kill-and-msg-function))
  1593. (defun python-django-qmgmt-graph_models-function (status-ok args)
  1594. "Callback for graph_model quick management command.
  1595. Argument STATUS-OK is non-nil if process exited successfully.
  1596. Argument ARGS is a plist with the switches and arguments passed
  1597. to the command. See `python-django-qmgmt-define' docstring for
  1598. details."
  1599. (when status-ok
  1600. (let ((open (y-or-n-p "Open generated graph? ")))
  1601. (kill-buffer)
  1602. (python-django-mgmt-restore-window-configuration)
  1603. (when open
  1604. (find-file (plist-get args 'filename))))))
  1605. (python-django-qmgmt-define graph_models-all
  1606. "Creates a Graph of models for all project apps."
  1607. (:submenu "Database" :switches "-ag" :binding "dgg"
  1608. :functions #'python-django-qmgmt-graph_models-function)
  1609. (filename
  1610. (expand-file-name
  1611. (read-file-name "Filename for generated Graph: "
  1612. default default))
  1613. (expand-file-name
  1614. "graph_all.png" python-django-project-root)
  1615. "--output=" t))
  1616. (python-django-qmgmt-define graph_models-apps
  1617. "Creates a Graph of models for given apps."
  1618. (:submenu "Database" :binding "dga"
  1619. :functions #'python-django-qmgmt-graph_models-function)
  1620. (apps (python-django-minibuffer-read-apps "Graph for App: "))
  1621. (filename
  1622. (expand-file-name
  1623. (read-file-name "Filename for generated Graph: " default default))
  1624. (expand-file-name
  1625. (format "graph_%s.png" (replace-regexp-in-string " " "_" apps))
  1626. python-django-project-root)
  1627. "--output=" t))
  1628. ;; i18n
  1629. (python-django-qmgmt-define makemessages-all
  1630. "Create/Update translation string files."
  1631. (:submenu "i18n" :switches "--all" :binding "im"
  1632. :functions #'python-django-qmgmt-kill-and-msg-function))
  1633. (python-django-qmgmt-define compilemessages-all
  1634. "Compile project .po files to .mo."
  1635. (:submenu "i18n" :binding "ic"
  1636. :functions #'python-django-qmgmt-kill-and-msg-function))
  1637. ;; Dev Server
  1638. (defcustom python-django-qmgmt-runserver-default-bindaddr "localhost:8000"
  1639. "Default binding address for quick runserver."
  1640. :group 'python-django
  1641. :type 'string
  1642. :safe 'stringp)
  1643. (defcustom python-django-qmgmt-testserver-default-bindaddr "localhost:8000"
  1644. "Default binding address for quick testserver."
  1645. :group 'python-django
  1646. :type 'string
  1647. :safe 'stringp)
  1648. (defcustom python-django-qmgmt-mail_debug-default-bindaddr "localhost:1025"
  1649. "Default binding address for quick mail_debug."
  1650. :group 'python-django
  1651. :type 'string
  1652. :safe 'stringp)
  1653. (python-django-qmgmt-define runserver
  1654. "Start development Web server."
  1655. (:submenu "Server" :binding "rr"
  1656. :make-comint-function #'python-django-mgmt-make-comint-for-runserver)
  1657. (bindaddr "Serve on [ip]:[port]: "
  1658. python-django-qmgmt-runserver-default-bindaddr))
  1659. (python-django-qmgmt-define runserver_plus
  1660. "Start extended development Web server."
  1661. (:submenu "Server" :binding "rp"
  1662. :make-comint-function #'python-django-mgmt-make-comint-for-runserver)
  1663. (bindaddr "Serve on [ip]:[port]: "
  1664. python-django-qmgmt-runserver-default-bindaddr))
  1665. (python-django-qmgmt-define testserver
  1666. "Start development server with data from the given fixture(s)."
  1667. (:submenu "Server" :binding "rt")
  1668. (bindaddr "Serve on [ip]:[port]: "
  1669. python-django-qmgmt-testserver-default-bindaddr)
  1670. (fixtures (python-django-minibuffer-read-file-names "Fixtures: ")))
  1671. (python-django-qmgmt-define mail_debug
  1672. "Start a test mail server for development."
  1673. (:submenu "Server" :binding "rm")
  1674. (bindaddr "Serve on [ip]:[port]: "
  1675. python-django-qmgmt-mail_debug-default-bindaddr))
  1676. ;; Testing
  1677. (python-django-qmgmt-define test-all
  1678. "Run the test suite for the entire project."
  1679. (:submenu "Test" :binding "tp"
  1680. :functions #'python-django-qmgmt-kill-and-msg-function))
  1681. (python-django-qmgmt-define test-app
  1682. "Run the test suite for the specified app."
  1683. (:submenu "Test" :binding "ta"
  1684. :functions #'python-django-qmgmt-kill-and-msg-function)
  1685. (app (python-django-minibuffer-read-app "Test App: ")))
  1686. ;; South integration
  1687. (defun python-django-qmgmt-open-migration-function (status-ok args)
  1688. "Callback for commands that create migrations.
  1689. Argument STATUS-OK is non-nil if process exited successfully.
  1690. Argument ARGS is a plist with the switches and arguments passed
  1691. to the command. See `python-django-qmgmt-define' docstring for
  1692. details."
  1693. (when status-ok
  1694. (let ((app (cdr (assq 'app args))))
  1695. (python-django-qmgmt-kill-and-msg-function status-ok args)
  1696. (and (y-or-n-p "Open the created migration? ")
  1697. (find-file
  1698. (expand-file-name
  1699. (car (last (python-django-info-get-app-migrations app)))
  1700. (expand-file-name
  1701. "migrations"
  1702. (python-django-info-get-app-path app))))))))
  1703. (python-django-qmgmt-define convert_to_south
  1704. "Convert given app to South."
  1705. (:submenu "South" :binding "soc"
  1706. :functions #'python-django-qmgmt-kill-and-msg-function)
  1707. (app (python-django-minibuffer-read-app "Convert App: ")))
  1708. (python-django-qmgmt-define datamigration
  1709. "Create a new datamigration for the given app."
  1710. (:submenu "South" :binding "sod"
  1711. :functions #'python-django-qmgmt-open-migration-function)
  1712. (app (python-django-minibuffer-read-app "Datamigration for App: "))
  1713. (name "Datamigration name: "))
  1714. (python-django-qmgmt-define migrate-all
  1715. "Run all migrations for all apps."
  1716. (:submenu "South" :switches "--all" :binding "somp"
  1717. :functions #'python-django-qmgmt-kill-and-msg-function)
  1718. (database (python-django-minibuffer-read-database "Database: " default)
  1719. "default" "--database="))
  1720. (python-django-qmgmt-define migrate-app
  1721. "Run all migrations for given app."
  1722. (:submenu "South" :binding "soma"
  1723. :functions #'python-django-qmgmt-kill-and-msg-function)
  1724. (database (python-django-minibuffer-read-database "Database: " default)
  1725. "default" "--database=")
  1726. (app (python-django-minibuffer-read-app "Migrate App: ")))
  1727. (python-django-qmgmt-define migrate-list
  1728. "Run all migrations for all apps."
  1729. (:submenu "South" :switches "--list" :binding "soml"
  1730. :functions #'python-django-qmgmt-kill-and-msg-function)
  1731. (database (python-django-minibuffer-read-database "Database: " default)
  1732. "default" "--database="))
  1733. (python-django-qmgmt-define migrate-app-to
  1734. "Run migrations for given app [up|down]-to given number."
  1735. (:submenu "South" :binding "somt"
  1736. :functions #'python-django-qmgmt-kill-and-msg-function)
  1737. (database (python-django-minibuffer-read-database "Database: " default)
  1738. "default" "--database=")
  1739. (app (python-django-minibuffer-read-app "Migrate App: "))
  1740. (migration (python-django-minibuffer-read-migration "To migration: " app)))
  1741. (python-django-qmgmt-define schemamigration-initial
  1742. "Create the initial schemamigration for the given app."
  1743. (:submenu "South" :switches "--initial" :binding "sosi"
  1744. :functions #'python-django-qmgmt-kill-and-msg-function)
  1745. (app (python-django-minibuffer-read-app
  1746. "Initial schemamigration for App: ")))
  1747. (python-django-qmgmt-define schemamigration
  1748. "Create new empty schemamigration for the given app."
  1749. (:submenu "South" :switches "--empty" :binding "soss"
  1750. :functions #'python-django-qmgmt-open-migration-function)
  1751. (app (python-django-minibuffer-read-app
  1752. "Initial schemamigration for App: "))
  1753. (name "Schemamigration name: "))
  1754. (python-django-qmgmt-define schemamigration-auto
  1755. "Create an automatic schemamigration for the given app."
  1756. (:submenu "South" :switches "--auto" :binding "sosa"
  1757. :functions #'python-django-qmgmt-open-migration-function)
  1758. (app (python-django-minibuffer-read-app
  1759. "Auto schemamigration for App: ")))
  1760. ;;; Fast commands
  1761. (defcustom python-django-cmd-etags-command
  1762. "etags `find -name \"*.py\"`"
  1763. "Command used to build tags tables."
  1764. :group 'python-django
  1765. :type 'string)
  1766. (defcustom python-django-cmd-grep-function nil
  1767. "Function to grep on a directory.
  1768. The function receives no args, however `default-directory' will
  1769. default to a sane value."
  1770. :group 'python-django
  1771. :type 'function)
  1772. (defun python-django-cmd-build-etags ()
  1773. "Build tags for current project."
  1774. (interactive)
  1775. (let ((current-dir default-directory))
  1776. (cd
  1777. (file-name-directory
  1778. python-django-project-manage.py))
  1779. (if (eq 0
  1780. (shell-command
  1781. python-django-cmd-etags-command))
  1782. (message "Tags created sucessfully")
  1783. (message "Tags creation failed"))
  1784. (cd current-dir)))
  1785. (defun python-django-cmd-grep ()
  1786. "Grep in project directories."
  1787. (interactive)
  1788. (let ((default-directory
  1789. (or (python-django-ui-directory-at-point)
  1790. (file-name-directory
  1791. python-django-project-manage.py))))
  1792. (if (not python-django-cmd-grep-function)
  1793. (call-interactively #'rgrep)
  1794. (funcall
  1795. python-django-cmd-grep-function default-directory))))
  1796. (defun python-django-cmd-open-docs ()
  1797. "Open Django documentation in a browser."
  1798. (interactive)
  1799. (browse-url
  1800. (format
  1801. "https://docs.djangoproject.com/en/%s/"
  1802. (substring (python-django-info-get-version) 0 3))))
  1803. (defun python-django-cmd-visit-settings ()
  1804. "Visit settings file."
  1805. (interactive)
  1806. (find-file (python-django-info-module-path
  1807. python-django-project-settings)))
  1808. (defun python-django-cmd-visit-virtualenv ()
  1809. "Visit virtualenv directory."
  1810. (interactive)
  1811. (and python-shell-virtualenv-path
  1812. (dired python-shell-virtualenv-path)))
  1813. (defun python-django-cmd-visit-project-root ()
  1814. "Visit project root directory."
  1815. (interactive)
  1816. (dired python-django-project-root))
  1817. (defun python-django-cmd-dired-at-point ()
  1818. "Open dired at current tree node."
  1819. (interactive)
  1820. (let ((dir (python-django-ui-directory-at-point)))
  1821. (and dir (dired dir))))
  1822. (defun python-django-cmd-directory-at-point ()
  1823. "Message the current directory at point."
  1824. (interactive)
  1825. (message (or (python-django-ui-directory-at-point) "")))
  1826. (defun python-django-cmd-jump-to-app (app)
  1827. "Jump to APP's directory."
  1828. (interactive
  1829. (list
  1830. (python-django-minibuffer-read-app "Jump to app: ")))
  1831. (when (python-django-info-get-app-path app)
  1832. (goto-char (point-min))
  1833. (re-search-forward (format " %s" app))
  1834. (python-django-ui-move-to-closest-icon)))
  1835. (defun python-django-cmd-jump-to-media (which)
  1836. "Jump to a WHICH media directory."
  1837. (interactive
  1838. (list
  1839. (python-django-minibuffer-read-from-list
  1840. "Jump to: " '("MEDIA_ROOT" "STATIC_ROOT"))))
  1841. (goto-char (point-min))
  1842. (re-search-forward (format " %s" which))
  1843. (python-django-ui-move-to-closest-icon))
  1844. (defun python-django-cmd-jump-to-template-dir (which)
  1845. "Jump to a WHICH template directory."
  1846. (interactive
  1847. (list
  1848. (python-django-minibuffer-read-from-list
  1849. "Jump to: "
  1850. (mapcar 'identity (python-django-info-get-setting "TEMPLATE_DIRS")))))
  1851. (goto-char (point-min))
  1852. (re-search-forward (format " %s" which))
  1853. (python-django-ui-move-to-closest-icon))
  1854. ;;; UI stuff
  1855. (defvar python-django-ui-ignored-dirs
  1856. '("." ".." ".bzr" ".cdv" "~.dep" "~.dot" "~.nib" "~.plst" ".git" ".hg" ".pc"
  1857. ".svn" "_MTN" "blib" "CVS" "RCS" "SCCS" "_darcs" "_sgbak" "autom4te.cache"
  1858. "cover_db" "_build" ".ropeproject" "__pycache__")
  1859. "Directories ignored when scanning project files.")
  1860. (defvar python-django-ui-allowed-extensions
  1861. '("css" "gif" "htm" "html" "jpg" "js" "json" "mo" "png" "po" "py" "txt" "xml"
  1862. "yaml" "scss" "less")
  1863. "Allowed extensions when scanning project files.")
  1864. (defcustom python-django-ui-image-enable t
  1865. "Enable images for widgets?"
  1866. :group 'python-django
  1867. :type 'boolean
  1868. :safe 'booleanp)
  1869. (defcustom python-django-ui-theme "folder"
  1870. "Default theme for widgets."
  1871. :group 'python-django
  1872. :type 'boolean
  1873. :safe 'stringp)
  1874. (defcustom python-django-ui-buffer-switch-function 'switch-to-buffer
  1875. "Function for switching to the project buffer.
  1876. The function receives one argument, the status buffer."
  1877. :group 'python-django
  1878. :type '(radio (function-item switch-to-buffer)
  1879. (function-item pop-to-buffer)
  1880. (function-item display-buffer)
  1881. (function :tag "Other")))
  1882. (defun python-django-ui-show-buffer (buffer)
  1883. "Show the Project BUFFER."
  1884. (funcall python-django-ui-buffer-switch-function buffer))
  1885. (defun python-django-ui-clean ()
  1886. "Empty current UI buffer."
  1887. (let ((inhibit-read-only t))
  1888. (erase-buffer)))
  1889. (defun python-django-ui-insert-header ()
  1890. "Draw header information."
  1891. (insert
  1892. (format "%s\t\t%s\n"
  1893. (propertize
  1894. "Django Version:"
  1895. 'face 'python-django-face-title)
  1896. (propertize
  1897. (python-django-info-get-version)
  1898. 'face 'python-django-face-django-version))
  1899. (format "%s\t\t%s\n"
  1900. (propertize
  1901. "Project:"
  1902. 'face 'python-django-face-title)
  1903. (propertize
  1904. python-django-project-root
  1905. 'face 'python-django-face-project-root))
  1906. (format "%s\t\t%s\n"
  1907. (propertize
  1908. "Settings:"
  1909. 'face 'python-django-face-title)
  1910. (propertize
  1911. python-django-project-settings
  1912. 'face 'python-django-face-settings-module))
  1913. (format "%s\t\t%s"
  1914. (propertize
  1915. "Virtualenv:"
  1916. 'face 'python-django-face-title)
  1917. (propertize
  1918. (or python-shell-virtualenv-path "None")
  1919. 'face 'python-django-face-virtualenv-path))
  1920. "\n\n\n"))
  1921. (defun python-django-ui-build-section-alist ()
  1922. "Create section Alist for current project."
  1923. (list
  1924. (cons
  1925. "Apps"
  1926. (mapcar
  1927. (lambda (app)
  1928. (cons app (python-django-info-get-app-path app)))
  1929. (python-django-info-get-installed-apps)))
  1930. (cons
  1931. "Media"
  1932. (list
  1933. (cons "MEDIA_ROOT" (python-django-info-get-setting "MEDIA_ROOT"))
  1934. (cons "STATIC_ROOT" (python-django-info-get-setting "STATIC_ROOT"))))
  1935. (cons
  1936. "Static Content" (mapcar
  1937. (lambda (dir)
  1938. ;; STATICFILES_DIRS elements can be either a
  1939. ;; string or a size-two tuple with the first
  1940. ;; element being the prefix and the latter
  1941. ;; being the path: http://bit.ly/16Fw9xW
  1942. (if (stringp dir)
  1943. (cons dir dir)
  1944. (cons (aref dir 0) (aref dir 1))))
  1945. (python-django-info-get-setting "STATICFILES_DIRS")))
  1946. (cons
  1947. "Templates" (mapcar
  1948. (lambda (dir)
  1949. (cons dir dir))
  1950. (python-django-info-get-setting "TEMPLATE_DIRS")))))
  1951. ;; Many kudos to Ye Wenbin since dirtree.el was of great help when
  1952. ;; looking for examples of `tree-widget':
  1953. ;; https://github.com/zkim/emacs-dirtree/blob/master/
  1954. (define-widget 'python-django-ui-tree-section-widget 'tree-widget
  1955. "Tree widget for sections of Django Project buffer."
  1956. :expander 'python-django-ui-tree-section-widget-expand
  1957. :help-echo 'ignore
  1958. :has-children t)
  1959. (define-widget 'python-django-ui-tree-section-node-widget 'push-button
  1960. "Widget for a nodes of `python-django-ui-tree-section-widget'."
  1961. :format "%[%t%]\n"
  1962. :button-face 'default
  1963. :notify 'python-django-ui-tree-section-widget-expand)
  1964. (define-widget 'python-django-ui-tree-dir-widget 'tree-widget
  1965. "Tree widget for directories of Django Project."
  1966. :expander 'python-django-ui-tree-dir-widget-expand
  1967. :help-echo 'ignore
  1968. :has-children t)
  1969. (define-widget 'python-django-ui-tree-file-widget 'push-button
  1970. "Widget for a files inside the `python-django-ui-tree-dir-widget'."
  1971. :format "%[%t%]\n"
  1972. :button-face 'default
  1973. :notify 'python-django-ui-tree-file-widget-select)
  1974. (defun python-django-ui-tree-section-widget-expand (tree &rest ignore)
  1975. "Expand directory for given section TREE widget.
  1976. Optional argument IGNORE is there for compatibility."
  1977. (or (widget-get tree :args)
  1978. (let ((section-alist (widget-get tree :section-alist)))
  1979. (mapcar (lambda (section)
  1980. (let ((name (car section))
  1981. (dir (cdr section)))
  1982. `(python-django-ui-tree-dir-widget
  1983. :node (python-django-ui-tree-file-widget
  1984. :tag ,name
  1985. :file ,dir)
  1986. :file ,dir
  1987. :open nil
  1988. :indent 0)))
  1989. section-alist))))
  1990. (defun python-django-ui-tree-dir-widget-expand (tree)
  1991. "Expand directory for given TREE widget."
  1992. (or (widget-get tree :args)
  1993. (let* ((dir (widget-get tree :file))
  1994. dir-list file-list)
  1995. (when (and dir (file-exists-p dir))
  1996. (dolist (file (directory-files dir t))
  1997. (let ((basename (file-name-nondirectory file)))
  1998. (if (file-directory-p file)
  1999. (when (not (member basename python-django-ui-ignored-dirs))
  2000. (setq dir-list (cons basename dir-list)))
  2001. (when (member (python-django-util-file-name-extension file)
  2002. python-django-ui-allowed-extensions)
  2003. (setq file-list (cons basename file-list))))))
  2004. (setq dir-list (sort dir-list 'string<))
  2005. (setq file-list (sort file-list 'string<))
  2006. (append
  2007. (mapcar (lambda (file)
  2008. `(python-django-ui-tree-dir-widget
  2009. :file ,(expand-file-name file dir)
  2010. :node (python-django-ui-tree-file-widget
  2011. :tag ,file
  2012. :file ,(expand-file-name file dir))))
  2013. dir-list)
  2014. (mapcar (lambda (file)
  2015. `(python-django-ui-tree-file-widget
  2016. :file ,(and file (not (string= file ""))
  2017. (expand-file-name file dir))
  2018. :tag ,file))
  2019. file-list))))))
  2020. (defun python-django-ui-tree-file-widget-select (node &rest ignore)
  2021. "Open file in other window.
  2022. Argument NODE and IGNORE are just for compatibility."
  2023. (let ((file (widget-get node :file)))
  2024. (and file (find-file-other-window file))))
  2025. (defun python-django-ui-tree-section-insert (name section-alist)
  2026. "Create tree widget for NAME and SECTION-ALIST."
  2027. (apply 'widget-create
  2028. `(python-django-ui-tree-section-widget
  2029. :node (python-django-ui-tree-section-node-widget
  2030. :tag ,name)
  2031. :section-alist ,section-alist
  2032. :open t)))
  2033. (defun python-django-ui-widget-move (arg)
  2034. "Move between widgets sensibly in the project buffer.
  2035. Movement between widgets of the tree happen line by line, leaving
  2036. point next to the closest icon available. With positive ARG move
  2037. forward that many times, else backwards."
  2038. (let* ((success-moves 0)
  2039. (forward (> arg 0))
  2040. (func (if forward
  2041. 'widget-forward
  2042. 'widget-backward))
  2043. (abs-arg (abs arg)))
  2044. (catch 'nowidget
  2045. (while (> abs-arg success-moves)
  2046. (if (memq (widget-type (widget-at (point)))
  2047. '(tree-widget-close-icon
  2048. tree-widget-empty-icon
  2049. tree-widget-leaf-icon
  2050. tree-widget-open-icon))
  2051. (ignore-errors (funcall func 2))
  2052. (ignore-errors (funcall func 1)))
  2053. (when (not (widget-at (point)))
  2054. (throw 'nowidget t))
  2055. (setq success-moves (1+ success-moves))))
  2056. (python-django-ui-move-to-closest-icon)
  2057. (setq default-directory
  2058. (or (python-django-ui-directory-at-point)
  2059. (file-name-directory python-django-project-manage.py)))))
  2060. (defun python-django-ui-widget-forward (arg)
  2061. "Move point to the next line's main widget.
  2062. With optional ARG, move across that many fields."
  2063. (interactive "p")
  2064. (python-django-ui-widget-move arg))
  2065. (defun python-django-ui-widget-backward (arg)
  2066. "Move point to the previous line's main widget.
  2067. With optional ARG, move across that many fields."
  2068. (interactive "p")
  2069. (python-django-ui-widget-move (- arg)))
  2070. (defun python-django-ui-move-up-tree (arg)
  2071. "Move point to the parent widget of the tree.
  2072. With optional ARG, move across that many fields."
  2073. (interactive "p")
  2074. (and (< arg 0) (setq arg (- arg)))
  2075. (python-django-ui-move-to-closest-icon)
  2076. (let ((start-depth (- (point) (line-beginning-position))))
  2077. (when (not (= 0 start-depth))
  2078. (while (<= start-depth (- (point) (line-beginning-position)))
  2079. (python-django-ui-widget-backward 1)))))
  2080. (defun python-django-ui-beginning-of-widgets ()
  2081. "Move to the first widget.
  2082. With optional ARG, move across that many fields."
  2083. (interactive)
  2084. (goto-char (point-min))
  2085. (python-django-ui-widget-forward 1))
  2086. (defun python-django-ui-end-of-widgets ()
  2087. "Move point to last widget.
  2088. With optional ARG, move across that many fields."
  2089. (interactive)
  2090. (goto-char (point-max))
  2091. (python-django-ui-widget-backward 1))
  2092. (defun python-django-ui-move-to-closest-icon ()
  2093. "Move to closest button from point."
  2094. (interactive)
  2095. (if (and
  2096. (not (widget-at (point)))
  2097. (not (widget-at (1- (point)))))
  2098. (progn
  2099. (widget-backward 1)
  2100. (beginning-of-line 1)
  2101. (widget-forward 1))
  2102. (beginning-of-line 1)
  2103. (and (not (widget-at (point)))
  2104. (widget-forward 1))))
  2105. (defun python-django-ui-safe-button-press ()
  2106. "Move to closest button from point and press it."
  2107. (interactive)
  2108. (and (not (widget-at (point)))
  2109. (python-django-ui-move-to-closest-icon))
  2110. (widget-button-press (point)))
  2111. (defun python-django-ui-widget-type-at-point ()
  2112. "Return the node type for current position."
  2113. (let* ((widget (widget-at (point)))
  2114. (file-p (widget-get
  2115. (tree-widget-node widget)
  2116. :tree-widget--guide-flags)))
  2117. (and widget (if file-p 'file 'dir))))
  2118. (defun python-django-ui-directory-at-point ()
  2119. "Return the node type for current position."
  2120. (widget-get
  2121. (widget-get (tree-widget-node (widget-at (point))) :parent) :file))
  2122. ;;;Main functions
  2123. (defcustom python-django-known-projects nil
  2124. "Alist of known projects."
  2125. :group 'python-django
  2126. :type '(repeat (list string string string))
  2127. :safe (lambda (val)
  2128. (and
  2129. (stringp (car val))
  2130. (stringp (nth 1 val))
  2131. (stringp (nth 2 val)))))
  2132. (defun python-django-mode-find-next-buffer ()
  2133. "Find the next Django project buffer available."
  2134. (let ((current-buffer (current-buffer)))
  2135. (catch 'buffer
  2136. (dolist (buf (buffer-list))
  2137. (and (with-current-buffer buf
  2138. (and (eq major-mode 'python-django-mode)
  2139. (not (equal buf current-buffer))))
  2140. (throw 'buffer buf))))))
  2141. (defun python-django-mode-on-kill-buffer ()
  2142. "Hook run on `buffer-kill-hook'."
  2143. (and (python-django-mgmt-buffer-list (current-buffer))
  2144. (call-interactively 'python-django-mgmt-kill-all)))
  2145. (define-derived-mode python-django-mode special-mode "Django"
  2146. "Major mode to manage Django projects.
  2147. \\{python-django-mode-map}")
  2148. ;;;###autoload
  2149. (defun python-django-open-project (directory settings &optional existing)
  2150. "Open a Django project at given DIRECTORY using SETTINGS.
  2151. Optional argument EXISTING is internal and should not be used.
  2152. The recommended way to chose your project root, is to use the
  2153. directory containing your settings module; for instance if your
  2154. settings module is in /path/django/settings.py, use /path/django/
  2155. as your project path and django.settings as your settings module.
  2156. When called with no `prefix-arg', this function will try to find
  2157. an opened project-buffer, if current buffer is already a project
  2158. buffer it will cycle to next opened project. If no project
  2159. buffers are found, then the user prompted for the project path
  2160. and settings module unless `python-django-project-root' and
  2161. `python-django-project-settings' are somehow set, normally via
  2162. directory local variables. If none of the above matched or the
  2163. function is called with one `prefix-arg' and there are projects
  2164. defined in the `python-django-known-projects' variable the user
  2165. is prompted for any of those known projects, if the variable
  2166. turns to be nil the user will be prompted for project-path and
  2167. settings module (the same happens when called with two or more
  2168. `prefix-arg')."
  2169. (interactive
  2170. (let ((buf
  2171. ;; Get an existing project buffer that's not the current.
  2172. (python-django-mode-find-next-buffer)))
  2173. (cond
  2174. ((and (not current-prefix-arg)
  2175. (not buf)
  2176. python-django-project-root
  2177. python-django-project-settings)
  2178. ;; There's no existing buffer but project variables are
  2179. ;; set, so use them to open the project.
  2180. (list python-django-project-root
  2181. python-django-project-settings
  2182. ;; if the user happens to be in the project buffer
  2183. ;; itself, do nothing.
  2184. (and (eq major-mode 'python-django-mode)
  2185. (current-buffer))))
  2186. ((and (not current-prefix-arg) buf)
  2187. ;; there's an existing buffer move/cycle to it.
  2188. (with-current-buffer buf
  2189. (list
  2190. python-django-project-root
  2191. python-django-project-settings
  2192. buf)))
  2193. ((or (and python-django-known-projects
  2194. (<= (prefix-numeric-value current-prefix-arg) 4)))
  2195. ;; When there are known projects and called at most with one
  2196. ;; prefix arg try opening a known project.
  2197. (cdr
  2198. (assoc
  2199. (python-django-minibuffer-read-from-list
  2200. "Project: " python-django-known-projects)
  2201. python-django-known-projects)))
  2202. (t
  2203. (let ((root))
  2204. ;; When called with two or more prefix arguments or all
  2205. ;; input methods failed.
  2206. (list
  2207. (setq root (read-directory-name
  2208. "Project Root: " python-django-project-root nil t))
  2209. (read-string
  2210. "Settings module: "
  2211. (or python-django-project-settings
  2212. (format "%s.settings"
  2213. (python-django-info-directory-basename root))))))))))
  2214. (if (not existing)
  2215. (let* ((directory (expand-file-name directory))
  2216. (project-name (python-django-info-directory-basename directory))
  2217. (buffer-name
  2218. (format "*Django: %s (%s)*"
  2219. project-name
  2220. (python-django-util-shorten-settings settings)))
  2221. (success t))
  2222. (with-current-buffer (get-buffer-create buffer-name)
  2223. (let ((inhibit-read-only t))
  2224. (python-django-mode)
  2225. (python-django-ui-clean)
  2226. (set (make-local-variable
  2227. 'python-django-info--get-setting-cache) nil)
  2228. (set (make-local-variable
  2229. 'python-django-info--get-version-cache) nil)
  2230. (set (make-local-variable
  2231. 'python-django-info--get-app-paths-cache) nil)
  2232. (set (make-local-variable
  2233. 'python-django-project-root) directory)
  2234. (set (make-local-variable
  2235. 'python-django-project-settings) settings)
  2236. (set (make-local-variable
  2237. 'python-django-project-name) project-name)
  2238. (set (make-local-variable
  2239. 'python-django-project-manage.py)
  2240. (python-django-info-find-manage.py directory))
  2241. (set (make-local-variable 'default-directory)
  2242. (file-name-directory
  2243. python-django-project-manage.py))
  2244. (python-django-util-clone-local-variables)
  2245. (set (make-local-variable 'tree-widget-image-enable)
  2246. python-django-ui-image-enable)
  2247. (tree-widget-set-theme python-django-ui-theme)
  2248. (condition-case err
  2249. (progn
  2250. (python-django-ui-insert-header)
  2251. (mapc (lambda (section)
  2252. (python-django-ui-tree-section-insert
  2253. (car section) (cdr section))
  2254. (insert "\n"))
  2255. (python-django-ui-build-section-alist)))
  2256. (user-error
  2257. (setq success nil)
  2258. (insert (error-message-string err))
  2259. (goto-char (point-min)))))
  2260. (when success
  2261. (add-hook 'kill-buffer-hook
  2262. #'python-django-mode-on-kill-buffer nil t)
  2263. (python-django-ui-beginning-of-widgets))
  2264. (python-django-ui-show-buffer (current-buffer))))
  2265. (python-django-ui-show-buffer existing)))
  2266. ;; Stolen from magit.
  2267. (defun python-django-close-project (&optional kill-buffer)
  2268. "Bury the buffer and delete its window.
  2269. With a prefix argument, KILL-BUFFER instead."
  2270. (interactive "P")
  2271. (quit-window kill-buffer (selected-window)))
  2272. (defun python-django-refresh-project ()
  2273. "Refresh Django project."
  2274. (interactive)
  2275. (python-django-open-project
  2276. python-django-project-root
  2277. python-django-project-settings))
  2278. (provide 'python-django)
  2279. ;; Local Variables:
  2280. ;; coding: utf-8
  2281. ;; indent-tabs-mode: nil
  2282. ;; End:
  2283. ;;; python-django.el ends here