Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

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