@@ -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() |
@@ -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, | |||||
] |
@@ -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, | |||||
] |
@@ -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, | |||||
] |
@@ -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, | |||||
] |
@@ -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, | |||||
] |