Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 

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