[fix] split commands into multiple files
This commit is contained in:
parent
1831cd0b02
commit
49de395407
7 changed files with 1225 additions and 1184 deletions
1184
frappe/commands.py
1184
frappe/commands.py
File diff suppressed because it is too large
Load diff
59
frappe/commands/__init__.py
Normal file
59
frappe/commands/__init__.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
import sys
|
||||
import click
|
||||
import cProfile
|
||||
import StringIO
|
||||
import pstats
|
||||
import frappe
|
||||
import frappe.utils
|
||||
from functools import wraps
|
||||
|
||||
click.disable_unicode_literals_warning = True
|
||||
|
||||
def pass_context(f):
|
||||
@wraps(f)
|
||||
def _func(ctx, *args, **kwargs):
|
||||
profile = ctx.obj['profile']
|
||||
if profile:
|
||||
pr = cProfile.Profile()
|
||||
pr.enable()
|
||||
|
||||
ret = f(frappe._dict(ctx.obj), *args, **kwargs)
|
||||
|
||||
if profile:
|
||||
pr.disable()
|
||||
s = StringIO.StringIO()
|
||||
ps = pstats.Stats(pr, stream=s)\
|
||||
.sort_stats('cumtime', 'tottime', 'ncalls')
|
||||
ps.print_stats()
|
||||
print s.getvalue()
|
||||
|
||||
return ret
|
||||
|
||||
return click.pass_context(_func)
|
||||
|
||||
def get_site(context):
|
||||
try:
|
||||
site = context.sites[0]
|
||||
return site
|
||||
except (IndexError, TypeError):
|
||||
print 'Please specify --site sitename'
|
||||
sys.exit(1)
|
||||
|
||||
def call_command(cmd, context):
|
||||
return click.Context(cmd, obj=context).forward(cmd)
|
||||
|
||||
def get_commands():
|
||||
# prevent circular imports
|
||||
from .docs import commands as doc_commands
|
||||
from .scheduler import commands as scheduler_commands
|
||||
from .site import commands as site_commands
|
||||
from .translate import commands as translate_commands
|
||||
from .utils import commands as utils_commands
|
||||
|
||||
return list(set(doc_commands + scheduler_commands + site_commands + translate_commands + utils_commands))
|
||||
|
||||
commands = get_commands()
|
||||
108
frappe/commands/docs.py
Normal file
108
frappe/commands/docs.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
from __future__ import unicode_literals, absolute_import
|
||||
import click
|
||||
import os
|
||||
import frappe
|
||||
from frappe.commands import pass_context
|
||||
|
||||
@click.command('make-docs')
|
||||
@pass_context
|
||||
@click.argument('app')
|
||||
@click.argument('docs_version')
|
||||
def make_docs(context, app, docs_version):
|
||||
"Setup docs in target folder of target app"
|
||||
from frappe.utils.setup_docs import setup_docs
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
make = setup_docs(app)
|
||||
make.build(docs_version)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('sync-docs')
|
||||
@pass_context
|
||||
@click.argument('app')
|
||||
def sync_docs(context, app):
|
||||
"Sync docs from /docs folder into the database (Web Page)"
|
||||
from frappe.utils.setup_docs import setup_docs
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
make = setup_docs(app)
|
||||
make.sync_docs()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
|
||||
@click.command('write-docs')
|
||||
@pass_context
|
||||
@click.argument('app')
|
||||
@click.argument('target')
|
||||
@click.option('--local', default=False, is_flag=True, help='Run app locally')
|
||||
def write_docs(context, app, target, local=False):
|
||||
"Setup docs in target folder of target app"
|
||||
from frappe.utils.setup_docs import setup_docs
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
make = setup_docs(app)
|
||||
make.make_docs(target, local)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('build-docs')
|
||||
@pass_context
|
||||
@click.argument('app')
|
||||
@click.option('--docs-version', default='current')
|
||||
@click.option('--target', default=None)
|
||||
@click.option('--local', default=False, is_flag=True, help='Run app locally')
|
||||
@click.option('--watch', default=False, is_flag=True, help='Watch for changes and rewrite')
|
||||
def build_docs(context, app, docs_version="current", target=None, local=False, watch=False):
|
||||
"Setup docs in target folder of target app"
|
||||
from frappe.utils import watch as start_watch
|
||||
if not target:
|
||||
target = os.path.abspath(os.path.join("..", "docs", app))
|
||||
|
||||
for site in context.sites:
|
||||
_build_docs_once(site, app, docs_version, target, local)
|
||||
|
||||
if watch:
|
||||
def trigger_make(source_path, event_type):
|
||||
if "/templates/autodoc/" in source_path:
|
||||
_build_docs_once(site, app, docs_version, target, local)
|
||||
|
||||
elif ("/docs.css" in source_path
|
||||
or "/docs/" in source_path
|
||||
or "docs.py" in source_path):
|
||||
_build_docs_once(site, app, docs_version, target, local, only_content_updated=True)
|
||||
|
||||
apps_path = frappe.get_app_path("frappe", "..", "..")
|
||||
start_watch(apps_path, handler=trigger_make)
|
||||
|
||||
def _build_docs_once(site, app, docs_version, target, local, only_content_updated=False):
|
||||
from frappe.utils.setup_docs import setup_docs
|
||||
|
||||
try:
|
||||
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
make = setup_docs(app)
|
||||
|
||||
if not only_content_updated:
|
||||
make.build(docs_version)
|
||||
make.sync_docs()
|
||||
|
||||
make.make_docs(target, local)
|
||||
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
commands = [
|
||||
build_docs,
|
||||
make_docs,
|
||||
sync_docs,
|
||||
write_docs,
|
||||
]
|
||||
202
frappe/commands/scheduler.py
Normal file
202
frappe/commands/scheduler.py
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
from __future__ import unicode_literals, absolute_import
|
||||
import click
|
||||
import json, sys
|
||||
import frappe
|
||||
from frappe.utils import cint
|
||||
from frappe.commands import pass_context, get_site
|
||||
|
||||
def _is_scheduler_enabled():
|
||||
enable_scheduler = False
|
||||
try:
|
||||
frappe.connect()
|
||||
enable_scheduler = cint(frappe.db.get_single_value("System Settings", "enable_scheduler")) and True or False
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
frappe.db.close()
|
||||
|
||||
return enable_scheduler
|
||||
|
||||
@click.command('trigger-scheduler-event')
|
||||
@click.argument('event')
|
||||
@pass_context
|
||||
def trigger_scheduler_event(context, event):
|
||||
"Trigger a scheduler event"
|
||||
import frappe.utils.scheduler
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.utils.scheduler.trigger(site, event, now=context.force)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('enable-scheduler')
|
||||
@pass_context
|
||||
def enable_scheduler(context):
|
||||
"Enable scheduler"
|
||||
import frappe.utils.scheduler
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.utils.scheduler.enable_scheduler()
|
||||
frappe.db.commit()
|
||||
print "Enabled for", site
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('disable-scheduler')
|
||||
@pass_context
|
||||
def disable_scheduler(context):
|
||||
"Disable scheduler"
|
||||
import frappe.utils.scheduler
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.utils.scheduler.disable_scheduler()
|
||||
frappe.db.commit()
|
||||
print "Disabled for", site
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
|
||||
|
||||
@click.command('scheduler')
|
||||
@click.option('--site', help='site name')
|
||||
@click.argument('state', type=click.Choice(['pause', 'resume', 'disable', 'enable']))
|
||||
@pass_context
|
||||
def scheduler(context, state, site=None):
|
||||
from frappe.installer import update_site_config
|
||||
import frappe.utils.scheduler
|
||||
|
||||
if not site:
|
||||
site = get_site(context)
|
||||
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
|
||||
if state == 'pause':
|
||||
update_site_config('pause_scheduler', 1)
|
||||
elif state == 'resume':
|
||||
update_site_config('pause_scheduler', 0)
|
||||
elif state == 'disable':
|
||||
frappe.connect()
|
||||
frappe.utils.scheduler.disable_scheduler()
|
||||
frappe.db.commit()
|
||||
elif state == 'enable':
|
||||
frappe.connect()
|
||||
frappe.utils.scheduler.enable_scheduler()
|
||||
frappe.db.commit()
|
||||
|
||||
print 'Scheduler {0}d for site {1}'.format(state, site)
|
||||
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
|
||||
@click.command('set-maintenance-mode')
|
||||
@click.option('--site', help='site name')
|
||||
@click.argument('state', type=click.Choice(['on', 'off']))
|
||||
@pass_context
|
||||
def set_maintenance_mode(context, state, site=None):
|
||||
from frappe.installer import update_site_config
|
||||
if not site:
|
||||
site = get_site(context)
|
||||
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
update_site_config('maintenance_mode', 1 if (state == 'on') else 0)
|
||||
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
|
||||
@click.command('doctor') #Passing context always gets a site and if there is no use site it breaks
|
||||
@click.option('--site', help='site name')
|
||||
def doctor(site=None):
|
||||
"Get diagnostic info about background workers"
|
||||
from frappe.utils.doctor import doctor as _doctor
|
||||
return _doctor(site=site)
|
||||
|
||||
@click.command('show-pending-jobs')
|
||||
@click.option('--site', help='site name')
|
||||
@pass_context
|
||||
def show_pending_jobs(context, site=None):
|
||||
"Get diagnostic info about background jobs"
|
||||
if not site:
|
||||
site = get_site(context)
|
||||
|
||||
from frappe.utils.doctor import pending_jobs as _pending_jobs
|
||||
return _pending_jobs(site=site)
|
||||
|
||||
@click.command('purge-jobs')
|
||||
@click.option('--site', help='site name')
|
||||
@click.option('--queue', default=None, help='one of "low", "default", "high')
|
||||
@click.option('--event', default=None, help='one of "all", "weekly", "monthly", "hourly", "daily", "weekly_long", "daily_long"')
|
||||
def purge_jobs(site=None, queue=None, event=None):
|
||||
"Purge any pending periodic tasks, if event option is not given, it will purge everything for the site"
|
||||
from frappe.utils.doctor import purge_pending_jobs
|
||||
frappe.init(site or '')
|
||||
count = purge_pending_jobs(event=event, site=site, queue=queue)
|
||||
print "Purged {} jobs".format(count)
|
||||
|
||||
@click.command('dump-queue-status')
|
||||
def dump_queue_status():
|
||||
"Dump detailed diagnostic infomation for task queues in JSON format"
|
||||
frappe.init('')
|
||||
from frappe.utils.doctor import dump_queue_status as _dump_queue_status, inspect_queue
|
||||
print json.dumps(_dump_queue_status(), indent=1)
|
||||
inspect_queue()
|
||||
|
||||
|
||||
@click.command('schedule')
|
||||
def start_scheduler():
|
||||
from frappe.utils.scheduler import start_scheduler
|
||||
start_scheduler()
|
||||
|
||||
@click.command('worker')
|
||||
@click.option('--queue', type=str)
|
||||
def start_worker(queue):
|
||||
from frappe.utils.background_jobs import start_worker
|
||||
start_worker(queue)
|
||||
|
||||
@click.command('ready-for-migration')
|
||||
@click.option('--site', help='site name')
|
||||
@pass_context
|
||||
def ready_for_migration(context, site=None):
|
||||
from frappe.utils.doctor import get_pending_jobs
|
||||
|
||||
if not site:
|
||||
site = get_site(context)
|
||||
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
pending_jobs = get_pending_jobs(site=site)
|
||||
|
||||
if pending_jobs:
|
||||
print 'NOT READY for migration: site {0} has pending background jobs'.format(site)
|
||||
sys.exit(1)
|
||||
|
||||
else:
|
||||
print 'READY for migration: site {0} does not have any background jobs'.format(site)
|
||||
return 0
|
||||
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
commands = [
|
||||
disable_scheduler,
|
||||
doctor,
|
||||
dump_queue_status,
|
||||
enable_scheduler,
|
||||
purge_jobs,
|
||||
ready_for_migration,
|
||||
scheduler,
|
||||
set_maintenance_mode,
|
||||
show_pending_jobs,
|
||||
start_scheduler,
|
||||
start_worker,
|
||||
trigger_scheduler_event,
|
||||
]
|
||||
348
frappe/commands/site.py
Normal file
348
frappe/commands/site.py
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
from __future__ import unicode_literals, absolute_import
|
||||
import click
|
||||
import hashlib, os
|
||||
import frappe
|
||||
from frappe.commands import pass_context, get_site
|
||||
from frappe.commands.scheduler import _is_scheduler_enabled
|
||||
|
||||
@click.command('new-site')
|
||||
@click.argument('site')
|
||||
@click.option('--db-name', help='Database name')
|
||||
@click.option('--mariadb-root-username', default='root', help='Root username for MariaDB')
|
||||
@click.option('--mariadb-root-password', help='Root password for MariaDB')
|
||||
@click.option('--admin-password', help='Administrator password for new site', default=None)
|
||||
@click.option('--verbose', is_flag=True, default=False, help='Verbose')
|
||||
@click.option('--force', help='Force restore if site/database already exists', is_flag=True, default=False)
|
||||
@click.option('--source_sql', help='Initiate database with a SQL file')
|
||||
@click.option('--install-app', multiple=True, help='Install app after installation')
|
||||
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):
|
||||
"Create a new site"
|
||||
if not db_name:
|
||||
db_name = hashlib.sha1(site).hexdigest()[:10]
|
||||
|
||||
frappe.init(site=site, new_site=True)
|
||||
_new_site(db_name, site, mariadb_root_username=mariadb_root_username, mariadb_root_password=mariadb_root_password, admin_password=admin_password, verbose=verbose, install_apps=install_app, source_sql=source_sql, force=force)
|
||||
if len(frappe.utils.get_sites()) == 1:
|
||||
use(site)
|
||||
|
||||
def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=None, admin_password=None, verbose=False, install_apps=None, source_sql=None,force=False, reinstall=False):
|
||||
"Install a new Frappe site"
|
||||
from frappe.installer import install_db, make_site_dirs
|
||||
from frappe.installer import install_app as _install_app
|
||||
import frappe.utils.scheduler
|
||||
|
||||
frappe.init(site=site)
|
||||
|
||||
try:
|
||||
# enable scheduler post install?
|
||||
enable_scheduler = _is_scheduler_enabled()
|
||||
except:
|
||||
enable_scheduler = False
|
||||
|
||||
install_db(root_login=mariadb_root_username, root_password=mariadb_root_password, db_name=db_name, admin_password=admin_password, verbose=verbose, source_sql=source_sql,force=force, reinstall=reinstall)
|
||||
make_site_dirs()
|
||||
_install_app("frappe", verbose=verbose, set_as_patched=not source_sql)
|
||||
|
||||
if frappe.conf.get("install_apps"):
|
||||
for app in frappe.conf.install_apps:
|
||||
_install_app(app, verbose=verbose, set_as_patched=not source_sql)
|
||||
|
||||
if install_apps:
|
||||
for app in install_apps:
|
||||
_install_app(app, verbose=verbose, set_as_patched=not source_sql)
|
||||
|
||||
frappe.utils.scheduler.toggle_scheduler(enable_scheduler)
|
||||
frappe.db.commit()
|
||||
|
||||
scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled"
|
||||
print "*** Scheduler is", scheduler_status, "***"
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('restore')
|
||||
@click.argument('sql-file-path')
|
||||
@click.option('--mariadb-root-username', default='root', help='Root username for MariaDB')
|
||||
@click.option('--mariadb-root-password', help='Root password for MariaDB')
|
||||
@click.option('--db-name', help='Database name for site in case it is a new one')
|
||||
@click.option('--admin-password', help='Administrator password for new site')
|
||||
@click.option('--install-app', multiple=True, help='Install app after installation')
|
||||
@click.option('--with-public-files', help='Restores the public files of the site, given path to its tar file')
|
||||
@click.option('--with-private-files', help='Restores the private files of the site, given path to its tar file')
|
||||
@pass_context
|
||||
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):
|
||||
"Restore site database from an sql file"
|
||||
from frappe.installer import extract_sql_gzip, extract_tar_files
|
||||
# Extract the gzip file if user has passed *.sql.gz file instead of *.sql file
|
||||
if sql_file_path.endswith('sql.gz'):
|
||||
sql_file_path = extract_sql_gzip(os.path.abspath(sql_file_path))
|
||||
|
||||
site = get_site(context)
|
||||
frappe.init(site=site)
|
||||
db_name = db_name or frappe.conf.db_name or hashlib.sha1(site).hexdigest()[:10]
|
||||
_new_site(db_name, site, mariadb_root_username=mariadb_root_username, mariadb_root_password=mariadb_root_password, admin_password=admin_password, verbose=context.verbose, install_apps=install_app, source_sql=sql_file_path, force=context.force)
|
||||
|
||||
# Extract public and/or private files to the restored site, if user has given the path
|
||||
if with_public_files:
|
||||
extract_tar_files(site, with_public_files, 'public')
|
||||
|
||||
if with_private_files:
|
||||
extract_tar_files(site, with_private_files, 'private')
|
||||
|
||||
@click.command('reinstall')
|
||||
@pass_context
|
||||
def reinstall(context):
|
||||
"Reinstall site ie. wipe all data and start over"
|
||||
site = get_site(context)
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.clear_cache()
|
||||
installed = frappe.get_installed_apps()
|
||||
frappe.clear_cache()
|
||||
except Exception:
|
||||
installed = []
|
||||
finally:
|
||||
if frappe.db:
|
||||
frappe.db.close()
|
||||
frappe.destroy()
|
||||
|
||||
frappe.init(site=site)
|
||||
_new_site(frappe.conf.db_name, site, verbose=context.verbose, force=True, reinstall=True, install_apps=installed)
|
||||
|
||||
@click.command('install-app')
|
||||
@click.argument('app')
|
||||
@pass_context
|
||||
def install_app(context, app):
|
||||
"Install a new app to site"
|
||||
from frappe.installer import install_app as _install_app
|
||||
for site in context.sites:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
try:
|
||||
_install_app(app, verbose=context.verbose)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('list-apps')
|
||||
@pass_context
|
||||
def list_apps(context):
|
||||
"List apps in site"
|
||||
site = get_site(context)
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
print "\n".join(frappe.get_installed_apps())
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('add-system-manager')
|
||||
@click.argument('email')
|
||||
@click.option('--first-name')
|
||||
@click.option('--last-name')
|
||||
@pass_context
|
||||
def add_system_manager(context, email, first_name, last_name):
|
||||
"Add a new system manager to a site"
|
||||
import frappe.utils.user
|
||||
for site in context.sites:
|
||||
frappe.connect(site=site)
|
||||
try:
|
||||
frappe.utils.user.add_system_manager(email, first_name, last_name)
|
||||
frappe.db.commit()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('migrate')
|
||||
@click.option('--rebuild-website', help="Rebuild webpages after migration")
|
||||
@pass_context
|
||||
def migrate(context, rebuild_website=False):
|
||||
"Run patches, sync schema and rebuild files/translations"
|
||||
from frappe.migrate import migrate
|
||||
|
||||
for site in context.sites:
|
||||
print 'Migrating', site
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
try:
|
||||
migrate(context.verbose, rebuild_website=rebuild_website)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('run-patch')
|
||||
@click.argument('module')
|
||||
@pass_context
|
||||
def run_patch(context, module):
|
||||
"Run a particular patch"
|
||||
import frappe.modules.patch_handler
|
||||
for site in context.sites:
|
||||
frappe.init(site=site)
|
||||
try:
|
||||
frappe.connect()
|
||||
frappe.modules.patch_handler.run_single(module, force=context.force)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('reload-doc')
|
||||
@click.argument('module')
|
||||
@click.argument('doctype')
|
||||
@click.argument('docname')
|
||||
@pass_context
|
||||
def reload_doc(context, module, doctype, docname):
|
||||
"Reload schema for a DocType"
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.reload_doc(module, doctype, docname, force=context.force)
|
||||
frappe.db.commit()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
|
||||
@click.command('use')
|
||||
@click.argument('site')
|
||||
def _use(site, sites_path='.'):
|
||||
"Set a default site"
|
||||
use(site, sites_path=sites_path)
|
||||
|
||||
def use(site, sites_path='.'):
|
||||
with open(os.path.join(sites_path, "currentsite.txt"), "w") as sitefile:
|
||||
sitefile.write(site)
|
||||
|
||||
@click.command('backup')
|
||||
@click.option('--with-files', default=False, is_flag=True, help="Take backup with files")
|
||||
@pass_context
|
||||
def backup(context, with_files=False, backup_path_db=None, backup_path_files=None,
|
||||
backup_path_private_files=None, quiet=False):
|
||||
"Backup"
|
||||
from frappe.utils.backups import scheduled_backup
|
||||
verbose = context.verbose
|
||||
for site in context.sites:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
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)
|
||||
if verbose:
|
||||
from frappe.utils import now
|
||||
print "database backup taken -", odb.backup_path_db, "- on", now()
|
||||
if with_files:
|
||||
print "files backup taken -", odb.backup_path_files, "- on", now()
|
||||
print "private files backup taken -", odb.backup_path_private_files, "- on", now()
|
||||
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('remove-from-installed-apps')
|
||||
@click.argument('app')
|
||||
@pass_context
|
||||
def remove_from_installed_apps(context, app):
|
||||
"Remove app from site's installed-apps list"
|
||||
from frappe.installer import remove_from_installed_apps
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
remove_from_installed_apps(app)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('uninstall-app')
|
||||
@click.argument('app')
|
||||
@click.option('--dry-run', help='List all doctypes that will be deleted', is_flag=True, default=False)
|
||||
@pass_context
|
||||
def uninstall(context, app, dry_run=False):
|
||||
"Remove app and linked modules from site"
|
||||
from frappe.installer import remove_app
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
remove_app(app, dry_run)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
|
||||
@click.command('drop-site')
|
||||
@click.argument('site')
|
||||
@click.option('--root-login', default='root')
|
||||
@click.option('--root-password')
|
||||
@click.option('--archived-sites-path')
|
||||
def drop_site(site, root_login='root', root_password=None, archived_sites_path=None):
|
||||
"Remove site from database and filesystem"
|
||||
from frappe.installer import get_current_host, make_connection
|
||||
from frappe.model.db_schema import DbManager
|
||||
from frappe.utils.backups import scheduled_backup
|
||||
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
scheduled_backup(ignore_files=False, force=True)
|
||||
|
||||
db_name = frappe.local.conf.db_name
|
||||
frappe.local.db = make_connection(root_login, root_password)
|
||||
dbman = DbManager(frappe.local.db)
|
||||
dbman.delete_user(db_name, get_current_host())
|
||||
dbman.drop_database(db_name)
|
||||
|
||||
if not archived_sites_path:
|
||||
archived_sites_path = os.path.join(frappe.get_app_path('frappe'), '..', '..', '..', 'archived_sites')
|
||||
|
||||
if not os.path.exists(archived_sites_path):
|
||||
os.mkdir(archived_sites_path)
|
||||
|
||||
move(archived_sites_path, site)
|
||||
|
||||
def move(dest_dir, site):
|
||||
import os
|
||||
if not os.path.isdir(dest_dir):
|
||||
raise Exception, "destination is not a directory or does not exist"
|
||||
|
||||
frappe.init(site)
|
||||
old_path = frappe.utils.get_site_path()
|
||||
new_path = os.path.join(dest_dir, site)
|
||||
|
||||
# check if site dump of same name already exists
|
||||
site_dump_exists = True
|
||||
count = 0
|
||||
while site_dump_exists:
|
||||
final_new_path = new_path + (count and str(count) or "")
|
||||
site_dump_exists = os.path.exists(final_new_path)
|
||||
count = int(count or 0) + 1
|
||||
|
||||
os.rename(old_path, final_new_path)
|
||||
frappe.destroy()
|
||||
return final_new_path
|
||||
|
||||
|
||||
@click.command('set-admin-password')
|
||||
@click.argument('admin-password')
|
||||
@pass_context
|
||||
def set_admin_password(context, admin_password):
|
||||
"Set Administrator password for a site"
|
||||
import getpass
|
||||
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
|
||||
while not admin_password:
|
||||
admin_password = getpass.getpass("Administrator's password for {0}: ".format(site))
|
||||
|
||||
frappe.connect()
|
||||
frappe.db.sql("""update __Auth set `password`=password(%s)
|
||||
where user='Administrator'""", (admin_password,))
|
||||
frappe.db.commit()
|
||||
admin_password = None
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
commands = [
|
||||
add_system_manager,
|
||||
backup,
|
||||
drop_site,
|
||||
install_app,
|
||||
list_apps,
|
||||
migrate,
|
||||
new_site,
|
||||
reinstall,
|
||||
reload_doc,
|
||||
remove_from_installed_apps,
|
||||
restore,
|
||||
run_patch,
|
||||
set_admin_password,
|
||||
uninstall,
|
||||
_use,
|
||||
]
|
||||
91
frappe/commands/translate.py
Normal file
91
frappe/commands/translate.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
from __future__ import unicode_literals, absolute_import
|
||||
import click
|
||||
import frappe
|
||||
from frappe.commands import pass_context, get_site
|
||||
|
||||
# translation
|
||||
@click.command('build-message-files')
|
||||
@pass_context
|
||||
def build_message_files(context):
|
||||
"Build message files for translation"
|
||||
import frappe.translate
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.translate.rebuild_all_translation_files()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('new-language') #, help="Create lang-code.csv for given app")
|
||||
@pass_context
|
||||
@click.argument('lang_code') #, help="Language code eg. en")
|
||||
@click.argument('app') #, help="App name eg. frappe")
|
||||
def new_language(context, lang_code, app):
|
||||
"""Create lang-code.csv for given app"""
|
||||
import frappe.translate
|
||||
|
||||
if not context['sites']:
|
||||
raise Exception('--site is required')
|
||||
|
||||
# init site
|
||||
frappe.connect(site=context['sites'][0])
|
||||
frappe.translate.write_translations_file(app, lang_code)
|
||||
|
||||
print "File created at ./apps/{app}/{app}/translations/{lang_code}.csv".format(app=app, lang_code=lang_code)
|
||||
print "You will need to add the language in frappe/data/languages.txt, if you haven't done it already."
|
||||
|
||||
@click.command('get-untranslated')
|
||||
@click.argument('lang')
|
||||
@click.argument('untranslated_file')
|
||||
@click.option('--all', default=False, is_flag=True, help='Get all message strings')
|
||||
@pass_context
|
||||
def get_untranslated(context, lang, untranslated_file, all=None):
|
||||
"Get untranslated strings for language"
|
||||
import frappe.translate
|
||||
site = get_site(context)
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.translate.get_untranslated(lang, untranslated_file, get_all=all)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('update-translations')
|
||||
@click.argument('lang')
|
||||
@click.argument('untranslated_file')
|
||||
@click.argument('translated-file')
|
||||
@pass_context
|
||||
def update_translations(context, lang, untranslated_file, translated_file):
|
||||
"Update translated strings"
|
||||
import frappe.translate
|
||||
site = get_site(context)
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.translate.update_translations(lang, untranslated_file, translated_file)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('import-translations')
|
||||
@click.argument('lang')
|
||||
@click.argument('path')
|
||||
@pass_context
|
||||
def import_translations(context, lang, path):
|
||||
"Update translated strings"
|
||||
import frappe.translate
|
||||
site = get_site(context)
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.translate.import_translations(lang, path)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
commands = [
|
||||
build_message_files,
|
||||
get_untranslated,
|
||||
import_translations,
|
||||
new_language,
|
||||
update_translations,
|
||||
]
|
||||
417
frappe/commands/utils.py
Normal file
417
frappe/commands/utils.py
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
from __future__ import unicode_literals, absolute_import
|
||||
import click
|
||||
import json, os, sys
|
||||
from distutils.spawn import find_executable
|
||||
import frappe
|
||||
from frappe.commands import pass_context, get_site
|
||||
|
||||
@click.command('build')
|
||||
@click.option('--make-copy', is_flag=True, default=False, help='Copy the files instead of symlinking')
|
||||
@click.option('--verbose', is_flag=True, default=False, help='Verbose')
|
||||
def build(make_copy=False, verbose=False):
|
||||
"Minify + concatenate JS and CSS files, build translations"
|
||||
import frappe.build
|
||||
import frappe
|
||||
frappe.init('')
|
||||
frappe.build.bundle(False, make_copy=make_copy, verbose=verbose)
|
||||
|
||||
@click.command('watch')
|
||||
def watch():
|
||||
"Watch and concatenate JS and CSS files as and when they change"
|
||||
import frappe.build
|
||||
frappe.init('')
|
||||
frappe.build.watch(True)
|
||||
|
||||
@click.command('clear-cache')
|
||||
@pass_context
|
||||
def clear_cache(context):
|
||||
"Clear cache, doctype cache and defaults"
|
||||
import frappe.sessions
|
||||
import frappe.website.render
|
||||
from frappe.desk.notifications import clear_notifications
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.connect(site)
|
||||
frappe.clear_cache()
|
||||
clear_notifications()
|
||||
frappe.website.render.clear_cache()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('clear-website-cache')
|
||||
@pass_context
|
||||
def clear_website_cache(context):
|
||||
"Clear website cache"
|
||||
import frappe.website.render
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.website.render.clear_cache()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('destroy-all-sessions')
|
||||
@pass_context
|
||||
def destroy_all_sessions(context):
|
||||
"Clear sessions of all users (logs them out)"
|
||||
import frappe.sessions
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.sessions.clear_all_sessions()
|
||||
frappe.db.commit()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('sync-www')
|
||||
@click.option('--force', help='Rebuild all pages', is_flag=True, default=False)
|
||||
@pass_context
|
||||
def sync_www(context, force=False):
|
||||
"Sync files from static pages from www directory to Web Pages"
|
||||
from frappe.website import statics
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
statics.sync_statics(rebuild=force)
|
||||
frappe.db.commit()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('build-website')
|
||||
@pass_context
|
||||
def build_website(context):
|
||||
"Sync statics and clear cache"
|
||||
from frappe.website import render, statics
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
render.clear_cache()
|
||||
statics.sync(verbose=context.verbose).start(rebuild=True)
|
||||
frappe.db.commit()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('reset-perms')
|
||||
@pass_context
|
||||
def reset_perms(context):
|
||||
"Reset permissions for all doctypes"
|
||||
from frappe.permissions import reset_perms
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
for d in frappe.db.sql_list("""select name from `tabDocType`
|
||||
where istable=0 and custom=0"""):
|
||||
frappe.clear_cache(doctype=d)
|
||||
reset_perms(d)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('execute')
|
||||
@click.argument('method')
|
||||
@click.option('--args')
|
||||
@click.option('--kwargs')
|
||||
@pass_context
|
||||
def execute(context, method, args=None, kwargs=None):
|
||||
"Execute a function"
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
|
||||
if args:
|
||||
try:
|
||||
args = eval(args)
|
||||
except NameError:
|
||||
args = [args]
|
||||
else:
|
||||
args = ()
|
||||
|
||||
if kwargs:
|
||||
kwargs = eval(args)
|
||||
else:
|
||||
kwargs = {}
|
||||
|
||||
ret = frappe.get_attr(method)(*args, **kwargs)
|
||||
|
||||
if frappe.db:
|
||||
frappe.db.commit()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
if ret:
|
||||
print json.dumps(ret)
|
||||
|
||||
|
||||
@click.command('export-doc')
|
||||
@click.argument('doctype')
|
||||
@click.argument('docname')
|
||||
@pass_context
|
||||
def export_doc(context, doctype, docname):
|
||||
"Export a single document to csv"
|
||||
import frappe.modules
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.modules.export_doc(doctype, docname)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('export-json')
|
||||
@click.argument('doctype')
|
||||
@click.argument('name')
|
||||
@click.argument('path')
|
||||
@pass_context
|
||||
def export_json(context, doctype, name, path):
|
||||
"Export doclist as json to the given path, use '-' as name for Singles."
|
||||
from frappe.core.page.data_import_tool import data_import_tool
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
data_import_tool.export_json(doctype, path, name=name)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('export-csv')
|
||||
@click.argument('doctype')
|
||||
@click.argument('path')
|
||||
@pass_context
|
||||
def export_csv(context, doctype, path):
|
||||
"Export data import template for DocType"
|
||||
from frappe.core.page.data_import_tool import data_import_tool
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
data_import_tool.export_csv(doctype, path)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('export-fixtures')
|
||||
@pass_context
|
||||
def export_fixtures(context):
|
||||
"Export fixtures"
|
||||
from frappe.utils.fixtures import export_fixtures
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
export_fixtures()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('import-doc')
|
||||
@click.argument('path')
|
||||
@pass_context
|
||||
def import_doc(context, path, force=False):
|
||||
"Import (insert/update) doclist. If the argument is a directory, all files ending with .json are imported"
|
||||
from frappe.core.page.data_import_tool import data_import_tool
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
data_import_tool.import_doc(path, overwrite=context.force)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('import-csv')
|
||||
@click.argument('path')
|
||||
@click.option('--only-insert', default=False, is_flag=True, help='Do not overwrite existing records')
|
||||
@click.option('--submit-after-import', default=False, is_flag=True, help='Submit document after importing it')
|
||||
@click.option('--ignore-encoding-errors', default=False, is_flag=True, help='Ignore encoding errors while coverting to unicode')
|
||||
@pass_context
|
||||
def import_csv(context, path, only_insert=False, submit_after_import=False, ignore_encoding_errors=False):
|
||||
"Import CSV using data import tool"
|
||||
from frappe.core.page.data_import_tool import importer
|
||||
from frappe.utils.csvutils import read_csv_content
|
||||
site = get_site(context)
|
||||
|
||||
with open(path, 'r') as csvfile:
|
||||
content = read_csv_content(csvfile.read())
|
||||
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
|
||||
try:
|
||||
importer.upload(content, submit_after_import=submit_after_import,
|
||||
ignore_encoding_errors=ignore_encoding_errors, overwrite=not only_insert,
|
||||
via_console=True)
|
||||
frappe.db.commit()
|
||||
except Exception:
|
||||
print frappe.get_traceback()
|
||||
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('bulk-rename')
|
||||
@click.argument('doctype')
|
||||
@click.argument('path')
|
||||
@pass_context
|
||||
def _bulk_rename(context, doctype, path):
|
||||
"Rename multiple records via CSV file"
|
||||
from frappe.model.rename_doc import bulk_rename
|
||||
from frappe.utils.csvutils import read_csv_content
|
||||
|
||||
site = get_site(context)
|
||||
|
||||
with open(path, 'r') as csvfile:
|
||||
rows = read_csv_content(csvfile.read())
|
||||
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
|
||||
bulk_rename(doctype, rows, via_console = True)
|
||||
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('mysql')
|
||||
@pass_context
|
||||
def mysql(context):
|
||||
"Start Mariadb console for a site"
|
||||
site = get_site(context)
|
||||
frappe.init(site=site)
|
||||
msq = find_executable('mysql')
|
||||
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"])
|
||||
|
||||
@click.command('console')
|
||||
@pass_context
|
||||
def console(context):
|
||||
"Start ipython console for a site"
|
||||
site = get_site(context)
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.local.lang = frappe.db.get_default("lang")
|
||||
import IPython
|
||||
IPython.embed()
|
||||
|
||||
@click.command('run-tests')
|
||||
@click.option('--app', help="For App")
|
||||
@click.option('--doctype', help="For DocType")
|
||||
@click.option('--test', multiple=True, help="Specific test")
|
||||
@click.option('--driver', help="For Travis")
|
||||
@click.option('--module', help="Run tests in a module")
|
||||
@click.option('--profile', is_flag=True, default=False)
|
||||
@pass_context
|
||||
def run_tests(context, app=None, module=None, doctype=None, test=(), driver=None, profile=False):
|
||||
"Run tests"
|
||||
import frappe.test_runner
|
||||
from frappe.utils import sel
|
||||
tests = test
|
||||
|
||||
site = get_site(context)
|
||||
frappe.init(site=site)
|
||||
|
||||
if frappe.conf.run_selenium_tests and False:
|
||||
sel.start(context.verbose, driver)
|
||||
|
||||
try:
|
||||
ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests,
|
||||
force=context.force, profile=profile)
|
||||
if len(ret.failures) == 0 and len(ret.errors) == 0:
|
||||
ret = 0
|
||||
finally:
|
||||
pass
|
||||
if frappe.conf.run_selenium_tests:
|
||||
sel.close()
|
||||
|
||||
sys.exit(ret)
|
||||
|
||||
@click.command('serve')
|
||||
@click.option('--port', default=8000)
|
||||
@click.option('--profile', is_flag=True, default=False)
|
||||
@pass_context
|
||||
def serve(context, port=None, profile=False, sites_path='.', site=None):
|
||||
"Start development web server"
|
||||
import frappe.app
|
||||
|
||||
if not context.sites:
|
||||
site = None
|
||||
else:
|
||||
site = context.sites[0]
|
||||
|
||||
frappe.app.serve(port=port, profile=profile, site=site, sites_path='.')
|
||||
|
||||
@click.command('request')
|
||||
@click.argument('args')
|
||||
@pass_context
|
||||
def request(context, args):
|
||||
"Run a request as an admin"
|
||||
import frappe.handler
|
||||
import frappe.api
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
if "?" in args:
|
||||
frappe.local.form_dict = frappe._dict([a.split("=") for a in args.split("?")[-1].split("&")])
|
||||
else:
|
||||
frappe.local.form_dict = frappe._dict()
|
||||
|
||||
if args.startswith("/api/method"):
|
||||
frappe.local.form_dict.cmd = args.split("?")[0].split("/")[-1]
|
||||
|
||||
frappe.handler.execute_cmd(frappe.form_dict.cmd)
|
||||
|
||||
print frappe.response
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('make-app')
|
||||
@click.argument('destination')
|
||||
@click.argument('app_name')
|
||||
def make_app(destination, app_name):
|
||||
"Creates a boilerplate app"
|
||||
from frappe.utils.boilerplate import make_boilerplate
|
||||
make_boilerplate(destination, app_name)
|
||||
|
||||
@click.command('set-config')
|
||||
@click.argument('key')
|
||||
@click.argument('value')
|
||||
@pass_context
|
||||
def set_config(context, key, value):
|
||||
"Insert/Update a value in site_config.json"
|
||||
from frappe.installer import update_site_config
|
||||
for site in context.sites:
|
||||
frappe.init(site=site)
|
||||
update_site_config(key, value)
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('version')
|
||||
def get_version():
|
||||
"Show the versions of all the installed apps"
|
||||
frappe.init('')
|
||||
for m in sorted(frappe.get_all_apps()):
|
||||
module = frappe.get_module(m)
|
||||
if hasattr(module, "__version__"):
|
||||
print "{0} {1}".format(m, module.__version__)
|
||||
|
||||
commands = [
|
||||
build,
|
||||
build_website,
|
||||
clear_cache,
|
||||
clear_website_cache,
|
||||
console,
|
||||
destroy_all_sessions,
|
||||
execute,
|
||||
export_csv,
|
||||
export_doc,
|
||||
export_fixtures,
|
||||
export_json,
|
||||
get_version,
|
||||
import_csv,
|
||||
import_doc,
|
||||
make_app,
|
||||
mysql,
|
||||
request,
|
||||
reset_perms,
|
||||
run_tests,
|
||||
serve,
|
||||
set_config,
|
||||
sync_www,
|
||||
watch,
|
||||
_bulk_rename,
|
||||
]
|
||||
Loading…
Add table
Reference in a new issue