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.
 
 
 
 
 
 

991 lines
30 KiB

  1. #!/usr/bin/env python2.7
  2. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  3. # MIT License. See license.txt
  4. from __future__ import unicode_literals
  5. import os
  6. import subprocess
  7. import frappe
  8. import json
  9. from frappe.utils import cint
  10. site_arg_optional = ['serve', 'build', 'watch', 'celery', 'resize_images', 'doctor', 'dump_queue_status', 'purge_all_tasks']
  11. def get_site(parsed_args):
  12. if not parsed_args.get("site") and os.path.exists(os.path.join(parsed_args["sites_path"], "currentsite.txt")):
  13. with open(os.path.join(parsed_args["sites_path"], "currentsite.txt"), "r") as sitefile:
  14. parsed_args["site"] = sitefile.read().strip()
  15. return parsed_args["site"]
  16. return parsed_args.get("site")
  17. def main():
  18. parsed_args = frappe._dict(vars(setup_parser()))
  19. fn = get_function(parsed_args)
  20. if parsed_args.get("sites_path"):
  21. parsed_args["sites_path"] = parsed_args["sites_path"][0]
  22. else:
  23. parsed_args["sites_path"] = os.environ.get("SITES_PATH", ".")
  24. sites_path = parsed_args.get("sites_path")
  25. if not parsed_args.get("make_app"):
  26. if parsed_args.get("site")=="all":
  27. for site in get_sites(parsed_args["sites_path"]):
  28. print "\nRunning", fn, "for", site
  29. print "-"*50
  30. args = parsed_args.copy()
  31. args["site"] = site
  32. frappe.init(site, sites_path=sites_path)
  33. ret = run(fn, args)
  34. if ret:
  35. # if there's a return value, it's an error, so quit
  36. return ret
  37. else:
  38. site = get_site(parsed_args)
  39. if fn not in site_arg_optional and not site:
  40. print 'site argument required'
  41. return 1
  42. elif site:
  43. frappe.init(site, sites_path=sites_path)
  44. else:
  45. # site argument optional
  46. frappe.init("", sites_path=sites_path)
  47. return run(fn, parsed_args)
  48. else:
  49. return run(fn, parsed_args)
  50. def cmd(fn):
  51. def new_fn(*args, **kwargs):
  52. import inspect
  53. fnargs, varargs, varkw, defaults = inspect.getargspec(fn)
  54. new_kwargs = {}
  55. for i, a in enumerate(fnargs):
  56. # should not pass an argument more than once
  57. if i >= len(args) and a in kwargs:
  58. new_kwargs[a] = kwargs.get(a)
  59. return fn(*args, **new_kwargs)
  60. return new_fn
  61. def run(fn, args):
  62. import cProfile, pstats, StringIO
  63. use_profiler = args.get("profile") and fn!="serve"
  64. if use_profiler:
  65. pr = cProfile.Profile()
  66. pr.enable()
  67. if isinstance(args.get(fn), (list, tuple)):
  68. out = globals().get(fn)(*args.get(fn), **args)
  69. else:
  70. out = globals().get(fn)(**args)
  71. if use_profiler:
  72. pr.disable()
  73. s = StringIO.StringIO()
  74. ps = pstats.Stats(pr, stream=s).sort_stats('tottime', 'ncalls')
  75. ps.print_stats()
  76. print s.getvalue()
  77. return out
  78. def get_function(args):
  79. for fn, val in args.items():
  80. if (val or isinstance(val, list)) and globals().get(fn):
  81. return fn
  82. def get_sites(sites_path=None):
  83. import os
  84. if not sites_path:
  85. sites_path = '.'
  86. return [site for site in os.listdir(sites_path)
  87. if os.path.isdir(os.path.join(sites_path, site))
  88. and not site in ('assets',)]
  89. def setup_parser():
  90. import argparse
  91. parser = argparse.ArgumentParser(description="Run frappe utility functions")
  92. setup_install(parser)
  93. setup_utilities(parser)
  94. setup_translation(parser)
  95. setup_test(parser)
  96. parser.add_argument("site", nargs="?")
  97. # common
  98. parser.add_argument("-f", "--force", default=False, action="store_true",
  99. help="Force execution where applicable (look for [-f] in help)")
  100. parser.add_argument("--all", default=False, action="store_true",
  101. help="Get all (where applicable)")
  102. parser.add_argument("--verbose", default=False, action="store_true",
  103. help="Show verbose output (where applicable)")
  104. parser.add_argument("--quiet", default=False, action="store_true",
  105. help="Do not show verbose output (where applicable)")
  106. parser.add_argument("--args", metavar="pass arguments", nargs="*",
  107. help="pass arguments to the method")
  108. return parser.parse_args()
  109. def setup_install(parser):
  110. parser.add_argument("--make_app", metavar=("DESTINATION", "APP-NAME"), nargs=2,
  111. help="Make a new application with boilerplate")
  112. parser.add_argument("--install", metavar="DB-NAME", nargs=1,
  113. help="Install a new db")
  114. parser.add_argument("--admin_password", metavar="ADMIN-PASSWD", help="Administrator password for site")
  115. parser.add_argument("--root_password", metavar="ROOT-PASSWD",
  116. help="MariaDB root password")
  117. parser.add_argument("--sites_path", metavar="SITES_PATH", nargs=1,
  118. help="path to directory with sites")
  119. parser.add_argument("--install_app", metavar="APP-NAME", nargs=1,
  120. help="Install a new app")
  121. parser.add_argument("--add_to_installed_apps", metavar="APP-NAME", nargs="*",
  122. help="Add these app(s) to Installed Apps")
  123. parser.add_argument("--remove_from_installed_apps", metavar="APP-NAME", nargs="*",
  124. help="Remove these app(s) from Installed Apps")
  125. parser.add_argument("--reinstall", default=False, action="store_true",
  126. help="Install a fresh app in db_name specified in conf.py")
  127. parser.add_argument("--restore", metavar=("DB-NAME", "SQL-FILE"), nargs=2,
  128. help="Restore from an sql file")
  129. parser.add_argument("--with_scheduler_enabled", default=False, action="store_true",
  130. help="Enable scheduler on restore")
  131. parser.add_argument("--add_system_manager", nargs="+",
  132. metavar=("EMAIL", "[FIRST-NAME] [LAST-NAME]"), help="Add a user with all roles")
  133. def setup_test(parser):
  134. parser.add_argument("--run_tests", default=False, action="store_true",
  135. help="Run tests options [-d doctype], [-m module]")
  136. parser.add_argument("--app", metavar="APP-NAME", nargs=1,
  137. help="Run command for specified app")
  138. parser.add_argument("-d", "--doctype", metavar="DOCTYPE", nargs=1,
  139. help="Run command for specified doctype")
  140. parser.add_argument("-m", "--module", metavar="MODULE", nargs=1,
  141. help="Run command for specified module")
  142. parser.add_argument("--tests", metavar="TEST FUNCTION", nargs="*",
  143. help="Run one or more specific test functions")
  144. parser.add_argument("--serve_test", action="store_true", help="Run development server for testing")
  145. parser.add_argument("--driver", nargs="?", help="Run selenium using given driver")
  146. def setup_utilities(parser):
  147. # serving
  148. parser.add_argument("--port", type=int, help="port for development server")
  149. parser.add_argument("--use", action="store_true", help="Set current site for development.")
  150. # update
  151. parser.add_argument("-u", "--update", nargs="*", metavar=("REMOTE", "BRANCH"),
  152. help="Perform git pull, run patches, sync schema and rebuild files/translations")
  153. parser.add_argument("--reload_gunicorn", default=False, action="store_true", help="reload gunicorn on update")
  154. parser.add_argument("--patch", nargs=1, metavar="PATCH-MODULE",
  155. help="Run a particular patch [-f]")
  156. parser.add_argument("-l", "--latest", default=False, action="store_true",
  157. help="Run patches, sync schema and rebuild files/translations")
  158. parser.add_argument("--sync_all", default=False, action="store_true",
  159. help="Reload all doctypes, pages, etc. using txt files [-f]")
  160. parser.add_argument("--update_all_sites", nargs="*", metavar=("REMOTE", "BRANCH"),
  161. help="Perform git pull, run patches, sync schema and rebuild files/translations")
  162. parser.add_argument("--reload_doc", nargs=3,
  163. metavar=('"MODULE"', '"DOCTYPE"', '"DOCNAME"'))
  164. # build
  165. parser.add_argument("-b", "--build", default=False, action="store_true",
  166. help="Minify + concatenate JS and CSS files, build translations")
  167. parser.add_argument("--make_copy", default=False, action="store_true",
  168. help="Make copy of assets instead of symlinks")
  169. parser.add_argument("-w", "--watch", default=False, action="store_true",
  170. help="Watch and concatenate JS and CSS files as and when they change")
  171. # misc
  172. parser.add_argument("--backup", default=False, action="store_true",
  173. help="Take backup of database in backup folder [--with_files]")
  174. parser.add_argument("--move", default=False, action="store_true",
  175. help="Move site to different directory defined by --dest_dir")
  176. parser.add_argument("--dest_dir", nargs=1, metavar="DEST-DIR",
  177. help="Move site to different directory")
  178. parser.add_argument("--with_files", default=False, action="store_true",
  179. help="Also take backup of files")
  180. parser.add_argument("--domain", nargs="*",
  181. help="Get or set domain in Website Settings")
  182. parser.add_argument("--make_conf", nargs="*", metavar=("DB-NAME", "DB-PASSWORD"),
  183. help="Create new conf.py file")
  184. parser.add_argument("--make_custom_server_script", nargs=1, metavar="DOCTYPE",
  185. help="Create new custome server script")
  186. parser.add_argument("--init_list", nargs=1, metavar="DOCTYPE",
  187. help="Create new list.js and list.html files")
  188. parser.add_argument("--set_admin_password", metavar='ADMIN-PASSWORD', nargs="*",
  189. help="Set administrator password")
  190. parser.add_argument("--request", metavar='URL-ARGS', nargs=1, help="Run request as admin")
  191. parser.add_argument("--mysql", action="store_true", help="get mysql shell for a site")
  192. parser.add_argument("--serve", action="store_true", help="Run development server")
  193. parser.add_argument("--profile", action="store_true", help="enable profiling in development server")
  194. parser.add_argument("--smtp", action="store_true", help="Run smtp debug server",
  195. dest="smtp_debug_server")
  196. parser.add_argument("--python", action="store_true", help="get python shell for a site")
  197. parser.add_argument("--ipython", action="store_true", help="get ipython shell for a site")
  198. parser.add_argument("--execute", help="execute a function", nargs=1, metavar="FUNCTION")
  199. parser.add_argument("--get_site_status", action="store_true", help="Get site details")
  200. parser.add_argument("--update_site_config", nargs=1,
  201. metavar="site-CONFIG-JSON",
  202. help="Update site_config.json for a given site")
  203. parser.add_argument("--resize_images", nargs=1, metavar="PATH", help="resize images to a max width of 500px")
  204. # clear
  205. parser.add_argument("--clear_web", default=False, action="store_true",
  206. help="Clear website cache")
  207. parser.add_argument("--build_website", default=False, action="store_true",
  208. help="Sync statics and clear cache")
  209. parser.add_argument("--sync_statics", default=False, action="store_true",
  210. help="Sync files from templates/statics to Web Pages")
  211. parser.add_argument("--clear_cache", default=False, action="store_true",
  212. help="Clear cache, doctype cache and defaults")
  213. parser.add_argument("--reset_perms", default=False, action="store_true",
  214. help="Reset permissions for all doctypes")
  215. parser.add_argument("--clear_all_sessions", default=False, action="store_true",
  216. help="Clear sessions of all users (logs them out)")
  217. # scheduler
  218. parser.add_argument("--run_scheduler", default=False, action="store_true",
  219. help="Trigger scheduler")
  220. parser.add_argument("--celery", nargs="*", help="Run Celery Commands")
  221. parser.add_argument("--run_scheduler_event", nargs=1,
  222. metavar="all | daily | weekly | monthly",
  223. help="Run a scheduler event")
  224. parser.add_argument("--enable_scheduler", default=False, action="store_true",
  225. help="Enable scheduler")
  226. parser.add_argument("--disable_scheduler", default=False, action="store_true",
  227. help="Disable scheduler")
  228. # replace
  229. parser.add_argument("--replace", nargs=3,
  230. metavar=("SEARCH-REGEX", "REPLACE-BY", "FILE-EXTN"),
  231. help="Multi-file search-replace [-f]")
  232. # import/export
  233. parser.add_argument("--export_doc", nargs=2, metavar=('"DOCTYPE"', '"DOCNAME"'))
  234. parser.add_argument("--export_doclist", nargs=3, metavar=("DOCTYPE", "NAME", "PATH"),
  235. help="""Export doclist as json to the given path, use '-' as name for Singles.""")
  236. parser.add_argument("--export_csv", nargs=2, metavar=("DOCTYPE", "PATH"),
  237. help="""Dump DocType as csv""")
  238. parser.add_argument("--export_fixtures", default=False, action="store_true",
  239. help="""Export fixtures""")
  240. parser.add_argument("--import_doc", nargs=1, metavar="PATH",
  241. help="""Import (insert/update) doclist. If the argument is a directory, all files ending with .json are imported""")
  242. # doctor
  243. parser.add_argument("--doctor", default=False, action="store_true",
  244. help="Print diagnostic information for task queues")
  245. parser.add_argument("--purge_all_tasks", default=False, action="store_true",
  246. help="Purge any pending periodic tasks of 'all' event. Doesn't purge hourly, daily and weekly")
  247. parser.add_argument("--dump_queue_status", default=False, action="store_true",
  248. help="Dump detailed diagnostic infomation for task queues in JSON format")
  249. def setup_translation(parser):
  250. parser.add_argument("--build_message_files", default=False, action="store_true",
  251. help="Build message files for translation.")
  252. parser.add_argument("--get_untranslated", nargs=2, metavar=("LANG-CODE", "TARGET-FILE-PATH"),
  253. help="""Get untranslated strings for lang.""")
  254. parser.add_argument("--update_translations", nargs=3,
  255. metavar=("LANG-CODE", "UNTRANSLATED-FILE-PATH", "TRANSLATED-FILE-PATH"),
  256. help="""Update translated strings.""")
  257. # methods
  258. @cmd
  259. def make_app(destination, app_name):
  260. from frappe.utils.boilerplate import make_boilerplate
  261. make_boilerplate(destination, app_name)
  262. @cmd
  263. def use(sites_path):
  264. with open(os.path.join(sites_path, "currentsite.txt"), "w") as sitefile:
  265. sitefile.write(frappe.local.site)
  266. # install
  267. def _install(db_name, root_login="root", root_password=None, source_sql=None,
  268. admin_password = 'admin', force=False, site_config=None, reinstall=False, quiet=False, install_apps=None):
  269. from frappe.installer import install_db, install_app, make_site_dirs
  270. import frappe.utils.scheduler
  271. verbose = not quiet
  272. # enable scheduler post install?
  273. enable_scheduler = _is_scheduler_enabled()
  274. install_db(root_login=root_login, root_password=root_password, db_name=db_name, source_sql=source_sql,
  275. admin_password = admin_password, verbose=verbose, force=force, site_config=site_config, reinstall=reinstall)
  276. make_site_dirs()
  277. install_app("frappe", verbose=verbose, set_as_patched=not source_sql)
  278. if frappe.conf.get("install_apps"):
  279. for app in frappe.conf.install_apps:
  280. install_app(app, verbose=verbose, set_as_patched=not source_sql)
  281. if install_apps:
  282. for app in install_apps:
  283. install_app(app, verbose=verbose, set_as_patched=not source_sql)
  284. frappe.utils.scheduler.toggle_scheduler(enable_scheduler)
  285. scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled"
  286. print "*** Scheduler is", scheduler_status, "***"
  287. @cmd
  288. def install(db_name, root_login="root", root_password=None, source_sql=None,
  289. admin_password=None, force=False, site_config=None, reinstall=False, quiet=False, install_apps=None):
  290. _install(db_name, root_login, root_password, source_sql, admin_password, force, site_config, reinstall, quiet, install_apps)
  291. frappe.destroy()
  292. def _is_scheduler_enabled():
  293. enable_scheduler = False
  294. try:
  295. frappe.connect()
  296. enable_scheduler = cint(frappe.db.get_default("enable_scheduler"))
  297. except:
  298. pass
  299. finally:
  300. frappe.db.close()
  301. return enable_scheduler
  302. @cmd
  303. def install_app(app_name, quiet=False):
  304. verbose = not quiet
  305. from frappe.installer import install_app
  306. frappe.connect()
  307. install_app(app_name, verbose=verbose)
  308. frappe.destroy()
  309. @cmd
  310. def add_to_installed_apps(*apps):
  311. from frappe.installer import add_to_installed_apps
  312. frappe.connect()
  313. all_apps = frappe.get_all_apps(with_frappe=True)
  314. for app in apps:
  315. if app in all_apps:
  316. add_to_installed_apps(app, rebuild_website=False)
  317. frappe.destroy()
  318. @cmd
  319. def remove_from_installed_apps(*apps):
  320. from frappe.installer import remove_from_installed_apps
  321. frappe.connect()
  322. for app in apps:
  323. remove_from_installed_apps(app)
  324. frappe.destroy()
  325. @cmd
  326. def reinstall(quiet=False):
  327. verbose = not quiet
  328. try:
  329. frappe.connect()
  330. installed = frappe.get_installed_apps()
  331. frappe.clear_cache()
  332. except:
  333. installed = []
  334. finally:
  335. frappe.db.close()
  336. install(db_name=frappe.conf.db_name, verbose=verbose, force=True, reinstall=True, install_apps=installed)
  337. @cmd
  338. def restore(db_name, source_sql, force=False, quiet=False, with_scheduler_enabled=False):
  339. import frappe.utils.scheduler
  340. _install(db_name, source_sql=source_sql, quiet=quiet, force=force)
  341. try:
  342. frappe.connect()
  343. frappe.utils.scheduler.toggle_scheduler(with_scheduler_enabled)
  344. scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled"
  345. print "*** Scheduler is", scheduler_status, "***"
  346. finally:
  347. frappe.destroy()
  348. @cmd
  349. def add_system_manager(email, first_name=None, last_name=None):
  350. import frappe.utils.user
  351. frappe.connect()
  352. frappe.utils.user.add_system_manager(email, first_name, last_name)
  353. frappe.db.commit()
  354. frappe.destroy()
  355. # utilities
  356. @cmd
  357. def update(remote=None, branch=None, reload_gunicorn=False):
  358. pull(remote=remote, branch=branch)
  359. # maybe there are new framework changes, any consequences?
  360. reload(frappe)
  361. build()
  362. latest()
  363. if reload_gunicorn:
  364. subprocess.check_output("killall -HUP gunicorn".split())
  365. @cmd
  366. def latest(rebuild_website=True, quiet=False):
  367. import frappe.modules.patch_handler
  368. import frappe.model.sync
  369. from frappe.utils.fixtures import sync_fixtures
  370. import frappe.translate
  371. from frappe.desk.notifications import clear_notifications
  372. verbose = not quiet
  373. frappe.connect()
  374. try:
  375. # run patches
  376. frappe.modules.patch_handler.run_all()
  377. # sync
  378. frappe.model.sync.sync_all(verbose=verbose)
  379. frappe.translate.clear_cache()
  380. sync_fixtures()
  381. clear_notifications()
  382. if rebuild_website:
  383. build_website()
  384. finally:
  385. frappe.destroy()
  386. @cmd
  387. def sync_all(force=False, quiet=False):
  388. import frappe.model.sync
  389. verbose = not quiet
  390. frappe.connect()
  391. frappe.model.sync.sync_all(force=force, verbose=verbose)
  392. frappe.destroy()
  393. @cmd
  394. def patch(patch_module, force=False):
  395. import frappe.modules.patch_handler
  396. frappe.connect()
  397. frappe.modules.patch_handler.run_single(patch_module, force=force)
  398. frappe.destroy()
  399. @cmd
  400. def update_all_sites(remote=None, branch=None, quiet=False):
  401. verbose = not quiet
  402. pull(remote, branch)
  403. # maybe there are new framework changes, any consequences?
  404. reload(frappe)
  405. build()
  406. for site in get_sites():
  407. frappe.init(site)
  408. latest(verbose=verbose)
  409. @cmd
  410. def reload_doc(module, doctype, docname, force=False):
  411. frappe.connect()
  412. frappe.reload_doc(module, doctype, docname, force=force)
  413. frappe.db.commit()
  414. frappe.destroy()
  415. @cmd
  416. def build(make_copy=False, verbose=False):
  417. import frappe.build
  418. import frappe
  419. frappe.build.bundle(False, make_copy=make_copy, verbose=verbose)
  420. @cmd
  421. def watch():
  422. import frappe.build
  423. frappe.build.watch(True)
  424. @cmd
  425. def backup(with_files=False, backup_path_db=None, backup_path_files=None, quiet=False):
  426. from frappe.utils.backups import scheduled_backup
  427. verbose = not quiet
  428. frappe.connect()
  429. odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files)
  430. if verbose:
  431. from frappe.utils import now
  432. print "database backup taken -", odb.backup_path_db, "- on", now()
  433. if with_files:
  434. print "files backup taken -", odb.backup_path_files, "- on", now()
  435. frappe.destroy()
  436. @cmd
  437. def move(dest_dir=None, site=None):
  438. import os
  439. if not dest_dir:
  440. raise Exception, "--dest_dir is required for --move"
  441. if not os.path.isdir(dest_dir):
  442. raise Exception, "destination is not a directory or does not exist"
  443. old_path = frappe.utils.get_site()
  444. new_path = os.path.join(dest_dir, site)
  445. # check if site dump of same name already exists
  446. site_dump_exists = True
  447. count = 0
  448. while site_dump_exists:
  449. final_new_path = new_path + (count and str(count) or "")
  450. site_dump_exists = os.path.exists(final_new_path)
  451. count = int(count or 0) + 1
  452. os.rename(old_path, final_new_path)
  453. frappe.destroy()
  454. return os.path.basename(final_new_path)
  455. @cmd
  456. def domain(host_url=None):
  457. frappe.connect()
  458. if host_url:
  459. frappe.db.set_value("Website Settings", None, "subdomain", host_url)
  460. frappe.db.commit()
  461. else:
  462. print frappe.db.get_value("Website Settings", None, "subdomain")
  463. frappe.destroy()
  464. @cmd
  465. def make_conf(db_name=None, db_password=None, site_config=None):
  466. from frappe.install_lib.install import make_conf
  467. make_conf(db_name=db_name, db_password=db_password, site_config=site_config)
  468. @cmd
  469. def make_custom_server_script(doctype):
  470. from frappe.custom.doctype.custom_script.custom_script import make_custom_server_script_file
  471. frappe.connect()
  472. make_custom_server_script_file(doctype)
  473. frappe.destroy()
  474. @cmd
  475. def init_list(doctype):
  476. import frappe.core.doctype.doctype.doctype
  477. frappe.core.doctype.doctype.doctype.init_list(doctype)
  478. # clear
  479. @cmd
  480. def clear_cache():
  481. import frappe.sessions
  482. from frappe.desk.notifications import clear_notifications
  483. frappe.connect()
  484. frappe.clear_cache()
  485. clear_notifications()
  486. frappe.destroy()
  487. @cmd
  488. def clear_web():
  489. import frappe.website.render
  490. frappe.connect()
  491. frappe.website.render.clear_cache()
  492. frappe.destroy()
  493. @cmd
  494. def clear_all_sessions():
  495. import frappe.sessions
  496. frappe.connect()
  497. frappe.sessions.clear_all_sessions()
  498. frappe.db.commit()
  499. frappe.destroy()
  500. @cmd
  501. def build_website(verbose=False):
  502. from frappe.website import render, statics
  503. frappe.connect()
  504. render.clear_cache()
  505. statics.sync(verbose=verbose).start(True)
  506. frappe.db.commit()
  507. frappe.destroy()
  508. @cmd
  509. def sync_statics(force=False):
  510. from frappe.website import statics
  511. frappe.connect()
  512. statics.sync_statics(rebuild = force)
  513. frappe.db.commit()
  514. frappe.destroy()
  515. @cmd
  516. def reset_perms():
  517. from frappe.permissions import reset_perms
  518. frappe.connect()
  519. for d in frappe.db.sql_list("""select name from `tabDocType`
  520. where ifnull(istable, 0)=0 and ifnull(custom, 0)=0"""):
  521. frappe.clear_cache(doctype=d)
  522. reset_perms(d)
  523. frappe.destroy()
  524. @cmd
  525. def execute(method, args=None):
  526. frappe.connect()
  527. if args:
  528. ret = frappe.get_attr(method)(*args)
  529. else:
  530. ret = frappe.get_attr(method)()
  531. if frappe.db:
  532. frappe.db.commit()
  533. frappe.destroy()
  534. if ret:
  535. print ret
  536. @cmd
  537. def celery(arg):
  538. import frappe
  539. import commands, os
  540. python = commands.getoutput('which python')
  541. os.execv(python, [python, "-m", "frappe.celery_app"] + arg.split())
  542. frappe.destroy()
  543. @cmd
  544. def run_scheduler_event(event, force=False):
  545. import frappe.utils.scheduler
  546. frappe.connect()
  547. frappe.utils.scheduler.trigger(frappe.local.site, event, now=force)
  548. frappe.destroy()
  549. @cmd
  550. def enable_scheduler():
  551. import frappe.utils.scheduler
  552. frappe.connect()
  553. frappe.utils.scheduler.enable_scheduler()
  554. frappe.db.commit()
  555. print "Enabled"
  556. frappe.destroy()
  557. @cmd
  558. def disable_scheduler():
  559. import frappe.utils.scheduler
  560. frappe.connect()
  561. frappe.utils.scheduler.disable_scheduler()
  562. frappe.db.commit()
  563. print "Disabled"
  564. frappe.destroy()
  565. # replace
  566. @cmd
  567. def replace(search_regex, replacement, extn, force=False):
  568. print search_regex, replacement, extn
  569. replace_code('.', search_regex, replacement, extn, force=force)
  570. # import/export
  571. @cmd
  572. def export_doc(doctype, docname):
  573. import frappe.modules
  574. frappe.connect()
  575. frappe.modules.export_doc(doctype, docname)
  576. frappe.destroy()
  577. @cmd
  578. def export_doclist(doctype, name, path):
  579. from frappe.core.page.data_import_tool import data_import_tool
  580. frappe.connect()
  581. data_import_tool.export_json(doctype, name, path)
  582. frappe.destroy()
  583. @cmd
  584. def export_csv(doctype, path):
  585. from frappe.core.page.data_import_tool import data_import_tool
  586. frappe.connect()
  587. data_import_tool.export_csv(doctype, path)
  588. frappe.destroy()
  589. @cmd
  590. def export_fixtures():
  591. from frappe.utils.fixtures import export_fixtures
  592. frappe.connect()
  593. export_fixtures()
  594. frappe.destroy()
  595. @cmd
  596. def import_doc(path, force=False):
  597. from frappe.core.page.data_import_tool import data_import_tool
  598. frappe.connect()
  599. data_import_tool.import_doc(path, overwrite=force)
  600. frappe.destroy()
  601. # translation
  602. @cmd
  603. def build_message_files():
  604. import frappe.translate
  605. frappe.connect()
  606. frappe.translate.rebuild_all_translation_files()
  607. frappe.destroy()
  608. @cmd
  609. def get_untranslated(lang, untranslated_file, all=None):
  610. import frappe.translate
  611. frappe.connect()
  612. frappe.translate.get_untranslated(lang, untranslated_file, get_all=all)
  613. frappe.destroy()
  614. @cmd
  615. def update_translations(lang, untranslated_file, translated_file):
  616. import frappe.translate
  617. frappe.connect()
  618. frappe.translate.update_translations(lang, untranslated_file, translated_file)
  619. frappe.destroy()
  620. # git
  621. @cmd
  622. def git(param):
  623. if isinstance(param, (list, tuple)):
  624. param = " ".join(param)
  625. import os
  626. os.system("""cd lib && git %s""" % param)
  627. os.system("""cd app && git %s""" % param)
  628. def get_remote_and_branch(remote=None, branch=None):
  629. if not (remote and branch):
  630. if not frappe.conf.branch:
  631. raise Exception("Please specify remote and branch")
  632. remote = remote or "origin"
  633. branch = branch or frappe.conf.branch
  634. frappe.destroy()
  635. return remote, branch
  636. @cmd
  637. def pull(remote=None, branch=None):
  638. remote, branch = get_remote_and_branch(remote, branch)
  639. git(("pull", remote, branch))
  640. @cmd
  641. def push(remote=None, branch=None):
  642. remote, branch = get_remote_and_branch(remote, branch)
  643. git(("push", remote, branch))
  644. @cmd
  645. def status():
  646. git("status")
  647. @cmd
  648. def commit(message):
  649. git("""commit -a -m "%s" """ % message.replace('"', '\"'))
  650. @cmd
  651. def checkout(branch):
  652. git(("checkout", branch))
  653. @cmd
  654. def set_admin_password(admin_password=None):
  655. import frappe
  656. import getpass
  657. while not admin_password:
  658. admin_password = getpass.getpass("Administrator's password: ")
  659. frappe.connect()
  660. frappe.db.sql("""update __Auth set `password`=password(%s)
  661. where user='Administrator'""", (admin_password,))
  662. frappe.db.commit()
  663. frappe.destroy()
  664. @cmd
  665. def mysql():
  666. import frappe
  667. import commands, os
  668. msq = commands.getoutput('which mysql')
  669. os.execv(msq, [msq, '-u', frappe.conf.db_name, '-p'+frappe.conf.db_password, frappe.conf.db_name, '-h', frappe.conf.db_host or "localhost", "-A"])
  670. frappe.destroy()
  671. @cmd
  672. def python(site):
  673. import frappe
  674. import commands, os
  675. python = commands.getoutput('which python')
  676. if site:
  677. os.environ["site"] = site
  678. os.environ["PYTHONSTARTUP"] = os.path.join(os.path.dirname(frappe.__file__), "pythonrc.py")
  679. os.execv(python, [python])
  680. frappe.destroy()
  681. @cmd
  682. def ipython(site):
  683. import frappe
  684. frappe.connect(site=site)
  685. import IPython
  686. IPython.embed()
  687. @cmd
  688. def smtp_debug_server():
  689. import commands, os
  690. python = commands.getoutput('which python')
  691. os.execv(python, [python, '-m', "smtpd", "-n", "-c", "DebuggingServer", "localhost:25"])
  692. @cmd
  693. def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driver=None, force=False):
  694. import frappe.test_runner
  695. from frappe.utils import sel
  696. # sel.start(verbose, driver)
  697. ret = 1
  698. try:
  699. ret = frappe.test_runner.main(app and app[0], module and module[0], doctype and doctype[0], verbose,
  700. tests=tests, force=force)
  701. if len(ret.failures) == 0 and len(ret.errors) == 0:
  702. ret = 0
  703. finally:
  704. pass
  705. # sel.close()
  706. return ret
  707. @cmd
  708. def serve(port=None, profile=False, sites_path='.', site=None):
  709. if not port: port = 8000
  710. import frappe.app
  711. frappe.app.serve(port=port, profile=profile, site=frappe.local.site, sites_path=sites_path)
  712. @cmd
  713. def serve_test(port=None, profile=False, sites_path='.', site=None):
  714. from frappe.utils import sel
  715. if not port: port = sel.port
  716. serve(port)
  717. @cmd
  718. def request(args):
  719. import frappe.handler
  720. import frappe.api
  721. frappe.connect()
  722. if "?" in args:
  723. frappe.local.form_dict = frappe._dict([a.split("=") for a in args.split("?")[-1].split("&")])
  724. else:
  725. frappe.local.form_dict = frappe._dict()
  726. if args.startswith("/api/method"):
  727. frappe.local.form_dict.cmd = args.split("?")[0].split("/")[-1]
  728. frappe.handler.execute_cmd(frappe.form_dict.cmd)
  729. print frappe.response
  730. frappe.destroy()
  731. @cmd
  732. def resize_images(path):
  733. import frappe.utils.image
  734. frappe.utils.image.resize_images(path)
  735. def replace_code(start, txt1, txt2, extn, search=None, force=False):
  736. """replace all txt1 by txt2 in files with extension (extn)"""
  737. import frappe.utils
  738. import os, re
  739. esc = frappe.utils.make_esc('[]')
  740. if not search: search = esc(txt1)
  741. for wt in os.walk(start, followlinks=1):
  742. for fn in wt[2]:
  743. if fn.split('.')[-1]==extn:
  744. fpath = os.path.join(wt[0], fn)
  745. with open(fpath, 'r') as f:
  746. content = f.read()
  747. if re.search(search, content):
  748. res = search_replace_with_prompt(fpath, txt1, txt2, force)
  749. if res == 'skip':
  750. return 'skip'
  751. def search_replace_with_prompt(fpath, txt1, txt2, force=False):
  752. """ Search and replace all txt1 by txt2 in the file with confirmation"""
  753. from termcolor import colored
  754. with open(fpath, 'r') as f:
  755. content = f.readlines()
  756. tmp = []
  757. for c in content:
  758. if c.find(txt1) != -1:
  759. print fpath
  760. print colored(txt1, 'red').join(c[:-1].split(txt1))
  761. a = ''
  762. if force:
  763. c = c.replace(txt1, txt2)
  764. else:
  765. while a.lower() not in ['y', 'n', 'skip']:
  766. a = raw_input('Do you want to Change [y/n/skip]?')
  767. if a.lower() == 'y':
  768. c = c.replace(txt1, txt2)
  769. elif a.lower() == 'skip':
  770. return 'skip'
  771. tmp.append(c)
  772. with open(fpath, 'w') as f:
  773. f.write(''.join(tmp))
  774. print colored('Updated', 'green')
  775. @cmd
  776. def get_site_status(verbose=False):
  777. import frappe
  778. import frappe.utils
  779. from frappe.utils.user import get_system_managers
  780. from frappe.core.doctype.user.user import get_total_users, get_active_users, \
  781. get_website_users, get_active_website_users
  782. import json
  783. frappe.connect()
  784. ret = {
  785. 'last_backup_on': frappe.local.conf.last_backup_on,
  786. 'active_users': get_active_users(),
  787. 'total_users': get_total_users(),
  788. 'active_website_users': get_active_website_users(),
  789. 'website_users': get_website_users(),
  790. 'system_managers': "\n".join(get_system_managers()),
  791. 'default_company': frappe.db.get_default("company"),
  792. 'disk_usage': frappe.utils.get_disk_usage(),
  793. 'working_directory': frappe.local.site_path
  794. }
  795. # country, timezone, industry
  796. for key in ["country", "time_zone", "industry"]:
  797. ret[key] = frappe.db.get_default(key)
  798. # basic usage/progress analytics
  799. for doctype in ("Company", "Customer", "Item", "Quotation", "Sales Invoice",
  800. "Journal Entry", "Stock Ledger Entry"):
  801. key = doctype.lower().replace(" ", "_") + "_exists"
  802. ret[key] = 1 if frappe.db.count(doctype) else 0
  803. frappe.destroy()
  804. if verbose:
  805. print json.dumps(ret, indent=1, sort_keys=True)
  806. return ret
  807. @cmd
  808. def update_site_config(site_config, verbose=False):
  809. import json
  810. if isinstance(site_config, basestring):
  811. site_config = json.loads(site_config)
  812. config = frappe.get_site_config()
  813. config.update(site_config)
  814. site_config_path = os.path.join(frappe.local.site_path, "site_config.json")
  815. with open(site_config_path, "w") as f:
  816. json.dump(config, f, indent=1, sort_keys=True)
  817. frappe.destroy()
  818. @cmd
  819. def doctor():
  820. from frappe.utils.doctor import doctor as _doctor
  821. return _doctor()
  822. @cmd
  823. def purge_all_tasks():
  824. from frappe.utils.doctor import purge_pending_tasks
  825. count = purge_pending_tasks()
  826. print "Purged {} tasks".format(count)
  827. @cmd
  828. def dump_queue_status():
  829. from frappe.utils.doctor import dump_queue_status as _dump_queue_status
  830. print json.dumps(_dump_queue_status(), indent=1)
  831. if __name__=="__main__":
  832. out = main()
  833. if out and out==1:
  834. exit(1)