def _drop_site(site, root_login='******', root_password=None, archived_sites_path=None, force=False, no_backup=False): "Remove site from database and filesystem" from frappe.database import drop_user_and_database from frappe.utils.backups import scheduled_backup frappe.init(site=site) frappe.connect() try: if not no_backup: scheduled_backup(ignore_files=False, force=True) except Exception as err: if force: pass else: messages = [ "=" * 80, "Error: The operation has stopped because backup of {0}'s database failed.".format(site), "Reason: {0}\n".format(str(err)), "Fix the issue and try again.", "Hint: Use 'bench drop-site {0} --force' to force the removal of {0}".format(site) ] click.echo("\n".join(messages)) sys.exit(1) drop_user_and_database(frappe.conf.db_name, root_login, root_password) 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 _drop_site(site, root_login='******', root_password=None, archived_sites_path=None, force=False): "Remove site from database and filesystem" from frappe.database import drop_user_and_database from frappe.utils.backups import scheduled_backup frappe.init(site=site) frappe.connect() try: scheduled_backup(ignore_files=False, force=True) except Exception as err: if force: pass else: click.echo("="*80) click.echo("Error: The operation has stopped because backup of {s}'s database failed.".format(s=site)) click.echo("Reason: {reason}{sep}".format(reason=err[1], sep="\n")) click.echo("Fix the issue and try again.") click.echo( "Hint: Use 'bench drop-site {s} --force' to force the removal of {s}".format(sep="\n", tab="\t", s=site) ) sys.exit(1) drop_user_and_database(frappe.conf.db_name, root_login, root_password) 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 remove_app(app_name, dry_run=False, yes=False): """Delete app and all linked to the app's module with the app.""" if not dry_run and not yes: confirm = raw_input( "All doctypes (including custom), modules related to this app will be deleted. Are you sure you want to continue (y/n) ? " ) if confirm != "y": return from frappe.utils.backups import scheduled_backup print "Backing up..." scheduled_backup(ignore_files=True) drop_doctypes = [] # remove modules, doctypes, roles for module_name in frappe.get_module_list(app_name): for doctype in frappe.get_list("DocType", filters={"module": module_name}, fields=["name", "issingle"]): print "removing DocType {0}...".format(doctype.name) # drop table if not dry_run: frappe.delete_doc("DocType", doctype.name) if not doctype.issingle: drop_doctypes.append(doctype.name) # remove reports for report in frappe.get_list("Report", filters={"module": module_name}): print "removing {0}...".format(report.name) if not dry_run: frappe.delete_doc("Report", report.name) for page in frappe.get_list("Page", filters={"module": module_name}): print "removing Page {0}...".format(page.name) # drop table if not dry_run: frappe.delete_doc("Page", page.name) print "removing Module {0}...".format(module_name) if not dry_run: frappe.delete_doc("Module Def", module_name) # delete desktop icons frappe.db.sql('delete from `tabDesktop Icon` where app=%s', app_name) remove_from_installed_apps(app_name) if not dry_run: # drop tables after a commit frappe.db.commit() for doctype in set(drop_doctypes): frappe.db.sql("drop table `tab{0}`".format(doctype))
def drop_site(site, root_login='******', 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 remove_app(app_name, dry_run=False): """Delete app and all linked to the app's module with the app.""" if not dry_run: confirm = raw_input("All doctypes (including custom), modules related to this app will be deleted. Are you sure you want to continue (y/n) ? ") if confirm!="y": return from frappe.utils.backups import scheduled_backup print "Backing up..." scheduled_backup(ignore_files=True) # remove modules, doctypes, roles for module_name in frappe.get_module_list(app_name): for doctype in frappe.get_list("DocType", filters={"module": module_name}, fields=["name", "issingle"]): print "removing {0}...".format(doctype.name) # drop table if not dry_run: if not doctype.issingle: frappe.db.sql("drop table `tab{0}`".format(doctype.name)) frappe.delete_doc("DocType", doctype.name) print "removing Module {0}...".format(module_name) if not dry_run: frappe.delete_doc("Module Def", module_name) remove_from_installed_apps(app_name)
def remove_app(app_name, dry_run=False, yes=False): """Delete app and all linked to the app's module with the app.""" if not dry_run and not yes: confirm = input("All doctypes (including custom), modules related to this app will be deleted. Are you sure you want to continue (y/n) ? ") if confirm!="y": return from frappe.utils.backups import scheduled_backup print("Backing up...") scheduled_backup(ignore_files=True) drop_doctypes = [] # remove modules, doctypes, roles for module_name in frappe.get_module_list(app_name): for doctype in frappe.get_list("DocType", filters={"module": module_name}, fields=["name", "issingle"]): print("removing DocType {0}...".format(doctype.name)) if not dry_run: frappe.delete_doc("DocType", doctype.name) if not doctype.issingle: drop_doctypes.append(doctype.name)
def remove_app(app_name, dry_run=False, yes=False): """Delete app and all linked to the app's module with the app.""" if not dry_run and not yes: confirm = raw_input("All doctypes (including custom), modules related to this app will be deleted. Are you sure you want to continue (y/n) ? ") if confirm!="y": return from frappe.utils.backups import scheduled_backup print "Backing up..." scheduled_backup(ignore_files=True) drop_doctypes = [] # remove modules, doctypes, roles for module_name in frappe.get_module_list(app_name): for doctype in frappe.get_list("DocType", filters={"module": module_name}, fields=["name", "issingle"]): print "removing DocType {0}...".format(doctype.name) # drop table if not dry_run: frappe.delete_doc("DocType", doctype.name) if not doctype.issingle: drop_doctypes.append(doctype.name) # remove reports for report in frappe.get_list("Report", filters={"module": module_name}): print "removing {0}...".format(report.name) if not dry_run: frappe.delete_doc("Report", report.name) for page in frappe.get_list("Page", filters={"module": module_name}): print "removing Page {0}...".format(page.name) # drop table if not dry_run: frappe.delete_doc("Page", page.name) print "removing Module {0}...".format(module_name) if not dry_run: frappe.delete_doc("Module Def", module_name) # delete desktop icons frappe.db.sql('delete from `tabDesktop Icon` where app=%s', app_name) remove_from_installed_apps(app_name) if not dry_run: # drop tables after a commit frappe.db.commit() for doctype in set(drop_doctypes): frappe.db.sql("drop table `tab{0}`".format(doctype))
def remove_app(app_name, dry_run=False, yes=False): """Delete app and all linked to the app's module with the app.""" if not dry_run and not yes: confirm = input("All doctypes (including custom), modules related to this app will be deleted. Are you sure you want to continue (y/n) ? ") if confirm!="y": return from frappe.utils.backups import scheduled_backup print("Backing up...") scheduled_backup(ignore_files=True) drop_doctypes = [] # remove modules, doctypes, roles for module_name in frappe.get_module_list(app_name): for doctype in frappe.get_list("DocType", filters={"module": module_name}, fields=["name", "issingle"]): print("removing DocType {0}...".format(doctype.name)) if not dry_run: frappe.delete_doc("DocType", doctype.name) if not doctype.issingle: drop_doctypes.append(doctype.name) # remove reports, pages and web forms for doctype in ("Report", "Page", "Web Form", "Print Format"): for record in frappe.get_list(doctype, filters={"module": module_name}): print("removing {0} {1}...".format(doctype, record.name)) if not dry_run: frappe.delete_doc(doctype, record.name) # remove data migration plans and linked data migration runs for plan in frappe.get_list("Data Migration Plan", filters={"module": module_name}): print("removing Data Migration Runs for Data Migration Plan {0}...".format(plan.name)) for record in frappe.get_list("Data Migration Run", filters={"data_migration_plan": plan.name}): if not dry_run: frappe.delete_doc("Data Migration Run", record.name) print("removing Data Migration Plan {0}...".format(plan.name)) if not dry_run: frappe.delete_doc("Data Migration Plan", plan.name) print("removing Module {0}...".format(module_name)) if not dry_run: frappe.delete_doc("Module Def", module_name) remove_from_installed_apps(app_name) if not dry_run: # drop tables after a commit frappe.db.commit() for doctype in set(drop_doctypes): frappe.db.sql("drop table `tab{0}`".format(doctype))
def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False): """Remove app and all linked to the app's module with the app from a site.""" import click site = frappe.local.site app_hooks = frappe.get_hooks(app_name=app_name) # dont allow uninstall app if not installed unless forced if not force: if app_name not in frappe.get_installed_apps(): click.secho(f"App {app_name} not installed on Site {site}", fg="yellow") return print(f"Uninstalling App {app_name} from Site {site}...") if not dry_run and not yes: confirm = click.confirm( "All doctypes (including custom), modules related to this app will be" " deleted. Are you sure you want to continue?") if not confirm: return if not (dry_run or no_backup): from frappe.utils.backups import scheduled_backup print("Backing up...") scheduled_backup(ignore_files=True) frappe.flags.in_uninstall = True for before_uninstall in app_hooks.before_uninstall or []: frappe.get_attr(before_uninstall)() modules = frappe.get_all("Module Def", filters={"app_name": app_name}, pluck="name") drop_doctypes = _delete_modules(modules, dry_run=dry_run) _delete_doctypes(drop_doctypes, dry_run=dry_run) if not dry_run: remove_from_installed_apps(app_name) frappe.get_single("Installed Applications").update_versions() frappe.db.commit() for after_uninstall in app_hooks.after_uninstall or []: frappe.get_attr(after_uninstall)() click.secho(f"Uninstalled App {app_name} from Site {site}", fg="green") frappe.flags.in_uninstall = False
def backup(context, with_files=False, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, quiet=False, verbose=False): "Backup" from frappe.utils.backups import scheduled_backup verbose = verbose or context.verbose exit_code = 0 for site in context.sites: try: 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, verbose=verbose) except Exception as e: if verbose: print("Backup failed for {0}. Database or site_config.json may be corrupted".format(site)) exit_code = 1 continue if verbose: from frappe.utils import now summary_title = "Backup Summary at {0}".format(now()) print(summary_title + "\n" + "-" * len(summary_title)) print("Database backup:", odb.backup_path_db) if with_files: print("Public files: ", odb.backup_path_files) print("Private files: ", odb.backup_path_private_files) frappe.destroy() if not context.sites: raise SiteNotSpecifiedError sys.exit(exit_code)
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()
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()
def trim_tables(context, dry_run, format, no_backup): if not context.sites: raise SiteNotSpecifiedError from frappe.model.meta import trim_tables from frappe.utils.backups import scheduled_backup for site in context.sites: frappe.init(site=site) frappe.connect() if not (no_backup or dry_run): click.secho(f"Taking backup for {frappe.local.site}", fg="green") odb = scheduled_backup(ignore_files=False, force=True) odb.print_summary() try: trimmed_data = trim_tables(dry_run=dry_run, quiet=format == 'json') if format == 'table' and not dry_run: click.secho( f"The following data have been removed from {frappe.local.site}", fg='green') handle_data(trimmed_data, format=format) finally: frappe.destroy()
def _drop_site(site, root_login='******', root_password=None, archived_sites_path=None, force=False): "Remove site from database and filesystem" from frappe.installer import get_root_connection from frappe.model.db_schema import DbManager from frappe.utils.backups import scheduled_backup frappe.init(site=site) frappe.connect() try: scheduled_backup(ignore_files=False, force=True) except SQLError as err: if err[0] == ER.NO_SUCH_TABLE: if force: pass else: click.echo("=" * 80) click.echo( "Error: The operation has stopped because backup of {s}'s database failed." .format(s=site)) click.echo("Reason: {reason}{sep}".format(reason=err[1], sep="\n")) click.echo("Fix the issue and try again.") click.echo( "Hint: Use 'bench drop-site {s} --force' to force the removal of {s}" .format(sep="\n", tab="\t", s=site)) sys.exit(1) db_name = frappe.local.conf.db_name frappe.local.db = get_root_connection(root_login, root_password) dbman = DbManager(frappe.local.db) dbman.delete_user(db_name, host="%") dbman.delete_user(db_name) 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 drop_site(site, root_login='******', root_password=None): 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) archived_sites_dir = os.path.join(frappe.get_app_path('frappe'), '..', '..', '..', 'archived_sites') if not os.path.exists(archived_sites_dir): os.mkdir(archived_sites_dir) move(archived_sites_dir, site)
def backup(context, with_files=False, backup_path=None, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, backup_path_conf=None, ignore_backup_conf=False, verbose=False, compress=False, include="", exclude=""): "Backup" from frappe.utils.backups import scheduled_backup verbose = verbose or context.verbose exit_code = 0 for site in context.sites: try: frappe.init(site=site) frappe.connect() odb = scheduled_backup( ignore_files=not with_files, backup_path=backup_path, backup_path_db=backup_path_db, backup_path_files=backup_path_files, backup_path_private_files=backup_path_private_files, backup_path_conf=backup_path_conf, ignore_conf=ignore_backup_conf, include_doctypes=include, exclude_doctypes=exclude, compress=compress, verbose=verbose, force=True) except Exception: click.secho( "Backup failed for Site {0}. Database or site_config.json may be corrupted" .format(site), fg="red") if verbose: print(frappe.get_traceback()) exit_code = 1 continue odb.print_summary() click.secho( "Backup for Site {0} has been successfully completed{1}".format( site, " with files" if with_files else ""), fg="green") frappe.destroy() if not context.sites: raise SiteNotSpecifiedError sys.exit(exit_code)
def backup(with_files=False, verbose=True, backup_path_db=None, backup_path_files=None): from frappe.utils.backups import scheduled_backup frappe.connect() odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files) 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() frappe.destroy() return odb
def backup(with_files=False, backup_path_db=None, backup_path_files=None, quiet=False): from frappe.utils.backups import scheduled_backup verbose = not quiet frappe.connect() odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files) 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() frappe.destroy()
def _drop_site(site, root_login='******', root_password=None, archived_sites_path=None, force=False): "Remove site from database and filesystem" from frappe.installer import get_root_connection from frappe.model.db_schema import DbManager from frappe.utils.backups import scheduled_backup frappe.init(site=site) frappe.connect() try: scheduled_backup(ignore_files=False, force=True) except SQLError as err: if err[0] == ER.NO_SUCH_TABLE: if force: pass else: click.echo("="*80) click.echo("Error: The operation has stopped because backup of {s}'s database failed.".format(s=site)) click.echo("Reason: {reason}{sep}".format(reason=err[1], sep="\n")) click.echo("Fix the issue and try again.") click.echo( "Hint: Use 'bench drop-site {s} --force' to force the removal of {s}".format(sep="\n", tab="\t", s=site) ) sys.exit(1) db_name = frappe.local.conf.db_name frappe.local.db = get_root_connection(root_login, root_password) dbman = DbManager(frappe.local.db) dbman.delete_user(db_name) 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 drop_site(site, root_login='******', root_password=None, archived_sites_path=None): "Remove site from database and filesystem" from frappe.installer import get_root_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 = get_root_connection(root_login, root_password) dbman = DbManager(frappe.local.db) dbman.delete_user(db_name) 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 backup(sites, with_files=False): for site in sites: frappe.init(site) frappe.connect() odb = scheduled_backup(ignore_files=not with_files, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, force=True) 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()
def backup(context, with_files=False, backup_path_db=None, backup_path_files=None, quiet=False): "Backup" from frappe.utils.backups import scheduled_backup verbose = context.verbose # for site in context.sites: # All sites bachup with sites - Gangadhar Sir for site in frappe.utils.get_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, 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() frappe.destroy()
def trim_database(context, dry_run, format, no_backup): if not context.sites: raise SiteNotSpecifiedError from frappe.utils.backups import scheduled_backup ALL_DATA = {} for site in context.sites: frappe.init(site=site) frappe.connect() TABLES_TO_DROP = [] STANDARD_TABLES = get_standard_tables() information_schema = frappe.qb.Schema("information_schema") table_name = frappe.qb.Field("table_name").as_("name") queried_result = frappe.qb.from_( information_schema.tables).select(table_name).where( information_schema.tables.table_schema == frappe.conf.db_name).run() database_tables = [x[0] for x in queried_result] doctype_tables = frappe.get_all("DocType", pluck="name") for x in database_tables: doctype = x.lstrip("tab") if not (doctype in doctype_tables or x.startswith("__") or x in STANDARD_TABLES): TABLES_TO_DROP.append(x) if not TABLES_TO_DROP: if format == "text": click.secho( f"No ghost tables found in {frappe.local.site}...Great!", fg="green") else: if not (no_backup or dry_run): if format == "text": print(f"Backing Up Tables: {', '.join(TABLES_TO_DROP)}") odb = scheduled_backup( ignore_conf=False, include_doctypes=",".join( x.lstrip("tab") for x in TABLES_TO_DROP), ignore_files=True, force=True, ) if format == "text": odb.print_summary() print("\nTrimming Database") for table in TABLES_TO_DROP: if format == "text": print(f"* Dropping Table '{table}'...") if not dry_run: frappe.db.sql_ddl(f"drop table `{table}`") ALL_DATA[frappe.local.site] = TABLES_TO_DROP frappe.destroy() if format == "json": import json print(json.dumps(ALL_DATA, indent=1))
def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False): """Remove app and all linked to the app's module with the app from a site.""" import click # dont allow uninstall app if not installed unless forced if not force: if app_name not in frappe.get_installed_apps(): click.secho("App {0} not installed on Site {1}".format( app_name, frappe.local.site), fg="yellow") return print("Uninstalling App {0} from Site {1}...".format( app_name, frappe.local.site)) if not dry_run and not yes: confirm = click.confirm( "All doctypes (including custom), modules related to this app will be deleted. Are you sure you want to continue?" ) if not confirm: return if not no_backup: from frappe.utils.backups import scheduled_backup print("Backing up...") scheduled_backup(ignore_files=True) frappe.flags.in_uninstall = True drop_doctypes = [] modules = ( x.name for x in frappe.get_all("Module Def", filters={"app_name": app_name})) for module_name in modules: print("Deleting Module '{0}'".format(module_name)) for doctype in frappe.get_list("DocType", filters={"module": module_name}, fields=["name", "issingle"]): print("* removing DocType '{0}'...".format(doctype.name)) if not dry_run: frappe.delete_doc("DocType", doctype.name) if not doctype.issingle: drop_doctypes.append(doctype.name) linked_doctypes = frappe.get_all("DocField", filters={ "fieldtype": "Link", "options": "Module Def" }, fields=['parent']) ordered_doctypes = ["Desk Page", "Report", "Page", "Web Form"] doctypes_with_linked_modules = ordered_doctypes + [ doctype.parent for doctype in linked_doctypes if doctype.parent not in ordered_doctypes ] for doctype in doctypes_with_linked_modules: for record in frappe.get_list(doctype, filters={"module": module_name}): print("* removing {0} '{1}'...".format(doctype, record.name)) if not dry_run: frappe.delete_doc(doctype, record.name) print("* removing Module Def '{0}'...".format(module_name)) if not dry_run: frappe.delete_doc("Module Def", module_name) if not dry_run: remove_from_installed_apps(app_name) for doctype in set(drop_doctypes): print("* dropping Table for '{0}'...".format(doctype)) frappe.db.sql("drop table `tab{0}`".format(doctype)) frappe.db.commit() click.secho("Uninstalled App {0} from Site {1}".format( app_name, frappe.local.site), fg="green") frappe.flags.in_uninstall = False
def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False): """Remove app and all linked to the app's module with the app from a site.""" import click site = frappe.local.site # dont allow uninstall app if not installed unless forced if not force: if app_name not in frappe.get_installed_apps(): click.secho(f"App {app_name} not installed on Site {site}", fg="yellow") return print(f"Uninstalling App {app_name} from Site {site}...") if not dry_run and not yes: confirm = click.confirm( "All doctypes (including custom), modules related to this app will be" " deleted. Are you sure you want to continue?") if not confirm: return if not (dry_run or no_backup): from frappe.utils.backups import scheduled_backup print("Backing up...") scheduled_backup(ignore_files=True) frappe.flags.in_uninstall = True drop_doctypes = [] modules = frappe.get_all("Module Def", filters={"app_name": app_name}, pluck="name") for module_name in modules: print(f"Deleting Module '{module_name}'") for doctype in frappe.get_all("DocType", filters={"module": module_name}, fields=["name", "issingle"]): print(f"* removing DocType '{doctype.name}'...") if not dry_run: frappe.delete_doc("DocType", doctype.name, ignore_on_trash=True) if not doctype.issingle: drop_doctypes.append(doctype.name) linked_doctypes = frappe.get_all("DocField", filters={ "fieldtype": "Link", "options": "Module Def" }, fields=["parent"]) ordered_doctypes = ["Workspace", "Report", "Page", "Web Form"] all_doctypes_with_linked_modules = ordered_doctypes + [ doctype.parent for doctype in linked_doctypes if doctype.parent not in ordered_doctypes ] doctypes_with_linked_modules = [ x for x in all_doctypes_with_linked_modules if frappe.db.exists("DocType", x) ] for doctype in doctypes_with_linked_modules: for record in frappe.get_all(doctype, filters={"module": module_name}, pluck="name"): print(f"* removing {doctype} '{record}'...") if not dry_run: frappe.delete_doc(doctype, record, ignore_on_trash=True, force=True) print(f"* removing Module Def '{module_name}'...") if not dry_run: frappe.delete_doc("Module Def", module_name, ignore_on_trash=True, force=True) for doctype in set(drop_doctypes): print(f"* dropping Table for '{doctype}'...") if not dry_run: frappe.db.sql_ddl(f"drop table `tab{doctype}`") if not dry_run: remove_from_installed_apps(app_name) frappe.db.commit() click.secho(f"Uninstalled App {app_name} from Site {site}", fg="green") frappe.flags.in_uninstall = False