From 9e139171f41405d677d62361fb2ef2190d475d5e Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 8 Apr 2020 18:35:49 +0530 Subject: [PATCH 001/107] feat: custom form dashboards --- frappe/custom/doctype/custom_link/__init__.py | 0 .../custom/doctype/custom_link/custom_link.js | 16 ++++++ .../doctype/custom_link/custom_link.json | 52 +++++++++++++++++++ .../custom/doctype/custom_link/custom_link.py | 43 +++++++++++++++ .../doctype/custom_link/test_custom_link.py | 10 ++++ frappe/desk/form/meta.py | 2 - frappe/desk/notifications.py | 7 +-- frappe/model/meta.py | 4 +- 8 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 frappe/custom/doctype/custom_link/__init__.py create mode 100644 frappe/custom/doctype/custom_link/custom_link.js create mode 100644 frappe/custom/doctype/custom_link/custom_link.json create mode 100644 frappe/custom/doctype/custom_link/custom_link.py create mode 100644 frappe/custom/doctype/custom_link/test_custom_link.py diff --git a/frappe/custom/doctype/custom_link/__init__.py b/frappe/custom/doctype/custom_link/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/custom_link/custom_link.js b/frappe/custom/doctype/custom_link/custom_link.js new file mode 100644 index 0000000000..f1c06daeeb --- /dev/null +++ b/frappe/custom/doctype/custom_link/custom_link.js @@ -0,0 +1,16 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Custom Link', { + refresh: function(frm) { + frm.set_query("document_type", function () { + return { + filters: { + custom: 0, + istable: 0, + module: ['not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]] + } + } + }); + } +}); diff --git a/frappe/custom/doctype/custom_link/custom_link.json b/frappe/custom/doctype/custom_link/custom_link.json new file mode 100644 index 0000000000..350e6b1c2d --- /dev/null +++ b/frappe/custom/doctype/custom_link/custom_link.json @@ -0,0 +1,52 @@ +{ + "actions": [], + "autoname": "field:document_type", + "creation": "2020-04-08 15:16:44.342509", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "links" + ], + "fields": [ + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Document Type", + "options": "DocType", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "links", + "fieldtype": "Table", + "label": "Links", + "options": "DocType Link" + } + ], + "links": [], + "modified": "2020-04-08 16:42:59.402671", + "modified_by": "Administrator", + "module": "Custom", + "name": "Custom Link", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/custom_link/custom_link.py b/frappe/custom/doctype/custom_link/custom_link.py new file mode 100644 index 0000000000..b9a88b58f2 --- /dev/null +++ b/frappe/custom/doctype/custom_link/custom_link.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class CustomLink(Document): + pass + +def get_custom_doctype_links(doctype, data): + if frappe.get_all("Custom Link", {"document_type": doctype}): + doc = frappe.get_doc("Custom Link", doctype) + + if not data.transactions: + # init groups + data.transactions = [] + data.non_standard_fieldnames = {} + + for link in doc.links: + link.added = False + for group in data.transactions: + # group found + if group.get("label") == link.group: + if not link.link_doctype in group.get("items"): + group.get("items").append(link.link_doctype) + link.added = True + + if not link.added: + # group not found, make a new group + data.transactions.append({ + "label": link.group, + "items": [link.link_doctype] + }) + + if link.link_fieldname != data.fieldname: + if data.fieldname: + data.non_standard_fieldnames[link.link_doctype] = link.link_fieldname + else: + data.fieldname = link.link_fieldname + + return data \ No newline at end of file diff --git a/frappe/custom/doctype/custom_link/test_custom_link.py b/frappe/custom/doctype/custom_link/test_custom_link.py new file mode 100644 index 0000000000..a292f73ad0 --- /dev/null +++ b/frappe/custom/doctype/custom_link/test_custom_link.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestCustomLink(unittest.TestCase): + pass diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index 26fc6037fd..ba0e5c2216 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -196,8 +196,6 @@ class FormMeta(Meta): self.get("__messages").update(messages, as_value=True) def load_dashboard(self): - if self.custom: - return self.set('__dashboard', self.get_dashboard_data()) def load_kanban_meta(self): diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index 3a8815ca71..109dd25f4f 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -268,8 +268,9 @@ def get_open_count(doctype, name, items=[]): "count": out, } - module = frappe.get_meta_module(doctype) - if hasattr(module, "get_timeline_data"): - out["timeline_data"] = module.get_timeline_data(doctype, name) + if not meta.custom: + module = frappe.get_meta_module(doctype) + if hasattr(module, "get_timeline_data"): + out["timeline_data"] = module.get_timeline_data(doctype, name) return out diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 5065684311..e6a89be108 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -26,6 +26,7 @@ from frappe.model.base_document import BaseDocument from frappe.modules import load_doctype_module from frappe.model.workflow import get_workflow_name from frappe import _ +from frappe.custom.doctype.custom_link.custom_link import get_custom_doctype_links def get_meta(doctype, cached=True): if cached: @@ -429,6 +430,7 @@ class Meta(Document): pass self.add_doctype_links(data) + get_custom_doctype_links(self.name, data) for hook in frappe.get_hooks("override_doctype_dashboards", {}).get(self.name, []): data = frappe.get_attr(hook)(data=data) @@ -447,7 +449,7 @@ class Meta(Document): link.added = False for group in data.transactions: # group found - if group.label == link.label: + if group.label == link.group: if not link.link_doctype in group.items: group.items.append(link.link_doctype) link.added = True From a25088a606dd50a293d1f6b542af9b8c75e8cd5e Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Mon, 13 Apr 2020 19:10:19 +0530 Subject: [PATCH 002/107] chore: show message on migrate when services are not running shows message before migrate if the database or redis service is not running instead of trying to migrate and then failing. Signed-off-by: Chinmay D. Pai --- frappe/migrate.py | 16 +++++++++++++ frappe/utils/connections.py | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 frappe/utils/connections.py diff --git a/frappe/migrate.py b/frappe/migrate.py index 043b6817d7..711f51c57a 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -5,11 +5,13 @@ from __future__ import unicode_literals import json import os +import sys import frappe import frappe.translate import frappe.modules.patch_handler import frappe.model.sync from frappe.utils.fixtures import sync_fixtures +from frappe.utils.connections import check_connection from frappe.cache_manager import clear_global_cache from frappe.desk.notifications import clear_notifications from frappe.website import render @@ -18,6 +20,7 @@ from frappe.modules.utils import sync_customizations from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs from frappe.utils import global_search + def migrate(verbose=True, rebuild_website=False, skip_failing=False): '''Migrate all apps to the latest version, will: - run before migrate hooks @@ -30,6 +33,19 @@ def migrate(verbose=True, rebuild_website=False, skip_failing=False): - run after migrate hooks ''' + service_status = check_connection(redis_services=["redis_cache"]) + if False in service_status.values(): + for service in service_status: + if not service_status.get(service, True): + print("{} service is not running.".format(service)) + print("""Cannot run bench migrate without the services running. +If you are running bench in development mode, make sure that bench is running: + +$ bench start + +Otherwise, check the server logs and ensure that all the required services are running.""") + sys.exit(1) + touched_tables_file = frappe.get_site_path('touched_tables.json') if os.path.exists(touched_tables_file): os.remove(touched_tables_file) diff --git a/frappe/utils/connections.py b/frappe/utils/connections.py new file mode 100644 index 0000000000..57b9399986 --- /dev/null +++ b/frappe/utils/connections.py @@ -0,0 +1,48 @@ +import socket + +from six.moves.urllib.parse import urlparse +from frappe import get_conf + +config = get_conf() +REDIS_KEYS = ('redis_cache', 'redis_queue', 'redis_socketio') + + +def is_open(ip, port, timeout=10): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(timeout) + try: + s.connect((ip, int(port))) + s.shutdown(socket.SHUT_RDWR) + return True + except socket.error: + return False + finally: + s.close() + + +def check_database(): + db_type = config.get("db_type", "mariadb") + if db_type == "mariadb": + db_host = config.get("db_host", "localhost") + db_port = config.get("db_port", 3306) + else: + db_host = "localhost" + db_port = 5342 + return {"db_type": is_open(db_host, db_port)} + + +def check_redis(redis_services=None): + services = redis_services or REDIS_KEYS + status = {} + for conn in services: + redis_url = urlparse(config.get(conn)).netloc + redis_host, redis_port = redis_url.split(":") + status[conn] = is_open(redis_host, redis_port) + return status + + +def check_connection(redis_services=None): + service_status = {} + service_status.update(check_database()) + service_status.update(check_redis(redis_services)) + return service_status From 318e397ab35cb0508ea8697b0088f23c37e67df2 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Mon, 13 Apr 2020 19:13:43 +0530 Subject: [PATCH 003/107] chore: unquote db_type Signed-off-by: Chinmay D. Pai --- frappe/utils/connections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/connections.py b/frappe/utils/connections.py index 57b9399986..bffbd98134 100644 --- a/frappe/utils/connections.py +++ b/frappe/utils/connections.py @@ -28,7 +28,7 @@ def check_database(): else: db_host = "localhost" db_port = 5342 - return {"db_type": is_open(db_host, db_port)} + return {db_type: is_open(db_host, db_port)} def check_redis(redis_services=None): From c0d0fcfd69227562069933449881d27470bb904b Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Mon, 13 Apr 2020 19:24:51 +0530 Subject: [PATCH 004/107] chore: do not hardcode anything for postgres Signed-off-by: Chinmay D. Pai --- frappe/utils/connections.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/frappe/utils/connections.py b/frappe/utils/connections.py index bffbd98134..6bd24d57ec 100644 --- a/frappe/utils/connections.py +++ b/frappe/utils/connections.py @@ -22,12 +22,8 @@ def is_open(ip, port, timeout=10): def check_database(): db_type = config.get("db_type", "mariadb") - if db_type == "mariadb": - db_host = config.get("db_host", "localhost") - db_port = config.get("db_port", 3306) - else: - db_host = "localhost" - db_port = 5342 + db_host = config.get("db_host", "localhost") + db_port = config.get("db_port", 3306 if db_type == "mariadb" else 5342) return {db_type: is_open(db_host, db_port)} From 19dc0a65b06e75def4d5b755eefb00eb805c64ab Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 13 Mar 2020 17:16:52 +0530 Subject: [PATCH 005/107] feat: initial bingup for cutomization export --- .../doctype/customization_export/__init__.py | 0 .../customization_export.json | 56 +++++++++++++ .../customization_export.py | 10 +++ .../doctype/manage_customization/__init__.py | 0 .../manage_customization.js | 46 +++++++++++ .../manage_customization.json | 73 +++++++++++++++++ .../manage_customization.py | 78 +++++++++++++++++++ .../test_manage_customization.py | 10 +++ 8 files changed, 273 insertions(+) create mode 100644 frappe/custom/doctype/customization_export/__init__.py create mode 100644 frappe/custom/doctype/customization_export/customization_export.json create mode 100644 frappe/custom/doctype/customization_export/customization_export.py create mode 100644 frappe/custom/doctype/manage_customization/__init__.py create mode 100644 frappe/custom/doctype/manage_customization/manage_customization.js create mode 100644 frappe/custom/doctype/manage_customization/manage_customization.json create mode 100644 frappe/custom/doctype/manage_customization/manage_customization.py create mode 100644 frappe/custom/doctype/manage_customization/test_manage_customization.py diff --git a/frappe/custom/doctype/customization_export/__init__.py b/frappe/custom/doctype/customization_export/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/customization_export/customization_export.json b/frappe/custom/doctype/customization_export/customization_export.json new file mode 100644 index 0000000000..bc59a89f4f --- /dev/null +++ b/frappe/custom/doctype/customization_export/customization_export.json @@ -0,0 +1,56 @@ +{ + "actions": [], + "creation": "2020-03-12 14:51:09.260025", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "sb_00", + "filters", + "cb_00", + "or_filters" + ], + "fields": [ + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Document Type", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "filters", + "fieldtype": "Code", + "in_list_view": 1, + "label": "Filters" + }, + { + "fieldname": "or_filters", + "fieldtype": "Code", + "in_list_view": 1, + "label": "Or Filters" + }, + { + "fieldname": "sb_00", + "fieldtype": "Section Break" + }, + { + "fieldname": "cb_00", + "fieldtype": "Column Break" + } + ], + "istable": 1, + "links": [], + "modified": "2020-03-13 16:53:15.214275", + "modified_by": "Administrator", + "module": "Custom", + "name": "Customization Export", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/customization_export/customization_export.py b/frappe/custom/doctype/customization_export/customization_export.py new file mode 100644 index 0000000000..70a7da3b08 --- /dev/null +++ b/frappe/custom/doctype/customization_export/customization_export.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class CustomizationExport(Document): + pass diff --git a/frappe/custom/doctype/manage_customization/__init__.py b/frappe/custom/doctype/manage_customization/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/manage_customization/manage_customization.js b/frappe/custom/doctype/manage_customization/manage_customization.js new file mode 100644 index 0000000000..41e2fd316b --- /dev/null +++ b/frappe/custom/doctype/manage_customization/manage_customization.js @@ -0,0 +1,46 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Manage Customization', { + refresh: function(frm) { + frappe.realtime.on("exporting_progress", (data) => { + if (data.progress !== data.total) { + frm.dashboard.show_progress("Exporting", data.progress / data.total * 100, __("{0}", [data.message])); + } else { + frm.dashboard.hide_progress(); + } + }); + + frappe.realtime.on("importing_progress", (data) => { + if (data.progress !== data.total) { + frm.dashboard.show_progress("Importing", data.progress / data.total * 100, __("{0}", [data.message])); + } else { + frm.dashboard.hide_progress(); + } + }); + }, + export: function(frm) { + frappe.call({ + method: 'frappe.custom.doctype.manage_customization.manage_customization.export_customizations', + callback: function(r) { + if (r.message) { + const args = { + cmd: 'frappe.custom.doctype.manage_customization.manage_customization.export_customizations', + data: r.message.data + }; + open_url_post(frappe.request.url, args); + } + } + }); + }, + import: function(frm) { + frappe.call({ + method: 'frappe.custom.doctype.manage_customization.manage_customization.import_customizations', + callback: function(r) { + if (r.message) { + console.log("done") + } + } + }); + } +}); diff --git a/frappe/custom/doctype/manage_customization/manage_customization.json b/frappe/custom/doctype/manage_customization/manage_customization.json new file mode 100644 index 0000000000..43c4596590 --- /dev/null +++ b/frappe/custom/doctype/manage_customization/manage_customization.json @@ -0,0 +1,73 @@ +{ + "actions": [], + "creation": "2020-03-01 23:21:17.394500", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "sb_00", + "export_customization", + "export", + "sb_01", + "import_customization", + "import" + ], + "fields": [ + { + "fieldname": "export_customization", + "fieldtype": "Table", + "label": "Customizations to Export", + "options": "Customization Export" + }, + { + "fieldname": "sb_00", + "fieldtype": "Section Break", + "label": "Export Customizations" + }, + { + "fieldname": "sb_01", + "fieldtype": "Section Break", + "label": "Import Customizations" + }, + { + "fieldname": "import_customization", + "fieldtype": "Attach", + "label": "Attach" + }, + { + "depends_on": "export_customization", + "fieldname": "export", + "fieldtype": "Button", + "label": "Export" + }, + { + "depends_on": "import_customization", + "fieldname": "import", + "fieldtype": "Button", + "label": "Import" + } + ], + "issingle": 1, + "links": [], + "modified": "2020-03-13 17:14:00.382402", + "modified_by": "Administrator", + "module": "Custom", + "name": "Manage Customization", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/manage_customization/manage_customization.py b/frappe/custom/doctype/manage_customization/manage_customization.py new file mode 100644 index 0000000000..3e21b587c9 --- /dev/null +++ b/frappe/custom/doctype/manage_customization/manage_customization.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +import json + +class ManageCustomization(Document): + pass + +@frappe.whitelist() +def export_customizations(): + """Export fixtures as JSON""" + + export_customizations_doc = frappe.get_single("Manage Customization") + customizations = [] + + for doctype in export_customizations_doc.export_customization: + filters, or_filters = {}, {} + + if doctype.get("filters"): + filters = json.loads(doctype.get("filters")) + if doctype.get("or_filters"): + or_filters = json.loads(doctype.get("or_filters")) + + docs = frappe.get_all(doctype.get("document_type"), filters=filters, or_filters=or_filters) + length = len(docs) + for idx, doc in enumerate(docs): + frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doctype.get("document_type")), user=frappe.session.user) + customizations.append(frappe.get_doc(doctype.get("document_type"), doc.name).as_dict()) + + return frappe._dict({ + "data": post_process(customizations) + }) + +@frappe.whitelist() +def import_customizations(): + """Import fixtures as JSON""" + + import_customizations_doc = frappe.get_single("Manage Customization") + + with open(frappe.utils.get_url() + import_customizations_doc.import_customization, 'r') as f: + f = json.loads(f) + + length = f.get("data") + + for idx, doc in enumerate(f.get("data")): + frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) + frappe.get_doc(doc).insert(ignore_permissions=True) + +def post_process(customizations): + del_keys = ('modified_by', 'creation', 'owner', 'idx', 'name', 'modified', 'docstatus') + + for doc in customizations: + for key in del_keys: + if key in doc: + del doc[key] + + for key, value in doc.items(): + if not isinstance(value, list): + continue + + for child in value: + for key in del_keys: + if key in child: + del child[key] + + return customizations + +@frappe.whitelist() +def download_customization_json(): + data = frappe._dict(frappe.local.form_dict) + frappe.response['filename'] = 'Customizations.json' + frappe.response['filecontent'] = data.get("data") + frappe.response['content_type'] = 'application/json' + frappe.response['type'] = 'download' \ No newline at end of file diff --git a/frappe/custom/doctype/manage_customization/test_manage_customization.py b/frappe/custom/doctype/manage_customization/test_manage_customization.py new file mode 100644 index 0000000000..af1a332bb4 --- /dev/null +++ b/frappe/custom/doctype/manage_customization/test_manage_customization.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestManageCustomization(unittest.TestCase): + pass From eb57ec7903cbd5dc58e5deb8281f850d37d3f327 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 13 Mar 2020 18:21:14 +0530 Subject: [PATCH 006/107] fix: importer for customization fix --- .../manage_customization.js | 6 ++---- .../manage_customization.py | 18 +++++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/frappe/custom/doctype/manage_customization/manage_customization.js b/frappe/custom/doctype/manage_customization/manage_customization.js index 41e2fd316b..418a7c2c17 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.js +++ b/frappe/custom/doctype/manage_customization/manage_customization.js @@ -36,10 +36,8 @@ frappe.ui.form.on('Manage Customization', { import: function(frm) { frappe.call({ method: 'frappe.custom.doctype.manage_customization.manage_customization.import_customizations', - callback: function(r) { - if (r.message) { - console.log("done") - } + callback: function() { + frappe.msgprint(__("Customizations Imported.")); } }); } diff --git a/frappe/custom/doctype/manage_customization/manage_customization.py b/frappe/custom/doctype/manage_customization/manage_customization.py index 3e21b587c9..feb140251a 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.py +++ b/frappe/custom/doctype/manage_customization/manage_customization.py @@ -39,16 +39,20 @@ def export_customizations(): def import_customizations(): """Import fixtures as JSON""" - import_customizations_doc = frappe.get_single("Manage Customization") + import_file = frappe.get_all("File", filters={ + "attached_to_doctype": "Manage Customization", + "attached_to_name": "Manage Customization" + }, limit=1, order_by="creation desc") - with open(frappe.utils.get_url() + import_customizations_doc.import_customization, 'r') as f: - f = json.loads(f) + if not import_file: + return - length = f.get("data") + content = json.loads(frappe.get_doc("File", import_file[0].name).get_content()) + length = len(content) - for idx, doc in enumerate(f.get("data")): - frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) - frappe.get_doc(doc).insert(ignore_permissions=True) + for idx, doc in enumerate(content.get("message").get("data")): + frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) + frappe.get_doc(doc).insert(ignore_permissions=True) def post_process(customizations): del_keys = ('modified_by', 'creation', 'owner', 'idx', 'name', 'modified', 'docstatus') From 846594d6c8516a9ed89ec73404b47cc46d5936a8 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 13 Mar 2020 18:41:36 +0530 Subject: [PATCH 007/107] fix: miscellaneous fixes --- .../custom/doctype/manage_customization/manage_customization.js | 2 +- .../custom/doctype/manage_customization/manage_customization.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/custom/doctype/manage_customization/manage_customization.js b/frappe/custom/doctype/manage_customization/manage_customization.js index 418a7c2c17..4c86519dd8 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.js +++ b/frappe/custom/doctype/manage_customization/manage_customization.js @@ -28,7 +28,7 @@ frappe.ui.form.on('Manage Customization', { cmd: 'frappe.custom.doctype.manage_customization.manage_customization.export_customizations', data: r.message.data }; - open_url_post(frappe.request.url, args); + open_url_post(frappe.request.url, args, true); } } }); diff --git a/frappe/custom/doctype/manage_customization/manage_customization.py b/frappe/custom/doctype/manage_customization/manage_customization.py index feb140251a..f3c18d5017 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.py +++ b/frappe/custom/doctype/manage_customization/manage_customization.py @@ -52,7 +52,7 @@ def import_customizations(): for idx, doc in enumerate(content.get("message").get("data")): frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) - frappe.get_doc(doc).insert(ignore_permissions=True) + frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) def post_process(customizations): del_keys = ('modified_by', 'creation', 'owner', 'idx', 'name', 'modified', 'docstatus') From b3f45bd81c81747713359bab032b962c4d166fac Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 14 Mar 2020 11:59:13 +0530 Subject: [PATCH 008/107] feat: rename to package --- frappe/config/customization.py | 8 +-- .../__init__.py | 0 .../package.js} | 18 +++--- .../package.json} | 58 +++++++++++-------- .../package.py} | 44 +++++++------- .../test_package.py} | 2 +- .../__init__.py | 0 .../package_doctype.json} | 22 +++---- .../package_doctype.py} | 2 +- 9 files changed, 82 insertions(+), 72 deletions(-) rename frappe/custom/doctype/{customization_export => package}/__init__.py (100%) rename frappe/custom/doctype/{manage_customization/manage_customization.js => package/package.js} (56%) rename frappe/custom/doctype/{manage_customization/manage_customization.json => package/package.json} (63%) rename frappe/custom/doctype/{manage_customization/manage_customization.py => package/package.py} (55%) rename frappe/custom/doctype/{manage_customization/test_manage_customization.py => package/test_package.py} (78%) rename frappe/custom/doctype/{manage_customization => package_doctype}/__init__.py (100%) rename frappe/custom/doctype/{customization_export/customization_export.json => package_doctype/package_doctype.json} (88%) rename frappe/custom/doctype/{customization_export/customization_export.py => package_doctype/package_doctype.py} (86%) diff --git a/frappe/config/customization.py b/frappe/config/customization.py index 06eaa2ea00..3d587e6839 100644 --- a/frappe/config/customization.py +++ b/frappe/config/customization.py @@ -3,7 +3,7 @@ from frappe import _ def get_data(): return [ - { + { "label": _("Form Customization"), "icon": "fa fa-glass", "items": [ @@ -57,9 +57,9 @@ def get_data(): }, { "type": "doctype", - "label": _("Custom Tags"), - "name": "Tag Category", - "description": _("Add your own Tag Categories") + "label": _("Package"), + "name": "Package", + "description": _("Import and Export Packages.") } ] } diff --git a/frappe/custom/doctype/customization_export/__init__.py b/frappe/custom/doctype/package/__init__.py similarity index 100% rename from frappe/custom/doctype/customization_export/__init__.py rename to frappe/custom/doctype/package/__init__.py diff --git a/frappe/custom/doctype/manage_customization/manage_customization.js b/frappe/custom/doctype/package/package.js similarity index 56% rename from frappe/custom/doctype/manage_customization/manage_customization.js rename to frappe/custom/doctype/package/package.js index 4c86519dd8..c362c51b24 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.js +++ b/frappe/custom/doctype/package/package.js @@ -1,18 +1,18 @@ // Copyright (c) 2020, Frappe Technologies and contributors // For license information, please see license.txt -frappe.ui.form.on('Manage Customization', { +frappe.ui.form.on('Package', { refresh: function(frm) { - frappe.realtime.on("exporting_progress", (data) => { - if (data.progress !== data.total) { + frappe.realtime.on("exporting_package", (data) => { + if (data.progress !== (data.total-1)) { frm.dashboard.show_progress("Exporting", data.progress / data.total * 100, __("{0}", [data.message])); } else { frm.dashboard.hide_progress(); } }); - frappe.realtime.on("importing_progress", (data) => { - if (data.progress !== data.total) { + frappe.realtime.on("importing_package", (data) => { + if (data.progress !== (data.total-1)) { frm.dashboard.show_progress("Importing", data.progress / data.total * 100, __("{0}", [data.message])); } else { frm.dashboard.hide_progress(); @@ -21,11 +21,11 @@ frappe.ui.form.on('Manage Customization', { }, export: function(frm) { frappe.call({ - method: 'frappe.custom.doctype.manage_customization.manage_customization.export_customizations', + method: 'frappe.custom.doctype.package.package.export_package', callback: function(r) { if (r.message) { const args = { - cmd: 'frappe.custom.doctype.manage_customization.manage_customization.export_customizations', + cmd: 'frappe.custom.doctype.package.package.download_package', data: r.message.data }; open_url_post(frappe.request.url, args, true); @@ -35,9 +35,9 @@ frappe.ui.form.on('Manage Customization', { }, import: function(frm) { frappe.call({ - method: 'frappe.custom.doctype.manage_customization.manage_customization.import_customizations', + method: 'frappe.custom.doctype.package.package.import_package', callback: function() { - frappe.msgprint(__("Customizations Imported.")); + frappe.msgprint(__("Package Imported.")); } }); } diff --git a/frappe/custom/doctype/manage_customization/manage_customization.json b/frappe/custom/doctype/package/package.json similarity index 63% rename from frappe/custom/doctype/manage_customization/manage_customization.json rename to frappe/custom/doctype/package/package.json index 43c4596590..cad2ca6788 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.json +++ b/frappe/custom/doctype/package/package.json @@ -1,58 +1,58 @@ { "actions": [], - "creation": "2020-03-01 23:21:17.394500", + "creation": "2020-03-14 11:20:14.850552", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ "sb_00", - "export_customization", + "export_package", "export", "sb_01", - "import_customization", + "import_package", "import" ], "fields": [ - { - "fieldname": "export_customization", - "fieldtype": "Table", - "label": "Customizations to Export", - "options": "Customization Export" - }, { "fieldname": "sb_00", "fieldtype": "Section Break", - "label": "Export Customizations" + "label": "Export Package" }, { - "fieldname": "sb_01", - "fieldtype": "Section Break", - "label": "Import Customizations" - }, - { - "fieldname": "import_customization", - "fieldtype": "Attach", - "label": "Attach" - }, - { - "depends_on": "export_customization", + "depends_on": "export_package", "fieldname": "export", "fieldtype": "Button", "label": "Export" }, { - "depends_on": "import_customization", + "fieldname": "sb_01", + "fieldtype": "Section Break", + "label": "Import Package" + }, + { + "depends_on": "import_package", "fieldname": "import", "fieldtype": "Button", "label": "Import" + }, + { + "fieldname": "export_package", + "fieldtype": "Table", + "label": "Package", + "options": "Package DocType" + }, + { + "fieldname": "import_package", + "fieldtype": "Attach", + "label": "Attach" } ], "issingle": 1, "links": [], - "modified": "2020-03-13 17:14:00.382402", + "modified": "2020-03-14 11:52:58.221081", "modified_by": "Administrator", "module": "Custom", - "name": "Manage Customization", + "name": "Package", "owner": "Administrator", "permissions": [ { @@ -64,6 +64,16 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "All", + "share": 1, + "write": 1 } ], "quick_entry": 1, diff --git a/frappe/custom/doctype/manage_customization/manage_customization.py b/frappe/custom/doctype/package/package.py similarity index 55% rename from frappe/custom/doctype/manage_customization/manage_customization.py rename to frappe/custom/doctype/package/package.py index f3c18d5017..e3a29144d9 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.py +++ b/frappe/custom/doctype/package/package.py @@ -7,17 +7,17 @@ import frappe from frappe.model.document import Document import json -class ManageCustomization(Document): +class Package(Document): pass @frappe.whitelist() -def export_customizations(): - """Export fixtures as JSON""" +def export_package(): + """Export package as JSON""" - export_customizations_doc = frappe.get_single("Manage Customization") - customizations = [] + package_doc = frappe.get_single("Package") + package = [] - for doctype in export_customizations_doc.export_customization: + for doctype in package_doc.export_package: filters, or_filters = {}, {} if doctype.get("filters"): @@ -28,36 +28,36 @@ def export_customizations(): docs = frappe.get_all(doctype.get("document_type"), filters=filters, or_filters=or_filters) length = len(docs) for idx, doc in enumerate(docs): - frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doctype.get("document_type")), user=frappe.session.user) - customizations.append(frappe.get_doc(doctype.get("document_type"), doc.name).as_dict()) + frappe.publish_realtime("exporting_package", dict(progress=idx, total=length, message=doctype.get("document_type")), user=frappe.session.user) + package.append(frappe.get_doc(doctype.get("document_type"), doc.name).as_dict()) return frappe._dict({ - "data": post_process(customizations) + "data": post_process(package) }) @frappe.whitelist() -def import_customizations(): - """Import fixtures as JSON""" +def import_package(): + """Import package from JSON""" - import_file = frappe.get_all("File", filters={ - "attached_to_doctype": "Manage Customization", - "attached_to_name": "Manage Customization" + package_file = frappe.get_all("File", filters={ + "attached_to_doctype": "Package", + "attached_to_name": "Package" }, limit=1, order_by="creation desc") - if not import_file: + if not package_file: return - content = json.loads(frappe.get_doc("File", import_file[0].name).get_content()) + content = json.loads(frappe.get_doc("File", package_file[0].name).get_content()) length = len(content) for idx, doc in enumerate(content.get("message").get("data")): - frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) + frappe.publish_realtime("importing_package", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) -def post_process(customizations): +def post_process(package): del_keys = ('modified_by', 'creation', 'owner', 'idx', 'name', 'modified', 'docstatus') - for doc in customizations: + for doc in package: for key in del_keys: if key in doc: del doc[key] @@ -71,12 +71,12 @@ def post_process(customizations): if key in child: del child[key] - return customizations + return package @frappe.whitelist() -def download_customization_json(): +def download_package(): data = frappe._dict(frappe.local.form_dict) - frappe.response['filename'] = 'Customizations.json' + frappe.response['filename'] = 'Package.json' frappe.response['filecontent'] = data.get("data") frappe.response['content_type'] = 'application/json' frappe.response['type'] = 'download' \ No newline at end of file diff --git a/frappe/custom/doctype/manage_customization/test_manage_customization.py b/frappe/custom/doctype/package/test_package.py similarity index 78% rename from frappe/custom/doctype/manage_customization/test_manage_customization.py rename to frappe/custom/doctype/package/test_package.py index af1a332bb4..3a17d51260 100644 --- a/frappe/custom/doctype/manage_customization/test_manage_customization.py +++ b/frappe/custom/doctype/package/test_package.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestManageCustomization(unittest.TestCase): +class TestPackage(unittest.TestCase): pass diff --git a/frappe/custom/doctype/manage_customization/__init__.py b/frappe/custom/doctype/package_doctype/__init__.py similarity index 100% rename from frappe/custom/doctype/manage_customization/__init__.py rename to frappe/custom/doctype/package_doctype/__init__.py diff --git a/frappe/custom/doctype/customization_export/customization_export.json b/frappe/custom/doctype/package_doctype/package_doctype.json similarity index 88% rename from frappe/custom/doctype/customization_export/customization_export.json rename to frappe/custom/doctype/package_doctype/package_doctype.json index bc59a89f4f..cfc4cbbe8f 100644 --- a/frappe/custom/doctype/customization_export/customization_export.json +++ b/frappe/custom/doctype/package_doctype/package_doctype.json @@ -1,6 +1,6 @@ { "actions": [], - "creation": "2020-03-12 14:51:09.260025", + "creation": "2020-03-14 11:20:42.875446", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", @@ -20,33 +20,33 @@ "options": "DocType", "reqd": 1 }, + { + "fieldname": "sb_00", + "fieldtype": "Section Break" + }, { "fieldname": "filters", "fieldtype": "Code", "in_list_view": 1, "label": "Filters" }, + { + "fieldname": "cb_00", + "fieldtype": "Column Break" + }, { "fieldname": "or_filters", "fieldtype": "Code", "in_list_view": 1, "label": "Or Filters" - }, - { - "fieldname": "sb_00", - "fieldtype": "Section Break" - }, - { - "fieldname": "cb_00", - "fieldtype": "Column Break" } ], "istable": 1, "links": [], - "modified": "2020-03-13 16:53:15.214275", + "modified": "2020-03-14 11:20:42.875446", "modified_by": "Administrator", "module": "Custom", - "name": "Customization Export", + "name": "Package DocType", "owner": "Administrator", "permissions": [], "quick_entry": 1, diff --git a/frappe/custom/doctype/customization_export/customization_export.py b/frappe/custom/doctype/package_doctype/package_doctype.py similarity index 86% rename from frappe/custom/doctype/customization_export/customization_export.py rename to frappe/custom/doctype/package_doctype/package_doctype.py index 70a7da3b08..c185395965 100644 --- a/frappe/custom/doctype/customization_export/customization_export.py +++ b/frappe/custom/doctype/package_doctype/package_doctype.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class CustomizationExport(Document): +class PackageDocType(Document): pass From 6aa5580eaee2bc6112d81a5d6be36dcc45a6ab35 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Sat, 14 Mar 2020 12:16:30 +0530 Subject: [PATCH 009/107] Update package.py --- frappe/custom/doctype/package/package.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index e3a29144d9..e3b6468791 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -50,7 +50,7 @@ def import_package(): content = json.loads(frappe.get_doc("File", package_file[0].name).get_content()) length = len(content) - for idx, doc in enumerate(content.get("message").get("data")): + for idx, doc in enumerate(content): frappe.publish_realtime("importing_package", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) @@ -79,4 +79,4 @@ def download_package(): frappe.response['filename'] = 'Package.json' frappe.response['filecontent'] = data.get("data") frappe.response['content_type'] = 'application/json' - frappe.response['type'] = 'download' \ No newline at end of file + frappe.response['type'] = 'download' From ea93f12a9a8b5557b1bbcc90c1d656f577f680aa Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 16 Mar 2020 22:29:55 +0530 Subject: [PATCH 010/107] feat: use UI for adding filters --- frappe/custom/doctype/package/package.js | 80 +++++++++++++++++++ frappe/custom/doctype/package/package.py | 29 +++---- .../package_doctype/package_doctype.json | 32 +++----- 3 files changed, 105 insertions(+), 36 deletions(-) diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index c362c51b24..08ec8ac96b 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -42,3 +42,83 @@ frappe.ui.form.on('Package', { }); } }); + +frappe.ui.form.on('Package DocType', { + form_render: function (frm, cdt, cdn) { + function _show_filters(filters, table) { + table.find('tbody').empty(); + + if (filters.length > 0) { + filters.forEach(filter => { + const filter_row = + $(` + ${filter[1]} + ${filter[2] || ""} + ${filter[3]} + `); + + table.find('tbody').append(filter_row); + }); + } else { + const filter_row = $(` + ${__("Click to Set Filters")}`); + table.find('tbody').append(filter_row); + } + } + + let row = frappe.get_doc(cdt, cdn); + + let wrapper = $(`[data-fieldname="filters_json"]`).empty(); + let table = $(` + + + + + + + + + +
${__('Filter')}${__('Condition')}${__('Value')}
`).appendTo(wrapper); + $(`

${__("Click table to edit")}

`).appendTo(wrapper); + + let filters = JSON.parse(row.filters_json || '[]'); + _show_filters(filters, table); + + table.on('click', () => { + frappe.model.with_doctype(row.document_type, function() { + let dialog = new frappe.ui.Dialog({ + title: __('Set Filters'), + fields: [ + { + fieldtype: 'HTML', + label: 'Filters', + fieldname: 'filter_area', + } + ], + primary_action: function() { + let values = filter_group.get_filters(); + let flt = [] + if (values) { + values.forEach(function(value) { + flt.push([value[0], value[1], value[2], value[3]]); + }); + } + row.filters_json = JSON.stringify(flt); + _show_filters(flt, table); + dialog.hide(); + }, + primary_action_label: "Set" + }); + + let filter_group = new frappe.ui.FilterGroup({ + parent: dialog.get_field('filter_area').$wrapper, + doctype: row.document_type, + on_change: () => {}, + }); + filter_group.add_filters_to_filter_group(filters); + dialog.show(); + }) + }); + }, +}); \ No newline at end of file diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index e3b6468791..06edcc4ce0 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -18,14 +18,12 @@ def export_package(): package = [] for doctype in package_doc.export_package: - filters, or_filters = {}, {} + filters = [] - if doctype.get("filters"): - filters = json.loads(doctype.get("filters")) - if doctype.get("or_filters"): - or_filters = json.loads(doctype.get("or_filters")) + if doctype.get("filters_json"): + filters = json.loads(doctype.get("filters_json")) - docs = frappe.get_all(doctype.get("document_type"), filters=filters, or_filters=or_filters) + docs = frappe.get_all(doctype.get("document_type"), filters=filters) length = len(docs) for idx, doc in enumerate(docs): frappe.publish_realtime("exporting_package", dict(progress=idx, total=length, message=doctype.get("document_type")), user=frappe.session.user) @@ -36,18 +34,21 @@ def export_package(): }) @frappe.whitelist() -def import_package(): +def import_package(package=None): """Import package from JSON""" - package_file = frappe.get_all("File", filters={ - "attached_to_doctype": "Package", - "attached_to_name": "Package" - }, limit=1, order_by="creation desc") + if not package: + package_file = frappe.get_all("File", filters={ + "attached_to_doctype": "Package", + "attached_to_name": "Package" + }, limit=1, order_by="creation desc") - if not package_file: - return + if not package_file: + return - content = json.loads(frappe.get_doc("File", package_file[0].name).get_content()) + package = frappe.get_doc("File", package_file[0].name).get_content() + + content = json.loads(package) length = len(content) for idx, doc in enumerate(content): diff --git a/frappe/custom/doctype/package_doctype/package_doctype.json b/frappe/custom/doctype/package_doctype/package_doctype.json index cfc4cbbe8f..353f01a673 100644 --- a/frappe/custom/doctype/package_doctype/package_doctype.json +++ b/frappe/custom/doctype/package_doctype/package_doctype.json @@ -6,10 +6,8 @@ "engine": "InnoDB", "field_order": [ "document_type", - "sb_00", - "filters", - "cb_00", - "or_filters" + "section_break_4", + "filters_json" ], "fields": [ { @@ -21,29 +19,19 @@ "reqd": 1 }, { - "fieldname": "sb_00", + "fieldname": "filters_json", + "fieldtype": "Code", + "label": "Filters", + "options": "JSON" + }, + { + "fieldname": "section_break_4", "fieldtype": "Section Break" - }, - { - "fieldname": "filters", - "fieldtype": "Code", - "in_list_view": 1, - "label": "Filters" - }, - { - "fieldname": "cb_00", - "fieldtype": "Column Break" - }, - { - "fieldname": "or_filters", - "fieldtype": "Code", - "in_list_view": 1, - "label": "Or Filters" } ], "istable": 1, "links": [], - "modified": "2020-03-14 11:20:42.875446", + "modified": "2020-03-16 22:11:40.479498", "modified_by": "Administrator", "module": "Custom", "name": "Package DocType", From f0ec5cec0e0da7b07caf1172fbb8e20fa8cf2230 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 17 Mar 2020 11:37:47 +0530 Subject: [PATCH 011/107] feat: package migrator --- .../doctype/package_migration/__init__.py | 0 .../package_migration/package_migration.js | 51 +++++++++++++++++++ .../package_migration/package_migration.json | 42 +++++++++++++++ .../package_migration/package_migration.py | 28 ++++++++++ .../test_package_migration.py | 10 ++++ 5 files changed, 131 insertions(+) create mode 100644 frappe/custom/doctype/package_migration/__init__.py create mode 100644 frappe/custom/doctype/package_migration/package_migration.js create mode 100644 frappe/custom/doctype/package_migration/package_migration.json create mode 100644 frappe/custom/doctype/package_migration/package_migration.py create mode 100644 frappe/custom/doctype/package_migration/test_package_migration.py diff --git a/frappe/custom/doctype/package_migration/__init__.py b/frappe/custom/doctype/package_migration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/package_migration/package_migration.js b/frappe/custom/doctype/package_migration/package_migration.js new file mode 100644 index 0000000000..020092f5f3 --- /dev/null +++ b/frappe/custom/doctype/package_migration/package_migration.js @@ -0,0 +1,51 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Package Migration', { + refresh: function(frm) { + if (frm.doc.attach) { + frm.add_custom_button(__("Connect"), function() { + let dialog = new frappe.ui.Dialog({ + title: __('Connect to Remote Instance'), + fields: [ + { + fieldtype: 'Data', + label: 'Remote Instance', + fieldname: 'remote_instance', + reqd: 1 + }, + { + fieldtype: 'Data', + label: 'User', + fieldname: 'user', + reqd: 1 + }, + { + fieldtype: 'Password', + label: 'Password', + fieldname: 'password', + reqd: 1 + }, + ], + primary_action: function() { + let values = dialog.get_values(); + frm.call("install_package_to_remote", { + "remote_instance": values.remote_instance, + "user": values.user, + "password": values.password + }).then((r) => { + console.log(r); + }) + dialog.hide(); + }, + primary_action_label: "Execute" + }); + dialog.show(); + }) + } + } +}); + +// "erpnext_support_password": "uYrMeEhb2NzuEGOk", +// "erpnext_support_url": "https://marketplace.erpnext.com", +// "erpnext_support_user": "erpnext_support_fQrhUujW@erpnext.com", \ No newline at end of file diff --git a/frappe/custom/doctype/package_migration/package_migration.json b/frappe/custom/doctype/package_migration/package_migration.json new file mode 100644 index 0000000000..a3c230502b --- /dev/null +++ b/frappe/custom/doctype/package_migration/package_migration.json @@ -0,0 +1,42 @@ +{ + "actions": [], + "creation": "2020-03-17 00:13:31.851579", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "attach" + ], + "fields": [ + { + "fieldname": "attach", + "fieldtype": "Attach", + "in_list_view": 1, + "label": "Attach", + "reqd": 1 + } + ], + "issingle": 1, + "links": [], + "modified": "2020-03-17 10:00:26.676328", + "modified_by": "Administrator", + "module": "Custom", + "name": "Package Migration", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/package_migration/package_migration.py b/frappe/custom/doctype/package_migration/package_migration.py new file mode 100644 index 0000000000..cdcbc0bd19 --- /dev/null +++ b/frappe/custom/doctype/package_migration/package_migration.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.frappeclient import FrappeClient +from frappe.model.document import Document +from frappe import _ + +class PackageMigration(Document): + + def install_package_to_remote(self, remote_instance, user, password): + connection = FrappeClient(remote_instance, user, password) + + package_file = frappe.get_all("File", filters={ + "attached_to_doctype": "Package Migration", + "attached_to_name": "Package Migration" + }, limit=1, order_by="creation desc") + + try: + connection.post_request({ + "cmd": "frappe.custom.doctype.package.package.import_package", + "package": frappe.get_doc("File", package_file[0].name).get_content() + }) + except Exception: + frappe.log_error(frappe.get_traceback()) + frappe.throw(_("Could not connect to Remote Site.")) diff --git a/frappe/custom/doctype/package_migration/test_package_migration.py b/frappe/custom/doctype/package_migration/test_package_migration.py new file mode 100644 index 0000000000..8b438145e8 --- /dev/null +++ b/frappe/custom/doctype/package_migration/test_package_migration.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestPackageMigration(unittest.TestCase): + pass From 230d78df2511032f7bc93021b004ab1786911726 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 18 Mar 2020 16:16:41 +0530 Subject: [PATCH 012/107] feat: deploy remote instance with package --- frappe/custom/doctype/package/package.js | 42 ++--------- frappe/custom/doctype/package/package.json | 37 +-------- frappe/custom/doctype/package/package.py | 75 ++++++++++++------- .../package_doctype/package_doctype.json | 23 +++++- .../package_migration/package_migration.js | 51 ------------- .../package_migration/package_migration.py | 28 ------- .../__init__.py | 0 frappe/custom/doctype/release/release.js | 22 ++++++ .../release.json} | 17 ++--- frappe/custom/doctype/release/release.py | 45 +++++++++++ .../test_release.py} | 2 +- .../doctype/release_instance/__init__.py | 0 .../release_instance/release_instance.json | 31 ++++++++ .../release_instance/release_instance.py | 10 +++ .../doctype/remote_instance/__init__.py | 0 .../remote_instance/remote_instance.js | 8 ++ .../remote_instance/remote_instance.json | 67 +++++++++++++++++ .../remote_instance/remote_instance.py | 10 +++ .../remote_instance/test_remote_instance.py | 10 +++ 19 files changed, 290 insertions(+), 188 deletions(-) delete mode 100644 frappe/custom/doctype/package_migration/package_migration.js delete mode 100644 frappe/custom/doctype/package_migration/package_migration.py rename frappe/custom/doctype/{package_migration => release}/__init__.py (100%) create mode 100644 frappe/custom/doctype/release/release.js rename frappe/custom/doctype/{package_migration/package_migration.json => release/release.json} (67%) create mode 100644 frappe/custom/doctype/release/release.py rename frappe/custom/doctype/{package_migration/test_package_migration.py => release/test_release.py} (79%) create mode 100644 frappe/custom/doctype/release_instance/__init__.py create mode 100644 frappe/custom/doctype/release_instance/release_instance.json create mode 100644 frappe/custom/doctype/release_instance/release_instance.py create mode 100644 frappe/custom/doctype/remote_instance/__init__.py create mode 100644 frappe/custom/doctype/remote_instance/remote_instance.js create mode 100644 frappe/custom/doctype/remote_instance/remote_instance.json create mode 100644 frappe/custom/doctype/remote_instance/remote_instance.py create mode 100644 frappe/custom/doctype/remote_instance/test_remote_instance.py diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index 08ec8ac96b..2fb0b8eb1d 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -3,44 +3,12 @@ frappe.ui.form.on('Package', { refresh: function(frm) { - frappe.realtime.on("exporting_package", (data) => { - if (data.progress !== (data.total-1)) { - frm.dashboard.show_progress("Exporting", data.progress / data.total * 100, __("{0}", [data.message])); - } else { - frm.dashboard.hide_progress(); - } - }); - - frappe.realtime.on("importing_package", (data) => { - if (data.progress !== (data.total-1)) { - frm.dashboard.show_progress("Importing", data.progress / data.total * 100, __("{0}", [data.message])); - } else { - frm.dashboard.hide_progress(); - } - }); + if (frm.doc.export_package) { + frm.add_custom_button(__("Go to Release"), function() { + frappe.set_route("Form", "Release", "Release"); + }); + } }, - export: function(frm) { - frappe.call({ - method: 'frappe.custom.doctype.package.package.export_package', - callback: function(r) { - if (r.message) { - const args = { - cmd: 'frappe.custom.doctype.package.package.download_package', - data: r.message.data - }; - open_url_post(frappe.request.url, args, true); - } - } - }); - }, - import: function(frm) { - frappe.call({ - method: 'frappe.custom.doctype.package.package.import_package', - callback: function() { - frappe.msgprint(__("Package Imported.")); - } - }); - } }); frappe.ui.form.on('Package DocType', { diff --git a/frappe/custom/doctype/package/package.json b/frappe/custom/doctype/package/package.json index cad2ca6788..1d71b744d1 100644 --- a/frappe/custom/doctype/package/package.json +++ b/frappe/custom/doctype/package/package.json @@ -5,51 +5,20 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "sb_00", - "export_package", - "export", - "sb_01", - "import_package", - "import" + "export_package" ], "fields": [ { - "fieldname": "sb_00", - "fieldtype": "Section Break", - "label": "Export Package" - }, - { - "depends_on": "export_package", - "fieldname": "export", - "fieldtype": "Button", - "label": "Export" - }, - { - "fieldname": "sb_01", - "fieldtype": "Section Break", - "label": "Import Package" - }, - { - "depends_on": "import_package", - "fieldname": "import", - "fieldtype": "Button", - "label": "Import" - }, - { + "description": "Click on the row for accessing filters.", "fieldname": "export_package", "fieldtype": "Table", "label": "Package", "options": "Package DocType" - }, - { - "fieldname": "import_package", - "fieldtype": "Attach", - "label": "Attach" } ], "issingle": 1, "links": [], - "modified": "2020-03-14 11:52:58.221081", + "modified": "2020-03-17 16:41:13.542896", "modified_by": "Administrator", "module": "Custom", "name": "Package", diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index 06edcc4ce0..a51386acfc 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -4,8 +4,10 @@ from __future__ import unicode_literals import frappe -from frappe.model.document import Document import json +from frappe.model.document import Document +from frappe.core.doctype.version.version import get_diff +from frappe.utils.file_manager import save_file class Package(Document): pass @@ -25,9 +27,29 @@ def export_package(): docs = frappe.get_all(doctype.get("document_type"), filters=filters) length = len(docs) + for idx, doc in enumerate(docs): - frappe.publish_realtime("exporting_package", dict(progress=idx, total=length, message=doctype.get("document_type")), user=frappe.session.user) - package.append(frappe.get_doc(doctype.get("document_type"), doc.name).as_dict()) + frappe.publish_realtime("exporting_package", {"progress":idx, "total":length, "message":doctype.get("document_type")}, + user=frappe.session.user) + + document = frappe.get_doc(doctype.get("document_type"), doc.name).as_dict() + + if doctype.attachments: + attachments = [] + filters = {"attached_to_doctype": document.get("doctype"), "attached_to_name": document.get("doctype")} + for f in frappe.get_list("File", filters=filters): + attachments.append({ + "fname": f.name, + "content": frappe.get_doc("File", f.name).get_content() + }) + + document.update({"attachments": json.dumps(attachments)}) + + if doctype.overwrite: + document.update({"overwrite": 1}) + + document.update({"modified": frappe.utils.get_datetime_str(document.get("modified"))}) + package.append(document) return frappe._dict({ "data": post_process(package) @@ -37,26 +59,33 @@ def export_package(): def import_package(package=None): """Import package from JSON""" - if not package: - package_file = frappe.get_all("File", filters={ - "attached_to_doctype": "Package", - "attached_to_name": "Package" - }, limit=1, order_by="creation desc") - - if not package_file: - return - - package = frappe.get_doc("File", package_file[0].name).get_content() - content = json.loads(package) length = len(content) - for idx, doc in enumerate(content): - frappe.publish_realtime("importing_package", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) - frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) + for doc in content: + docname = doc.pop("name") + modified = doc.pop("modified") + overwrite = doc.pop("overwrite") + exists = frappe.db.exists(doc.get("doctype"), docname) + + if not exists: + d = frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) + if doc.get("attachments"): + add_attachment(doc.get("attachments"), d) + elif exists and overwrite: + document = frappe.get_doc(doc.get("doctype"), doc.pop("name")) + if frappe.utils.get_datetime(document.modified) < frappe.utils.get_datetime(modified): + document.update(doc) + document.save() + if doc.get("attachments"): + add_attachment(doc.get("attachments"), document) + +def add_attachment(attachments, doc): + for attachment in attachments: + save_file(attachment.get("fname"), attachment.get("content"), doc.get("doctype"), doc.get("name")) def post_process(package): - del_keys = ('modified_by', 'creation', 'owner', 'idx', 'name', 'modified', 'docstatus') + del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus') for doc in package: for key in del_keys: @@ -68,16 +97,8 @@ def post_process(package): continue for child in value: - for key in del_keys: + for key in del_keys + ('name'): if key in child: del child[key] return package - -@frappe.whitelist() -def download_package(): - data = frappe._dict(frappe.local.form_dict) - frappe.response['filename'] = 'Package.json' - frappe.response['filecontent'] = data.get("data") - frappe.response['content_type'] = 'application/json' - frappe.response['type'] = 'download' diff --git a/frappe/custom/doctype/package_doctype/package_doctype.json b/frappe/custom/doctype/package_doctype/package_doctype.json index 353f01a673..058cff6480 100644 --- a/frappe/custom/doctype/package_doctype/package_doctype.json +++ b/frappe/custom/doctype/package_doctype/package_doctype.json @@ -6,6 +6,9 @@ "engine": "InnoDB", "field_order": [ "document_type", + "column_break_2", + "attachments", + "overwrite", "section_break_4", "filters_json" ], @@ -27,11 +30,29 @@ { "fieldname": "section_break_4", "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "attachments", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Include Attachments" + }, + { + "default": "0", + "fieldname": "overwrite", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Overwrite" } ], "istable": 1, "links": [], - "modified": "2020-03-16 22:11:40.479498", + "modified": "2020-03-17 17:27:58.859896", "modified_by": "Administrator", "module": "Custom", "name": "Package DocType", diff --git a/frappe/custom/doctype/package_migration/package_migration.js b/frappe/custom/doctype/package_migration/package_migration.js deleted file mode 100644 index 020092f5f3..0000000000 --- a/frappe/custom/doctype/package_migration/package_migration.js +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Package Migration', { - refresh: function(frm) { - if (frm.doc.attach) { - frm.add_custom_button(__("Connect"), function() { - let dialog = new frappe.ui.Dialog({ - title: __('Connect to Remote Instance'), - fields: [ - { - fieldtype: 'Data', - label: 'Remote Instance', - fieldname: 'remote_instance', - reqd: 1 - }, - { - fieldtype: 'Data', - label: 'User', - fieldname: 'user', - reqd: 1 - }, - { - fieldtype: 'Password', - label: 'Password', - fieldname: 'password', - reqd: 1 - }, - ], - primary_action: function() { - let values = dialog.get_values(); - frm.call("install_package_to_remote", { - "remote_instance": values.remote_instance, - "user": values.user, - "password": values.password - }).then((r) => { - console.log(r); - }) - dialog.hide(); - }, - primary_action_label: "Execute" - }); - dialog.show(); - }) - } - } -}); - -// "erpnext_support_password": "uYrMeEhb2NzuEGOk", -// "erpnext_support_url": "https://marketplace.erpnext.com", -// "erpnext_support_user": "erpnext_support_fQrhUujW@erpnext.com", \ No newline at end of file diff --git a/frappe/custom/doctype/package_migration/package_migration.py b/frappe/custom/doctype/package_migration/package_migration.py deleted file mode 100644 index cdcbc0bd19..0000000000 --- a/frappe/custom/doctype/package_migration/package_migration.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.frappeclient import FrappeClient -from frappe.model.document import Document -from frappe import _ - -class PackageMigration(Document): - - def install_package_to_remote(self, remote_instance, user, password): - connection = FrappeClient(remote_instance, user, password) - - package_file = frappe.get_all("File", filters={ - "attached_to_doctype": "Package Migration", - "attached_to_name": "Package Migration" - }, limit=1, order_by="creation desc") - - try: - connection.post_request({ - "cmd": "frappe.custom.doctype.package.package.import_package", - "package": frappe.get_doc("File", package_file[0].name).get_content() - }) - except Exception: - frappe.log_error(frappe.get_traceback()) - frappe.throw(_("Could not connect to Remote Site.")) diff --git a/frappe/custom/doctype/package_migration/__init__.py b/frappe/custom/doctype/release/__init__.py similarity index 100% rename from frappe/custom/doctype/package_migration/__init__.py rename to frappe/custom/doctype/release/__init__.py diff --git a/frappe/custom/doctype/release/release.js b/frappe/custom/doctype/release/release.js new file mode 100644 index 0000000000..b0919bef43 --- /dev/null +++ b/frappe/custom/doctype/release/release.js @@ -0,0 +1,22 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Release', { + refresh: function(frm) { + frm.add_custom_button(__("Go to Package"), function() { + frappe.set_route("Form", "Package", "Package"); + }); + + frm.add_custom_button(__("Release"), function() { + frm.call("create_release").then(() => {}); + }); + + frappe.realtime.on("exporting_package", (data) => { + if (data.progress !== (data.total-1)) { + frm.dashboard.show_progress("Releasing Package", data.progress / data.total * 100, __("{0}", [data.message])); + } else { + frm.dashboard.hide_progress(); + } + }); + } +}); diff --git a/frappe/custom/doctype/package_migration/package_migration.json b/frappe/custom/doctype/release/release.json similarity index 67% rename from frappe/custom/doctype/package_migration/package_migration.json rename to frappe/custom/doctype/release/release.json index a3c230502b..ffe7c599cc 100644 --- a/frappe/custom/doctype/package_migration/package_migration.json +++ b/frappe/custom/doctype/release/release.json @@ -1,27 +1,26 @@ { "actions": [], - "creation": "2020-03-17 00:13:31.851579", + "creation": "2020-03-17 13:27:30.158389", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "attach" + "instances" ], "fields": [ { - "fieldname": "attach", - "fieldtype": "Attach", - "in_list_view": 1, - "label": "Attach", - "reqd": 1 + "fieldname": "instances", + "fieldtype": "Table", + "label": "Instances", + "options": "Release Instance" } ], "issingle": 1, "links": [], - "modified": "2020-03-17 10:00:26.676328", + "modified": "2020-03-17 15:37:21.813063", "modified_by": "Administrator", "module": "Custom", - "name": "Package Migration", + "name": "Release", "owner": "Administrator", "permissions": [ { diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py new file mode 100644 index 0000000000..27ecf5c459 --- /dev/null +++ b/frappe/custom/doctype/release/release.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import json +from frappe import _ +from frappe.model.document import Document +from frappe.custom.doctype.package.package import export_package +from frappe.frappeclient import FrappeClient +from frappe.utils.file_manager import save_file +from frappe.model.naming import make_autoname +from frappe.utils.password import get_decrypted_password + +class Release(Document): + + def create_release(self): + package = export_package().get("data") + + for dt_file in frappe.get_list("File", filters={"attached_to_doctype": "Release", "attached_to_name": "Release"}): + frappe.delete_doc_if_exists("File", dt_file.name) + + file_name = make_autoname("Package") + save_file(file_name, json.dumps(package), "Release", "Release") + + length = len(self.instances) + for instance in self.instances: + message = _("Releasing to {0}").format(instance.instance) + frappe.publish_realtime("exporting_package", {"progress": idx, "total": length, "message": message}, + user=frappe.session.user) + remote = frappe.get_doc("Remote Instance", instance.instance) + self.install_package_to_remote(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) + + def install_package_to_remote(self, remote_instance, user, password): + connection = FrappeClient(remote_instance, user, password) + + try: + connection.post_request({ + "cmd": "frappe.custom.doctype.package.package.import_package", + "package": package + }) + except Exception: + frappe.log_error(frappe.get_traceback()) + frappe.throw(_("Could not connect to Site {0}.").format(remote_instance)) diff --git a/frappe/custom/doctype/package_migration/test_package_migration.py b/frappe/custom/doctype/release/test_release.py similarity index 79% rename from frappe/custom/doctype/package_migration/test_package_migration.py rename to frappe/custom/doctype/release/test_release.py index 8b438145e8..13e4e26ac0 100644 --- a/frappe/custom/doctype/package_migration/test_package_migration.py +++ b/frappe/custom/doctype/release/test_release.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestPackageMigration(unittest.TestCase): +class TestRelease(unittest.TestCase): pass diff --git a/frappe/custom/doctype/release_instance/__init__.py b/frappe/custom/doctype/release_instance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/release_instance/release_instance.json b/frappe/custom/doctype/release_instance/release_instance.json new file mode 100644 index 0000000000..a91b00666f --- /dev/null +++ b/frappe/custom/doctype/release_instance/release_instance.json @@ -0,0 +1,31 @@ +{ + "actions": [], + "creation": "2020-03-17 12:24:29.615432", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "instance" + ], + "fields": [ + { + "fieldname": "instance", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Instance", + "options": "Remote Instance" + } + ], + "istable": 1, + "links": [], + "modified": "2020-03-17 13:35:38.456830", + "modified_by": "Administrator", + "module": "Custom", + "name": "Release Instance", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/release_instance/release_instance.py b/frappe/custom/doctype/release_instance/release_instance.py new file mode 100644 index 0000000000..f5b745a9b4 --- /dev/null +++ b/frappe/custom/doctype/release_instance/release_instance.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ReleaseInstance(Document): + pass diff --git a/frappe/custom/doctype/remote_instance/__init__.py b/frappe/custom/doctype/remote_instance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/remote_instance/remote_instance.js b/frappe/custom/doctype/remote_instance/remote_instance.js new file mode 100644 index 0000000000..50096efe4a --- /dev/null +++ b/frappe/custom/doctype/remote_instance/remote_instance.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Remote Instance', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/custom/doctype/remote_instance/remote_instance.json b/frappe/custom/doctype/remote_instance/remote_instance.json new file mode 100644 index 0000000000..4224d40c4c --- /dev/null +++ b/frappe/custom/doctype/remote_instance/remote_instance.json @@ -0,0 +1,67 @@ +{ + "actions": [], + "autoname": "field:instance", + "creation": "2020-03-17 12:08:33.304631", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "instance", + "user", + "password" + ], + "fields": [ + { + "fieldname": "instance", + "fieldtype": "Data", + "label": "Instance", + "unique": 1 + }, + { + "fieldname": "user", + "fieldtype": "Data", + "label": "User" + }, + { + "fieldname": "password", + "fieldtype": "Password", + "label": "Password" + } + ], + "links": [], + "modified": "2020-03-17 13:34:40.924501", + "modified_by": "Administrator", + "module": "Custom", + "name": "Remote Instance", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/remote_instance/remote_instance.py b/frappe/custom/doctype/remote_instance/remote_instance.py new file mode 100644 index 0000000000..5fd513a2f6 --- /dev/null +++ b/frappe/custom/doctype/remote_instance/remote_instance.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class RemoteInstance(Document): + pass diff --git a/frappe/custom/doctype/remote_instance/test_remote_instance.py b/frappe/custom/doctype/remote_instance/test_remote_instance.py new file mode 100644 index 0000000000..0eb7561bf5 --- /dev/null +++ b/frappe/custom/doctype/remote_instance/test_remote_instance.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestRemoteInstance(unittest.TestCase): + pass From a02847f12676f310b637ad1cfb89436c24896613 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 18 Mar 2020 23:18:06 +0530 Subject: [PATCH 013/107] fix: remove release instance doctype --- frappe/custom/doctype/package/package.py | 17 +++-- frappe/custom/doctype/release/release.js | 9 +-- frappe/custom/doctype/release/release.json | 4 +- frappe/custom/doctype/release/release.py | 25 +++---- .../release_instance.js} | 2 +- .../release_instance/release_instance.json | 57 ++++++++++++++-- .../test_release_instance.py} | 2 +- .../__init__.py | 0 .../release_instance_list.json | 32 +++++++++ .../release_instance_list.py} | 2 +- .../remote_instance/remote_instance.json | 67 ------------------- 11 files changed, 116 insertions(+), 101 deletions(-) rename frappe/custom/doctype/{remote_instance/remote_instance.js => release_instance/release_instance.js} (79%) rename frappe/custom/doctype/{remote_instance/test_remote_instance.py => release_instance/test_release_instance.py} (79%) rename frappe/custom/doctype/{remote_instance => release_instance_list}/__init__.py (100%) create mode 100644 frappe/custom/doctype/release_instance_list/release_instance_list.json rename frappe/custom/doctype/{remote_instance/remote_instance.py => release_instance_list/release_instance_list.py} (86%) delete mode 100644 frappe/custom/doctype/remote_instance/remote_instance.json diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index a51386acfc..bedc567066 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -5,9 +5,11 @@ from __future__ import unicode_literals import frappe import json +import pickle from frappe.model.document import Document from frappe.core.doctype.version.version import get_diff from frappe.utils.file_manager import save_file +from frappe import _ class Package(Document): pass @@ -29,7 +31,7 @@ def export_package(): length = len(docs) for idx, doc in enumerate(docs): - frappe.publish_realtime("exporting_package", {"progress":idx, "total":length, "message":doctype.get("document_type")}, + frappe.publish_realtime("package", {"progress":idx, "total":length, "message":doctype.get("document_type"), "prefix": _("Exporting")}, user=frappe.session.user) document = frappe.get_doc(doctype.get("document_type"), doc.name).as_dict() @@ -51,15 +53,13 @@ def export_package(): document.update({"modified": frappe.utils.get_datetime_str(document.get("modified"))}) package.append(document) - return frappe._dict({ - "data": post_process(package) - }) + return post_process(package) @frappe.whitelist() def import_package(package=None): """Import package from JSON""" - content = json.loads(package) + content = pickle.loads(package) length = len(content) for doc in content: @@ -85,7 +85,12 @@ def add_attachment(attachments, doc): save_file(attachment.get("fname"), attachment.get("content"), doc.get("doctype"), doc.get("name")) def post_process(package): + """ + Remove the keys from Document and Child Document + Convert datetime, date, time to str + """ del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus') + child_del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus', 'name') for doc in package: for key in del_keys: @@ -97,7 +102,7 @@ def post_process(package): continue for child in value: - for key in del_keys + ('name'): + for key in child_del_keys: if key in child: del child[key] diff --git a/frappe/custom/doctype/release/release.js b/frappe/custom/doctype/release/release.js index b0919bef43..709ae18d36 100644 --- a/frappe/custom/doctype/release/release.js +++ b/frappe/custom/doctype/release/release.js @@ -8,12 +8,13 @@ frappe.ui.form.on('Release', { }); frm.add_custom_button(__("Release"), function() { - frm.call("create_release").then(() => {}); + frm.call("create_release"); }); - frappe.realtime.on("exporting_package", (data) => { - if (data.progress !== (data.total-1)) { - frm.dashboard.show_progress("Releasing Package", data.progress / data.total * 100, __("{0}", [data.message])); + frappe.realtime.on("package", (data) => { + frm.dashboard.show_progress(data.prefix, data.progress / data.total * 100, __("{0}", [data.message])); + if ((data.progress+1) != data.total) { + frm.dashboard.show_progress(data.prefix, data.progress / data.total * 100, __("{0}", [data.message])); } else { frm.dashboard.hide_progress(); } diff --git a/frappe/custom/doctype/release/release.json b/frappe/custom/doctype/release/release.json index ffe7c599cc..ba6c69c49f 100644 --- a/frappe/custom/doctype/release/release.json +++ b/frappe/custom/doctype/release/release.json @@ -12,12 +12,12 @@ "fieldname": "instances", "fieldtype": "Table", "label": "Instances", - "options": "Release Instance" + "options": "Release Instance List" } ], "issingle": 1, "links": [], - "modified": "2020-03-17 15:37:21.813063", + "modified": "2020-03-18 18:25:23.539372", "modified_by": "Administrator", "module": "Custom", "name": "Release", diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py index 27ecf5c459..ae798f7d85 100644 --- a/frappe/custom/doctype/release/release.py +++ b/frappe/custom/doctype/release/release.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe import json +import pickle from frappe import _ from frappe.model.document import Document from frappe.custom.doctype.package.package import export_package @@ -16,30 +17,30 @@ from frappe.utils.password import get_decrypted_password class Release(Document): def create_release(self): - package = export_package().get("data") + package = export_package() for dt_file in frappe.get_list("File", filters={"attached_to_doctype": "Release", "attached_to_name": "Release"}): frappe.delete_doc_if_exists("File", dt_file.name) file_name = make_autoname("Package") - save_file(file_name, json.dumps(package), "Release", "Release") + save_file(file_name, pickle.dumps(package), "Release", "Release") length = len(self.instances) - for instance in self.instances: - message = _("Releasing to {0}").format(instance.instance) - frappe.publish_realtime("exporting_package", {"progress": idx, "total": length, "message": message}, + for idx, instance in enumerate(self.instances): + frappe.publish_realtime("package", {"progress": idx, "total": length, "message": instance.instance, "prefix": _("Deploying")}, user=frappe.session.user) - remote = frappe.get_doc("Remote Instance", instance.instance) - self.install_package_to_remote(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) - def install_package_to_remote(self, remote_instance, user, password): - connection = FrappeClient(remote_instance, user, password) + self.install_package_to_remote(package, instance) + + def install_package_to_remote(self, package, instance): + remote = frappe.get_doc("Release Instance", instance.instance) + connection = FrappeClient(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) try: connection.post_request({ "cmd": "frappe.custom.doctype.package.package.import_package", - "package": package + "package": pickle.dumps(package) }) - except Exception: + except Exception as e: frappe.log_error(frappe.get_traceback()) - frappe.throw(_("Could not connect to Site {0}.").format(remote_instance)) + frappe.throw(_("Error while installing package to site {0}. Please check Error Logs.").format(remote.instance)) diff --git a/frappe/custom/doctype/remote_instance/remote_instance.js b/frappe/custom/doctype/release_instance/release_instance.js similarity index 79% rename from frappe/custom/doctype/remote_instance/remote_instance.js rename to frappe/custom/doctype/release_instance/release_instance.js index 50096efe4a..7447c9c706 100644 --- a/frappe/custom/doctype/remote_instance/remote_instance.js +++ b/frappe/custom/doctype/release_instance/release_instance.js @@ -1,7 +1,7 @@ // Copyright (c) 2020, Frappe Technologies and contributors // For license information, please see license.txt -frappe.ui.form.on('Remote Instance', { +frappe.ui.form.on('Release Instance', { // refresh: function(frm) { // } diff --git a/frappe/custom/doctype/release_instance/release_instance.json b/frappe/custom/doctype/release_instance/release_instance.json index a91b00666f..d1bf07db59 100644 --- a/frappe/custom/doctype/release_instance/release_instance.json +++ b/frappe/custom/doctype/release_instance/release_instance.json @@ -1,29 +1,72 @@ { "actions": [], + "autoname": "field:instance", "creation": "2020-03-17 12:24:29.615432", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "instance" + "instance", + "user", + "password" ], "fields": [ { "fieldname": "instance", - "fieldtype": "Link", + "fieldtype": "Data", "in_list_view": 1, - "label": "Instance", - "options": "Remote Instance" + "in_standard_filter": 1, + "label": "Instance URL", + "unique": 1 + }, + { + "fieldname": "user", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "User", + "options": "Email" + }, + { + "fieldname": "password", + "fieldtype": "Password", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Password" } ], - "istable": 1, "links": [], - "modified": "2020-03-17 13:35:38.456830", + "modified": "2020-03-18 18:54:55.801749", "modified_by": "Administrator", "module": "Custom", "name": "Release Instance", "owner": "Administrator", - "permissions": [], + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1, + "write": 1 + } + ], "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", diff --git a/frappe/custom/doctype/remote_instance/test_remote_instance.py b/frappe/custom/doctype/release_instance/test_release_instance.py similarity index 79% rename from frappe/custom/doctype/remote_instance/test_remote_instance.py rename to frappe/custom/doctype/release_instance/test_release_instance.py index 0eb7561bf5..95df304262 100644 --- a/frappe/custom/doctype/remote_instance/test_remote_instance.py +++ b/frappe/custom/doctype/release_instance/test_release_instance.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestRemoteInstance(unittest.TestCase): +class TestReleaseInstance(unittest.TestCase): pass diff --git a/frappe/custom/doctype/remote_instance/__init__.py b/frappe/custom/doctype/release_instance_list/__init__.py similarity index 100% rename from frappe/custom/doctype/remote_instance/__init__.py rename to frappe/custom/doctype/release_instance_list/__init__.py diff --git a/frappe/custom/doctype/release_instance_list/release_instance_list.json b/frappe/custom/doctype/release_instance_list/release_instance_list.json new file mode 100644 index 0000000000..d9b9b63340 --- /dev/null +++ b/frappe/custom/doctype/release_instance_list/release_instance_list.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2020-03-18 18:25:02.024237", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "instance" + ], + "fields": [ + { + "fieldname": "instance", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Instance", + "options": "Release Instance", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-03-18 22:55:20.130597", + "modified_by": "Administrator", + "module": "Custom", + "name": "Release Instance List", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/remote_instance/remote_instance.py b/frappe/custom/doctype/release_instance_list/release_instance_list.py similarity index 86% rename from frappe/custom/doctype/remote_instance/remote_instance.py rename to frappe/custom/doctype/release_instance_list/release_instance_list.py index 5fd513a2f6..f6eb68a535 100644 --- a/frappe/custom/doctype/remote_instance/remote_instance.py +++ b/frappe/custom/doctype/release_instance_list/release_instance_list.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class RemoteInstance(Document): +class ReleaseInstanceList(Document): pass diff --git a/frappe/custom/doctype/remote_instance/remote_instance.json b/frappe/custom/doctype/remote_instance/remote_instance.json deleted file mode 100644 index 4224d40c4c..0000000000 --- a/frappe/custom/doctype/remote_instance/remote_instance.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "actions": [], - "autoname": "field:instance", - "creation": "2020-03-17 12:08:33.304631", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "instance", - "user", - "password" - ], - "fields": [ - { - "fieldname": "instance", - "fieldtype": "Data", - "label": "Instance", - "unique": 1 - }, - { - "fieldname": "user", - "fieldtype": "Data", - "label": "User" - }, - { - "fieldname": "password", - "fieldtype": "Password", - "label": "Password" - } - ], - "links": [], - "modified": "2020-03-17 13:34:40.924501", - "modified_by": "Administrator", - "module": "Custom", - "name": "Remote Instance", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "All", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file From 1901650514037d29786e2d1841681a39fd7f4648 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 19 Mar 2020 11:33:25 +0530 Subject: [PATCH 014/107] fix: add utils for converting to string --- frappe/custom/doctype/package/package.py | 31 ++++++++++++++++++++---- frappe/utils/data.py | 13 ++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index bedc567066..5ee781c608 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import json -import pickle +import datetime from frappe.model.document import Document from frappe.core.doctype.version.version import get_diff from frappe.utils.file_manager import save_file @@ -59,7 +59,7 @@ def export_package(): def import_package(package=None): """Import package from JSON""" - content = pickle.loads(package) + content = json.loads(package) length = len(content) for doc in content: @@ -98,12 +98,33 @@ def post_process(package): del doc[key] for key, value in doc.items(): + stringified_value = get_stringified_value(value) + if stringified_value: + doc[key] = stringified_value + if not isinstance(value, list): continue for child in value: - for key in child_del_keys: - if key in child: - del child[key] + for child_key in child_del_keys: + if child_key in child: + del child[child_key] + + for child_key, child_value in child.items(): + stringified_value = get_stringified_value(child_value) + if stringified_value: + child[child_key] = stringified_value return package + +def get_stringified_value(value): + if isinstance(value, datetime.datetime): + return frappe.utils.get_datetime_str(value) + + if isinstance(value, datetime.date): + return frappe.utils.get_date_str(value) + + if isinstance(value, datetime.timedelta): + return frappe.utils.get_time_str(value) + + return None \ No newline at end of file diff --git a/frappe/utils/data.py b/frappe/utils/data.py index acebfa2271..4c64624098 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -209,6 +209,19 @@ def get_datetime_str(datetime_obj): datetime_obj = get_datetime(datetime_obj) return datetime_obj.strftime(DATETIME_FORMAT) +def get_date_str(date_obj): + if isinstance(date_obj, string_types): + date_obj = get_datetime(date_obj) + return date_obj.strftime(DATE_FORMAT) + +def get_time_str(timedelta_obj): + if isinstance(timedelta_obj, string_types): + timedelta_obj = to_timedelta(date_obj) + + hours, remainder = divmod(timedelta_obj.seconds, 3600) + minutes, seconds = divmod(remainder, 60) + return "{0}:{1}:{2}".format(hours, minutes, seconds) + def get_user_date_format(): """Get the current user date format. The result will be cached.""" if getattr(frappe.local, "user_date_format", None) is None: From a47efa8d96c98ba8202755bdf80cdc9292a7f493 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 19 Mar 2020 18:09:10 +0530 Subject: [PATCH 015/107] fix: file attachments --- frappe/custom/doctype/package/package.py | 35 ++++++++++++------------ frappe/custom/doctype/release/release.py | 5 ++-- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index 5ee781c608..88549b644e 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -6,9 +6,10 @@ from __future__ import unicode_literals import frappe import json import datetime +import base64 from frappe.model.document import Document from frappe.core.doctype.version.version import get_diff -from frappe.utils.file_manager import save_file +from frappe.utils.file_manager import save_file, get_file from frappe import _ class Package(Document): @@ -35,22 +36,19 @@ def export_package(): user=frappe.session.user) document = frappe.get_doc(doctype.get("document_type"), doc.name).as_dict() + attachments = [] if doctype.attachments: - attachments = [] - filters = {"attached_to_doctype": document.get("doctype"), "attached_to_name": document.get("doctype")} + filters = {"attached_to_doctype": document.get("doctype"), "attached_to_name": document.get("name")} for f in frappe.get_list("File", filters=filters): - attachments.append({ - "fname": f.name, - "content": frappe.get_doc("File", f.name).get_content() - }) + fname, fcontents = get_file(f.name) + attachments.append({"fname": fname, "content": base64.b64encode(fcontents).decode('ascii')}) - document.update({"attachments": json.dumps(attachments)}) + document.update({ + "attachments": attachments, + "overwrite": True if doctype.overwrite else False + }) - if doctype.overwrite: - document.update({"overwrite": 1}) - - document.update({"modified": frappe.utils.get_datetime_str(document.get("modified"))}) package.append(document) return post_process(package) @@ -66,23 +64,24 @@ def import_package(package=None): docname = doc.pop("name") modified = doc.pop("modified") overwrite = doc.pop("overwrite") + attachments = doc.get("attachments") exists = frappe.db.exists(doc.get("doctype"), docname) if not exists: d = frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) - if doc.get("attachments"): - add_attachment(doc.get("attachments"), d) + if attachments: + add_attachment(attachments, d) elif exists and overwrite: - document = frappe.get_doc(doc.get("doctype"), doc.pop("name")) + document = frappe.get_doc(doc.get("doctype"), docname) if frappe.utils.get_datetime(document.modified) < frappe.utils.get_datetime(modified): document.update(doc) document.save() - if doc.get("attachments"): - add_attachment(doc.get("attachments"), document) + if attachments: + add_attachment(attachments, document) def add_attachment(attachments, doc): for attachment in attachments: - save_file(attachment.get("fname"), attachment.get("content"), doc.get("doctype"), doc.get("name")) + save_file(attachment.get("fname"), base64.b64decode(attachment.get("content")), doc.get("doctype"), doc.get("name")) def post_process(package): """ diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py index ae798f7d85..099e98a4fb 100644 --- a/frappe/custom/doctype/release/release.py +++ b/frappe/custom/doctype/release/release.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import frappe import json -import pickle from frappe import _ from frappe.model.document import Document from frappe.custom.doctype.package.package import export_package @@ -23,7 +22,7 @@ class Release(Document): frappe.delete_doc_if_exists("File", dt_file.name) file_name = make_autoname("Package") - save_file(file_name, pickle.dumps(package), "Release", "Release") + save_file(file_name, json.dumps(package), "Release", "Release") length = len(self.instances) for idx, instance in enumerate(self.instances): @@ -39,7 +38,7 @@ class Release(Document): try: connection.post_request({ "cmd": "frappe.custom.doctype.package.package.import_package", - "package": pickle.dumps(package) + "package": json.dumps(package) }) except Exception as e: frappe.log_error(frappe.get_traceback()) From 4a38cf09838db5c7f64b5f8e1a8e55e509954e52 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 19 Mar 2020 19:45:21 +0530 Subject: [PATCH 016/107] fix: package import fixes --- frappe/custom/doctype/package/package.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index 88549b644e..31f681486f 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -61,19 +61,19 @@ def import_package(package=None): length = len(content) for doc in content: - docname = doc.pop("name") modified = doc.pop("modified") overwrite = doc.pop("overwrite") - attachments = doc.get("attachments") - exists = frappe.db.exists(doc.get("doctype"), docname) + attachments = doc.pop("attachments") + exists = frappe.db.exists(doc.get("doctype"), doc.get("name")) if not exists: d = frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) if attachments: add_attachment(attachments, d) - elif exists and overwrite: + elif exists: + docname = doc.pop("name") document = frappe.get_doc(doc.get("doctype"), docname) - if frappe.utils.get_datetime(document.modified) < frappe.utils.get_datetime(modified): + if frappe.utils.get_datetime(document.modified) < frappe.utils.get_datetime(modified) and not overwrite: document.update(doc) document.save() if attachments: From b77d93d22af1425fe7ea2a43d08d1a2fb7369f67 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 19 Mar 2020 21:32:34 +0530 Subject: [PATCH 017/107] fix: add filters for document type in package --- frappe/custom/doctype/package/package.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index 2fb0b8eb1d..f6b908bcf1 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -8,6 +8,14 @@ frappe.ui.form.on('Package', { frappe.set_route("Form", "Release", "Release"); }); } + + frm.set_query("document_type", "export_package", function () { + return { + filters: { + "istable": 0, + } + }; + }) }, }); @@ -54,6 +62,11 @@ frappe.ui.form.on('Package DocType', { _show_filters(filters, table); table.on('click', () => { + if (!row.document_type) { + frappe.msgprint(__("Select Document Type.")); + return; + } + frappe.model.with_doctype(row.document_type, function() { let dialog = new frappe.ui.Dialog({ title: __('Set Filters'), From 0f1eaee1be856a4df4f2734a9d584cf1e701eebd Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 20 Mar 2020 13:28:35 +0530 Subject: [PATCH 018/107] feat: add manual import --- frappe/custom/doctype/package/package.js | 3 +++ frappe/custom/doctype/package/package.json | 24 ++++++++++++++++++++-- frappe/custom/doctype/package/package.py | 21 +++++++++++++++---- frappe/utils/data.py | 2 +- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index f6b908bcf1..aa6fdbe635 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -17,6 +17,9 @@ frappe.ui.form.on('Package', { }; }) }, + import: function(frm) { + frm.call("import_from_package"); + } }); frappe.ui.form.on('Package DocType', { diff --git a/frappe/custom/doctype/package/package.json b/frappe/custom/doctype/package/package.json index 1d71b744d1..7bfdf719cf 100644 --- a/frappe/custom/doctype/package/package.json +++ b/frappe/custom/doctype/package/package.json @@ -5,7 +5,10 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "export_package" + "export_package", + "import_section", + "attach", + "import" ], "fields": [ { @@ -14,11 +17,28 @@ "fieldtype": "Table", "label": "Package", "options": "Package DocType" + }, + { + "collapsible": 1, + "collapsible_depends_on": "attach", + "fieldname": "import_section", + "fieldtype": "Section Break", + "label": "Import Package" + }, + { + "fieldname": "import", + "fieldtype": "Button", + "label": "Import" + }, + { + "fieldname": "attach", + "fieldtype": "Attach", + "label": "Attach Package" } ], "issingle": 1, "links": [], - "modified": "2020-03-17 16:41:13.542896", + "modified": "2020-03-19 22:03:06.012473", "modified_by": "Administrator", "module": "Custom", "name": "Package", diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index 31f681486f..2bf5a5f7cc 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -11,9 +11,22 @@ from frappe.model.document import Document from frappe.core.doctype.version.version import get_diff from frappe.utils.file_manager import save_file, get_file from frappe import _ +from six import string_types class Package(Document): - pass + + def import_from_package(self): + filters = {"attached_to_doctype": "Package", "attached_to_name": "Package"} + files = frappe.get_list("File", filters=filters, limit=1, order_by="creation desc") + if not files: + frappe.msgprint(_("No file attach for Importing.")) + return + + for f in files: + fname, fcontents = get_file(f.name) + import_package(fcontents) + + frappe.msgprint(_("Package Imported.")) @frappe.whitelist() def export_package(): @@ -57,10 +70,10 @@ def export_package(): def import_package(package=None): """Import package from JSON""" - content = json.loads(package) - length = len(content) + if isinstance(package, string_types): + package = json.loads(package) - for doc in content: + for doc in package: modified = doc.pop("modified") overwrite = doc.pop("overwrite") attachments = doc.pop("attachments") diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 4c64624098..96bdb20841 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -216,7 +216,7 @@ def get_date_str(date_obj): def get_time_str(timedelta_obj): if isinstance(timedelta_obj, string_types): - timedelta_obj = to_timedelta(date_obj) + timedelta_obj = to_timedelta(timedelta_obj) hours, remainder = divmod(timedelta_obj.seconds, 3600) minutes, seconds = divmod(remainder, 60) From 8302f251762ea9362ca7b1d1b92f91542b781139 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 20 Mar 2020 15:21:21 +0530 Subject: [PATCH 019/107] chore: codacy fixes --- frappe/custom/doctype/package/package.js | 4 ++-- frappe/custom/doctype/package/package.py | 11 ++++------- frappe/custom/doctype/release/release.py | 9 +++++++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index aa6fdbe635..a053b7d864 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -82,7 +82,7 @@ frappe.ui.form.on('Package DocType', { ], primary_action: function() { let values = filter_group.get_filters(); - let flt = [] + let flt = []; if (values) { values.forEach(function(value) { flt.push([value[0], value[1], value[2], value[3]]); @@ -102,7 +102,7 @@ frappe.ui.form.on('Package DocType', { }); filter_group.add_filters_to_filter_group(filters); dialog.show(); - }) + }); }); }, }); \ No newline at end of file diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index 2bf5a5f7cc..d5da360089 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -8,7 +8,6 @@ import json import datetime import base64 from frappe.model.document import Document -from frappe.core.doctype.version.version import get_diff from frappe.utils.file_manager import save_file, get_file from frappe import _ from six import string_types @@ -30,7 +29,7 @@ class Package(Document): @frappe.whitelist() def export_package(): - """Export package as JSON""" + """Export package as JSON.""" package_doc = frappe.get_single("Package") package = [] @@ -68,7 +67,7 @@ def export_package(): @frappe.whitelist() def import_package(package=None): - """Import package from JSON""" + """Import package from JSON.""" if isinstance(package, string_types): package = json.loads(package) @@ -97,10 +96,8 @@ def add_attachment(attachments, doc): save_file(attachment.get("fname"), base64.b64decode(attachment.get("content")), doc.get("doctype"), doc.get("name")) def post_process(package): - """ - Remove the keys from Document and Child Document - Convert datetime, date, time to str - """ + """Remove the keys from Document and Child Document. Convert datetime, date, time to str.""" + del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus') child_del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus', 'name') diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py index 099e98a4fb..e68be15778 100644 --- a/frappe/custom/doctype/release/release.py +++ b/frappe/custom/doctype/release/release.py @@ -33,13 +33,18 @@ class Release(Document): def install_package_to_remote(self, package, instance): remote = frappe.get_doc("Release Instance", instance.instance) - connection = FrappeClient(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) + + try: + connection = FrappeClient(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) + except Exception: + frappe.log_error(frappe.get_traceback()) + frappe.throw(_("Couldn't connect to site {0}. Please check Error Logs.").format(remote.instance)) try: connection.post_request({ "cmd": "frappe.custom.doctype.package.package.import_package", "package": json.dumps(package) }) - except Exception as e: + except Exception: frappe.log_error(frappe.get_traceback()) frappe.throw(_("Error while installing package to site {0}. Please check Error Logs.").format(remote.instance)) From ddb02bd7366ec83abe6abd9297b383595cde97da Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 31 Mar 2020 11:15:01 +0530 Subject: [PATCH 020/107] Update package.js --- frappe/custom/doctype/package/package.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index a053b7d864..54af424e4d 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -15,7 +15,7 @@ frappe.ui.form.on('Package', { "istable": 0, } }; - }) + }); }, import: function(frm) { frm.call("import_from_package"); @@ -105,4 +105,4 @@ frappe.ui.form.on('Package DocType', { }); }); }, -}); \ No newline at end of file +}); From c12cfffc84c1800807acebd4380fbd1f72dfc750 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 31 Mar 2020 11:45:59 +0530 Subject: [PATCH 021/107] Update package.py --- frappe/custom/doctype/package/package.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index d5da360089..a42de94896 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -30,7 +30,6 @@ class Package(Document): @frappe.whitelist() def export_package(): """Export package as JSON.""" - package_doc = frappe.get_single("Package") package = [] @@ -68,7 +67,6 @@ def export_package(): @frappe.whitelist() def import_package(package=None): """Import package from JSON.""" - if isinstance(package, string_types): package = json.loads(package) @@ -97,7 +95,6 @@ def add_attachment(attachments, doc): def post_process(package): """Remove the keys from Document and Child Document. Convert datetime, date, time to str.""" - del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus') child_del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus', 'name') @@ -136,4 +133,4 @@ def get_stringified_value(value): if isinstance(value, datetime.timedelta): return frappe.utils.get_time_str(value) - return None \ No newline at end of file + return None From 20d54e4588c74920bcbe8033ed2f9a9641f7e0e6 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 6 Apr 2020 14:09:19 +0530 Subject: [PATCH 022/107] fix: child doctype naming --- frappe/custom/doctype/package/package.js | 4 +- frappe/custom/doctype/package/package.json | 18 ++--- frappe/custom/doctype/package/package.py | 19 ++++-- .../__init__.py | 0 .../package_detail/package_detail.json | 65 +++++++++++++++++++ .../package_detail.py} | 2 +- .../package_doctype/package_doctype.json | 65 ------------------- .../release_instance/release_instance.json | 14 +--- 8 files changed, 93 insertions(+), 94 deletions(-) rename frappe/custom/doctype/{package_doctype => package_detail}/__init__.py (100%) create mode 100644 frappe/custom/doctype/package_detail/package_detail.json rename frappe/custom/doctype/{package_doctype/package_doctype.py => package_detail/package_detail.py} (88%) delete mode 100644 frappe/custom/doctype/package_doctype/package_doctype.json diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index 54af424e4d..adad9d7e52 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -3,7 +3,7 @@ frappe.ui.form.on('Package', { refresh: function(frm) { - if (frm.doc.export_package) { + if (frm.doc.package_details) { frm.add_custom_button(__("Go to Release"), function() { frappe.set_route("Form", "Release", "Release"); }); @@ -22,7 +22,7 @@ frappe.ui.form.on('Package', { } }); -frappe.ui.form.on('Package DocType', { +frappe.ui.form.on('Package Details', { form_render: function (frm, cdt, cdn) { function _show_filters(filters, table) { table.find('tbody').empty(); diff --git a/frappe/custom/doctype/package/package.json b/frappe/custom/doctype/package/package.json index 7bfdf719cf..0607abf6d7 100644 --- a/frappe/custom/doctype/package/package.json +++ b/frappe/custom/doctype/package/package.json @@ -5,19 +5,12 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "export_package", + "package_details", "import_section", "attach", "import" ], "fields": [ - { - "description": "Click on the row for accessing filters.", - "fieldname": "export_package", - "fieldtype": "Table", - "label": "Package", - "options": "Package DocType" - }, { "collapsible": 1, "collapsible_depends_on": "attach", @@ -34,11 +27,18 @@ "fieldname": "attach", "fieldtype": "Attach", "label": "Attach Package" + }, + { + "description": "Click on the row for accessing filters.", + "fieldname": "package_details", + "fieldtype": "Table", + "label": "Package Details", + "options": "Package Detail" } ], "issingle": 1, "links": [], - "modified": "2020-03-19 22:03:06.012473", + "modified": "2020-04-06 14:32:42.202640", "modified_by": "Administrator", "module": "Custom", "name": "Package", diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index a42de94896..ed9cb66957 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -33,7 +33,7 @@ def export_package(): package_doc = frappe.get_single("Package") package = [] - for doctype in package_doc.export_package: + for doctype in package_doc.package_details: filters = [] if doctype.get("filters_json"): @@ -43,17 +43,28 @@ def export_package(): length = len(docs) for idx, doc in enumerate(docs): - frappe.publish_realtime("package", {"progress":idx, "total":length, "message":doctype.get("document_type"), "prefix": _("Exporting")}, + frappe.publish_realtime("package", { + "progress":idx, "total":length, + "message":doctype.get("document_type"), + "prefix": _("Exporting") + }, user=frappe.session.user) document = frappe.get_doc(doctype.get("document_type"), doc.name).as_dict() attachments = [] if doctype.attachments: - filters = {"attached_to_doctype": document.get("doctype"), "attached_to_name": document.get("name")} + filters = { + "attached_to_doctype": document.get("doctype"), + "attached_to_name": document.get("name") + } + for f in frappe.get_list("File", filters=filters): fname, fcontents = get_file(f.name) - attachments.append({"fname": fname, "content": base64.b64encode(fcontents).decode('ascii')}) + attachments.append({ + "fname": fname, + "content": base64.b64encode(fcontents).decode('ascii') + }) document.update({ "attachments": attachments, diff --git a/frappe/custom/doctype/package_doctype/__init__.py b/frappe/custom/doctype/package_detail/__init__.py similarity index 100% rename from frappe/custom/doctype/package_doctype/__init__.py rename to frappe/custom/doctype/package_detail/__init__.py diff --git a/frappe/custom/doctype/package_detail/package_detail.json b/frappe/custom/doctype/package_detail/package_detail.json new file mode 100644 index 0000000000..f749587a42 --- /dev/null +++ b/frappe/custom/doctype/package_detail/package_detail.json @@ -0,0 +1,65 @@ +{ + "engine": "InnoDB", + "field_order": [ + "document_type", + "column_break_2", + "attachments", + "overwrite", + "section_break_4", + "filters_json" + ], + "istable": 1, + "modified_by": "Administrator", + "name": "Package Detail", + "links": [], + "fields": [ + { + "label": "Document Type", + "in_list_view": 1, + "fieldtype": "Link", + "reqd": 1, + "fieldname": "document_type", + "options": "DocType" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "in_list_view": 1, + "fieldname": "attachments", + "fieldtype": "Check", + "label": "Include Attachments" + }, + { + "default": "0", + "in_list_view": 1, + "fieldname": "overwrite", + "fieldtype": "Check", + "label": "Overwrite" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "options": "JSON", + "fieldname": "filters_json", + "fieldtype": "Code", + "label": "Filters" + } + ], + "track_changes": 1, + "creation": "2020-04-06 12:59:59.657816", + "doctype": "DocType", + "actions": [], + "modified": "2020-04-06 12:59:59.657816", + "sort_order": "DESC", + "module": "Custom", + "owner": "Administrator", + "sort_field": "modified", + "editable_grid": 1, + "quick_entry": 1, + "permissions": [] +} \ No newline at end of file diff --git a/frappe/custom/doctype/package_doctype/package_doctype.py b/frappe/custom/doctype/package_detail/package_detail.py similarity index 88% rename from frappe/custom/doctype/package_doctype/package_doctype.py rename to frappe/custom/doctype/package_detail/package_detail.py index c185395965..0460329d85 100644 --- a/frappe/custom/doctype/package_doctype/package_doctype.py +++ b/frappe/custom/doctype/package_detail/package_detail.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class PackageDocType(Document): +class PackageDetail(Document): pass diff --git a/frappe/custom/doctype/package_doctype/package_doctype.json b/frappe/custom/doctype/package_doctype/package_doctype.json deleted file mode 100644 index 058cff6480..0000000000 --- a/frappe/custom/doctype/package_doctype/package_doctype.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "actions": [], - "creation": "2020-03-14 11:20:42.875446", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "document_type", - "column_break_2", - "attachments", - "overwrite", - "section_break_4", - "filters_json" - ], - "fields": [ - { - "fieldname": "document_type", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Document Type", - "options": "DocType", - "reqd": 1 - }, - { - "fieldname": "filters_json", - "fieldtype": "Code", - "label": "Filters", - "options": "JSON" - }, - { - "fieldname": "section_break_4", - "fieldtype": "Section Break" - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "attachments", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Include Attachments" - }, - { - "default": "0", - "fieldname": "overwrite", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Overwrite" - } - ], - "istable": 1, - "links": [], - "modified": "2020-03-17 17:27:58.859896", - "modified_by": "Administrator", - "module": "Custom", - "name": "Package DocType", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/custom/doctype/release_instance/release_instance.json b/frappe/custom/doctype/release_instance/release_instance.json index d1bf07db59..90484b16f4 100644 --- a/frappe/custom/doctype/release_instance/release_instance.json +++ b/frappe/custom/doctype/release_instance/release_instance.json @@ -36,7 +36,7 @@ } ], "links": [], - "modified": "2020-03-18 18:54:55.801749", + "modified": "2020-04-06 14:31:49.443654", "modified_by": "Administrator", "module": "Custom", "name": "Release Instance", @@ -53,18 +53,6 @@ "role": "System Manager", "share": 1, "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "All", - "share": 1, - "write": 1 } ], "quick_entry": 1, From 1e7c4531e95e28b80cbacf65d2da6ce810ca1bd8 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 6 Apr 2020 14:52:20 +0530 Subject: [PATCH 023/107] fix: package overwriting --- frappe/custom/doctype/package/package.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index ed9cb66957..0cdfe4a87e 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -91,14 +91,22 @@ def import_package(package=None): d = frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) if attachments: add_attachment(attachments, d) - elif exists: + else: docname = doc.pop("name") document = frappe.get_doc(doc.get("doctype"), docname) - if frappe.utils.get_datetime(document.modified) < frappe.utils.get_datetime(modified) and not overwrite: - document.update(doc) - document.save() - if attachments: - add_attachment(attachments, document) + + if overwrite: + update_document(document, doc, attachments) + + else: + if frappe.utils.get_datetime(document.modified) < frappe.utils.get_datetime(modified): + update_document(document, doc, attachments) + +def update_document(document, doc, attachments): + document.update(doc) + document.save() + if attachments: + add_attachment(attachments, document) def add_attachment(attachments, doc): for attachment in attachments: From 514f2bdae998b1bb5b37b90409b31086828252fa Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 6 Apr 2020 15:32:44 +0530 Subject: [PATCH 024/107] fix: rename release related doctypes --- frappe/custom/doctype/package/package.js | 2 +- frappe/custom/doctype/release/release.json | 4 +- frappe/custom/doctype/release/release.py | 2 +- .../release_instance/release_instance.js | 8 ---- .../release_instance/release_instance.json | 48 ++++--------------- .../release_instance/test_release_instance.py | 10 ---- .../doctype/release_instance_list/__init__.py | 0 .../release_instance_list.json | 32 ------------- .../release_instance_list.py | 10 ---- 9 files changed, 13 insertions(+), 103 deletions(-) delete mode 100644 frappe/custom/doctype/release_instance/release_instance.js delete mode 100644 frappe/custom/doctype/release_instance/test_release_instance.py delete mode 100644 frappe/custom/doctype/release_instance_list/__init__.py delete mode 100644 frappe/custom/doctype/release_instance_list/release_instance_list.json delete mode 100644 frappe/custom/doctype/release_instance_list/release_instance_list.py diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index adad9d7e52..1743f38dd3 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -9,7 +9,7 @@ frappe.ui.form.on('Package', { }); } - frm.set_query("document_type", "export_package", function () { + frm.set_query("document_type", "package_details", function () { return { filters: { "istable": 0, diff --git a/frappe/custom/doctype/release/release.json b/frappe/custom/doctype/release/release.json index ba6c69c49f..b3f44196d0 100644 --- a/frappe/custom/doctype/release/release.json +++ b/frappe/custom/doctype/release/release.json @@ -12,12 +12,12 @@ "fieldname": "instances", "fieldtype": "Table", "label": "Instances", - "options": "Release Instance List" + "options": "Release Instance" } ], "issingle": 1, "links": [], - "modified": "2020-03-18 18:25:23.539372", + "modified": "2020-04-06 14:56:56.206720", "modified_by": "Administrator", "module": "Custom", "name": "Release", diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py index e68be15778..c02aa0605d 100644 --- a/frappe/custom/doctype/release/release.py +++ b/frappe/custom/doctype/release/release.py @@ -32,7 +32,7 @@ class Release(Document): self.install_package_to_remote(package, instance) def install_package_to_remote(self, package, instance): - remote = frappe.get_doc("Release Instance", instance.instance) + remote = frappe.get_doc("Instance", instance.instance) try: connection = FrappeClient(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) diff --git a/frappe/custom/doctype/release_instance/release_instance.js b/frappe/custom/doctype/release_instance/release_instance.js deleted file mode 100644 index 7447c9c706..0000000000 --- a/frappe/custom/doctype/release_instance/release_instance.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Release Instance', { - // refresh: function(frm) { - - // } -}); diff --git a/frappe/custom/doctype/release_instance/release_instance.json b/frappe/custom/doctype/release_instance/release_instance.json index 90484b16f4..2ebdc93954 100644 --- a/frappe/custom/doctype/release_instance/release_instance.json +++ b/frappe/custom/doctype/release_instance/release_instance.json @@ -1,60 +1,30 @@ { "actions": [], - "autoname": "field:instance", - "creation": "2020-03-17 12:24:29.615432", + "creation": "2020-03-18 18:25:02.024237", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "instance", - "user", - "password" + "instance" ], "fields": [ { "fieldname": "instance", - "fieldtype": "Data", + "fieldtype": "Link", "in_list_view": 1, - "in_standard_filter": 1, - "label": "Instance URL", - "unique": 1 - }, - { - "fieldname": "user", - "fieldtype": "Data", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "User", - "options": "Email" - }, - { - "fieldname": "password", - "fieldtype": "Password", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Password" + "label": "Instance", + "options": "Instance", + "reqd": 1 } ], + "istable": 1, "links": [], - "modified": "2020-04-06 14:31:49.443654", + "modified": "2020-04-06 15:30:08.966941", "modified_by": "Administrator", "module": "Custom", "name": "Release Instance", "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], + "permissions": [], "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", diff --git a/frappe/custom/doctype/release_instance/test_release_instance.py b/frappe/custom/doctype/release_instance/test_release_instance.py deleted file mode 100644 index 95df304262..0000000000 --- a/frappe/custom/doctype/release_instance/test_release_instance.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt -from __future__ import unicode_literals - -# import frappe -import unittest - -class TestReleaseInstance(unittest.TestCase): - pass diff --git a/frappe/custom/doctype/release_instance_list/__init__.py b/frappe/custom/doctype/release_instance_list/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/custom/doctype/release_instance_list/release_instance_list.json b/frappe/custom/doctype/release_instance_list/release_instance_list.json deleted file mode 100644 index d9b9b63340..0000000000 --- a/frappe/custom/doctype/release_instance_list/release_instance_list.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "actions": [], - "creation": "2020-03-18 18:25:02.024237", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "instance" - ], - "fields": [ - { - "fieldname": "instance", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Instance", - "options": "Release Instance", - "reqd": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-03-18 22:55:20.130597", - "modified_by": "Administrator", - "module": "Custom", - "name": "Release Instance List", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/custom/doctype/release_instance_list/release_instance_list.py b/frappe/custom/doctype/release_instance_list/release_instance_list.py deleted file mode 100644 index f6eb68a535..0000000000 --- a/frappe/custom/doctype/release_instance_list/release_instance_list.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -# import frappe -from frappe.model.document import Document - -class ReleaseInstanceList(Document): - pass From 23a76cea202588b6d450057cba7a454837f035cd Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 6 Apr 2020 15:42:16 +0530 Subject: [PATCH 025/107] fix: rename instance to deploy instance --- .../doctype/deploy_instance/__init__.py | 0 .../deploy_instance/deploy_instance.js | 8 +++ .../deploy_instance/deploy_instance.json | 63 +++++++++++++++++++ .../deploy_instance/deploy_instance.py | 10 +++ .../deploy_instance/test_deploy_instance.py | 10 +++ frappe/custom/doctype/release/release.py | 2 +- .../release_instance/release_instance.json | 4 +- 7 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 frappe/custom/doctype/deploy_instance/__init__.py create mode 100644 frappe/custom/doctype/deploy_instance/deploy_instance.js create mode 100644 frappe/custom/doctype/deploy_instance/deploy_instance.json create mode 100644 frappe/custom/doctype/deploy_instance/deploy_instance.py create mode 100644 frappe/custom/doctype/deploy_instance/test_deploy_instance.py diff --git a/frappe/custom/doctype/deploy_instance/__init__.py b/frappe/custom/doctype/deploy_instance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/deploy_instance/deploy_instance.js b/frappe/custom/doctype/deploy_instance/deploy_instance.js new file mode 100644 index 0000000000..d4b6e75155 --- /dev/null +++ b/frappe/custom/doctype/deploy_instance/deploy_instance.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('DeployInstance', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/custom/doctype/deploy_instance/deploy_instance.json b/frappe/custom/doctype/deploy_instance/deploy_instance.json new file mode 100644 index 0000000000..2822ab81f9 --- /dev/null +++ b/frappe/custom/doctype/deploy_instance/deploy_instance.json @@ -0,0 +1,63 @@ +{ + "actions": [], + "autoname": "field:instance", + "creation": "2020-04-06 15:34:44.428007", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "instance", + "user", + "password" + ], + "fields": [ + { + "fieldname": "instance", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Instance URL", + "unique": 1 + }, + { + "fieldname": "user", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "User", + "options": "Email" + }, + { + "fieldname": "password", + "fieldtype": "Password", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Password" + } + ], + "links": [], + "modified": "2020-04-06 15:40:39.242881", + "modified_by": "Administrator", + "module": "Custom", + "name": "Deploy Instance", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_views": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/deploy_instance/deploy_instance.py b/frappe/custom/doctype/deploy_instance/deploy_instance.py new file mode 100644 index 0000000000..9bc2e9192e --- /dev/null +++ b/frappe/custom/doctype/deploy_instance/deploy_instance.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class DeployInstance(Document): + pass diff --git a/frappe/custom/doctype/deploy_instance/test_deploy_instance.py b/frappe/custom/doctype/deploy_instance/test_deploy_instance.py new file mode 100644 index 0000000000..cdacc3a1ca --- /dev/null +++ b/frappe/custom/doctype/deploy_instance/test_deploy_instance.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestDeployInstance(unittest.TestCase): + pass diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py index c02aa0605d..05b99f3e57 100644 --- a/frappe/custom/doctype/release/release.py +++ b/frappe/custom/doctype/release/release.py @@ -32,7 +32,7 @@ class Release(Document): self.install_package_to_remote(package, instance) def install_package_to_remote(self, package, instance): - remote = frappe.get_doc("Instance", instance.instance) + remote = frappe.get_doc("Deploy Instance", instance.instance) try: connection = FrappeClient(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) diff --git a/frappe/custom/doctype/release_instance/release_instance.json b/frappe/custom/doctype/release_instance/release_instance.json index 2ebdc93954..4ac4f30246 100644 --- a/frappe/custom/doctype/release_instance/release_instance.json +++ b/frappe/custom/doctype/release_instance/release_instance.json @@ -13,13 +13,13 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Instance", - "options": "Instance", + "options": "Deploy Instance", "reqd": 1 } ], "istable": 1, "links": [], - "modified": "2020-04-06 15:30:08.966941", + "modified": "2020-04-06 15:37:07.099322", "modified_by": "Administrator", "module": "Custom", "name": "Release Instance", From 08d7f905b57060ae4d6418ee2e681bef4830e96e Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 15 Apr 2020 16:36:41 +0530 Subject: [PATCH 026/107] feat: provision to deploy packages --- .../deploy_instance/deploy_instance.json | 45 ++++++----------- frappe/custom/doctype/package/package.js | 24 ++++++--- frappe/custom/doctype/package/package.json | 14 +++++- frappe/custom/doctype/package/package.py | 37 +++++++++++++- frappe/custom/doctype/release/__init__.py | 0 frappe/custom/doctype/release/release.js | 23 --------- frappe/custom/doctype/release/release.json | 41 --------------- frappe/custom/doctype/release/release.py | 50 ------------------- frappe/custom/doctype/release/test_release.py | 10 ---- .../doctype/release_instance/__init__.py | 0 .../release_instance/release_instance.json | 32 ------------ .../release_instance/release_instance.py | 10 ---- 12 files changed, 80 insertions(+), 206 deletions(-) delete mode 100644 frappe/custom/doctype/release/__init__.py delete mode 100644 frappe/custom/doctype/release/release.js delete mode 100644 frappe/custom/doctype/release/release.json delete mode 100644 frappe/custom/doctype/release/release.py delete mode 100644 frappe/custom/doctype/release/test_release.py delete mode 100644 frappe/custom/doctype/release_instance/__init__.py delete mode 100644 frappe/custom/doctype/release_instance/release_instance.json delete mode 100644 frappe/custom/doctype/release_instance/release_instance.py diff --git a/frappe/custom/doctype/deploy_instance/deploy_instance.json b/frappe/custom/doctype/deploy_instance/deploy_instance.json index 2822ab81f9..4bb140f088 100644 --- a/frappe/custom/doctype/deploy_instance/deploy_instance.json +++ b/frappe/custom/doctype/deploy_instance/deploy_instance.json @@ -1,63 +1,46 @@ { "actions": [], - "autoname": "field:instance", - "creation": "2020-04-06 15:34:44.428007", + "creation": "2020-03-18 18:25:02.024237", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "instance", - "user", + "instance_url", + "username", "password" ], "fields": [ { - "fieldname": "instance", + "fieldname": "instance_url", "fieldtype": "Data", "in_list_view": 1, - "in_standard_filter": 1, "label": "Instance URL", - "unique": 1 + "reqd": 1 }, { - "fieldname": "user", + "fieldname": "username", "fieldtype": "Data", "in_list_view": 1, - "in_standard_filter": 1, - "label": "User", - "options": "Email" + "label": "Username", + "reqd": 1 }, { "fieldname": "password", "fieldtype": "Password", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Password" + "label": "Password", + "reqd": 1 } ], + "istable": 1, "links": [], - "modified": "2020-04-06 15:40:39.242881", + "modified": "2020-04-14 13:56:31.167730", "modified_by": "Administrator", "module": "Custom", "name": "Deploy Instance", "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], + "permissions": [], "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_views": 1 + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index 1743f38dd3..47a7bce0df 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -3,12 +3,6 @@ frappe.ui.form.on('Package', { refresh: function(frm) { - if (frm.doc.package_details) { - frm.add_custom_button(__("Go to Release"), function() { - frappe.set_route("Form", "Release", "Release"); - }); - } - frm.set_query("document_type", "package_details", function () { return { filters: { @@ -16,13 +10,29 @@ frappe.ui.form.on('Package', { } }; }); + + frappe.realtime.on("package", (data) => { + frm.dashboard.show_progress(data.prefix, data.progress / data.total * 100, __("{0}", [data.message])); + if ((data.progress+1) != data.total) { + frm.dashboard.show_progress(data.prefix, data.progress / data.total * 100, __("{0}", [data.message])); + } else { + frm.dashboard.hide_progress(); + } + }); + + if(frm.doc.instances){ + frm.add_custom_button(__("Deploy"), function() { + frm.call("deploy_package"); + }); + } + }, import: function(frm) { frm.call("import_from_package"); } }); -frappe.ui.form.on('Package Details', { +frappe.ui.form.on('Package Detail', { form_render: function (frm, cdt, cdn) { function _show_filters(filters, table) { table.find('tbody').empty(); diff --git a/frappe/custom/doctype/package/package.json b/frappe/custom/doctype/package/package.json index 0607abf6d7..2bec7f9b79 100644 --- a/frappe/custom/doctype/package/package.json +++ b/frappe/custom/doctype/package/package.json @@ -6,6 +6,8 @@ "engine": "InnoDB", "field_order": [ "package_details", + "section_break_2", + "instances", "import_section", "attach", "import" @@ -34,11 +36,21 @@ "fieldtype": "Table", "label": "Package Details", "options": "Package Detail" + }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "instances", + "fieldtype": "Table", + "label": "Instances", + "options": "Deploy Instance" } ], "issingle": 1, "links": [], - "modified": "2020-04-06 14:32:42.202640", + "modified": "2020-04-14 13:50:50.779396", "modified_by": "Administrator", "module": "Custom", "name": "Package", diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index 0cdfe4a87e..b4427d55bc 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -11,9 +11,11 @@ from frappe.model.document import Document from frappe.utils.file_manager import save_file, get_file from frappe import _ from six import string_types +from frappe.frappeclient import FrappeClient +from frappe.model.naming import make_autoname +from frappe.utils.password import get_decrypted_password class Package(Document): - def import_from_package(self): filters = {"attached_to_doctype": "Package", "attached_to_name": "Package"} files = frappe.get_list("File", filters=filters, limit=1, order_by="creation desc") @@ -27,6 +29,39 @@ class Package(Document): frappe.msgprint(_("Package Imported.")) + def deploy_package(self): + package = export_package() + + for dt_file in frappe.get_list("File", filters={"attached_to_doctype": "Release", "attached_to_name": "Release"}): + frappe.delete_doc_if_exists("File", dt_file.name) + + file_name = make_autoname("Package") + save_file(file_name, json.dumps(package), "Package", "Package") + + length = len(self.instances) + for idx, instance in enumerate(self.instances): + frappe.publish_realtime("package", {"progress": idx, "total": length, "message": instance.instance_url, "prefix": _("Deploying")}, + user=frappe.session.user) + + self.install_package_to_remote(package, instance) + + def install_package_to_remote(self, package, instance): + print((instance.doctype, instance.name)) + try: + connection = FrappeClient(instance.instance_url, instance.username, get_decrypted_password(instance.doctype, instance.name)) + except Exception: + frappe.log_error(frappe.get_traceback()) + frappe.throw(_("Couldn't connect to site {0}. Please check Error Logs.").format(instance.instance_url)) + + try: + connection.post_request({ + "cmd": "frappe.custom.doctype.package.package.import_package", + "package": json.dumps(package) + }) + except Exception: + frappe.log_error(frappe.get_traceback()) + frappe.throw(_("Error while installing package to site {0}. Please check Error Logs.").format(instance.instance_url)) + @frappe.whitelist() def export_package(): """Export package as JSON.""" diff --git a/frappe/custom/doctype/release/__init__.py b/frappe/custom/doctype/release/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/custom/doctype/release/release.js b/frappe/custom/doctype/release/release.js deleted file mode 100644 index 709ae18d36..0000000000 --- a/frappe/custom/doctype/release/release.js +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Release', { - refresh: function(frm) { - frm.add_custom_button(__("Go to Package"), function() { - frappe.set_route("Form", "Package", "Package"); - }); - - frm.add_custom_button(__("Release"), function() { - frm.call("create_release"); - }); - - frappe.realtime.on("package", (data) => { - frm.dashboard.show_progress(data.prefix, data.progress / data.total * 100, __("{0}", [data.message])); - if ((data.progress+1) != data.total) { - frm.dashboard.show_progress(data.prefix, data.progress / data.total * 100, __("{0}", [data.message])); - } else { - frm.dashboard.hide_progress(); - } - }); - } -}); diff --git a/frappe/custom/doctype/release/release.json b/frappe/custom/doctype/release/release.json deleted file mode 100644 index b3f44196d0..0000000000 --- a/frappe/custom/doctype/release/release.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "actions": [], - "creation": "2020-03-17 13:27:30.158389", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "instances" - ], - "fields": [ - { - "fieldname": "instances", - "fieldtype": "Table", - "label": "Instances", - "options": "Release Instance" - } - ], - "issingle": 1, - "links": [], - "modified": "2020-04-06 14:56:56.206720", - "modified_by": "Administrator", - "module": "Custom", - "name": "Release", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py deleted file mode 100644 index 05b99f3e57..0000000000 --- a/frappe/custom/doctype/release/release.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -import json -from frappe import _ -from frappe.model.document import Document -from frappe.custom.doctype.package.package import export_package -from frappe.frappeclient import FrappeClient -from frappe.utils.file_manager import save_file -from frappe.model.naming import make_autoname -from frappe.utils.password import get_decrypted_password - -class Release(Document): - - def create_release(self): - package = export_package() - - for dt_file in frappe.get_list("File", filters={"attached_to_doctype": "Release", "attached_to_name": "Release"}): - frappe.delete_doc_if_exists("File", dt_file.name) - - file_name = make_autoname("Package") - save_file(file_name, json.dumps(package), "Release", "Release") - - length = len(self.instances) - for idx, instance in enumerate(self.instances): - frappe.publish_realtime("package", {"progress": idx, "total": length, "message": instance.instance, "prefix": _("Deploying")}, - user=frappe.session.user) - - self.install_package_to_remote(package, instance) - - def install_package_to_remote(self, package, instance): - remote = frappe.get_doc("Deploy Instance", instance.instance) - - try: - connection = FrappeClient(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) - except Exception: - frappe.log_error(frappe.get_traceback()) - frappe.throw(_("Couldn't connect to site {0}. Please check Error Logs.").format(remote.instance)) - - try: - connection.post_request({ - "cmd": "frappe.custom.doctype.package.package.import_package", - "package": json.dumps(package) - }) - except Exception: - frappe.log_error(frappe.get_traceback()) - frappe.throw(_("Error while installing package to site {0}. Please check Error Logs.").format(remote.instance)) diff --git a/frappe/custom/doctype/release/test_release.py b/frappe/custom/doctype/release/test_release.py deleted file mode 100644 index 13e4e26ac0..0000000000 --- a/frappe/custom/doctype/release/test_release.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt -from __future__ import unicode_literals - -# import frappe -import unittest - -class TestRelease(unittest.TestCase): - pass diff --git a/frappe/custom/doctype/release_instance/__init__.py b/frappe/custom/doctype/release_instance/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/custom/doctype/release_instance/release_instance.json b/frappe/custom/doctype/release_instance/release_instance.json deleted file mode 100644 index 4ac4f30246..0000000000 --- a/frappe/custom/doctype/release_instance/release_instance.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "actions": [], - "creation": "2020-03-18 18:25:02.024237", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "instance" - ], - "fields": [ - { - "fieldname": "instance", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Instance", - "options": "Deploy Instance", - "reqd": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-04-06 15:37:07.099322", - "modified_by": "Administrator", - "module": "Custom", - "name": "Release Instance", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/custom/doctype/release_instance/release_instance.py b/frappe/custom/doctype/release_instance/release_instance.py deleted file mode 100644 index f5b745a9b4..0000000000 --- a/frappe/custom/doctype/release_instance/release_instance.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -# import frappe -from frappe.model.document import Document - -class ReleaseInstance(Document): - pass From e91050b0e496d872d08a7898fb2124b3a64424ef Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 May 2020 13:14:22 +0530 Subject: [PATCH 027/107] fix: add admin to email unsubscribe --- frappe/desk/page/setup_wizard/install_fixtures.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/frappe/desk/page/setup_wizard/install_fixtures.py b/frappe/desk/page/setup_wizard/install_fixtures.py index c857bd077f..b74153f1c7 100644 --- a/frappe/desk/page/setup_wizard/install_fixtures.py +++ b/frappe/desk/page/setup_wizard/install_fixtures.py @@ -12,6 +12,7 @@ def install(): update_salutations() update_global_search_doctypes() setup_email_linking() + add_unsubscribe() @frappe.whitelist() def update_genders(): @@ -35,4 +36,12 @@ def setup_email_linking(): "email_id": "email_linking@example.com", }) doc.insert(ignore_permissions=True, ignore_if_duplicate=True) - \ No newline at end of file + +def add_unsubscribe(): + email_unsubscribe = [ + {"email": "admin@example.com", "global_unsubscribe": 1}, + {"email": "guest@example.com", "global_unsubscribe": 1} + ] + + for unsubscribe in email_unsubscribe: + frappe.get_doc(unsubscribe).insert() \ No newline at end of file From 37e81e33a52da50ff16ed3e476afe97124a75000 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 May 2020 13:20:59 +0530 Subject: [PATCH 028/107] fix: add patch --- frappe/desk/page/setup_wizard/install_fixtures.py | 5 ++++- frappe/patches.txt | 1 + frappe/patches/v13_0/email_unsubscribe.py | 13 +++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 frappe/patches/v13_0/email_unsubscribe.py diff --git a/frappe/desk/page/setup_wizard/install_fixtures.py b/frappe/desk/page/setup_wizard/install_fixtures.py index b74153f1c7..692c1a117a 100644 --- a/frappe/desk/page/setup_wizard/install_fixtures.py +++ b/frappe/desk/page/setup_wizard/install_fixtures.py @@ -44,4 +44,7 @@ def add_unsubscribe(): ] for unsubscribe in email_unsubscribe: - frappe.get_doc(unsubscribe).insert() \ No newline at end of file + if not frappe.get_all("Email Unsubscribe", filters=unsubscribe): + doc = frappe.new_doc("Email Unsubscribe") + doc.update(unsubscribe) + doc.insert(ignore_permissions=True) \ No newline at end of file diff --git a/frappe/patches.txt b/frappe/patches.txt index cbda8cf677..5d45cbec66 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -273,3 +273,4 @@ execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Settings') frappe.patches.v12_0.remove_parent_and_parenttype_from_print_formats execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders() frappe.patches.v13_0.website_theme_custom_scss +frappe.patches.v13_0.email_unsubscribe diff --git a/frappe/patches/v13_0/email_unsubscribe.py b/frappe/patches/v13_0/email_unsubscribe.py new file mode 100644 index 0000000000..69ed1be948 --- /dev/null +++ b/frappe/patches/v13_0/email_unsubscribe.py @@ -0,0 +1,13 @@ +import frappe + +def execute(): + email_unsubscribe = [ + {"email": "admin@example.com", "global_unsubscribe": 1}, + {"email": "guest@example.com", "global_unsubscribe": 1} + ] + + for unsubscribe in email_unsubscribe: + if not frappe.get_all("Email Unsubscribe", filters=unsubscribe): + doc = frappe.new_doc("Email Unsubscribe") + doc.update(unsubscribe) + doc.insert(ignore_permissions=True) \ No newline at end of file From fe911aa0c59251b8ebf25cd95cf263ce5a073b08 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 7 May 2020 18:25:17 +0530 Subject: [PATCH 029/107] Update package.js --- frappe/custom/doctype/package/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index 47a7bce0df..956bbfc05c 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -20,7 +20,7 @@ frappe.ui.form.on('Package', { } }); - if(frm.doc.instances){ + if (frm.doc.instances) { frm.add_custom_button(__("Deploy"), function() { frm.call("deploy_package"); }); From 62c1b74932294e232dcc214b75b55006e214f386 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 29 Apr 2020 17:52:02 +0530 Subject: [PATCH 030/107] feat: add heatmap type dashboard chart --- frappe/core/page/dashboard/dashboard.js | 4 +- .../dashboard_chart/dashboard_chart.js | 6 +- .../dashboard_chart/dashboard_chart.json | 19 +-- .../dashboard_chart/dashboard_chart.py | 30 ++++- .../js/frappe/views/reports/report_utils.js | 2 +- .../public/js/frappe/widgets/chart_widget.js | 120 ++++++++++++------ frappe/public/less/desktop.less | 48 +++++++ 7 files changed, 175 insertions(+), 54 deletions(-) diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index 222a31a863..0d1337351e 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -6,7 +6,7 @@ frappe.provide('frappe.dashboards.chart_sources'); frappe.pages['dashboard'].on_page_load = function(wrapper) { - var page = frappe.ui.make_app_page({ + frappe.ui.make_app_page({ parent: wrapper, title: __("Dashboard"), single_column: true @@ -21,7 +21,7 @@ frappe.pages['dashboard'].on_page_load = function(wrapper) { class Dashboard { constructor(wrapper) { this.wrapper = $(wrapper); - $(`
+ $(`
`).appendTo(this.wrapper.find(".page-content").empty()); this.container = this.wrapper.find(".dashboard-graph"); diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index f8d5886b26..894dcccb27 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -88,9 +88,9 @@ frappe.ui.form.on('Dashboard Chart', { } if (frm.doc.chart_type == 'Group By') { - frm.set_df_property('type', 'options', ['Line', 'Bar', 'Percentage', 'Pie']); + frm.set_df_property('type', 'options', ['Line', 'Bar', 'Percentage', 'Pie', 'Donut']); } else { - frm.set_df_property('type', 'options', ['Line', 'Bar']); + frm.set_df_property('type', 'options', ['Line', 'Bar', 'Heatmap']); } frm.set_value('document_type', ''); @@ -358,5 +358,3 @@ frappe.ui.form.on('Dashboard Chart', { }, }); - - diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index b5201a8b1f..d6f204db00 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -28,12 +28,12 @@ "to_date", "time_interval", "timeseries", + "type", "filters_section", "filters_json", "chart_options_section", - "type", - "column_break_2", "color", + "column_break_2", "custom_options", "section_break_10", "last_synced_on" @@ -85,22 +85,22 @@ "fieldtype": "Column Break" }, { - "depends_on": "timeseries", + "depends_on": "eval: doc.timeseries && doc.type !== 'Heatmap'", "fieldname": "timespan", "fieldtype": "Select", "label": "Timespan", "options": "Last Year\nLast Quarter\nLast Month\nLast Week\nSelect Date Range" }, { - "depends_on": "timeseries", + "depends_on": "eval: doc.timeseries && doc.type !== 'Heatmap'", "fieldname": "time_interval", "fieldtype": "Select", "label": "Time Interval", "options": "Yearly\nQuarterly\nMonthly\nWeekly\nDaily" }, { - "default": "0", - "depends_on": "eval: ['Count', 'Sum', 'Average'].includes(doc.chart_type)", + "default": "1", + "depends_on": "eval: doc.chart_type !== 'Group By'", "fieldname": "timeseries", "fieldtype": "Check", "label": "Time Series" @@ -123,10 +123,11 @@ "label": "Chart Options" }, { + "default": "Line", "fieldname": "type", "fieldtype": "Select", "label": "Type", - "options": "Line\nBar\nPercentage\nPie\nDonut", + "options": "Line\nBar\nHeatmap", "reqd": 1 }, { @@ -134,7 +135,7 @@ "fieldtype": "Column Break" }, { - "depends_on": "eval:doc.chart_type !== 'Report'", + "depends_on": "eval: doc.chart_type !== 'Report' && doc.type !== 'Heatmap'", "fieldname": "color", "fieldtype": "Color", "label": "Color" @@ -231,7 +232,7 @@ } ], "links": [], - "modified": "2020-05-01 15:22:59.119341", + "modified": "2020-04-29 17:50:59.867496", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart", diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 417ef2ba82..2b095b0263 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -87,7 +87,10 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d if chart.chart_type == 'Group By': chart_config = get_group_by_chart_config(chart, filters) else: - chart_config = get_chart_config(chart, filters, timespan, timegrain, from_date, to_date) + if chart.type == 'Heatmap': + chart_config = get_heatmap_chart_config(chart, filters) + else: + chart_config = get_chart_config(chart, filters, timespan, timegrain, from_date, to_date) return chart_config @@ -174,6 +177,31 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date): return chart_config +def get_heatmap_chart_config(chart, filters): + aggregate_function = get_aggregate_function(chart.chart_type) + value_field = chart.value_based_on or '1' + doctype = chart.document_type + datefield = chart.based_on + filters.append([doctype, datefield, '>', 'subdate(curdate(), interval 1 year)', False]) + + data = dict(frappe.db.get_all( + doctype, + fields = [ + 'unix_timestamp(date({datefield}))'.format(datefield=datefield), + '{aggregate_function}({value_field})'.format(aggregate_function=aggregate_function, value_field=value_field), + ], + filters = filters, + group_by = 'date(creation)', + as_list = 1, + order_by = 'creation asc', + ignore_ifnull = True + )) + + chart_config = { + 'labels': '', + 'dataPoints': data, + } + return chart_config def get_group_by_chart_config(chart, filters): diff --git a/frappe/public/js/frappe/views/reports/report_utils.js b/frappe/public/js/frappe/views/reports/report_utils.js index a8149b9134..7b1205482f 100644 --- a/frappe/public/js/frappe/views/reports/report_utils.js +++ b/frappe/public/js/frappe/views/reports/report_utils.js @@ -20,7 +20,7 @@ frappe.report_utils = { return { data: { - labels: labels, + labels: labels.length? labels: null, datasets: datasets }, truncateLegends: 1, diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index a50acfcd9d..4f95649635 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -40,6 +40,10 @@ export default class ChartWidget extends Widget { setup_container() { this.body.empty(); + if (this.chart_doc.type == 'Heatmap') { + this.setup_heatmap_container(); + } + this.loading = $( `
${__( "Loading..." @@ -60,6 +64,12 @@ export default class ChartWidget extends Widget { this.set_chart_title(); } + setup_heatmap_container() { + this.widget.addClass('heatmap-chart'); + this.widget.removeClass('full-width').addClass('full-width'); + this.width = 'Full'; + } + set_summary() { if (!this.$summary) { this.$summary = $(`
`).hide(); @@ -104,6 +114,10 @@ export default class ChartWidget extends Widget { } render_time_series_filters() { + if (this.chart_doc.type == 'Heatmap') { + return; + } + let filters = [ { label: this.chart_settings.timespan || this.chart_doc.timespan, @@ -465,51 +479,17 @@ export default class ChartWidget extends Widget { } render() { - const chart_type_map = { - Line: "line", - Bar: "bar", - Percentage: "percentage", - Pie: "pie", - Donut: "donut" - }; - - let colors = []; - - if (this.chart_doc.y_axis.length) { - this.chart_doc.y_axis.map(field => { - colors.push(field.color); - }); - } else if (["Line", "Bar"].includes(this.chart_doc.type)) { - colors = [this.chart_doc.color || []]; - } - - if (!this.data || !this.data.labels.length || !Object.keys(this.data).length) { + if (!this.data || !this.data.labels || !Object.keys(this.data).length) { this.chart_wrapper.hide(); this.loading.hide(); - this.$summary.hide(); + this.$summary && this.$summary.hide(); this.empty.show(); } else { this.loading.hide(); this.empty.hide(); this.chart_wrapper.show(); - let chart_args = { - data: this.data, - type: chart_type_map[this.chart_doc.type], - colors: colors, - height: this.height, - axisOptions: { - xIsSeries: this.chart_doc.timeseries, - shortenYAxisNumbers: 1 - } - }; - - if (this.chart_doc.custom_options) { - let custom_options = JSON.parse(this.chart_doc.custom_options); - for (let key in custom_options) { - chart_args[key] = custom_options[key]; - } - } + const chart_args = this.get_chart_args(); if (!this.dashboard_chart) { this.dashboard_chart = new frappe.Chart( @@ -519,7 +499,73 @@ export default class ChartWidget extends Widget { } else { this.dashboard_chart.update(this.data); } + this.width == "Full" && this.summary && this.set_summary(); + this.chart_doc.type == 'Heatmap' && this.render_heatmap_legend(); + } + } + + + get_chart_args() { + let colors = this.get_chart_colors(); + + const chart_type_map = { + Line: "line", + Bar: "bar", + Percentage: "percentage", + Pie: "pie", + Donut: "donut", + Heatmap: "heatmap" + }; + + let chart_args = { + data: this.data, + type: chart_type_map[this.chart_doc.type], + colors: colors, + height: this.height, + axisOptions: { + xIsSeries: this.chart_doc.timeseries, + shortenYAxisNumbers: 1 + } + }; + + return chart_args; + } + + get_chart_colors() { + let colors = []; + if (this.chart_doc.y_axis.length) { + this.chart_doc.y_axis.map(field => { + colors.push(field.color); + }); + } else if (["Line", "Bar"].includes(this.chart_doc.type)) { + colors = [this.chart_doc.color || "light-blue"]; + } else if (this.chart_doc.type == "Heatmap") { + colors = []; + } + + return colors; + } + + render_heatmap_legend() { + if (!this.$heatmap_legend) { + this.$heatmap_legend = + $(` +
+
    +
  • +
  • +
  • +
  • +
  • +
+
+
${__("Less")}
+
${__("More")}
+
+
+ `); + this.body.append(this.$heatmap_legend); } } diff --git a/frappe/public/less/desktop.less b/frappe/public/less/desktop.less index 1e64533079..da59fd8c0a 100644 --- a/frappe/public/less/desktop.less +++ b/frappe/public/less/desktop.less @@ -293,6 +293,54 @@ } } + &.dashboard-widget-box.heatmap-chart { + min-height: 0px; + height: 180px; + + .widget-footer { + display: none; + } + + .frappe-chart .chart-legend { + display: none; + } + + .widget-body { + display: flex; + max-height: 100%; + + .chart-container { + margin-left: 65px; + margin-top: -15px; + } + + .heatmap-legend { + display: flex; + height: 100%; + margin-top: 30px; + + .legend-colors { + padding-left: 1; + padding-left: 15px; + list-style: none; + } + + li { + width: 10px; + height: 10px; + margin: 5px; + } + + .legend-label { + color: #555b51; + font-size: 11px; + margin-left: 15px; + line-height: 1.6em; + } + } + } + } + &.onboarding-widget-box { margin-bottom: 50px; margin-top: 10px; From 08eeb8350f6494deef8b7222dc5b9e7747452297 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 29 Apr 2020 18:01:58 +0530 Subject: [PATCH 031/107] fix: hide chart for smaller widths --- frappe/public/less/desktop.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frappe/public/less/desktop.less b/frappe/public/less/desktop.less index da59fd8c0a..44c5636731 100644 --- a/frappe/public/less/desktop.less +++ b/frappe/public/less/desktop.less @@ -341,6 +341,12 @@ } } + @media (max-width: 768px) { + &.dashboard-widget-box.heatmap-chart { + display: none; + } + } + &.onboarding-widget-box { margin-bottom: 50px; margin-top: 10px; From d83127021023b0ec018e43d771125c52cb662038 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 29 Apr 2020 18:04:52 +0530 Subject: [PATCH 032/107] fix: set label as empty list --- frappe/desk/doctype/dashboard_chart/dashboard_chart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 2b095b0263..21af41adad 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -198,7 +198,7 @@ def get_heatmap_chart_config(chart, filters): )) chart_config = { - 'labels': '', + 'labels': [], 'dataPoints': data, } return chart_config From 302af369ac11ba3a4e72376f6ccb8f164f57a10f Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 30 Apr 2020 18:35:49 +0530 Subject: [PATCH 033/107] fix: set timeseries according to chart type --- .../doctype/dashboard_chart/dashboard_chart.js | 18 +++++++++++------- .../dashboard_chart/dashboard_chart.json | 6 +++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index 894dcccb27..7836a66443 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -49,6 +49,7 @@ frappe.ui.form.on('Dashboard Chart', { }); frm.set_df_property("filters_section", "hidden", 1); + frm.trigger('set_time_series') frm.set_query('document_type', function() { return { filters: { @@ -71,6 +72,7 @@ frappe.ui.form.on('Dashboard Chart', { }, chart_type: function(frm) { + frm.trigger('set_time_series') if (frm.doc.chart_type == 'Report') { frm.set_query('report_name', () => { return { @@ -80,13 +82,6 @@ frappe.ui.form.on('Dashboard Chart', { } }); } else { - // set timeseries based on chart type - if (['Count', 'Average', 'Sum'].includes(frm.doc.chart_type)) { - frm.set_value('timeseries', 1); - } else { - frm.set_value('timeseries', 0); - } - if (frm.doc.chart_type == 'Group By') { frm.set_df_property('type', 'options', ['Line', 'Bar', 'Percentage', 'Pie', 'Donut']); } else { @@ -97,6 +92,15 @@ frappe.ui.form.on('Dashboard Chart', { } }, + set_time_series: function(frm) { + // set timeseries based on chart type + if (['Count', 'Average', 'Sum'].includes(frm.doc.chart_type)) { + frm.set_value('timeseries', 1); + } else { + frm.set_value('timeseries', 0); + } + }, + document_type: function(frm) { // update `based_on` options based on date / datetime fields frm.set_value('source', ''); diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index d6f204db00..f62d032767 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -99,8 +99,8 @@ "options": "Yearly\nQuarterly\nMonthly\nWeekly\nDaily" }, { - "default": "1", - "depends_on": "eval: doc.chart_type !== 'Group By'", + "default": "0", + "depends_on": "eval: !['Group By', 'Report'].includes(doc.chart_type)\n", "fieldname": "timeseries", "fieldtype": "Check", "label": "Time Series" @@ -232,7 +232,7 @@ } ], "links": [], - "modified": "2020-04-29 17:50:59.867496", + "modified": "2020-04-30 18:30:46.242316", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart", From bd3d572376b56ab6522968cca753745caf9aaca8 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 30 Apr 2020 18:51:53 +0530 Subject: [PATCH 034/107] fix: fix modified timestamp --- frappe/desk/doctype/dashboard_chart/dashboard_chart.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index f62d032767..19d358c8c5 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -232,7 +232,7 @@ } ], "links": [], - "modified": "2020-04-30 18:30:46.242316", + "modified": "2020-04-23 13:01:07.178866", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart", From 98aad96ee8647b9f5078219665caf2a72c96bac2 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 30 Apr 2020 19:20:06 +0530 Subject: [PATCH 035/107] fix: missing semicolons --- frappe/desk/doctype/dashboard_chart/dashboard_chart.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index 7836a66443..4c525e56b3 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -49,7 +49,7 @@ frappe.ui.form.on('Dashboard Chart', { }); frm.set_df_property("filters_section", "hidden", 1); - frm.trigger('set_time_series') + frm.trigger('set_time_series'); frm.set_query('document_type', function() { return { filters: { @@ -72,7 +72,7 @@ frappe.ui.form.on('Dashboard Chart', { }, chart_type: function(frm) { - frm.trigger('set_time_series') + frm.trigger('set_time_series'); if (frm.doc.chart_type == 'Report') { frm.set_query('report_name', () => { return { From b045840228ce0327c0e39a2c220a0a9a6ac99530 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 1 May 2020 19:17:03 +0530 Subject: [PATCH 036/107] fix: fix heatmap chart css for smaller widths --- .../public/js/frappe/widgets/chart_widget.js | 2 +- frappe/public/less/desktop.less | 27 +++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 4f95649635..b160afa1fa 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -548,7 +548,7 @@ export default class ChartWidget extends Widget { } render_heatmap_legend() { - if (!this.$heatmap_legend) { + if (!this.$heatmap_legend && this.widget.width() > 991) { this.$heatmap_legend = $(`
diff --git a/frappe/public/less/desktop.less b/frappe/public/less/desktop.less index 44c5636731..fbd7f988a3 100644 --- a/frappe/public/less/desktop.less +++ b/frappe/public/less/desktop.less @@ -301,6 +301,10 @@ display: none; } + .widget-control { + z-index: 1; + } + .frappe-chart .chart-legend { display: none; } @@ -308,16 +312,19 @@ .widget-body { display: flex; max-height: 100%; + margin: auto; + margin-top: -15px; .chart-container { - margin-left: 65px; - margin-top: -15px; + height: 100%; + .frappe-chart { + height: 100%; + } } .heatmap-legend { display: flex; - height: 100%; - margin-top: 30px; + margin: 45px 20px 0 20px; .legend-colors { padding-left: 1; @@ -337,13 +344,17 @@ margin-left: 15px; line-height: 1.6em; } + + @media (max-width: 991px) { + display: none; + } } } - } - @media (max-width: 768px) { - &.dashboard-widget-box.heatmap-chart { - display: none; + @media (max-width: 768px) { + .heatmap-chart { + display: none; + } } } From fe31522cf58376b6db2f99b7d8dd0bdde9b69bd9 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 5 May 2020 18:27:30 +0530 Subject: [PATCH 037/107] feat: add year field for heatmap charts --- .../dashboard_chart/dashboard_chart.js | 10 ++++++++++ .../dashboard_chart/dashboard_chart.json | 9 ++++++++- .../dashboard_chart/dashboard_chart.py | 20 ++++++++++++++----- frappe/desk/page/user_profile/user_profile.js | 19 ++---------------- .../public/js/frappe/utils/dashboard_utils.js | 16 +++++++++++++++ .../public/js/frappe/widgets/chart_widget.js | 9 ++++++++- 6 files changed, 59 insertions(+), 24 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index 4c525e56b3..90e8ce288a 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -58,6 +58,7 @@ frappe.ui.form.on('Dashboard Chart', { } }); frm.trigger('update_options'); + frm.trigger('set_heatmap_year_options'); if (frm.doc.report_name) { frm.trigger('set_chart_report_filters'); } @@ -71,6 +72,15 @@ frappe.ui.form.on('Dashboard Chart', { frm.trigger("show_filters"); }, + set_heatmap_year_options: function(frm) { + if (frm.doc.type == 'Heatmap') { + frappe.db.get_doc('System Settings').then(doc => { + const creation_date = doc.creation; + frm.set_df_property('heatmap_year', 'options', frappe.dashboard_utils.get_years_since_creation(creation_date)); + }); + } + }, + chart_type: function(frm) { frm.trigger('set_time_series'); if (frm.doc.chart_type == 'Report') { diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index 19d358c8c5..e377ef740e 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -23,6 +23,7 @@ "number_of_groups", "column_break_6", "is_public", + "heatmap_year", "timespan", "from_date", "to_date", @@ -229,10 +230,16 @@ "fieldname": "is_public", "fieldtype": "Check", "label": "Is Public" + }, + { + "depends_on": "eval: doc.type == 'Heatmap'", + "fieldname": "heatmap_year", + "fieldtype": "Select", + "label": "Year" } ], "links": [], - "modified": "2020-04-23 13:01:07.178866", + "modified": "2020-05-01 19:45:01.669384", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart", diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 21af41adad..529e60f3a2 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -8,7 +8,7 @@ from frappe import _ import datetime import json from frappe.utils.dashboard import cache_source, get_from_date_from_timespan -from frappe.utils import nowdate, add_to_date, getdate, get_last_day, formatdate, get_datetime +from frappe.utils import nowdate, add_to_date, getdate, get_last_day, formatdate, get_datetime, cint from frappe.model.naming import append_number_if_name_exists from frappe.boot import get_allowed_reports from frappe.model.document import Document @@ -182,18 +182,28 @@ def get_heatmap_chart_config(chart, filters): value_field = chart.value_based_on or '1' doctype = chart.document_type datefield = chart.based_on - filters.append([doctype, datefield, '>', 'subdate(curdate(), interval 1 year)', False]) + year = cint(chart.heatmap_year) if chart.heatmap_year else getdate(nowdate()).year + year_start_date = datetime.date(year, 1, 1).strftime('%Y-%m-%d') + next_year_start_date = datetime.date(year + 1, 1, 1).strftime('%Y-%m-%d') + + filters.append([doctype, datefield, '>', "{date}".format(date=year_start_date), False]) + filters.append([doctype, datefield, '<', "{date}".format(date=next_year_start_date), False]) + + if frappe.db.db_type == 'mariadb': + timestamp_field = 'unix_timestamp({datefield})'.format(datefield=datefield) + else: + timestamp_field = 'extract(epoch from timestamp {datefield})'.format(datefield=datefield) data = dict(frappe.db.get_all( doctype, fields = [ - 'unix_timestamp(date({datefield}))'.format(datefield=datefield), + timestamp_field, '{aggregate_function}({value_field})'.format(aggregate_function=aggregate_function, value_field=value_field), ], filters = filters, - group_by = 'date(creation)', + group_by = 'date({datefield})'.format(datefield=datefield), as_list = 1, - order_by = 'creation asc', + order_by = '{datefield} asc'.format(datefield=datefield), ignore_ifnull = True )) diff --git a/frappe/desk/page/user_profile/user_profile.js b/frappe/desk/page/user_profile/user_profile.js index ff1e906cff..c43ff27ba3 100644 --- a/frappe/desk/page/user_profile/user_profile.js +++ b/frappe/desk/page/user_profile/user_profile.js @@ -108,21 +108,6 @@ class UserProfile { }); } - get_years_since_creation() { - //Get years since user account created - this.user_creation = frappe.boot.user.creation; - let creation_year = this.get_year(this.user_creation); - let current_year = this.get_year(frappe.datetime.now_date()); - let years_list = []; - for (var year = current_year; year >= creation_year; year--) { - years_list.push(year); - } - return years_list; - } - - get_year(date_str) { - return date_str.substring(0, date_str.indexOf('-')); - } render_line_chart() { this.line_chart_filters = [['Energy Point Log', 'user', '=', this.user_id, false]]; @@ -246,8 +231,8 @@ class UserProfile { create_heatmap_chart_filters() { let filters = [ { - label: this.get_year(frappe.datetime.now_date()), - options: this.get_years_since_creation(), + label: frappe.dashboard_utils.get_year(frappe.datetime.now_date()), + options: frappe.dashboard_utils.get_years_since_creation(frappe.boot.user.creation), action: (selected_item) => { this.update_heatmap_data(frappe.datetime.obj_to_str(selected_item)); } diff --git a/frappe/public/js/frappe/utils/dashboard_utils.js b/frappe/public/js/frappe/utils/dashboard_utils.js index a1628be34a..d1621a3e15 100644 --- a/frappe/public/js/frappe/utils/dashboard_utils.js +++ b/frappe/public/js/frappe/utils/dashboard_utils.js @@ -82,5 +82,21 @@ frappe.dashboard_utils = { ).then(settings => { return settings; }); + }, + + get_years_since_creation(creation) { + //Get years since user account created + let creation_year = this.get_year(creation); + let current_year = this.get_year(frappe.datetime.now_date()); + let years_list = []; + for (var year = current_year; year >= creation_year; year--) { + years_list.push(year); + } + return years_list; + }, + + get_year(date_str) { + return date_str.substring(0, date_str.indexOf('-')); } + }; \ No newline at end of file diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index b160afa1fa..f2753be32e 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -61,6 +61,7 @@ export default class ChartWidget extends Widget { this.chart_wrapper = $(`
`); this.chart_wrapper.appendTo(this.body); + this.$heatmap_legend = null; this.set_chart_title(); } @@ -288,7 +289,7 @@ export default class ChartWidget extends Widget { }, { label: __("Reset Chart"), - action: "action-list", + action: "action-reset", handler: () => { this.reset_chart(); delete this.dashboard_chart; @@ -529,6 +530,12 @@ export default class ChartWidget extends Widget { } }; + if (this.chart_doc.type == "Heatmap") { + const heatmap_year = parseInt(this.chart_doc.heatmap_year || "2020"); + chart_args.data.start = new Date(`${heatmap_year}-01-01`); + chart_args.data.end = new Date(`${heatmap_year+1}-01-01`); + } + return chart_args; } From 31a523a720c88c8141eaae04094a4d408d611ca7 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 5 May 2020 19:21:51 +0530 Subject: [PATCH 038/107] feat: add year filter to heatmap charts --- .../dashboard_chart/dashboard_chart.py | 10 +- .../public/js/frappe/widgets/chart_widget.js | 131 ++++++++++-------- frappe/utils/dashboard.py | 1 + 3 files changed, 82 insertions(+), 60 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 529e60f3a2..e7e4aefe09 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -58,13 +58,13 @@ def has_permission(doc, ptype, user): @frappe.whitelist() @cache_source def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None, - to_date = None, timespan = None, time_interval = None, refresh = None): + to_date = None, timespan = None, time_interval = None, heatmap_year=None, refresh = None): if chart_name: chart = frappe.get_doc('Dashboard Chart', chart_name) else: chart = frappe._dict(frappe.parse_json(chart)) - + heatmap_year = heatmap_year or chart.heatmap_year timespan = timespan or chart.timespan if timespan == 'Select Date Range': @@ -88,7 +88,7 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d chart_config = get_group_by_chart_config(chart, filters) else: if chart.type == 'Heatmap': - chart_config = get_heatmap_chart_config(chart, filters) + chart_config = get_heatmap_chart_config(chart, filters, heatmap_year) else: chart_config = get_chart_config(chart, filters, timespan, timegrain, from_date, to_date) @@ -177,12 +177,12 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date): return chart_config -def get_heatmap_chart_config(chart, filters): +def get_heatmap_chart_config(chart, filters, heatmap_year): aggregate_function = get_aggregate_function(chart.chart_type) value_field = chart.value_based_on or '1' doctype = chart.document_type datefield = chart.based_on - year = cint(chart.heatmap_year) if chart.heatmap_year else getdate(nowdate()).year + year = cint(heatmap_year) if heatmap_year else getdate(nowdate()).year year_start_date = datetime.date(year, 1, 1).strftime('%Y-%m-%d') next_year_start_date = datetime.date(year + 1, 1, 1).strftime('%Y-%m-%d') diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index f2753be32e..abd99e360d 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -115,58 +115,7 @@ export default class ChartWidget extends Widget { } render_time_series_filters() { - if (this.chart_doc.type == 'Heatmap') { - return; - } - - let filters = [ - { - label: this.chart_settings.timespan || this.chart_doc.timespan, - options: [ - "Select Date Range", - "Last Year", - "Last Quarter", - "Last Month", - "Last Week" - ], - action: selected_item => { - this.selected_timespan = selected_item; - - if (this.selected_timespan === "Select Date Range") { - this.render_date_range_fields(); - } else { - this.selected_from_date = null; - this.selected_to_date = null; - if (this.date_field_wrapper) { - this.date_field_wrapper.hide(); - - // Title maybe hidden becuase of date range fields - // in half width chart - this.title_field.show(); - this.head.css('flex-direction', "row"); - } - - this.save_chart_config_for_user({ - 'timespan': this.selected_timespan, - 'from_date': null, - 'to_date': null - - }); - this.fetch_and_update_chart(); - } - } - }, - { - label: this.chart_settings.time_interval || this.chart_doc.time_interval, - options: ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily"], - action: selected_item => { - this.selected_time_interval = selected_item; - this.save_chart_config_for_user({'time_interval': this.selected_time_interval}); - this.fetch_and_update_chart(); - } - } - ]; - + let filters = this.get_time_series_filters(); frappe.dashboard_utils.render_chart_filters( filters, "chart-actions", @@ -175,12 +124,77 @@ export default class ChartWidget extends Widget { ); } + get_time_series_filters() { + let filters; + if (this.chart_doc.type == 'Heatmap') { + filters = [{ + label: this.chart_settings.heatmap_year || this.chart_doc.heatmap_year, + options: frappe.dashboard_utils.get_years_since_creation(frappe.boot.user.creation), + action: selected_item => { + this.selected_heatmap_year = selected_item; + this.save_chart_config_for_user({'heatmap_year': this.selected_heatmap_year}); + this.fetch_and_update_chart(); + } + }]; + } else { + filters = [ + { + label: this.chart_settings.timespan || this.chart_doc.timespan, + options: [ + "Select Date Range", + "Last Year", + "Last Quarter", + "Last Month", + "Last Week" + ], + action: selected_item => { + this.selected_timespan = selected_item; + + if (this.selected_timespan === "Select Date Range") { + this.render_date_range_fields(); + } else { + this.selected_from_date = null; + this.selected_to_date = null; + if (this.date_field_wrapper) { + this.date_field_wrapper.hide(); + + // Title maybe hidden becuase of date range fields + // in half width chart + this.title_field.show(); + this.head.css('flex-direction', "row"); + } + + this.save_chart_config_for_user({ + 'timespan': this.selected_timespan, + 'from_date': null, + 'to_date': null + + }); + this.fetch_and_update_chart(); + } + } + }, + { + label: this.chart_settings.time_interval || this.chart_doc.time_interval, + options: ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily"], + action: selected_item => { + this.selected_time_interval = selected_item; + this.save_chart_config_for_user({'time_interval': this.selected_time_interval}); + this.fetch_and_update_chart(); + } + } + ]; + } + return filters; + } + fetch_and_update_chart() { this.args = { timespan: this.selected_timespan || this.chart_settings.timespan, time_interval: this.selected_time_interval || this.chart_settings.time_interval, from_date: this.selected_from_date || this.chart_settings.from_date, - to_date: this.selected_to_date || this.chart_settings.to_date + to_date: this.selected_to_date || this.chart_settings.to_date, + heatmap_year: this.selected_heatmap_year || this.chart_settings.heatmap_year, }; this.fetch(this.filters, true, this.args).then(data => { @@ -406,6 +420,9 @@ export default class ChartWidget extends Widget { this.save_chart_config_for_user(null, 1); this.chart_settings = {}; this.filters = null; + this.selected_time_interval = null; + this.selected_timespan = null; + this.selected_heatmap_year = null; } save_chart_config_for_user(config, reset=0) { @@ -473,7 +490,8 @@ export default class ChartWidget extends Widget { time_interval: args && args.time_interval ? args.time_interval : null, timespan: args && args.timespan ? args.timespan : null, from_date: args && args.from_date ? args.from_date : null, - to_date: args && args.to_date ? args.to_date : null + to_date: args && args.to_date ? args.to_date : null, + heatmap_year: args && args.heatmap_year ? args.heatmap_year : null, }; } return frappe.xcall(method, args); @@ -531,7 +549,10 @@ export default class ChartWidget extends Widget { }; if (this.chart_doc.type == "Heatmap") { - const heatmap_year = parseInt(this.chart_doc.heatmap_year || "2020"); + if (!this.chart_doc.heatmap_year) { + this.chart_doc.heatmap_year = frappe.dashboard_utils.get_year(frappe.datetime.now_date()); + } + const heatmap_year = parseInt(this.selected_heatmap_year || this.chart_settings.heatmap_year || this.chart_doc.heatmap_year); chart_args.data.start = new Date(`${heatmap_year}-01-01`); chart_args.data.end = new Date(`${heatmap_year+1}-01-01`); } diff --git a/frappe/utils/dashboard.py b/frappe/utils/dashboard.py index 7cc97ed3f9..8edcf085f0 100644 --- a/frappe/utils/dashboard.py +++ b/frappe/utils/dashboard.py @@ -41,6 +41,7 @@ def generate_and_cache_results(args, function, cache_key, chart): to_date = args.to_date or None, time_interval = args.time_interval or None, timespan = args.timespan or None, + heatmap_year = args.heatmap_year or None ) except TypeError as e: if str(e) == "'NoneType' object is not iterable": From a3bedcbef478dac49fe221854f2026d6415889a2 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 5 May 2020 19:30:18 +0530 Subject: [PATCH 039/107] fix: fix media query to hide heatmap for smaller widths --- frappe/public/less/desktop.less | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/public/less/desktop.less b/frappe/public/less/desktop.less index fbd7f988a3..6d4bbca8e8 100644 --- a/frappe/public/less/desktop.less +++ b/frappe/public/less/desktop.less @@ -350,11 +350,11 @@ } } } + } - @media (max-width: 768px) { - .heatmap-chart { - display: none; - } + @media (max-width: 768px) { + &.dashboard-widget-box.heatmap-chart { + display: none; } } From f662d9011e803ece7644610a1eba1bba128f7bca Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 5 May 2020 23:03:51 +0530 Subject: [PATCH 040/107] fix: style for heatmap chart loading state --- frappe/public/less/desktop.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/public/less/desktop.less b/frappe/public/less/desktop.less index 6d4bbca8e8..eef0b29875 100644 --- a/frappe/public/less/desktop.less +++ b/frappe/public/less/desktop.less @@ -309,6 +309,10 @@ display: none; } + .chart-loading-state { + height: 160px !important; + } + .widget-body { display: flex; max-height: 100%; From 9eabcce714332aeed994cd30c3307bbd86e74b8e Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 5 May 2020 23:04:21 +0530 Subject: [PATCH 041/107] fix: undefined selected heatmap year --- frappe/public/js/frappe/widgets/chart_widget.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index abd99e360d..4be9ce1fd9 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -549,9 +549,6 @@ export default class ChartWidget extends Widget { }; if (this.chart_doc.type == "Heatmap") { - if (!this.chart_doc.heatmap_year) { - this.chart_doc.heatmap_year = frappe.dashboard_utils.get_year(frappe.datetime.now_date()); - } const heatmap_year = parseInt(this.selected_heatmap_year || this.chart_settings.heatmap_year || this.chart_doc.heatmap_year); chart_args.data.start = new Date(`${heatmap_year}-01-01`); chart_args.data.end = new Date(`${heatmap_year+1}-01-01`); @@ -616,6 +613,10 @@ export default class ChartWidget extends Widget { let saved_filters = this.chart_settings.filters || null; this.filters = saved_filters || this.filters || JSON.parse(this.chart_doc.filters_json || "[]"); + + if (this.chart_doc.type == 'Heatmap' && !this.chart_doc.heatmap_year) { + this.chart_doc.heatmap_year = frappe.dashboard_utils.get_year(frappe.datetime.now_date()); + } } get_settings() { From 7c3749c2a8c1bd4eff62d5570176d0ce9b981762 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Sat, 9 May 2020 17:20:25 +0530 Subject: [PATCH 042/107] fix: scaffolding for migrate-to command added --- frappe/commands/site.py | 74 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 52994ccec3..d5329d4cf9 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -1,6 +1,7 @@ # imports - standard imports import atexit import compileall +import getpass import hashlib import os import re @@ -9,6 +10,7 @@ import sys # imports - third party imports import click +import requests # imports - module imports import frappe @@ -256,6 +258,77 @@ def migrate(context, rebuild_website=False, skip_failing=False): print("Compiling Python Files...") compileall.compile_dir('../apps', quiet=1, rx=re.compile('.*node_modules.*')) + +@click.command('migrate-to') +@click.argument('site_url') +@pass_context +def migrate_to(context, site_url): + ''' + >>> import requests + >>> session = requests.Session() + >>> session.post("http://cloud:8002/api/method/login", {"usr":"gavin18d@gmail.com", "pwd":"bfdiljn;jnd"}) + + >>> session.headers.update({"X-Press-Team": "gavin18d@gmail.com"}) + >>> session.post("http://cloud:8002/api/method/press.api.site.options_for_new").json() + ''' + + if site_url in ("frappe.cloud", "frappecloud.com"): + site_url = "frappe.cloud" + + login_url = "https://{site_url}/api/method/login" + upload_url = "https://{site_url}/api/method/press.api.site.new_from_existing_account" + options_url = "https://{site_url}/api/method/press.api.site.options_for_new" + + else: + print(f"{site_url} is not supported yet") + sys.exit(1) + + for site in context.site: + username = input() + password = getpass.unix_getpass() + auth_credentials = {"usr": username, "pwd": password} + + # create frapp_cloud session + session = requests.Session() + login_sc = session.post(login_url, auth_credentials) + + if login_sc.ok: + print(f"Auth Successful w {site_url}") + + # get options + session.headers.update({"X-Press-Team": username}) + site_options_sc = session.post(options_url) + + if site_options_sc.ok: + site_options = site_options_sc.json() + + else: + print(f"Request failed with Status Code: {site_options_sc.status_code}") + sys.exit(1) + + # set preferences from options + + # take backup + import frappe.utils.backups + print(f"Taking backup for site {site}") + odb = frappe.utils.backups.new_backup(ignore_files=False, force=True) + + # relative paths here + odb.backup_path_db + odb.backup_path_files + odb.backup_path_private_files + + site_files = { + "files": [] + } + + # push to frappe_cloud + session.post(upload_url, files=site_files) + + else: + print(f"Request failed with Status Code: {login_sc.status_code}") + + @click.command('run-patch') @click.argument('module') @pass_context @@ -559,6 +632,7 @@ commands = [ install_app, list_apps, migrate, + migrate_to, new_site, reinstall, reload_doc, From 51289dbd008d0ada4da690669a02950f3c99b677 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Sat, 9 May 2020 17:21:29 +0530 Subject: [PATCH 043/107] fix: set totals currency for field --- frappe/public/js/frappe/views/reports/report_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 43540f449d..0c1f0f4635 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -1020,7 +1020,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { name: __('Totals Row'), content: totals[col.id], format: value => { - return frappe.format(value, col.docfield, { always_show_decimals: true }); + return frappe.format(value, col.docfield, { always_show_decimals: true }, data[0]); } } }) From 451772dfab13abfb440c199c34d67887cf1921d9 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 11 May 2020 15:47:19 +0530 Subject: [PATCH 044/107] refactor(commands): migrate-to frappecloud connector --- frappe/commands/site.py | 67 +------------ frappe/utils/remote_migrations.py | 151 ++++++++++++++++++++++++++++++ requirements.txt | 1 + 3 files changed, 155 insertions(+), 64 deletions(-) create mode 100644 frappe/utils/remote_migrations.py diff --git a/frappe/commands/site.py b/frappe/commands/site.py index d5329d4cf9..058554cff6 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -263,70 +263,9 @@ def migrate(context, rebuild_website=False, skip_failing=False): @click.argument('site_url') @pass_context def migrate_to(context, site_url): - ''' - >>> import requests - >>> session = requests.Session() - >>> session.post("http://cloud:8002/api/method/login", {"usr":"gavin18d@gmail.com", "pwd":"bfdiljn;jnd"}) - - >>> session.headers.update({"X-Press-Team": "gavin18d@gmail.com"}) - >>> session.post("http://cloud:8002/api/method/press.api.site.options_for_new").json() - ''' - - if site_url in ("frappe.cloud", "frappecloud.com"): - site_url = "frappe.cloud" - - login_url = "https://{site_url}/api/method/login" - upload_url = "https://{site_url}/api/method/press.api.site.new_from_existing_account" - options_url = "https://{site_url}/api/method/press.api.site.options_for_new" - - else: - print(f"{site_url} is not supported yet") - sys.exit(1) - - for site in context.site: - username = input() - password = getpass.unix_getpass() - auth_credentials = {"usr": username, "pwd": password} - - # create frapp_cloud session - session = requests.Session() - login_sc = session.post(login_url, auth_credentials) - - if login_sc.ok: - print(f"Auth Successful w {site_url}") - - # get options - session.headers.update({"X-Press-Team": username}) - site_options_sc = session.post(options_url) - - if site_options_sc.ok: - site_options = site_options_sc.json() - - else: - print(f"Request failed with Status Code: {site_options_sc.status_code}") - sys.exit(1) - - # set preferences from options - - # take backup - import frappe.utils.backups - print(f"Taking backup for site {site}") - odb = frappe.utils.backups.new_backup(ignore_files=False, force=True) - - # relative paths here - odb.backup_path_db - odb.backup_path_files - odb.backup_path_private_files - - site_files = { - "files": [] - } - - # push to frappe_cloud - session.post(upload_url, files=site_files) - - else: - print(f"Request failed with Status Code: {login_sc.status_code}") + from frappe.utils.remote_migrations import migrate_to + for site in context.sites: + migrate_to(site, site_url) @click.command('run-patch') diff --git a/frappe/utils/remote_migrations.py b/frappe/utils/remote_migrations.py new file mode 100644 index 0000000000..7664411474 --- /dev/null +++ b/frappe/utils/remote_migrations.py @@ -0,0 +1,151 @@ +import getpass +import sys + +import requests +from terminaltables import AsciiTable + +import frappe +import frappe.utils.backups + + +def choose(plans_list): + plans_table = [] + available_plans = [] + selected_plan = None + + print(f"{len(plans_list)} plans available") + + plans_table.append([x for x in plans_list[0].keys()]) + for plan in plans_list: + row_data = [x for x in plan.values()] + available_plans.append(row_data[0]) + plans_table.append(row_data) + + print(AsciiTable(plans_table).table) + + while True: + if selected_plan: + break + else: + input_plan = input("Send plan?: ") + if input_plan in available_plans: + return selected_plan + else: + print("Invalid selection...try again") + + +def filter_apps(app_groups): + from frappe.utils import get_installed_apps_info + + allowed_apps = [] + filtered_apps = [] + existing_apps = [] + + for group in app_groups: + for app in group.get("apps"): + app_name = group.get('scrubbed') + branch = group.get('branch') + allowed_apps.append(tuple(app_name, branch)) + + for app in get_installed_apps_info(): + app_name = app.get('app_name') + branch = app.get('branch') + existing_apps.append(tuple(app_name, branch)) + + filtered_apps = [app[0] for app in existing_apps if app in allowed_apps] + + return "Vanilla Version 12", filtered_apps + + +def migrate_to(local_site, remote_site): + if remote_site in ("frappe.cloud", "frappecloud.com"): + remote_site = "cloud:8002" + # remote_site = "frappecloud.com" + return frappecloud_migrator(local_site, remote_site) + else: + print(f"{remote_site} is not supported yet") + sys.exit(1) + + +def frappecloud_migrator(local_site, remote_site): + # test: + login_url = f"http://{remote_site}/api/method/login" + upload_url = f"http://{remote_site}/api/method/press.api.site.new" + files_url = f"http://{remote_site}/api/method/upload_file" + options_url = f"http://{remote_site}/api/method/press.api.site.options_for_new" + + # production: + # login_url = f"https://{remote_site}/api/method/login" + # upload_url = f"https://{remote_site}/api/method/press.api.site.new_from_existing_account" + # options_url = f"https://{remote_site}/api/method/press.api.site.options_for_new" + + print(f"Frappe Cloud credentials @ {remote_site}") + + username = input("Username: ") + password = getpass.unix_getpass() + auth_credentials = {"usr": username, "pwd": password} + + # create frapp_cloud session + session = requests.Session() + login_sc = session.post(login_url, auth_credentials) + + if login_sc.ok: + print(f"Authorization Successful!") + + # get options + session.headers.update({"X-Press-Team": username}) + site_options_sc = session.post(options_url) + + if site_options_sc.ok: + site_options = site_options_sc.json()["message"] + app_groups = site_options_sc.json()["groups"] + + else: + print(f"Request failed with Status Code: {site_options_sc.status_code}") + sys.exit(1) + + # set preferences from options + subdomain = input("Enter subdomain: ") + plan = choose(site_options['plans']) + + frappe.init(site=local_site) + frappe.connect() + + # apps currently on site....vanilla. + selected_group, filtered_apps = filter_apps(app_groups) + + # take backup + print(f"Taking backup for site {local_site}") + odb = frappe.utils.backups.new_backup(ignore_files=False, force=True) + files_session = {} + + # upload files + for file_type, file_path in [("database", odb.backup_path_db), ("public", odb.backup_path_files), ("private", odb.backup_path_private_files)]: + file_upload_response = session.post(files_url, data={}, files={ + "file": open(file_path, "rb"), + "is_private": 1, + "folder": "Home", + "method": "press.api.site.upload_backup", + "type": file_type + }) + if file_upload_response.ok: + files_session[file_type] = file_upload_response.json()["message"] + else: + print(f"Upload failed for: {file_path}") + + files_uploaded = { k, v["file_url"] for k, v in files_session.items() } + + # push to frappe_cloud + session.post(upload_url, data={ + "site": { + "apps": filtered_apps, + "files": files_uploaded, + "group": selected_group, + "name": subdomain, + "plan": plan + } + }) + frappe.destroy() + + else: + print(f"Request failed with Status Code: {login_sc.status_code}") diff --git a/requirements.txt b/requirements.txt index 6c22d66139..c4cb79892a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,6 +60,7 @@ semantic-version==2.8.4 six==1.14.0 sqlparse==0.2.4 stripe==2.40.0 +terminaltables unittest-xml-reporting==2.5.2 urllib3==1.25.8 watchdog==0.8.0 From ed74f52e206f7108938ef23fecb198c30a99a683 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 11 May 2020 20:12:05 +0530 Subject: [PATCH 045/107] feat: single doctype to store installed apps data updated on migrate versions enabled --- .../installed_application_details/__init__.py | 0 .../installed_application_details.json | 49 +++++++++++++++++++ .../installed_application_details.py | 10 ++++ .../installed_applications/__init__.py | 0 .../installed_applications.js | 8 +++ .../installed_applications.json | 42 ++++++++++++++++ .../installed_applications.py | 17 +++++++ .../test_installed_applications.py | 10 ++++ frappe/migrate.py | 3 ++ .../website_theme/standard/standard.json | 2 +- 10 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 frappe/core/doctype/installed_application_details/__init__.py create mode 100644 frappe/core/doctype/installed_application_details/installed_application_details.json create mode 100644 frappe/core/doctype/installed_application_details/installed_application_details.py create mode 100644 frappe/core/doctype/installed_applications/__init__.py create mode 100644 frappe/core/doctype/installed_applications/installed_applications.js create mode 100644 frappe/core/doctype/installed_applications/installed_applications.json create mode 100644 frappe/core/doctype/installed_applications/installed_applications.py create mode 100644 frappe/core/doctype/installed_applications/test_installed_applications.py diff --git a/frappe/core/doctype/installed_application_details/__init__.py b/frappe/core/doctype/installed_application_details/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/installed_application_details/installed_application_details.json b/frappe/core/doctype/installed_application_details/installed_application_details.json new file mode 100644 index 0000000000..ec453c320f --- /dev/null +++ b/frappe/core/doctype/installed_application_details/installed_application_details.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2020-05-11 17:44:54.674657", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "application_name", + "application_version", + "git_branch" + ], + "fields": [ + { + "fieldname": "application_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Application Name", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "application_version", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Application Version", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "git_branch", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Git Branch", + "read_only": 1, + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-05-11 19:54:07.141641", + "modified_by": "Administrator", + "module": "Core", + "name": "Installed Application Details", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/core/doctype/installed_application_details/installed_application_details.py b/frappe/core/doctype/installed_application_details/installed_application_details.py new file mode 100644 index 0000000000..3dd2b67228 --- /dev/null +++ b/frappe/core/doctype/installed_application_details/installed_application_details.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class InstalledApplicationDetails(Document): + pass diff --git a/frappe/core/doctype/installed_applications/__init__.py b/frappe/core/doctype/installed_applications/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/installed_applications/installed_applications.js b/frappe/core/doctype/installed_applications/installed_applications.js new file mode 100644 index 0000000000..9a1fd5ac18 --- /dev/null +++ b/frappe/core/doctype/installed_applications/installed_applications.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Installed Applications', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/core/doctype/installed_applications/installed_applications.json b/frappe/core/doctype/installed_applications/installed_applications.json new file mode 100644 index 0000000000..8f0e658d92 --- /dev/null +++ b/frappe/core/doctype/installed_applications/installed_applications.json @@ -0,0 +1,42 @@ +{ + "actions": [], + "creation": "2020-05-11 17:45:41.587750", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "installed_applications" + ], + "fields": [ + { + "fieldname": "installed_applications", + "fieldtype": "Table", + "label": "Installed Applications", + "options": "Installed Application Details", + "read_only": 1 + } + ], + "issingle": 1, + "links": [], + "modified": "2020-05-11 19:52:47.775562", + "modified_by": "Administrator", + "module": "Core", + "name": "Installed Applications", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/core/doctype/installed_applications/installed_applications.py b/frappe/core/doctype/installed_applications/installed_applications.py new file mode 100644 index 0000000000..0b9603a005 --- /dev/null +++ b/frappe/core/doctype/installed_applications/installed_applications.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class InstalledApplications(Document): + def before_save(self): + self.delete_key("installed_applications") + for app in frappe.utils.get_installed_apps_info(): + self.append("installed_applications", { + "application_name": app.get("app_name"), + "application_version": app.get("version"), + "git_branch": app.get("branch") + }) \ No newline at end of file diff --git a/frappe/core/doctype/installed_applications/test_installed_applications.py b/frappe/core/doctype/installed_applications/test_installed_applications.py new file mode 100644 index 0000000000..ab9b849fa1 --- /dev/null +++ b/frappe/core/doctype/installed_applications/test_installed_applications.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestInstalledApplications(unittest.TestCase): + pass diff --git a/frappe/migrate.py b/frappe/migrate.py index 094abbe099..0b4680ced3 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -67,6 +67,9 @@ def migrate(verbose=True, rebuild_website=False, skip_failing=False): # add static pages to global search global_search.update_global_search_for_all_web_pages() + # updating installed applications data + frappe.get_single('Installed Applications').save() + #run after_migrate hooks for app in frappe.get_installed_apps(): for fn in frappe.get_hooks('after_migrate', app_name=app): diff --git a/frappe/website/website_theme/standard/standard.json b/frappe/website/website_theme/standard/standard.json index 1729e4616a..e56d1934c6 100644 --- a/frappe/website/website_theme/standard/standard.json +++ b/frappe/website/website_theme/standard/standard.json @@ -19,4 +19,4 @@ "theme": "Standard", "theme_scss": "$enable-shadows: false;\n$enable-gradients: false;\n$enable-rounded: true;\n\n@import \"frappe/public/scss/website\";\n\n", "theme_url": "/assets/css/standard_style.css" -} \ No newline at end of file +} From b48d2921bbef1e1e2f8003853c008b4f43316bbf Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 12 May 2020 10:18:48 +0530 Subject: [PATCH 046/107] chore(installed-applications): renamed doctype and docfields --- .../__init__.py | 0 .../installed_application.json} | 40 +++++++++---------- .../installed_application.py} | 2 +- .../installed_applications.json | 4 +- .../installed_applications.py | 9 +++-- 5 files changed, 28 insertions(+), 27 deletions(-) rename frappe/core/doctype/{installed_application_details => installed_application}/__init__.py (100%) rename frappe/core/doctype/{installed_application_details/installed_application_details.json => installed_application/installed_application.json} (78%) rename frappe/core/doctype/{installed_application_details/installed_application_details.py => installed_application/installed_application.py} (84%) diff --git a/frappe/core/doctype/installed_application_details/__init__.py b/frappe/core/doctype/installed_application/__init__.py similarity index 100% rename from frappe/core/doctype/installed_application_details/__init__.py rename to frappe/core/doctype/installed_application/__init__.py diff --git a/frappe/core/doctype/installed_application_details/installed_application_details.json b/frappe/core/doctype/installed_application/installed_application.json similarity index 78% rename from frappe/core/doctype/installed_application_details/installed_application_details.json rename to frappe/core/doctype/installed_application/installed_application.json index ec453c320f..1f32c557ce 100644 --- a/frappe/core/doctype/installed_application_details/installed_application_details.json +++ b/frappe/core/doctype/installed_application/installed_application.json @@ -4,27 +4,11 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ - "application_name", - "application_version", + "app_name", + "app_version", "git_branch" ], "fields": [ - { - "fieldname": "application_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Application Name", - "read_only": 1, - "reqd": 1 - }, - { - "fieldname": "application_version", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Application Version", - "read_only": 1, - "reqd": 1 - }, { "fieldname": "git_branch", "fieldtype": "Data", @@ -32,14 +16,30 @@ "label": "Git Branch", "read_only": 1, "reqd": 1 + }, + { + "fieldname": "app_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Application Name", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "app_version", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Application Version", + "read_only": 1, + "reqd": 1 } ], "istable": 1, "links": [], - "modified": "2020-05-11 19:54:07.141641", + "modified": "2020-05-12 10:09:49.148087", "modified_by": "Administrator", "module": "Core", - "name": "Installed Application Details", + "name": "Installed Application", "owner": "Administrator", "permissions": [], "quick_entry": 1, diff --git a/frappe/core/doctype/installed_application_details/installed_application_details.py b/frappe/core/doctype/installed_application/installed_application.py similarity index 84% rename from frappe/core/doctype/installed_application_details/installed_application_details.py rename to frappe/core/doctype/installed_application/installed_application.py index 3dd2b67228..6bb12afc49 100644 --- a/frappe/core/doctype/installed_application_details/installed_application_details.py +++ b/frappe/core/doctype/installed_application/installed_application.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class InstalledApplicationDetails(Document): +class InstalledApplication(Document): pass diff --git a/frappe/core/doctype/installed_applications/installed_applications.json b/frappe/core/doctype/installed_applications/installed_applications.json index 8f0e658d92..f2345e66b2 100644 --- a/frappe/core/doctype/installed_applications/installed_applications.json +++ b/frappe/core/doctype/installed_applications/installed_applications.json @@ -12,13 +12,13 @@ "fieldname": "installed_applications", "fieldtype": "Table", "label": "Installed Applications", - "options": "Installed Application Details", + "options": "Installed Application", "read_only": 1 } ], "issingle": 1, "links": [], - "modified": "2020-05-11 19:52:47.775562", + "modified": "2020-05-12 10:09:14.310622", "modified_by": "Administrator", "module": "Core", "name": "Installed Applications", diff --git a/frappe/core/doctype/installed_applications/installed_applications.py b/frappe/core/doctype/installed_applications/installed_applications.py index 0b9603a005..aa0401f368 100644 --- a/frappe/core/doctype/installed_applications/installed_applications.py +++ b/frappe/core/doctype/installed_applications/installed_applications.py @@ -7,11 +7,12 @@ import frappe from frappe.model.document import Document class InstalledApplications(Document): - def before_save(self): + def update_versions(self): self.delete_key("installed_applications") for app in frappe.utils.get_installed_apps_info(): self.append("installed_applications", { - "application_name": app.get("app_name"), - "application_version": app.get("version"), + "app_name": app.get("app_name"), + "app_version": app.get("version"), "git_branch": app.get("branch") - }) \ No newline at end of file + }) + self.save() \ No newline at end of file From 2473b1ccd7b7472e70fca383a7a2fce4ad6b5a6a Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 12 May 2020 10:20:10 +0530 Subject: [PATCH 047/107] fix: explicitly called method instead of updating implicitly --- frappe/migrate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/migrate.py b/frappe/migrate.py index 0b4680ced3..3325bc8a2d 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -68,7 +68,7 @@ def migrate(verbose=True, rebuild_website=False, skip_failing=False): global_search.update_global_search_for_all_web_pages() # updating installed applications data - frappe.get_single('Installed Applications').save() + frappe.get_single('Installed Applications').update_versions() #run after_migrate hooks for app in frappe.get_installed_apps(): From 91e3b2406ada6a46eb4f09cf8918ab9364784b09 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 12 May 2020 12:01:12 +0530 Subject: [PATCH 048/107] fix: allow validations only on Data fields --- frappe/public/js/frappe/form/controls/data.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index c943ec89bb..25f3ef76af 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -87,6 +87,9 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ return val==null ? "" : val; }, validate: function(v) { + if (this.df.fieldtype !== "Data") { + return v; + } if (!v) { return ''; } From 280c4fb0a8c7c4ed425b3050e857c92aff75a777 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 12 May 2020 12:01:40 +0530 Subject: [PATCH 049/107] fix: improve buggy regex for email rfc822 compliant email regex added from https://emailregex.com/ https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript/46181#46181 will give you enough content to go over before deciding how your email validation should work :') eg for bug: string like '"purchase=fmtrd.com@mail.erpnext.com" ' would take sometimes over 10s or even freeze the tab for 10m at the least via validate_email --- frappe/public/js/frappe/utils/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 7eff0b8e24..7d2c20c693 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -250,7 +250,8 @@ Object.assign(frappe.utils, { regExp = /^\w+$/; break; case "email": - regExp = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i; + // from https://emailregex.com/ + regExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; break; case "url": regExp = /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i; From e559e30d0235e6f42379317fdc2cf125b60ab080 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 13 May 2020 19:42:54 +0530 Subject: [PATCH 050/107] refactor(migrate-to): code restructure and bug fixes --- frappe/utils/remote_migrations.py | 351 ++++++++++++++++++++---------- 1 file changed, 242 insertions(+), 109 deletions(-) diff --git a/frappe/utils/remote_migrations.py b/frappe/utils/remote_migrations.py index 7664411474..abe2b921e8 100644 --- a/frappe/utils/remote_migrations.py +++ b/frappe/utils/remote_migrations.py @@ -1,17 +1,45 @@ +# imports - standard imports +import functools import getpass +import json +import re import sys +# imports - third party imports +import click import requests from terminaltables import AsciiTable +# imports - module imports import frappe import frappe.utils.backups +from frappe.utils import get_installed_apps_info -def choose(plans_list): +def render_table(data): + print(AsciiTable(data).table) + +def padme(me): + def empty_line(*args, **kwargs): + result = me(*args, **kwargs) + print() + return result + return empty_line + +@functools.lru_cache(maxsize=1024) +def get_first_party_apps(): + apps = [] + for org in ["frappe", "erpnext"]: + req = requests.get(f"https://api.github.com/users/{org}/repos", {"type": "sources", "per_page": 200}) + if req.ok: + apps.extend([x["name"] for x in req.json()]) + return apps + + +@padme +def choose_plan(plans_list): plans_table = [] available_plans = [] - selected_plan = None print(f"{len(plans_list)} plans available") @@ -21,40 +49,229 @@ def choose(plans_list): available_plans.append(row_data[0]) plans_table.append(row_data) - print(AsciiTable(plans_table).table) + render_table(plans_table) while True: - if selected_plan: - break + input_plan = input("Send plan?: ").strip() + if input_plan in available_plans: + print(f"{input_plan} Plan selected ✅") + return input_plan else: - input_plan = input("Send plan?: ") - if input_plan in available_plans: - return selected_plan - else: - print("Invalid selection...try again") + print("Invalid selection...try again ❌") -def filter_apps(app_groups): - from frappe.utils import get_installed_apps_info +def check_app_compat(available_group): + frappe_upgrade_msg = "" + trimmed_available_group = set([(app['scrubbed'], app['branch']) for app in available_group['apps']]) + existing_group = set([(app['app_name'], app['branch']) for app in get_installed_apps_info()]) - allowed_apps = [] + print("Checking availability of existing app group") + incompatible_apps = [] filtered_apps = [] - existing_apps = [] + branch_msgs = [] - for group in app_groups: - for app in group.get("apps"): - app_name = group.get('scrubbed') - branch = group.get('branch') - allowed_apps.append(tuple(app_name, branch)) + for (app, branch) in existing_group: + if (app, branch) not in trimmed_available_group: + app_title = [group["name"] for group in available_group["apps"] if group["scrubbed"] == app] + if app_title: + app_title = app_title[0] + is_compat = False + if app not in get_first_party_apps(): + incompatible_apps.append(app) + print(f"❌ App {app}:{branch}") + else: + available_branch = [a['branch'] for a in available_group['apps'] if a['scrubbed'] == app] + if not available_branch: + print(f"App {app} doesn't exist in selected group") + continue + else: + available_branch = available_branch[0] + print(f"⚠️ {app}:{branch} => {available_branch}") + branch_msgs.append([app.title(), branch, available_branch]) + filtered_apps.append(app_title) + else: + filtered_apps.append(app_title) - for app in get_installed_apps_info(): - app_name = app.get('app_name') - branch = app.get('branch') - existing_apps.append(tuple(app_name, branch)) + start_msg = "\nSelecting this group will " + incompatible_apps = f"drop {len(incompatible_apps)} apps: " + ", ".join(incompatible_apps) + " and " if incompatible_apps else "" + branch_change = "upgrade:\n" + "\n".join(["{}: {} => {}".format(*x) for x in branch_msgs]) if branch_msgs else "" + changes = (incompatible_apps + branch_change) or "be perfect for you :)" + warning_message = start_msg + changes - filtered_apps = [app[0] for app in existing_apps if app in allowed_apps] + print(warning_message) - return "Vanilla Version 12", filtered_apps + return is_compat, filtered_apps + + +def generate_app_group_table(app_groups): + app_groups_table = [["#", "App Group", "Apps"]] + + for _, app_group in enumerate(app_groups): + row = [_ + 1, app_group["name"], ", ".join([f"{app['scrubbed']}:{app['branch']}" for app in app_group['apps']])] + app_groups_table.append(row) + + render_table(app_groups_table) + + +@padme +def filter_apps(app_groups): + # try for default group first...then let em select which group + default_group = [g for g in app_groups if g['default']][0] + is_compat, filtered_apps = check_app_compat(default_group) + + if not is_compat and not click.confirm("Continue anyway?"): + generate_app_group_table(app_groups) + + while True: + try: + app_group_index = int(input("Select App Group #: ").strip()) - 1 + selected_group = app_groups[app_group_index] + is_compat, filtered_apps = check_app_compat(selected_group) + except: + print("Invalid Selection") + sys.exit(1) + + if is_compat or click.confirm("Continue anyway?"): + break + + return default_group['name'], filtered_apps + +@padme +def create_session(): + # take user input from STDIN + username = input("Username: ").strip() + password = getpass.unix_getpass() + + auth_credentials = {"usr": username, "pwd": password} + + session = requests.Session() + login_sc = session.post(login_url, auth_credentials) + + if login_sc.ok: + print(f"Authorization Successful! ✅") + session.headers.update({"X-Press-Team": username}) + return session + else: + print(f"Authorization Failed with Error Code {login_sc.status_code}") + + +def get_new_site_options(): + site_options_sc = session.post(options_url) + + if site_options_sc.ok: + site_options = site_options_sc.json()["message"] + return site_options + else: + print(f"Couldn't retrive New site information: {site_options_sc.status_code}") + + +def is_valid_subdomain(subdomain): + matched = re.match("^[a-z0-9][a-z0-9-]*[a-z0-9]$", subdomain) + if matched: + return True + print('Subdomain contains invalid characters. Use lowercase characters, numbers and hyphens') + + +def is_subdomain_available(subdomain): + res = session.post(site_exists_url, {"subdomain": subdomain}) + if res.ok: + available = not res.json()['message'] + if not available: + print('Subdomain already exists! Try another one') + + return available + + +@padme +def get_subdomain(domain): + while True: + subdomain = input("Enter subdomain: ").strip() + if is_valid_subdomain(subdomain): + if is_subdomain_available(subdomain): + print(f"Site Domain: {subdomain}.{domain}") + return subdomain + + +@padme +def upload_backup(local_site): + # take backup + print(f"Taking backup for site {local_site}") + odb = frappe.utils.backups.new_backup(ignore_files=False, force=True) + files_session = {} + + # upload files + for file_type, file_path in [ + ("database", odb.backup_path_db), + ("public", odb.backup_path_files), + ("private", odb.backup_path_private_files) + ]: + file_upload_response = session.post(files_url, data={}, files={ + "file": open(file_path, "rb"), + "is_private": 1, + "folder": "Home", + "method": "press.api.site.upload_backup", + "type": file_type + }) + if file_upload_response.ok: + files_session[file_type] = file_upload_response.json()["message"] + else: + print(f"Upload failed for: {file_path}") + + files_uploaded = { k: v["file_url"] for k, v in files_session.items() } + + return files_uploaded + + +def frappecloud_migrator(local_site, remote_site): + # test (change to https !!!): + global login_url, upload_url, files_url, options_url, site_exists_url, session + + login_url = f"http://{remote_site}/api/method/login" + upload_url = f"http://{remote_site}/api/method/press.api.site.new" + files_url = f"http://{remote_site}/api/method/upload_file" + options_url = f"http://{remote_site}/api/method/press.api.site.options_for_new" + site_exists_url = f"http://{remote_site}/api/method/press.api.site.exists" + + print(f"Frappe Cloud credentials @ {remote_site}") + + # get credentials + auth user + start session + session = create_session() + + if session: + # connect to site db + frappe.init(site=local_site) + frappe.connect() + + # get new site options + site_options = get_new_site_options() + + # set preferences from site options + subdomain = get_subdomain(site_options['domain']) + plan = choose_plan(site_options['plans']) + + app_groups = site_options["groups"] + selected_group, filtered_apps = filter_apps(app_groups) + files_uploaded = upload_backup(local_site) + + # push to frappe_cloud + payload = json.dumps({ + "site": { + "apps": filtered_apps, + "files": files_uploaded, + "group": selected_group, + "name": subdomain, + "plan": plan + } + }) + + session.headers.update({"Content-Type": "application/json; charset=utf-8"}) + site_creation_request = session.post(upload_url, payload) + frappe.destroy() + + if site_creation_request.ok: + print(f"Site creation started at {site_creation_request.json()['message']}") + else: + print(f"Request failed with error code {site_creation_request.status_code}") def migrate_to(local_site, remote_site): @@ -65,87 +282,3 @@ def migrate_to(local_site, remote_site): else: print(f"{remote_site} is not supported yet") sys.exit(1) - - -def frappecloud_migrator(local_site, remote_site): - # test: - login_url = f"http://{remote_site}/api/method/login" - upload_url = f"http://{remote_site}/api/method/press.api.site.new" - files_url = f"http://{remote_site}/api/method/upload_file" - options_url = f"http://{remote_site}/api/method/press.api.site.options_for_new" - - # production: - # login_url = f"https://{remote_site}/api/method/login" - # upload_url = f"https://{remote_site}/api/method/press.api.site.new_from_existing_account" - # options_url = f"https://{remote_site}/api/method/press.api.site.options_for_new" - - print(f"Frappe Cloud credentials @ {remote_site}") - - username = input("Username: ") - password = getpass.unix_getpass() - auth_credentials = {"usr": username, "pwd": password} - - # create frapp_cloud session - session = requests.Session() - login_sc = session.post(login_url, auth_credentials) - - if login_sc.ok: - print(f"Authorization Successful!") - - # get options - session.headers.update({"X-Press-Team": username}) - site_options_sc = session.post(options_url) - - if site_options_sc.ok: - site_options = site_options_sc.json()["message"] - app_groups = site_options_sc.json()["groups"] - - else: - print(f"Request failed with Status Code: {site_options_sc.status_code}") - sys.exit(1) - - # set preferences from options - subdomain = input("Enter subdomain: ") - plan = choose(site_options['plans']) - - frappe.init(site=local_site) - frappe.connect() - - # apps currently on site....vanilla. - selected_group, filtered_apps = filter_apps(app_groups) - - # take backup - print(f"Taking backup for site {local_site}") - odb = frappe.utils.backups.new_backup(ignore_files=False, force=True) - files_session = {} - - # upload files - for file_type, file_path in [("database", odb.backup_path_db), ("public", odb.backup_path_files), ("private", odb.backup_path_private_files)]: - file_upload_response = session.post(files_url, data={}, files={ - "file": open(file_path, "rb"), - "is_private": 1, - "folder": "Home", - "method": "press.api.site.upload_backup", - "type": file_type - }) - if file_upload_response.ok: - files_session[file_type] = file_upload_response.json()["message"] - else: - print(f"Upload failed for: {file_path}") - - files_uploaded = { k, v["file_url"] for k, v in files_session.items() } - - # push to frappe_cloud - session.post(upload_url, data={ - "site": { - "apps": filtered_apps, - "files": files_uploaded, - "group": selected_group, - "name": subdomain, - "plan": plan - } - }) - frappe.destroy() - - else: - print(f"Request failed with Status Code: {login_sc.status_code}") From 55fe81aaac2d436071d1dd0d0b2379841513e01d Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 13 May 2020 20:56:51 +0530 Subject: [PATCH 051/107] perf: Add HTTP Link header for preload hints Parse rendered templates and find stylesheet and script files. Add these files to the HTTP Link header. In the case of http1.1 browser can fetch these resources before parsing the DOM. In the case of http2 NGINX can use HTTP2 Server Push (http2_push_preload on). https://www.w3.org/TR/preload/ --- frappe/website/render.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/frappe/website/render.py b/frappe/website/render.py index 601a4e7eb2..f2078ebfc1 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -7,8 +7,10 @@ from frappe import _ import frappe.sessions from frappe.utils import cstr import os, mimetypes, json +import re import six +from bs4 import BeautifulSoup from six import iteritems from werkzeug.wrappers import Response from werkzeug.routing import Map, Rule, NotFound @@ -128,12 +130,35 @@ def build_response(path, data, http_status_code, headers=None): response.headers["X-Page-Name"] = path.encode("ascii", errors="xmlcharrefreplace") response.headers["X-From-Cache"] = frappe.local.response.from_cache or False + add_preload_headers(response) if headers: for key, val in iteritems(headers): response.headers[key] = val.encode("ascii", errors="xmlcharrefreplace") return response + +def add_preload_headers(response): + try: + preload = [] + soup = BeautifulSoup(response.data, "lxml") + for elem in soup.find_all('script', src=re.compile(".*")): + preload.append(("script", elem.get("src"))) + + for elem in soup.find_all('link', rel="stylesheet"): + preload.append(("style", elem.get("href"))) + + links = [] + for type, link in preload: + links.append("; rel=preload; as={}".format(link.lstrip("/"), type)) + + if links: + response.headers["Link"] = ",".join(links) + except Exception: + import traceback + traceback.print_exc() + + def render_page_by_language(path): translated_languages = frappe.get_hooks("translated_languages_for_website") user_lang = guess_language(translated_languages) From d3f8a5bbb9fe49c22e363c76dac155fe30ba4f5a Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 14 May 2020 10:22:51 +0530 Subject: [PATCH 052/107] style: remove unsused imports, spacing for consistency --- frappe/commands/site.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 058554cff6..10bf700340 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -1,7 +1,6 @@ # imports - standard imports import atexit import compileall -import getpass import hashlib import os import re @@ -10,7 +9,6 @@ import sys # imports - third party imports import click -import requests # imports - module imports import frappe @@ -258,7 +256,6 @@ def migrate(context, rebuild_website=False, skip_failing=False): print("Compiling Python Files...") compileall.compile_dir('../apps', quiet=1, rx=re.compile('.*node_modules.*')) - @click.command('migrate-to') @click.argument('site_url') @pass_context @@ -267,7 +264,6 @@ def migrate_to(context, site_url): for site in context.sites: migrate_to(site, site_url) - @click.command('run-patch') @click.argument('module') @pass_context @@ -334,18 +330,19 @@ def use(site, sites_path='.'): @click.command('backup') @click.option('--with-files', default=False, is_flag=True, help="Take backup with files") +@click.option('--verbose', default=False, is_flag=True) @pass_context def backup(context, with_files=False, backup_path_db=None, backup_path_files=None, - backup_path_private_files=None, quiet=False): + backup_path_private_files=None, quiet=False, verbose=False): "Backup" from frappe.utils.backups import scheduled_backup - verbose = context.verbose + 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) + 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)) @@ -354,10 +351,12 @@ def backup(context, with_files=False, backup_path_db=None, backup_path_files=Non if verbose: from frappe.utils import now - print("database backup taken -", odb.backup_path_db, "- on", 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("files backup taken -", odb.backup_path_files, "- on", now()) - print("private files backup taken -", odb.backup_path_private_files, "- on", now()) + print("Public files: ", odb.backup_path_files) + print("Private files: ", odb.backup_path_private_files) frappe.destroy() sys.exit(exit_code) From 2924b98097cfaac3b84b35b43088f73e93e2878d Mon Sep 17 00:00:00 2001 From: Mathieu Brunot Date: Thu, 14 May 2020 08:51:39 +0200 Subject: [PATCH 053/107] fix: Exit chat from website (#10201) --- frappe/public/js/frappe/chat.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/chat.js b/frappe/public/js/frappe/chat.js index f54b9e5cbe..6b723d508c 100644 --- a/frappe/public/js/frappe/chat.js +++ b/frappe/public/js/frappe/chat.js @@ -2259,14 +2259,19 @@ class extends Component { ) : null, h("div","", h("div", { class: "panel-title" }, - h("div", { class: "cursor-pointer", onclick: () => { frappe.set_route(item.route) }}, + h("div", { class: "cursor-pointer", onclick: () => { + frappe.session.user !== "Guest" ? + frappe.set_route(item.route) : null; + }}, h(frappe.Chat.Widget.MediaProfile, { ...item }) ) ) ), - h("div", { class: popper ? "col-xs-1" : "col-xs-3" }, + h("div", { class: popper ? "col-xs-2" : "col-xs-3" }, h("div", { class: "text-right" }, - + frappe._.is_mobile() && h(frappe.components.Button, { class: "frappe-chat-close", onclick: props.toggle }, + h(frappe.components.Octicon, { type: "x" }) + ) ) ) ) From 637409095c8ff718abaf2c0cf72872361418addb Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 14 May 2020 12:24:00 +0530 Subject: [PATCH 054/107] fix: simplyify and segregate migrator utils --- frappe/utils/commands.py | 25 ++++ frappe/utils/remote_migrations.py | 238 ++++++++++++++---------------- 2 files changed, 136 insertions(+), 127 deletions(-) create mode 100644 frappe/utils/commands.py diff --git a/frappe/utils/commands.py b/frappe/utils/commands.py new file mode 100644 index 0000000000..3c16193173 --- /dev/null +++ b/frappe/utils/commands.py @@ -0,0 +1,25 @@ +import functools +import requests +from terminaltables import AsciiTable + + +@functools.lru_cache(maxsize=1024) +def get_first_party_apps(): + apps = [] + for org in ["frappe", "erpnext"]: + req = requests.get(f"https://api.github.com/users/{org}/repos", {"type": "sources", "per_page": 200}) + if req.ok: + apps.extend([x["name"] for x in req.json()]) + return apps + + +def render_table(data): + print(AsciiTable(data).table) + + +def padme(me): + def empty_line(*args, **kwargs): + result = me(*args, **kwargs) + print() + return result + return empty_line diff --git a/frappe/utils/remote_migrations.py b/frappe/utils/remote_migrations.py index abe2b921e8..c89134f27c 100644 --- a/frappe/utils/remote_migrations.py +++ b/frappe/utils/remote_migrations.py @@ -7,107 +7,122 @@ import sys # imports - third party imports import click +from html2text import html2text import requests -from terminaltables import AsciiTable # imports - module imports import frappe import frappe.utils.backups from frappe.utils import get_installed_apps_info +from frappe.utils.commands import get_first_party_apps, render_table, padme -def render_table(data): - print(AsciiTable(data).table) -def padme(me): - def empty_line(*args, **kwargs): - result = me(*args, **kwargs) - print() - return result - return empty_line +def get_new_site_options(): + site_options_sc = session.post(options_url) -@functools.lru_cache(maxsize=1024) -def get_first_party_apps(): - apps = [] - for org in ["frappe", "erpnext"]: - req = requests.get(f"https://api.github.com/users/{org}/repos", {"type": "sources", "per_page": 200}) - if req.ok: - apps.extend([x["name"] for x in req.json()]) - return apps + if site_options_sc.ok: + site_options = site_options_sc.json()["message"] + return site_options + else: + print("Couldn't retrive New site information: {}".format(site_options_sc.status_code)) + + +def is_valid_subdomain(subdomain): + if len(subdomain) < 6: + print("Subdomain too short. Use 5 or more characters") + return False + matched = re.match("^[a-z0-9][a-z0-9-]*[a-z0-9]$", subdomain) + if matched: + return True + print("Subdomain contains invalid characters. Use lowercase characters, numbers and hyphens") + + +def is_subdomain_available(subdomain): + res = session.post(site_exists_url, {"subdomain": subdomain}) + if res.ok: + available = not res.json()["message"] + if not available: + print("Subdomain already exists! Try another one") + + return available + + +def render_plan_table(plans_list): + plans_table = [] + + # title row + visible_headers = ["name", "concurrent_users", "cpu_time_per_day"] + plans_table.append(visible_headers) + + # all rows + for plan in plans_list: + plans_table.append([plan[header] for header in visible_headers]) + + render_table(plans_table) @padme def choose_plan(plans_list): - plans_table = [] - available_plans = [] - - print(f"{len(plans_list)} plans available") - - plans_table.append([x for x in plans_list[0].keys()]) - for plan in plans_list: - row_data = [x for x in plan.values()] - available_plans.append(row_data[0]) - plans_table.append(row_data) - - render_table(plans_table) + print("{} plans available".format(len(plans_list))) + available_plans = [plan["name"] for plan in plans_list] + render_plan_table(plans_list) while True: input_plan = input("Send plan?: ").strip() if input_plan in available_plans: - print(f"{input_plan} Plan selected ✅") + print("{} Plan selected ✅".format(input_plan)) return input_plan else: print("Invalid selection...try again ❌") +@padme def check_app_compat(available_group): frappe_upgrade_msg = "" - trimmed_available_group = set([(app['scrubbed'], app['branch']) for app in available_group['apps']]) - existing_group = set([(app['app_name'], app['branch']) for app in get_installed_apps_info()]) - + is_compat = True + incompatible_apps, filtered_apps, branch_msgs = [], [], [] + existing_group = [(app["app_name"], app["branch"]) for app in get_installed_apps_info()] print("Checking availability of existing app group") - incompatible_apps = [] - filtered_apps = [] - branch_msgs = [] for (app, branch) in existing_group: - if (app, branch) not in trimmed_available_group: - app_title = [group["name"] for group in available_group["apps"] if group["scrubbed"] == app] - if app_title: - app_title = app_title[0] - is_compat = False - if app not in get_first_party_apps(): - incompatible_apps.append(app) - print(f"❌ App {app}:{branch}") - else: - available_branch = [a['branch'] for a in available_group['apps'] if a['scrubbed'] == app] - if not available_branch: - print(f"App {app} doesn't exist in selected group") - continue - else: - available_branch = available_branch[0] - print(f"⚠️ {app}:{branch} => {available_branch}") + info = [ (a["name"], a["branch"]) for a in available_group["apps"] if a["scrubbed"] == app] + if info: + app_title, available_branch = info[0] + + if branch != available_branch: + print("⚠️ {}:{} => {}".format(app, branch, available_branch)) branch_msgs.append([app.title(), branch, available_branch]) filtered_apps.append(app_title) + is_compat = False + + else: + print("✅ App {}:{}".format(app, branch)) + filtered_apps.append(app_title) + else: - filtered_apps.append(app_title) + incompatible_apps.append(app) + print("❌ App {}:{}".format(app, branch)) + is_compat = False start_msg = "\nSelecting this group will " - incompatible_apps = f"drop {len(incompatible_apps)} apps: " + ", ".join(incompatible_apps) + " and " if incompatible_apps else "" + incompatible_apps = "drop {} apps: ".format(len(incompatible_apps)) + ", ".join(incompatible_apps) + " and " if incompatible_apps else "" branch_change = "upgrade:\n" + "\n".join(["{}: {} => {}".format(*x) for x in branch_msgs]) if branch_msgs else "" changes = (incompatible_apps + branch_change) or "be perfect for you :)" warning_message = start_msg + changes - print(warning_message) return is_compat, filtered_apps -def generate_app_group_table(app_groups): +def render_group_table(app_groups): + # title row app_groups_table = [["#", "App Group", "Apps"]] - for _, app_group in enumerate(app_groups): - row = [_ + 1, app_group["name"], ", ".join([f"{app['scrubbed']}:{app['branch']}" for app in app_group['apps']])] + # all rows + for idx, app_group in enumerate(app_groups): + apps_list = ", ".join(["{}:{}".format(app["scrubbed"], app["branch"]) for app in app_group["apps"]]) + row = [idx + 1, app_group["name"], apps_list] app_groups_table.append(row) render_table(app_groups_table) @@ -115,26 +130,21 @@ def generate_app_group_table(app_groups): @padme def filter_apps(app_groups): - # try for default group first...then let em select which group - default_group = [g for g in app_groups if g['default']][0] - is_compat, filtered_apps = check_app_compat(default_group) + render_group_table(app_groups) - if not is_compat and not click.confirm("Continue anyway?"): - generate_app_group_table(app_groups) + while True: + try: + app_group_index = int(input("Select App Group #: ").strip()) - 1 + selected_group = app_groups[app_group_index] + is_compat, filtered_apps = check_app_compat(selected_group) + except: + print("Invalid Selection") + sys.exit(1) - while True: - try: - app_group_index = int(input("Select App Group #: ").strip()) - 1 - selected_group = app_groups[app_group_index] - is_compat, filtered_apps = check_app_compat(selected_group) - except: - print("Invalid Selection") - sys.exit(1) + if is_compat or click.confirm("Continue anyway?"): + break - if is_compat or click.confirm("Continue anyway?"): - break - - return default_group['name'], filtered_apps + return selected_group["name"], filtered_apps @padme def create_session(): @@ -148,63 +158,35 @@ def create_session(): login_sc = session.post(login_url, auth_credentials) if login_sc.ok: - print(f"Authorization Successful! ✅") + print("Authorization Successful! ✅") session.headers.update({"X-Press-Team": username}) return session else: - print(f"Authorization Failed with Error Code {login_sc.status_code}") - - -def get_new_site_options(): - site_options_sc = session.post(options_url) - - if site_options_sc.ok: - site_options = site_options_sc.json()["message"] - return site_options - else: - print(f"Couldn't retrive New site information: {site_options_sc.status_code}") - - -def is_valid_subdomain(subdomain): - matched = re.match("^[a-z0-9][a-z0-9-]*[a-z0-9]$", subdomain) - if matched: - return True - print('Subdomain contains invalid characters. Use lowercase characters, numbers and hyphens') - - -def is_subdomain_available(subdomain): - res = session.post(site_exists_url, {"subdomain": subdomain}) - if res.ok: - available = not res.json()['message'] - if not available: - print('Subdomain already exists! Try another one') - - return available + print("Authorization Failed with Error Code {}".format(login_sc.status_code)) @padme def get_subdomain(domain): while True: subdomain = input("Enter subdomain: ").strip() - if is_valid_subdomain(subdomain): - if is_subdomain_available(subdomain): - print(f"Site Domain: {subdomain}.{domain}") - return subdomain + if is_valid_subdomain(subdomain) and is_subdomain_available(subdomain): + print("Site Domain: {}.{}".format(subdomain, domain)) + return subdomain @padme def upload_backup(local_site): # take backup - print(f"Taking backup for site {local_site}") - odb = frappe.utils.backups.new_backup(ignore_files=False, force=True) files_session = {} + print("Taking backup for site {}".format(local_site)) + odb = frappe.utils.backups.new_backup(ignore_files=False, force=True) # upload files - for file_type, file_path in [ + for x, (file_type, file_path) in enumerate([ ("database", odb.backup_path_db), ("public", odb.backup_path_files), ("private", odb.backup_path_private_files) - ]: + ]): file_upload_response = session.post(files_url, data={}, files={ "file": open(file_path, "rb"), "is_private": 1, @@ -212,27 +194,28 @@ def upload_backup(local_site): "method": "press.api.site.upload_backup", "type": file_type }) + print("Uploading files ({}/3)".format(x+1), end="\r") if file_upload_response.ok: files_session[file_type] = file_upload_response.json()["message"] else: - print(f"Upload failed for: {file_path}") + print("Upload failed for: {}".format(file_path)) files_uploaded = { k: v["file_url"] for k, v in files_session.items() } + print("Uploaded backup files! ✅") return files_uploaded def frappecloud_migrator(local_site, remote_site): - # test (change to https !!!): global login_url, upload_url, files_url, options_url, site_exists_url, session - login_url = f"http://{remote_site}/api/method/login" - upload_url = f"http://{remote_site}/api/method/press.api.site.new" - files_url = f"http://{remote_site}/api/method/upload_file" - options_url = f"http://{remote_site}/api/method/press.api.site.options_for_new" - site_exists_url = f"http://{remote_site}/api/method/press.api.site.exists" + login_url = "https://{}/api/method/login".format(remote_site) + upload_url = "https://{}/api/method/press.api.site.new".format(remote_site) + files_url = "https://{}/api/method/upload_file".format(remote_site) + options_url = "https://{}/api/method/press.api.site.options_for_new".format(remote_site) + site_exists_url = "https://{}/api/method/press.api.site.exists".format(remote_site) - print(f"Frappe Cloud credentials @ {remote_site}") + print("Frappe Cloud credentials @ {}".format(remote_site)) # get credentials + auth user + start session session = create_session() @@ -246,8 +229,8 @@ def frappecloud_migrator(local_site, remote_site): site_options = get_new_site_options() # set preferences from site options - subdomain = get_subdomain(site_options['domain']) - plan = choose_plan(site_options['plans']) + subdomain = get_subdomain(site_options["domain"]) + plan = choose_plan(site_options["plans"]) app_groups = site_options["groups"] selected_group, filtered_apps = filter_apps(app_groups) @@ -269,16 +252,17 @@ def frappecloud_migrator(local_site, remote_site): frappe.destroy() if site_creation_request.ok: - print(f"Site creation started at {site_creation_request.json()['message']}") + print("Site creation started at {}".format(site_creation_request.json()["message"])) else: - print(f"Request failed with error code {site_creation_request.status_code}") + print("Request failed with error code {}".format(site_creation_request.status_code)) + reason = html2text(site_creation_request.text) + print(reason) def migrate_to(local_site, remote_site): if remote_site in ("frappe.cloud", "frappecloud.com"): - remote_site = "cloud:8002" - # remote_site = "frappecloud.com" + remote_site = "frappecloud.com" return frappecloud_migrator(local_site, remote_site) else: - print(f"{remote_site} is not supported yet") + print("{} is not supported yet".format(remote_site)) sys.exit(1) From 1ee09fa76ef71c95b9f1444596e82873850e1e9d Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 14 May 2020 12:25:02 +0530 Subject: [PATCH 055/107] chore: pin terminaltables dependency --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c4cb79892a..e0ab166a8c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,7 +60,7 @@ semantic-version==2.8.4 six==1.14.0 sqlparse==0.2.4 stripe==2.40.0 -terminaltables +terminaltables==3.1.0 unittest-xml-reporting==2.5.2 urllib3==1.25.8 watchdog==0.8.0 From 07893b295b260e44e0ef7f8d02351cdd3cf0934f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 May 2020 12:31:20 +0530 Subject: [PATCH 056/107] fix: enable forms for developers --- frappe/desk/doctype/desk_page/desk_page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/desk_page/desk_page.js b/frappe/desk/doctype/desk_page/desk_page.js index ec8eaaa60b..503859eb61 100644 --- a/frappe/desk/doctype/desk_page/desk_page.js +++ b/frappe/desk/doctype/desk_page/desk_page.js @@ -12,7 +12,7 @@ frappe.ui.form.on('Desk Page', { frm.set_df_property("extends", "read_only", true); } - if (frm.doc.for_user || frm.doc.is_standard) { + if (frm.doc.for_user || (frm.doc.is_standard && !frappe.boot.developer_mode)) { frm.trigger('disable_form'); } }, From 2c5fe271ff61b4896eeafe58dfdac08abd86a76c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 May 2020 12:45:56 +0530 Subject: [PATCH 057/107] feat: hide desk dashboard if onboarding is present --- frappe/public/js/frappe/views/desktop/desktop.js | 3 ++- frappe/public/js/frappe/widgets/widget_group.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index 5956a6310d..51add61f07 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -294,7 +294,7 @@ class DesktopPage { make_charts() { return frappe.dashboard_utils.get_dashboard_settings().then(settings => { - let chart_config = settings.chart_config? JSON.parse(settings.chart_config): {}; + let chart_config = settings.chart_config ? JSON.parse(settings.chart_config): {}; if (this.data.charts.items) { this.data.charts.items.map(chart => { chart.chart_settings = chart_config[chart.chart_name] || {}; @@ -306,6 +306,7 @@ class DesktopPage { container: this.page, type: "chart", columns: 1, + hidden: Boolean(this.onboarding_widget), options: { allow_sorting: this.allow_customization, allow_create: this.allow_customization, diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index 8c8dd02968..2147cd0dee 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -52,6 +52,7 @@ export default class WidgetGroup {
`); this.widget_area = widget_area; + if (this.hidden) this.widget_area.hide() this.title_area = widget_area.find(".widget-group-title"); this.control_area = widget_area.find(".widget-group-control"); this.body = widget_area.find(".widget-group-body"); @@ -96,7 +97,7 @@ export default class WidgetGroup { } customize() { - this.widget_area.show(); + if (!this.hidden) this.widget_area.show(); this.widgets_list.forEach((wid) => { wid.customize(this.options); }); From a48c24e4bf9bf96671a4f1d4359fc34a0c59faac Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 14 May 2020 13:01:46 +0530 Subject: [PATCH 058/107] chore: consistent functions used for inputs, removed unused imports --- frappe/utils/commands.py | 14 ++++++++++++++ frappe/utils/remote_migrations.py | 21 +++++++++++---------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/frappe/utils/commands.py b/frappe/utils/commands.py index 3c16193173..babefc56e2 100644 --- a/frappe/utils/commands.py +++ b/frappe/utils/commands.py @@ -23,3 +23,17 @@ def padme(me): print() return result return empty_line + + +def log(message, colour=''): + colours = { + "nc": '\033[0m', + "blue": '\033[94m', + "green": '\033[92m', + "yellow": '\033[93m', + "red": '\033[91m', + "silver": '\033[90m' + } + colour = colours.get(colour, "") + end_line = '\033[0m' + print(colour + message + end_line) diff --git a/frappe/utils/remote_migrations.py b/frappe/utils/remote_migrations.py index c89134f27c..656fb803de 100644 --- a/frappe/utils/remote_migrations.py +++ b/frappe/utils/remote_migrations.py @@ -14,8 +14,7 @@ import requests import frappe import frappe.utils.backups from frappe.utils import get_installed_apps_info -from frappe.utils.commands import get_first_party_apps, render_table, padme - +from frappe.utils.commands import render_table, padme def get_new_site_options(): @@ -69,12 +68,12 @@ def choose_plan(plans_list): render_plan_table(plans_list) while True: - input_plan = input("Send plan?: ").strip() + input_plan = click.prompt("Select Plan").strip() if input_plan in available_plans: print("{} Plan selected ✅".format(input_plan)) return input_plan else: - print("Invalid selection...try again ❌") + print("Invalid Selection ❌") @padme @@ -133,15 +132,17 @@ def filter_apps(app_groups): render_group_table(app_groups) while True: + app_group_index = click.prompt("Select App Group #", type=int) - 1 try: - app_group_index = int(input("Select App Group #: ").strip()) - 1 selected_group = app_groups[app_group_index] - is_compat, filtered_apps = check_app_compat(selected_group) except: - print("Invalid Selection") - sys.exit(1) + print("Invalid Selection ❌") + break + + is_compat, filtered_apps = check_app_compat(selected_group) if is_compat or click.confirm("Continue anyway?"): + print("App Group {} selected! ✅".format(selected_group["name"])) break return selected_group["name"], filtered_apps @@ -149,7 +150,7 @@ def filter_apps(app_groups): @padme def create_session(): # take user input from STDIN - username = input("Username: ").strip() + username = click.prompt("Username").strip() password = getpass.unix_getpass() auth_credentials = {"usr": username, "pwd": password} @@ -168,7 +169,7 @@ def create_session(): @padme def get_subdomain(domain): while True: - subdomain = input("Enter subdomain: ").strip() + subdomain = click.prompt("Enter subdomain: ").strip() if is_valid_subdomain(subdomain) and is_subdomain_available(subdomain): print("Site Domain: {}.{}".format(subdomain, domain)) return subdomain From 3fcc15685ab85929b6f594e137251c38a3c99974 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Sun, 10 May 2020 04:07:14 +0530 Subject: [PATCH 059/107] fix: dont show any output unless verbose set --- frappe/utils/backups.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index a4c2c4bb70..0a04db2c3e 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -103,7 +103,8 @@ class BackupGenerator: cmd_string = """tar -cf %s %s""" % (backup_path, files_path) err, out = frappe.utils.execute_in_shell(cmd_string) - print('Backed up files', os.path.abspath(backup_path)) + if verbose: + print('Backed up files', os.path.abspath(backup_path)) def take_dump(self): import frappe.utils @@ -151,7 +152,6 @@ def get_backup(): This function is executed when the user clicks on Toos > Download Backup """ - #if verbose: print frappe.db.cur_db_name + " " + conf.db_password delete_temp_backups() odb = BackupGenerator(frappe.conf.db_name, frappe.conf.db_name,\ frappe.conf.db_password, db_host = frappe.db.host) From 00bace4d3fcff50372e34e2c0d2772a7098cf926 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 May 2020 14:38:38 +0530 Subject: [PATCH 060/107] fix: linting issues sider --- frappe/public/js/frappe/widgets/widget_group.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index 2147cd0dee..e82cbc6edf 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -52,7 +52,7 @@ export default class WidgetGroup {
`); this.widget_area = widget_area; - if (this.hidden) this.widget_area.hide() + if (this.hidden) this.widget_area.hide(); this.title_area = widget_area.find(".widget-group-title"); this.control_area = widget_area.find(".widget-group-control"); this.body = widget_area.find(".widget-group-body"); From 791bb82255ed3eb1dd6a7bd65893f4283a283d8b Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 14 May 2020 15:10:51 +0530 Subject: [PATCH 061/107] feat: rename doctype --- .../deploy_instance/deploy_instance.js | 8 --- .../deploy_instance/test_deploy_instance.py | 10 --- .../__init__.py | 0 .../package_publish_target.json} | 7 +- .../package_publish_target.py} | 2 +- .../__init__.py | 0 .../package_publish_tool.js} | 47 +++++++++--- .../package_publish_tool.json} | 58 ++++++++------- .../package_publish_tool.py} | 71 ++++++++----------- .../test_package_publish_tool.py} | 2 +- frappe/public/js/frappe/utils/pretty_date.js | 1 + 11 files changed, 104 insertions(+), 102 deletions(-) delete mode 100644 frappe/custom/doctype/deploy_instance/deploy_instance.js delete mode 100644 frappe/custom/doctype/deploy_instance/test_deploy_instance.py rename frappe/custom/doctype/{deploy_instance => package_publish_target}/__init__.py (100%) rename frappe/custom/doctype/{deploy_instance/deploy_instance.json => package_publish_target/package_publish_target.json} (83%) rename frappe/custom/doctype/{deploy_instance/deploy_instance.py => package_publish_target/package_publish_target.py} (86%) rename frappe/custom/doctype/{package => package_publish_tool}/__init__.py (100%) rename frappe/custom/doctype/{package/package.js => package_publish_tool/package_publish_tool.js} (66%) rename frappe/custom/doctype/{package/package.json => package_publish_tool/package_publish_tool.json} (62%) rename frappe/custom/doctype/{package/package.py => package_publish_tool/package_publish_tool.py} (68%) rename frappe/custom/doctype/{package/test_package.py => package_publish_tool/test_package_publish_tool.py} (78%) diff --git a/frappe/custom/doctype/deploy_instance/deploy_instance.js b/frappe/custom/doctype/deploy_instance/deploy_instance.js deleted file mode 100644 index d4b6e75155..0000000000 --- a/frappe/custom/doctype/deploy_instance/deploy_instance.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('DeployInstance', { - // refresh: function(frm) { - - // } -}); diff --git a/frappe/custom/doctype/deploy_instance/test_deploy_instance.py b/frappe/custom/doctype/deploy_instance/test_deploy_instance.py deleted file mode 100644 index cdacc3a1ca..0000000000 --- a/frappe/custom/doctype/deploy_instance/test_deploy_instance.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt -from __future__ import unicode_literals - -# import frappe -import unittest - -class TestDeployInstance(unittest.TestCase): - pass diff --git a/frappe/custom/doctype/deploy_instance/__init__.py b/frappe/custom/doctype/package_publish_target/__init__.py similarity index 100% rename from frappe/custom/doctype/deploy_instance/__init__.py rename to frappe/custom/doctype/package_publish_target/__init__.py diff --git a/frappe/custom/doctype/deploy_instance/deploy_instance.json b/frappe/custom/doctype/package_publish_target/package_publish_target.json similarity index 83% rename from frappe/custom/doctype/deploy_instance/deploy_instance.json rename to frappe/custom/doctype/package_publish_target/package_publish_target.json index 4bb140f088..ccf3f2ea05 100644 --- a/frappe/custom/doctype/deploy_instance/deploy_instance.json +++ b/frappe/custom/doctype/package_publish_target/package_publish_target.json @@ -1,6 +1,6 @@ { "actions": [], - "creation": "2020-03-18 18:25:02.024237", + "creation": "2020-05-13 16:04:32.724663", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", @@ -27,16 +27,17 @@ { "fieldname": "password", "fieldtype": "Password", + "in_list_view": 1, "label": "Password", "reqd": 1 } ], "istable": 1, "links": [], - "modified": "2020-04-14 13:56:31.167730", + "modified": "2020-05-13 16:04:49.943057", "modified_by": "Administrator", "module": "Custom", - "name": "Deploy Instance", + "name": "Package Publish Target", "owner": "Administrator", "permissions": [], "quick_entry": 1, diff --git a/frappe/custom/doctype/deploy_instance/deploy_instance.py b/frappe/custom/doctype/package_publish_target/package_publish_target.py similarity index 86% rename from frappe/custom/doctype/deploy_instance/deploy_instance.py rename to frappe/custom/doctype/package_publish_target/package_publish_target.py index 9bc2e9192e..34eee02562 100644 --- a/frappe/custom/doctype/deploy_instance/deploy_instance.py +++ b/frappe/custom/doctype/package_publish_target/package_publish_target.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class DeployInstance(Document): +class PackagePublishTarget(Document): pass diff --git a/frappe/custom/doctype/package/__init__.py b/frappe/custom/doctype/package_publish_tool/__init__.py similarity index 100% rename from frappe/custom/doctype/package/__init__.py rename to frappe/custom/doctype/package_publish_tool/__init__.py diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package_publish_tool/package_publish_tool.js similarity index 66% rename from frappe/custom/doctype/package/package.js rename to frappe/custom/doctype/package_publish_tool/package_publish_tool.js index 956bbfc05c..3d84a50820 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.js @@ -1,7 +1,7 @@ // Copyright (c) 2020, Frappe Technologies and contributors // For license information, please see license.txt -frappe.ui.form.on('Package', { +frappe.ui.form.on('Package Publish Tool', { refresh: function(frm) { frm.set_query("document_type", "package_details", function () { return { @@ -20,15 +20,46 @@ frappe.ui.form.on('Package', { } }); - if (frm.doc.instances) { - frm.add_custom_button(__("Deploy"), function() { - frm.call("deploy_package"); + frm.trigger("show_instructions"); + frm.trigger("last_deployed_on"); + frm.trigger("set_dirty_trigger"); + frm.trigger("set_deploy_primary_action"); + }, + last_deployed_on: function(frm) { + if (frm.doc.last_deployed_on) { + frm.trigger("show_indicator"); + } + }, + show_indicator: function(frm) { + let pretty_date = frappe.datetime.prettyDate(frm.doc.last_deployed_on); + frm.page.set_indicator(__("Last deployed {0}", [pretty_date]), "blue"); + }, + set_dirty_trigger: function(frm) { + $(frm.wrapper).on("dirty", function() { + frm.page.set_primary_action(__('Save'), () => frm.save()); + }) + }, + set_deploy_primary_action: function(frm) { + if (frm.doc.package_details.length && frm.doc.instances.length){ + frm.page.set_primary_action(__("Deploy"), function () { + frappe.call({ + method: "frappe.custom.doctype.package_publish_tool.package_publish_tool.deploy_package", + callback: function(r) { + frappe.msgprint(__("Package has been published.")); + } + }); }); } - }, - import: function(frm) { - frm.call("import_from_package"); + show_instructions: function(frm) { + let field = frm.get_field("html_info"); + field.html(` +

+ - Create a Package by selecting the Documents and filters in the Package table.
+ - Add the Remote Instance's URL, Username and Password to the Instances table.
+ - Once the above details are filled, Deploy button will be visible and the Package will be deployed on click.
+

+ `); } }); @@ -115,4 +146,4 @@ frappe.ui.form.on('Package Detail', { }); }); }, -}); +}); \ No newline at end of file diff --git a/frappe/custom/doctype/package/package.json b/frappe/custom/doctype/package_publish_tool/package_publish_tool.json similarity index 62% rename from frappe/custom/doctype/package/package.json rename to frappe/custom/doctype/package_publish_tool/package_publish_tool.json index 2bec7f9b79..cdb6f0ce84 100644 --- a/frappe/custom/doctype/package/package.json +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.json @@ -1,59 +1,57 @@ { "actions": [], - "creation": "2020-03-14 11:20:14.850552", + "creation": "2020-05-13 15:54:38.082657", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "html_info", + "sb_00", "package_details", - "section_break_2", + "sb_01", "instances", - "import_section", - "attach", - "import" + "last_deployed_on" ], "fields": [ - { - "collapsible": 1, - "collapsible_depends_on": "attach", - "fieldname": "import_section", - "fieldtype": "Section Break", - "label": "Import Package" - }, - { - "fieldname": "import", - "fieldtype": "Button", - "label": "Import" - }, - { - "fieldname": "attach", - "fieldtype": "Attach", - "label": "Attach Package" - }, { "description": "Click on the row for accessing filters.", "fieldname": "package_details", "fieldtype": "Table", - "label": "Package Details", + "label": "Package", "options": "Package Detail" }, - { - "fieldname": "section_break_2", - "fieldtype": "Section Break" - }, { "fieldname": "instances", "fieldtype": "Table", "label": "Instances", - "options": "Deploy Instance" + "options": "Package Publish Target" + }, + { + "fieldname": "html_info", + "fieldtype": "HTML" + }, + { + "fieldname": "last_deployed_on", + "fieldtype": "Datetime", + "hidden": 1, + "label": "Last Deployed On", + "read_only": 1 + }, + { + "fieldname": "sb_00", + "fieldtype": "Section Break" + }, + { + "fieldname": "sb_01", + "fieldtype": "Section Break" } ], "issingle": 1, "links": [], - "modified": "2020-04-14 13:50:50.779396", + "modified": "2020-05-14 15:09:38.092918", "modified_by": "Administrator", "module": "Custom", - "name": "Package", + "name": "Package Publish Tool", "owner": "Administrator", "permissions": [ { diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package_publish_tool/package_publish_tool.py similarity index 68% rename from frappe/custom/doctype/package/package.py rename to frappe/custom/doctype/package_publish_tool/package_publish_tool.py index b4427d55bc..f4d71a9fd9 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.py @@ -15,57 +15,46 @@ from frappe.frappeclient import FrappeClient from frappe.model.naming import make_autoname from frappe.utils.password import get_decrypted_password -class Package(Document): - def import_from_package(self): - filters = {"attached_to_doctype": "Package", "attached_to_name": "Package"} - files = frappe.get_list("File", filters=filters, limit=1, order_by="creation desc") - if not files: - frappe.msgprint(_("No file attach for Importing.")) - return +class PackagePublishTool(Document): + pass - for f in files: - fname, fcontents = get_file(f.name) - import_package(fcontents) +@frappe.whitelist() +def deploy_package(): + package, doc = export_package() - frappe.msgprint(_("Package Imported.")) + file_name = make_autoname("Package.####") + save_file(file_name, json.dumps(package), "Package Publish Tool", "Package Publish Tool") - def deploy_package(self): - package = export_package() + length = len(doc.instances) + for idx, instance in enumerate(doc.instances): + frappe.publish_realtime("package", {"progress": idx, "total": length, "message": instance.instance_url, "prefix": _("Deploying")}, + user=frappe.session.user) - for dt_file in frappe.get_list("File", filters={"attached_to_doctype": "Release", "attached_to_name": "Release"}): - frappe.delete_doc_if_exists("File", dt_file.name) + install_package_to_remote(package, instance) - file_name = make_autoname("Package") - save_file(file_name, json.dumps(package), "Package", "Package") + frappe.db.set_value("Package Publish Tool", "Package Publish Tool", "last_deployed_on", frappe.utils.now_datetime()) - length = len(self.instances) - for idx, instance in enumerate(self.instances): - frappe.publish_realtime("package", {"progress": idx, "total": length, "message": instance.instance_url, "prefix": _("Deploying")}, - user=frappe.session.user) +def install_package_to_remote(package, instance): + return + try: + connection = FrappeClient(instance.instance_url, instance.username, get_decrypted_password(instance.doctype, instance.name)) + except Exception: + frappe.log_error(frappe.get_traceback()) + frappe.throw(_("Couldn't connect to site {0}. Please check Error Logs.").format(instance.instance_url)) - self.install_package_to_remote(package, instance) - - def install_package_to_remote(self, package, instance): - print((instance.doctype, instance.name)) - try: - connection = FrappeClient(instance.instance_url, instance.username, get_decrypted_password(instance.doctype, instance.name)) - except Exception: - frappe.log_error(frappe.get_traceback()) - frappe.throw(_("Couldn't connect to site {0}. Please check Error Logs.").format(instance.instance_url)) - - try: - connection.post_request({ - "cmd": "frappe.custom.doctype.package.package.import_package", - "package": json.dumps(package) - }) - except Exception: - frappe.log_error(frappe.get_traceback()) - frappe.throw(_("Error while installing package to site {0}. Please check Error Logs.").format(instance.instance_url)) + try: + connection.post_request({ + "cmd": "frappe.custom.doctype.package_publish_tool.package_publish_tool.import_package", + "package": json.dumps(package) + }) + except Exception: + frappe.log_error(frappe.get_traceback()) + frappe.throw(_("Error while installing package to site {0}. Please check Error Logs.").format(instance.instance_url)) @frappe.whitelist() def export_package(): """Export package as JSON.""" - package_doc = frappe.get_single("Package") + package_doc = frappe.get_single("Package Publish Tool") package = [] for doctype in package_doc.package_details: @@ -108,7 +97,7 @@ def export_package(): package.append(document) - return post_process(package) + return post_process(package), package_doc @frappe.whitelist() def import_package(package=None): diff --git a/frappe/custom/doctype/package/test_package.py b/frappe/custom/doctype/package_publish_tool/test_package_publish_tool.py similarity index 78% rename from frappe/custom/doctype/package/test_package.py rename to frappe/custom/doctype/package_publish_tool/test_package_publish_tool.py index 3a17d51260..8332240543 100644 --- a/frappe/custom/doctype/package/test_package.py +++ b/frappe/custom/doctype/package_publish_tool/test_package_publish_tool.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestPackage(unittest.TestCase): +class TestPackagePublishTool(unittest.TestCase): pass diff --git a/frappe/public/js/frappe/utils/pretty_date.js b/frappe/public/js/frappe/utils/pretty_date.js index ef235ed3b1..7618d58829 100644 --- a/frappe/public/js/frappe/utils/pretty_date.js +++ b/frappe/public/js/frappe/utils/pretty_date.js @@ -76,6 +76,7 @@ window.comment_when = function(datetime, mini) { + prettyDate(datetime, mini) + ''; }; frappe.datetime.comment_when = comment_when; +frappe.datetime.prettyDate = prettyDate; frappe.datetime.refresh_when = function() { if (jQuery) { From 94b6fa4319a0bff37428f7875e8843a06bc1607e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 14 May 2020 15:27:17 +0530 Subject: [PATCH 062/107] fix: report filters in dialog --- frappe/public/js/frappe/views/reports/query_report.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 5105494862..e79e43ae02 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -330,8 +330,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { evaluate_depends_on_value(expression, filter_label) { let out = null; - let filters = this.get_filter_values(); - if (filters) { + let doc = this.get_filter_values(); + if (doc) { if (typeof expression === 'boolean') { out = expression; } else if (expression.substr(0, 5) == 'eval:') { @@ -341,7 +341,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { frappe.throw(__(`Invalid "depends_on" expression set in filter ${filter_label}`)); } } else { - var value = filters[expression]; + var value = doc[expression]; if ($.isArray(value)) { out = !!value.length; } else { From e5d5d0b6bb6c74007da312fb647d9a16b3f2e61d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 May 2020 16:07:57 +0530 Subject: [PATCH 063/107] feat: allow linking dashboard in desk page --- frappe/desk/desktop.py | 2 ++ frappe/desk/doctype/desk_shortcut/desk_shortcut.json | 4 ++-- frappe/public/js/frappe/widgets/utils.js | 2 ++ frappe/public/js/frappe/widgets/widget_dialog.js | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index ddcfb670d4..e2fe020eca 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -127,6 +127,8 @@ class Workspace: return name in self.allowed_reports if item_type == "help": return True + if item_type == "dashboard": + return True return False diff --git a/frappe/desk/doctype/desk_shortcut/desk_shortcut.json b/frappe/desk/doctype/desk_shortcut/desk_shortcut.json index 550ea609c8..f3fd546a77 100644 --- a/frappe/desk/doctype/desk_shortcut/desk_shortcut.json +++ b/frappe/desk/doctype/desk_shortcut/desk_shortcut.json @@ -23,7 +23,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Type", - "options": "DocType\nReport\nPage", + "options": "DocType\nReport\nPage\nDashboard", "reqd": 1 }, { @@ -88,7 +88,7 @@ ], "istable": 1, "links": [], - "modified": "2020-05-13 19:26:34.229669", + "modified": "2020-05-14 16:02:15.420993", "modified_by": "Administrator", "module": "Desk", "name": "Desk Shortcut", diff --git a/frappe/public/js/frappe/widgets/utils.js b/frappe/public/js/frappe/widgets/utils.js index 0d93bb3784..f336335d1c 100644 --- a/frappe/public/js/frappe/widgets/utils.js +++ b/frappe/public/js/frappe/widgets/utils.js @@ -24,6 +24,8 @@ function generate_route(item) { route = "List/" + item.doctype + "/Report/" + item.name; } else if (type === "page") { route = item.name; + } else if (type === "dashboard") { + route = "dashboard/" + item.name; } route = "#" + route; diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index 31215a40c3..5c44533b37 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -145,7 +145,7 @@ class ShortcutDialog extends WidgetDialog { fieldname: "type", label: "Type", reqd: 1, - options: "DocType\nReport\nPage", + options: "DocType\nReport\nPage\nDashboard", onchange: () => { if (this.dialog.get_value("type") == "DocType") { this.dialog.fields_dict.link_to.get_query = () => { From b55d168f2ab7b0564c531048a83fa38950d7ecd0 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 14 May 2020 16:10:58 +0530 Subject: [PATCH 064/107] fix: show alert while deploying --- .../doctype/package_publish_tool/package_publish_tool.js | 5 +++++ .../package_publish_tool/package_publish_tool.json | 8 +++++--- .../doctype/package_publish_tool/package_publish_tool.py | 1 - 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/frappe/custom/doctype/package_publish_tool/package_publish_tool.js b/frappe/custom/doctype/package_publish_tool/package_publish_tool.js index 3d84a50820..7822299f21 100644 --- a/frappe/custom/doctype/package_publish_tool/package_publish_tool.js +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.js @@ -42,6 +42,11 @@ frappe.ui.form.on('Package Publish Tool', { set_deploy_primary_action: function(frm) { if (frm.doc.package_details.length && frm.doc.instances.length){ frm.page.set_primary_action(__("Deploy"), function () { + frappe.show_alert({ + message: __("Deploying Package"), + indicator: "green" + }); + frappe.call({ method: "frappe.custom.doctype.package_publish_tool.package_publish_tool.deploy_package", callback: function(r) { diff --git a/frappe/custom/doctype/package_publish_tool/package_publish_tool.json b/frappe/custom/doctype/package_publish_tool/package_publish_tool.json index cdb6f0ce84..9b1db2c2ff 100644 --- a/frappe/custom/doctype/package_publish_tool/package_publish_tool.json +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.json @@ -18,13 +18,15 @@ "fieldname": "package_details", "fieldtype": "Table", "label": "Package", - "options": "Package Detail" + "options": "Package Detail", + "reqd": 1 }, { "fieldname": "instances", "fieldtype": "Table", "label": "Instances", - "options": "Package Publish Target" + "options": "Package Publish Target", + "reqd": 1 }, { "fieldname": "html_info", @@ -48,7 +50,7 @@ ], "issingle": 1, "links": [], - "modified": "2020-05-14 15:09:38.092918", + "modified": "2020-05-14 16:06:19.714596", "modified_by": "Administrator", "module": "Custom", "name": "Package Publish Tool", diff --git a/frappe/custom/doctype/package_publish_tool/package_publish_tool.py b/frappe/custom/doctype/package_publish_tool/package_publish_tool.py index f4d71a9fd9..09549107be 100644 --- a/frappe/custom/doctype/package_publish_tool/package_publish_tool.py +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.py @@ -35,7 +35,6 @@ def deploy_package(): frappe.db.set_value("Package Publish Tool", "Package Publish Tool", "last_deployed_on", frappe.utils.now_datetime()) def install_package_to_remote(package, instance): - return try: connection = FrappeClient(instance.instance_url, instance.username, get_decrypted_password(instance.doctype, instance.name)) except Exception: From c14b6c592a1eacd4158801ad9865dd7ed00c9a87 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 14 May 2020 16:27:34 +0530 Subject: [PATCH 065/107] fix: move code to meta.py --- .../custom/doctype/custom_link/custom_link.js | 4 +++ .../custom/doctype/custom_link/custom_link.py | 33 ------------------ frappe/model/meta.py | 34 ++++++++++++++++++- 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/frappe/custom/doctype/custom_link/custom_link.js b/frappe/custom/doctype/custom_link/custom_link.js index f1c06daeeb..9540ec5662 100644 --- a/frappe/custom/doctype/custom_link/custom_link.js +++ b/frappe/custom/doctype/custom_link/custom_link.js @@ -12,5 +12,9 @@ frappe.ui.form.on('Custom Link', { } } }); + + frm.add_custom_button(__('Go to {0} List', [frm.doc.document_type]), function() { + frappe.set_route('List', frm.doc.document_type); + }); } }); diff --git a/frappe/custom/doctype/custom_link/custom_link.py b/frappe/custom/doctype/custom_link/custom_link.py index b9a88b58f2..be57d256f2 100644 --- a/frappe/custom/doctype/custom_link/custom_link.py +++ b/frappe/custom/doctype/custom_link/custom_link.py @@ -8,36 +8,3 @@ from frappe.model.document import Document class CustomLink(Document): pass - -def get_custom_doctype_links(doctype, data): - if frappe.get_all("Custom Link", {"document_type": doctype}): - doc = frappe.get_doc("Custom Link", doctype) - - if not data.transactions: - # init groups - data.transactions = [] - data.non_standard_fieldnames = {} - - for link in doc.links: - link.added = False - for group in data.transactions: - # group found - if group.get("label") == link.group: - if not link.link_doctype in group.get("items"): - group.get("items").append(link.link_doctype) - link.added = True - - if not link.added: - # group not found, make a new group - data.transactions.append({ - "label": link.group, - "items": [link.link_doctype] - }) - - if link.link_fieldname != data.fieldname: - if data.fieldname: - data.non_standard_fieldnames[link.link_doctype] = link.link_fieldname - else: - data.fieldname = link.link_fieldname - - return data \ No newline at end of file diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 156fe2dba3..717070a217 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -26,7 +26,6 @@ from frappe.model.base_document import BaseDocument from frappe.modules import load_doctype_module from frappe.model.workflow import get_workflow_name from frappe import _ -from frappe.custom.doctype.custom_link.custom_link import get_custom_doctype_links def get_meta(doctype, cached=True): if cached: @@ -609,3 +608,36 @@ def trim_tables(doctype=None): query = """alter table `tab{doctype}` {columns}""".format( doctype=doctype, columns=columns_to_remove) frappe.db.sql_ddl(query) + +def get_custom_doctype_links(doctype, data): + if frappe.get_all("Custom Link", {"document_type": doctype}): + doc = frappe.get_doc("Custom Link", doctype) + + if not data.transactions: + # init groups + data.transactions = [] + data.non_standard_fieldnames = {} + + for link in doc.links: + link.added = False + for group in data.transactions: + # group found + if group.get("label") == link.group: + if not link.link_doctype in group.get("items"): + group.get("items").append(link.link_doctype) + link.added = True + + if not link.added: + # group not found, make a new group + data.transactions.append({ + "label": link.group, + "items": [link.link_doctype] + }) + + if link.link_fieldname != data.fieldname: + if data.fieldname: + data.non_standard_fieldnames[link.link_doctype] = link.link_fieldname + else: + data.fieldname = link.link_fieldname + + return data \ No newline at end of file From aa98d6ffb6f32af4f9a15701350b05fce104967c Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 14 May 2020 16:37:14 +0530 Subject: [PATCH 066/107] fix: sider changes --- frappe/custom/doctype/custom_link/custom_link.js | 2 +- frappe/custom/doctype/custom_link/custom_link.py | 2 +- frappe/model/meta.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/custom/doctype/custom_link/custom_link.js b/frappe/custom/doctype/custom_link/custom_link.js index 9540ec5662..8662724b1a 100644 --- a/frappe/custom/doctype/custom_link/custom_link.js +++ b/frappe/custom/doctype/custom_link/custom_link.js @@ -10,7 +10,7 @@ frappe.ui.form.on('Custom Link', { istable: 0, module: ['not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]] } - } + }; }); frm.add_custom_button(__('Go to {0} List', [frm.doc.document_type]), function() { diff --git a/frappe/custom/doctype/custom_link/custom_link.py b/frappe/custom/doctype/custom_link/custom_link.py index be57d256f2..11316d5751 100644 --- a/frappe/custom/doctype/custom_link/custom_link.py +++ b/frappe/custom/doctype/custom_link/custom_link.py @@ -3,7 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe +# import frappe from frappe.model.document import Document class CustomLink(Document): diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 717070a217..a9ae19af6d 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -623,7 +623,7 @@ def get_custom_doctype_links(doctype, data): for group in data.transactions: # group found if group.get("label") == link.group: - if not link.link_doctype in group.get("items"): + if link.link_doctype not in group.get("items"): group.get("items").append(link.link_doctype) link.added = True From 69afa4f10cabc89846fc321c96c9856c32132460 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 May 2020 16:57:35 +0530 Subject: [PATCH 067/107] feat: onboarding enhancements (#10356) * feat: added go to path option in onboarding step * feat: added show full form check and doctype cleanup * feat: added show full form controller to onboarding widget * feat: don't mark complete step on go to page * feat: make callback message optional * feat: mark go to page as non mandatory * feat: use check for skipped step * fix: spaces * feat: set form as dirty on routing * style: remove unused variable --- .../onboarding_step/onboarding_step.json | 59 ++++++++++++++-- .../onboarding_step/onboarding_step.py | 4 ++ .../js/frappe/widgets/onboarding_widget.js | 68 ++++++++++++++++--- 3 files changed, 116 insertions(+), 15 deletions(-) diff --git a/frappe/desk/doctype/onboarding_step/onboarding_step.json b/frappe/desk/doctype/onboarding_step/onboarding_step.json index 3bc9d5f286..37d1d63dbe 100644 --- a/frappe/desk/doctype/onboarding_step/onboarding_step.json +++ b/frappe/desk/doctype/onboarding_step/onboarding_step.json @@ -15,11 +15,16 @@ "action", "column_break_7", "reference_document", + "show_full_form", "is_single", "reference_report", "report_reference_doctype", "report_type", "report_description", + "path", + "callback_title", + "callback_message", + "validate_action", "field", "value_to_validate", "video_url" @@ -58,7 +63,7 @@ "fieldname": "action", "fieldtype": "Select", "label": "Action", - "options": "Create Entry\nUpdate Settings\nShow Form Tour\nView Report\nWatch Video", + "options": "Create Entry\nUpdate Settings\nShow Form Tour\nView Report\nGo to Page\nWatch Video", "reqd": 1 }, { @@ -70,6 +75,7 @@ "fieldname": "reference_document", "fieldtype": "Link", "label": "Reference Document", + "mandatory_depends_on": "eval:doc.action == \"Create Entry\" || doc.action == \"Update Settings\" || doc.action == \"Create Entry\" || doc.action == \"Show Form Tour\"", "options": "DocType" }, { @@ -84,7 +90,8 @@ "depends_on": "eval:doc.action == \"Watch Video\"", "fieldname": "video_url", "fieldtype": "Data", - "label": "Video URL" + "label": "Video URL", + "mandatory_depends_on": "eval:doc.action == \"Watch Video\"" }, { "depends_on": "eval:doc.action == \"View Report\"", @@ -102,17 +109,19 @@ "label": "Is Skipped" }, { - "depends_on": "eval:doc.action == \"Update Settings\"", + "depends_on": "eval:doc.action == \"Update Settings\" && doc.validate_action", "fieldname": "field", "fieldtype": "Select", - "label": "Field" + "label": "Field", + "mandatory_depends_on": "eval:doc.action == \"Update Settings\" && doc.validate_action" }, { - "depends_on": "eval:doc.action == \"Update Settings\"", + "depends_on": "eval:doc.action == \"Update Settings\" && doc.validate_action", "description": "Use % for any non empty value.", "fieldname": "value_to_validate", "fieldtype": "Data", - "label": "Value to Validate" + "label": "Value to Validate", + "mandatory_depends_on": "eval:doc.action == \"Update Settings\" && doc.validate_action" }, { "depends_on": "eval:doc.action == \"View Report\"", @@ -136,10 +145,46 @@ "fieldname": "is_single", "fieldtype": "Check", "label": "Is Single" + }, + { + "depends_on": "eval:doc.action == \"Go to Page\"", + "description": "Example: #Tree/Account", + "fieldname": "path", + "fieldtype": "Data", + "label": "Path", + "mandatory_depends_on": "eval:doc.action == \"Go to Page\"" + }, + { + "depends_on": "eval:doc.action == \"Go to Page\"", + "fieldname": "callback_title", + "fieldtype": "Data", + "label": "Callback Title" + }, + { + "depends_on": "eval:doc.action == \"Go to Page\"", + "description": "This will be shown in a modal after routing", + "fieldname": "callback_message", + "fieldtype": "Small Text", + "label": "Callback Message" + }, + { + "default": "1", + "depends_on": "eval:doc.action == \"Update Settings\"", + "fieldname": "validate_action", + "fieldtype": "Check", + "label": "Validate Field" + }, + { + "default": "0", + "depends_on": "eval:doc.action == \"Create Entry\"", + "description": "Show full form instead of a quick entry modal", + "fieldname": "show_full_form", + "fieldtype": "Check", + "label": "Show Full Form?" } ], "links": [], - "modified": "2020-05-11 13:24:05.457160", + "modified": "2020-05-14 15:10:05.627706", "modified_by": "Administrator", "module": "Desk", "name": "Onboarding Step", diff --git a/frappe/desk/doctype/onboarding_step/onboarding_step.py b/frappe/desk/doctype/onboarding_step/onboarding_step.py index e1cc5dfba4..8086acbb2a 100644 --- a/frappe/desk/doctype/onboarding_step/onboarding_step.py +++ b/frappe/desk/doctype/onboarding_step/onboarding_step.py @@ -10,3 +10,7 @@ class OnboardingStep(Document): def before_export(self, doc): doc.is_complete = 0 doc.is_skipped = 0 + + def validate(self): + if self.action == "Go to Page": + self.is_mandatory = 0 diff --git a/frappe/public/js/frappe/widgets/onboarding_widget.js b/frappe/public/js/frappe/widgets/onboarding_widget.js index e347a70036..e869b281ae 100644 --- a/frappe/public/js/frappe/widgets/onboarding_widget.js +++ b/frappe/public/js/frappe/widgets/onboarding_widget.js @@ -25,7 +25,7 @@ export default class OnboardingWidget extends Widget { if (step.is_skipped) { status = "skipped"; - icon_class = "fa-times-circle-o"; + icon_class = "fa-check-circle-o"; } if (step.is_complete) { @@ -56,10 +56,17 @@ export default class OnboardingWidget extends Widget { // Setup actions let actions = { "Watch Video": () => this.show_video(step), - "Create Entry": () => this.show_quick_entry(step), + "Create Entry": () => { + if (step.show_full_form) { + this.create_entry(step); + } else { + this.show_quick_entry(step); + } + }, "Show Form Tour": () => this.show_form_tour(step), "Update Settings": () => this.update_settings(step), "View Report": () => this.open_report(step), + "Go to Page": () => this.go_to_page(step), }; $step.find("#title").on("click", actions[step.action]); @@ -68,6 +75,24 @@ export default class OnboardingWidget extends Widget { return $step; } + go_to_page(step) { + frappe.set_route(step.path).then(() => { + if (step.callback_message) { + let msg_dialog = frappe.msgprint({ + message: __(step.callback_message), + title: __(step.callback_title), + primary_action: { + action: () => { + msg_dialog.hide(); + }, + label: () => __("Continue"), + }, + wide: true, + }); + } + }); + } + open_report(step) { let route = generate_route({ name: step.reference_report, @@ -75,10 +100,9 @@ export default class OnboardingWidget extends Widget { is_query_report: ["Query Report", "Script Report"].includes( step.report_type ), - doctype: step.report_reference_doctype + doctype: step.report_reference_doctype, }); - let current_route = frappe.get_route(); frappe.set_route(route).then(() => { @@ -133,7 +157,7 @@ export default class OnboardingWidget extends Widget { msg_dialog.hide(); }, label: () => __("Continue"), - } + }, }); }); }; @@ -147,6 +171,7 @@ export default class OnboardingWidget extends Widget { frappe.route_hooks = {}; frappe.route_hooks.after_load = (frm) => { frm.scroll_to_field(step.field); + frm.doc.__unsaved = true; }; frappe.route_hooks.after_save = (frm) => { @@ -204,6 +229,33 @@ export default class OnboardingWidget extends Widget { frappe.set_route("Form", step.reference_document); } + create_entry(step) { + let current_route = frappe.get_route(); + + frappe.route_hooks = {}; + + frappe.route_hooks.after_save = () => { + frappe.msgprint({ + message: __("You're doing great, let's take you back to the onboarding page."), + title: __("Good Work 🎉"), + primary_action: { + action: () => { + frappe.set_route(current_route).then(() => { + this.mark_complete(step); + }); + }, + label: __("Continue"), + }, + }); + + frappe.msg_dialog.custom_onhide = () => { + this.mark_complete(step); + }; + }; + + frappe.set_route(`Form/${step.reference_document}/New ${step.reference_document} 1`); + } + show_quick_entry(step) { let current_route = frappe.get_route_str(); frappe.ui.form.make_quick_entry( @@ -221,7 +273,7 @@ export default class OnboardingWidget extends Widget { }); }, label: __("Continue"), - } + }, }); frappe.msg_dialog.custom_onhide = () => { @@ -271,7 +323,7 @@ export default class OnboardingWidget extends Widget { update_step_status(step, status, value, callback) { let icon_class = { is_complete: "fa-check-circle-o", - is_skipped: "fa-times-circle-o", + is_skipped: "fa-check-circle-o", }; frappe @@ -394,4 +446,4 @@ export default class OnboardingWidget extends Widget { }); dismiss.appendTo(this.action_area); } -} \ No newline at end of file +} From 79f41fd43f6e311dcddc617cac8d792f7380d01b Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 14 May 2020 17:05:44 +0530 Subject: [PATCH 068/107] fix: rename to __attachments --- .../package_detail/package_detail.json | 65 ------------------- .../__init__.py | 0 .../package_document_type.json | 65 +++++++++++++++++++ .../package_document_type.py} | 2 +- .../package_publish_tool.json | 4 +- .../package_publish_tool.py | 12 ++-- 6 files changed, 74 insertions(+), 74 deletions(-) delete mode 100644 frappe/custom/doctype/package_detail/package_detail.json rename frappe/custom/doctype/{package_detail => package_document_type}/__init__.py (100%) create mode 100644 frappe/custom/doctype/package_document_type/package_document_type.json rename frappe/custom/doctype/{package_detail/package_detail.py => package_document_type/package_document_type.py} (86%) diff --git a/frappe/custom/doctype/package_detail/package_detail.json b/frappe/custom/doctype/package_detail/package_detail.json deleted file mode 100644 index f749587a42..0000000000 --- a/frappe/custom/doctype/package_detail/package_detail.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "engine": "InnoDB", - "field_order": [ - "document_type", - "column_break_2", - "attachments", - "overwrite", - "section_break_4", - "filters_json" - ], - "istable": 1, - "modified_by": "Administrator", - "name": "Package Detail", - "links": [], - "fields": [ - { - "label": "Document Type", - "in_list_view": 1, - "fieldtype": "Link", - "reqd": 1, - "fieldname": "document_type", - "options": "DocType" - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "default": "0", - "in_list_view": 1, - "fieldname": "attachments", - "fieldtype": "Check", - "label": "Include Attachments" - }, - { - "default": "0", - "in_list_view": 1, - "fieldname": "overwrite", - "fieldtype": "Check", - "label": "Overwrite" - }, - { - "fieldname": "section_break_4", - "fieldtype": "Section Break" - }, - { - "options": "JSON", - "fieldname": "filters_json", - "fieldtype": "Code", - "label": "Filters" - } - ], - "track_changes": 1, - "creation": "2020-04-06 12:59:59.657816", - "doctype": "DocType", - "actions": [], - "modified": "2020-04-06 12:59:59.657816", - "sort_order": "DESC", - "module": "Custom", - "owner": "Administrator", - "sort_field": "modified", - "editable_grid": 1, - "quick_entry": 1, - "permissions": [] -} \ No newline at end of file diff --git a/frappe/custom/doctype/package_detail/__init__.py b/frappe/custom/doctype/package_document_type/__init__.py similarity index 100% rename from frappe/custom/doctype/package_detail/__init__.py rename to frappe/custom/doctype/package_document_type/__init__.py diff --git a/frappe/custom/doctype/package_document_type/package_document_type.json b/frappe/custom/doctype/package_document_type/package_document_type.json new file mode 100644 index 0000000000..6d011bd4e4 --- /dev/null +++ b/frappe/custom/doctype/package_document_type/package_document_type.json @@ -0,0 +1,65 @@ +{ + "actions": [], + "creation": "2020-05-14 16:45:47.196395", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "column_break_2", + "attachments", + "overwrite", + "section_break_4", + "filters_json" + ], + "fields": [ + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Document Type", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "attachments", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Include Attachments" + }, + { + "default": "0", + "fieldname": "overwrite", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Overwrite" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "filters_json", + "fieldtype": "Code", + "label": "Filters", + "options": "JSON" + } + ], + "istable": 1, + "links": [], + "modified": "2020-05-14 16:45:47.196395", + "modified_by": "Administrator", + "module": "Custom", + "name": "Package Document Type", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/package_detail/package_detail.py b/frappe/custom/doctype/package_document_type/package_document_type.py similarity index 86% rename from frappe/custom/doctype/package_detail/package_detail.py rename to frappe/custom/doctype/package_document_type/package_document_type.py index 0460329d85..6e166eecbd 100644 --- a/frappe/custom/doctype/package_detail/package_detail.py +++ b/frappe/custom/doctype/package_document_type/package_document_type.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class PackageDetail(Document): +class PackageDocumentType(Document): pass diff --git a/frappe/custom/doctype/package_publish_tool/package_publish_tool.json b/frappe/custom/doctype/package_publish_tool/package_publish_tool.json index 9b1db2c2ff..59d22f6ad4 100644 --- a/frappe/custom/doctype/package_publish_tool/package_publish_tool.json +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.json @@ -18,7 +18,7 @@ "fieldname": "package_details", "fieldtype": "Table", "label": "Package", - "options": "Package Detail", + "options": "Package Document Type", "reqd": 1 }, { @@ -50,7 +50,7 @@ ], "issingle": 1, "links": [], - "modified": "2020-05-14 16:06:19.714596", + "modified": "2020-05-14 16:46:20.374773", "modified_by": "Administrator", "module": "Custom", "name": "Package Publish Tool", diff --git a/frappe/custom/doctype/package_publish_tool/package_publish_tool.py b/frappe/custom/doctype/package_publish_tool/package_publish_tool.py index 09549107be..415839322c 100644 --- a/frappe/custom/doctype/package_publish_tool/package_publish_tool.py +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.py @@ -12,7 +12,7 @@ from frappe.utils.file_manager import save_file, get_file from frappe import _ from six import string_types from frappe.frappeclient import FrappeClient -from frappe.model.naming import make_autoname +from frappe.utils import get_datetime_str, get_datetime from frappe.utils.password import get_decrypted_password class PackagePublishTool(Document): @@ -22,7 +22,7 @@ class PackagePublishTool(Document): def deploy_package(): package, doc = export_package() - file_name = make_autoname("Package.####") + file_name = "Package-" + get_datetime_str(get_datetime()) save_file(file_name, json.dumps(package), "Package Publish Tool", "Package Publish Tool") length = len(doc.instances) @@ -90,8 +90,8 @@ def export_package(): }) document.update({ - "attachments": attachments, - "overwrite": True if doctype.overwrite else False + "__attachments": attachments, + "__overwrite": True if doctype.overwrite else False }) package.append(document) @@ -106,8 +106,8 @@ def import_package(package=None): for doc in package: modified = doc.pop("modified") - overwrite = doc.pop("overwrite") - attachments = doc.pop("attachments") + overwrite = doc.pop("__overwrite") + attachments = doc.pop("__attachments") exists = frappe.db.exists(doc.get("doctype"), doc.get("name")) if not exists: From 8df9cb6bede5733ba372f75e413cb89f76ea87cc Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 14 May 2020 17:09:27 +0530 Subject: [PATCH 069/107] chore: optimize imports, style fixes --- frappe/utils/remote_migrations.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frappe/utils/remote_migrations.py b/frappe/utils/remote_migrations.py index 656fb803de..90f8db0b74 100644 --- a/frappe/utils/remote_migrations.py +++ b/frappe/utils/remote_migrations.py @@ -1,5 +1,4 @@ # imports - standard imports -import functools import getpass import json import re @@ -78,7 +77,6 @@ def choose_plan(plans_list): @padme def check_app_compat(available_group): - frappe_upgrade_msg = "" is_compat = True incompatible_apps, filtered_apps, branch_msgs = [], [], [] existing_group = [(app["app_name"], app["branch"]) for app in get_installed_apps_info()] @@ -135,7 +133,7 @@ def filter_apps(app_groups): app_group_index = click.prompt("Select App Group #", type=int) - 1 try: selected_group = app_groups[app_group_index] - except: + except IndexError: print("Invalid Selection ❌") break @@ -169,7 +167,7 @@ def create_session(): @padme def get_subdomain(domain): while True: - subdomain = click.prompt("Enter subdomain: ").strip() + subdomain = click.prompt("Enter subdomain").strip() if is_valid_subdomain(subdomain) and is_subdomain_available(subdomain): print("Site Domain: {}.{}".format(subdomain, domain)) return subdomain From 30a4fd7ac0a4a30dc33baee8745f0d89986ba43b Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 14 May 2020 17:16:37 +0530 Subject: [PATCH 070/107] fix: allow validations for all fieldtypes --- frappe/public/js/frappe/form/controls/data.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index 25f3ef76af..c943ec89bb 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -87,9 +87,6 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ return val==null ? "" : val; }, validate: function(v) { - if (this.df.fieldtype !== "Data") { - return v; - } if (!v) { return ''; } From e5a2171a66f19e1900d5316f2a1c7e17cc6f54b8 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 14 May 2020 17:18:05 +0530 Subject: [PATCH 071/107] fix: set query report object for chart filters --- .../doctype/dashboard_chart/dashboard_chart.js | 14 +++----------- frappe/public/js/frappe/widgets/chart_widget.js | 12 +++--------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index f8d5886b26..fb6f9252ce 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -283,17 +283,7 @@ frappe.ui.form.on('Dashboard Chart', { }); } } else if (frm.chart_filters.length) { - fields = frm.chart_filters.filter(f => { - if (f.on_change && !f.reqd) { - return false; - } - if (f.get_query || f.get_data) { - f.read_only = 1; - } - - return f.fieldname; - }); - + fields = frm.chart_filters.filter(f => f.fieldname); fields.map( f => { if (filters[f.fieldname]) { let condition = '='; @@ -353,6 +343,8 @@ frappe.ui.form.on('Dashboard Chart', { } dialog.show(); + //Set query report object so that it can be used while fetching filter values in the report + frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list}); dialog.set_values(filters); }); }, diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index a50acfcd9d..b4c622f151 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -332,15 +332,7 @@ export default class ChartWidget extends Widget { } ]; } else { - fields = filters.filter(f => { - if (f.on_change && !f.reqd) { - return false; - } - if (f.get_query || f.get_data) { - f.read_only = 1; - } - return f.fieldname; - }); + fields = filters.filter(f => f.fieldname); } } else { fields = [ @@ -384,6 +376,8 @@ export default class ChartWidget extends Widget { } dialog.show(); + //Set query report object so that it can be used while fetching filter values in the report + frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list}); dialog.set_values(this.filters); } From 12ca630c522f668e92995a4451b177f83a42d1a8 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 14 May 2020 17:22:51 +0530 Subject: [PATCH 072/107] fix: merge code --- frappe/model/meta.py | 89 +++++++++++++++----------------------------- 1 file changed, 31 insertions(+), 58 deletions(-) diff --git a/frappe/model/meta.py b/frappe/model/meta.py index a9ae19af6d..c8fd1a2ac2 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -434,7 +434,6 @@ class Meta(Document): pass self.add_doctype_links(data) - get_custom_doctype_links(self.name, data) if not self.custom: for hook in frappe.get_hooks("override_doctype_dashboards", {}).get(self.name, []): @@ -444,34 +443,41 @@ class Meta(Document): def add_doctype_links(self, data): '''add `links` child table in standard link dashboard format''' + dashboard_links = [] + if hasattr(self, 'links') and self.links: - if not data.transactions: - # init groups - data.transactions = [] - data.non_standard_fieldnames = {} + dashboard_links.extend(self.links) - for link in self.links: - link.added = False - for group in data.transactions: - group = frappe._dict(group) - # group found - if link.group and group.label == link.group: - if link.link_doctype not in group.get('items'): - group.get('items').append(link.link_doctype) - link.added = True + if frappe.get_all("Custom Link", {"document_type": self.name}): + dashboard_links.extend(frappe.get_doc("Custom Link", self.name).links) - if not link.added: - # group not found, make a new group - data.transactions.append(dict( - label = link.group, - items = [link.link_doctype] - )) + if not data.transactions: + # init groups + data.transactions = [] + data.non_standard_fieldnames = {} - if link.link_fieldname != data.fieldname: - if data.fieldname: - data.non_standard_fieldnames[link.link_doctype] = link.link_fieldname - else: - data.fieldname = link.link_fieldname + for link in dashboard_links: + link.added = False + for group in data.transactions: + group = frappe._dict(group) + # group found + if link.group and group.label == link.group: + if link.link_doctype not in group.get('items'): + group.get('items').append(link.link_doctype) + link.added = True + + if not link.added: + # group not found, make a new group + data.transactions.append(dict( + label = link.group, + items = [link.link_doctype] + )) + + if link.link_fieldname != data.fieldname: + if data.fieldname: + data.non_standard_fieldnames[link.link_doctype] = link.link_fieldname + else: + data.fieldname = link.link_fieldname def get_row_template(self): @@ -608,36 +614,3 @@ def trim_tables(doctype=None): query = """alter table `tab{doctype}` {columns}""".format( doctype=doctype, columns=columns_to_remove) frappe.db.sql_ddl(query) - -def get_custom_doctype_links(doctype, data): - if frappe.get_all("Custom Link", {"document_type": doctype}): - doc = frappe.get_doc("Custom Link", doctype) - - if not data.transactions: - # init groups - data.transactions = [] - data.non_standard_fieldnames = {} - - for link in doc.links: - link.added = False - for group in data.transactions: - # group found - if group.get("label") == link.group: - if link.link_doctype not in group.get("items"): - group.get("items").append(link.link_doctype) - link.added = True - - if not link.added: - # group not found, make a new group - data.transactions.append({ - "label": link.group, - "items": [link.link_doctype] - }) - - if link.link_fieldname != data.fieldname: - if data.fieldname: - data.non_standard_fieldnames[link.link_doctype] = link.link_fieldname - else: - data.fieldname = link.link_fieldname - - return data \ No newline at end of file From f4b9f8a31453c02733d2ee82a5db5f63c3014fbd Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 May 2020 19:27:09 +0530 Subject: [PATCH 073/107] feat: trigger after insert for submittable quick entry --- frappe/public/js/frappe/form/quick_entry.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js index 9996389a4e..dbb3792262 100644 --- a/frappe/public/js/frappe/form/quick_entry.js +++ b/frappe/public/js/frappe/form/quick_entry.js @@ -213,8 +213,15 @@ frappe.ui.form.QuickEntryForm = Class.extend({ me.dialog.doc = r.message; if (frappe._from_link) { frappe.ui.form.update_calling_link(me.dialog.doc); + } else { + if(me.after_insert) { + me.after_insert(me.dialog.doc); + } else { + me.open_form_if_not_list(); + } } - cur_frm.reload_doc(); + + cur_frm && cur_frm.reload_doc(); } }); }, From 657d001eefda12759f075d416044cf39c8609ac5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 May 2020 19:28:08 +0530 Subject: [PATCH 074/107] feat: don't show full link option if force is set in quick entry --- frappe/public/js/frappe/form/quick_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js index dbb3792262..5b427feb1c 100644 --- a/frappe/public/js/frappe/form/quick_entry.js +++ b/frappe/public/js/frappe/form/quick_entry.js @@ -107,7 +107,7 @@ frappe.ui.form.QuickEntryForm = Class.extend({ }); this.register_primary_action(); - this.render_edit_in_full_page_link(); + !this.force && this.render_edit_in_full_page_link(); // ctrl+enter to save this.dialog.wrapper.keydown(function(e) { if((e.ctrlKey || e.metaKey) && e.which==13) { From dae9eb5a2a5a24fd2b63079a7052a21a66986065 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 14 May 2020 19:32:01 +0530 Subject: [PATCH 075/107] fix: don't get filters if string type --- frappe/desk/notifications.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index 109dd25f4f..4a1302788b 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -212,7 +212,10 @@ def get_notification_config(): def get_filters_for(doctype): '''get open filters for doctype''' config = get_notification_config() - return config.get("for_doctype").get(doctype, {}) + doctype_config = config.get("for_doctype").get(doctype, {}) + filters = doctype_config if not isinstance(doctype_config, string_types) else None + + return filters @frappe.whitelist() @frappe.read_only() From 21d7926d60288a3fac6f46b5da9a2cba1548667d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 May 2020 19:41:55 +0530 Subject: [PATCH 076/107] fix: linting --- frappe/public/js/frappe/form/quick_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js index 5b427feb1c..68444c8a3b 100644 --- a/frappe/public/js/frappe/form/quick_entry.js +++ b/frappe/public/js/frappe/form/quick_entry.js @@ -214,7 +214,7 @@ frappe.ui.form.QuickEntryForm = Class.extend({ if (frappe._from_link) { frappe.ui.form.update_calling_link(me.dialog.doc); } else { - if(me.after_insert) { + if (me.after_insert) { me.after_insert(me.dialog.doc); } else { me.open_form_if_not_list(); From 76c8e62e354f30a7e21ecbe7d86831d3ac047543 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 May 2020 19:51:58 +0530 Subject: [PATCH 077/107] feat: store if document is submitable --- frappe/desk/desktop.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index e2fe020eca..06aff0b714 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -274,6 +274,8 @@ class Workspace: for doc in self.onboarding_doc.get_steps(): step = doc.as_dict().copy() step.label = _(doc.title) + if step.action == "Create Entry": + step.is_submittable = frappe.db.get_value("DocType", step.reference_document, 'is_submittable') steps.append(step) return steps @@ -516,4 +518,5 @@ def update_onboarding_step(name, field, value): value: Value to be updated """ + return frappe.db.set_value("Onboarding Step", name, field, value) From 5fe6ef8c4a91915a30862d3f9d68fac67a8e47f9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 May 2020 19:52:08 +0530 Subject: [PATCH 078/107] feat: added after submit hooks --- frappe/public/js/frappe/form/form.js | 6 ++++++ .../js/frappe/widgets/onboarding_widget.js | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 01dfbf81f9..bad7c877fc 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -651,6 +651,12 @@ frappe.ui.form.Form = class FrappeForm { callback && callback(); me.script_manager.trigger("on_submit") .then(() => resolve(me)); + if (frappe.route_hooks.after_submit) { + let route_callback = frappe.route_hooks.after_submit; + delete frappe.route_hooks.after_submit; + + route_callback(me); + } } }, btn, () => me.handle_save_fail(btn, on_error), resolve); }); diff --git a/frappe/public/js/frappe/widgets/onboarding_widget.js b/frappe/public/js/frappe/widgets/onboarding_widget.js index e869b281ae..821824a2d2 100644 --- a/frappe/public/js/frappe/widgets/onboarding_widget.js +++ b/frappe/public/js/frappe/widgets/onboarding_widget.js @@ -233,8 +233,7 @@ export default class OnboardingWidget extends Widget { let current_route = frappe.get_route(); frappe.route_hooks = {}; - - frappe.route_hooks.after_save = () => { + let callback = () => { frappe.msgprint({ message: __("You're doing great, let's take you back to the onboarding page."), title: __("Good Work 🎉"), @@ -253,6 +252,18 @@ export default class OnboardingWidget extends Widget { }; }; + if (step.is_submittable) { + frappe.route_hooks.after_save = () => { + frappe.msgprint({ + message: __("Submit this document to complete this step."), + title: __("Great") + }); + }; + frappe.route_hooks.after_submit = callback; + } else { + frappe.route_hooks.after_save = callback; + } + frappe.set_route(`Form/${step.reference_document}/New ${step.reference_document} 1`); } @@ -325,6 +336,8 @@ export default class OnboardingWidget extends Widget { is_complete: "fa-check-circle-o", is_skipped: "fa-check-circle-o", }; + // Clear any hooks + frappe.route_hooks = {}; frappe .call("frappe.desk.desktop.update_onboarding_step", { From 0091f514b5c984c71467f7093b86b09bacf253d5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 May 2020 20:00:34 +0530 Subject: [PATCH 079/107] fix: remove return --- frappe/desk/desktop.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 06aff0b714..29f9aea4be 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -518,5 +518,4 @@ def update_onboarding_step(name, field, value): value: Value to be updated """ - return frappe.db.set_value("Onboarding Step", name, field, value) From ba2da078af338530543a29d070e4ac2b24c19ff7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 May 2020 20:00:43 +0530 Subject: [PATCH 080/107] feat: use cached db value --- frappe/desk/desktop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 29f9aea4be..512b3f2890 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -275,7 +275,7 @@ class Workspace: step = doc.as_dict().copy() step.label = _(doc.title) if step.action == "Create Entry": - step.is_submittable = frappe.db.get_value("DocType", step.reference_document, 'is_submittable') + step.is_submittable = frappe.db.get_value("DocType", step.reference_document, 'is_submittable', cache=True) steps.append(step) return steps From 0f94edd87cceaa741cd400a078b0f83d361e69f0 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 May 2020 21:31:02 +0530 Subject: [PATCH 081/107] feat: allow overriding filters in dashboards --- frappe/public/js/frappe/widgets/chart_widget.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 35ddaab4cf..c42e6053eb 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -361,7 +361,12 @@ export default class ChartWidget extends Widget { } ]; } else { - fields = filters.filter(f => f.fieldname); + fields = filters + .filter(df => df.fieldname) + .map(df => { + Object.assign(df, df.dashboard_config || {}); + return df; + }); } } else { fields = [ From bc8620d42038d1974e9c8e286395d2cc7522662a Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 28 Apr 2020 16:46:22 +0530 Subject: [PATCH 082/107] feat: add default custom options to dashboard --- frappe/desk/doctype/dashboard/dashboard.json | 10 +++- frappe/desk/doctype/dashboard/dashboard.py | 8 ++- .../dashboard_chart/dashboard_chart.json | 2 +- .../public/js/frappe/widgets/chart_widget.js | 53 +++++++++++++++++++ 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/dashboard/dashboard.json b/frappe/desk/doctype/dashboard/dashboard.json index c17bc3235c..a3a6d492f1 100644 --- a/frappe/desk/doctype/dashboard/dashboard.json +++ b/frappe/desk/doctype/dashboard/dashboard.json @@ -9,6 +9,7 @@ "dashboard_name", "is_default", "charts", + "default_chart_custom_options", "cards" ], "fields": [ @@ -33,6 +34,13 @@ "options": "Dashboard Chart Link", "reqd": 1 }, + { + "description": "Set Default Options for all charts on this Dashboard (Ex: \"colors\": [\"#d1d8dd\", \"#ff5858\"])", + "fieldname": "default_chart_custom_options", + "fieldtype": "Code", + "label": "Default Chart Custom Options", + "options": "JSON" + }, { "fieldname": "cards", "fieldtype": "Table", @@ -41,7 +49,7 @@ } ], "links": [], - "modified": "2020-04-19 17:44:36.237163", + "modified": "2020-04-28 14:17:02.391084", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard", diff --git a/frappe/desk/doctype/dashboard/dashboard.py b/frappe/desk/doctype/dashboard/dashboard.py index b85e135071..952db21042 100644 --- a/frappe/desk/doctype/dashboard/dashboard.py +++ b/frappe/desk/doctype/dashboard/dashboard.py @@ -19,7 +19,13 @@ def get_permitted_charts(dashboard_name): dashboard = frappe.get_doc('Dashboard', dashboard_name) for chart in dashboard.charts: if frappe.has_permission('Dashboard Chart', doc=chart.chart): - permitted_charts.append(chart) + chart_dict = frappe._dict() + chart_dict.update(chart.as_dict()) + + if dashboard.get('default_chart_custom_options'): + chart_dict.custom_options = dashboard.get('default_chart_custom_options') + permitted_charts.append(chart_dict) + return permitted_charts @frappe.whitelist() diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index e377ef740e..472d02a75e 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -219,7 +219,7 @@ "options": "Dashboard Chart Field" }, { - "description": "Ex: \"colors\": [\"#d1d8dd\", \"#ff5858\"]", + "description": " Ex: \"colors\": [\"#d1d8dd\", \"#ff5858\"] (the options set here will override the default custom options set in the Dashboard)", "fieldname": "custom_options", "fieldtype": "Code", "label": "Custom Options" diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index c42e6053eb..a0211284bf 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -593,6 +593,59 @@ export default class ChartWidget extends Widget { } } + get_chart_args() { + let colors = this.get_chart_colors(); + + const chart_type_map = { + Line: "line", + Bar: "bar", + Percentage: "percentage", + Pie: "pie", + Donut: "donut" + }; + + let chart_args = { + data: this.data, + type: chart_type_map[this.chart_doc.type], + colors: colors, + height: this.height, + axisOptions: { + xIsSeries: this.chart_doc.timeseries, + shortenYAxisNumbers: 1 + } + }; + + let set_options = (options) => { + let custom_options = JSON.parse(options); + for (let key in custom_options) { + chart_args[key] = custom_options[key]; + } + }; + + if (this.custom_options) { + set_options(this.custom_options); + } + + if (this.chart_doc.custom_options) { + set_options(this.chart_doc.custom_options); + } + + return chart_args; + } + + get_chart_colors() { + let colors = []; + if (this.chart_doc.y_axis.length) { + this.chart_doc.y_axis.map(field => { + colors.push(field.color); + }); + } else if (["Line", "Bar"].includes(this.chart_doc.type)) { + colors = [this.chart_doc.color || "light-blue"]; + } + + return colors; + } + update_last_synced() { let last_synced_text = __("Last synced {0}", [ comment_when(this.chart_doc.last_synced_on) From 08f740acf8cb3fc5704fc4be1e971fcba0f11fbd Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 29 Apr 2020 13:30:05 +0530 Subject: [PATCH 083/107] fix: better naming --- frappe/desk/doctype/dashboard/dashboard.json | 6 +++++- frappe/desk/doctype/dashboard/dashboard.py | 4 ++-- frappe/desk/doctype/dashboard_chart/dashboard_chart.json | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/frappe/desk/doctype/dashboard/dashboard.json b/frappe/desk/doctype/dashboard/dashboard.json index a3a6d492f1..e17fb66436 100644 --- a/frappe/desk/doctype/dashboard/dashboard.json +++ b/frappe/desk/doctype/dashboard/dashboard.json @@ -9,8 +9,12 @@ "dashboard_name", "is_default", "charts", +<<<<<<< HEAD "default_chart_custom_options", "cards" +======= + "chart_options" +>>>>>>> fix: better naming ], "fields": [ { @@ -49,7 +53,7 @@ } ], "links": [], - "modified": "2020-04-28 14:17:02.391084", + "modified": "2020-04-29 13:26:37.362482", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard", diff --git a/frappe/desk/doctype/dashboard/dashboard.py b/frappe/desk/doctype/dashboard/dashboard.py index 952db21042..9368315343 100644 --- a/frappe/desk/doctype/dashboard/dashboard.py +++ b/frappe/desk/doctype/dashboard/dashboard.py @@ -22,8 +22,8 @@ def get_permitted_charts(dashboard_name): chart_dict = frappe._dict() chart_dict.update(chart.as_dict()) - if dashboard.get('default_chart_custom_options'): - chart_dict.custom_options = dashboard.get('default_chart_custom_options') + if dashboard.get('chart_options'): + chart_dict.custom_options = dashboard.get('chart_options') permitted_charts.append(chart_dict) return permitted_charts diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index 472d02a75e..3332534de6 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -219,7 +219,7 @@ "options": "Dashboard Chart Field" }, { - "description": " Ex: \"colors\": [\"#d1d8dd\", \"#ff5858\"] (the options set here will override the default custom options set in the Dashboard)", + "description": " Ex: \"colors\": [\"#d1d8dd\", \"#ff5858\"] (the options set here will override the chart options set in the Dashboard)", "fieldname": "custom_options", "fieldtype": "Code", "label": "Custom Options" From d5364f759bb47139b35adc714b15abb497d69f40 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 29 Apr 2020 18:47:14 +0530 Subject: [PATCH 084/107] fix: validate chart options json --- frappe/desk/doctype/dashboard/dashboard.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frappe/desk/doctype/dashboard/dashboard.py b/frappe/desk/doctype/dashboard/dashboard.py index 9368315343..3d86a1e48a 100644 --- a/frappe/desk/doctype/dashboard/dashboard.py +++ b/frappe/desk/doctype/dashboard/dashboard.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals from frappe.model.document import Document import frappe +import json class Dashboard(Document): def on_update(self): @@ -13,6 +14,16 @@ class Dashboard(Document): frappe.db.sql('''update tabDashboard set is_default = 0 where name != %s''', self.name) + def validate(self): + self.validate_custom_options() + + def validate_custom_options(self): + if self.chart_options: + try: + json.loads(self.chart_options) + except ValueError as error: + frappe.throw("Invalid json added in the custom options: %s" % error) + @frappe.whitelist() def get_permitted_charts(dashboard_name): permitted_charts = [] From e34e8715d360195c7e3356f57b916e5a584447d2 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 5 May 2020 12:17:04 +0530 Subject: [PATCH 085/107] fix: translate messages --- frappe/desk/doctype/dashboard/dashboard.py | 2 +- frappe/desk/doctype/dashboard_chart/dashboard_chart.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/dashboard/dashboard.py b/frappe/desk/doctype/dashboard/dashboard.py index 3d86a1e48a..daf065949c 100644 --- a/frappe/desk/doctype/dashboard/dashboard.py +++ b/frappe/desk/doctype/dashboard/dashboard.py @@ -22,7 +22,7 @@ class Dashboard(Document): try: json.loads(self.chart_options) except ValueError as error: - frappe.throw("Invalid json added in the custom options: %s" % error) + frappe.throw(_("Invalid json added in the custom options: %s" % error)) @frappe.whitelist() def get_permitted_charts(dashboard_name): diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index e7e4aefe09..4044a66638 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -435,11 +435,11 @@ class DashboardChart(Document): def check_document_type(self): if frappe.get_meta(self.document_type).issingle: - frappe.throw("You cannot create a dashboard chart from single DocTypes") + frappe.throw(_("You cannot create a dashboard chart from single DocTypes")) def validate_custom_options(self): if self.custom_options: try: json.loads(self.custom_options) except ValueError as error: - frappe.throw("Invalid json added in the custom options: %s" % error) \ No newline at end of file + frappe.throw(_("Invalid json added in the custom options: %s" % error)) \ No newline at end of file From 7425e5cc6c2f2e5798d4edd144b043964f852e06 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 5 May 2020 12:29:21 +0530 Subject: [PATCH 086/107] fix: invalid json --- frappe/desk/doctype/dashboard/dashboard.json | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/frappe/desk/doctype/dashboard/dashboard.json b/frappe/desk/doctype/dashboard/dashboard.json index e17fb66436..c0e2bddcf8 100644 --- a/frappe/desk/doctype/dashboard/dashboard.json +++ b/frappe/desk/doctype/dashboard/dashboard.json @@ -9,12 +9,8 @@ "dashboard_name", "is_default", "charts", -<<<<<<< HEAD - "default_chart_custom_options", + "chart_options", "cards" -======= - "chart_options" ->>>>>>> fix: better naming ], "fields": [ { @@ -40,9 +36,9 @@ }, { "description": "Set Default Options for all charts on this Dashboard (Ex: \"colors\": [\"#d1d8dd\", \"#ff5858\"])", - "fieldname": "default_chart_custom_options", + "fieldname": "chart_options", "fieldtype": "Code", - "label": "Default Chart Custom Options", + "label": "Chart Options", "options": "JSON" }, { From 58d75ad5d7f40ba71f3ba40685d03354321ef2ed Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 5 May 2020 12:40:15 +0530 Subject: [PATCH 087/107] fix: import _ --- frappe/desk/doctype/dashboard/dashboard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/desk/doctype/dashboard/dashboard.py b/frappe/desk/doctype/dashboard/dashboard.py index daf065949c..621ab19ecd 100644 --- a/frappe/desk/doctype/dashboard/dashboard.py +++ b/frappe/desk/doctype/dashboard/dashboard.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals from frappe.model.document import Document import frappe +from frappe import _ import json class Dashboard(Document): From 3a090e71382c19cda42ca3bce5b7c6bb5dd4fad4 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 8 May 2020 16:18:49 +0530 Subject: [PATCH 088/107] fix: translation --- frappe/desk/doctype/dashboard/dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/dashboard/dashboard.py b/frappe/desk/doctype/dashboard/dashboard.py index 621ab19ecd..af0c48d9c6 100644 --- a/frappe/desk/doctype/dashboard/dashboard.py +++ b/frappe/desk/doctype/dashboard/dashboard.py @@ -23,7 +23,7 @@ class Dashboard(Document): try: json.loads(self.chart_options) except ValueError as error: - frappe.throw(_("Invalid json added in the custom options: %s" % error)) + frappe.throw(_("Invalid json added in the custom options: {0}").format(error)) @frappe.whitelist() def get_permitted_charts(dashboard_name): From ee292ed2d0b5f51791fddb98c6994304c86833e8 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 8 May 2020 16:20:35 +0530 Subject: [PATCH 089/107] style: remove leading space --- frappe/desk/doctype/dashboard_chart/dashboard_chart.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index 3332534de6..72f5c43316 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -219,7 +219,7 @@ "options": "Dashboard Chart Field" }, { - "description": " Ex: \"colors\": [\"#d1d8dd\", \"#ff5858\"] (the options set here will override the chart options set in the Dashboard)", + "description": "Ex: \"colors\": [\"#d1d8dd\", \"#ff5858\"] (the options set here will override the chart options set in the Dashboard)", "fieldname": "custom_options", "fieldtype": "Code", "label": "Custom Options" @@ -283,4 +283,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From 0bba2e8c28b0d434bc09a3e0fb6d6ecff42c4e5d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 8 May 2020 16:20:52 +0530 Subject: [PATCH 090/107] fix: minor translation issue --- frappe/desk/doctype/dashboard_chart/dashboard_chart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 4044a66638..7e375e835f 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -442,4 +442,4 @@ class DashboardChart(Document): try: json.loads(self.custom_options) except ValueError as error: - frappe.throw(_("Invalid json added in the custom options: %s" % error)) \ No newline at end of file + frappe.throw(_("Invalid json added in the custom options: {0}").format(error)) From 9c412d60df01b5f0c555f102b950092a669ba14e Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 15 May 2020 00:40:01 +0530 Subject: [PATCH 091/107] fix: fix rebase --- .../public/js/frappe/widgets/chart_widget.js | 69 ++++--------------- 1 file changed, 15 insertions(+), 54 deletions(-) diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index a0211284bf..e5378cf2ab 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -523,7 +523,6 @@ export default class ChartWidget extends Widget { } } - get_chart_args() { let colors = this.get_chart_colors(); @@ -553,6 +552,21 @@ export default class ChartWidget extends Widget { chart_args.data.end = new Date(`${heatmap_year+1}-01-01`); } + let set_options = (options) => { + let custom_options = JSON.parse(options); + for (let key in custom_options) { + chart_args[key] = custom_options[key]; + } + }; + + if (this.custom_options) { + set_options(this.custom_options); + } + + if (this.chart_doc.custom_options) { + set_options(this.chart_doc.custom_options); + } + return chart_args; } @@ -593,59 +607,6 @@ export default class ChartWidget extends Widget { } } - get_chart_args() { - let colors = this.get_chart_colors(); - - const chart_type_map = { - Line: "line", - Bar: "bar", - Percentage: "percentage", - Pie: "pie", - Donut: "donut" - }; - - let chart_args = { - data: this.data, - type: chart_type_map[this.chart_doc.type], - colors: colors, - height: this.height, - axisOptions: { - xIsSeries: this.chart_doc.timeseries, - shortenYAxisNumbers: 1 - } - }; - - let set_options = (options) => { - let custom_options = JSON.parse(options); - for (let key in custom_options) { - chart_args[key] = custom_options[key]; - } - }; - - if (this.custom_options) { - set_options(this.custom_options); - } - - if (this.chart_doc.custom_options) { - set_options(this.chart_doc.custom_options); - } - - return chart_args; - } - - get_chart_colors() { - let colors = []; - if (this.chart_doc.y_axis.length) { - this.chart_doc.y_axis.map(field => { - colors.push(field.color); - }); - } else if (["Line", "Bar"].includes(this.chart_doc.type)) { - colors = [this.chart_doc.color || "light-blue"]; - } - - return colors; - } - update_last_synced() { let last_synced_text = __("Last synced {0}", [ comment_when(this.chart_doc.last_synced_on) From 76db5e11c8cb8ee5f12e71e46c29e6c78d5d71bd Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 27 Apr 2020 16:51:31 +0530 Subject: [PATCH 092/107] feat: Section Break without border --- frappe/core/doctype/docfield/docfield.json | 10 +++++++++- .../custom/doctype/custom_field/custom_field.json | 10 +++++++++- .../doctype/customize_form/customize_form.py | 3 ++- .../customize_form_field/customize_form_field.json | 10 +++++++++- frappe/database/mariadb/framework_mariadb.sql | 1 + frappe/database/postgres/framework_postgres.sql | 1 + frappe/public/js/frappe/form/layout.js | 14 ++++++++++++++ frappe/public/less/form.less | 11 ++++++++++- 8 files changed, 55 insertions(+), 5 deletions(-) diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 6d8ee41a5a..8e7516cd0a 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -43,6 +43,7 @@ "report_hide", "remember_last_selected_value", "ignore_xss_filter", + "hide_border", "property_depends_on_section", "mandatory_depends_on", "column_break_38", @@ -448,12 +449,19 @@ { "fieldname": "column_break_38", "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype=='Section Break'", + "fieldname": "hide_border", + "fieldtype": "Check", + "label": "Hide Border" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-19 21:54:13.783908", + "modified": "2020-04-27 11:38:21.223185", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 394f38b56c..122e6c7070 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -48,6 +48,7 @@ "allow_in_quick_entry", "ignore_xss_filter", "translatable", + "hide_border", "description", "permlevel", "width", @@ -378,12 +379,19 @@ "fieldname": "in_preview", "fieldtype": "Check", "label": "In Preview" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype=='Section Break'", + "fieldname": "hide_border", + "fieldtype": "Check", + "label": "Hide Border" } ], "icon": "fa fa-glass", "idx": 1, "links": [], - "modified": "2020-04-10 11:57:10.392218", + "modified": "2020-04-27 11:40:48.325481", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index ebf01d11b3..6a54d9c7e6 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -76,7 +76,8 @@ docfield_properties = { 'remember_last_selected_value': 'Check', 'allow_bulk_edit': 'Check', 'auto_repeat': 'Link', - 'allow_in_quick_entry': 'Check' + 'allow_in_quick_entry': 'Check', + 'hide_border': 'Check' } allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'), diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index d7887cf8bd..2c5fb874f7 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -39,6 +39,7 @@ "allow_on_submit", "report_hide", "remember_last_selected_value", + "hide_border", "property_depends_on_section", "mandatory_depends_on", "column_break_33", @@ -388,12 +389,19 @@ "fieldname": "in_preview", "fieldtype": "Check", "label": "In Preview" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype=='Section Break'", + "fieldname": "hide_border", + "fieldtype": "Check", + "label": "Hide Border" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-10 11:58:44.573537", + "modified": "2020-04-27 11:39:26.389300", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", diff --git a/frappe/database/mariadb/framework_mariadb.sql b/frappe/database/mariadb/framework_mariadb.sql index 46940cc846..bd93069a3f 100644 --- a/frappe/database/mariadb/framework_mariadb.sql +++ b/frappe/database/mariadb/framework_mariadb.sql @@ -63,6 +63,7 @@ CREATE TABLE `tabDocField` ( `precision` varchar(255) DEFAULT NULL, `length` int(11) NOT NULL DEFAULT 0, `translatable` int(1) NOT NULL DEFAULT 0, + `hide_border` int(1) NOT NULL DEFAULT 0, PRIMARY KEY (`name`), KEY `parent` (`parent`), KEY `label` (`label`), diff --git a/frappe/database/postgres/framework_postgres.sql b/frappe/database/postgres/framework_postgres.sql index 26760dbcc9..76309e7347 100644 --- a/frappe/database/postgres/framework_postgres.sql +++ b/frappe/database/postgres/framework_postgres.sql @@ -63,6 +63,7 @@ CREATE TABLE "tabDocField" ( "precision" varchar(255) DEFAULT NULL, "length" bigint NOT NULL DEFAULT 0, "translatable" smallint NOT NULL DEFAULT 0, + "hide_border" smallint NOT NULL DEFAULT 0, PRIMARY KEY ("name") ) ; diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 5aeb29b1ed..26131fc9c7 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -236,6 +236,7 @@ frappe.ui.form.Layout = Class.extend({ // collapse sections if(this.frm) { this.refresh_section_collapse(); + this.refresh_section_border(); } }, @@ -307,6 +308,16 @@ frappe.ui.form.Layout = Class.extend({ } }, + refresh_section_border: function() { + if(!this.doc) return; + this.sections.forEach(section => { + const df = section.df; + if (df && cint(df.hide_border)) { + section.hide_border(true); + } + }) + }, + attach_doc_and_docfields: function(refresh) { var me = this; for(var i=0, l=this.fields_list.length; i Date: Mon, 11 May 2020 17:29:24 +0530 Subject: [PATCH 093/107] fix: linting issue --- frappe/public/js/frappe/form/layout.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 26131fc9c7..c8d5c9b422 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -309,13 +309,13 @@ frappe.ui.form.Layout = Class.extend({ }, refresh_section_border: function() { - if(!this.doc) return; + if (!this.doc) return; this.sections.forEach(section => { const df = section.df; if (df && cint(df.hide_border)) { section.hide_border(true); } - }) + }); }, attach_doc_and_docfields: function(refresh) { From 30c22ba3e576770bea3e28287e912637d4720918 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 15 May 2020 10:11:42 +0530 Subject: [PATCH 094/107] fix: hide section border on make, instead of refresh --- frappe/public/js/frappe/form/layout.js | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index c8d5c9b422..d6106255a0 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -236,7 +236,6 @@ frappe.ui.form.Layout = Class.extend({ // collapse sections if(this.frm) { this.refresh_section_collapse(); - this.refresh_section_border(); } }, @@ -308,16 +307,6 @@ frappe.ui.form.Layout = Class.extend({ } }, - refresh_section_border: function() { - if (!this.doc) return; - this.sections.forEach(section => { - const df = section.df; - if (df && cint(df.hide_border)) { - section.hide_border(true); - } - }); - }, - attach_doc_and_docfields: function(refresh) { var me = this; for(var i=0, l=this.fields_list.length; i').appendTo(this.wrapper); }, + make_head: function() { var me = this; if(!this.df.collapsible) { @@ -674,12 +666,11 @@ frappe.ui.form.Section = Class.extend({ } }); }, - hide_border(hide) { - this.body.parent().toggleClass("hide-border", hide); - }, + is_collapsed() { return this.body.hasClass('hide'); }, + has_missing_mandatory: function() { var missing_mandatory = false; for (var j=0, l=this.fields_list.length; j < l; j++) { From 85d86fbedfd0707e9d9493470349e884b8fe74ac Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 15 May 2020 11:21:24 +0530 Subject: [PATCH 095/107] refactor: moved remote_migrations to frappe.integrations.frappe_providers fix: exit code fixes and minor bug fix --- frappe/commands/site.py | 8 +++---- .../integrations/frappe_providers/__init__.py | 11 +++++++++ .../frappe_providers/frappecloud.py} | 23 ++++++++----------- 3 files changed, 24 insertions(+), 18 deletions(-) create mode 100644 frappe/integrations/frappe_providers/__init__.py rename frappe/{utils/remote_migrations.py => integrations/frappe_providers/frappecloud.py} (95%) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 10bf700340..1f22650930 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -257,12 +257,12 @@ def migrate(context, rebuild_website=False, skip_failing=False): compileall.compile_dir('../apps', quiet=1, rx=re.compile('.*node_modules.*')) @click.command('migrate-to') -@click.argument('site_url') +@click.argument('frappe_provider') @pass_context -def migrate_to(context, site_url): - from frappe.utils.remote_migrations import migrate_to +def migrate_to(context, frappe_provider): + from frappe.integrations.frappe_providers import migrate_to for site in context.sites: - migrate_to(site, site_url) + migrate_to(site, frappe_provider) @click.command('run-patch') @click.argument('module') diff --git a/frappe/integrations/frappe_providers/__init__.py b/frappe/integrations/frappe_providers/__init__.py new file mode 100644 index 0000000000..31a0cf1c04 --- /dev/null +++ b/frappe/integrations/frappe_providers/__init__.py @@ -0,0 +1,11 @@ +# imports - module imports +from frappe.integrations.frappe_providers.frappecloud import frappecloud_migrator + + +def migrate_to(local_site, frappe_provider): + if frappe_provider in ("frappe.cloud", "frappecloud.com"): + frappe_provider = "staging.frappe.cloud" + return frappecloud_migrator(local_site, frappe_provider) + else: + print("{} is not supported yet".format(frappe_provider)) + sys.exit(1) diff --git a/frappe/utils/remote_migrations.py b/frappe/integrations/frappe_providers/frappecloud.py similarity index 95% rename from frappe/utils/remote_migrations.py rename to frappe/integrations/frappe_providers/frappecloud.py index 90f8db0b74..1785823a2d 100644 --- a/frappe/utils/remote_migrations.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -13,7 +13,7 @@ import requests import frappe import frappe.utils.backups from frappe.utils import get_installed_apps_info -from frappe.utils.commands import render_table, padme +from frappe.utils.commands import render_table, add_line_after def get_new_site_options(): @@ -27,7 +27,7 @@ def get_new_site_options(): def is_valid_subdomain(subdomain): - if len(subdomain) < 6: + if len(subdomain) < 5: print("Subdomain too short. Use 5 or more characters") return False matched = re.match("^[a-z0-9][a-z0-9-]*[a-z0-9]$", subdomain) @@ -60,7 +60,7 @@ def render_plan_table(plans_list): render_table(plans_table) -@padme +@add_line_after def choose_plan(plans_list): print("{} plans available".format(len(plans_list))) available_plans = [plan["name"] for plan in plans_list] @@ -75,7 +75,7 @@ def choose_plan(plans_list): print("Invalid Selection ❌") -@padme +@add_line_after def check_app_compat(available_group): is_compat = True incompatible_apps, filtered_apps, branch_msgs = [], [], [] @@ -125,7 +125,7 @@ def render_group_table(app_groups): render_table(app_groups_table) -@padme +@add_line_after def filter_apps(app_groups): render_group_table(app_groups) @@ -145,7 +145,7 @@ def filter_apps(app_groups): return selected_group["name"], filtered_apps -@padme +@add_line_after def create_session(): # take user input from STDIN username = click.prompt("Username").strip() @@ -164,7 +164,7 @@ def create_session(): print("Authorization Failed with Error Code {}".format(login_sc.status_code)) -@padme +@add_line_after def get_subdomain(domain): while True: subdomain = click.prompt("Enter subdomain").strip() @@ -173,7 +173,7 @@ def get_subdomain(domain): return subdomain -@padme +@add_line_after def upload_backup(local_site): # take backup files_session = {} @@ -256,12 +256,7 @@ def frappecloud_migrator(local_site, remote_site): print("Request failed with error code {}".format(site_creation_request.status_code)) reason = html2text(site_creation_request.text) print(reason) + sys.exit(1) - -def migrate_to(local_site, remote_site): - if remote_site in ("frappe.cloud", "frappecloud.com"): - remote_site = "frappecloud.com" - return frappecloud_migrator(local_site, remote_site) else: - print("{} is not supported yet".format(remote_site)) sys.exit(1) From 15b96f8915f2d88bb2bfa200b63f7176c06f3cc9 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 15 May 2020 11:45:44 +0530 Subject: [PATCH 096/107] fix: sider fixes --- .../doctype/package_publish_tool/package_publish_tool.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/custom/doctype/package_publish_tool/package_publish_tool.js b/frappe/custom/doctype/package_publish_tool/package_publish_tool.js index 7822299f21..4c10b609a8 100644 --- a/frappe/custom/doctype/package_publish_tool/package_publish_tool.js +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.js @@ -37,10 +37,10 @@ frappe.ui.form.on('Package Publish Tool', { set_dirty_trigger: function(frm) { $(frm.wrapper).on("dirty", function() { frm.page.set_primary_action(__('Save'), () => frm.save()); - }) + }); }, set_deploy_primary_action: function(frm) { - if (frm.doc.package_details.length && frm.doc.instances.length){ + if (frm.doc.package_details.length && frm.doc.instances.length) { frm.page.set_primary_action(__("Deploy"), function () { frappe.show_alert({ message: __("Deploying Package"), @@ -49,7 +49,7 @@ frappe.ui.form.on('Package Publish Tool', { frappe.call({ method: "frappe.custom.doctype.package_publish_tool.package_publish_tool.deploy_package", - callback: function(r) { + callback: function() { frappe.msgprint(__("Package has been published.")); } }); From ed344ae8cf2d303a55c1cee07b30765babf1fdad Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 15 May 2020 11:23:42 +0530 Subject: [PATCH 097/107] chore: updated docstrings for command APIs --- frappe/commands/site.py | 1 + frappe/utils/commands.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 1f22650930..d92c727dc7 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -260,6 +260,7 @@ def migrate(context, rebuild_website=False, skip_failing=False): @click.argument('frappe_provider') @pass_context def migrate_to(context, frappe_provider): + "Migrates site to the specified provider" from frappe.integrations.frappe_providers import migrate_to for site in context.sites: migrate_to(site, frappe_provider) diff --git a/frappe/utils/commands.py b/frappe/utils/commands.py index babefc56e2..99322b50ba 100644 --- a/frappe/utils/commands.py +++ b/frappe/utils/commands.py @@ -5,6 +5,7 @@ from terminaltables import AsciiTable @functools.lru_cache(maxsize=1024) def get_first_party_apps(): + """Get list of all apps under orgs: frappe. erpnext from GitHub""" apps = [] for org in ["frappe", "erpnext"]: req = requests.get(f"https://api.github.com/users/{org}/repos", {"type": "sources", "per_page": 200}) @@ -17,15 +18,17 @@ def render_table(data): print(AsciiTable(data).table) -def padme(me): +def add_line_after(function): + """Adds an extra line to STDOUT after the execution of a function this decorates""" def empty_line(*args, **kwargs): - result = me(*args, **kwargs) + result = function(*args, **kwargs) print() return result return empty_line def log(message, colour=''): + """Coloured log outputs to STDOUT""" colours = { "nc": '\033[0m', "blue": '\033[94m', From c6a66eb47b895521fa2a3660316fcaf46d149d0b Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 15 May 2020 13:31:13 +0530 Subject: [PATCH 098/107] fix: use different number system for india --- .../js/frappe/widgets/number_card_widget.js | 3 +- frappe/public/js/frappe/widgets/utils.js | 45 ++++++++++++++----- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/frappe/public/js/frappe/widgets/number_card_widget.js b/frappe/public/js/frappe/widgets/number_card_widget.js index cda17e08bc..77cb8a59c2 100644 --- a/frappe/public/js/frappe/widgets/number_card_widget.js +++ b/frappe/public/js/frappe/widgets/number_card_widget.js @@ -119,7 +119,8 @@ export default class NumberCardWidget extends Widget { get_formatted_number() { const based_on_df = frappe.meta.get_docfield(this.card_doc.document_type, this.card_doc.aggregate_function_based_on); - const shortened_number = shorten_number(this.number); + const default_country = frappe.sys_defaults.country; + const shortened_number = shorten_number(this.number, default_country); let number_parts = shortened_number.split(' '); const symbol = number_parts[1] || ''; diff --git a/frappe/public/js/frappe/widgets/utils.js b/frappe/public/js/frappe/widgets/utils.js index f336335d1c..ab807bb0b1 100644 --- a/frappe/public/js/frappe/widgets/utils.js +++ b/frappe/public/js/frappe/widgets/utils.js @@ -127,19 +127,44 @@ function go_to_list_with_filters(doctype, filters) { }); } -function shorten_number(number) { +function shorten_number(number, country) { + country = country || ''; + const number_system = get_number_system(country); let x = Math.abs(Math.round(number)); + for (const map of number_system) { + if (x >= map.divisor) { + return Math.round(number/map.divisor) + ' ' + map.symbol; + } + }; + return number.toFixed(); +} - switch (true) { - case x >= 1.0e+12: - return Math.round(number/1.0e+12) + " T"; - case x >= 1.0e+9: - return Math.round(number/1.0e+9) + " B"; - case x >= 1.0e+6: - return Math.round(number/1.0e+6) + " M"; - default: - return number.toFixed(); +function get_number_system(country) { + let number_system_map = { + 'India': + [{ + divisor: 1.0e+7, + symbol: 'Cr' + }, + { + divisor: 1.0e+5, + symbol: 'Lakh' + }], + '': + [{ + divisor: 1.0e+12, + symbol: 'T' + }, + { + divisor: 1.0e+9, + symbol: 'B' + }, + { + divisor: 1.0e+6, + symbol: 'M' + }] } + return number_system_map[country]; } export { generate_route, generate_grid, build_summary_item, go_to_list_with_filters, shorten_number }; \ No newline at end of file From 2ebdb292f93823f242b40360bdeda8785de6020c Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 15 May 2020 13:35:21 +0530 Subject: [PATCH 099/107] style: fix semicolon --- frappe/public/js/frappe/widgets/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/widgets/utils.js b/frappe/public/js/frappe/widgets/utils.js index ab807bb0b1..c92bdc1b5f 100644 --- a/frappe/public/js/frappe/widgets/utils.js +++ b/frappe/public/js/frappe/widgets/utils.js @@ -135,7 +135,7 @@ function shorten_number(number, country) { if (x >= map.divisor) { return Math.round(number/map.divisor) + ' ' + map.symbol; } - }; + } return number.toFixed(); } @@ -163,7 +163,7 @@ function get_number_system(country) { divisor: 1.0e+6, symbol: 'M' }] - } + }; return number_system_map[country]; } From db3b57116d84a7f3d94fff1c2e63ecbc42b2ad76 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 15 May 2020 15:16:56 +0530 Subject: [PATCH 100/107] fix: consistent UI and minor fixes --- .../frappe_providers/frappecloud.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index 1785823a2d..3815042e9d 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -50,12 +50,13 @@ def render_plan_table(plans_list): plans_table = [] # title row - visible_headers = ["name", "concurrent_users", "cpu_time_per_day"] - plans_table.append(visible_headers) + visible_headers = ["name", "cpu_time_per_day"] + plans_table.append(["Plan", "CPU Time"]) # all rows for plan in plans_list: - plans_table.append([plan[header] for header in visible_headers]) + plan, cpu_time = [plan[header] for header in visible_headers] + plans_table.append([plan, "{} hour{}/day".format(cpu_time, "" if cpu_time < 2 else "s")]) render_table(plans_table) @@ -83,13 +84,13 @@ def check_app_compat(available_group): print("Checking availability of existing app group") for (app, branch) in existing_group: - info = [ (a["name"], a["branch"]) for a in available_group["apps"] if a["scrubbed"] == app] + info = [ (a["name"], a["branch"]) for a in available_group["apps"] if a["scrubbed"] == app ] if info: app_title, available_branch = info[0] if branch != available_branch: - print("⚠️ {}:{} => {}".format(app, branch, available_branch)) - branch_msgs.append([app.title(), branch, available_branch]) + print("⚠️ App {}:{} => {}".format(app, branch, available_branch)) + branch_msgs.append([app, branch, available_branch]) filtered_apps.append(app_title) is_compat = False @@ -103,8 +104,8 @@ def check_app_compat(available_group): is_compat = False start_msg = "\nSelecting this group will " - incompatible_apps = "drop {} apps: ".format(len(incompatible_apps)) + ", ".join(incompatible_apps) + " and " if incompatible_apps else "" - branch_change = "upgrade:\n" + "\n".join(["{}: {} => {}".format(*x) for x in branch_msgs]) if branch_msgs else "" + incompatible_apps = ("\n\nDrop the following apps:\n" + "\n".join(incompatible_apps)) if incompatible_apps else "" + branch_change = ("\n\nUpgrade the following apps:\n" + "\n".join(["{}: {} => {}".format(*x) for x in branch_msgs])) if branch_msgs else "" changes = (incompatible_apps + branch_change) or "be perfect for you :)" warning_message = start_msg + changes print(warning_message) @@ -130,12 +131,14 @@ def filter_apps(app_groups): render_group_table(app_groups) while True: - app_group_index = click.prompt("Select App Group #", type=int) - 1 + app_group_index = click.prompt("Select App Group Number", type=int) - 1 try: + if app_group_index == -1: + raise IndexError selected_group = app_groups[app_group_index] except IndexError: print("Invalid Selection ❌") - break + continue is_compat, filtered_apps = check_app_compat(selected_group) @@ -251,7 +254,9 @@ def frappecloud_migrator(local_site, remote_site): frappe.destroy() if site_creation_request.ok: - print("Site creation started at {}".format(site_creation_request.json()["message"])) + site_url = site_creation_request.json()["message"] + print("View your site dashboard at {}/dashboard/#/sites/{} ✨".format(remote_site, site_url)) + print("Your site URL: {}".format(site_url)) else: print("Request failed with error code {}".format(site_creation_request.status_code)) reason = html2text(site_creation_request.text) From a8c01a091518ee1e0f823bd4e9190e31aa14c875 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 15 May 2020 15:32:13 +0530 Subject: [PATCH 101/107] fix: Success message --- frappe/integrations/frappe_providers/frappecloud.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index 3815042e9d..4f33c990f9 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -255,7 +255,8 @@ def frappecloud_migrator(local_site, remote_site): if site_creation_request.ok: site_url = site_creation_request.json()["message"] - print("View your site dashboard at {}/dashboard/#/sites/{} ✨".format(remote_site, site_url)) + print("Your site {} is being migrated ✨".format(local_site)) + print("View your site dashboard at {}/dashboard/#/sites/{}".format(remote_site, site_url)) print("Your site URL: {}".format(site_url)) else: print("Request failed with error code {}".format(site_creation_request.status_code)) From 4af13edc87fee4083491fcc14197040300b4575f Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 15 May 2020 16:13:16 +0530 Subject: [PATCH 102/107] fix: Remove hardcoded url --- frappe/integrations/frappe_providers/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/integrations/frappe_providers/__init__.py b/frappe/integrations/frappe_providers/__init__.py index 31a0cf1c04..ec762627d0 100644 --- a/frappe/integrations/frappe_providers/__init__.py +++ b/frappe/integrations/frappe_providers/__init__.py @@ -4,7 +4,6 @@ from frappe.integrations.frappe_providers.frappecloud import frappecloud_migrato def migrate_to(local_site, frappe_provider): if frappe_provider in ("frappe.cloud", "frappecloud.com"): - frappe_provider = "staging.frappe.cloud" return frappecloud_migrator(local_site, frappe_provider) else: print("{} is not supported yet".format(frappe_provider)) From 7a58cbeff287a36700a4c316463f060be885ab62 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 15 May 2020 16:30:17 +0530 Subject: [PATCH 103/107] fix: hardcode url frappe.cloud => frappecloud.com --- frappe/integrations/frappe_providers/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/integrations/frappe_providers/__init__.py b/frappe/integrations/frappe_providers/__init__.py index ec762627d0..a5cf284151 100644 --- a/frappe/integrations/frappe_providers/__init__.py +++ b/frappe/integrations/frappe_providers/__init__.py @@ -4,6 +4,7 @@ from frappe.integrations.frappe_providers.frappecloud import frappecloud_migrato def migrate_to(local_site, frappe_provider): if frappe_provider in ("frappe.cloud", "frappecloud.com"): + frappe_provider = "frappecloud.com" return frappecloud_migrator(local_site, frappe_provider) else: print("{} is not supported yet".format(frappe_provider)) From b2e4ab201f53b9609509ef24e0be2a8de617c31b Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 15 May 2020 17:01:20 +0530 Subject: [PATCH 104/107] fix: change child doctype name --- .../custom/doctype/package_publish_tool/package_publish_tool.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/custom/doctype/package_publish_tool/package_publish_tool.js b/frappe/custom/doctype/package_publish_tool/package_publish_tool.js index 4c10b609a8..7e3b65936f 100644 --- a/frappe/custom/doctype/package_publish_tool/package_publish_tool.js +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.js @@ -68,7 +68,7 @@ frappe.ui.form.on('Package Publish Tool', { } }); -frappe.ui.form.on('Package Detail', { +frappe.ui.form.on('Package Document Type', { form_render: function (frm, cdt, cdn) { function _show_filters(filters, table) { table.find('tbody').empty(); From e02feac919e928f1f3db304320a06bd44f618588 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 15 May 2020 17:22:48 +0530 Subject: [PATCH 105/107] fix: do not link package to doc --- .../custom/doctype/package_publish_tool/package_publish_tool.js | 1 + .../custom/doctype/package_publish_tool/package_publish_tool.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/custom/doctype/package_publish_tool/package_publish_tool.js b/frappe/custom/doctype/package_publish_tool/package_publish_tool.js index 7e3b65936f..baf1ab1fcb 100644 --- a/frappe/custom/doctype/package_publish_tool/package_publish_tool.js +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.js @@ -50,6 +50,7 @@ frappe.ui.form.on('Package Publish Tool', { frappe.call({ method: "frappe.custom.doctype.package_publish_tool.package_publish_tool.deploy_package", callback: function() { + frm.reload_doc(); frappe.msgprint(__("Package has been published.")); } }); diff --git a/frappe/custom/doctype/package_publish_tool/package_publish_tool.py b/frappe/custom/doctype/package_publish_tool/package_publish_tool.py index 415839322c..a01dd0ba47 100644 --- a/frappe/custom/doctype/package_publish_tool/package_publish_tool.py +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.py @@ -23,7 +23,6 @@ def deploy_package(): package, doc = export_package() file_name = "Package-" + get_datetime_str(get_datetime()) - save_file(file_name, json.dumps(package), "Package Publish Tool", "Package Publish Tool") length = len(doc.instances) for idx, instance in enumerate(doc.instances): From 8e204e9fb92cc1ceeaa003eff86d1acb634c70f9 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 15 May 2020 17:40:33 +0530 Subject: [PATCH 106/107] fix: UX fixes --- .../package_publish_target.json | 4 ++-- .../package_publish_tool.js | 22 +++++++++++-------- .../package_publish_tool.json | 6 ++--- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/frappe/custom/doctype/package_publish_target/package_publish_target.json b/frappe/custom/doctype/package_publish_target/package_publish_target.json index ccf3f2ea05..baeb7cb8bc 100644 --- a/frappe/custom/doctype/package_publish_target/package_publish_target.json +++ b/frappe/custom/doctype/package_publish_target/package_publish_target.json @@ -14,7 +14,7 @@ "fieldname": "instance_url", "fieldtype": "Data", "in_list_view": 1, - "label": "Instance URL", + "label": "Site URL", "reqd": 1 }, { @@ -34,7 +34,7 @@ ], "istable": 1, "links": [], - "modified": "2020-05-13 16:04:49.943057", + "modified": "2020-05-15 17:35:16.282235", "modified_by": "Administrator", "module": "Custom", "name": "Package Publish Target", diff --git a/frappe/custom/doctype/package_publish_tool/package_publish_tool.js b/frappe/custom/doctype/package_publish_tool/package_publish_tool.js index baf1ab1fcb..a0190a8d8c 100644 --- a/frappe/custom/doctype/package_publish_tool/package_publish_tool.js +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.js @@ -32,7 +32,7 @@ frappe.ui.form.on('Package Publish Tool', { }, show_indicator: function(frm) { let pretty_date = frappe.datetime.prettyDate(frm.doc.last_deployed_on); - frm.page.set_indicator(__("Last deployed {0}", [pretty_date]), "blue"); + frm.page.set_indicator(__("Last published {0}", [pretty_date]), "blue"); }, set_dirty_trigger: function(frm) { $(frm.wrapper).on("dirty", function() { @@ -41,9 +41,9 @@ frappe.ui.form.on('Package Publish Tool', { }, set_deploy_primary_action: function(frm) { if (frm.doc.package_details.length && frm.doc.instances.length) { - frm.page.set_primary_action(__("Deploy"), function () { + frm.page.set_primary_action(__("Publish"), function () { frappe.show_alert({ - message: __("Deploying Package"), + message: __("Publishing documents..."), indicator: "green" }); @@ -51,7 +51,7 @@ frappe.ui.form.on('Package Publish Tool', { method: "frappe.custom.doctype.package_publish_tool.package_publish_tool.deploy_package", callback: function() { frm.reload_doc(); - frappe.msgprint(__("Package has been published.")); + frappe.msgprint(__("Documents have been published.")); } }); }); @@ -60,11 +60,15 @@ frappe.ui.form.on('Package Publish Tool', { show_instructions: function(frm) { let field = frm.get_field("html_info"); field.html(` -

- - Create a Package by selecting the Documents and filters in the Package table.
- - Add the Remote Instance's URL, Username and Password to the Instances table.
- - Once the above details are filled, Deploy button will be visible and the Package will be deployed on click.
+

+ Package Publish Tool let's you copy documents from your site to any other remote site. + Follow the steps below to publish.

+
    +
  1. Add Document Types that you want to copy from the table below. You can also add filters by expanding the row.
  2. +
  3. Add the Sites URL where you want to copy these documents, and enter the Username and Password.
  4. +
  5. Click on Save. Now, you can click on Publish and the documents will be copied.
  6. +
`); } }); @@ -152,4 +156,4 @@ frappe.ui.form.on('Package Document Type', { }); }); }, -}); \ No newline at end of file +}); diff --git a/frappe/custom/doctype/package_publish_tool/package_publish_tool.json b/frappe/custom/doctype/package_publish_tool/package_publish_tool.json index 59d22f6ad4..0f85ae0348 100644 --- a/frappe/custom/doctype/package_publish_tool/package_publish_tool.json +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.json @@ -17,14 +17,14 @@ "description": "Click on the row for accessing filters.", "fieldname": "package_details", "fieldtype": "Table", - "label": "Package", + "label": "Document Types", "options": "Package Document Type", "reqd": 1 }, { "fieldname": "instances", "fieldtype": "Table", - "label": "Instances", + "label": "Sites", "options": "Package Publish Target", "reqd": 1 }, @@ -50,7 +50,7 @@ ], "issingle": 1, "links": [], - "modified": "2020-05-14 16:46:20.374773", + "modified": "2020-05-15 17:31:37.060199", "modified_by": "Administrator", "module": "Custom", "name": "Package Publish Tool", From f7f81a0843cb7d988f340eeae67e7cea258afd02 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 15 May 2020 17:52:40 +0530 Subject: [PATCH 107/107] chore: add missed import --- frappe/integrations/frappe_providers/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/integrations/frappe_providers/__init__.py b/frappe/integrations/frappe_providers/__init__.py index a5cf284151..0b689478d2 100644 --- a/frappe/integrations/frappe_providers/__init__.py +++ b/frappe/integrations/frappe_providers/__init__.py @@ -1,3 +1,6 @@ +# imports - standard imports +import sys + # imports - module imports from frappe.integrations.frappe_providers.frappecloud import frappecloud_migrator