Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 
 

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