Emacs personal configuration
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.

2529 line
97KB

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