Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

569 wiersze
18 KiB

  1. from __future__ import unicode_literals, absolute_import, print_function
  2. import click
  3. import hashlib, os, sys, compileall
  4. import frappe
  5. from frappe import _
  6. from frappe.commands import pass_context, get_site
  7. from frappe.commands.scheduler import _is_scheduler_enabled
  8. from frappe.limits import update_limits, get_limits
  9. from frappe.installer import update_site_config
  10. from frappe.utils import touch_file, get_site_path
  11. from six import text_type
  12. # imports - third-party imports
  13. from pymysql.constants import ER
  14. # imports - module imports
  15. from frappe.exceptions import SQLError
  16. @click.command('new-site')
  17. @click.argument('site')
  18. @click.option('--db-name', help='Database name')
  19. @click.option('--mariadb-root-username', default='root', help='Root username for MariaDB')
  20. @click.option('--mariadb-root-password', help='Root password for MariaDB')
  21. @click.option('--admin-password', help='Administrator password for new site', default=None)
  22. @click.option('--verbose', is_flag=True, default=False, help='Verbose')
  23. @click.option('--force', help='Force restore if site/database already exists', is_flag=True, default=False)
  24. @click.option('--source_sql', help='Initiate database with a SQL file')
  25. @click.option('--install-app', multiple=True, help='Install app after installation')
  26. def new_site(site, mariadb_root_username=None, mariadb_root_password=None, admin_password=None, verbose=False, install_apps=None, source_sql=None, force=None, install_app=None, db_name=None):
  27. "Create a new site"
  28. frappe.init(site=site, new_site=True)
  29. _new_site(db_name, site, mariadb_root_username=mariadb_root_username, mariadb_root_password=mariadb_root_password, admin_password=admin_password,
  30. verbose=verbose, install_apps=install_app, source_sql=source_sql, force=force)
  31. if len(frappe.utils.get_sites()) == 1:
  32. use(site)
  33. def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=None, admin_password=None,
  34. verbose=False, install_apps=None, source_sql=None,force=False, reinstall=False):
  35. """Install a new Frappe site"""
  36. if not db_name:
  37. db_name = hashlib.sha1(site.encode()).hexdigest()[:16]
  38. from frappe.installer import install_db, make_site_dirs
  39. from frappe.installer import install_app as _install_app
  40. import frappe.utils.scheduler
  41. frappe.init(site=site)
  42. try:
  43. # enable scheduler post install?
  44. enable_scheduler = _is_scheduler_enabled()
  45. except Exception:
  46. enable_scheduler = False
  47. make_site_dirs()
  48. installing = None
  49. try:
  50. installing = touch_file(get_site_path('locks', 'installing.lock'))
  51. install_db(root_login=mariadb_root_username, root_password=mariadb_root_password, db_name=db_name,
  52. admin_password=admin_password, verbose=verbose, source_sql=source_sql,force=force, reinstall=reinstall)
  53. apps_to_install = ['frappe'] + (frappe.conf.get("install_apps") or []) + (list(install_apps) or [])
  54. for app in apps_to_install:
  55. _install_app(app, verbose=verbose, set_as_patched=not source_sql)
  56. frappe.utils.scheduler.toggle_scheduler(enable_scheduler)
  57. frappe.db.commit()
  58. scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled"
  59. print("*** Scheduler is", scheduler_status, "***")
  60. except frappe.exceptions.ImproperDBConfigurationError:
  61. _drop_site(site, mariadb_root_username, mariadb_root_password, force=True)
  62. finally:
  63. if installing and os.path.exists(installing):
  64. os.remove(installing)
  65. frappe.destroy()
  66. @click.command('restore')
  67. @click.argument('sql-file-path')
  68. @click.option('--mariadb-root-username', default='root', help='Root username for MariaDB')
  69. @click.option('--mariadb-root-password', help='Root password for MariaDB')
  70. @click.option('--db-name', help='Database name for site in case it is a new one')
  71. @click.option('--admin-password', help='Administrator password for new site')
  72. @click.option('--install-app', multiple=True, help='Install app after installation')
  73. @click.option('--with-public-files', help='Restores the public files of the site, given path to its tar file')
  74. @click.option('--with-private-files', help='Restores the private files of the site, given path to its tar file')
  75. @pass_context
  76. def restore(context, sql_file_path, mariadb_root_username=None, mariadb_root_password=None, db_name=None, verbose=None, install_app=None, admin_password=None, force=None, with_public_files=None, with_private_files=None):
  77. "Restore site database from an sql file"
  78. from frappe.installer import extract_sql_gzip, extract_tar_files
  79. # Extract the gzip file if user has passed *.sql.gz file instead of *.sql file
  80. if not os.path.exists(sql_file_path):
  81. sql_file_path = '../' + sql_file_path
  82. if not os.path.exists(sql_file_path):
  83. print('Invalid path {0}' + sql_file_path[3:])
  84. sys.exit(1)
  85. if sql_file_path.endswith('sql.gz'):
  86. sql_file_path = extract_sql_gzip(os.path.abspath(sql_file_path))
  87. site = get_site(context)
  88. frappe.init(site=site)
  89. _new_site(frappe.conf.db_name, site, mariadb_root_username=mariadb_root_username,
  90. mariadb_root_password=mariadb_root_password, admin_password=admin_password,
  91. verbose=context.verbose, install_apps=install_app, source_sql=sql_file_path,
  92. force=context.force)
  93. # Extract public and/or private files to the restored site, if user has given the path
  94. if with_public_files:
  95. public = extract_tar_files(site, with_public_files, 'public')
  96. os.remove(public)
  97. if with_private_files:
  98. private = extract_tar_files(site, with_private_files, 'private')
  99. os.remove(private)
  100. @click.command('reinstall')
  101. @click.option('--admin-password', help='Administrator Password for reinstalled site')
  102. @click.option('--yes', is_flag=True, default=False, help='Pass --yes to skip confirmation')
  103. @pass_context
  104. def reinstall(context, admin_password=None, yes=False):
  105. "Reinstall site ie. wipe all data and start over"
  106. site = get_site(context)
  107. _reinstall(site, admin_password, yes, verbose=context.verbose)
  108. def _reinstall(site, admin_password=None, yes=False, verbose=False):
  109. if not yes:
  110. click.confirm('This will wipe your database. Are you sure you want to reinstall?', abort=True)
  111. try:
  112. frappe.init(site=site)
  113. frappe.connect()
  114. frappe.clear_cache()
  115. installed = frappe.get_installed_apps()
  116. frappe.clear_cache()
  117. except Exception:
  118. installed = []
  119. finally:
  120. if frappe.db:
  121. frappe.db.close()
  122. frappe.destroy()
  123. frappe.init(site=site)
  124. _new_site(frappe.conf.db_name, site, verbose=verbose, force=True, reinstall=True,
  125. install_apps=installed, admin_password=admin_password)
  126. @click.command('install-app')
  127. @click.argument('app')
  128. @pass_context
  129. def install_app(context, app):
  130. "Install a new app to site"
  131. from frappe.installer import install_app as _install_app
  132. for site in context.sites:
  133. frappe.init(site=site)
  134. frappe.connect()
  135. try:
  136. _install_app(app, verbose=context.verbose)
  137. finally:
  138. frappe.destroy()
  139. @click.command('list-apps')
  140. @pass_context
  141. def list_apps(context):
  142. "List apps in site"
  143. site = get_site(context)
  144. frappe.init(site=site)
  145. frappe.connect()
  146. print("\n".join(frappe.get_installed_apps()))
  147. frappe.destroy()
  148. @click.command('add-system-manager')
  149. @click.argument('email')
  150. @click.option('--first-name')
  151. @click.option('--last-name')
  152. @click.option('--send-welcome-email', default=False, is_flag=True)
  153. @pass_context
  154. def add_system_manager(context, email, first_name, last_name, send_welcome_email):
  155. "Add a new system manager to a site"
  156. import frappe.utils.user
  157. for site in context.sites:
  158. frappe.connect(site=site)
  159. try:
  160. frappe.utils.user.add_system_manager(email, first_name, last_name, send_welcome_email)
  161. frappe.db.commit()
  162. finally:
  163. frappe.destroy()
  164. @click.command('disable-user')
  165. @click.argument('email')
  166. @pass_context
  167. def disable_user(context, email):
  168. site = get_site(context)
  169. with frappe.init_site(site):
  170. frappe.connect()
  171. user = frappe.get_doc("User", email)
  172. user.enabled = 0
  173. user.save(ignore_permissions=True)
  174. frappe.db.commit()
  175. @click.command('migrate')
  176. @click.option('--rebuild-website', help="Rebuild webpages after migration")
  177. @pass_context
  178. def migrate(context, rebuild_website=False):
  179. "Run patches, sync schema and rebuild files/translations"
  180. from frappe.migrate import migrate
  181. for site in context.sites:
  182. print('Migrating', site)
  183. frappe.init(site=site)
  184. frappe.connect()
  185. try:
  186. migrate(context.verbose, rebuild_website=rebuild_website)
  187. finally:
  188. frappe.destroy()
  189. compileall.compile_dir('../apps', quiet=1)
  190. @click.command('run-patch')
  191. @click.argument('module')
  192. @pass_context
  193. def run_patch(context, module):
  194. "Run a particular patch"
  195. import frappe.modules.patch_handler
  196. for site in context.sites:
  197. frappe.init(site=site)
  198. try:
  199. frappe.connect()
  200. frappe.modules.patch_handler.run_single(module, force=context.force)
  201. finally:
  202. frappe.destroy()
  203. @click.command('reload-doc')
  204. @click.argument('module')
  205. @click.argument('doctype')
  206. @click.argument('docname')
  207. @pass_context
  208. def reload_doc(context, module, doctype, docname):
  209. "Reload schema for a DocType"
  210. for site in context.sites:
  211. try:
  212. frappe.init(site=site)
  213. frappe.connect()
  214. frappe.reload_doc(module, doctype, docname, force=context.force)
  215. frappe.db.commit()
  216. finally:
  217. frappe.destroy()
  218. @click.command('reload-doctype')
  219. @click.argument('doctype')
  220. @pass_context
  221. def reload_doctype(context, doctype):
  222. "Reload schema for a DocType"
  223. for site in context.sites:
  224. try:
  225. frappe.init(site=site)
  226. frappe.connect()
  227. frappe.reload_doctype(doctype, force=context.force)
  228. frappe.db.commit()
  229. finally:
  230. frappe.destroy()
  231. @click.command('use')
  232. @click.argument('site')
  233. def _use(site, sites_path='.'):
  234. "Set a default site"
  235. use(site, sites_path=sites_path)
  236. def use(site, sites_path='.'):
  237. with open(os.path.join(sites_path, "currentsite.txt"), "w") as sitefile:
  238. sitefile.write(site)
  239. @click.command('backup')
  240. @click.option('--with-files', default=False, is_flag=True, help="Take backup with files")
  241. @pass_context
  242. def backup(context, with_files=False, backup_path_db=None, backup_path_files=None,
  243. backup_path_private_files=None, quiet=False):
  244. "Backup"
  245. from frappe.utils.backups import scheduled_backup
  246. verbose = context.verbose
  247. for site in context.sites:
  248. frappe.init(site=site)
  249. frappe.connect()
  250. odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files, backup_path_private_files=backup_path_private_files, force=True)
  251. if verbose:
  252. from frappe.utils import now
  253. print("database backup taken -", odb.backup_path_db, "- on", now())
  254. if with_files:
  255. print("files backup taken -", odb.backup_path_files, "- on", now())
  256. print("private files backup taken -", odb.backup_path_private_files, "- on", now())
  257. frappe.destroy()
  258. @click.command('remove-from-installed-apps')
  259. @click.argument('app')
  260. @pass_context
  261. def remove_from_installed_apps(context, app):
  262. "Remove app from site's installed-apps list"
  263. from frappe.installer import remove_from_installed_apps
  264. for site in context.sites:
  265. try:
  266. frappe.init(site=site)
  267. frappe.connect()
  268. remove_from_installed_apps(app)
  269. finally:
  270. frappe.destroy()
  271. @click.command('uninstall-app')
  272. @click.argument('app')
  273. @click.option('--yes', '-y', help='To bypass confirmation prompt for uninstalling the app', is_flag=True, default=False, multiple=True)
  274. @click.option('--dry-run', help='List all doctypes that will be deleted', is_flag=True, default=False)
  275. @pass_context
  276. def uninstall(context, app, dry_run=False, yes=False):
  277. "Remove app and linked modules from site"
  278. from frappe.installer import remove_app
  279. for site in context.sites:
  280. try:
  281. frappe.init(site=site)
  282. frappe.connect()
  283. remove_app(app, dry_run, yes)
  284. finally:
  285. frappe.destroy()
  286. @click.command('drop-site')
  287. @click.argument('site')
  288. @click.option('--root-login', default='root')
  289. @click.option('--root-password')
  290. @click.option('--archived-sites-path')
  291. @click.option('--force', help='Force drop-site even if an error is encountered', is_flag=True, default=False)
  292. def drop_site(site, root_login='root', root_password=None, archived_sites_path=None, force=False):
  293. _drop_site(site, root_login, root_password, archived_sites_path, force)
  294. def _drop_site(site, root_login='root', root_password=None, archived_sites_path=None, force=False):
  295. "Remove site from database and filesystem"
  296. from frappe.installer import get_root_connection
  297. from frappe.model.db_schema import DbManager
  298. from frappe.utils.backups import scheduled_backup
  299. frappe.init(site=site)
  300. frappe.connect()
  301. try:
  302. scheduled_backup(ignore_files=False, force=True)
  303. except SQLError as err:
  304. if err[0] == ER.NO_SUCH_TABLE:
  305. if force:
  306. pass
  307. else:
  308. click.echo("="*80)
  309. click.echo("Error: The operation has stopped because backup of {s}'s database failed.".format(s=site))
  310. click.echo("Reason: {reason}{sep}".format(reason=err[1], sep="\n"))
  311. click.echo("Fix the issue and try again.")
  312. click.echo(
  313. "Hint: Use 'bench drop-site {s} --force' to force the removal of {s}".format(sep="\n", tab="\t", s=site)
  314. )
  315. sys.exit(1)
  316. db_name = frappe.local.conf.db_name
  317. frappe.local.db = get_root_connection(root_login, root_password)
  318. dbman = DbManager(frappe.local.db)
  319. dbman.delete_user(db_name)
  320. dbman.drop_database(db_name)
  321. if not archived_sites_path:
  322. archived_sites_path = os.path.join(frappe.get_app_path('frappe'), '..', '..', '..', 'archived_sites')
  323. if not os.path.exists(archived_sites_path):
  324. os.mkdir(archived_sites_path)
  325. move(archived_sites_path, site)
  326. def move(dest_dir, site):
  327. if not os.path.isdir(dest_dir):
  328. raise Exception("destination is not a directory or does not exist")
  329. frappe.init(site)
  330. old_path = frappe.utils.get_site_path()
  331. new_path = os.path.join(dest_dir, site)
  332. # check if site dump of same name already exists
  333. site_dump_exists = True
  334. count = 0
  335. while site_dump_exists:
  336. final_new_path = new_path + (count and str(count) or "")
  337. site_dump_exists = os.path.exists(final_new_path)
  338. count = int(count or 0) + 1
  339. os.rename(old_path, final_new_path)
  340. frappe.destroy()
  341. return final_new_path
  342. @click.command('set-admin-password')
  343. @click.argument('admin-password')
  344. @click.option('--logout-all-sessions', help='Logout from all sessions', is_flag=True, default=False)
  345. @pass_context
  346. def set_admin_password(context, admin_password, logout_all_sessions=False):
  347. "Set Administrator password for a site"
  348. import getpass
  349. from frappe.utils.password import update_password
  350. for site in context.sites:
  351. try:
  352. frappe.init(site=site)
  353. while not admin_password:
  354. admin_password = getpass.getpass("Administrator's password for {0}: ".format(site))
  355. frappe.connect()
  356. update_password(user='Administrator', pwd=admin_password, logout_all_sessions=logout_all_sessions)
  357. frappe.db.commit()
  358. admin_password = None
  359. finally:
  360. frappe.destroy()
  361. @click.command('set-limit')
  362. @click.option('--site', help='site name')
  363. @click.argument('limit')
  364. @click.argument('value')
  365. @pass_context
  366. def set_limit(context, site, limit, value):
  367. """Sets user / space / email limit for a site"""
  368. _set_limits(context, site, ((limit, value),))
  369. @click.command('set-limits')
  370. @click.option('--site', help='site name')
  371. @click.option('--limit', 'limits', type=(text_type, text_type), multiple=True)
  372. @pass_context
  373. def set_limits(context, site, limits):
  374. _set_limits(context, site, limits)
  375. def _set_limits(context, site, limits):
  376. import datetime
  377. if not limits:
  378. return
  379. if not site:
  380. site = get_site(context)
  381. with frappe.init_site(site):
  382. frappe.connect()
  383. new_limits = {}
  384. for limit, value in limits:
  385. if limit not in ('daily_emails', 'emails', 'space', 'users', 'email_group',
  386. 'expiry', 'support_email', 'support_chat', 'upgrade_url'):
  387. frappe.throw(_('Invalid limit {0}').format(limit))
  388. if limit=='expiry' and value:
  389. try:
  390. datetime.datetime.strptime(value, '%Y-%m-%d')
  391. except ValueError:
  392. raise ValueError("Incorrect data format, should be YYYY-MM-DD")
  393. elif limit=='space':
  394. value = float(value)
  395. elif limit in ('users', 'emails', 'email_group', 'daily_emails'):
  396. value = int(value)
  397. new_limits[limit] = value
  398. update_limits(new_limits)
  399. @click.command('clear-limits')
  400. @click.option('--site', help='site name')
  401. @click.argument('limits', nargs=-1, type=click.Choice(['emails', 'space', 'users', 'email_group',
  402. 'expiry', 'support_email', 'support_chat', 'upgrade_url', 'daily_emails']))
  403. @pass_context
  404. def clear_limits(context, site, limits):
  405. """Clears given limit from the site config, and removes limit from site config if its empty"""
  406. from frappe.limits import clear_limit as _clear_limit
  407. if not limits:
  408. return
  409. if not site:
  410. site = get_site(context)
  411. with frappe.init_site(site):
  412. _clear_limit(limits)
  413. # Remove limits from the site_config, if it's empty
  414. limits = get_limits()
  415. if not limits:
  416. update_site_config('limits', 'None', validate=False)
  417. @click.command('set-last-active-for-user')
  418. @click.option('--user', help="Setup last active date for user")
  419. @pass_context
  420. def set_last_active_for_user(context, user=None):
  421. "Set users last active date to current datetime"
  422. from frappe.core.doctype.user.user import get_system_users
  423. from frappe.utils.user import set_last_active_to_now
  424. site = get_site(context)
  425. with frappe.init_site(site):
  426. frappe.connect()
  427. if not user:
  428. user = get_system_users(limit=1)
  429. if len(user) > 0:
  430. user = user[0]
  431. else:
  432. return
  433. set_last_active_to_now(user)
  434. frappe.db.commit()
  435. @click.command('publish-realtime')
  436. @click.argument('event')
  437. @click.option('--message')
  438. @click.option('--room')
  439. @click.option('--user')
  440. @click.option('--doctype')
  441. @click.option('--docname')
  442. @click.option('--after-commit')
  443. @pass_context
  444. def publish_realtime(context, event, message, room, user, doctype, docname, after_commit):
  445. "Publish realtime event from bench"
  446. from frappe import publish_realtime
  447. for site in context.sites:
  448. try:
  449. frappe.init(site=site)
  450. frappe.connect()
  451. publish_realtime(event, message=message, room=room, user=user, doctype=doctype, docname=docname,
  452. after_commit=after_commit)
  453. frappe.db.commit()
  454. finally:
  455. frappe.destroy()
  456. commands = [
  457. add_system_manager,
  458. backup,
  459. drop_site,
  460. install_app,
  461. list_apps,
  462. migrate,
  463. new_site,
  464. reinstall,
  465. reload_doc,
  466. reload_doctype,
  467. remove_from_installed_apps,
  468. restore,
  469. run_patch,
  470. set_admin_password,
  471. uninstall,
  472. set_limit,
  473. set_limits,
  474. clear_limits,
  475. disable_user,
  476. _use,
  477. set_last_active_for_user,
  478. publish_realtime,
  479. ]