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.
 
 
 
 
 
 

489 regels
13 KiB

  1. from __future__ import unicode_literals, absolute_import, print_function
  2. import click
  3. import json, os, sys
  4. from distutils.spawn import find_executable
  5. import frappe
  6. from frappe.commands import pass_context, get_site
  7. @click.command('build')
  8. @click.option('--make-copy', is_flag=True, default=False, help='Copy the files instead of symlinking')
  9. @click.option('--verbose', is_flag=True, default=False, help='Verbose')
  10. def build(make_copy=False, verbose=False):
  11. "Minify + concatenate JS and CSS files, build translations"
  12. import frappe.build
  13. import frappe
  14. frappe.init('')
  15. frappe.build.bundle(False, make_copy=make_copy, verbose=verbose)
  16. @click.command('watch')
  17. def watch():
  18. "Watch and concatenate JS and CSS files as and when they change"
  19. # if os.environ.get('CI'):
  20. # return
  21. import frappe.build
  22. frappe.init('')
  23. frappe.build.watch(True)
  24. @click.command('clear-cache')
  25. @pass_context
  26. def clear_cache(context):
  27. "Clear cache, doctype cache and defaults"
  28. import frappe.sessions
  29. import frappe.website.render
  30. from frappe.desk.notifications import clear_notifications
  31. for site in context.sites:
  32. try:
  33. frappe.connect(site)
  34. frappe.clear_cache()
  35. clear_notifications()
  36. frappe.website.render.clear_cache()
  37. finally:
  38. frappe.destroy()
  39. @click.command('clear-website-cache')
  40. @pass_context
  41. def clear_website_cache(context):
  42. "Clear website cache"
  43. import frappe.website.render
  44. for site in context.sites:
  45. try:
  46. frappe.init(site=site)
  47. frappe.connect()
  48. frappe.website.render.clear_cache()
  49. finally:
  50. frappe.destroy()
  51. @click.command('destroy-all-sessions')
  52. @click.option('--reason')
  53. @pass_context
  54. def destroy_all_sessions(context, reason=None):
  55. "Clear sessions of all users (logs them out)"
  56. import frappe.sessions
  57. for site in context.sites:
  58. try:
  59. frappe.init(site=site)
  60. frappe.connect()
  61. frappe.sessions.clear_all_sessions(reason)
  62. frappe.db.commit()
  63. finally:
  64. frappe.destroy()
  65. @click.command('reset-perms')
  66. @pass_context
  67. def reset_perms(context):
  68. "Reset permissions for all doctypes"
  69. from frappe.permissions import reset_perms
  70. for site in context.sites:
  71. try:
  72. frappe.init(site=site)
  73. frappe.connect()
  74. for d in frappe.db.sql_list("""select name from `tabDocType`
  75. where istable=0 and custom=0"""):
  76. frappe.clear_cache(doctype=d)
  77. reset_perms(d)
  78. finally:
  79. frappe.destroy()
  80. @click.command('execute')
  81. @click.argument('method')
  82. @click.option('--args')
  83. @click.option('--kwargs')
  84. @pass_context
  85. def execute(context, method, args=None, kwargs=None):
  86. "Execute a function"
  87. for site in context.sites:
  88. try:
  89. frappe.init(site=site)
  90. frappe.connect()
  91. if args:
  92. try:
  93. args = eval(args)
  94. except NameError:
  95. args = [args]
  96. else:
  97. args = ()
  98. if kwargs:
  99. kwargs = eval(kwargs)
  100. else:
  101. kwargs = {}
  102. ret = frappe.get_attr(method)(*args, **kwargs)
  103. if frappe.db:
  104. frappe.db.commit()
  105. finally:
  106. frappe.destroy()
  107. if ret:
  108. print(json.dumps(ret))
  109. @click.command('add-to-email-queue')
  110. @click.argument('email-path')
  111. @pass_context
  112. def add_to_email_queue(context, email_path):
  113. "Add an email to the Email Queue"
  114. site = get_site(context)
  115. if os.path.isdir(email_path):
  116. with frappe.init_site(site):
  117. frappe.connect()
  118. for email in os.listdir(email_path):
  119. with open(os.path.join(email_path, email)) as email_data:
  120. kwargs = json.load(email_data)
  121. kwargs['delayed'] = True
  122. frappe.sendmail(**kwargs)
  123. frappe.db.commit()
  124. @click.command('export-doc')
  125. @click.argument('doctype')
  126. @click.argument('docname')
  127. @pass_context
  128. def export_doc(context, doctype, docname):
  129. "Export a single document to csv"
  130. import frappe.modules
  131. for site in context.sites:
  132. try:
  133. frappe.init(site=site)
  134. frappe.connect()
  135. frappe.modules.export_doc(doctype, docname)
  136. finally:
  137. frappe.destroy()
  138. @click.command('export-json')
  139. @click.argument('doctype')
  140. @click.argument('path')
  141. @click.option('--name', help='Export only one document')
  142. @pass_context
  143. def export_json(context, doctype, path, name=None):
  144. "Export doclist as json to the given path, use '-' as name for Singles."
  145. from frappe.core.page.data_import_tool import data_import_tool
  146. for site in context.sites:
  147. try:
  148. frappe.init(site=site)
  149. frappe.connect()
  150. data_import_tool.export_json(doctype, path, name=name)
  151. finally:
  152. frappe.destroy()
  153. @click.command('export-csv')
  154. @click.argument('doctype')
  155. @click.argument('path')
  156. @pass_context
  157. def export_csv(context, doctype, path):
  158. "Export data import template with data for DocType"
  159. from frappe.core.page.data_import_tool import data_import_tool
  160. for site in context.sites:
  161. try:
  162. frappe.init(site=site)
  163. frappe.connect()
  164. data_import_tool.export_csv(doctype, path)
  165. finally:
  166. frappe.destroy()
  167. @click.command('export-fixtures')
  168. @pass_context
  169. def export_fixtures(context):
  170. "Export fixtures"
  171. from frappe.utils.fixtures import export_fixtures
  172. for site in context.sites:
  173. try:
  174. frappe.init(site=site)
  175. frappe.connect()
  176. export_fixtures()
  177. finally:
  178. frappe.destroy()
  179. @click.command('import-doc')
  180. @click.argument('path')
  181. @pass_context
  182. def import_doc(context, path, force=False):
  183. "Import (insert/update) doclist. If the argument is a directory, all files ending with .json are imported"
  184. from frappe.core.page.data_import_tool import data_import_tool
  185. if not os.path.exists(path):
  186. path = os.path.join('..', path)
  187. if not os.path.exists(path):
  188. print('Invalid path {0}'.format(path))
  189. sys.exit(1)
  190. for site in context.sites:
  191. try:
  192. frappe.init(site=site)
  193. frappe.connect()
  194. data_import_tool.import_doc(path, overwrite=context.force)
  195. finally:
  196. frappe.destroy()
  197. @click.command('import-csv')
  198. @click.argument('path')
  199. @click.option('--only-insert', default=False, is_flag=True, help='Do not overwrite existing records')
  200. @click.option('--submit-after-import', default=False, is_flag=True, help='Submit document after importing it')
  201. @click.option('--ignore-encoding-errors', default=False, is_flag=True, help='Ignore encoding errors while coverting to unicode')
  202. @click.option('--no-email', default=True, is_flag=True, help='Send email if applicable')
  203. @pass_context
  204. def import_csv(context, path, only_insert=False, submit_after_import=False, ignore_encoding_errors=False, no_email=True):
  205. "Import CSV using data import tool"
  206. from frappe.core.page.data_import_tool import importer
  207. from frappe.utils.csvutils import read_csv_content
  208. site = get_site(context)
  209. if not os.path.exists(path):
  210. path = os.path.join('..', path)
  211. if not os.path.exists(path):
  212. print('Invalid path {0}'.format(path))
  213. sys.exit(1)
  214. with open(path, 'r') as csvfile:
  215. content = read_csv_content(csvfile.read())
  216. frappe.init(site=site)
  217. frappe.connect()
  218. try:
  219. importer.upload(content, submit_after_import=submit_after_import, no_email=no_email,
  220. ignore_encoding_errors=ignore_encoding_errors, overwrite=not only_insert,
  221. via_console=True)
  222. frappe.db.commit()
  223. except Exception:
  224. print(frappe.get_traceback())
  225. frappe.destroy()
  226. @click.command('bulk-rename')
  227. @click.argument('doctype')
  228. @click.argument('path')
  229. @pass_context
  230. def _bulk_rename(context, doctype, path):
  231. "Rename multiple records via CSV file"
  232. from frappe.model.rename_doc import bulk_rename
  233. from frappe.utils.csvutils import read_csv_content
  234. site = get_site(context)
  235. with open(path, 'r') as csvfile:
  236. rows = read_csv_content(csvfile.read())
  237. frappe.init(site=site)
  238. frappe.connect()
  239. bulk_rename(doctype, rows, via_console = True)
  240. frappe.destroy()
  241. @click.command('mysql')
  242. @pass_context
  243. def mysql(context):
  244. "Start Mariadb console for a site"
  245. site = get_site(context)
  246. frappe.init(site=site)
  247. msq = find_executable('mysql')
  248. 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"])
  249. @click.command('console')
  250. @pass_context
  251. def console(context):
  252. "Start ipython console for a site"
  253. site = get_site(context)
  254. frappe.init(site=site)
  255. frappe.connect()
  256. frappe.local.lang = frappe.db.get_default("lang")
  257. import IPython
  258. IPython.embed()
  259. @click.command('run-tests')
  260. @click.option('--app', help="For App")
  261. @click.option('--doctype', help="For DocType")
  262. @click.option('--test', multiple=True, help="Specific test")
  263. @click.option('--driver', help="For Travis")
  264. @click.option('--ui-tests', is_flag=True, default=False, help="Run UI Tests")
  265. @click.option('--module', help="Run tests in a module")
  266. @click.option('--profile', is_flag=True, default=False)
  267. @click.option('--junit-xml-output', help="Destination file path for junit xml report")
  268. @pass_context
  269. def run_tests(context, app=None, module=None, doctype=None, test=(),
  270. driver=None, profile=False, junit_xml_output=False, ui_tests = False):
  271. "Run tests"
  272. import frappe.test_runner
  273. tests = test
  274. site = get_site(context)
  275. frappe.init(site=site)
  276. ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests,
  277. force=context.force, profile=profile, junit_xml_output=junit_xml_output,
  278. ui_tests = ui_tests)
  279. if len(ret.failures) == 0 and len(ret.errors) == 0:
  280. ret = 0
  281. if os.environ.get('CI'):
  282. sys.exit(ret)
  283. @click.command('run-ui-tests')
  284. @click.option('--app', help="App to run tests on, leave blank for all apps")
  285. @click.option('--test', help="File name of the test you want to run")
  286. @click.option('--profile', is_flag=True, default=False)
  287. @pass_context
  288. def run_ui_tests(context, app=None, test=False, profile=False):
  289. "Run UI tests"
  290. import frappe.test_runner
  291. site = get_site(context)
  292. frappe.init(site=site)
  293. frappe.connect()
  294. ret = frappe.test_runner.run_ui_tests(app=app, test=test, verbose=context.verbose,
  295. profile=profile)
  296. if len(ret.failures) == 0 and len(ret.errors) == 0:
  297. ret = 0
  298. if os.environ.get('CI'):
  299. sys.exit(ret)
  300. @click.command('serve')
  301. @click.option('--port', default=8000)
  302. @click.option('--profile', is_flag=True, default=False)
  303. @pass_context
  304. def serve(context, port=None, profile=False, sites_path='.', site=None):
  305. "Start development web server"
  306. import frappe.app
  307. if not context.sites:
  308. site = None
  309. else:
  310. site = context.sites[0]
  311. frappe.app.serve(port=port, profile=profile, site=site, sites_path='.')
  312. @click.command('request')
  313. @click.argument('args')
  314. @pass_context
  315. def request(context, args):
  316. "Run a request as an admin"
  317. import frappe.handler
  318. import frappe.api
  319. for site in context.sites:
  320. try:
  321. frappe.init(site=site)
  322. frappe.connect()
  323. if "?" in args:
  324. frappe.local.form_dict = frappe._dict([a.split("=") for a in args.split("?")[-1].split("&")])
  325. else:
  326. frappe.local.form_dict = frappe._dict()
  327. if args.startswith("/api/method"):
  328. frappe.local.form_dict.cmd = args.split("?")[0].split("/")[-1]
  329. frappe.handler.execute_cmd(frappe.form_dict.cmd)
  330. print(frappe.response)
  331. finally:
  332. frappe.destroy()
  333. @click.command('make-app')
  334. @click.argument('destination')
  335. @click.argument('app_name')
  336. def make_app(destination, app_name):
  337. "Creates a boilerplate app"
  338. from frappe.utils.boilerplate import make_boilerplate
  339. make_boilerplate(destination, app_name)
  340. @click.command('set-config')
  341. @click.argument('key')
  342. @click.argument('value')
  343. @click.option('--as-dict', is_flag=True, default=False)
  344. @pass_context
  345. def set_config(context, key, value, as_dict=False):
  346. "Insert/Update a value in site_config.json"
  347. from frappe.installer import update_site_config
  348. import ast
  349. if as_dict:
  350. value = ast.literal_eval(value)
  351. for site in context.sites:
  352. frappe.init(site=site)
  353. update_site_config(key, value, validate=False)
  354. frappe.destroy()
  355. @click.command('version')
  356. def get_version():
  357. "Show the versions of all the installed apps"
  358. frappe.init('')
  359. for m in sorted(frappe.get_all_apps()):
  360. module = frappe.get_module(m)
  361. if hasattr(module, "__version__"):
  362. print("{0} {1}".format(m, module.__version__))
  363. @click.command('setup-global-help')
  364. @click.option('--mariadb_root_password')
  365. def setup_global_help(mariadb_root_password=None):
  366. '''setup help table in a separate database that will be
  367. shared by the whole bench and set `global_help_setup` as 1 in
  368. common_site_config.json'''
  369. from frappe.installer import update_site_config
  370. frappe.local.flags = frappe._dict()
  371. frappe.local.flags.in_setup_help = True
  372. frappe.local.flags.in_install = True
  373. frappe.local.lang = 'en'
  374. frappe.local.conf = frappe.get_site_config(sites_path='.')
  375. update_site_config('global_help_setup', 1,
  376. site_config_path=os.path.join('.', 'common_site_config.json'))
  377. if mariadb_root_password:
  378. frappe.local.conf.root_password = mariadb_root_password
  379. from frappe.utils.help import sync
  380. sync()
  381. @click.command('setup-help')
  382. @pass_context
  383. def setup_help(context):
  384. '''Setup help table in the current site (called after migrate)'''
  385. from frappe.utils.help import sync
  386. for site in context.sites:
  387. try:
  388. frappe.init(site)
  389. frappe.connect()
  390. sync()
  391. finally:
  392. frappe.destroy()
  393. commands = [
  394. build,
  395. clear_cache,
  396. clear_website_cache,
  397. console,
  398. destroy_all_sessions,
  399. execute,
  400. export_csv,
  401. export_doc,
  402. export_fixtures,
  403. export_json,
  404. get_version,
  405. import_csv,
  406. import_doc,
  407. make_app,
  408. mysql,
  409. request,
  410. reset_perms,
  411. run_tests,
  412. run_ui_tests,
  413. serve,
  414. set_config,
  415. watch,
  416. _bulk_rename,
  417. add_to_email_queue,
  418. setup_global_help,
  419. setup_help
  420. ]