Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 
 
 

486 rindas
15 KiB

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