From 9e139171f41405d677d62361fb2ef2190d475d5e Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 8 Apr 2020 18:35:49 +0530 Subject: [PATCH 001/274] 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/274] 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/274] 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/274] 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 fca6181d6771235d883bd1c4cc0ce57c3c32065c Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 15 Apr 2020 21:32:22 +0530 Subject: [PATCH 005/274] feat: multiple assignments --- frappe/desk/form/assign_to.py | 77 +++---- .../js/frappe/form/sidebar/assign_to.js | 202 +++++++++++------- 2 files changed, 164 insertions(+), 115 deletions(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 76c7caa63d..a0f2f99910 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -11,6 +11,7 @@ from frappe.desk.doctype.notification_log.notification_log import enqueue_create get_title, get_title_html import frappe.utils import frappe.share +import json class DuplicateToDoError(frappe.ValidationError): pass @@ -19,7 +20,7 @@ def get(args=None): if not args: args = frappe.local.form_dict - return frappe.get_all('ToDo', fields = ['owner', 'description'], filters = dict( + return frappe.get_all('ToDo', fields = ['owner', 'name'], filters = dict( reference_type = args.get('doctype'), reference_name = args.get('name'), status = ('!=', 'Cancelled') @@ -40,49 +41,53 @@ def add(args=None): if not args: args = frappe.local.form_dict - if frappe.db.sql("""SELECT `owner` - FROM `tabToDo` - WHERE `reference_type`=%(doctype)s - AND `reference_name`=%(name)s - AND `status`='Open' - AND `owner`=%(assign_to)s""", args): - frappe.throw(_("Already in user's To Do list"), DuplicateToDoError) - else: - from frappe.utils import nowdate - - if not args.get('description'): - args['description'] = _('Assignment for {0} {1}').format(args['doctype'], args['name']) - - d = frappe.get_doc({ - "doctype":"ToDo", - "owner": args['assign_to'], + for assign_to in json.loads(args.get("assign_to")): + filters = { "reference_type": args['doctype'], "reference_name": args['name'], - "description": args.get('description'), - "priority": args.get("priority", "Medium"), "status": "Open", - "date": args.get('date', nowdate()), - "assigned_by": args.get('assigned_by', frappe.session.user), - 'assignment_rule': args.get('assignment_rule') - }).insert(ignore_permissions=True) + "owner": assign_to + } - # set assigned_to if field exists - if frappe.get_meta(args['doctype']).get_field("assigned_to"): - frappe.db.set_value(args['doctype'], args['name'], "assigned_to", args['assign_to']) + if frappe.get_all("ToDo", filters=filters): + if not args.get("bulk_assign"): + frappe.throw(_("Already in user's ToDo list"), DuplicateToDoError) + else: + from frappe.utils import nowdate - doc = frappe.get_doc(args['doctype'], args['name']) + if not args.get('description'): + args['description'] = _('Assignment for {0} {1}').format(args['doctype'], args['name']) - # if assignee does not have permissions, share - if not frappe.has_permission(doc=doc, user=args['assign_to']): - frappe.share.add(doc.doctype, doc.name, args['assign_to']) - frappe.msgprint(_('Shared with user {0} with read access').format(args['assign_to']), alert=True) + d = frappe.get_doc({ + "doctype":"ToDo", + "owner": assign_to, + "reference_type": args['doctype'], + "reference_name": args['name'], + "description": args.get('description'), + "priority": args.get("priority", "Medium"), + "status": "Open", + "date": args.get('date', nowdate()), + "assigned_by": args.get('assigned_by', frappe.session.user), + 'assignment_rule': args.get('assignment_rule') + }).insert(ignore_permissions=True) - # make this document followed by assigned user - follow_document(args['doctype'], args['name'], args['assign_to']) + # set assigned_to if field exists + if frappe.get_meta(args['doctype']).get_field("assigned_to"): + frappe.db.set_value(args['doctype'], args['name'], "assigned_to", assign_to) - # notify - notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',\ - description=args.get("description")) + doc = frappe.get_doc(args['doctype'], args['name']) + + # if assignee does not have permissions, share + if not frappe.has_permission(doc=doc, user=assign_to): + frappe.share.add(doc.doctype, doc.name, assign_to) + frappe.msgprint(_('Shared with user {0} with read access').format(assign_to, alert=True)) + + # make this document followed by assigned user + follow_document(args['doctype'], args['name'], assign_to) + + # notify + notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',\ + description=args.get("description")) return get(args) diff --git a/frappe/public/js/frappe/form/sidebar/assign_to.js b/frappe/public/js/frappe/form/sidebar/assign_to.js index 61d1789518..b78540f6a7 100644 --- a/frappe/public/js/frappe/form/sidebar/assign_to.js +++ b/frappe/public/js/frappe/form/sidebar/assign_to.js @@ -87,23 +87,17 @@ frappe.ui.form.AssignTo = Class.extend({ if(!me.assign_to) { me.assign_to = new frappe.ui.form.AssignToDialog({ - obj: me, - method: 'frappe.desk.form.assign_to.add', + method: "frappe.desk.form.assign_to.add", doctype: me.frm.doctype, docname: me.frm.docname, - callback: function(r) { + frm: me.frm, + callback: function (r) { me.render(r.message); } }); } me.assign_to.dialog.clear(); - - if(me.frm.meta.title_field) { - me.assign_to.dialog.set_value("description", me.frm.doc[me.frm.meta.title_field]) - } - me.assign_to.dialog.show(); - me.assign_to = null; }, remove: function(owner) { var me = this; @@ -130,81 +124,131 @@ frappe.ui.form.AssignTo = Class.extend({ frappe.ui.form.AssignToDialog = Class.extend({ init: function(opts){ - var me = this - var dialog = new frappe.ui.Dialog({ - title: __('Add to To Do'), - fields: [ - { fieldtype: 'Link', fieldname: 'assign_to', options: 'User', label: __("Assign To"), reqd: true, filters: { 'user_type': 'System User' }}, - { fieldtype: 'Check', fieldname: 'myself', label: __("Assign to me"), "default": 0 }, - { fieldtype: 'Small Text', fieldname: 'description', label: __("Comment") }, - { fieldtype: 'Section Break' }, - { fieldtype: 'Column Break' }, - { fieldtype: 'Date', fieldname: 'date', label: __("Complete By") }, - { fieldtype: 'Column Break' }, - { fieldtype: 'Select', fieldname: 'priority', label: __("Priority"), - options: [ - { value: 'Low', label: __('Low') }, - { value: 'Medium', label: __('Medium') }, - { value: 'High', label: __('High') } - ], - // Pick up priority from the source document, if it exists and is available in ToDo - 'default': ["Low", "Medium", "High"].includes(opts.obj.frm && opts.obj.frm.doc.priority - ? opts.obj.frm.doc.priority : 'Medium') - }, - ], - primary_action: function() { frappe.ui.add_assignment(opts, this) }, - primary_action_label: __("Add") - }) - $.extend(me, dialog); + $.extend(this, opts) - me.dialog = dialog; - - me.dialog.fields_dict.assign_to.get_query = "frappe.core.doctype.user.user.user_query"; - - var myself = me.dialog.get_input("myself").on("click", function() { - me.toggle_myself(this); - }); - me.toggle_myself(myself); - }, - toggle_myself: function(myself) { - var me = this; - if($(myself).prop("checked")) { - me.dialog.set_value("assign_to", frappe.session.user); - me.dialog.get_field("notify").$wrapper.toggle(false); - me.dialog.get_field("assign_to").$wrapper.toggle(false); - } else { - me.dialog.set_value("assign_to", ""); - me.dialog.get_field("assign_to").$wrapper.toggle(true); - } + this.make(); + this.set_description_from_doc(); }, + make: function() { + let me = this -}); + me.dialog = new frappe.ui.Dialog({ + title: __('Add to ToDo'), + fields: me.get_fields(), + primary_action_label: __("Add"), + primary_action: function() { + let args = me.dialog.get_values(); -frappe.ui.add_assignment = function(opts, dialog) { - var assign_to = dialog.fields_dict.assign_to.get_value(); - var args = dialog.get_values(); - if(args && assign_to) { - dialog.set_message('Assigning...'); - return frappe.call({ - method: opts.method, - args: $.extend(args, { - doctype: opts.doctype, - name: opts.docname, - assign_to: assign_to, - bulk_assign: opts.bulk_assign || false, - re_assign: opts.re_assign || false - }), - btn: dialog.get_primary_btn(), - callback: function(r) { - if(!r.exc) { - if(opts.callback){ - opts.callback(r); - } - dialog && dialog.hide(); - } else { - dialog.clear_message(); + if (args && args.assign_to) { + me.dialog.set_message("Assigning..."); + + frappe.call({ + method: me.method, + args: $.extend(args, { + doctype: me.doctype, + name: me.docname, + assign_to: args.assign_to, + bulk_assign: me.bulk_assign || false, + re_assign: me.re_assign || false + }), + btn: me.dialog.get_primary_btn(), + callback: function(r) { + if (!r.exc) { + if (me.callback) { + me.callback(r); + } + me.dialog && me.dialog.hide(); + } else { + me.dialog.clear_message(); + } + }, + }); } }, }); + }, + assign_to_me: function() { + let me = this; + let assign_to = []; + + if(me.dialog.get_value("assign_to_me")) { + assign_to.push(frappe.session.user); + } + + me.dialog.set_value("assign_to", assign_to); + }, + set_description_from_doc: function() { + let me = this; + + if (me.frm && me.frm.meta.title_field) { + me.dialog.set_value("description", me.frm.doc[me.frm.meta.title_field]); + } + }, + set_assign_to_field_description: function() { + let me = this; + + let assignees = me.dialog.get_value("assign_to"); + let field = me.dialog.get_field("assign_to"); + let description = ""; + + if (assignees.length > 0) { + description = "Assign To: " + assignees.join(", "); + } + + field.set_description(description); + }, + get_fields: function() { + let me = this; + + return [ + { + fieldtype: 'MultiSelectList', + fieldname: 'assign_to', + label: __("Assign To"), + reqd: true, + get_data: function(txt) { + return frappe.db.get_link_options("User", txt, {user_type: "System User", enabled: 1}); + }, + onchange: () => me.set_assign_to_field_description() + }, + { + label: __("Assign to me"), + fieldtype: 'Check', + fieldname: 'assign_to_me', + default: 0, + onchange: () => me.assign_to_me() + }, + { + label: __("Comment"), + fieldtype: 'Small Text', + fieldname: 'description' + }, + { + fieldtype: 'Section Break' + }, + { + fieldtype: 'Column Break' + }, + { + label: __("Complete By"), + fieldtype: 'Date', + fieldname: 'date' + }, + { + fieldtype: 'Column Break' + }, + { + label: __("Priority"), + fieldtype: 'Select', + fieldname: 'priority', + options: [ + { value: 'Low', label: __('Low') }, + { value: 'Medium', label: __('Medium') }, + { value: 'High', label: __('High') } + ], + // Pick up priority from the source document, if it exists and is available in ToDo + default: ["Low", "Medium", "High"].includes(me.frm && me.frm.doc.priority ? me.frm.doc.priority : 'Medium') + } + ] } -} +}); From 6b9de21d539d4a64fe82473be994af9bc814eb61 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 15 Apr 2020 22:20:23 +0530 Subject: [PATCH 006/274] fix: minor bug fixes for notifying assignees --- frappe/desk/form/assign_to.py | 4 ++-- frappe/public/js/frappe/form/sidebar/assign_to.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index a0f2f99910..8a8219eb22 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -85,8 +85,8 @@ def add(args=None): # make this document followed by assigned user follow_document(args['doctype'], args['name'], assign_to) - # notify - notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',\ + # notify + notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',\ description=args.get("description")) return get(args) diff --git a/frappe/public/js/frappe/form/sidebar/assign_to.js b/frappe/public/js/frappe/form/sidebar/assign_to.js index b78540f6a7..03fba17ec7 100644 --- a/frappe/public/js/frappe/form/sidebar/assign_to.js +++ b/frappe/public/js/frappe/form/sidebar/assign_to.js @@ -124,7 +124,7 @@ frappe.ui.form.AssignTo = Class.extend({ frappe.ui.form.AssignToDialog = Class.extend({ init: function(opts){ - $.extend(this, opts) + $.extend(this, opts); this.make(); this.set_description_from_doc(); @@ -148,7 +148,7 @@ frappe.ui.form.AssignToDialog = Class.extend({ doctype: me.doctype, name: me.docname, assign_to: args.assign_to, - bulk_assign: me.bulk_assign || false, + bulk_assign: me.bulk_assign || false, re_assign: me.re_assign || false }), btn: me.dialog.get_primary_btn(), @@ -171,7 +171,7 @@ frappe.ui.form.AssignToDialog = Class.extend({ let me = this; let assign_to = []; - if(me.dialog.get_value("assign_to_me")) { + if (me.dialog.get_value("assign_to_me")) { assign_to.push(frappe.session.user); } @@ -249,6 +249,6 @@ frappe.ui.form.AssignToDialog = Class.extend({ // Pick up priority from the source document, if it exists and is available in ToDo default: ["Low", "Medium", "High"].includes(me.frm && me.frm.doc.priority ? me.frm.doc.priority : 'Medium') } - ] + ]; } }); From 7886365c090ad1b645b63c0987af83e13366b1e9 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 15 Apr 2020 22:48:58 +0530 Subject: [PATCH 007/274] chore: code cleanup --- frappe/desk/form/assign_to.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 8a8219eb22..be83dc957e 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -20,11 +20,11 @@ def get(args=None): if not args: args = frappe.local.form_dict - return frappe.get_all('ToDo', fields = ['owner', 'name'], filters = dict( + return frappe.get_all('ToDo', fields=['owner', 'name'], filters=dict( reference_type = args.get('doctype'), reference_name = args.get('name'), status = ('!=', 'Cancelled') - ), limit = 5) + ), limit=5) @frappe.whitelist() def add(args=None): @@ -59,7 +59,7 @@ def add(args=None): args['description'] = _('Assignment for {0} {1}').format(args['doctype'], args['name']) d = frappe.get_doc({ - "doctype":"ToDo", + "doctype": "ToDo", "owner": assign_to, "reference_type": args['doctype'], "reference_name": args['name'], @@ -86,15 +86,13 @@ def add(args=None): follow_document(args['doctype'], args['name'], assign_to) # notify - notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',\ + notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN', description=args.get("description")) return get(args) @frappe.whitelist() def add_multiple(args=None): - import json - if not args: args = frappe.local.form_dict From 7dac9e2a0342809453bdb44d0588606715b5154b Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 16 Apr 2020 21:55:52 +0530 Subject: [PATCH 008/274] fix: change to multiselect pills --- .../js/frappe/form/sidebar/assign_to.js | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/frappe/public/js/frappe/form/sidebar/assign_to.js b/frappe/public/js/frappe/form/sidebar/assign_to.js index 03fba17ec7..84fde564c0 100644 --- a/frappe/public/js/frappe/form/sidebar/assign_to.js +++ b/frappe/public/js/frappe/form/sidebar/assign_to.js @@ -184,32 +184,18 @@ frappe.ui.form.AssignToDialog = Class.extend({ me.dialog.set_value("description", me.frm.doc[me.frm.meta.title_field]); } }, - set_assign_to_field_description: function() { - let me = this; - - let assignees = me.dialog.get_value("assign_to"); - let field = me.dialog.get_field("assign_to"); - let description = ""; - - if (assignees.length > 0) { - description = "Assign To: " + assignees.join(", "); - } - - field.set_description(description); - }, get_fields: function() { let me = this; return [ { - fieldtype: 'MultiSelectList', + fieldtype: 'MultiSelectPills', fieldname: 'assign_to', label: __("Assign To"), reqd: true, get_data: function(txt) { return frappe.db.get_link_options("User", txt, {user_type: "System User", enabled: 1}); - }, - onchange: () => me.set_assign_to_field_description() + } }, { label: __("Assign to me"), @@ -242,9 +228,18 @@ frappe.ui.form.AssignToDialog = Class.extend({ fieldtype: 'Select', fieldname: 'priority', options: [ - { value: 'Low', label: __('Low') }, - { value: 'Medium', label: __('Medium') }, - { value: 'High', label: __('High') } + { + value: 'Low', + label: __('Low') + }, + { + value: 'Medium', + label: __('Medium') + }, + { + value: 'High', + label: __('High') + } ], // Pick up priority from the source document, if it exists and is available in ToDo default: ["Low", "Medium", "High"].includes(me.frm && me.frm.doc.priority ? me.frm.doc.priority : 'Medium') From acc7de7335e28b13c5590b95a42ed972198de83b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 17 Apr 2020 18:49:04 +0530 Subject: [PATCH 009/274] feat: added pandas date_range to utils --- frappe/utils/dateutils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frappe/utils/dateutils.py b/frappe/utils/dateutils.py index d5b7a3136b..907cab3d02 100644 --- a/frappe/utils/dateutils.py +++ b/frappe/utils/dateutils.py @@ -73,3 +73,9 @@ def datetime_in_user_format(date_time): date_time = get_datetime(date_time) from frappe.utils import formatdate return formatdate(date_time.date()) + " " + date_time.strftime("%H:%M") + +def get_date_range(from_date, to_date, frequency="M", normalize=True): + from pandas import date_range + date_range = date_range(start=from_date, end=to_date, freq=frequency, normalize=normalize).to_pydatetime().tolist() + + return date_range \ No newline at end of file From da6024242769e8c0934f8bc283ae8c9d93fbc573 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 17 Apr 2020 18:49:12 +0530 Subject: [PATCH 010/274] feat: added website analytics report --- frappe/website/report/__init__.py | 0 .../report/website_analytics/__init__.py | 0 .../website_analytics/website_analytics.js | 34 ++++ .../website_analytics/website_analytics.json | 27 ++++ .../website_analytics/website_analytics.py | 145 ++++++++++++++++++ 5 files changed, 206 insertions(+) create mode 100644 frappe/website/report/__init__.py create mode 100644 frappe/website/report/website_analytics/__init__.py create mode 100644 frappe/website/report/website_analytics/website_analytics.js create mode 100644 frappe/website/report/website_analytics/website_analytics.json create mode 100644 frappe/website/report/website_analytics/website_analytics.py diff --git a/frappe/website/report/__init__.py b/frappe/website/report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/website/report/website_analytics/__init__.py b/frappe/website/report/website_analytics/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/website/report/website_analytics/website_analytics.js b/frappe/website/report/website_analytics/website_analytics.js new file mode 100644 index 0000000000..b607a16eb4 --- /dev/null +++ b/frappe/website/report/website_analytics/website_analytics.js @@ -0,0 +1,34 @@ +// Copyright (c) 2016, Frappe Technologies and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Website Analytics"] = { + "filters": [ + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date", + default: frappe.datetime.add_days(frappe.datetime.now_date(true), -7), + reqd: 1 + }, + { + fieldname:"to_date", + label: __("To Date"), + fieldtype: "Date", + default: frappe.datetime.now_date(true), + reqd: 1 + }, + { + fieldname: "range", + label: __("Range"), + fieldtype: "Select", + options: [ + { "value": "D", "label": __("Daily") }, + { "value": "W", "label": __("Weekly") }, + { "value": "M", "label": __("Monthly") }, + ], + default: "D", + reqd: 1 + } + ] +}; diff --git a/frappe/website/report/website_analytics/website_analytics.json b/frappe/website/report/website_analytics/website_analytics.json new file mode 100644 index 0000000000..62c5751a5c --- /dev/null +++ b/frappe/website/report/website_analytics/website_analytics.json @@ -0,0 +1,27 @@ +{ + "add_total_row": 0, + "creation": "2020-04-17 13:04:45.770148", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2020-04-17 16:10:30.168312", + "modified_by": "Administrator", + "module": "Website", + "name": "Website Analytics", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Web Page View", + "report_name": "Website Analytics", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Website Manager" + } + ] +} \ No newline at end of file diff --git a/frappe/website/report/website_analytics/website_analytics.py b/frappe/website/report/website_analytics/website_analytics.py new file mode 100644 index 0000000000..8b2d5b3806 --- /dev/null +++ b/frappe/website/report/website_analytics/website_analytics.py @@ -0,0 +1,145 @@ +# Copyright (c) 2013, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils.dateutils import get_date_range + +def execute(filters=None): + return WebsiteAnalytics(filters).run() + +class WebsiteAnalytics(object): + def __init__(self, filters=None): + self.filters = frappe._dict(filters or {}) + self.filters.to_date = frappe.utils.add_days(self.filters.to_date, 1) + self.query_filters = {'creation': ['between', [self.filters.from_date, self.filters.to_date]]} + + def run(self): + columns = self.get_columns() + data = self.get_data() + summary = self.get_report_summary() + chart = self.get_chart_data() + return columns, data, None, chart, summary + + def get_columns(self): + return [ + { + "fieldname": "path", + "label": "Page", + "fieldtype": "Data", + "width": 300 + }, + { + "fieldname": "count", + "label": "Page Views", + "fieldtype": "Int", + "width": 150 + } + ] + + def get_data(self): + data = frappe.get_all("Web Page View", fields=['path', 'count(*) as count'], filters=self.query_filters, group_by="path", order_by='count desc') + return data + + def get_chart_data(self): + def _get_field_for_chart(filters_range): + field = 'creation' + date_format = '%Y-%m-%d' + + if filters_range == "W": + field = 'ADDDATE(creation, INTERVAL 1-DAYOFWEEK(creation) DAY)' + + elif filters_range == "M": + date_format = '%Y-%m-01' + + return field, date_format + + field, date_format = _get_field_for_chart(self.filters.range) + + data = frappe.db.sql(""" + SELECT + DATE_FORMAT({0}, %s) as date, + COUNT(*) as count, + count(CASE WHEN is_unique = 1 THEN 1 END) as unique_count + FROM `tabWeb Page View` + WHERE creation BETWEEN %s AND %s + GROUP BY DATE_FORMAT({0}, %s) + ORDER BY creation + """.format(field), (date_format, self.filters.from_date, self.filters.to_date, date_format), as_dict=1, debug=1) + + return self.prepare_chart_data(data) + + def prepare_chart_data(self, data): + date_range = get_date_range(self.filters.from_date, self.filters.to_date, self.filters.range) + if self.filters.range == "M": + date_range = [frappe.utils.add_days(dd, 1) for dd in date_range] + + labels = [] + total_dataset = [] + unique_dataset = [] + + def get_data_for_date(date): + for item in data: + item_date = frappe.utils.get_datetime(item.get("date")).date() + if item_date == date.date(): + return item + return {'count': 0, 'unique_count': 0} + + + for date in date_range: + labels.append(date.strftime("%b %d %Y")) + match = get_data_for_date(date) + total_dataset.append(match.get('count', 0)) + unique_dataset.append(match.get('unique_count', 0)) + + chart = { + "data": { + 'labels': labels, + 'datasets': [ + { + 'name': "Total Views", + 'type': 'line', + 'values': total_dataset + }, + { + 'name': "Unique Visits", + 'type': 'line', + 'values': unique_dataset + } + ] + }, + "type": "axis-mixed", + 'lineOptions': { + 'regionFill': 1, + }, + 'axisOptions': { + 'xIsSeries': 1 + } + } + + return chart + + + def get_report_summary(self): + summary_data = frappe.get_all("Web Page View", fields=['is_unique', 'count(*) as count'], filters=self.query_filters, group_by="is_unique") + + total_count = 0 + unique_count = 0 + for data in summary_data: + if data.get('is_unique'): + unique_count = data.get('count') + total_count += data.get('count') + report_summary = [ + { + "value": total_count, + "label": "Total Page Views", + "datatype": "Int", + }, + { + "value": unique_count, + "label": "Unique Page Views", + "datatype": "Int", + }, + + ] + return report_summary \ No newline at end of file From 9b185802aaab09aa84dff1fce76292ea1edcdc54 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 27 Apr 2020 18:56:12 +0530 Subject: [PATCH 011/274] fix: fix calculation of dates and values for dashboard charts --- .../dashboard_chart/dashboard_chart.py | 108 ++++-------------- .../dashboard_chart/test_dashboard_chart.py | 58 +++++++--- 2 files changed, 64 insertions(+), 102 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 7bed8f4504..4502748504 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -128,7 +128,6 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date): to_date = datetime.datetime.now() doctype = chart.document_type - unit_function = get_unit_function(doctype, chart.based_on, timegrain) datefield = chart.based_on aggregate_function = get_aggregate_function(chart.chart_type) value_field = chart.value_based_on or '1' @@ -141,23 +140,18 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date): data = frappe.db.get_all( doctype, fields = [ - 'extract(year from `tab{doctype}`.{datefield}) as _year'.format(doctype=doctype, datefield=datefield), - '{} as _unit'.format(unit_function), + '{} as _unit'.format(datefield), '{aggregate_function}({value_field})'.format(aggregate_function=aggregate_function, value_field=value_field), ], filters = filters, - group_by = '_year, _unit', - order_by = '_year asc, _unit asc', + group_by = '_unit', + order_by = '_unit asc', as_list = True, ignore_ifnull = True ) + result = get_result(data, timegrain, from_date, to_date) - # result given as year, unit -> convert it to end of period of that unit - result = convert_to_dates(data, timegrain) - - # add missing data points for periods where there was no result - result = add_missing_values(result, timegrain, timespan, from_date, to_date) chart_config = { "labels": [formatdate(r[0].strftime('%Y-%m-%d')) for r in result], "datasets": [{ @@ -217,75 +211,22 @@ def get_aggregate_function(chart_type): }[chart_type] -def convert_to_dates(data, timegrain): - """ Converts individual dates within data to the end of period """ - result = [] - for d in data: - if d[2] != 0: - if timegrain == 'Daily': - result.append([add_to_date('{:d}-01-01'.format(int(d[0])), days = d[1] - 1), d[2]]) - elif timegrain == 'Weekly': - result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), weeks = d[1] + 1), days = -1), d[2]]) - elif timegrain == 'Monthly': - result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), months=d[1]), days = -1), d[2]]) - elif timegrain == 'Quarterly': - result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), months=d[1] * 3), days = -1), d[2]]) - elif timegrain == 'Yearly': - result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), months=12), days = -1), d[2]]) - result[-1][0] = getdate(result[-1][0]) - - return result - -def get_unit_function(doctype, datefield, timegrain): - unit_function = '' - if timegrain=='Daily': - if frappe.db.db_type == 'mariadb': - unit_function = 'dayofyear(`tab{doctype}`.{datefield})'.format( - doctype=doctype, datefield=datefield) - else: - unit_function = 'extract(doy from `tab{doctype}`.{datefield})'.format( - doctype=doctype, datefield=datefield) - - else: - unit_function = 'extract({unit} from `tab{doctype}`.{datefield})'.format( - unit = timegrain[:-2].lower(), doctype=doctype, datefield=datefield) - - return unit_function - -def add_missing_values(data, timegrain, timespan, from_date, to_date): - # add missing intervals +def get_result(data, timegrain, from_date, to_date): + start_date = getdate(from_date) + end_date = getdate(to_date) result = [] - if timespan != 'All Time': - first_expected_date = get_period_ending(from_date, timegrain) - # fill out data before the first data point - first_data_point_date = data[0][0] if data else getdate(add_to_date(to_date, days=1)) - while first_data_point_date > first_expected_date: - result.append([first_expected_date, 0.0]) - first_expected_date = get_next_expected_date(first_expected_date, timegrain) + while start_date <= end_date: + next_date = get_next_expected_date(start_date, timegrain) + result.append([next_date, 0.0]) + start_date = next_date - # fill data points and missing points - for i, d in enumerate(data): - result.append(d) - - next_expected_date = get_next_expected_date(d[0], timegrain) - - if i < len(data)-1: - next_date = data[i+1][0] - else: - # already reached at end of data, see if we need any more dates - next_date = getdate(nowdate()) - - # if next data point is earler than the expected date - # need to fill out missing data points - while next_date > next_expected_date: - # fill missing value - result.append([next_expected_date, 0.0]) - next_expected_date = get_next_expected_date(next_expected_date, timegrain) - - # add date for the last period (if missing) - if result and get_period_ending(to_date, timegrain) > result[-1][0]: - result.append([get_period_ending(to_date, timegrain), 0.0]) + data_index = 0 + if data: + for i, d in enumerate(result): + while data_index < len(data) and getdate(data[data_index][0]) <= d[0]: + d[1] += data[data_index][1] + data_index += 1 return result @@ -314,17 +255,12 @@ def get_period_ending(date, timegrain): return getdate(date) def get_week_ending(date): - # fun fact: week ends on the day before 1st Jan of the year. - # for 2019 it is Monday + # week starts on monday + from datetime import timedelta + start = date - timedelta(days = date.weekday()) + end = start + timedelta(days=6) - week_of_the_year = int(date.strftime('%U')) - - if week_of_the_year == 52: - date = add_to_date(date, years=1) - # first day of next week - date = add_to_date('{}-01-01'.format(date.year), weeks = (week_of_the_year%52) + 1) - # last day of this week - return add_to_date(date, days=-1) + return end def get_month_ending(date): month_of_the_year = int(date.strftime('%m')) diff --git a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py index 4425c4fd45..dfc6edbf58 100644 --- a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py @@ -17,10 +17,9 @@ class TestDashboardChart(unittest.TestCase): self.assertEqual(get_period_ending('2019-04-10', 'Daily'), getdate('2019-04-10')) - # fun fact: week ends on the day before 1st Jan of the year. - # for 2019 it is Monday + # week starts on monday self.assertEqual(get_period_ending('2019-04-10', 'Weekly'), - getdate('2019-04-15')) + getdate('2019-04-14')) self.assertEqual(get_period_ending('2019-04-10', 'Monthly'), getdate('2019-04-30')) @@ -133,6 +132,34 @@ class TestDashboardChart(unittest.TestCase): frappe.db.rollback() + def test_weekly_dashboard_chart(self): + insert_test_records() + + if frappe.db.exists('Dashboard Chart', 'Test Weekly Dashboard Chart'): + frappe.delete_doc('Dashboard Chart', 'Test Weekly Dashboard Chart') + + frappe.get_doc(dict( + doctype = 'Dashboard Chart', + chart_name = 'Test Weekly Dashboard Chart', + chart_type = 'Sum', + document_type = 'Communication', + based_on = 'communication_date', + value_based_on = 'rating', + timespan = 'Select Date Range', + time_interval = 'Weekly', + from_date = datetime(2018, 12, 30), + to_date = datetime(2019, 1, 15), + filters_json = '[]', + timeseries = 1 + )).insert() + + result = get(chart_name ='Test Weekly Dashboard Chart', refresh = 1) + + self.assertEqual(result.get('datasets')[0].get('values'), [200.0, 400.0, 0.0]) + self.assertEqual(result.get('labels'), [formatdate('2019-01-06'), formatdate('2019-01-13'), formatdate('2019-01-20')]) + + frappe.db.rollback() + def test_group_by_chart_type(self): if frappe.db.exists('Dashboard Chart', 'Test Group By Dashboard Chart'): frappe.delete_doc('Dashboard Chart', 'Test Group By Dashboard Chart') @@ -155,17 +182,16 @@ class TestDashboardChart(unittest.TestCase): frappe.db.rollback() - def test_dashboard_with_single_doctype(self): - if frappe.db.exists('Dashboard Chart', 'Test Single DocType In Dashboard Chart'): - frappe.delete_doc('Dashboard Chart', 'Test Single DocType In Dashboard Chart') +def insert_test_records(): + create_new_communication(datetime(2019, 1, 10), 100) + create_new_communication(datetime(2019, 1, 6), 200) + create_new_communication(datetime(2019, 1, 8), 300) - chart_doc = frappe.get_doc(dict( - doctype = 'Dashboard Chart', - chart_name = 'Test Single DocType In Dashboard Chart', - chart_type = 'Count', - document_type = 'System Settings', - group_by_based_on = 'Created On', - filters_json = '{}', - )) - - self.assertRaises(frappe.ValidationError, chart_doc.insert) +def create_new_communication(date, rating): + communication = { + 'doctype': 'Communication', + 'subject': 'Test Communication', + 'rating': rating, + 'communication_date': date + } + frappe.get_doc(communication).insert() From 19dc0a65b06e75def4d5b755eefb00eb805c64ab Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 13 Mar 2020 17:16:52 +0530 Subject: [PATCH 012/274] 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 013/274] 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 014/274] 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 015/274] 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 016/274] 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 017/274] 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 018/274] 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 019/274] 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 020/274] 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 021/274] 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 022/274] 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 023/274] 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 024/274] 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 025/274] 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 026/274] 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 027/274] 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 028/274] 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 029/274] 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 030/274] 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 031/274] 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 032/274] 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 033/274] 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 b7408102411e4375aa1a155b3ee2bc809782aebc Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 4 May 2020 14:00:14 +0530 Subject: [PATCH 034/274] feat(blog settings): social share settings --- .../doctype/blog_settings/blog_settings.json | 182 ++++++------------ .../doctype/social_link_settings/__init__.py | 0 .../social_link_settings.json | 43 +++++ .../social_link_settings.py | 10 + 4 files changed, 107 insertions(+), 128 deletions(-) create mode 100644 frappe/website/doctype/social_link_settings/__init__.py create mode 100644 frappe/website/doctype/social_link_settings/social_link_settings.json create mode 100644 frappe/website/doctype/social_link_settings/social_link_settings.py diff --git a/frappe/website/doctype/blog_settings/blog_settings.json b/frappe/website/doctype/blog_settings/blog_settings.json index 1ee974d850..f0e51de170 100644 --- a/frappe/website/doctype/blog_settings/blog_settings.json +++ b/frappe/website/doctype/blog_settings/blog_settings.json @@ -1,139 +1,65 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-03-11 17:48:16", - "custom": 0, - "description": "Blog Settings", - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 0, + "actions": [], + "creation": "2013-03-11 17:48:16", + "description": "Blog Settings", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "blog_title", + "blog_introduction", + "writers_introduction", + "section_break_4", + "social_share_settings" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "blog_title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Blog Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "blog_title", + "fieldtype": "Data", + "label": "Blog Title" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "blog_introduction", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Blog Introduction", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "blog_introduction", + "fieldtype": "Small Text", + "label": "Blog Introduction" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "writers_introduction", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Writers Introduction", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "writers_introduction", + "fieldtype": "Small Text", + "label": "Writers Introduction" + }, + { + "collapsible": 1, + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "social_share_settings", + "fieldtype": "Table", + "label": "Social Share Settings", + "options": "Social Link Settings" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-cog", - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2016-12-29 14:40:41.629468", - "modified_by": "Administrator", - "module": "Website", - "name": "Blog Settings", - "owner": "Administrator", + ], + "icon": "fa fa-cog", + "idx": 1, + "issingle": 1, + "links": [], + "modified": "2020-05-04 09:10:41.815238", + "modified_by": "Administrator", + "module": "Website", + "name": "Blog Settings", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Website Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Website Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "track_changes": 1, - "track_seen": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/website/doctype/social_link_settings/__init__.py b/frappe/website/doctype/social_link_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/website/doctype/social_link_settings/social_link_settings.json b/frappe/website/doctype/social_link_settings/social_link_settings.json new file mode 100644 index 0000000000..459c98eed6 --- /dev/null +++ b/frappe/website/doctype/social_link_settings/social_link_settings.json @@ -0,0 +1,43 @@ +{ + "actions": [], + "creation": "2020-04-30 07:39:33.095554", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "social_link_type", + "color", + "background_color" + ], + "fields": [ + { + "fieldname": "social_link_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Social Link Type", + "options": "\nfacebook\nlinkedin\ntwitter\nemail" + }, + { + "fieldname": "color", + "fieldtype": "Color", + "in_list_view": 1, + "label": "Color" + }, + { + "fieldname": "background_color", + "fieldtype": "Color", + "in_list_view": 1, + "label": "Background Color" + } + ], + "istable": 1, + "links": [], + "modified": "2020-05-01 23:55:04.731518", + "modified_by": "Administrator", + "module": "Website", + "name": "Social Link Settings", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/frappe/website/doctype/social_link_settings/social_link_settings.py b/frappe/website/doctype/social_link_settings/social_link_settings.py new file mode 100644 index 0000000000..b382afac99 --- /dev/null +++ b/frappe/website/doctype/social_link_settings/social_link_settings.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 SocialLinkSettings(Document): + pass From ef3e3685e90449f3379102833f40d6c7d6a50674 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 4 May 2020 18:01:14 +0530 Subject: [PATCH 035/274] feat(about): add social media links --- frappe/public/js/frappe/ui/toolbar/about.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frappe/public/js/frappe/ui/toolbar/about.js b/frappe/public/js/frappe/ui/toolbar/about.js index 13ba4836fb..87462916c2 100644 --- a/frappe/public/js/frappe/ui/toolbar/about.js +++ b/frappe/public/js/frappe/ui/toolbar/about.js @@ -9,6 +9,12 @@ frappe.ui.misc.about = function() { Website: https://frappe.io

\

\ Source: https://github.com/frappe

\ +

\ + Linkedin: https://linkedin.com/company/frappe-tech

\ +

\ + Facebook: https://facebook.com/erpnext

\ +

\ + Twitter: https://twitter.com/erpnext

\
\

Installed Apps

\
Loading versions...
\ From 77d2c3d8cfaaa61aa7c8984f6e5ec3640c6ab020 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 5 May 2020 13:43:34 +0530 Subject: [PATCH 036/274] feat: added patch for is unique --- frappe/patches.txt | 1 + .../patches/v13_0/set_unique_for_page_view.py | 6 +++++ .../doctype/web_page_view/web_page_view.json | 24 ++++++++++++++----- 3 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 frappe/patches/v13_0/set_unique_for_page_view.py diff --git a/frappe/patches.txt b/frappe/patches.txt index a086fa6f4a..3d8b0732d2 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -278,3 +278,4 @@ frappe.patches.v13_0.set_path_for_homepage_in_web_page_view frappe.patches.v13_0.migrate_translation_column_data frappe.patches.v13_0.set_read_times frappe.patches.v13_0.remove_web_view +frappe.patches.v13_0.set_unique_for_page_view \ No newline at end of file diff --git a/frappe/patches/v13_0/set_unique_for_page_view.py b/frappe/patches/v13_0/set_unique_for_page_view.py new file mode 100644 index 0000000000..2a084e52e3 --- /dev/null +++ b/frappe/patches/v13_0/set_unique_for_page_view.py @@ -0,0 +1,6 @@ +import frappe + +def execute(): + frappe.reload_doc('website', 'doctype', 'web_page_view', force=True) + site_url = frappe.utils.get_site_url(frappe.local.site) + frappe.db.sql("""UPDATE `tabWeb Page View` set is_unique=1 where referrer LIKE '%{0}%'""".format(site_url)) diff --git a/frappe/website/doctype/web_page_view/web_page_view.json b/frappe/website/doctype/web_page_view/web_page_view.json index 7a1a210d62..4243df39b1 100644 --- a/frappe/website/doctype/web_page_view/web_page_view.json +++ b/frappe/website/doctype/web_page_view/web_page_view.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_import": 1, "creation": "2020-04-15 22:54:46.009703", "doctype": "DocType", "editable_grid": 1, @@ -9,7 +10,9 @@ "referrer", "browser", "browser_version", - "date" + "is_unique", + "time_zone", + "user_agent" ], "fields": [ { @@ -39,15 +42,24 @@ "set_only_once": 1 }, { - "fieldname": "date", - "fieldtype": "Datetime", - "label": "Date", - "set_only_once": 1 + "fieldname": "is_unique", + "fieldtype": "Data", + "label": "Is Unique" + }, + { + "fieldname": "time_zone", + "fieldtype": "Data", + "label": "Time Zone" + }, + { + "fieldname": "user_agent", + "fieldtype": "Data", + "label": "User Agent" } ], "in_create": 1, "links": [], - "modified": "2020-04-15 23:31:27.517793", + "modified": "2020-05-05 14:11:24.718770", "modified_by": "Administrator", "module": "Website", "name": "Web Page View", From ab9e596f627dc70dab273d1b8485796559ea23a3 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 5 May 2020 15:06:14 +0530 Subject: [PATCH 037/274] feat: do not track 404 --- frappe/www/404.html | 6 ++++-- frappe/www/website_script.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/www/404.html b/frappe/www/404.html index 47685c45d0..dc178dbdc8 100644 --- a/frappe/www/404.html +++ b/frappe/www/404.html @@ -15,7 +15,9 @@ html, body { } {% include "templates/styles/card_style.css" %} - +
{{_("Page Missing or Moved")}} @@ -29,4 +31,4 @@ html, body { background-color: #f5f7fa; } -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/frappe/www/website_script.js b/frappe/www/website_script.js index 7fdc2e94d6..e31b6812d5 100644 --- a/frappe/www/website_script.js +++ b/frappe/www/website_script.js @@ -14,7 +14,7 @@ ga('send', 'pageview'); {%- endif %} {% if enable_view_tracking %} - if (navigator.doNotTrack != 1) { + if (navigator.doNotTrack != 1 && !window.is_404) { frappe.ready(() => { let browser = frappe.utils.get_browser(); frappe.call("frappe.website.doctype.web_page_view.web_page_view.make_view_log", { From fb66f85d6364f26b4d1cc4281aed6ac80ee8bdb7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 5 May 2020 15:06:48 +0530 Subject: [PATCH 038/274] feat: update website analytics report --- .../website_analytics/website_analytics.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/frappe/website/report/website_analytics/website_analytics.py b/frappe/website/report/website_analytics/website_analytics.py index 8b2d5b3806..b821cbff1f 100644 --- a/frappe/website/report/website_analytics/website_analytics.py +++ b/frappe/website/report/website_analytics/website_analytics.py @@ -17,8 +17,8 @@ class WebsiteAnalytics(object): def run(self): columns = self.get_columns() data = self.get_data() - summary = self.get_report_summary() chart = self.get_chart_data() + summary = self.get_report_summary() return columns, data, None, chart, summary def get_columns(self): @@ -56,7 +56,7 @@ class WebsiteAnalytics(object): field, date_format = _get_field_for_chart(self.filters.range) - data = frappe.db.sql(""" + self.chart_data = frappe.db.sql(""" SELECT DATE_FORMAT({0}, %s) as date, COUNT(*) as count, @@ -65,9 +65,9 @@ class WebsiteAnalytics(object): WHERE creation BETWEEN %s AND %s GROUP BY DATE_FORMAT({0}, %s) ORDER BY creation - """.format(field), (date_format, self.filters.from_date, self.filters.to_date, date_format), as_dict=1, debug=1) + """.format(field), (date_format, self.filters.from_date, self.filters.to_date, date_format), as_dict=1) - return self.prepare_chart_data(data) + return self.prepare_chart_data(self.chart_data) def prepare_chart_data(self, data): date_range = get_date_range(self.filters.from_date, self.filters.to_date, self.filters.range) @@ -121,14 +121,12 @@ class WebsiteAnalytics(object): def get_report_summary(self): - summary_data = frappe.get_all("Web Page View", fields=['is_unique', 'count(*) as count'], filters=self.query_filters, group_by="is_unique") - total_count = 0 unique_count = 0 - for data in summary_data: - if data.get('is_unique'): - unique_count = data.get('count') + for data in self.chart_data: + unique_count += data.get('unique_count') total_count += data.get('count') + report_summary = [ { "value": total_count, From 606c57be5d849335513025d6ef12e1ab50613c1b Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 5 May 2020 17:35:26 +0530 Subject: [PATCH 039/274] fix: error message --- frappe/desk/form/assign_to.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index be83dc957e..6178690b1f 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -41,6 +41,9 @@ def add(args=None): if not args: args = frappe.local.form_dict + users_with_duplicate_todo = [] + shared_with_users = [] + for assign_to in json.loads(args.get("assign_to")): filters = { "reference_type": args['doctype'], @@ -50,8 +53,7 @@ def add(args=None): } if frappe.get_all("ToDo", filters=filters): - if not args.get("bulk_assign"): - frappe.throw(_("Already in user's ToDo list"), DuplicateToDoError) + users_with_duplicate_todo.append(assign_to) else: from frappe.utils import nowdate @@ -80,7 +82,7 @@ def add(args=None): # if assignee does not have permissions, share if not frappe.has_permission(doc=doc, user=assign_to): frappe.share.add(doc.doctype, doc.name, assign_to) - frappe.msgprint(_('Shared with user {0} with read access').format(assign_to, alert=True)) + shared_with_users.append(assign_to) # make this document followed by assigned user follow_document(args['doctype'], args['name'], assign_to) @@ -89,6 +91,14 @@ def add(args=None): notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN', description=args.get("description")) + if shared_with_users: + user_list = format_message_for_assign_to(shared_with_users) + frappe.msgprint(_("Shared with the following Users with Read access:{0}").format(user_list, alert=True)) + + if users_with_duplicate_todo: + user_list = format_message_for_assign_to(users_with_duplicate_todo) + frappe.msgprint(_("Already in the following Users ToDo list:{0}").format(user_list, alert=True)) + return get(args) @frappe.whitelist() @@ -186,3 +196,5 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE', enqueue_create_notification(owner, notification_doc) +def format_message_for_assign_to(users): + return "

" + "
".join(users) \ No newline at end of file From f684756fba34fcd5ac1099ea7938f64fe08e574d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 5 May 2020 18:22:04 +0530 Subject: [PATCH 040/274] feat: added website analytics report --- .../website_analytics/website_analytics.js | 2 - .../website_analytics/website_analytics.py | 116 +++++++++++++++--- 2 files changed, 98 insertions(+), 20 deletions(-) diff --git a/frappe/website/report/website_analytics/website_analytics.js b/frappe/website/report/website_analytics/website_analytics.js index b607a16eb4..7e051afa8c 100644 --- a/frappe/website/report/website_analytics/website_analytics.js +++ b/frappe/website/report/website_analytics/website_analytics.js @@ -9,14 +9,12 @@ frappe.query_reports["Website Analytics"] = { label: __("From Date"), fieldtype: "Date", default: frappe.datetime.add_days(frappe.datetime.now_date(true), -7), - reqd: 1 }, { fieldname:"to_date", label: __("To Date"), fieldtype: "Date", default: frappe.datetime.now_date(true), - reqd: 1 }, { fieldname: "range", diff --git a/frappe/website/report/website_analytics/website_analytics.py b/frappe/website/report/website_analytics/website_analytics.py index b821cbff1f..694bc9e797 100644 --- a/frappe/website/report/website_analytics/website_analytics.py +++ b/frappe/website/report/website_analytics/website_analytics.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +from datetime import datetime from frappe.utils.dateutils import get_date_range def execute(filters=None): @@ -11,6 +12,16 @@ def execute(filters=None): class WebsiteAnalytics(object): def __init__(self, filters=None): self.filters = frappe._dict(filters or {}) + + if not self.filters.to_date: + self.filters.to_date = datetime.now() + + if not self.filters.from_date: + self.filters.from_date = frappe.utils.add_days(self.filters.to_date, -7) + + if not self.filters.range: + self.filters.range = "D" + self.filters.to_date = frappe.utils.add_days(self.filters.to_date, 1) self.query_filters = {'creation': ['between', [self.filters.from_date, self.filters.to_date]]} @@ -19,7 +30,8 @@ class WebsiteAnalytics(object): data = self.get_data() chart = self.get_chart_data() summary = self.get_report_summary() - return columns, data, None, chart, summary + + return columns, data[:250], None, chart, summary def get_columns(self): return [ @@ -34,38 +46,105 @@ class WebsiteAnalytics(object): "label": "Page Views", "fieldtype": "Int", "width": 150 + }, + { + "fieldname": "unique_count", + "label": "Unique Visitors", + "fieldtype": "Int", + "width": 150 } ] def get_data(self): - data = frappe.get_all("Web Page View", fields=['path', 'count(*) as count'], filters=self.query_filters, group_by="path", order_by='count desc') + pg_query = """ + SELECT + path, + COUNT(*) as count, + COUNT(CASE WHEN CAST(is_unique as Integer) = 1 THEN 1 END) as unique_count + FROM `tabWeb Page View` + WHERE coalesce("tabWeb Page View".creation, '0001-01-01') BETWEEN %s AND %s + GROUP BY path + ORDER BY count desc + """ + + mariadb_query = """ + SELECT + path, + COUNT(*) as count, + COUNT(CASE WHEN is_unique = 1 THEN 1 END) as unique_count + FROM `tabWeb Page View` + WHERE creation BETWEEN %s AND %s + GROUP BY path + ORDER BY count desc + """ + + data = frappe.db.multisql({ + "mariadb": mariadb_query, + "postgres": pg_query + }, (self.filters.from_date, self.filters.to_date)) return data - def get_chart_data(self): - def _get_field_for_chart(filters_range): - field = 'creation' - date_format = '%Y-%m-%d' + def _get_query_for_mariadb(self): + filters_range = self.filters.range + field = 'creation' + date_format = '%Y-%m-%d' - if filters_range == "W": - field = 'ADDDATE(creation, INTERVAL 1-DAYOFWEEK(creation) DAY)' + if filters_range == "W": + field = 'ADDDATE(creation, INTERVAL 1-DAYOFWEEK(creation) DAY)' - elif filters_range == "M": - date_format = '%Y-%m-01' + elif filters_range == "M": + date_format = '%Y-%m-01' - return field, date_format - - field, date_format = _get_field_for_chart(self.filters.range) - - self.chart_data = frappe.db.sql(""" + query = """ SELECT DATE_FORMAT({0}, %s) as date, COUNT(*) as count, - count(CASE WHEN is_unique = 1 THEN 1 END) as unique_count + COUNT(CASE WHEN is_unique = 1 THEN 1 END) as unique_count FROM `tabWeb Page View` WHERE creation BETWEEN %s AND %s GROUP BY DATE_FORMAT({0}, %s) ORDER BY creation - """.format(field), (date_format, self.filters.from_date, self.filters.to_date, date_format), as_dict=1) + """.format(field) + + values = (date_format, self.filters.from_date, self.filters.to_date, date_format) + + return query, values + + def _get_query_for_postgres(self): + filters_range = self.filters.range + field = 'creation' + granularity = 'day' + + if filters_range == "W": + granularity = 'week' + + elif filters_range == "M": + granularity = 'day' + + query = """ + SELECT + DATE_TRUNC(%s, {0}) as date, + COUNT(*) as count, + COUNT(CASE WHEN CAST(is_unique as Integer) = 1 THEN 1 END) as unique_count + FROM "tabWeb Page View" + WHERE coalesce("tabWeb Page View".{0}, '0001-01-01') BETWEEN %s AND %s + GROUP BY date_trunc(%s, {0}) + ORDER BY date + """.format(field) + + values = (granularity, self.filters.from_date, self.filters.to_date, granularity) + + return query, values + + def get_chart_data(self): + current_dialect = frappe.db.db_type or 'mariadb' + + if current_dialect == 'mariadb': + query, values = self._get_query_for_mariadb() + else: + query, values = self._get_query_for_postgres() + + self.chart_data = frappe.db.sql(query, values=values, as_dict=1) return self.prepare_chart_data(self.chart_data) @@ -114,7 +193,8 @@ class WebsiteAnalytics(object): }, 'axisOptions': { 'xIsSeries': 1 - } + }, + 'colors': ['#7cd6fd', '#5e64ff'] } return chart From 83380f25475edfb3bec8d79cad7013d78443b9b4 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 5 May 2020 18:22:47 +0530 Subject: [PATCH 041/274] feat: added website analytics chart as JSON fixture --- frappe/model/sync.py | 5 ++-- .../website_analytics/website_analytics.json | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 frappe/website/dashboard_chart/website_analytics/website_analytics.json diff --git a/frappe/model/sync.py b/frappe/model/sync.py index c2acb59f63..3dba994134 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -52,7 +52,8 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe ("desk", "desk_card"), ("desk", "desk_chart"), ("desk", "desk_shortcut"), - ("desk", "desk_page")): + ("desk", "desk_page"), + ("desk", "dashboard_chart")): files.append(os.path.join(frappe.get_app_path("frappe"), d[0], "doctype", d[1], d[1] + ".json")) @@ -82,7 +83,7 @@ def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=F document_types = ['doctype', 'page', 'report', 'dashboard_chart_source', 'print_format', 'website_theme', 'web_form', 'web_template', 'notification', 'print_style', 'data_migration_mapping', 'data_migration_plan', 'desk_page', - 'onboarding_step', 'onboarding'] + 'onboarding_step', 'onboarding', 'dashboard_chart'] for doctype in document_types: doctype_path = os.path.join(start_path, doctype) diff --git a/frappe/website/dashboard_chart/website_analytics/website_analytics.json b/frappe/website/dashboard_chart/website_analytics/website_analytics.json new file mode 100644 index 0000000000..eeeb1a11f9 --- /dev/null +++ b/frappe/website/dashboard_chart/website_analytics/website_analytics.json @@ -0,0 +1,24 @@ +{ + "chart_name": "Website Analytics", + "chart_type": "Report", + "creation": "2020-05-05 18:14:19.369181", + "custom_options": "{\"type\": \"line\", \"lineOptions\": {\"regionFill\": 1}, \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"colors\": [\"#7cd6fd\", \"#5e64ff\"], \"tooltipOptions\": {}}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "filters_json": "{}", + "group_by_type": "Count", + "idx": 0, + "is_custom": 1, + "is_public": 1, + "modified": "2020-05-05 18:16:47.383649", + "modified_by": "Administrator", + "name": "Website Analytics", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Website Analytics", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Line", + "y_axis": [] +} \ No newline at end of file From 06e4e6a3b933b28e29bcd7e01bf1027939a236a1 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 5 May 2020 18:29:42 +0530 Subject: [PATCH 042/274] feat: add analytics chart to desk --- frappe/website/desk_page/website/website.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frappe/website/desk_page/website/website.json b/frappe/website/desk_page/website/website.json index 1c6066d21e..c42a17d404 100644 --- a/frappe/website/desk_page/website/website.json +++ b/frappe/website/desk_page/website/website.json @@ -27,7 +27,11 @@ } ], "category": "Modules", - "charts": [], + "charts": [ + { + "chart_name": "Website Analytics" + } + ], "creation": "2020-03-02 14:13:51.089373", "developer_mode_only": 0, "disable_user_customization": 0, @@ -37,7 +41,7 @@ "idx": 0, "is_standard": 1, "label": "Website", - "modified": "2020-04-26 13:03:49.094728", + "modified": "2020-05-05 18:17:13.232473", "modified_by": "Administrator", "module": "Website", "name": "Website", From e71b14c17dc9bf6b5ba7f22f8ed2899fa175e5f6 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 5 May 2020 19:53:50 +0530 Subject: [PATCH 043/274] fix: Schedule Date for Daily and Weekly frequency --- frappe/automation/doctype/auto_repeat/auto_repeat.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index bfcaf684d6..c447c55727 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -299,17 +299,20 @@ def get_next_schedule_date(schedule_date, frequency, start_date, repeat_on_day=N next_date = get_next_date(start_date, month_count) else: days = 7 if frequency == 'Weekly' else 1 - next_date = add_days(start_date, days) + next_date = add_days(schedule_date, days) # next schedule date should be after or on current date if not for_full_schedule: while getdate(next_date) < getdate(today()): if month_count: month_count += month_map.get(frequency) - next_date = get_next_date(start_date, month_count, day_count) + next_date = get_next_date(start_date, month_count, day_count) + elif days: + next_date = add_days(next_date, days) return next_date + def get_next_date(dt, mcount, day=None): dt = getdate(dt) dt += relativedelta(months=mcount, day=day) From 6ea8881b29106edf45155b9ac565d95f9672ded2 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 May 2020 12:48:15 +0530 Subject: [PATCH 044/274] fix: remove exact phrase search --- frappe/utils/global_search.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index 3c4b9583f8..0272ae16f4 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -81,10 +81,10 @@ def rebuild_for_doctype(doctype): return filters meta = frappe.get_meta(doctype) - + if cint(meta.issingle) == 1: return - + if cint(meta.istable) == 1: parent_doctypes = frappe.get_all("DocField", fields="parent", filters={ "fieldtype": ["in", frappe.model.table_fields], @@ -506,15 +506,13 @@ def web_search(text, scope=None, start=0, limit=20): mariadb_conditions = postgres_conditions = ' '.join([published_condition, scope_condition]) # https://mariadb.com/kb/en/library/full-text-index-overview/#in-boolean-mode - text = '"{}"'.format(text) - mariadb_conditions += 'MATCH(`content`) AGAINST (%(text)s IN BOOLEAN MODE)' - postgres_conditions += 'TO_TSVECTOR("content") @@ PLAINTO_TSQUERY(%(text)s)' + mariadb_conditions += 'MATCH(`content`) AGAINST ({} IN BOOLEAN MODE)'.format(frappe.db.escape('+' + text + '*')) + postgres_conditions += 'TO_TSVECTOR("content") @@ PLAINTO_TSQUERY({})'.format(frappe.db.escape(text)) values = { "scope": "".join([scope, "%"]) if scope else '', "limit": limit, - "start": start, - "text": text + "start": start } result = frappe.db.multisql({ From e91050b0e496d872d08a7898fb2124b3a64424ef Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 May 2020 13:14:22 +0530 Subject: [PATCH 045/274] 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 046/274] 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 04a59352f7ccaf66758632e15415395844246280 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 May 2020 17:40:50 +0530 Subject: [PATCH 047/274] fix: test cases --- frappe/desk/form/assign_to.py | 2 +- frappe/public/js/frappe/form/sidebar/assign_to.js | 2 +- frappe/tests/test_assign.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 6178690b1f..6c8cd5dfbd 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -44,7 +44,7 @@ def add(args=None): users_with_duplicate_todo = [] shared_with_users = [] - for assign_to in json.loads(args.get("assign_to")): + for assign_to in frappe.parse_json(args.get("assign_to")): filters = { "reference_type": args['doctype'], "reference_name": args['name'], diff --git a/frappe/public/js/frappe/form/sidebar/assign_to.js b/frappe/public/js/frappe/form/sidebar/assign_to.js index 84fde564c0..95ceb246e6 100644 --- a/frappe/public/js/frappe/form/sidebar/assign_to.js +++ b/frappe/public/js/frappe/form/sidebar/assign_to.js @@ -130,7 +130,7 @@ frappe.ui.form.AssignToDialog = Class.extend({ this.set_description_from_doc(); }, make: function() { - let me = this + let me = this; me.dialog = new frappe.ui.Dialog({ title: __('Add to ToDo'), diff --git a/frappe/tests/test_assign.py b/frappe/tests/test_assign.py index f32e3c9272..439e1546c0 100644 --- a/frappe/tests/test_assign.py +++ b/frappe/tests/test_assign.py @@ -60,7 +60,7 @@ class TestAssign(unittest.TestCase): def assign(doc, user): return frappe.desk.form.assign_to.add({ - "assign_to": user, + "assign_to": [user], "doctype": doc.doctype, "name": doc.name, "description": 'test', From 2c12af23191518a625b77c7cf6e715826b709db8 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 7 May 2020 12:12:04 +0530 Subject: [PATCH 048/274] fix: consider permissions for charts and cards --- frappe/desk/doctype/dashboard_chart/dashboard_chart.py | 4 ++-- frappe/desk/doctype/number_card/number_card.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 7ddb3d98f0..c03f6f8156 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -144,7 +144,7 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date): filters.append([doctype, datefield, '>=', from_date, False]) filters.append([doctype, datefield, '<=', to_date, False]) - data = frappe.db.get_all( + data = frappe.db.get_list( doctype, fields = [ 'extract(year from `tab{doctype}`.{datefield}) as _year'.format(doctype=doctype, datefield=datefield), @@ -182,7 +182,7 @@ def get_group_by_chart_config(chart, filters): group_by_field = chart.group_by_based_on doctype = chart.document_type - data = frappe.db.get_all( + data = frappe.db.get_list( doctype, fields = [ '{} as name'.format(group_by_field), diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py index 2c5655beda..2c072f44c4 100644 --- a/frappe/desk/doctype/number_card/number_card.py +++ b/frappe/desk/doctype/number_card/number_card.py @@ -65,7 +65,7 @@ def get_result(doc, to_date=None): if to_date: filters.append([doc.document_type, 'creation', '<', to_date, False]) - res = frappe.db.get_all(doc.document_type, fields=fields, filters=filters) + res = frappe.db.get_list(doc.document_type, fields=fields, filters=filters) number = res[0]['result'] if res else 0 return cint(number) From fff07548fd0e05b5e4232cbda25237a5137ff012 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 7 May 2020 14:00:32 +0530 Subject: [PATCH 049/274] fix: show restricted button to indicate permission restrictions --- frappe/core/page/dashboard/dashboard.css | 5 +++++ frappe/core/page/dashboard/dashboard.js | 7 +++++++ frappe/public/js/frappe/list/list_view.js | 2 +- .../frappe/views/dashboard/dashboard_view.js | 8 +++++++- frappe/public/less/dashboard_view.less | 12 ++++++++++++ frappe/public/less/desk.less | 17 +++++++++++++++++ frappe/public/less/list.less | 18 +----------------- 7 files changed, 50 insertions(+), 19 deletions(-) diff --git a/frappe/core/page/dashboard/dashboard.css b/frappe/core/page/dashboard/dashboard.css index e69de29bb2..e1687a0675 100644 --- a/frappe/core/page/dashboard/dashboard.css +++ b/frappe/core/page/dashboard/dashboard.css @@ -0,0 +1,5 @@ +.restricted-button { + cursor: default; + position: relative; + right: -5px; +} \ No newline at end of file diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index 222a31a863..0634d6518c 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -26,6 +26,13 @@ class Dashboard {
`).appendTo(this.wrapper.find(".page-content").empty()); this.container = this.wrapper.find(".dashboard-graph"); this.page = wrapper.page; + + this.page.set_title_sub( + $(``) + ) } show() { diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index af9a3c0221..dd9362d664 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -139,7 +139,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { show_restricted_list_indicator_if_applicable() { const match_rules_list = frappe.perm.get_match_rules(this.doctype); if (match_rules_list.length) { - this.restricted_list = $(``) + this.restricted_list = $(``) .prepend('') .click(() => this.show_restrictions(match_rules_list)) .appendTo(this.page.page_form); diff --git a/frappe/public/js/frappe/views/dashboard/dashboard_view.js b/frappe/public/js/frappe/views/dashboard/dashboard_view.js index 13c44d2130..83f45da5be 100644 --- a/frappe/public/js/frappe/views/dashboard/dashboard_view.js +++ b/frappe/public/js/frappe/views/dashboard/dashboard_view.js @@ -41,7 +41,13 @@ frappe.views.DashboardView = class DashboardView extends frappe.views.ListView { this.$dashboard_page = this.$page.find('.layout-main-section-wrapper').addClass('dashboard-page'); this.$page.find('.page-form').empty().html( `
-
${dashboard_name}
+
+ ${dashboard_name} +
+
${__('Customize')}
${__('Reset')} diff --git a/frappe/public/less/dashboard_view.less b/frappe/public/less/dashboard_view.less index 874e4e2e36..5af532b9c7 100644 --- a/frappe/public/less/dashboard_view.less +++ b/frappe/public/less/dashboard_view.less @@ -28,6 +28,18 @@ display: flex; justify-content: space-between; width: 100%; + + .header-title { + line-height: 1.5em; + vertical-align: text-bottom; + } + + .restricted-button { + cursor: default; + position: relative; + right: 5px; + top: -3px; + } } .customize-dashboard { diff --git a/frappe/public/less/desk.less b/frappe/public/less/desk.less index 6d44fc5192..e85367435e 100644 --- a/frappe/public/less/desk.less +++ b/frappe/public/less/desk.less @@ -316,6 +316,23 @@ li.user-progress { } } +.restricted-button { + padding: 0px 10px; + border: 1px solid @yellow; + height: 25px; + font-weight: 600; + border-radius: 5px; + background: lightyellow; + color: @text-light; + margin: auto 5px auto auto; + font-size: @text-small; + outline: 0; + .octicon { + padding-right: 5px; + font-size: 12px; + } +} + /* on small screens, show only icons on top */ @media (max-width: 767px) { .module-view-layout .nav-stacked > li { diff --git a/frappe/public/less/list.less b/frappe/public/less/list.less index 3294e24bf3..639e67e3a7 100644 --- a/frappe/public/less/list.less +++ b/frappe/public/less/list.less @@ -298,26 +298,10 @@ input.list-check-all, input.list-row-checkbox { .awesomplete > ul { min-width: 300px; } - .restricted-list { - padding: 0px 10px; - border: 1px solid @yellow; - height: 25px; - font-weight: 600; - border-radius: 5px; - background: lightyellow; - color: @text-light; - margin: auto 5px auto auto; - font-size: @text-small; - outline: 0; - .octicon { - padding-right: 5px; - font-size: 12px; - } - } } .frappe-rtl { - .restricted-list { + .restricted-button { margin: auto auto auto 5px; direction: ltr; } From eeaca0968f210c288e82961f0305b9316840f555 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 7 May 2020 16:03:04 +0530 Subject: [PATCH 050/274] feat: add styling and links for sharing blogs --- frappe/website/doctype/blog_post/blog_post.py | 23 +++++++++ .../blog_post/templates/blog_post.html | 50 ++++++++++++++----- .../doctype/blog_settings/blog_settings.js | 2 +- .../website_theme/standard/standard.json | 2 +- 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index be58bec842..d24c3a5e8e 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -61,6 +61,7 @@ class BlogPost(WebsiteGenerator): # temp fields context.full_name = get_fullname(self.owner) context.updated = global_date_format(self.published_on) + context.social_links = self.fetch_social_links_info() if self.blogger: context.blogger_info = frappe.get_doc("Blogger", self.blogger).as_dict() @@ -89,6 +90,28 @@ class BlogPost(WebsiteGenerator): {"name": "Blog", "route": "/blog"}, {"label": context.category.title, "route":context.category.route}] + + def fetch_social_links_info(self): + url = frappe.local.site + "/" +self.route + social_url_map = { + "twitter": "https://twitter.com/intent/tweet?text=" +self.title + "&url=" + url, + "facebook": "https://www.facebook.com/sharer.php?u=" + url, + "linkedin": "https://www.linkedin.com/sharing/share-offsite/?url=" + url, + "email": "mailto:?subject=" + self.title + "&body=" + url, + } + + social_link = [] + for link in frappe.get_cached_doc("Blog Settings").social_share_settings: + social_media = link.social_link_type + + social_link.append({ + 'icon': social_media if not social_media == 'email' else 'envelope', + 'url': social_url_map.get(social_media), + 'color': link.color, + 'background': link.background_color + }) + return social_link + def load_comments(self, context): context.comment_list = get_comment_list(self.doctype, self.name) diff --git a/frappe/website/doctype/blog_post/templates/blog_post.html b/frappe/website/doctype/blog_post/templates/blog_post.html index 4c4219ea54..f01d3a7e9b 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post.html +++ b/frappe/website/doctype/blog_post/templates/blog_post.html @@ -9,18 +9,30 @@
-

{{ title }}

-

- {{ frappe.format_date(published_on) }} - {% if read_time %} - · - {{ read_time }} min read - {% endif %} -

+ +

{{ title }}

+

+ {{ blog_intro }} +

+
+ +
+ {{ frappe.format_date(published_on) }} + {% if read_time %} + · + {{ read_time }} min read + {% endif %} + {% if social_links %} + + {% endif %} +
-

- {{ blog_intro }} -

{{ content }}
@@ -51,6 +63,20 @@ margin: 0 auto; font-size: 1.2rem; } - +.meta-info { + display: flex; + justify-content: space-between; + align-items: flex-end; +} +.social-links { + margin-right: 0px; +} +.social-links a { + font-size: 1.25rem; + margin: 0 5px 0 0; + padding: 5px; + width: 2.5rem; + text-align: center; +} {% endblock %} diff --git a/frappe/website/doctype/blog_settings/blog_settings.js b/frappe/website/doctype/blog_settings/blog_settings.js index b52c8862c5..90421b9550 100644 --- a/frappe/website/doctype/blog_settings/blog_settings.js +++ b/frappe/website/doctype/blog_settings/blog_settings.js @@ -5,4 +5,4 @@ frappe.ui.form.on('Blog Settings', { refresh: function(frm) { } -}); +}) \ No newline at end of file diff --git a/frappe/website/website_theme/standard/standard.json b/frappe/website/website_theme/standard/standard.json index 1729e4616a..db79bde0a8 100644 --- a/frappe/website/website_theme/standard/standard.json +++ b/frappe/website/website_theme/standard/standard.json @@ -10,7 +10,7 @@ "font_properties": "300,600", "footer": [], "idx": 26, - "modified": "2020-04-29 12:26:48.399125", + "modified": "2020-05-06 17:36:19.830993", "modified_by": "Administrator", "module": "Website", "name": "Standard", From fe911aa0c59251b8ebf25cd95cf263ce5a073b08 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 7 May 2020 18:25:17 +0530 Subject: [PATCH 051/274] 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 8a918fd54dce8c0ab4ad2326d2f6dc8257412d33 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 7 May 2020 19:18:08 +0530 Subject: [PATCH 052/274] feat: use new sync API --- frappe/model/sync.py | 5 ++- .../website_analytics/website_analytics.json | 24 ------------- frappe/website/dashboard_fixtures.py | 36 +++++++++++++++++++ 3 files changed, 38 insertions(+), 27 deletions(-) delete mode 100644 frappe/website/dashboard_chart/website_analytics/website_analytics.json create mode 100644 frappe/website/dashboard_fixtures.py diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 7991d11d15..320cc24677 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -55,8 +55,7 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe ("desk", "desk_card"), ("desk", "desk_chart"), ("desk", "desk_shortcut"), - ("desk", "desk_page"), - ("desk", "dashboard_chart")): + ("desk", "desk_page")): files.append(os.path.join(frappe.get_app_path("frappe"), d[0], "doctype", d[1], d[1] + ".json")) @@ -86,7 +85,7 @@ def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=F document_types = ['doctype', 'page', 'report', 'dashboard_chart_source', 'print_format', 'website_theme', 'web_form', 'web_template', 'notification', 'print_style', 'data_migration_mapping', 'data_migration_plan', 'desk_page', - 'onboarding_step', 'onboarding', 'dashboard_chart'] + 'onboarding_step', 'onboarding'] for doctype in document_types: doctype_path = os.path.join(start_path, doctype) diff --git a/frappe/website/dashboard_chart/website_analytics/website_analytics.json b/frappe/website/dashboard_chart/website_analytics/website_analytics.json deleted file mode 100644 index eeeb1a11f9..0000000000 --- a/frappe/website/dashboard_chart/website_analytics/website_analytics.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "chart_name": "Website Analytics", - "chart_type": "Report", - "creation": "2020-05-05 18:14:19.369181", - "custom_options": "{\"type\": \"line\", \"lineOptions\": {\"regionFill\": 1}, \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"colors\": [\"#7cd6fd\", \"#5e64ff\"], \"tooltipOptions\": {}}", - "docstatus": 0, - "doctype": "Dashboard Chart", - "filters_json": "{}", - "group_by_type": "Count", - "idx": 0, - "is_custom": 1, - "is_public": 1, - "modified": "2020-05-05 18:16:47.383649", - "modified_by": "Administrator", - "name": "Website Analytics", - "number_of_groups": 0, - "owner": "Administrator", - "report_name": "Website Analytics", - "time_interval": "Yearly", - "timeseries": 0, - "timespan": "Last Year", - "type": "Line", - "y_axis": [] -} \ No newline at end of file diff --git a/frappe/website/dashboard_fixtures.py b/frappe/website/dashboard_fixtures.py new file mode 100644 index 0000000000..4e5b5454bb --- /dev/null +++ b/frappe/website/dashboard_fixtures.py @@ -0,0 +1,36 @@ +import frappe + +def get_data(): + return frappe._dict({ + "dashboards": get_dashboards(), + "charts": get_charts(), + "number_cards": get_number_cards(), + }) + +def get_dashboards(): + return [{ + "name": "Website", + "dashboard_name": "Website", + "charts": [ + { "chart": "Website Analytics", "width": "Full" } + ] + }] + +def get_charts(): + return [{ + "chart_name": "Website Analytics", + "chart_type": "Report", + "custom_options": "{\"type\": \"line\", \"lineOptions\": {\"regionFill\": 1}, \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"colors\": [\"#7cd6fd\", \"#5e64ff\"], \"tooltipOptions\": {}}", + "doctype": "Dashboard Chart", + "filters_json": "{}", + "group_by_type": "Count", + "is_custom": 1, + "is_public": 1, + "name": "Website Analytics", + "number_of_groups": 0, + "report_name": "Website Analytics", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Line" + }] \ No newline at end of file From 80f6ff7f17c95f1046d7b377e5395a46ff403e42 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 7 May 2020 23:34:37 +0530 Subject: [PATCH 053/274] style: formatting fixes --- frappe/core/page/dashboard/dashboard.js | 2 +- frappe/public/less/desk.less | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index 0634d6518c..db2f8f8988 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -32,7 +32,7 @@ class Dashboard { ${__('Restricted')} `) - ) + ); } show() { diff --git a/frappe/public/less/desk.less b/frappe/public/less/desk.less index e85367435e..4c2c37c785 100644 --- a/frappe/public/less/desk.less +++ b/frappe/public/less/desk.less @@ -327,7 +327,7 @@ li.user-progress { margin: auto 5px auto auto; font-size: @text-small; outline: 0; - .octicon { + .octicon-lock { padding-right: 5px; font-size: 12px; } From 50bde698bb5848f3303a9fbb84634a0aac53d4d5 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 8 May 2020 15:11:21 +0530 Subject: [PATCH 054/274] fix: alignment issues on mobile --- frappe/website/doctype/blog_post/templates/blog_post.html | 4 +++- frappe/website/doctype/blog_settings/blog_settings.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/blog_post/templates/blog_post.html b/frappe/website/doctype/blog_post/templates/blog_post.html index f01d3a7e9b..12e5ccf2d7 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post.html +++ b/frappe/website/doctype/blog_post/templates/blog_post.html @@ -67,14 +67,16 @@ display: flex; justify-content: space-between; align-items: flex-end; + flex-wrap: wrap; } .social-links { margin-right: 0px; + margin-top: 1rem; } .social-links a { font-size: 1.25rem; margin: 0 5px 0 0; - padding: 5px; + padding: 5px 0; width: 2.5rem; text-align: center; } diff --git a/frappe/website/doctype/blog_settings/blog_settings.js b/frappe/website/doctype/blog_settings/blog_settings.js index 90421b9550..5ffc4ffc44 100644 --- a/frappe/website/doctype/blog_settings/blog_settings.js +++ b/frappe/website/doctype/blog_settings/blog_settings.js @@ -5,4 +5,4 @@ frappe.ui.form.on('Blog Settings', { refresh: function(frm) { } -}) \ No newline at end of file +}); \ No newline at end of file From 9e0b96c5c90239442a3240a74ae8d4378c1d9387 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 8 May 2020 18:21:18 +0530 Subject: [PATCH 055/274] fix: function not found --- frappe/website/dashboard_fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/dashboard_fixtures.py b/frappe/website/dashboard_fixtures.py index 4e5b5454bb..01f1376d43 100644 --- a/frappe/website/dashboard_fixtures.py +++ b/frappe/website/dashboard_fixtures.py @@ -4,7 +4,7 @@ def get_data(): return frappe._dict({ "dashboards": get_dashboards(), "charts": get_charts(), - "number_cards": get_number_cards(), + "number_cards": None, }) def get_dashboards(): From 62c1b74932294e232dcc214b75b55006e214f386 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 29 Apr 2020 17:52:02 +0530 Subject: [PATCH 056/274] 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 057/274] 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 058/274] 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 059/274] 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 060/274] 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 061/274] 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 062/274] 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 063/274] 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 064/274] 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 065/274] 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 066/274] 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 067/274] 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 068/274] 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 069/274] 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 be27513a56c69f6a70bcbf5b03b99847ea1415d9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 11 May 2020 12:09:54 +0530 Subject: [PATCH 070/274] feat: added driver.js with style overrides --- frappe/public/build.json | 5 +- frappe/public/js/frappe/ui/driver.js | 3 + frappe/public/less/driver.less | 180 +++++++++++++++++++++++++++ package.json | 1 + yarn.lock | 5 + 5 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 frappe/public/js/frappe/ui/driver.js create mode 100644 frappe/public/less/driver.less diff --git a/frappe/public/build.json b/frappe/public/build.json index d56907b558..e4c90cb708 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -112,7 +112,9 @@ "public/less/chat.less", "public/less/filters.less", "public/less/social.less", - "node_modules/frappe-charts/dist/frappe-charts.min.css" + "node_modules/frappe-charts/dist/frappe-charts.min.css", + "node_modules/driver.js/dist/driver.min.css", + "public/less/driver.less" ], "css/frappe-rtl.css": [ "public/css/bootstrap-rtl.css", @@ -244,6 +246,7 @@ "public/js/frappe/utils/energy_point_utils.js", "public/js/frappe/utils/dashboard_utils.js", "public/js/frappe/ui/chart.js", + "public/js/frappe/ui/driver.js", "public/js/frappe/barcode_scanner/index.js" ], "css/form.min.css": [ diff --git a/frappe/public/js/frappe/ui/driver.js b/frappe/public/js/frappe/ui/driver.js new file mode 100644 index 0000000000..98ed49ec05 --- /dev/null +++ b/frappe/public/js/frappe/ui/driver.js @@ -0,0 +1,3 @@ +import Driver from 'driver.js'; + +frappe.Driver = Driver; \ No newline at end of file diff --git a/frappe/public/less/driver.less b/frappe/public/less/driver.less new file mode 100644 index 0000000000..9dc7cde0b1 --- /dev/null +++ b/frappe/public/less/driver.less @@ -0,0 +1,180 @@ +@import "frappe/public/less/variables.less"; + +div#driver-popover-item { + .driver-popover-footer { + display: block; + margin-top: 12px; + + button { + // Edited + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; + display: inline-block; + margin-bottom: 0; + font-weight: normal; + text-align: center; + white-space: nowrap; + vertical-align: middle; + text-shadow: none !important; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + } + + button.driver-disabled { + color: grey; + cursor: default; + pointer-events: none; + } + + .driver-close-btn { + // Edited + float: left; + color: inherit; + background-color: #f0f4f7; + border-color: transparent; + } + + .driver-navigation-btns { + // Edited + .driver-prev-btn { + color: inherit; + background-color: #f0f4f7; + border-color: transparent; + } + + .driver-next-btn { + color: #fff; + background-color: #5e64ff; + border-color: #444bff; + } + } + } + .driver-popover-title { + // Edited + font: 18px/normal sans-serif; + margin: 0 0 5px; + font-weight: 500; + display: block; + position: relative; + line-height: 1.5; + zoom: 1; + } + .driver-popover-description { + // Edited + margin-bottom: 0; + font: 12px/normal sans-serif; + line-height: 1.5; + color: @text-muted; + font-weight: 400; + zoom: 1; + } +} + +.driver-clearfix { + &:after { + content: ""; + display: table; + clear: both; + } + &:before { + content: ""; + display: table; + } +} + +.driver-stage-no-animation { + -webkit-transition: none !important; + -moz-transition: none !important; + -ms-transition: none !important; + -o-transition: none !important; + transition: none !important; + background: transparent !important; + outline: 5000px solid rgba(0,0,0,.75); +} + +div#driver-page-overlay { + background: @modal-backdrop-bg; + position: fixed; + bottom: 0; + right: 0; + display: block; + width: 100%; + height: 100%; + zoom: 1; + filter: alpha(opacity=25); + opacity: .25; + z-index: 100002 !important; + top: 0; + left: 0; + -webkit-transition: all .3s; + -moz-transition: all .3s; + -ms-transition: all .3s; + -o-transition: all .3s; + transition: all .3s; +} + +div#driver-highlighted-element-stage { + top: 0; + left: 0; + -webkit-transition: all .3s; + -moz-transition: all .3s; + -ms-transition: all .3s; + -o-transition: all .3s; + transition: all .3s; + position: absolute; + height: 50px; + width: 300px; + background: #fff; + z-index: 100003 !important; + display: none; + border-radius: 2px; +} + +.driver-highlighted-element { + z-index: 100004 !important; +} + +.driver-position-relative { + position: relative !important; +} + +.driver-fix-stacking { + z-index: auto !important; + opacity: 1 !important; + -webkit-transform: none !important; + -moz-transform: none !important; + -ms-transform: none !important; + -o-transform: none !important; + transform: none !important; + -webkit-filter: none !important; + -moz-filter: none !important; + -ms-filter: none !important; + -o-filter: none !important; + filter: none !important; + -webkit-perspective: none !important; + -moz-perspective: none !important; + -ms-perspective: none !important; + -o-perspective: none !important; + perspective: none !important; + -webkit-transform-style: flat !important; + -moz-transform-style: flat !important; + -ms-transform-style: flat !important; + transform-style: flat !important; + -webkit-transform-box: border-box !important; + -moz-transform-box: border-box !important; + -ms-transform-box: border-box !important; + -o-transform-box: border-box !important; + transform-box: border-box !important; + will-change: unset !important; +} + + diff --git a/package.json b/package.json index e735beee9b..fae9798706 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "bootstrap": "^4.4.1", "cookie": "^0.4.0", "cssnano": "^4.1.10", + "driver.js": "^0.9.8", "express": "^4.17.1", "fast-deep-equal": "^2.0.1", "frappe-charts": "^1.3.2", diff --git a/yarn.lock b/yarn.lock index b189296086..73816bdece 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1770,6 +1770,11 @@ double-ended-queue@^2.1.0-0: resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c" integrity sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw= +driver.js@^0.9.8: + version "0.9.8" + resolved "https://registry.yarnpkg.com/driver.js/-/driver.js-0.9.8.tgz#4b327f4537b1c9b9fb19419de86174be821ae32a" + integrity sha512-bczjyKdX6XmFyCDkwtRmlaORDwfBk1xXmRO0CAe5VwNQTM98aWaG2LAIiIdTe53iV/B7W5lXlIy2xYtf0JRb7Q== + duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" From 1ab4f21a3609296b7cd1898f6064a578920cb897 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 11 May 2020 12:36:25 +0530 Subject: [PATCH 071/274] feat: added frappe tour API --- frappe/public/js/frappe/form/form.js | 29 ++++++++++++++++++++++++++++ frappe/public/js/frappe/provide.js | 1 + 2 files changed, 30 insertions(+) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index a5853d96f5..0368311a2d 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1556,6 +1556,35 @@ frappe.ui.form.Form = class FrappeForm { $el.find('input, select, textarea').focus(); }, 1000); } + + show_tour() { + if (!frappe.tour.hasOwnProperty(this.doctype) || !Array.isArray(frappe.tour[this.doctype])) { + return + } + + const driver = new frappe.Driver({ + overlayClickNext: true, + keyboardControl: true, + nextBtnText: 'Next', // Next button text for this step + prevBtnText: 'Previous', + }); + + let steps = frappe.tour[this.doctype].map(step => { + let field = this.get_docfield(step.fieldname); + return { + element: `.frappe-control[title='${step.fieldname}']`, + popover: { + title: step.title || field.label, + description: step.description + } + } + }); + + console.log(steps); + + driver.defineSteps(steps); + driver.start(); + } }; frappe.validated = 0; diff --git a/frappe/public/js/frappe/provide.js b/frappe/public/js/frappe/provide.js index 1dacc4dd47..d4d0fdffb8 100644 --- a/frappe/public/js/frappe/provide.js +++ b/frappe/public/js/frappe/provide.js @@ -35,6 +35,7 @@ frappe.provide('locals.DocType'); // for listviews frappe.provide("frappe.listview_settings"); +frappe.provide("frappe.tour"); frappe.provide("frappe.listview_parent_route"); // constants From b24de5b703c8ac2c7fed1afa5a84821768406c99 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 11 May 2020 12:37:25 +0530 Subject: [PATCH 072/274] feat: setup tour for note --- frappe/desk/doctype/note/note.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/frappe/desk/doctype/note/note.js b/frappe/desk/doctype/note/note.js index c237998ccf..5718180b70 100644 --- a/frappe/desk/doctype/note/note.js +++ b/frappe/desk/doctype/note/note.js @@ -1,9 +1,9 @@ frappe.ui.form.on("Note", { refresh: function(frm) { - if(frm.doc.__islocal) { + if (frm.doc.__islocal) { frm.events.set_editable(frm, true); } else { - if(!frm.doc.content) { + if (!frm.doc.content) { frm.doc.content = ""; } @@ -18,16 +18,15 @@ frappe.ui.form.on("Note", { // hide all fields other than content // no permission - if(editable && !frm.perm[0].write) return; + if (editable && !frm.perm[0].write) return; // content read_only - frm.set_df_property("content", "read_only", editable ? 0: 1); + frm.set_df_property("content", "read_only", editable ? 0 : 1); // hide all other fields $.each(frm.fields_dict, function(fieldname) { - - if(fieldname !== "content") { - frm.set_df_property(fieldname, "hidden", editable ? 0: 1); + if (fieldname !== "content") { + frm.set_df_property(fieldname, "hidden", editable ? 0 : 1); } }); @@ -39,3 +38,16 @@ frappe.ui.form.on("Note", { frm.is_note_editable = editable; } }); + +frappe.tour['Note'] = [ + { + fieldname: "title", + title: "Title of the Note", + description: "This is the name by which the note will be saved, you can change this later", + }, + { + fieldname: "public", + title: "Sets the Note to Public", + description: "You can change the visibility of the note with this, setting it to public will allow other users to view it.", + }, +]; \ No newline at end of file From 470abc20a1463ae68cbc33f4d8d748edb2a9f47d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 11 May 2020 12:56:40 +0530 Subject: [PATCH 073/274] feat: expand all sections before tour --- frappe/public/js/frappe/form/form.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 0368311a2d..41bde104dd 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1569,6 +1569,8 @@ frappe.ui.form.Form = class FrappeForm { prevBtnText: 'Previous', }); + this.layout.sections.forEach(section => section.collapse(false)) + let steps = frappe.tour[this.doctype].map(step => { let field = this.get_docfield(step.fieldname); return { @@ -1580,8 +1582,6 @@ frappe.ui.form.Form = class FrappeForm { } }); - console.log(steps); - driver.defineSteps(steps); driver.start(); } From bf7de110e27a50906a3d4384c9489a769af3b5a0 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 11 May 2020 12:57:37 +0530 Subject: [PATCH 074/274] feat: added steps for website settings --- .../doctype/website_settings/website_settings.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/frappe/website/doctype/website_settings/website_settings.js b/frappe/website/doctype/website_settings/website_settings.js index be294258f4..7fbb785acf 100644 --- a/frappe/website/doctype/website_settings/website_settings.js +++ b/frappe/website/doctype/website_settings/website_settings.js @@ -95,4 +95,17 @@ frappe.ui.form.on('Top Bar Item', { label: function(frm, doctype, name) { frm.events.set_parent_options(frm, doctype, name); }, -}); \ No newline at end of file +}); + +frappe.tour['Website Settings'] = [ + { + fieldname: "enable_view_tracking", + title: __("Enable Tracking Page Views"), + description: __("Checking this will enable tracking page views for blogs, web pages, etc."), + }, + { + fieldname: "disable_signup", + title: __("Disable Signup for your site"), + description: __("Check this if you don't want users to sign up for an account on your site. Users won't get desk access unless you explicitly provide it."), + } +]; \ No newline at end of file From 66a58ba09a3ca9158f19408fd8a298f782c27cb2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 11 May 2020 14:03:12 +0530 Subject: [PATCH 075/274] test: next schedule date of auto repeat with daily frequency --- frappe/automation/doctype/auto_repeat/test_auto_repeat.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/automation/doctype/auto_repeat/test_auto_repeat.py b/frappe/automation/doctype/auto_repeat/test_auto_repeat.py index 769c77b67c..60fa9cb59e 100644 --- a/frappe/automation/doctype/auto_repeat/test_auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/test_auto_repeat.py @@ -99,13 +99,18 @@ class TestAutoRepeat(unittest.TestCase): def test_next_schedule_date(self): current_date = getdate(today()) todo = frappe.get_doc( - dict(doctype='ToDo', description='test next schedule date todo', assigned_by='Administrator')).insert() + dict(doctype='ToDo', description='test next schedule date for monthly', assigned_by='Administrator')).insert() doc = make_auto_repeat(frequency='Monthly', reference_document=todo.name, start_date=add_months(today(), -2)) # next_schedule_date is set as on or after current date # it should not be a previous month's date self.assertTrue((doc.next_schedule_date >= current_date)) + todo = frappe.get_doc( + dict(doctype='ToDo', description='test next schedule date for daily', assigned_by='Administrator')).insert() + doc = make_auto_repeat(frequency='Daily', reference_document=todo.name, start_date=add_days(today(), -2)) + self.assertEqual(getdate(doc.next_schedule_date), current_date) + def make_auto_repeat(**args): args = frappe._dict(args) From a358bcdc33246b81120ff8ee558e51b2cfdd5e41 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 11 May 2020 14:07:21 +0530 Subject: [PATCH 076/274] feat: add form tour as onboarding option --- .../doctype/onboarding_step/onboarding_step.js | 18 ++++++++++++++++++ .../onboarding_step/onboarding_step.json | 15 ++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/onboarding_step/onboarding_step.js b/frappe/desk/doctype/onboarding_step/onboarding_step.js index 3e5d4d4260..793e044d98 100644 --- a/frappe/desk/doctype/onboarding_step/onboarding_step.js +++ b/frappe/desk/doctype/onboarding_step/onboarding_step.js @@ -25,6 +25,24 @@ frappe.ui.form.on("Onboarding Step", { } }, + action: function(frm) { + if (frm.doc.action == "Show Form Tour") { + frm.fields_dict.reference_document.set_description(`You need to add the steps in the contoller JS file. For example: note.js +

+frappe.tour['Note'] = [
+	{
+		fieldname: "title",
+		title: "Title of the Note",
+		description: "...",
+	}
+];
+
+ `); + } else { + frm.fields_dict.reference_document.set_description(null); + } + }, + disable_form: function(frm) { frm.set_read_only(); frm.fields diff --git a/frappe/desk/doctype/onboarding_step/onboarding_step.json b/frappe/desk/doctype/onboarding_step/onboarding_step.json index e1035a4343..3bc9d5f286 100644 --- a/frappe/desk/doctype/onboarding_step/onboarding_step.json +++ b/frappe/desk/doctype/onboarding_step/onboarding_step.json @@ -15,6 +15,7 @@ "action", "column_break_7", "reference_document", + "is_single", "reference_report", "report_reference_doctype", "report_type", @@ -57,7 +58,7 @@ "fieldname": "action", "fieldtype": "Select", "label": "Action", - "options": "Create Entry\nUpdate Settings\nView Report\nWatch Video", + "options": "Create Entry\nUpdate Settings\nShow Form Tour\nView Report\nWatch Video", "reqd": 1 }, { @@ -65,7 +66,7 @@ "fieldtype": "Column Break" }, { - "depends_on": "eval:doc.action == \"Create Entry\" || doc.action == \"Update Settings\"", + "depends_on": "eval:doc.action == \"Create Entry\" || doc.action == \"Update Settings\" || doc.action == \"Create Entry\" || doc.action == \"Show Form Tour\"", "fieldname": "reference_document", "fieldtype": "Link", "label": "Reference Document", @@ -127,10 +128,18 @@ "fieldtype": "Data", "label": "Report Reference Doctype", "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.action == \"Create Entry\" || doc.action == \"Update Settings\" || doc.action == \"Create Entry\" || doc.action == \"Show Form Tour\"", + "fetch_from": "reference_document.issingle", + "fieldname": "is_single", + "fieldtype": "Check", + "label": "Is Single" } ], "links": [], - "modified": "2020-05-04 12:53:19.276952", + "modified": "2020-05-11 13:24:05.457160", "modified_by": "Administrator", "module": "Desk", "name": "Onboarding Step", From 5c241255516edfbc62e1ace935746611ab03551d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 11 May 2020 14:07:55 +0530 Subject: [PATCH 077/274] feat: add on_finish hook for tour --- frappe/public/js/frappe/form/form.js | 8 ++++- .../js/frappe/widgets/onboarding_widget.js | 35 ++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 41bde104dd..3742ea36dc 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1557,7 +1557,7 @@ frappe.ui.form.Form = class FrappeForm { }, 1000); } - show_tour() { + show_tour(on_finish) { if (!frappe.tour.hasOwnProperty(this.doctype) || !Array.isArray(frappe.tour[this.doctype])) { return } @@ -1567,6 +1567,12 @@ frappe.ui.form.Form = class FrappeForm { keyboardControl: true, nextBtnText: 'Next', // Next button text for this step prevBtnText: 'Previous', + opacity: 0.25, + onNext: (el) => { + if (!driver.hasNextStep()) { + on_finish && on_finish(); + } + } }); this.layout.sections.forEach(section => section.collapse(false)) diff --git a/frappe/public/js/frappe/widgets/onboarding_widget.js b/frappe/public/js/frappe/widgets/onboarding_widget.js index 78305edd5d..f9abbaf64e 100644 --- a/frappe/public/js/frappe/widgets/onboarding_widget.js +++ b/frappe/public/js/frappe/widgets/onboarding_widget.js @@ -57,6 +57,7 @@ export default class OnboardingWidget extends Widget { let actions = { "Watch Video": () => this.show_video(step), "Create Entry": () => 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), }; @@ -86,7 +87,9 @@ export default class OnboardingWidget extends Widget { primary_action: { action: () => { msg_dialog.hide(); - this.mark_complete(step); + frappe.set_route(current_route).then(() => { + this.mark_complete(step); + }); }, label: () => __("Continue"), }, @@ -105,6 +108,36 @@ export default class OnboardingWidget extends Widget { }); } + show_form_tour(step) { + let route; + if (step.is_single) { + route = `Form/${step.reference_document}` + } else { + route = `Form/${step.reference_document}/New ${step.reference_document}` + } + + frappe.route_options = {}; + frappe.route_options.after_load = (frm) => { + frm.show_tour(() => { + let msg_dialog = frappe.msgprint({ + message: __("Let's take you back to onboarding"), + title: __("Great Job"), + primary_action: { + action: () => { + frappe.set_route(current_route).then(() => { + this.mark_complete(step); + }); + msg_dialog.hide(); + }, + label: () => __("Continue"), + } + }); + }); + }; + + frappe.set_route(route) + } + update_settings(step) { let current_route = frappe.get_route(); From 986504d46d7960fe2cb272111e3d02fa33a23f9f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 11 May 2020 14:19:22 +0530 Subject: [PATCH 078/274] fix: routing bug for tour --- frappe/public/js/frappe/widgets/onboarding_widget.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/onboarding_widget.js b/frappe/public/js/frappe/widgets/onboarding_widget.js index f9abbaf64e..94b73d0c8b 100644 --- a/frappe/public/js/frappe/widgets/onboarding_widget.js +++ b/frappe/public/js/frappe/widgets/onboarding_widget.js @@ -78,6 +78,7 @@ export default class OnboardingWidget extends Widget { doctype: step.report_reference_doctype }); + let current_route = frappe.get_route(); frappe.set_route(route).then(() => { @@ -86,10 +87,10 @@ export default class OnboardingWidget extends Widget { title: __(step.reference_report), primary_action: { action: () => { - msg_dialog.hide(); frappe.set_route(current_route).then(() => { this.mark_complete(step); }); + msg_dialog.hide(); }, label: () => __("Continue"), }, @@ -116,6 +117,8 @@ export default class OnboardingWidget extends Widget { route = `Form/${step.reference_document}/New ${step.reference_document}` } + let current_route = frappe.get_route(); + frappe.route_options = {}; frappe.route_options.after_load = (frm) => { frm.show_tour(() => { From 451772dfab13abfb440c199c34d67887cf1921d9 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 11 May 2020 15:47:19 +0530 Subject: [PATCH 079/274] 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 d40b60d95b454b9b253689c6ba69b04b7edb9da9 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 11 May 2020 18:15:38 +0530 Subject: [PATCH 080/274] fix(Report): hide chart if group by is applied to report --- frappe/public/js/frappe/ui/group_by/group_by.js | 1 + frappe/public/js/frappe/views/reports/report_view.js | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js index 831bafa8e8..5053ad71f4 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -241,6 +241,7 @@ frappe.ui.GroupBy = class { this.order_by = ''; this.group_by = null; + this.report_view.group_by = null; this.aggregate_function = null; this.aggregate_on = null; $(".groupby").val(""); diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 856061f1f0..9399acf39e 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -113,7 +113,9 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { } else { this.save_report_settings(); } - this.init_chart(); + if (!this.group_by) { + this.init_chart(); + } } set_dirty_state_for_custom_report() { @@ -177,9 +179,12 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { this.render_count(); this.setup_columns(); - if (this.chart) { + if (this.group_by) { + this.$charts_wrapper.addClass('hidden'); + } else if (this.chart) { this.refresh_charts(); } + if (this.datatable && !force) { this.datatable.refresh(this.get_data(this.data), this.columns); return; From 4e79dc98e02f039bfa826dbaf4c70a5370d9eb07 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 11 May 2020 18:22:13 +0530 Subject: [PATCH 081/274] fix: show chart if hidden --- frappe/public/js/frappe/views/reports/report_view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 9399acf39e..f794248ad9 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -182,6 +182,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { if (this.group_by) { this.$charts_wrapper.addClass('hidden'); } else if (this.chart) { + this.$charts_wrapper.removeClass('hidden'); this.refresh_charts(); } From ed74f52e206f7108938ef23fecb198c30a99a683 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 11 May 2020 20:12:05 +0530 Subject: [PATCH 082/274] 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 083/274] 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 084/274] 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 085/274] 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 086/274] 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 4018bf6f5e54636a6cb21df0394c252c1b3b1228 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 15:36:48 +0530 Subject: [PATCH 087/274] refactor: remove default rules --- frappe/public/less/driver.less | 104 --------------------------------- 1 file changed, 104 deletions(-) diff --git a/frappe/public/less/driver.less b/frappe/public/less/driver.less index 9dc7cde0b1..d331b92e24 100644 --- a/frappe/public/less/driver.less +++ b/frappe/public/less/driver.less @@ -29,12 +29,6 @@ div#driver-popover-item { border: 1px solid transparent; } - button.driver-disabled { - color: grey; - cursor: default; - pointer-events: none; - } - .driver-close-btn { // Edited float: left; @@ -79,102 +73,4 @@ div#driver-popover-item { } } -.driver-clearfix { - &:after { - content: ""; - display: table; - clear: both; - } - &:before { - content: ""; - display: table; - } -} - -.driver-stage-no-animation { - -webkit-transition: none !important; - -moz-transition: none !important; - -ms-transition: none !important; - -o-transition: none !important; - transition: none !important; - background: transparent !important; - outline: 5000px solid rgba(0,0,0,.75); -} - -div#driver-page-overlay { - background: @modal-backdrop-bg; - position: fixed; - bottom: 0; - right: 0; - display: block; - width: 100%; - height: 100%; - zoom: 1; - filter: alpha(opacity=25); - opacity: .25; - z-index: 100002 !important; - top: 0; - left: 0; - -webkit-transition: all .3s; - -moz-transition: all .3s; - -ms-transition: all .3s; - -o-transition: all .3s; - transition: all .3s; -} - -div#driver-highlighted-element-stage { - top: 0; - left: 0; - -webkit-transition: all .3s; - -moz-transition: all .3s; - -ms-transition: all .3s; - -o-transition: all .3s; - transition: all .3s; - position: absolute; - height: 50px; - width: 300px; - background: #fff; - z-index: 100003 !important; - display: none; - border-radius: 2px; -} - -.driver-highlighted-element { - z-index: 100004 !important; -} - -.driver-position-relative { - position: relative !important; -} - -.driver-fix-stacking { - z-index: auto !important; - opacity: 1 !important; - -webkit-transform: none !important; - -moz-transform: none !important; - -ms-transform: none !important; - -o-transform: none !important; - transform: none !important; - -webkit-filter: none !important; - -moz-filter: none !important; - -ms-filter: none !important; - -o-filter: none !important; - filter: none !important; - -webkit-perspective: none !important; - -moz-perspective: none !important; - -ms-perspective: none !important; - -o-perspective: none !important; - perspective: none !important; - -webkit-transform-style: flat !important; - -moz-transform-style: flat !important; - -ms-transform-style: flat !important; - transform-style: flat !important; - -webkit-transform-box: border-box !important; - -moz-transform-box: border-box !important; - -ms-transform-box: border-box !important; - -o-transform-box: border-box !important; - transform-box: border-box !important; - will-change: unset !important; -} - From 4f3c40ae3d0cb2fb3478e68e3313fa487098d5b5 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 12 May 2020 15:52:03 +0530 Subject: [PATCH 088/274] fix(google-contact): remove parent kwarg from create contact --- frappe/integrations/doctype/google_contacts/google_contacts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.py b/frappe/integrations/doctype/google_contacts/google_contacts.py index 0e28c1306c..5874c79108 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.py +++ b/frappe/integrations/doctype/google_contacts/google_contacts.py @@ -218,7 +218,7 @@ def insert_contacts_to_google_contacts(doc, method=None): emailAddresses = [{"value": email_id.email_id} for email_id in doc.email_ids] try: - contact = google_contacts.people().createContact(parent='people/me', body={"names": [names],"phoneNumbers": phoneNumbers, + contact = google_contacts.people().createContact(body={"names": [names],"phoneNumbers": phoneNumbers, "emailAddresses": emailAddresses}).execute() frappe.db.set_value("Contact", doc.name, "google_contacts_id", contact.get("resourceName")) except HttpError as err: From 48382fdca0adf7842120ec79585236c242cb4bdc Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 16:15:36 +0530 Subject: [PATCH 089/274] refactor: rename onboarding to module onboarding --- frappe/desk/desktop.py | 4 ++-- frappe/desk/doctype/desk_page/desk_page.json | 4 ++-- .../doctype/{onboarding => module_onboarding}/__init__.py | 0 .../module_onboarding.js} | 2 +- .../module_onboarding.json} | 2 +- .../module_onboarding.py} | 8 ++++---- .../test_module_onboarding.py} | 2 +- frappe/model/sync.py | 4 ++-- frappe/modules/import_file.py | 2 +- frappe/website/onboarding/website/website.json | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) rename frappe/desk/doctype/{onboarding => module_onboarding}/__init__.py (100%) rename frappe/desk/doctype/{onboarding/onboarding.js => module_onboarding/module_onboarding.js} (92%) rename frappe/desk/doctype/{onboarding/onboarding.json => module_onboarding/module_onboarding.json} (98%) rename frappe/desk/doctype/{onboarding/onboarding.py => module_onboarding/module_onboarding.py} (73%) rename frappe/desk/doctype/{onboarding/test_onboarding.py => module_onboarding/test_module_onboarding.py} (79%) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index f2047003fa..a912ca341c 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -61,10 +61,10 @@ class Workspace: if not self.doc.onboarding: return None - if frappe.db.get_value("Onboarding", self.doc.onboarding, "is_complete"): + if frappe.db.get_value("Module Onboarding", self.doc.onboarding, "is_complete"): return None - doc = frappe.get_doc("Onboarding", self.doc.onboarding) + doc = frappe.get_doc("Module Onboarding", self.doc.onboarding) # Check if user is allowed allowed_roles = set(doc.get_allowed_roles()) diff --git a/frappe/desk/doctype/desk_page/desk_page.json b/frappe/desk/doctype/desk_page/desk_page.json index cb106c5dd4..0064d7bd28 100644 --- a/frappe/desk/doctype/desk_page/desk_page.json +++ b/frappe/desk/doctype/desk_page/desk_page.json @@ -188,11 +188,11 @@ "fieldname": "onboarding", "fieldtype": "Link", "label": "Onboarding", - "options": "Onboarding" + "options": "Module Onboarding" } ], "links": [], - "modified": "2020-04-26 12:21:46.205079", + "modified": "2020-05-12 16:12:20.708394", "modified_by": "Administrator", "module": "Desk", "name": "Desk Page", diff --git a/frappe/desk/doctype/onboarding/__init__.py b/frappe/desk/doctype/module_onboarding/__init__.py similarity index 100% rename from frappe/desk/doctype/onboarding/__init__.py rename to frappe/desk/doctype/module_onboarding/__init__.py diff --git a/frappe/desk/doctype/onboarding/onboarding.js b/frappe/desk/doctype/module_onboarding/module_onboarding.js similarity index 92% rename from frappe/desk/doctype/onboarding/onboarding.js rename to frappe/desk/doctype/module_onboarding/module_onboarding.js index bed7dbd5de..48ab047c5e 100644 --- a/frappe/desk/doctype/onboarding/onboarding.js +++ b/frappe/desk/doctype/module_onboarding/module_onboarding.js @@ -1,7 +1,7 @@ // Copyright (c) 2020, Frappe Technologies and contributors // For license information, please see license.txt -frappe.ui.form.on("Onboarding", { +frappe.ui.form.on("Module ModuleOnboarding", { refresh: function(frm) { frappe.boot.developer_mode && frm.set_intro( diff --git a/frappe/desk/doctype/onboarding/onboarding.json b/frappe/desk/doctype/module_onboarding/module_onboarding.json similarity index 98% rename from frappe/desk/doctype/onboarding/onboarding.json rename to frappe/desk/doctype/module_onboarding/module_onboarding.json index b1d563a9dc..9810e7a15f 100644 --- a/frappe/desk/doctype/onboarding/onboarding.json +++ b/frappe/desk/doctype/module_onboarding/module_onboarding.json @@ -93,7 +93,7 @@ "modified": "2020-05-01 19:37:21.492405", "modified_by": "Administrator", "module": "Desk", - "name": "Onboarding", + "name": "Module Onboarding", "owner": "Administrator", "permissions": [ { diff --git a/frappe/desk/doctype/onboarding/onboarding.py b/frappe/desk/doctype/module_onboarding/module_onboarding.py similarity index 73% rename from frappe/desk/doctype/onboarding/onboarding.py rename to frappe/desk/doctype/module_onboarding/module_onboarding.py index c8527d22b6..a723953a11 100644 --- a/frappe/desk/doctype/onboarding/onboarding.py +++ b/frappe/desk/doctype/module_onboarding/module_onboarding.py @@ -8,16 +8,16 @@ from frappe.model.document import Document from frappe.modules.export_file import export_to_files -class Onboarding(Document): +class ModuleOnboarding(Document): def on_update(self): if frappe.conf.developer_mode: - export_to_files(record_list=[['Onboarding', self.name]], record_module=self.module) + export_to_files(record_list=[['Module ModuleOnboarding', self.name]], record_module=self.module) for step in self.steps: - export_to_files(record_list=[['Onboarding Step', step.step]], record_module=self.module) + export_to_files(record_list=[['Module ModuleOnboarding Step', step.step]], record_module=self.module) def get_steps(self): - return [frappe.get_doc("Onboarding Step", step.step) for step in self.steps] + return [frappe.get_doc("Module ModuleOnboarding Step", step.step) for step in self.steps] def get_allowed_roles(self): all_roles = [role.role for role in self.allow_roles] diff --git a/frappe/desk/doctype/onboarding/test_onboarding.py b/frappe/desk/doctype/module_onboarding/test_module_onboarding.py similarity index 79% rename from frappe/desk/doctype/onboarding/test_onboarding.py rename to frappe/desk/doctype/module_onboarding/test_module_onboarding.py index 8a9e346fd9..ef305667b1 100644 --- a/frappe/desk/doctype/onboarding/test_onboarding.py +++ b/frappe/desk/doctype/module_onboarding/test_module_onboarding.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestOnboarding(unittest.TestCase): +class TestModuleOnboarding(unittest.TestCase): pass diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 320cc24677..b7d9d4d548 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -51,7 +51,7 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe ("desk", "onboarding_permission"), ("desk", "onboarding_step"), ("desk", "onboarding_step_map"), - ("desk", "onboarding"), + ("desk", "module_onboarding"), ("desk", "desk_card"), ("desk", "desk_chart"), ("desk", "desk_shortcut"), @@ -85,7 +85,7 @@ def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=F document_types = ['doctype', 'page', 'report', 'dashboard_chart_source', 'print_format', 'website_theme', 'web_form', 'web_template', 'notification', 'print_style', 'data_migration_mapping', 'data_migration_plan', 'desk_page', - 'onboarding_step', 'onboarding'] + 'onboarding_step', 'module_onboarding'] for doctype in document_types: doctype_path = os.path.join(start_path, doctype) diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index cddef4f910..27649b8da9 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -13,7 +13,7 @@ ignore_values = { "Print Format": ["disabled"], "Notification": ["enabled"], "Print Style": ["disabled"], - "Onboarding": ['is_complete'], + "Module Onboarding": ['is_complete'], "Onboarding Step": ['is_complete', 'is_skipped'] } diff --git a/frappe/website/onboarding/website/website.json b/frappe/website/onboarding/website/website.json index ee4403557b..b849a809ed 100644 --- a/frappe/website/onboarding/website/website.json +++ b/frappe/website/onboarding/website/website.json @@ -6,7 +6,7 @@ ], "creation": "2020-04-26 13:03:30.405135", "docstatus": 0, - "doctype": "Onboarding", + "doctype": "Module Onboarding", "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/website", "idx": 0, "is_complete": 0, From ef0c99c922e0b058e86712c65afe6f6ca779a120 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 16:24:59 +0530 Subject: [PATCH 090/274] refactor: rename folder for website onboarding --- .../{onboarding => module_onboarding}/website/website.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename frappe/website/{onboarding => module_onboarding}/website/website.json (100%) diff --git a/frappe/website/onboarding/website/website.json b/frappe/website/module_onboarding/website/website.json similarity index 100% rename from frappe/website/onboarding/website/website.json rename to frappe/website/module_onboarding/website/website.json From 963483cb0e29a7aa1baa1bda7c2e519714776d61 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 16:28:54 +0530 Subject: [PATCH 091/274] feat: add patch to rename Onboarding --- frappe/patches.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/patches.txt b/frappe/patches.txt index a086fa6f4a..ef9e9fe4bb 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -278,3 +278,4 @@ frappe.patches.v13_0.set_path_for_homepage_in_web_page_view frappe.patches.v13_0.migrate_translation_column_data frappe.patches.v13_0.set_read_times frappe.patches.v13_0.remove_web_view +execute:frappe.rename_doc("DocType", "Onboarding", "Module Onboarding") From bcf20a8a5c69f3735905f8fee49ad482030e9376 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 16:41:48 +0530 Subject: [PATCH 092/274] fix: controller methods --- frappe/desk/doctype/module_onboarding/module_onboarding.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/module_onboarding/module_onboarding.py b/frappe/desk/doctype/module_onboarding/module_onboarding.py index a723953a11..2be427749c 100644 --- a/frappe/desk/doctype/module_onboarding/module_onboarding.py +++ b/frappe/desk/doctype/module_onboarding/module_onboarding.py @@ -11,13 +11,13 @@ from frappe.modules.export_file import export_to_files class ModuleOnboarding(Document): def on_update(self): if frappe.conf.developer_mode: - export_to_files(record_list=[['Module ModuleOnboarding', self.name]], record_module=self.module) + export_to_files(record_list=[['ModuleOnboarding', self.name]], record_module=self.module) for step in self.steps: - export_to_files(record_list=[['Module ModuleOnboarding Step', step.step]], record_module=self.module) + export_to_files(record_list=[['ModuleOnboarding Step', step.step]], record_module=self.module) def get_steps(self): - return [frappe.get_doc("Module ModuleOnboarding Step", step.step) for step in self.steps] + return [frappe.get_doc("ModuleOnboarding Step", step.step) for step in self.steps] def get_allowed_roles(self): all_roles = [role.role for role in self.allow_roles] From dd1e3bbf7a5a1fa62b4f42adb7dea2dd89a88e02 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 16:41:58 +0530 Subject: [PATCH 093/274] feat: add ignore flag in patch --- frappe/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/patches.txt b/frappe/patches.txt index ec0c515907..118d42ba16 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -279,4 +279,4 @@ frappe.patches.v13_0.migrate_translation_column_data frappe.patches.v13_0.set_read_times frappe.patches.v13_0.remove_web_view frappe.patches.v13_0.remove_tailwind_from_page_builder -execute:frappe.rename_doc("DocType", "Onboarding", "Module Onboarding") +execute:frappe.rename_doc("DocType", "Onboarding", "Module Onboarding", ignore_if_exists=True) From 22f59b7e6a46cd73202a65c3398d780a44580818 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 16:42:59 +0530 Subject: [PATCH 094/274] fix: onboarding step name in query --- frappe/desk/doctype/module_onboarding/module_onboarding.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/module_onboarding/module_onboarding.py b/frappe/desk/doctype/module_onboarding/module_onboarding.py index 2be427749c..89160a60f0 100644 --- a/frappe/desk/doctype/module_onboarding/module_onboarding.py +++ b/frappe/desk/doctype/module_onboarding/module_onboarding.py @@ -11,13 +11,13 @@ from frappe.modules.export_file import export_to_files class ModuleOnboarding(Document): def on_update(self): if frappe.conf.developer_mode: - export_to_files(record_list=[['ModuleOnboarding', self.name]], record_module=self.module) + export_to_files(record_list=[['Module Onboarding', self.name]], record_module=self.module) for step in self.steps: - export_to_files(record_list=[['ModuleOnboarding Step', step.step]], record_module=self.module) + export_to_files(record_list=[['Onboarding Step', step.step]], record_module=self.module) def get_steps(self): - return [frappe.get_doc("ModuleOnboarding Step", step.step) for step in self.steps] + return [frappe.get_doc("Onboarding Step", step.step) for step in self.steps] def get_allowed_roles(self): all_roles = [role.role for role in self.allow_roles] From 759db9f3241e11b6ac52c4a857eb3aa7e7aec908 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 16:55:22 +0530 Subject: [PATCH 095/274] refactor: add check to onboarding rename patch --- frappe/patches.txt | 2 +- frappe/patches/v13_0/rename_onboarding.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 frappe/patches/v13_0/rename_onboarding.py diff --git a/frappe/patches.txt b/frappe/patches.txt index 118d42ba16..86491b32b4 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -279,4 +279,4 @@ frappe.patches.v13_0.migrate_translation_column_data frappe.patches.v13_0.set_read_times frappe.patches.v13_0.remove_web_view frappe.patches.v13_0.remove_tailwind_from_page_builder -execute:frappe.rename_doc("DocType", "Onboarding", "Module Onboarding", ignore_if_exists=True) +frappe.patches.v13_0.rename_onboarding diff --git a/frappe/patches/v13_0/rename_onboarding.py b/frappe/patches/v13_0/rename_onboarding.py new file mode 100644 index 0000000000..9b3add0620 --- /dev/null +++ b/frappe/patches/v13_0/rename_onboarding.py @@ -0,0 +1,10 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + if frappe.db.exists("DocType", "Onboarding"): + execute:frappe.rename_doc("DocType", "Onboarding", "Module Onboarding", ignore_if_exists=True) + From 413fa607210bbc17f5c7d0d67df6785ec8e05747 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 17:22:15 +0530 Subject: [PATCH 096/274] fix: styling fixes for sider --- frappe/public/js/frappe/form/form.js | 24 +++++++++---------- .../js/frappe/widgets/onboarding_widget.js | 6 ++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 3742ea36dc..afdde6a207 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1558,24 +1558,24 @@ frappe.ui.form.Form = class FrappeForm { } show_tour(on_finish) { - if (!frappe.tour.hasOwnProperty(this.doctype) || !Array.isArray(frappe.tour[this.doctype])) { - return + if (!Array.isArray(frappe.tour[this.doctype])) { + return; } const driver = new frappe.Driver({ overlayClickNext: true, keyboardControl: true, - nextBtnText: 'Next', // Next button text for this step - prevBtnText: 'Previous', - opacity: 0.25, - onNext: (el) => { - if (!driver.hasNextStep()) { - on_finish && on_finish(); - } - } + nextBtnText: 'Next', + prevBtnText: 'Previous', + opacity: 0.25, + onNext: () => { + if (!driver.hasNextStep()) { + on_finish && on_finish(); + } + } }); - this.layout.sections.forEach(section => section.collapse(false)) + this.layout.sections.forEach(section => section.collapse(false)); let steps = frappe.tour[this.doctype].map(step => { let field = this.get_docfield(step.fieldname); @@ -1585,7 +1585,7 @@ frappe.ui.form.Form = class FrappeForm { title: step.title || field.label, description: step.description } - } + }; }); driver.defineSteps(steps); diff --git a/frappe/public/js/frappe/widgets/onboarding_widget.js b/frappe/public/js/frappe/widgets/onboarding_widget.js index 94b73d0c8b..84bdbd56ac 100644 --- a/frappe/public/js/frappe/widgets/onboarding_widget.js +++ b/frappe/public/js/frappe/widgets/onboarding_widget.js @@ -112,9 +112,9 @@ export default class OnboardingWidget extends Widget { show_form_tour(step) { let route; if (step.is_single) { - route = `Form/${step.reference_document}` + route = `Form/${step.reference_document}`; } else { - route = `Form/${step.reference_document}/New ${step.reference_document}` + route = `Form/${step.reference_document}/New ${step.reference_document}`; } let current_route = frappe.get_route(); @@ -138,7 +138,7 @@ export default class OnboardingWidget extends Widget { }); }; - frappe.set_route(route) + frappe.set_route(route); } update_settings(step) { From 502fb92904253f20d9a30b912cdaf6d4b271169c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 17:51:18 +0530 Subject: [PATCH 097/274] fix: js controller name for module_onboarding --- frappe/desk/doctype/module_onboarding/module_onboarding.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/module_onboarding/module_onboarding.js b/frappe/desk/doctype/module_onboarding/module_onboarding.js index 48ab047c5e..d95920e2ca 100644 --- a/frappe/desk/doctype/module_onboarding/module_onboarding.js +++ b/frappe/desk/doctype/module_onboarding/module_onboarding.js @@ -1,7 +1,7 @@ // Copyright (c) 2020, Frappe Technologies and contributors // For license information, please see license.txt -frappe.ui.form.on("Module ModuleOnboarding", { +frappe.ui.form.on("Module Onboarding", { refresh: function(frm) { frappe.boot.developer_mode && frm.set_intro( From c16c7fb1219daf326c05e75d097d60db2ebdbfa0 Mon Sep 17 00:00:00 2001 From: Abhishek Balam Date: Tue, 12 May 2020 18:23:57 +0530 Subject: [PATCH 098/274] fix typo 'Send Attachements' in Newsletter --- frappe/email/doctype/newsletter/newsletter..json | 0 frappe/email/doctype/newsletter/newsletter.json | 16 ++++++++-------- frappe/email/doctype/newsletter/newsletter.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 frappe/email/doctype/newsletter/newsletter..json diff --git a/frappe/email/doctype/newsletter/newsletter..json b/frappe/email/doctype/newsletter/newsletter..json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json index 719d51c176..01f75be954 100644 --- a/frappe/email/doctype/newsletter/newsletter.json +++ b/frappe/email/doctype/newsletter/newsletter.json @@ -17,7 +17,7 @@ "subject", "message", "send_unsubscribe_link", - "send_attachements", + "send_attachments", "published", "route", "test_the_newsletter", @@ -73,12 +73,6 @@ "fieldtype": "Check", "label": "Send Unsubscribe Link" }, - { - "default": "0", - "fieldname": "send_attachements", - "fieldtype": "Check", - "label": "Send Attachements" - }, { "default": "0", "fieldname": "published", @@ -127,6 +121,12 @@ { "fieldname": "column_break_2", "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "send_attachments", + "fieldtype": "Check", + "label": "Send Attachments" } ], "has_web_view": 1, @@ -135,7 +135,7 @@ "is_published_field": "published", "links": [], "max_attachments": 3, - "modified": "2020-03-02 06:26:51.622521", + "modified": "2020-05-12 18:09:40.137138", "modified_by": "Administrator", "module": "Email", "name": "Newsletter", diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 2469569892..2dccfbead4 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -67,7 +67,7 @@ class Newsletter(WebsiteGenerator): frappe.db.auto_commit_on_many_writes = True attachments = [] - if self.send_attachements: + if self.send_attachments: files = frappe.get_all("File", fields=["name"], filters={"attached_to_doctype": "Newsletter", "attached_to_name": self.name}, order_by="creation desc") From f170d87019641c79fde53aec43f43b0e76d7946e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 18:32:58 +0530 Subject: [PATCH 099/274] feat: use frappe.route_hooks namespace --- frappe/public/js/frappe/form/form.js | 12 ++++++------ frappe/public/js/frappe/router.js | 1 + frappe/public/js/frappe/widgets/onboarding_widget.js | 10 +++++----- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index afdde6a207..01dfbf81f9 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -464,9 +464,9 @@ frappe.ui.form.Form = class FrappeForm { } run_after_load_hook() { - if (frappe.route_options.after_load) { - let route_callback = frappe.route_options.after_load; - delete frappe.route_options.after_load; + if (frappe.route_hooks.after_load) { + let route_callback = frappe.route_hooks.after_load; + delete frappe.route_hooks.after_load; route_callback(this); } @@ -580,9 +580,9 @@ frappe.ui.form.Form = class FrappeForm { me.script_manager.trigger("after_save"); - if (frappe.route_options.after_save) { - let route_callback = frappe.route_options.after_save; - delete frappe.route_options.after_save; + if (frappe.route_hooks.after_save) { + let route_callback = frappe.route_hooks.after_save; + delete frappe.route_hooks.after_save; route_callback(me); } diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index 06bd6a3bd9..f3f3285245 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -12,6 +12,7 @@ frappe.route_history = []; frappe.view_factory = {}; frappe.view_factories = []; frappe.route_options = null; +frappe.route_hooks = {}; frappe.route = function() { diff --git a/frappe/public/js/frappe/widgets/onboarding_widget.js b/frappe/public/js/frappe/widgets/onboarding_widget.js index 84bdbd56ac..e347a70036 100644 --- a/frappe/public/js/frappe/widgets/onboarding_widget.js +++ b/frappe/public/js/frappe/widgets/onboarding_widget.js @@ -119,8 +119,8 @@ export default class OnboardingWidget extends Widget { let current_route = frappe.get_route(); - frappe.route_options = {}; - frappe.route_options.after_load = (frm) => { + frappe.route_hooks = {}; + frappe.route_hooks.after_load = (frm) => { frm.show_tour(() => { let msg_dialog = frappe.msgprint({ message: __("Let's take you back to onboarding"), @@ -144,12 +144,12 @@ export default class OnboardingWidget extends Widget { update_settings(step) { let current_route = frappe.get_route(); - frappe.route_options = {}; - frappe.route_options.after_load = (frm) => { + frappe.route_hooks = {}; + frappe.route_hooks.after_load = (frm) => { frm.scroll_to_field(step.field); }; - frappe.route_options.after_save = (frm) => { + frappe.route_hooks.after_save = (frm) => { let success = false; let args = {}; From f2cd5f691131976ab1b11d94898844ec25ec81ca Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 19:14:30 +0530 Subject: [PATCH 100/274] fix (test): run invalid login test before successful test --- cypress/integration/login.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cypress/integration/login.js b/cypress/integration/login.js index 3f13130b58..904b9e0721 100644 --- a/cypress/integration/login.js +++ b/cypress/integration/login.js @@ -21,15 +21,6 @@ context('Login', () => { cy.location('pathname').should('eq', '/login'); }); - it('logs in using correct credentials', () => { - cy.get('#login_email').type('Administrator'); - cy.get('#login_password').type(Cypress.config('adminPassword')); - - cy.get('.btn-login').click(); - cy.location('pathname').should('eq', '/desk'); - cy.window().its('frappe.session.user').should('eq', 'Administrator'); - }); - it('shows invalid login if incorrect credentials', () => { cy.get('#login_email').type('Administrator'); cy.get('#login_password').type('qwer'); @@ -38,4 +29,13 @@ context('Login', () => { cy.get('.page-card-head').contains('Invalid Login. Try again.'); cy.location('pathname').should('eq', '/login'); }); + + it('logs in using correct credentials', () => { + cy.get('#login_email').type('Administrator'); + cy.get('#login_password').type(Cypress.config('adminPassword')); + + cy.get('.btn-login').click(); + cy.location('pathname').should('eq', '/desk'); + cy.window().its('frappe.session.user').should('eq', 'Administrator'); + }); }); From 7c1afd49922a9df35cf167e77da11a30ce29f935 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 19:08:58 +0530 Subject: [PATCH 101/274] fix (tests): wait before and after routing --- cypress/integration/list_view_settings.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cypress/integration/list_view_settings.js b/cypress/integration/list_view_settings.js index 51cba94a70..47f8efe94b 100644 --- a/cypress/integration/list_view_settings.js +++ b/cypress/integration/list_view_settings.js @@ -9,7 +9,9 @@ context('List View Settings', () => { cy.get('.sidebar-stat').should('contain', "Tags"); }); it('disable count and sidebar stats then verify', () => { + cy.wait(300); cy.visit('/desk#List/DocType/List'); + cy.wait(300); cy.get('.list-count').should('contain', "20 of"); cy.get('button').contains('Menu').click(); cy.get('.dropdown-menu li').filter(':visible').contains('Settings').click(); From ac53c4fd9adc2340ade5645c4beb4b7cb7473e99 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 12 May 2020 21:00:58 +0530 Subject: [PATCH 102/274] fix: update patch --- frappe/patches/v13_0/rename_onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/patches/v13_0/rename_onboarding.py b/frappe/patches/v13_0/rename_onboarding.py index 9b3add0620..c506c6076e 100644 --- a/frappe/patches/v13_0/rename_onboarding.py +++ b/frappe/patches/v13_0/rename_onboarding.py @@ -6,5 +6,5 @@ import frappe def execute(): if frappe.db.exists("DocType", "Onboarding"): - execute:frappe.rename_doc("DocType", "Onboarding", "Module Onboarding", ignore_if_exists=True) + frappe.rename_doc("DocType", "Onboarding", "Module Onboarding", ignore_if_exists=True) From d53b88a72fea30aa8d4004a3b35c04a6af572f85 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Tue, 12 May 2020 22:34:10 +0530 Subject: [PATCH 103/274] perf: Make tests faster (#10307) * feat: Ignore versions for test * test: Do not ignore version where needed * perf: Use frappe.db.get_single_value to leverage caching - frappe.get_cached_value does not support single value caching * style: Fix sider issue --- frappe/__init__.py | 2 +- frappe/desk/doctype/note/test_note.py | 8 ++++---- .../document_follow/test_document_follow.py | 20 +++++++++---------- frappe/model/document.py | 5 ++--- .../energy_point_settings.py | 2 +- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index eae8b0d76f..f0b6bfe41b 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -345,7 +345,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, style="margin: 0;">{}'''.format(table_rows) if flags.print_messages and out.message: - print("Message: " + repr(out.message).encode("utf-8")) + print(f"Message: {repr(out.message).encode('utf-8')}") if title: out.title = title diff --git a/frappe/desk/doctype/note/test_note.py b/frappe/desk/doctype/note/test_note.py index 8d46eaf336..38894a9c3d 100644 --- a/frappe/desk/doctype/note/test_note.py +++ b/frappe/desk/doctype/note/test_note.py @@ -20,7 +20,7 @@ class TestNote(unittest.TestCase): note = self.insert_note() note.title = 'test note 1' note.content = '1' - note.save() + note.save(ignore_version=False) version = frappe.get_doc('Version', dict(docname=note.name)) data = version.get_data() @@ -33,7 +33,7 @@ class TestNote(unittest.TestCase): # test add note.append('seen_by', {'user': 'Administrator'}) - note.save() + note.save(ignore_version=False) version = frappe.get_doc('Version', dict(docname=note.name)) data = version.get_data() @@ -48,7 +48,7 @@ class TestNote(unittest.TestCase): # test row change note.seen_by[0].user = 'Guest' - note.save() + note.save(ignore_version=False) version = frappe.get_doc('Version', dict(docname=note.name)) data = version.get_data() @@ -62,7 +62,7 @@ class TestNote(unittest.TestCase): # test remove note.seen_by = [] - note.save() + note.save(ignore_version=False) version = frappe.get_doc('Version', dict(docname=note.name)) data = version.get_data() diff --git a/frappe/email/doctype/document_follow/test_document_follow.py b/frappe/email/doctype/document_follow/test_document_follow.py index 4f1a8733cc..1208a6c5c1 100644 --- a/frappe/email/doctype/document_follow/test_document_follow.py +++ b/frappe/email/doctype/document_follow/test_document_follow.py @@ -14,7 +14,7 @@ class TestDocumentFollow(unittest.TestCase): event_doc = get_event() event_doc.description = "This is a test description for sending mail" - event_doc.save() + event_doc.save(ignore_version=False) doc = document_follow.follow_document("Event", event_doc.name , user.name, force=True) self.assertEquals(doc.user, user.name) @@ -45,12 +45,12 @@ def get_event(): return doc def get_user(): - doc = frappe.new_doc("User") - doc.email = "test@docsub.com" - doc.first_name = "Test" - doc.last_name = "User" - doc.send_welcome_email = 0 - doc.document_follow_notify = 1 - doc.document_follow_frequency = "Hourly" - doc.insert() - return doc \ No newline at end of file + doc = frappe.new_doc("User") + doc.email = "test@docsub.com" + doc.first_name = "Test" + doc.last_name = "User" + doc.send_welcome_email = 0 + doc.document_follow_notify = 1 + doc.document_follow_frequency = "Hourly" + doc.insert() + return doc \ No newline at end of file diff --git a/frappe/model/document.py b/frappe/model/document.py index 65cb6073b7..843cb421fe 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -297,8 +297,7 @@ class Document(BaseDocument): if ignore_permissions!=None: self.flags.ignore_permissions = ignore_permissions - if ignore_version!=None: - self.flags.ignore_version = ignore_version + self.flags.ignore_version = frappe.flags.in_test if ignore_version is None else ignore_version if self.get("__islocal") or not self.get("name"): self.insert() @@ -1339,4 +1338,4 @@ def check_doctype_has_consumers(doctype): if len(event_consumers) and event_consumers[0]: return True - return False \ No newline at end of file + return False diff --git a/frappe/social/doctype/energy_point_settings/energy_point_settings.py b/frappe/social/doctype/energy_point_settings/energy_point_settings.py index 737aab587c..7299eef916 100644 --- a/frappe/social/doctype/energy_point_settings/energy_point_settings.py +++ b/frappe/social/doctype/energy_point_settings/energy_point_settings.py @@ -12,7 +12,7 @@ class EnergyPointSettings(Document): pass def is_energy_point_enabled(): - return frappe.get_cached_value('Energy Point Settings', None, 'enabled') + return frappe.db.get_single_value('Energy Point Settings', 'enabled', True) def allocate_review_points(): settings = frappe.get_single('Energy Point Settings') From d94e48dbfb03b8f4cf131e30f69b77a184940182 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Wed, 13 May 2020 02:03:12 +0530 Subject: [PATCH 104/274] chore: set python_requires to >=3.6 recent changes in frappe/__init__.py make use of f-strings that are only available on python>=3.6 Signed-off-by: Chinmay D. Pai --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c70e138cd2..515e9448c2 100644 --- a/setup.py +++ b/setup.py @@ -58,5 +58,6 @@ setup( cmdclass = \ { 'clean': CleanCommand - } + }, + python_requires='>=3.6' ) From 79ff0eeaf8de7a3ec45471da4e88916b0739f2c7 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 13 May 2020 05:45:38 +0530 Subject: [PATCH 105/274] fix: Better error handling in FrappeClient login --- frappe/frappeclient.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/frappe/frappeclient.py b/frappe/frappeclient.py index 8611c21720..919c334e51 100644 --- a/frappe/frappeclient.py +++ b/frappe/frappeclient.py @@ -15,6 +15,9 @@ class AuthError(Exception): class SiteExpiredError(Exception): pass +class SiteUnreachableError(Exception): + pass + class FrappeException(Exception): pass @@ -53,9 +56,16 @@ class FrappeClient(object): if r.status_code==200 and r.json().get('message') in ("Logged In", "No App"): return r.json() + elif r.status_code == 502: + raise SiteUnreachableError else: - if json.loads(r.text).get('exc_type') == "SiteExpiredError": - raise SiteExpiredError + try: + error = json.loads(r.text) + if error.get('exc_type') == "SiteExpiredError": + raise SiteExpiredError + except json.decoder.JSONDecodeError: + error = r.text + print(error) raise AuthError def setup_key_authentication_headers(self): From ba7227ab084302f569eded82b706a949c3a81abf Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 13 May 2020 12:31:00 +0530 Subject: [PATCH 106/274] feat: Rate Limiter --- frappe/rate_limiter.py | 81 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 frappe/rate_limiter.py diff --git a/frappe/rate_limiter.py b/frappe/rate_limiter.py new file mode 100644 index 0000000000..26cad7de3e --- /dev/null +++ b/frappe/rate_limiter.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals + +from datetime import datetime +import frappe +from frappe import _ +from frappe.utils import cint +from werkzeug.wrappers import Response + + +def apply(): + rate_limit = frappe.conf.rate_limit + if rate_limit: + frappe.local.rate_limiter = RateLimiter(rate_limit["limit"], rate_limit["window"]) + frappe.local.rate_limiter.apply() + + +def update(): + if hasattr(frappe.local, "rate_limiter"): + frappe.local.rate_limiter.update() + + +def respond(): + if hasattr(frappe.local, "rate_limiter"): + return frappe.local.rate_limiter.respond() + + +class RateLimiter: + def __init__(self, limit, window): + self.limit = int(limit * 1000000) + self.window = window + + self.start = datetime.utcnow() + timestamp = int(frappe.utils.now_datetime().timestamp()) + + self.window_number, self.spent = divmod(timestamp, self.window) + self.key = frappe.cache().make_key(f"rate-limit-counter-{self.window_number}") + self.counter = cint(frappe.cache().get(self.key)) + self.remaining = max(self.limit - self.counter, 0) + self.reset = self.window - self.spent + + self.end = None + self.duration = None + self.rejected = False + + def apply(self): + if self.counter > self.limit: + self.rejected = True + self.reject() + + def reject(self): + raise frappe.TooManyRequestsError + + def update(self): + self.end = datetime.utcnow() + self.duration = int((self.end - self.start).total_seconds() * 1000000) + + pipeline = frappe.cache().pipeline() + pipeline.incrby(self.key, self.duration) + pipeline.expire(self.key, self.window) + pipeline.execute() + + def headers(self): + headers = { + "X-RateLimit-Reset": self.reset, + "X-RateLimit-Limit": self.limit, + "X-RateLimit-Remaining": self.remaining, + } + if self.rejected: + headers["Retry-After"] = self.reset + else: + headers["X-RateLimit-Used"] = self.duration + + return headers + + def respond(self): + if self.rejected: + return Response(_("Too Many Requests"), status=429, headers=self.headers()) From f71dc3c991d5ee0e8b347d7df303dbcfc5448910 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 13 May 2020 12:32:20 +0530 Subject: [PATCH 107/274] feat(rate-limiter): Log data with monitor --- frappe/monitor.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frappe/monitor.py b/frappe/monitor.py index b056286cf9..6802a59584 100644 --- a/frappe/monitor.py +++ b/frappe/monitor.py @@ -81,6 +81,12 @@ class Monitor: self.data.request.status_code = response.status_code self.data.request.response_length = int(response.headers.get("Content-Length", 0)) + if hasattr(frappe.local, "rate_limiter"): + limiter = frappe.local.rate_limiter + self.data.request.counter = limiter.counter + if limiter.rejected: + self.data.request.reset = limiter.reset + self.store() except Exception: traceback.print_exc() From 19e8008c3c310e56a3d21fe3c0bb8e8d5eaac498 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 13 May 2020 12:33:34 +0530 Subject: [PATCH 108/274] feat(rate-limiter): Ignore requests above limit --- frappe/app.py | 9 +++++++++ frappe/exceptions.py | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/frappe/app.py b/frappe/app.py index 41798b0bc4..3bb764149b 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -26,6 +26,7 @@ from frappe.core.doctype.comment.comment import update_comments_in_parent_after_ from frappe import _ import frappe.recorder import frappe.monitor +import frappe.rate_limiter local_manager = LocalManager([frappe.local]) @@ -54,6 +55,7 @@ def application(request): frappe.recorder.record() frappe.monitor.start() + frappe.rate_limiter.apply() if frappe.local.form_dict.cmd: response = frappe.handler.handle() @@ -93,9 +95,13 @@ def application(request): if response and hasattr(frappe.local, 'cookie_manager'): frappe.local.cookie_manager.flush_cookies(response=response) + frappe.rate_limiter.update() frappe.monitor.stop(response) frappe.recorder.dump() + if response and hasattr(frappe.local, 'rate_limiter'): + response.headers.extend(frappe.local.rate_limiter.headers()) + frappe.destroy() return response @@ -171,6 +177,9 @@ def handle_exception(e): http_status_code=http_status_code, indicator_color='red') return_as_message = True + elif http_status_code == 429: + response = frappe.rate_limiter.respond() + else: traceback = "
" + sanitize_html(frappe.get_traceback()) + "
" if frappe.local.flags.disable_traceback: diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 9a1c1fb0b0..5a1181f31e 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -49,6 +49,11 @@ class Redirect(Exception): class CSRFTokenError(Exception): http_status_code = 400 + +class TooManyRequestsError(Exception): + http_status_code = 429 + + class ImproperDBConfigurationError(Exception): """ Used when frappe detects that database or tables are not properly From c532a873703c11c774cf25d108a48266a64c2892 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 13 May 2020 12:33:58 +0530 Subject: [PATCH 109/274] tests(rate-limiter): Rate Limiter tests --- frappe/tests/test_rate_limiter.py | 114 ++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 frappe/tests/test_rate_limiter.py diff --git a/frappe/tests/test_rate_limiter.py b/frappe/tests/test_rate_limiter.py new file mode 100644 index 0000000000..40a05153ee --- /dev/null +++ b/frappe/tests/test_rate_limiter.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import unittest +import frappe +import time +import frappe.rate_limiter +from frappe.rate_limiter import RateLimiter +from frappe.utils import cint +from werkzeug.wrappers import Response + + +class TestRateLimiter(unittest.TestCase): + def setUp(self): + pass + + def test_apply_with_limit(self): + frappe.conf.rate_limit = {"window": 86400, "limit": 1} + frappe.rate_limiter.apply() + + self.assertTrue(hasattr(frappe.local, "rate_limiter")) + self.assertIsInstance(frappe.local.rate_limiter, RateLimiter) + + frappe.cache().delete(frappe.local.rate_limiter.key) + delattr(frappe.local, "rate_limiter") + + def test_apply_without_limit(self): + frappe.conf.rate_limit = None + frappe.rate_limiter.apply() + + self.assertFalse(hasattr(frappe.local, "rate_limiter")) + + def test_respond_over_limit(self): + limiter = RateLimiter(0.01, 86400) + time.sleep(0.01) + limiter.update() + + frappe.conf.rate_limit = {"window": 86400, "limit": 0.01} + self.assertRaises(frappe.TooManyRequestsError, frappe.rate_limiter.apply) + frappe.rate_limiter.update() + + response = frappe.rate_limiter.respond() + + self.assertIsInstance(response, Response) + self.assertEqual(response.status_code, 429) + self.assertIn("Retry-After", response.headers) + self.assertNotIn("X-RateLimit-Used", response.headers) + self.assertIn("X-RateLimit-Reset", response.headers) + self.assertIn("X-RateLimit-Limit", response.headers) + self.assertIn("X-RateLimit-Remaining", response.headers) + self.assertTrue(int(response.headers["X-RateLimit-Reset"]) <= 86400) + self.assertEqual(int(response.headers["X-RateLimit-Limit"]), 10000) + self.assertEqual(int(response.headers["X-RateLimit-Remaining"]), 0) + + frappe.cache().delete(limiter.key) + frappe.cache().delete(frappe.local.rate_limiter.key) + delattr(frappe.local, "rate_limiter") + + def test_respond_under_limit(self): + frappe.conf.rate_limit = {"window": 86400, "limit": 0.01} + frappe.rate_limiter.apply() + frappe.rate_limiter.update() + response = frappe.rate_limiter.respond() + self.assertEqual(response, None) + + frappe.cache().delete(frappe.local.rate_limiter.key) + delattr(frappe.local, "rate_limiter") + + def test_headers_under_limit(self): + frappe.conf.rate_limit = {"window": 86400, "limit": 0.01} + frappe.rate_limiter.apply() + frappe.rate_limiter.update() + headers = frappe.local.rate_limiter.headers() + self.assertNotIn("Retry-After", headers) + self.assertIn("X-RateLimit-Reset", headers) + self.assertTrue(int(headers["X-RateLimit-Reset"] < 86400)) + self.assertEqual(int(headers["X-RateLimit-Used"]), frappe.local.rate_limiter.duration) + self.assertEqual(int(headers["X-RateLimit-Limit"]), 10000) + self.assertEqual(int(headers["X-RateLimit-Remaining"]), 10000) + + frappe.cache().delete(frappe.local.rate_limiter.key) + delattr(frappe.local, "rate_limiter") + + def test_reject_over_limit(self): + limiter = RateLimiter(0.01, 86400) + time.sleep(0.01) + limiter.update() + + limiter = RateLimiter(0.01, 86400) + self.assertRaises(frappe.TooManyRequestsError, limiter.apply) + + frappe.cache().delete(limiter.key) + + def test_do_not_reject_under_limit(self): + limiter = RateLimiter(0.01, 86400) + time.sleep(0.01) + limiter.update() + + limiter = RateLimiter(0.02, 86400) + self.assertEqual(limiter.apply(), None) + + frappe.cache().delete(limiter.key) + + def test_update_method(self): + limiter = RateLimiter(0.01, 86400) + time.sleep(0.01) + limiter.update() + + self.assertEqual(limiter.duration, cint(frappe.cache().get(limiter.key))) + + frappe.cache().delete(limiter.key) From 39ecc61b61d75bec0bfadf655de08e6a6c9dae69 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 12:34:34 +0530 Subject: [PATCH 110/274] feat: added tour for web page --- frappe/website/doctype/web_page/web_page.js | 55 +++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/frappe/website/doctype/web_page/web_page.js b/frappe/website/doctype/web_page/web_page.js index c0a3bcdc20..c714b98241 100644 --- a/frappe/website/doctype/web_page/web_page.js +++ b/frappe/website/doctype/web_page/web_page.js @@ -48,3 +48,58 @@ frappe.ui.form.on('Web Page', { frappe.utils.set_meta_tag(frm.doc.route); } }); + +frappe.tour['Web Page'] = [ + { + fieldname: "title", + title: "Title of the page", + description: "This title will be used as the title of the webpage as well as in meta tags", + }, + { + fieldname: "published", + title: "Makes the page public", + description: "Checking this will publish the page on your website and it'll be visible to everyone.", + }, + { + fieldname: "route", + title: "URL of the page", + description: "This will be automatically generated when you publish the page, you can also enter a route yourself if you wish", + }, + { + fieldname: "content_type", + title: "Content type for building the page", + description: `You can select one from the following,
+
    +
  • Rich Text: Standard rich text editor with controls
  • +
  • Markdown: Github flavoured markdown syntax
  • +
  • HTML: HTML with jinja support
  • +
  • Page Builder: Frappe page builder using components
  • +
+ ` + }, + { + fieldname: "insert_code", + title: "Client Script", + description: "Checking this will show a text area where you can write custom javascript that will run on this page.", + }, + { + fieldname: "meta_title", + title: "Meta title for SEO", + description: "By default the title is used as meta title, adding a value here will override it.", + }, + { + fieldname: "meta_title", + title: "Meta Title", + description: "By default the title is used as meta title, adding a value here will override it.", + }, + { + fieldname: "meta_description", + title: "Meta Description", + description: "The meta description is an HTML attribute that provides a brief summary of a web page. Search engines such as Google often display the meta description in search results, which can influence click-through rates." + }, + { + fieldname: "meta_image", + title: "Meta Image", + description: "The meta image is unique image representing the content of the page. Images for this Card should be at least 280px in width, and at least 150px in height." + }, +]; \ No newline at end of file From aa76a03d266d085080dcfdc389badaea4f69ef07 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 12:34:55 +0530 Subject: [PATCH 111/274] feat: added web page tour to onboarding --- .../module_onboarding/website/website.json | 5 ++++- .../add_blog_category/add_blog_category.json | 1 + .../create_blogger/create_blogger.json | 1 + .../enable_website_tracking.json | 1 + .../introduction_to_website.json | 1 + .../web_page_tour/web_page_tour.json | 17 +++++++++++++++++ 6 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 frappe/website/onboarding_step/web_page_tour/web_page_tour.json diff --git a/frappe/website/module_onboarding/website/website.json b/frappe/website/module_onboarding/website/website.json index b849a809ed..606ef09811 100644 --- a/frappe/website/module_onboarding/website/website.json +++ b/frappe/website/module_onboarding/website/website.json @@ -10,7 +10,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/website", "idx": 0, "is_complete": 0, - "modified": "2020-04-30 20:23:06.438314", + "modified": "2020-05-13 12:32:35.269025", "modified_by": "Administrator", "module": "Website", "name": "Website", @@ -27,6 +27,9 @@ }, { "step": "Enable Website Tracking" + }, + { + "step": "Web Page Tour" } ], "subtitle": "Blogs, website view tracking, and more.", diff --git a/frappe/website/onboarding_step/add_blog_category/add_blog_category.json b/frappe/website/onboarding_step/add_blog_category/add_blog_category.json index a0d07c8464..5936e78c68 100644 --- a/frappe/website/onboarding_step/add_blog_category/add_blog_category.json +++ b/frappe/website/onboarding_step/add_blog_category/add_blog_category.json @@ -6,6 +6,7 @@ "idx": 0, "is_complete": 0, "is_mandatory": 0, + "is_single": 0, "is_skipped": 0, "modified": "2020-04-30 19:06:10.750976", "modified_by": "Administrator", diff --git a/frappe/website/onboarding_step/create_blogger/create_blogger.json b/frappe/website/onboarding_step/create_blogger/create_blogger.json index 5162e7e895..841325ca6a 100644 --- a/frappe/website/onboarding_step/create_blogger/create_blogger.json +++ b/frappe/website/onboarding_step/create_blogger/create_blogger.json @@ -6,6 +6,7 @@ "idx": 0, "is_complete": 0, "is_mandatory": 0, + "is_single": 0, "is_skipped": 0, "modified": "2020-04-30 19:06:10.694419", "modified_by": "Administrator", diff --git a/frappe/website/onboarding_step/enable_website_tracking/enable_website_tracking.json b/frappe/website/onboarding_step/enable_website_tracking/enable_website_tracking.json index 56a4fa58b6..b37a704b1e 100644 --- a/frappe/website/onboarding_step/enable_website_tracking/enable_website_tracking.json +++ b/frappe/website/onboarding_step/enable_website_tracking/enable_website_tracking.json @@ -7,6 +7,7 @@ "idx": 0, "is_complete": 0, "is_mandatory": 0, + "is_single": 0, "is_skipped": 0, "modified": "2020-04-30 20:22:50.778590", "modified_by": "Administrator", diff --git a/frappe/website/onboarding_step/introduction_to_website/introduction_to_website.json b/frappe/website/onboarding_step/introduction_to_website/introduction_to_website.json index 683d0a889e..f3c95657e8 100644 --- a/frappe/website/onboarding_step/introduction_to_website/introduction_to_website.json +++ b/frappe/website/onboarding_step/introduction_to_website/introduction_to_website.json @@ -6,6 +6,7 @@ "idx": 0, "is_complete": 0, "is_mandatory": 1, + "is_single": 0, "is_skipped": 0, "modified": "2020-04-30 19:06:10.578218", "modified_by": "Administrator", diff --git a/frappe/website/onboarding_step/web_page_tour/web_page_tour.json b/frappe/website/onboarding_step/web_page_tour/web_page_tour.json new file mode 100644 index 0000000000..1ee98d69ee --- /dev/null +++ b/frappe/website/onboarding_step/web_page_tour/web_page_tour.json @@ -0,0 +1,17 @@ +{ + "action": "Show Form Tour", + "creation": "2020-05-13 12:32:15.966570", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-13 12:32:15.966570", + "modified_by": "Administrator", + "name": "Web Page Tour", + "owner": "Administrator", + "reference_document": "Web Page", + "title": "Learn about Web Pages" +} \ No newline at end of file From aef567fddef2f9e84305e3cda9c8f3d922ca6f78 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 13 May 2020 13:45:55 +0530 Subject: [PATCH 112/274] fix(rate-limiter): Remove duplicate headers --- frappe/rate_limiter.py | 2 +- frappe/tests/test_rate_limiter.py | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/frappe/rate_limiter.py b/frappe/rate_limiter.py index 26cad7de3e..e29b2b3061 100644 --- a/frappe/rate_limiter.py +++ b/frappe/rate_limiter.py @@ -78,4 +78,4 @@ class RateLimiter: def respond(self): if self.rejected: - return Response(_("Too Many Requests"), status=429, headers=self.headers()) + return Response(_("Too Many Requests"), status=429) diff --git a/frappe/tests/test_rate_limiter.py b/frappe/tests/test_rate_limiter.py index 40a05153ee..292b521460 100644 --- a/frappe/tests/test_rate_limiter.py +++ b/frappe/tests/test_rate_limiter.py @@ -46,14 +46,16 @@ class TestRateLimiter(unittest.TestCase): self.assertIsInstance(response, Response) self.assertEqual(response.status_code, 429) - self.assertIn("Retry-After", response.headers) - self.assertNotIn("X-RateLimit-Used", response.headers) - self.assertIn("X-RateLimit-Reset", response.headers) - self.assertIn("X-RateLimit-Limit", response.headers) - self.assertIn("X-RateLimit-Remaining", response.headers) - self.assertTrue(int(response.headers["X-RateLimit-Reset"]) <= 86400) - self.assertEqual(int(response.headers["X-RateLimit-Limit"]), 10000) - self.assertEqual(int(response.headers["X-RateLimit-Remaining"]), 0) + + headers = frappe.local.rate_limiter.headers() + self.assertIn("Retry-After", headers) + self.assertNotIn("X-RateLimit-Used", headers) + self.assertIn("X-RateLimit-Reset", headers) + self.assertIn("X-RateLimit-Limit", headers) + self.assertIn("X-RateLimit-Remaining", headers) + self.assertTrue(int(headers["X-RateLimit-Reset"]) <= 86400) + self.assertEqual(int(headers["X-RateLimit-Limit"]), 10000) + self.assertEqual(int(headers["X-RateLimit-Remaining"]), 0) frappe.cache().delete(limiter.key) frappe.cache().delete(frappe.local.rate_limiter.key) From 9bd31b05fa0f02ae3ebcd7d70d041db6a7c12a0f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:12:59 +0530 Subject: [PATCH 113/274] refactor: minor change in field order --- frappe/desk/doctype/desk_page/desk_page.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/desk_page/desk_page.json b/frappe/desk/doctype/desk_page/desk_page.json index 0064d7bd28..851eb43b23 100644 --- a/frappe/desk/doctype/desk_page/desk_page.json +++ b/frappe/desk/doctype/desk_page/desk_page.json @@ -8,8 +8,8 @@ "engine": "InnoDB", "field_order": [ "label", - "extends", "for_user", + "extends", "module", "category", "restrict_to_domain", @@ -170,7 +170,7 @@ "search_index": 1 }, { - "depends_on": "eval:doc.extends_another_page == 1", + "depends_on": "eval:doc.extends_another_page == 1 || doc.for_user", "fieldname": "extends", "fieldtype": "Link", "in_standard_filter": 1, @@ -192,7 +192,7 @@ } ], "links": [], - "modified": "2020-05-12 16:12:20.708394", + "modified": "2020-05-13 19:01:42.041524", "modified_by": "Administrator", "module": "Desk", "name": "Desk Page", From a789ed772eef71997f3ee966239774919af441de Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:13:18 +0530 Subject: [PATCH 114/274] feat: allow form editing for users --- frappe/desk/doctype/desk_page/desk_page.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/desk_page/desk_page.js b/frappe/desk/doctype/desk_page/desk_page.js index 3087a5f5b8..e11d83816f 100644 --- a/frappe/desk/doctype/desk_page/desk_page.js +++ b/frappe/desk/doctype/desk_page/desk_page.js @@ -2,10 +2,16 @@ // For license information, please see license.txt frappe.ui.form.on('Desk Page', { - setup: function(frm) { + refresh: function(frm) { frm.get_field("is_standard").toggle(frappe.boot.developer_mode); frm.get_field("extends_another_page").toggle(frappe.boot.developer_mode); - if (!frappe.boot.developer_mode || frm.doc.for_user) { + frm.get_field("developer_mode_only").toggle(frappe.boot.developer_mode); + + if (frm.doc.for_user) { + frm.set_df_property("extends", "read_only", true); + } + + if (frm.doc.for_user || frm.doc.is_standard) { frm.trigger('disable_form'); } }, From dad0a87f78531ea9fcf04da55c906486c103b8f0 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:13:33 +0530 Subject: [PATCH 115/274] feat: allow custom pages in sidebar query --- frappe/desk/desktop.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index a912ca341c..284aba20c8 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -292,7 +292,6 @@ def get_desk_sidebar_items(flatten=False): filters = { 'restrict_to_domain': ['in', frappe.get_active_domains()], 'extends_another_page': 0, - 'is_standard': 1, 'for_user': '', 'module': ['not in', blocked_modules] } From 3e661589d141db29e6c0ffeab51f04ec6699a961 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:27:12 +0530 Subject: [PATCH 116/274] feat: enable save explicitly during refresh --- 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 e11d83816f..ec8eaaa60b 100644 --- a/frappe/desk/doctype/desk_page/desk_page.js +++ b/frappe/desk/doctype/desk_page/desk_page.js @@ -3,6 +3,7 @@ frappe.ui.form.on('Desk Page', { refresh: function(frm) { + frm.enable_save(); frm.get_field("is_standard").toggle(frappe.boot.developer_mode); frm.get_field("extends_another_page").toggle(frappe.boot.developer_mode); frm.get_field("developer_mode_only").toggle(frappe.boot.developer_mode); @@ -17,7 +18,6 @@ frappe.ui.form.on('Desk Page', { }, disable_form: function(frm) { - frm.set_read_only(); frm.fields .filter(field => field.has_input) .forEach(field => { From 830e89164486e58fa2b3d095f3f532fc6048223a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:27:36 +0530 Subject: [PATCH 117/274] refactor: better UX for shortcut table --- frappe/desk/doctype/desk_shortcut/desk_shortcut.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/desk_shortcut/desk_shortcut.json b/frappe/desk/doctype/desk_shortcut/desk_shortcut.json index 9f8990732a..550ea609c8 100644 --- a/frappe/desk/doctype/desk_shortcut/desk_shortcut.json +++ b/frappe/desk/doctype/desk_shortcut/desk_shortcut.json @@ -6,9 +6,9 @@ "engine": "InnoDB", "field_order": [ "type", - "label", - "column_break_4", "link_to", + "column_break_4", + "label", "icon", "restrict_to_domain", "section_break_5", @@ -81,13 +81,14 @@ { "fieldname": "label", "fieldtype": "Data", + "in_list_view": 1, "label": "Label", "reqd": 1 } ], "istable": 1, "links": [], - "modified": "2020-04-07 19:04:23.645198", + "modified": "2020-05-13 19:26:34.229669", "modified_by": "Administrator", "module": "Desk", "name": "Desk Shortcut", From e559e30d0235e6f42379317fdc2cf125b60ab080 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 13 May 2020 19:42:54 +0530 Subject: [PATCH 118/274] 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 0ae5e65c2cfe30d89f1599b6fafac3adb93b670c Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 13 May 2020 19:46:09 +0530 Subject: [PATCH 119/274] fix: Routing for doctypes having treeviews (#10300) * fix: Routing for doctypes having treeviews * fix: Linting errors --- frappe/public/js/frappe/model/model.js | 5 +++++ frappe/public/js/frappe/widgets/utils.js | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index b87dad1d36..663850d08c 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -268,6 +268,11 @@ $.extend(frappe.model, { return frappe.boot.single_types.indexOf(doctype) != -1; }, + is_tree: function(doctype) { + if (!doctype) return false; + return frappe.boot.treeviews.indexOf(doctype) != -1; + }, + can_import: function(doctype, frm) { // system manager can always import if(frappe.user_roles.includes("System Manager")) return true; diff --git a/frappe/public/js/frappe/widgets/utils.js b/frappe/public/js/frappe/widgets/utils.js index 59067bd9a0..0d93bb3784 100644 --- a/frappe/public/js/frappe/widgets/utils.js +++ b/frappe/public/js/frappe/widgets/utils.js @@ -8,7 +8,9 @@ function generate_route(item) { if (item.link) { route = strip(item.link, "#"); } else if (type === "doctype") { - if (frappe.model.is_single(item.doctype)) { + if (frappe.model.is_tree(item.doctype)) { + route = "Tree/" + item.doctype; + } else if (frappe.model.is_single(item.doctype)) { route = "Form/" + item.doctype; } else { if (item.filters) { From 218e945b2614b0b2a72d59bfaf74ca2862a4794f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:56:00 +0530 Subject: [PATCH 120/274] feat: add get_last_day_of_week function --- frappe/utils/data.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 58c74a905d..906b07fd08 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -190,6 +190,10 @@ def get_first_day(dt, d_years=0, d_months=0): def get_first_day_of_week(dt): return dt - datetime.timedelta(days=dt.weekday()) +def get_last_day_of_week(dt): + dt = get_first_day_of_week(dt) + return dt + datetime.timedelta(days=6) + def get_last_day(dt): """ Returns last day of the month using: From 8de7f7602e7aa6ba8ac10b60b5a83886fd77ab93 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:56:16 +0530 Subject: [PATCH 121/274] refactor: replace pandas date range with native function --- frappe/utils/dateutils.py | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/frappe/utils/dateutils.py b/frappe/utils/dateutils.py index 907cab3d02..b98c1c12fc 100644 --- a/frappe/utils/dateutils.py +++ b/frappe/utils/dateutils.py @@ -6,6 +6,9 @@ import frappe import frappe.defaults import datetime from frappe.utils import get_datetime +from frappe.utils import nowdate, add_to_date, getdate, get_datetime +from frappe.utils.data import get_last_day_of_week +from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending from six import string_types # global values -- used for caching @@ -74,8 +77,29 @@ def datetime_in_user_format(date_time): from frappe.utils import formatdate return formatdate(date_time.date()) + " " + date_time.strftime("%H:%M") -def get_date_range(from_date, to_date, frequency="M", normalize=True): - from pandas import date_range - date_range = date_range(start=from_date, end=to_date, freq=frequency, normalize=normalize).to_pydatetime().tolist() +def get_dates_from_timegrain(from_date, to_date, timegrain="Daily"): + from_date = getdate(from_date) + to_date = getdate(to_date) - return date_range \ No newline at end of file + days = months = years = 0 + if "Daily" == timegrain: + days = 1 + elif "Weekly" == timegrain: + days = 7 + elif "Monthly" == timegrain: + months = 1 + elif "Quarterly" == timegrain: + months = 3 + + if "Weekly" == timegrain: + dates = [get_last_day_of_week(from_date)] + else: + dates = [get_period_ending(from_date, timegrain)] + + while getdate(dates[-1]) < getdate(to_date): + if "Weekly" == timegrain: + date = get_last_day_of_week(add_to_date(dates[-1], years=years, months=months, days=days)) + else: + date = get_period_ending(add_to_date(dates[-1], years=years, months=months, days=days), timegrain) + dates.append(date) + return dates \ No newline at end of file From 0eaf778fe24911970f9e1b02e0e743fc63082103 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:56:33 +0530 Subject: [PATCH 122/274] refactor: change values for report range options --- .../report/website_analytics/website_analytics.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/website/report/website_analytics/website_analytics.js b/frappe/website/report/website_analytics/website_analytics.js index 7e051afa8c..9079949724 100644 --- a/frappe/website/report/website_analytics/website_analytics.js +++ b/frappe/website/report/website_analytics/website_analytics.js @@ -8,7 +8,7 @@ frappe.query_reports["Website Analytics"] = { fieldname: "from_date", label: __("From Date"), fieldtype: "Date", - default: frappe.datetime.add_days(frappe.datetime.now_date(true), -7), + default: frappe.datetime.add_days(frappe.datetime.now_date(true), -100), }, { fieldname:"to_date", @@ -21,11 +21,11 @@ frappe.query_reports["Website Analytics"] = { label: __("Range"), fieldtype: "Select", options: [ - { "value": "D", "label": __("Daily") }, - { "value": "W", "label": __("Weekly") }, - { "value": "M", "label": __("Monthly") }, + { "value": "Daily", "label": __("Daily") }, + { "value": "Weekly", "label": __("Weekly") }, + { "value": "Monthly", "label": __("Monthly") }, ], - default: "D", + default: "Daily", reqd: 1 } ] From 0bb10609989a71bcde291cb7cfaec43b428cb978 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:57:30 +0530 Subject: [PATCH 123/274] feat: use new date range function --- .../website_analytics/website_analytics.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/frappe/website/report/website_analytics/website_analytics.py b/frappe/website/report/website_analytics/website_analytics.py index 694bc9e797..97c330fed9 100644 --- a/frappe/website/report/website_analytics/website_analytics.py +++ b/frappe/website/report/website_analytics/website_analytics.py @@ -4,7 +4,8 @@ from __future__ import unicode_literals import frappe from datetime import datetime -from frappe.utils.dateutils import get_date_range +from frappe.utils import getdate +from frappe.utils.dateutils import get_dates_from_timegrain def execute(filters=None): return WebsiteAnalytics(filters).run() @@ -20,7 +21,7 @@ class WebsiteAnalytics(object): self.filters.from_date = frappe.utils.add_days(self.filters.to_date, -7) if not self.filters.range: - self.filters.range = "D" + self.filters.range = "Daily" self.filters.to_date = frappe.utils.add_days(self.filters.to_date, 1) self.query_filters = {'creation': ['between', [self.filters.from_date, self.filters.to_date]]} @@ -89,10 +90,10 @@ class WebsiteAnalytics(object): field = 'creation' date_format = '%Y-%m-%d' - if filters_range == "W": + if filters_range == "Weekly": field = 'ADDDATE(creation, INTERVAL 1-DAYOFWEEK(creation) DAY)' - elif filters_range == "M": + elif filters_range == "Monthly": date_format = '%Y-%m-01' query = """ @@ -115,10 +116,10 @@ class WebsiteAnalytics(object): field = 'creation' granularity = 'day' - if filters_range == "W": + if filters_range == "Weekly": granularity = 'week' - elif filters_range == "M": + elif filters_range == "Monthly": granularity = 'day' query = """ @@ -149,8 +150,8 @@ class WebsiteAnalytics(object): return self.prepare_chart_data(self.chart_data) def prepare_chart_data(self, data): - date_range = get_date_range(self.filters.from_date, self.filters.to_date, self.filters.range) - if self.filters.range == "M": + date_range = get_dates_from_timegrain(self.filters.from_date, self.filters.to_date, self.filters.range) + if self.filters.range == "Monthly": date_range = [frappe.utils.add_days(dd, 1) for dd in date_range] labels = [] @@ -159,8 +160,8 @@ class WebsiteAnalytics(object): def get_data_for_date(date): for item in data: - item_date = frappe.utils.get_datetime(item.get("date")).date() - if item_date == date.date(): + item_date = getdate(item.get("date")) + if item_date == date: return item return {'count': 0, 'unique_count': 0} From e2b5fcb6abe46d4b3da13263aca427d316d068ae Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:58:33 +0530 Subject: [PATCH 124/274] chore: update fixtures --- frappe/website/dashboard_fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/dashboard_fixtures.py b/frappe/website/dashboard_fixtures.py index 01f1376d43..1ac7ca60ec 100644 --- a/frappe/website/dashboard_fixtures.py +++ b/frappe/website/dashboard_fixtures.py @@ -20,7 +20,7 @@ def get_charts(): return [{ "chart_name": "Website Analytics", "chart_type": "Report", - "custom_options": "{\"type\": \"line\", \"lineOptions\": {\"regionFill\": 1}, \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"colors\": [\"#7cd6fd\", \"#5e64ff\"], \"tooltipOptions\": {}}", + "custom_options": "{\"type\": \"line\", \"lineOptions\": {\"regionFill\": 1}, \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}}", "doctype": "Dashboard Chart", "filters_json": "{}", "group_by_type": "Count", From 9d03190335093f16a06a8a8b62936009e05e1129 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 13 May 2020 20:10:35 +0530 Subject: [PATCH 125/274] fix: Re-raise swallowed exception --- frappe/website/render.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/website/render.py b/frappe/website/render.py index ae84d85e5b..2e448e7d5b 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -180,6 +180,8 @@ def build(path): return build_page(path) else: raise + except Exception: + raise def build_page(path): if not getattr(frappe.local, "path", None): From 16b518b7c54b3c9c44d9c39879c788c878b3e20b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 13 May 2020 20:10:46 +0530 Subject: [PATCH 126/274] fix: Redirect website user to /me --- frappe/www/login.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/www/login.py b/frappe/www/login.py index b1abd6a2c4..65952f0154 100644 --- a/frappe/www/login.py +++ b/frappe/www/login.py @@ -20,7 +20,7 @@ def get_context(context): if frappe.session.user != "Guest": if not redirect_to: - redirect_to = "/" if frappe.session.data.user_type=="Website User" else "/desk" + redirect_to = "/me" if frappe.session.data.user_type=="Website User" else "/desk" frappe.local.flags.redirect_location = redirect_to raise frappe.Redirect @@ -96,4 +96,4 @@ def login_via_token(login_token): frappe.local.form_dict.sid = sid frappe.local.login_manager = LoginManager() - redirect_post_login(desk_user = frappe.db.get_value("User", frappe.session.user, "user_type")=="System User") \ No newline at end of file + redirect_post_login(desk_user = frappe.db.get_value("User", frappe.session.user, "user_type")=="System User") From 977e6c23f235ea5dfeb7a8c958a2816c11a3e440 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 20:42:19 +0530 Subject: [PATCH 127/274] perf(desk): Even faster desk (#9930) * feat (perf): even faster desk * fix: user not found bug * feat: store db counts * feat: use cached db.get_value * style (sider): comparison to None should be 'if cond is None:' * feat: add keys to user_cache_keys * refactor: cache blocked modules and roles Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/boot.py | 19 +++++++++++---- frappe/cache_manager.py | 3 ++- frappe/desk/desktop.py | 53 +++++++++++++++++++++++++++++------------ 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index 9d5dbe1909..42b5ca38b7 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -107,13 +107,20 @@ def load_desktop_data(bootinfo): bootinfo.allowed_modules = get_modules_from_all_apps_for_user() bootinfo.allowed_workspaces = get_desk_sidebar_items(True) -def get_allowed_pages(): - return get_user_pages_or_reports('Page') +def get_allowed_pages(cache=False): + return get_user_pages_or_reports('Page', cache=cache) -def get_allowed_reports(): - return get_user_pages_or_reports('Report') +def get_allowed_reports(cache=False): + return get_user_pages_or_reports('Report', cache=cache) + +def get_user_pages_or_reports(parent, cache=False): + _cache = frappe.cache() + + if cache: + has_role = _cache.get_value('has_role:' + parent, user=frappe.session.user) + if has_role: + return has_role -def get_user_pages_or_reports(parent): roles = frappe.get_roles() has_role = {} column = get_column(parent) @@ -184,6 +191,8 @@ def get_user_pages_or_reports(parent): for report in reports: has_role[report.name]["report_type"] = report.report_type + # Expire every six hours + _cache.set_value('has_role:' + parent, has_role, frappe.session.user, 21600) return has_role def get_column(doctype): diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 2daed59074..4560680653 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -20,7 +20,8 @@ global_cache_keys = ("app_hooks", "installed_apps", user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang", "defaults", "user_permissions", "home_page", "linked_with", - "desktop_icons", 'portal_menu_items') + "desktop_icons", 'portal_menu_items', 'user_perm_can_read', + "has_role:Page", "has_role:Report") doctype_cache_keys = ("meta", "form_meta", "table_columns", "last_modified", "linked_doctypes", 'notifications', 'workflow' ,'energy_point_rule_map') diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index a912ca341c..76be5f581d 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -21,19 +21,17 @@ class Workspace: self.extended_charts = [] self.extended_shortcuts = [] - user = frappe.get_user() - user.build_permissions() - - user_doc = frappe.get_doc('User', frappe.session.user) - self.blocked_modules = user_doc.get_blocked_modules() + self.user = frappe.get_user() + self.allowed_modules = self.get_cached_value('user_allowed_modules', self.get_allowed_modules) self.doc = self.get_page_for_user() - if self.doc.module in self.blocked_modules: + if self.doc.module not in self.allowed_modules: raise frappe.PermissionError - self.user = user - self.allowed_pages = get_allowed_pages() - self.allowed_reports = get_allowed_reports() + self.can_read = self.get_cached_value('user_perm_can_read', self.get_can_read_items) + + self.allowed_pages = get_allowed_pages(cache=True) + self.allowed_reports = get_allowed_reports(cache=True) self.onboarding_doc = self.get_onboarding_doc() self.onboarding = None @@ -41,6 +39,31 @@ class Workspace: self.restricted_doctypes = frappe.cache().get_value("domain_restricted_doctypes") or build_domain_restriced_doctype_cache() self.restricted_pages = frappe.cache().get_value("domain_restricted_pages") or build_domain_restriced_page_cache() + def get_cached_value(self, cache_key, fallback_fn): + _cache = frappe.cache() + + value = _cache.get_value(cache_key, user=frappe.session.user) + if value: + return value + + value = fallback_fn() + + # Expire every six hour + _cache.set_value(cache_key, value, frappe.session.user, 21600) + return value + + def get_can_read_items(self): + if not self.user.can_read: + self.user.build_permissions() + + return self.user.can_read + + def get_allowed_modules(self): + if not self.user.allow_modules: + self.user.build_permissions() + + return self.user.allow_modules + def get_page_for_user(self): filters = { 'extends': self.page_name, @@ -68,7 +91,7 @@ class Workspace: # Check if user is allowed allowed_roles = set(doc.get_allowed_roles()) - user_roles = set(self.user.get_roles()) + user_roles = set(frappe.get_roles()) if not allowed_roles & user_roles: return None @@ -83,7 +106,7 @@ class Workspace: "extends": self.page_name, 'restrict_to_domain': ['in', frappe.get_active_domains()], 'for_user': '', - 'module': ['not in', self.blocked_modules] + 'module': ['in', self.allowed_modules] }) pages = [frappe.get_doc("Desk Page", page['name']) for page in pages] @@ -97,7 +120,7 @@ class Workspace: item_type = item_type.lower() if item_type == "doctype": - return (name in self.user.can_read and name in self.restricted_doctypes) + return (name in self.can_read and name in self.restricted_doctypes) if item_type == "page": return (name in self.allowed_pages and name in self.restricted_pages) if item_type == "report": @@ -140,9 +163,9 @@ class Workspace: default_country = frappe.db.get_default("country") def _doctype_contains_a_record(name): - exists = self.table_counts.get(name) - if not exists: - if not frappe.db.get_value('DocType', name, 'issingle'): + exists = self.table_counts.get(name, None) + if exists is None: + if not frappe.db.get_value('DocType', name, 'issingle', cache=True): exists = frappe.db.count(name) else: exists = True From 55fe81aaac2d436071d1dd0d0b2379841513e01d Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 13 May 2020 20:56:51 +0530 Subject: [PATCH 128/274] 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 9bba3154bc602f77f9e9c9f82fc0de40de00dce8 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Wed, 13 May 2020 21:22:08 +0530 Subject: [PATCH 129/274] fix: redirect after login fix sanitise redirect test for redirect after login --- cypress/integration/login.js | 17 +++++++++++++++++ frappe/public/js/frappe/utils/common.js | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/cypress/integration/login.js b/cypress/integration/login.js index 904b9e0721..b3227fa3ba 100644 --- a/cypress/integration/login.js +++ b/cypress/integration/login.js @@ -38,4 +38,21 @@ context('Login', () => { cy.location('pathname').should('eq', '/desk'); cy.window().its('frappe.session.user').should('eq', 'Administrator'); }); + + it('check redirect after login', () => { + const payload = new URLSearchParams({ + uuid: '6fed1519-cfd8-4a2d-84a6-9a1799c7c741', + encoded_string: 'hello all', + encoded_url: 'http://test.localhost/callback', + base64_string: 'aGVsbG8gYWxs' + }); + + cy.request('/api/method/logout'); + cy.visit('/login?redirect-to=/me?' + encodeURIComponent(payload.toString())); + cy.get('#login_email').type('Administrator'); + cy.get('#login_password').type(Cypress.config('adminPassword')); + + cy.get('.btn-login').click(); + cy.url().should('include', '/me?' + payload.toString()); + }); }); diff --git a/frappe/public/js/frappe/utils/common.js b/frappe/public/js/frappe/utils/common.js index 1cdabf23e0..9ff4ade761 100644 --- a/frappe/public/js/frappe/utils/common.js +++ b/frappe/public/js/frappe/utils/common.js @@ -276,7 +276,7 @@ frappe.utils.sanitise_redirect = (url) => { // check for base domain only if the url is absolute // return true for relative url (except protocol-relative urls) - return is_absolute(url) ? domain(location.href) !== domain(url) : true; + return is_absolute(url) ? domain(location.href) !== domain(url) : false; } })(); From a0d8b1a1b3e775b55d49c9b6d0429b75d8e1b781 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Thu, 14 May 2020 05:51:08 +0530 Subject: [PATCH 130/274] fix: add comments to login ui test --- cypress/integration/login.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cypress/integration/login.js b/cypress/integration/login.js index b3227fa3ba..861377444c 100644 --- a/cypress/integration/login.js +++ b/cypress/integration/login.js @@ -40,6 +40,8 @@ context('Login', () => { }); it('check redirect after login', () => { + + // mock for OAuth 2.0 client_id, redirect_uri, scope and state const payload = new URLSearchParams({ uuid: '6fed1519-cfd8-4a2d-84a6-9a1799c7c741', encoded_string: 'hello all', @@ -48,11 +50,19 @@ context('Login', () => { }); cy.request('/api/method/logout'); - cy.visit('/login?redirect-to=/me?' + encodeURIComponent(payload.toString())); + + // redirect-to /me page with params to mock OAuth 2.0 like request + cy.visit( + '/login?redirect-to=/me?' + + encodeURIComponent(payload.toString().replace("+", " ")) + ); + cy.get('#login_email').type('Administrator'); cy.get('#login_password').type(Cypress.config('adminPassword')); cy.get('.btn-login').click(); - cy.url().should('include', '/me?' + payload.toString()); + + // verify redirected location and url params after login + cy.url().should('include', '/me?' + payload.toString().replace('+', '%20')); }); }); From 7d0faa940ebcb5cbe58debbaec473aed5cfda7a4 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 14 May 2020 09:57:48 +0530 Subject: [PATCH 131/274] fix(minor): Custom Web Forms wont have templates --- frappe/www/list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/www/list.py b/frappe/www/list.py index c48cb40d4f..313505b729 100644 --- a/frappe/www/list.py +++ b/frappe/www/list.py @@ -168,7 +168,7 @@ def get_list_context(context, doctype, web_form_name=None): list_context = update_context_from_module(web_form.get_web_form_module(), list_context) # get path from '/templates/' folder of the doctype - if not list_context.row_template: + if not meta.custom and not list_context.row_template: list_context.row_template = meta.get_row_template() return list_context From d3f8a5bbb9fe49c22e363c76dac155fe30ba4f5a Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 14 May 2020 10:22:51 +0530 Subject: [PATCH 132/274] 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 133/274] 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 134/274] 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 135/274] 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 136/274] 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 137/274] 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 138/274] 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 139/274] 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 140/274] 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 141/274] 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 142/274] 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 143/274] 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 144/274] 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 145/274] 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 146/274] 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 147/274] 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 148/274] 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 149/274] 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 150/274] 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 151/274] 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 152/274] 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 153/274] 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 154/274] 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 155/274] 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 156/274] 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 157/274] 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 158/274] 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 159/274] 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 160/274] 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 161/274] 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 162/274] 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 163/274] 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 164/274] 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 165/274] 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 166/274] 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 167/274] 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 168/274] 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 169/274] 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 170/274] 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 171/274] 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 172/274] 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 173/274] 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 174/274] 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 175/274] 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 176/274] 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 177/274] 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 178/274] 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 179/274] 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 9d4a4ef6f4c1a92ec5f0918f4773cb605586cbc9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 15 May 2020 14:03:04 +0530 Subject: [PATCH 180/274] feat: collapse sections by default for page builder dialog --- frappe/public/js/frappe/utils/web_page_block.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/web_page_block.js b/frappe/public/js/frappe/utils/web_page_block.js index bcf821bcfa..acc7943671 100644 --- a/frappe/public/js/frappe/utils/web_page_block.js +++ b/frappe/public/js/frappe/utils/web_page_block.js @@ -4,7 +4,12 @@ frappe.ui.form.on('Web Page Block', { frappe.model.with_doc('Web Template', row.web_template).then(doc => { let d = new frappe.ui.Dialog({ title: __('Edit Values'), - fields: doc.fields, + fields: doc.fields.map(df => { + if (df.fieldtype == "Section Break") { + df.collapsible = 1; + } + return df + }), primary_action(values) { frappe.model.set_value( cdt, From a7b431465d127360f995f45acd6bb605b58e111f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 15 May 2020 14:03:37 +0530 Subject: [PATCH 181/274] feat: allow 9 cards in card grid --- .../section_with_cards.html | 2 +- .../section_with_cards.json | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/frappe/website/web_template/section_with_cards/section_with_cards.html b/frappe/website/web_template/section_with_cards/section_with_cards.html index 1f0f71a15b..431a64a86d 100644 --- a/frappe/website/web_template/section_with_cards/section_with_cards.html +++ b/frappe/website/web_template/section_with_cards/section_with_cards.html @@ -29,7 +29,7 @@ {%- set card_size = card_size or 'Small' -%}
- {%- for index in ['1', '2', '3', '4', '5', '6', '7', '8'] -%} + {%- for index in ['1', '2', '3', '4', '5', '6', '7', '8', '9'] -%} {%- set title = values['card_' + index + '_title'] -%} {%- set content = values['card_' + index + '_content'] -%} {%- set url = values['card_' + index + '_url'] -%} diff --git a/frappe/website/web_template/section_with_cards/section_with_cards.json b/frappe/website/web_template/section_with_cards/section_with_cards.json index 9ec430ae60..311cced829 100644 --- a/frappe/website/web_template/section_with_cards/section_with_cards.json +++ b/frappe/website/web_template/section_with_cards/section_with_cards.json @@ -213,10 +213,34 @@ "fieldtype": "Data", "label": "URL", "reqd": 0 + }, + { + "fieldname": "card_9", + "fieldtype": "Section Break", + "label": "Card 9", + "reqd": 0 + }, + { + "fieldname": "card_9_title", + "fieldtype": "Data", + "label": "Title", + "reqd": 0 + }, + { + "fieldname": "card_9_content", + "fieldtype": "Small Text", + "label": "Content", + "reqd": 0 + }, + { + "fieldname": "card_9_url", + "fieldtype": "Data", + "label": "URL", + "reqd": 0 } ], "idx": 0, - "modified": "2020-04-29 22:40:03.362229", + "modified": "2020-05-15 13:52:02.984001", "modified_by": "Administrator", "name": "Section with Cards", "owner": "Administrator", From 7721d540167d30f76308a2bc557afb29f021b33a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 15 May 2020 14:15:59 +0530 Subject: [PATCH 182/274] feat: add hide login to website settings --- frappe/templates/includes/navbar/navbar_login.html | 2 +- .../doctype/website_settings/website_settings.json | 9 ++++++++- .../website/doctype/website_settings/website_settings.py | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/frappe/templates/includes/navbar/navbar_login.html b/frappe/templates/includes/navbar/navbar_login.html index 2a58efe039..4e2c6dc93b 100644 --- a/frappe/templates/includes/navbar/navbar_login.html +++ b/frappe/templates/includes/navbar/navbar_login.html @@ -1,5 +1,5 @@ -{% if not only_static %} +{% if not only_static and not hide_login %} {% if frappe.session.user != 'Guest' %}