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.
 
 
 
 
 
 

538 Zeilen
14 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.page.data_import_tool import data_import_tool
  148. for site in context.sites:
  149. try:
  150. frappe.init(site=site)
  151. frappe.connect()
  152. data_import_tool.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.page.data_import_tool import data_import_tool
  162. for site in context.sites:
  163. try:
  164. frappe.init(site=site)
  165. frappe.connect()
  166. data_import_tool.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.page.data_import_tool import data_import_tool
  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_tool.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 tool"
  208. from frappe.core.page.data_import_tool 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('--test', multiple=True, help="Specific test")
  265. @click.option('--driver', help="For Travis")
  266. @click.option('--ui-tests', is_flag=True, default=False, help="Run UI Tests")
  267. @click.option('--module', help="Run tests in a module")
  268. @click.option('--profile', is_flag=True, default=False)
  269. @click.option('--junit-xml-output', help="Destination file path for junit xml report")
  270. @pass_context
  271. def run_tests(context, app=None, module=None, doctype=None, test=(),
  272. driver=None, profile=False, junit_xml_output=False, ui_tests = False):
  273. "Run tests"
  274. import frappe.test_runner
  275. tests = test
  276. site = get_site(context)
  277. frappe.init(site=site)
  278. ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests,
  279. force=context.force, profile=profile, junit_xml_output=junit_xml_output,
  280. ui_tests = ui_tests)
  281. if len(ret.failures) == 0 and len(ret.errors) == 0:
  282. ret = 0
  283. if os.environ.get('CI'):
  284. sys.exit(ret)
  285. @click.command('run-ui-tests')
  286. @click.option('--app', help="App to run tests on, leave blank for all apps")
  287. @click.option('--test', help="File name of the test you want to run")
  288. @click.option('--profile', is_flag=True, default=False)
  289. @pass_context
  290. def run_ui_tests(context, app=None, test=False, profile=False):
  291. "Run UI tests"
  292. import frappe.test_runner
  293. site = get_site(context)
  294. frappe.init(site=site)
  295. frappe.connect()
  296. ret = frappe.test_runner.run_ui_tests(app=app, test=test, verbose=context.verbose,
  297. profile=profile)
  298. if len(ret.failures) == 0 and len(ret.errors) == 0:
  299. ret = 0
  300. if os.environ.get('CI'):
  301. sys.exit(ret)
  302. @click.command('run-setup-wizard-ui-test')
  303. @click.option('--app', help="App to run tests on, leave blank for all apps")
  304. @click.option('--profile', is_flag=True, default=False)
  305. @pass_context
  306. def run_setup_wizard_ui_test(context, app=None, profile=False):
  307. "Run setup wizard UI test"
  308. import frappe.test_runner
  309. site = get_site(context)
  310. frappe.init(site=site)
  311. frappe.connect()
  312. ret = frappe.test_runner.run_setup_wizard_ui_test(app=app, verbose=context.verbose,
  313. profile=profile)
  314. if len(ret.failures) == 0 and len(ret.errors) == 0:
  315. ret = 0
  316. if os.environ.get('CI'):
  317. sys.exit(ret)
  318. @click.command('serve')
  319. @click.option('--port', default=8000)
  320. @click.option('--profile', is_flag=True, default=False)
  321. @pass_context
  322. def serve(context, port=None, profile=False, sites_path='.', site=None):
  323. "Start development web server"
  324. import frappe.app
  325. if not context.sites:
  326. site = None
  327. else:
  328. site = context.sites[0]
  329. frappe.app.serve(port=port, profile=profile, site=site, sites_path='.')
  330. @click.command('request')
  331. @click.option('--args', help='arguments like `?cmd=test&key=value` or `/api/request/method?..`')
  332. @click.option('--path', help='path to request JSON')
  333. @pass_context
  334. def request(context, args=None, path=None):
  335. "Run a request as an admin"
  336. import frappe.handler
  337. import frappe.api
  338. for site in context.sites:
  339. try:
  340. frappe.init(site=site)
  341. frappe.connect()
  342. if args:
  343. if "?" in args:
  344. frappe.local.form_dict = frappe._dict([a.split("=") for a in args.split("?")[-1].split("&")])
  345. else:
  346. frappe.local.form_dict = frappe._dict()
  347. if args.startswith("/api/method"):
  348. frappe.local.form_dict.cmd = args.split("?")[0].split("/")[-1]
  349. elif path:
  350. with open(os.path.join('..', path), 'r') as f:
  351. args = json.loads(f.read())
  352. frappe.local.form_dict = frappe._dict(args)
  353. frappe.handler.execute_cmd(frappe.form_dict.cmd)
  354. print(frappe.response)
  355. finally:
  356. frappe.destroy()
  357. @click.command('make-app')
  358. @click.argument('destination')
  359. @click.argument('app_name')
  360. def make_app(destination, app_name):
  361. "Creates a boilerplate app"
  362. from frappe.utils.boilerplate import make_boilerplate
  363. make_boilerplate(destination, app_name)
  364. @click.command('set-config')
  365. @click.argument('key')
  366. @click.argument('value')
  367. @click.option('--as-dict', is_flag=True, default=False)
  368. @pass_context
  369. def set_config(context, key, value, as_dict=False):
  370. "Insert/Update a value in site_config.json"
  371. from frappe.installer import update_site_config
  372. import ast
  373. if as_dict:
  374. value = ast.literal_eval(value)
  375. for site in context.sites:
  376. frappe.init(site=site)
  377. update_site_config(key, value, validate=False)
  378. frappe.destroy()
  379. @click.command('version')
  380. def get_version():
  381. "Show the versions of all the installed apps"
  382. frappe.init('')
  383. for m in sorted(frappe.get_all_apps()):
  384. module = frappe.get_module(m)
  385. if hasattr(module, "__version__"):
  386. print("{0} {1}".format(m, module.__version__))
  387. @click.command('setup-global-help')
  388. @click.option('--mariadb_root_password')
  389. def setup_global_help(mariadb_root_password=None):
  390. '''setup help table in a separate database that will be
  391. shared by the whole bench and set `global_help_setup` as 1 in
  392. common_site_config.json'''
  393. from frappe.installer import update_site_config
  394. frappe.local.flags = frappe._dict()
  395. frappe.local.flags.in_setup_help = True
  396. frappe.local.flags.in_install = True
  397. frappe.local.lang = 'en'
  398. frappe.local.conf = frappe.get_site_config(sites_path='.')
  399. update_site_config('global_help_setup', 1,
  400. site_config_path=os.path.join('.', 'common_site_config.json'))
  401. if mariadb_root_password:
  402. frappe.local.conf.root_password = mariadb_root_password
  403. from frappe.utils.help import sync
  404. sync()
  405. @click.command('setup-help')
  406. @pass_context
  407. def setup_help(context):
  408. '''Setup help table in the current site (called after migrate)'''
  409. from frappe.utils.help import sync
  410. for site in context.sites:
  411. try:
  412. frappe.init(site)
  413. frappe.connect()
  414. sync()
  415. finally:
  416. frappe.destroy()
  417. @click.command('rebuild-global-search')
  418. @pass_context
  419. def rebuild_global_search(context):
  420. '''Setup help table in the current site (called after migrate)'''
  421. from frappe.utils.global_search import (get_doctypes_with_global_search, rebuild_for_doctype)
  422. for site in context.sites:
  423. try:
  424. frappe.init(site)
  425. frappe.connect()
  426. doctypes = get_doctypes_with_global_search()
  427. for i, doctype in enumerate(doctypes):
  428. rebuild_for_doctype(doctype)
  429. update_progress_bar('Rebuilding Global Search', i, len(doctypes))
  430. finally:
  431. frappe.destroy()
  432. commands = [
  433. build,
  434. clear_cache,
  435. clear_website_cache,
  436. console,
  437. destroy_all_sessions,
  438. execute,
  439. export_csv,
  440. export_doc,
  441. export_fixtures,
  442. export_json,
  443. get_version,
  444. import_csv,
  445. import_doc,
  446. make_app,
  447. mysql,
  448. request,
  449. reset_perms,
  450. run_tests,
  451. run_ui_tests,
  452. run_setup_wizard_ui_test,
  453. serve,
  454. set_config,
  455. watch,
  456. _bulk_rename,
  457. add_to_email_queue,
  458. setup_global_help,
  459. setup_help,
  460. rebuild_global_search
  461. ]